forked from UKSOURCE/cms.hailearning.edu.vn
438 lines
22 KiB
Plaintext
438 lines
22 KiB
Plaintext
<div class="container">
|
|
<div class="d-flex justify-content-between align-items-center mt-4 mb-4">
|
|
<div>
|
|
<h1 class="h3 mb-0" style="color: var(--primary-dark);">
|
|
<%= title %>
|
|
</h1>
|
|
<p class="text-muted mb-0">Manage testimonials section</p>
|
|
</div>
|
|
<div>
|
|
<a href="<%= frontendUrl %>/" class="btn btn-outline-primary" target="_blank">
|
|
<i class="fas fa-external-link-alt me-2"></i>View Homepage
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row">
|
|
<div class="col-12">
|
|
<form method="POST" class="content-with-fixed-buttons" id="testimonialForm"
|
|
action=" /update">
|
|
<!-- Hidden input for items JSON data -->
|
|
<input type="hidden" name="items" id="itemsJson">
|
|
|
|
<div class="card shadow-sm border-0 mb-4">
|
|
<div class="card-header bg-white border-bottom">
|
|
<h5 class="mb-0"><i class="fas fa-quote-left me-2"></i>Testimonials Settings</h5>
|
|
</div>
|
|
|
|
<div class="card-body">
|
|
<!-- Header Section -->
|
|
<div class="row g-3 mb-4">
|
|
<div class="col-md-6">
|
|
<label class="form-label fw-medium">Heading</label>
|
|
<input type="text" class="form-control" name="heading" id="heading"
|
|
value="<%= data.heading || '' %>">
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label class="form-label fw-medium">Subheading</label>
|
|
<input type="text" class="form-control" name="subheading" id="subheading"
|
|
value="<%= data.subheading || '' %>">
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Video Section -->
|
|
<div class="row g-3 mb-4">
|
|
<div class="col-md-6">
|
|
<label class="form-label fw-medium">Video URL</label>
|
|
<input type="text" class="form-control" name="videoUrl" id="videoUrl"
|
|
value="<%= data.videoUrl || '' %>"
|
|
placeholder="https://www.youtube.com/watch?v=...">
|
|
<small class="text-muted">YouTube video URL</small>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label class="form-label fw-medium">Video Thumbnail</label>
|
|
<div class="input-group">
|
|
<input type="text" class="form-control" name="videoThumbnail" id="videoThumbnail"
|
|
value="<%= data.videoThumbnail || '' %>">
|
|
<button type="button" class="btn btn-outline-primary btn-upload-image"
|
|
data-target-input="videoThumbnail" data-image-type="testimonial">
|
|
<i class="fas fa-upload me-1"></i>Upload
|
|
</button>
|
|
</div>
|
|
<% if (data.videoThumbnail) { %>
|
|
<img src="<%= data.videoThumbnail %>" class="img-thumbnail mt-2"
|
|
style="max-height: 100px;" alt="Thumbnail preview">
|
|
<% } %>
|
|
</div>
|
|
</div>
|
|
|
|
<hr class="my-4">
|
|
|
|
<!-- Testimonial Items -->
|
|
<div class="d-flex justify-content-between align-items-center mb-3">
|
|
<h6 class="fw-medium mb-0">Testimonial Items</h6>
|
|
<button type="button" class="btn btn-primary btn-sm" onclick="addTestimonialItem()">
|
|
<i class="fas fa-plus"></i> Add Testimonial
|
|
</button>
|
|
</div>
|
|
|
|
<div id="testimonialItemsContainer">
|
|
<% if (data.items && data.items.length> 0) { %>
|
|
<% data.items.forEach((item, index)=> { %>
|
|
<div class="card mb-3 testimonial-item">
|
|
<div class="card-body">
|
|
<div class="row g-3">
|
|
<div class="col-md-3">
|
|
<label class="form-label">Name</label>
|
|
<input type="text" class="form-control" name="itemName_<%= index %>"
|
|
value="<%= item.name || '' %>">
|
|
</div>
|
|
<div class="col-md-3">
|
|
<label class="form-label">Role</label>
|
|
<input type="text" class="form-control" name="itemRole_<%= index %>"
|
|
value="<%= item.role || '' %>">
|
|
</div>
|
|
<div class="col-md-3">
|
|
<label class="form-label">Country</label>
|
|
<input type="text" class="form-control"
|
|
name="itemCountry_<%= index %>"
|
|
value="<%= item.country || '' %>">
|
|
</div>
|
|
<div class="col-md-3">
|
|
<label class="form-label">Rating (1-5)</label>
|
|
<select class="form-select" name="itemRating_<%= index %>">
|
|
<% for (let i=1; i <=5; i++) { %>
|
|
<option value="<%= i %>" <%=item.rating===i ? 'selected'
|
|
: '' %>>
|
|
<%= i %> Star<%= i> 1 ? 's' : '' %>
|
|
</option>
|
|
<% } %>
|
|
</select>
|
|
</div>
|
|
<div class="col-md-8">
|
|
<label class="form-label">Comment</label>
|
|
<textarea class="form-control" name="itemComment_<%= index %>"
|
|
rows="3"><%= item.comment || '' %></textarea>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<label class="form-label">Avatar</label>
|
|
<div class="input-group">
|
|
<input type="text" class="form-control"
|
|
name="itemAvatar_<%= index %>"
|
|
value="<%= item.avatar || '' %>">
|
|
<button type="button"
|
|
class="btn btn-outline-primary btn-upload-image"
|
|
data-target-input="itemAvatar_<%= index %>"
|
|
data-image-type="testimonial">
|
|
<i class="fas fa-upload me-1"></i>Upload
|
|
</button>
|
|
</div>
|
|
<% if (item.avatar) { %>
|
|
<img src="<%= item.avatar %>"
|
|
class="img-thumbnail mt-2 avatar-preview"
|
|
style="max-height: 60px; max-width: 60px; border-radius: 50%;"
|
|
alt="Avatar">
|
|
<% } %>
|
|
</div>
|
|
</div>
|
|
<button type="button" class="btn btn-outline-danger btn-sm mt-3"
|
|
onclick="removeTestimonialItem(this)">
|
|
<i class="fas fa-trash me-2"></i>Remove
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<% }); %>
|
|
<% } %>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Fixed Bottom Buttons -->
|
|
<div class="fixed-bottom-buttons">
|
|
<button type="reset" class="btn btn-secondary" onclick="resetForm()">
|
|
<i class="fas fa-undo me-2"></i>Reset
|
|
</button>
|
|
<button type="submit" class="btn btn-primary" id="submitBtn">
|
|
<i class="fas fa-save me-2"></i>Save Changes
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script type="application/json" id="testimonialDataJson"><%- JSON.stringify(data) %></script>
|
|
|
|
<script>
|
|
let originalFormData = null;
|
|
|
|
document.addEventListener('DOMContentLoaded', function () {
|
|
try {
|
|
var jsonScript = document.getElementById('testimonialDataJson');
|
|
originalFormData = JSON.parse(jsonScript.textContent);
|
|
} catch (e) {
|
|
console.error('Error parsing originalFormData:', e);
|
|
originalFormData = { items: [] };
|
|
}
|
|
|
|
initializeFormHandlers();
|
|
});
|
|
|
|
function initializeFormHandlers() {
|
|
const form = document.getElementById('testimonialForm');
|
|
form.addEventListener('submit', async function (e) {
|
|
e.preventDefault();
|
|
const submitBtn = document.getElementById('submitBtn');
|
|
submitBtn.disabled = true;
|
|
submitBtn.innerHTML = '<i class="fas fa-spinner fa-spin me-2"></i>Saving...';
|
|
|
|
try {
|
|
updateItemsJson();
|
|
this.submit();
|
|
} catch (error) {
|
|
console.error('Error updating data:', error);
|
|
alert('Failed to process form data. Please try again.');
|
|
submitBtn.disabled = false;
|
|
submitBtn.innerHTML = '<i class="fas fa-save me-2"></i>Save Changes';
|
|
}
|
|
});
|
|
|
|
document.querySelectorAll('.btn-upload-image').forEach(button => {
|
|
button.addEventListener('click', function () {
|
|
const targetInput = this.dataset.targetInput;
|
|
const imageType = this.dataset.imageType;
|
|
openImageUploader(targetInput, imageType);
|
|
});
|
|
});
|
|
}
|
|
|
|
function updateItemsJson() {
|
|
const items = [];
|
|
document.querySelectorAll('.testimonial-item').forEach((item, index) => {
|
|
const name = item.querySelector(`[name="itemName_${index}"]`)?.value ||
|
|
item.querySelector('[name^="itemName_"]')?.value || '';
|
|
const role = item.querySelector(`[name="itemRole_${index}"]`)?.value ||
|
|
item.querySelector('[name^="itemRole_"]')?.value || '';
|
|
const country = item.querySelector(`[name="itemCountry_${index}"]`)?.value ||
|
|
item.querySelector('[name^="itemCountry_"]')?.value || '';
|
|
const rating = parseInt(item.querySelector(`[name="itemRating_${index}"]`)?.value ||
|
|
item.querySelector('[name^="itemRating_"]')?.value || '5');
|
|
const comment = item.querySelector(`[name="itemComment_${index}"]`)?.value ||
|
|
item.querySelector('[name^="itemComment_"]')?.value || '';
|
|
const avatar = item.querySelector(`[name="itemAvatar_${index}"]`)?.value ||
|
|
item.querySelector('[name^="itemAvatar_"]')?.value || '';
|
|
|
|
items.push({ name, role, country, rating, comment, avatar });
|
|
});
|
|
|
|
document.getElementById('itemsJson').value = JSON.stringify(items);
|
|
}
|
|
|
|
function addTestimonialItem() {
|
|
const container = document.getElementById('testimonialItemsContainer');
|
|
const index = container.querySelectorAll('.testimonial-item').length;
|
|
|
|
const html = `
|
|
<div class="card mb-3 testimonial-item">
|
|
<div class="card-body">
|
|
<div class="row g-3">
|
|
<div class="col-md-3">
|
|
<label class="form-label">Name</label>
|
|
<input type="text" class="form-control" name="itemName_${index}" value="">
|
|
</div>
|
|
<div class="col-md-3">
|
|
<label class="form-label">Role</label>
|
|
<input type="text" class="form-control" name="itemRole_${index}" value="">
|
|
</div>
|
|
<div class="col-md-3">
|
|
<label class="form-label">Country</label>
|
|
<input type="text" class="form-control" name="itemCountry_${index}" value="">
|
|
</div>
|
|
<div class="col-md-3">
|
|
<label class="form-label">Rating (1-5)</label>
|
|
<select class="form-select" name="itemRating_${index}">
|
|
<option value="1">1 Star</option>
|
|
<option value="2">2 Stars</option>
|
|
<option value="3">3 Stars</option>
|
|
<option value="4">4 Stars</option>
|
|
<option value="5" selected>5 Stars</option>
|
|
</select>
|
|
</div>
|
|
<div class="col-md-8">
|
|
<label class="form-label">Comment</label>
|
|
<textarea class="form-control" name="itemComment_${index}" rows="3"></textarea>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<label class="form-label">Avatar</label>
|
|
<div class="input-group">
|
|
<input type="text" class="form-control" name="itemAvatar_${index}" value="">
|
|
<button type="button" class="btn btn-outline-primary btn-upload-image"
|
|
data-target-input="itemAvatar_${index}" data-image-type="testimonial">
|
|
<i class="fas fa-upload me-1"></i>Upload
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<button type="button" class="btn btn-outline-danger btn-sm mt-3"
|
|
onclick="removeTestimonialItem(this)">
|
|
<i class="fas fa-trash me-2"></i>Remove
|
|
</button>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
container.insertAdjacentHTML('beforeend', html);
|
|
|
|
// Re-attach upload handlers for new item
|
|
container.querySelector('.testimonial-item:last-child .btn-upload-image').addEventListener('click', function () {
|
|
const targetInput = this.dataset.targetInput;
|
|
const imageType = this.dataset.imageType;
|
|
openImageUploader(targetInput, imageType);
|
|
});
|
|
}
|
|
|
|
function removeTestimonialItem(button) {
|
|
if (confirm('Are you sure you want to remove this testimonial?')) {
|
|
button.closest('.testimonial-item').remove();
|
|
reindexItems();
|
|
}
|
|
}
|
|
|
|
function reindexItems() {
|
|
document.querySelectorAll('.testimonial-item').forEach((item, index) => {
|
|
item.querySelectorAll('[name^="item"]').forEach(input => {
|
|
const baseName = input.name.replace(/_\d+$/, '');
|
|
input.name = `${baseName}_${index}`;
|
|
});
|
|
});
|
|
}
|
|
|
|
function resetForm() {
|
|
if (confirm('Are you sure you want to reset all changes?')) {
|
|
location.reload();
|
|
}
|
|
}
|
|
|
|
function openImageUploader(targetInput, imageType) {
|
|
const fileInput = document.createElement('input');
|
|
fileInput.type = 'file';
|
|
fileInput.accept = 'image/*';
|
|
fileInput.style.display = 'none';
|
|
document.body.appendChild(fileInput);
|
|
|
|
fileInput.onchange = async function (e) {
|
|
const file = e.target.files[0];
|
|
if (!file) return;
|
|
|
|
try {
|
|
const formData = new FormData();
|
|
formData.append('image', file);
|
|
|
|
const uploadBtn = document.querySelector(`[data-target-input="${targetInput}"]`);
|
|
const originalBtnHtml = uploadBtn ? uploadBtn.innerHTML : 'Upload';
|
|
if (uploadBtn) {
|
|
uploadBtn.disabled = true;
|
|
uploadBtn.innerHTML = '<i class="fas fa-spinner fa-spin me-1"></i>Uploading...';
|
|
}
|
|
|
|
const response = await fetch(`/admin/upload/image?imageType=${imageType}`, {
|
|
method: 'POST',
|
|
body: formData
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error('Upload failed');
|
|
}
|
|
|
|
const result = await response.json();
|
|
|
|
if (!result.success) {
|
|
throw new Error(result.error || 'Upload failed');
|
|
}
|
|
|
|
const input = document.getElementById(targetInput) || document.querySelector(`[name="${targetInput}"]`);
|
|
if (!input) {
|
|
throw new Error('Target input not found');
|
|
}
|
|
|
|
input.value = result.path;
|
|
|
|
// Update preview if exists
|
|
const previewUrl = (result.path && (result.path.startsWith('http://') || result.path.startsWith('https://')))
|
|
? result.path
|
|
: (window.location.origin + result.path);
|
|
|
|
const parent = input.closest('.col-md-4') || input.closest('div');
|
|
let previewImg = parent.querySelector('.avatar-preview, .img-thumbnail');
|
|
|
|
if (previewImg) {
|
|
previewImg.src = previewUrl;
|
|
} else {
|
|
const img = document.createElement('img');
|
|
img.src = previewUrl;
|
|
img.className = 'img-thumbnail mt-2';
|
|
img.style.maxHeight = '60px';
|
|
img.alt = 'Preview';
|
|
input.closest('.input-group').after(img);
|
|
}
|
|
|
|
if (uploadBtn) {
|
|
uploadBtn.disabled = false;
|
|
uploadBtn.innerHTML = originalBtnHtml;
|
|
}
|
|
|
|
showToast('Success', 'Image uploaded successfully', 'success');
|
|
|
|
} catch (error) {
|
|
console.error('Upload error:', error);
|
|
showToast('Error', 'Failed to upload image: ' + error.message, 'error');
|
|
|
|
const uploadBtn = document.querySelector(`[data-target-input="${targetInput}"]`);
|
|
if (uploadBtn) {
|
|
uploadBtn.disabled = false;
|
|
uploadBtn.innerHTML = '<i class="fas fa-upload me-1"></i>Upload';
|
|
}
|
|
} finally {
|
|
document.body.removeChild(fileInput);
|
|
}
|
|
};
|
|
|
|
fileInput.click();
|
|
}
|
|
|
|
function showToast(title, message, type = 'info') {
|
|
const toast = document.createElement('div');
|
|
toast.className = `toast align-items-center text-white bg-${type === 'error' ? 'danger' : type} border-0`;
|
|
toast.setAttribute('role', 'alert');
|
|
toast.setAttribute('aria-live', 'assertive');
|
|
toast.setAttribute('aria-atomic', 'true');
|
|
|
|
toast.innerHTML = `
|
|
<div class="d-flex">
|
|
<div class="toast-body">
|
|
<strong>${title}:</strong> ${message}
|
|
</div>
|
|
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast" aria-label="Close"></button>
|
|
</div>
|
|
`;
|
|
|
|
let container = document.querySelector('.toast-container');
|
|
if (!container) {
|
|
container = document.createElement('div');
|
|
container.className = 'toast-container position-fixed top-0 end-0 p-3';
|
|
document.body.appendChild(container);
|
|
}
|
|
container.appendChild(toast);
|
|
|
|
const bsToast = new bootstrap.Toast(toast, {
|
|
animation: true,
|
|
autohide: true,
|
|
delay: 3000
|
|
});
|
|
bsToast.show();
|
|
|
|
toast.addEventListener('hidden.bs.toast', () => {
|
|
toast.remove();
|
|
});
|
|
}
|
|
</script> |