Files
r2xrzh9q2z-lab d1b931d547 first commit
2026-02-02 11:07:09 +07:00

662 lines
26 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);">Form Management</h1>
<p class="text-muted mb-0">Edit form content for Academics page</p>
</div>
<div>
<a href="/admin/academics" class="btn btn-outline-primary">
<i class="fas fa-arrow-left me-1"></i>Back to Academics
</a>
</div>
</div>
<div class="row">
<div class="col-12">
<form id="formManagementForm" action="/admin/form/update" method="POST" class="content-with-fixed-buttons">
<input type="hidden" name="admissionJson" id="admissionJson">
<input type="hidden" name="applyJson" id="applyJson">
<input type="hidden" name="applicationFormJson" id="applicationFormJson">
<!-- Navigation Tabs -->
<div class="card shadow-sm border-0 mb-4">
<div class="card-header bg-white border-bottom">
<ul class="nav nav-tabs card-header-tabs" role="tablist">
<li class="nav-item">
<a class="nav-link active" data-bs-toggle="tab" href="#admission" role="tab">
<i class="fas fa-door-open me-2"></i>Admission
</a>
</li>
<li class="nav-item">
<a class="nav-link" data-bs-toggle="tab" href="#apply" role="tab">
<i class="fas fa-check-circle me-2"></i>Apply
</a>
</li>
<li class="nav-item">
<a class="nav-link" data-bs-toggle="tab" href="#application-form" role="tab">
<i class="fas fa-file-alt me-2"></i>Application Form
</a>
</li>
</ul>
</div>
<div class="card-body">
<div class="tab-content">
<!-- Admission Tab -->
<div class="tab-pane fade show active" id="admission" role="tabpanel">
<div class="row g-3">
<div class="col-md-12">
<label class="form-label fw-medium">Background Image</label>
<div class="row">
<div class="col-md-6">
<div class="input-group">
<input type="text" class="form-control" id="admissionBgImage" name="admissionBgImage" value="<%= form.admission && form.admission.background_image ? form.admission.background_image : '' %>">
<button type="button" class="btn btn-outline-primary btn-upload-image" data-target-input="admissionBgImage" data-image-type="academics">
<i class="fas fa-upload me-1"></i>Upload
</button>
</div>
</div>
<div class="col-md-6">
<div class="image-preview mt-2">
<% if (form.admission && form.admission.background_image) { %>
<img src="<%= form.admission.background_image %>" alt="Background preview" class="img-thumbnail" style="height:200px;width:100%;object-fit:cover;">
<% } %>
</div>
</div>
</div>
</div>
<div class="col-md-6">
<label class="form-label fw-medium">Title</label>
<input type="text" class="form-control" id="admissionTitle" name="admissionTitle" value="<%= form.admission && form.admission.title ? form.admission.title : '' %>">
</div>
<div class="col-md-6">
<label class="form-label fw-medium">Year</label>
<input type="text" class="form-control" id="admissionYear" name="admissionYear" value="<%= form.admission && form.admission.year ? form.admission.year : '' %>">
</div>
<div class="col-md-12">
<label class="form-label fw-medium">Description</label>
<textarea class="form-control" id="admissionDescription" name="admissionDescription" rows="3"><%= form.admission && form.admission.description ? form.admission.description : '' %></textarea>
</div>
<!-- Admission Form Fields -->
<div class="col-md-12">
<div class="card">
<div class="card-header">
<h5 class="card-title mb-0">Form Fields</h5>
</div>
<div class="card-body">
<div id="admissionFormFields">
<% (form.admission && form.admission.form && form.admission.form.fields || []).forEach((field, index) => { %>
<div class="mb-3 border p-3">
<div class="row g-2">
<div class="col-md-6">
<label class="form-label">Field Type</label>
<input type="text" class="form-control" value="<%= field.type %>">
</div>
<div class="col-md-6">
<label class="form-label">Placeholder</label>
<input type="text" class="form-control" value="<%= field.placeholder %>">
</div>
</div>
</div>
<% }); %>
</div>
</div>
</div>
</div>
<div class="col-md-6">
<label class="form-label fw-medium">Form Button Text</label>
<input type="text" class="form-control" id="admissionFormButton" name="admissionFormButton" value="<%= form.admission && form.admission.form && form.admission.form.button ? form.admission.form.button.text : '' %>">
</div>
<div class="col-md-6">
<label class="form-label fw-medium">Form Button URL</label>
<input type="text" class="form-control" id="admissionFormButtonUrl" name="admissionFormButtonUrl" value="<%= form.admission && form.admission.form && form.admission.form.button ? form.admission.form.button.url : '' %>">
</div>
</div>
</div>
<!-- Apply Tab -->
<div class="tab-pane fade" id="apply" role="tabpanel">
<div class="row g-3">
<div class="col-md-12">
<label class="form-label fw-medium">Title</label>
<input type="text" class="form-control" id="applyTitle" name="applyTitle" value="<%= form.apply && form.apply.title ? form.apply.title : '' %>">
</div>
<!-- Apply Steps -->
<div class="col-md-12">
<div class="card">
<div class="card-header">
<h5 class="card-title mb-0">Application Steps</h5>
</div>
<div class="card-body">
<div id="applySteps">
<% (form.apply && form.apply.steps || []).forEach((step, index) => { %>
<div class="mb-3 border p-3">
<div class="row g-2">
<div class="col-md-6">
<label class="form-label">Title</label>
<input type="text" class="form-control" name="applyStepTitle_<%= index %>" value="<%= step.title %>">
</div>
<div class="col-md-12">
<label class="form-label">Description</label>
<textarea class="form-control" name="applyStepDescription_<%= index %>" rows="2"><%= step.description %></textarea>
</div>
</div>
</div>
<% }); %>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Application Form Tab -->
<div class="tab-pane fade" id="application-form" role="tabpanel">
<div class="row g-3">
<div class="col-md-12">
<label class="form-label fw-medium">Title</label>
<input type="text" class="form-control" id="applicationFormTitle" name="applicationFormTitle" value="<%= form.application_form && form.application_form.title ? form.application_form.title : '' %>">
</div>
<div class="col-md-12">
<label class="form-label fw-medium">Question</label>
<input type="text" class="form-control" id="applicationFormQuestion" name="applicationFormQuestion" value="<%= form.application_form && form.application_form.question ? form.application_form.question : '' %>">
</div>
<!-- Button Section -->
<div class="col-md-12">
<div class="card">
<div class="card-header">
<h5 class="card-title mb-0">Button</h5>
</div>
<div class="card-body">
<div class="row g-2">
<div class="col-md-4">
<label class="form-label">Text</label>
<input type="text" class="form-control" id="applicationFormButtonText" name="applicationFormButtonText" value="<%= form.application_form && form.application_form.button ? form.application_form.button.text : '' %>">
</div>
<div class="col-md-4">
<label class="form-label">Icon</label>
<input type="text" class="form-control" id="applicationFormButtonIcon" name="applicationFormButtonIcon" value="<%= form.application_form && form.application_form.button ? form.application_form.button.icon : '' %>">
</div>
<div class="col-md-4">
<label class="form-label">URL</label>
<input type="text" class="form-control" id="applicationFormButtonUrl" name="applicationFormButtonUrl" value="<%= form.application_form && form.application_form.button ? form.application_form.button.url : '' %>">
</div>
</div>
</div>
</div>
</div>
<!-- Links Section -->
<div class="col-md-12">
<div class="card">
<div class="card-header">
<h5 class="card-title mb-0">Links</h5>
</div>
<div class="card-body">
<div id="applicationFormLinks">
<% (form.application_form && form.application_form.links || []).forEach((link, index) => { %>
<div class="mb-3 border p-3">
<div class="row g-2">
<div class="col-md-6">
<label class="form-label">Text</label>
<input type="text" class="form-control" name="applicationFormLinkText_<%= index %>" value="<%= link.text %>">
</div>
<div class="col-md-6">
<label class="form-label">URL</label>
<input type="text" class="form-control" name="applicationFormLinkUrl_<%= index %>" value="<%= link.url %>">
</div>
</div>
</div>
<% }); %>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Fixed Bottom Buttons -->
<div class="fixed-bottom-buttons">
<button type="reset" class="btn btn-secondary">
<i class="fas fa-undo"></i>
<span>Reset</span>
</button>
<button type="submit" class="btn btn-primary">
<i class="fas fa-save"></i>
<span>Save Changes</span>
</button>
</div>
</form>
</div>
</div>
</div>
<!-- Thêm input file ẩn cho upload ảnh -->
<input type="file" id="directImageUpload" style="display: none;">
<input type="hidden" id="currentImageType" name="imageType">
<input type="hidden" id="currentTargetInput" name="targetInput">
<script>
let originalFormData = null;
document.addEventListener('DOMContentLoaded', function() {
// Lưu trữ dữ liệu gốc để so sánh thay đổi
originalFormData = <%- JSON.stringify(form) %>;
// Khởi tạo form handlers
initializeFormHandlers();
});
function initializeFormHandlers() {
// Form submission
const form = document.querySelector('form');
form.addEventListener('submit', handleFormSubmit);
// Upload ảnh
document.querySelectorAll('.btn-upload-image').forEach(button => {
button.addEventListener('click', function() {
const targetInput = this.dataset.targetInput;
const imageType = this.dataset.imageType || 'academics';
openImageUploader(targetInput, imageType);
});
});
// Khởi tạo direct image upload
initializeDirectImageUpload();
}
function initializeDirectImageUpload() {
const fileInput = document.getElementById('directImageUpload');
fileInput.addEventListener('change', handleDirectImageUpload);
}
function openImageUploader(targetInput, imageType) {
// Lưu thông tin upload hiện tại
document.getElementById('currentImageType').value = imageType;
document.getElementById('currentTargetInput').value = targetInput;
// Kích hoạt input file
const fileInput = document.getElementById('directImageUpload');
fileInput.click();
}
async function handleDirectImageUpload(e) {
if (!this.files || !this.files[0]) return;
const file = this.files[0];
const imageType = document.getElementById('currentImageType').value;
const targetInput = document.getElementById('currentTargetInput').value;
try {
// Disable nút upload và hiển thị loading
const uploadButton = document.querySelector(`[data-target-input="${targetInput}"]`);
if (uploadButton) {
uploadButton.disabled = true;
uploadButton.innerHTML = '<i class="fas fa-spinner fa-spin me-1"></i>Uploading...';
}
// Tạo form data
const formData = new FormData();
formData.append('image', file);
// Upload ảnh
const response = await fetch(`/admin/upload/image?imageType=${imageType}`, {
method: 'POST',
body: formData
});
const result = await response.json();
if (result.success && result.path) {
// Tìm input cần cập nhật, ưu tiên theo name, nếu không có thì tìm theo id
const inputElement = document.querySelector(`[name="${targetInput}"]`) || document.getElementById(targetInput);
if (inputElement) {
inputElement.value = result.path;
// Cập nhật preview
updateImagePreview(inputElement, result.path);
}
showToast('Success', 'Image uploaded successfully', 'success');
} else {
throw new Error(result.error || 'Upload failed');
}
} catch (error) {
console.error('Upload error:', error);
showToast('Error', 'Failed to upload image: ' + error.message, 'error');
} finally {
// Reset nút upload
const uploadButton = document.querySelector(`[data-target-input="${targetInput}"]`);
if (uploadButton) {
uploadButton.disabled = false;
uploadButton.innerHTML = '<i class="fas fa-upload me-1"></i>Upload';
}
// Reset file input
this.value = '';
}
}
function updateImagePreview(inputElement, imagePath) {
// Tìm hoặc tạo phần tử preview theo cách giống như Partnerships
let imgPreview = inputElement.parentElement.nextElementSibling;
while (imgPreview && !imgPreview.classList.contains('mt-2')) {
imgPreview = imgPreview.nextElementSibling;
}
if (!imgPreview) {
// Tạo mới phần tử preview nếu chưa có
imgPreview = document.createElement('div');
imgPreview.className = 'mt-2';
const img = document.createElement('img');
img.className = 'img-thumbnail';
// Set style dựa vào loại ảnh
const targetInput = inputElement.name || inputElement.id;
if (targetInput.toLowerCase().includes('logo')) {
img.style.height = '100px';
img.style.objectFit = 'contain';
} else {
img.style.height = '200px';
img.style.width = '100%';
img.style.objectFit = 'cover';
}
img.alt = 'Image preview';
imgPreview.appendChild(img);
inputElement.parentElement.parentElement.appendChild(imgPreview);
}
// Cập nhật đường dẫn hình ảnh
const img = imgPreview.querySelector('img');
if (img) {
img.src = imagePath;
}
}
function handleFormSubmit(e) {
e.preventDefault();
try {
console.log('=== Form Submit Debug ===');
// Chuẩn bị dữ liệu form
const formData = prepareFormData();
console.log('Complete form data:', formData);
// Log từng section
Object.keys(formData).forEach(key => {
console.log(`${key} data:`, formData[key]);
});
// Validate dữ liệu
const errors = validateFormData(formData);
if (errors.length > 0) {
console.log('Validation errors:', errors);
showToast('Error', errors.join('. '), 'error');
return;
}
// Log hidden inputs trước khi cập nhật
Object.keys(formData).forEach(key => {
const input = document.getElementById(`${key}Json`);
console.log(`Hidden input ${key}Json:`, {
element: input,
value: input?.value
});
});
// Cập nhật hidden inputs (map key -> input id)
const keyToInputId = {
admission: 'admissionJson',
apply: 'applyJson',
application_form: 'applicationFormJson'
};
Object.keys(formData).forEach(key => {
const inputId = keyToInputId[key] || `${key}Json`;
const input = document.getElementById(inputId);
if (input) {
const jsonValue = JSON.stringify(formData[key]);
input.value = jsonValue;
console.log(`Updated ${inputId} with:`, jsonValue);
} else {
console.warn(`Hidden input not found: ${inputId}`);
}
});
// Thông báo đang lưu
showToast('Info', 'Saving changes...', 'info');
// Log form final state
console.log('Form final state:', {
admissionJson: document.getElementById('admissionJson')?.value,
applyJson: document.getElementById('applyJson')?.value,
applicationFormJson: document.getElementById('applicationFormJson')?.value
});
// Submit form
this.submit();
} catch (error) {
console.error('Form submission error:', error);
console.error('Error stack:', error.stack);
showToast('Error', 'Error processing form: ' + error.message, 'error');
}
}
function prepareFormData() {
return {
admission: prepareAdmissionData(),
apply: prepareApplyData(),
application_form: prepareApplicationFormData()
};
}
function prepareAdmissionData() {
try {
// Basic info
const data = {
background_image: document.getElementById('admissionBgImage')?.value || '',
title: document.getElementById('admissionTitle')?.value || '',
year: document.getElementById('admissionYear')?.value || '',
description: document.getElementById('admissionDescription')?.value || '',
form: {
fields: [],
button: {
text: document.getElementById('admissionFormButton')?.value || '',
url: document.getElementById('admissionFormButtonUrl')?.value || '#'
}
}
};
// Form fields
const formFields = document.querySelectorAll('#admissionFormFields > div');
if (formFields) {
data.form.fields = Array.from(formFields).map(field => {
const inputs = field.querySelectorAll('input');
return {
type: inputs[0]?.value || '',
placeholder: inputs[1]?.value || ''
};
}).filter(field => field.type || field.placeholder);
}
return data;
} catch (error) {
console.error('Error preparing admission data:', error);
return {
background_image: '',
title: '',
year: '',
description: '',
form: {
fields: [],
button: {
text: '',
url: '#'
}
}
};
}
}
function prepareApplyData() {
try {
const data = {
title: document.getElementById('applyTitle')?.value || '',
steps: []
};
// Steps
const steps = document.querySelectorAll('#applySteps > div');
if (steps) {
data.steps = Array.from(steps).map(step => ({
title: step.querySelector('input[name^="applyStepTitle_"]')?.value || '',
description: step.querySelector('textarea[name^="applyStepDescription_"]')?.value || ''
})).filter(step => step.title || step.description);
}
return data;
} catch (error) {
console.error('Error preparing apply data:', error);
return {
title: '',
steps: []
};
}
}
function prepareApplicationFormData() {
try {
console.log('=== Debug Application Form Data ===');
// Log các elements
console.log('Title element:', document.getElementById('applicationFormTitle'));
console.log('Question element:', document.getElementById('applicationFormQuestion'));
console.log('Button text element:', document.getElementById('applicationFormButtonText'));
console.log('Button icon element:', document.getElementById('applicationFormButtonIcon'));
const data = {
title: document.getElementById('applicationFormTitle')?.value || '',
question: document.getElementById('applicationFormQuestion')?.value || '',
button: {
text: document.getElementById('applicationFormButtonText')?.value || '',
icon: document.getElementById('applicationFormButtonIcon')?.value || '',
url: document.getElementById('applicationFormButtonUrl')?.value || '#'
},
links: []
};
// Log giá trị cơ bản
console.log('Basic data:', {
title: data.title,
question: data.question,
button: data.button
});
// Debug links
const linksContainer = document.getElementById('applicationFormLinks');
console.log('Links container:', linksContainer);
const linkDivs = document.querySelectorAll('#applicationFormLinks > div');
console.log('Found link divs:', linkDivs.length);
if (linkDivs.length > 0) {
data.links = Array.from(linkDivs).map((link, index) => {
const textInput = link.querySelector('input[name^="applicationFormLinkText_"]');
const urlInput = link.querySelector('input[name^="applicationFormLinkUrl_"]');
return {
text: textInput?.value || '',
url: urlInput?.value || ''
};
}).filter(link => link.text || link.url);
}
console.log('Final application form data:', data);
return data;
} catch (error) {
console.error('Error preparing application form data:', error);
console.error('Error stack:', error.stack);
return {
title: '',
question: '',
button: {
text: '',
icon: '',
url: '#'
},
links: []
};
}
}
function validateFormData(data) {
const errors = [];
// Validate admission
if (!data.admission.title) errors.push('Admission title is required');
if (!data.admission.year) errors.push('Admission year is required');
// Validate apply
if (!data.apply.title) errors.push('Apply title is required');
if (!Array.isArray(data.apply.steps)) errors.push('Apply steps must be an array');
// Validate application form
if (!data.application_form.title) errors.push('Application form title is required');
if (!data.application_form.question) errors.push('Application form question is required');
return errors;
}
function showToast(title, message, type = 'info') {
if (window.toastManager) {
window.toastManager[type](message);
return;
}
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>
`;
// Add toast to container
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);
// Show toast
const bsToast = new bootstrap.Toast(toast, {
animation: true,
autohide: true,
delay: 3000
});
bsToast.show();
// Remove toast after hidden
toast.addEventListener('hidden.bs.toast', () => {
toast.remove();
});
}
</script>