forked from UKSOURCE/cms.lams
691 lines
27 KiB
Plaintext
691 lines
27 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)">
|
||
Header Management
|
||
</h1>
|
||
<p class="text-muted mb-0">Edit header content and menu structure</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="row">
|
||
<div class="col-12">
|
||
<div class="content-with-fixed-buttons">
|
||
<!-- Hidden inputs for JSON data -->
|
||
<input type="hidden" name="logo" id="logoInput" />
|
||
<input type="hidden" name="activeTab" id="activeTabInput" value="logo" />
|
||
|
||
<!-- 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 <%= activeTab === 'logo' ? 'active' : '' %>"
|
||
data-bs-toggle="tab"
|
||
href="#logo"
|
||
role="tab"
|
||
>
|
||
<i class="fas fa-image me-2"></i>Logo
|
||
</a>
|
||
</li>
|
||
<li class="nav-item">
|
||
<a
|
||
class="nav-link <%= activeTab === 'buttons' ? 'active' : '' %>"
|
||
data-bs-toggle="tab"
|
||
href="#buttons"
|
||
role="tab"
|
||
>
|
||
<i class="fas fa-mouse-pointer me-2"></i>Buttons
|
||
</a>
|
||
</li>
|
||
<li class="nav-item">
|
||
<a
|
||
class="nav-link <%= activeTab === 'menu' ? 'active' : '' %>"
|
||
data-bs-toggle="tab"
|
||
href="#menu"
|
||
role="tab"
|
||
>
|
||
<i class="fas fa-sitemap me-2"></i>Menu Structure
|
||
</a>
|
||
</li>
|
||
</ul>
|
||
</div>
|
||
|
||
<div class="card-body">
|
||
<div class="tab-content">
|
||
<!-- Logo Tab -->
|
||
<div class="tab-pane fade <%= activeTab === 'logo' ? 'show active' : '' %>" id="logo" role="tabpanel">
|
||
<div class="row g-4">
|
||
<div class="col-md-12">
|
||
<div class="card border shadow-sm">
|
||
<div class="card-header bg-white">
|
||
<h6 class="mb-0">
|
||
<i class="fas fa-image me-2"></i>Logo Configuration
|
||
</h6>
|
||
</div>
|
||
<div class="card-body">
|
||
<div class="row g-3">
|
||
<div class="col-md-6">
|
||
<label class="form-label fw-medium">Logo Image</label>
|
||
<div class="input-group mb-2">
|
||
<input
|
||
type="text"
|
||
class="form-control"
|
||
id="logoImage"
|
||
value="<%= data.logo %>"
|
||
placeholder="/path/to/logo.png"
|
||
/>
|
||
<button
|
||
type="button"
|
||
class="btn btn-outline-primary btn-upload-image"
|
||
data-target-input="logoImage"
|
||
data-image-type="header"
|
||
>
|
||
<i class="fas fa-upload me-1"></i>Upload
|
||
</button>
|
||
</div>
|
||
<small class="text-muted">Recommended size: 200x60px</small>
|
||
</div>
|
||
<div class="col-md-6" id="logoPreviewContainer">
|
||
<% if (data.logo) { %>
|
||
<img
|
||
src="<%= data.logo %>"
|
||
class="img-thumbnail"
|
||
style="max-height: 100px; max-width: 300px; object-fit: contain; background: #b8b76a;"
|
||
alt="Logo preview"
|
||
/>
|
||
<% } %>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Buttons Tab -->
|
||
<div class="tab-pane fade <%= activeTab === 'buttons' ? 'show active' : '' %>" id="buttons" role="tabpanel">
|
||
<div class="row g-4">
|
||
<!-- Sign In Button -->
|
||
<div class="col-md-6">
|
||
<div class="card border shadow-sm">
|
||
<div class="card-header bg-white">
|
||
<h6 class="mb-0"><i class="fas fa-sign-in-alt me-2"></i>Sign In Button</h6>
|
||
</div>
|
||
<div class="card-body">
|
||
<div class="mb-3">
|
||
<label class="form-label fw-medium">Label</label>
|
||
<input type="text" class="form-control" id="signInLabel"
|
||
value="<%= data.signInButton.label %>" placeholder="Sign In" />
|
||
</div>
|
||
<div class="mb-3">
|
||
<label class="form-label fw-medium">URL</label>
|
||
<input type="text" class="form-control" id="signInHref"
|
||
value="<%= data.signInButton.href %>" placeholder="/signin" />
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<!-- CTA Button -->
|
||
<div class="col-md-6">
|
||
<div class="card border shadow-sm">
|
||
<div class="card-header bg-white">
|
||
<h6 class="mb-0"><i class="fas fa-mouse-pointer me-2"></i>CTA Button</h6>
|
||
</div>
|
||
<div class="card-body">
|
||
<div class="mb-3">
|
||
<label class="form-label fw-medium">Label</label>
|
||
<input type="text" class="form-control" id="ctaLabel"
|
||
value="<%= data.ctaButton.label %>" placeholder="Request Info" />
|
||
</div>
|
||
<div class="mb-3">
|
||
<label class="form-label fw-medium">URL</label>
|
||
<input type="text" class="form-control" id="ctaHref"
|
||
value="<%= data.ctaButton.href %>" placeholder="/request" />
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Menu Structure Tab -->
|
||
<div class="tab-pane fade <%= activeTab === 'menu' ? 'show active' : '' %>" id="menu" role="tabpanel">
|
||
<%- include('menu') %>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Fixed actions for ALL tabs -->
|
||
<div class="card-footer bg-light d-flex justify-content-end py-3 gap-2">
|
||
<button type="button" class="btn btn-outline-secondary px-4" id="headerResetBtn">
|
||
<i class="fas fa-undo me-1"></i>Reset
|
||
</button>
|
||
<button type="button" id="saveHeaderBtn" class="btn btn-outline-primary px-4">
|
||
<i class="fas fa-save me-1"></i>Save Changes
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
document.addEventListener('DOMContentLoaded', function () {
|
||
// Safely remove any lingering modal backdrops on page load/navigation
|
||
function cleanupModals() {
|
||
// Basic reset
|
||
document.body.classList.remove('modal-open');
|
||
document.body.style.overflow = '';
|
||
document.body.style.paddingRight = '';
|
||
document.body.style.pointerEvents = 'auto';
|
||
|
||
// Remove all backdrop/overlay elements
|
||
const selector = '.modal-backdrop, .overlay, .loading';
|
||
document.querySelectorAll(selector).forEach(el => {
|
||
try {
|
||
el.remove();
|
||
} catch (err) {
|
||
console.warn('Error removing overlay:', err);
|
||
}
|
||
});
|
||
|
||
// Cleanup dynamically created modals that are not shown
|
||
document.querySelectorAll('.modal.fade:not(#modalAddMenu)').forEach(m => {
|
||
if (!m.classList.contains('show')) {
|
||
m.remove();
|
||
}
|
||
});
|
||
|
||
console.log('DOM Cleaned Up: Backdrops removed, body interaction restored.');
|
||
}
|
||
|
||
// Ensure all modals are in body root (prevents stacking context issues)
|
||
function relocateModals() {
|
||
const modals = document.querySelectorAll('.modal');
|
||
modals.forEach(modal => {
|
||
if (modal.parentElement !== document.body) {
|
||
document.body.appendChild(modal);
|
||
}
|
||
});
|
||
}
|
||
|
||
window.cleanupModals = cleanupModals;
|
||
|
||
// Initial cleanup and relocation
|
||
relocateModals();
|
||
cleanupModals();
|
||
|
||
const urlParams = new URLSearchParams(window.location.search);
|
||
const activeTabObj = urlParams.get('activeTab') || urlParams.get('tab');
|
||
|
||
if (activeTabObj) {
|
||
const tabTrigger = document.querySelector(`a[href="#${activeTabObj}"]`);
|
||
if (tabTrigger) {
|
||
new bootstrap.Tab(tabTrigger).show();
|
||
document.getElementById('activeTabInput').value = activeTabObj;
|
||
}
|
||
}
|
||
|
||
// Listen for tab changes
|
||
document.querySelectorAll('a[data-bs-toggle="tab"]').forEach(tab => {
|
||
tab.addEventListener('shown.bs.tab', function (event) {
|
||
const targetId = event.target.getAttribute('href').substring(1);
|
||
document.getElementById('activeTabInput').value = targetId;
|
||
|
||
// Update URL without reload to preserve tab state
|
||
const url = new URL(window.location);
|
||
url.searchParams.set('tab', targetId);
|
||
window.history.replaceState({}, '', url);
|
||
|
||
// Only load Menu Tree if clicking on the menu tab
|
||
if (targetId === 'menu') {
|
||
loadMenuTree();
|
||
}
|
||
});
|
||
});
|
||
|
||
// Detect changes to highlight Save button
|
||
function markChanged() {
|
||
const saveBtn = document.getElementById('saveHeaderBtn');
|
||
if (saveBtn) {
|
||
saveBtn.classList.remove('btn-outline-primary');
|
||
saveBtn.classList.add('btn-primary');
|
||
}
|
||
}
|
||
|
||
// Attach listeners to all inputs for change detection
|
||
const headerInputs = document.querySelectorAll('#logo input, #buttons input');
|
||
headerInputs.forEach(input => {
|
||
input.addEventListener('input', markChanged);
|
||
input.addEventListener('change', markChanged);
|
||
});
|
||
|
||
// Reset button logic
|
||
const headerResetBtn = document.getElementById('headerResetBtn');
|
||
if (headerResetBtn) {
|
||
headerResetBtn.addEventListener('click', function() {
|
||
if (confirm('Are you sure you want to discard all unsaved changes and reset to current saved data?')) {
|
||
window.location.reload(); // Simplest and most reliable reset
|
||
}
|
||
});
|
||
}
|
||
|
||
// Exposed markChanged for other components (like menu)
|
||
window.markHeaderChanged = markChanged;
|
||
|
||
const refreshMenuTreeBtn = document.getElementById('refreshMenuTree');
|
||
if (refreshMenuTreeBtn) {
|
||
refreshMenuTreeBtn.addEventListener('click', function () {
|
||
loadMenuTree();
|
||
});
|
||
}
|
||
|
||
// saveMenuChanges has its own onclick in menu.ejs, no need for redundant listener here
|
||
// But we'll keep a log to see if it's called
|
||
console.log('=== TRACE: Global click listeners initialized ===');
|
||
|
||
// Cleanup modals when any modal is hidden
|
||
document.addEventListener('hidden.bs.modal', function() {
|
||
cleanupModals();
|
||
});
|
||
|
||
document.querySelectorAll('.btn-upload-image').forEach(button => {
|
||
button.addEventListener('click', function () {
|
||
const targetInput = this.dataset.targetInput;
|
||
const imageType = this.dataset.imageType;
|
||
openImageUploader(targetInput, imageType);
|
||
});
|
||
});
|
||
|
||
const saveHeaderBtn = document.getElementById('saveHeaderBtn');
|
||
if (saveHeaderBtn) {
|
||
saveHeaderBtn.addEventListener('click', async function (e) {
|
||
console.log('=== TRACE: saveHeaderBtn Clicked (Unified) ===');
|
||
|
||
const submitBtn = this;
|
||
const originalText = submitBtn.innerHTML;
|
||
submitBtn.innerHTML = '<i class="fas fa-spinner fa-spin me-1"></i>Saving everything...';
|
||
submitBtn.disabled = true;
|
||
|
||
try {
|
||
// 1. Collect and Save Header Data (Logo + Buttons)
|
||
const headerData = {
|
||
logo: document.getElementById('logoImage').value,
|
||
signInButton: {
|
||
label: document.getElementById('signInLabel').value,
|
||
href: document.getElementById('signInHref').value,
|
||
},
|
||
ctaButton: {
|
||
label: document.getElementById('ctaLabel').value,
|
||
href: document.getElementById('ctaHref').value,
|
||
},
|
||
};
|
||
|
||
const headerResponse = await fetch('/admin/header/update', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify(headerData)
|
||
});
|
||
const headerResult = await headerResponse.json();
|
||
|
||
// 2. Save Menu Structure if we have the management script loaded
|
||
let menuResult = { success: true };
|
||
if (typeof window.saveMenuChanges === 'function') {
|
||
menuResult = await window.saveMenuChanges(false); // Save without redundant notification
|
||
}
|
||
|
||
if (headerResult.success && menuResult.success) {
|
||
showNotification('All changes saved successfully', 'success');
|
||
submitBtn.classList.remove('btn-primary');
|
||
submitBtn.classList.add('btn-outline-primary');
|
||
|
||
// Reload to refresh data, preserve current tab
|
||
const currentTab = document.getElementById('activeTabInput').value;
|
||
setTimeout(() => {
|
||
window.location.href = window.location.pathname + '?tab=' + currentTab;
|
||
}, 1000);
|
||
} else {
|
||
const errorMsg = (!headerResult.success ? headerResult.message : '') || (!menuResult.success ? menuResult.message : '') || 'Unable to save some changes';
|
||
showNotification('Error: ' + errorMsg, 'error');
|
||
}
|
||
} catch (error) {
|
||
console.error('=== TRACE: Unified Save ERROR ===', error);
|
||
showNotification('Error: ' + error.message, 'error');
|
||
} finally {
|
||
submitBtn.innerHTML = originalText;
|
||
submitBtn.disabled = false;
|
||
}
|
||
});
|
||
}
|
||
|
||
|
||
/**
|
||
* Show toast notification at top of page
|
||
* Auto-hides after 3 seconds
|
||
*/
|
||
function showNotification(message, type = 'info') {
|
||
// Create toast container if it doesn't exist
|
||
let toastContainer = document.getElementById('toastContainer');
|
||
if (!toastContainer) {
|
||
toastContainer = document.createElement('div');
|
||
toastContainer.id = 'toastContainer';
|
||
toastContainer.style.cssText = `
|
||
position: fixed;
|
||
top: 20px;
|
||
right: 20px;
|
||
z-index: 9999;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 10px;
|
||
pointer-events: none;
|
||
`;
|
||
document.body.appendChild(toastContainer);
|
||
}
|
||
|
||
// Create toast element
|
||
const toast = document.createElement('div');
|
||
const bgColor = type === 'success' ? '#28a745' : type === 'error' ? '#dc3545' : '#17a2b8';
|
||
const icon = type === 'success' ? '✓' : type === 'error' ? '✕' : 'ℹ';
|
||
|
||
toast.style.cssText = `
|
||
background-color: ${bgColor};
|
||
color: white;
|
||
padding: 12px 16px;
|
||
border-radius: 4px;
|
||
box-shadow: 0 2px 8px rgba(0,0,0,0.2);
|
||
font-size: 14px;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
animation: slideIn 0.3s ease-out;
|
||
pointer-events: auto;
|
||
cursor: pointer;
|
||
`;
|
||
|
||
toast.innerHTML = `
|
||
<span style="font-weight: bold; font-size: 16px;">${icon}</span>
|
||
<span>${message}</span>
|
||
`;
|
||
|
||
toastContainer.appendChild(toast);
|
||
|
||
// Auto-hide after 3 seconds
|
||
setTimeout(() => {
|
||
toast.style.animation = 'slideOut 0.3s ease-out';
|
||
setTimeout(() => toast.remove(), 300);
|
||
}, 3000);
|
||
|
||
// Click to dismiss
|
||
toast.addEventListener('click', () => {
|
||
toast.style.animation = 'slideOut 0.3s ease-out';
|
||
setTimeout(() => toast.remove(), 300);
|
||
});
|
||
}
|
||
|
||
// Add CSS animations for toast
|
||
const toastStyles = document.createElement('style');
|
||
toastStyles.textContent = `
|
||
@keyframes slideIn {
|
||
from {
|
||
transform: translateX(400px);
|
||
opacity: 0;
|
||
}
|
||
to {
|
||
transform: translateX(0);
|
||
opacity: 1;
|
||
}
|
||
}
|
||
|
||
@keyframes slideOut {
|
||
from {
|
||
transform: translateX(0);
|
||
opacity: 1;
|
||
}
|
||
to {
|
||
transform: translateX(400px);
|
||
opacity: 0;
|
||
}
|
||
}
|
||
`;
|
||
document.head.appendChild(toastStyles);
|
||
|
||
function loadMenuTree() {
|
||
const container = document.getElementById('menuTreeContainer');
|
||
if (!container) return; // Safely return if element doesn't exist
|
||
|
||
// If container is empty (or only has the spinner), we can show a message or fetch data
|
||
// But since we use EJS for server-side rendering, we usually don't want to overwrite it
|
||
console.log("Menu tab activated");
|
||
}
|
||
|
||
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;
|
||
|
||
if (!file.type.startsWith('image/')) {
|
||
alert('Please select a valid image file');
|
||
document.body.removeChild(fileInput);
|
||
return;
|
||
}
|
||
|
||
try {
|
||
const uploadBtn = document.querySelector(`[data-target-input="${targetInput}"]`);
|
||
const originalBtnHtml = uploadBtn.innerHTML;
|
||
uploadBtn.disabled = true;
|
||
uploadBtn.innerHTML = '<i class="fas fa-spinner fa-spin me-1"></i>Uploading...';
|
||
|
||
const formData = new FormData();
|
||
formData.append('image', file);
|
||
|
||
const response = await fetch(`/admin/upload/image?imageType=${imageType}`, {
|
||
method: 'POST',
|
||
body: formData
|
||
});
|
||
|
||
if (!response.ok) {
|
||
throw new Error(`HTTP ${response.status}: Upload failed`);
|
||
}
|
||
|
||
const result = await response.json();
|
||
|
||
if (!result.success) {
|
||
throw new Error(result.error || 'Upload failed');
|
||
}
|
||
|
||
const input = document.getElementById(targetInput);
|
||
if (!input) {
|
||
throw new Error(`Input field #${targetInput} not found`);
|
||
}
|
||
input.value = result.path;
|
||
|
||
updateImagePreview(targetInput, result.url);
|
||
markChanged();
|
||
|
||
uploadBtn.disabled = false;
|
||
uploadBtn.innerHTML = originalBtnHtml;
|
||
} catch (error) {
|
||
console.error('Upload error:', error);
|
||
alert('Upload failed: ' + error.message);
|
||
const uploadBtn = document.querySelector(`[data-target-input="${targetInput}"]`);
|
||
uploadBtn.disabled = false;
|
||
uploadBtn.innerHTML = originalBtnHtml;
|
||
} finally {
|
||
if (document.body.contains(fileInput)) {
|
||
document.body.removeChild(fileInput);
|
||
}
|
||
}
|
||
};
|
||
|
||
fileInput.click();
|
||
}
|
||
|
||
function updateImagePreview(inputId, imageUrl) {
|
||
const previewContainer = document.getElementById('logoPreviewContainer');
|
||
if (!previewContainer) {
|
||
return;
|
||
}
|
||
|
||
let img = previewContainer.querySelector('img');
|
||
|
||
if (img) {
|
||
img.src = imageUrl;
|
||
} else {
|
||
img = document.createElement('img');
|
||
img.src = imageUrl;
|
||
img.className = 'img-thumbnail';
|
||
img.style.maxHeight = '100px';
|
||
img.style.maxWidth = '300px';
|
||
img.style.objectFit = 'contain';
|
||
img.style.backgroundColor = '#b8b76a';
|
||
img.alt = 'Logo preview';
|
||
previewContainer.appendChild(img);
|
||
}
|
||
}
|
||
});
|
||
</script>
|
||
|
||
<!-- Modal Add/Edit Menu (Moved OUTSIDE tabs to prevent z-index/freeze issues) -->
|
||
<div class="modal fade" id="modalAddMenu" tabindex="-1" aria-hidden="true">
|
||
<div class="modal-dialog modal-dialog-centered">
|
||
<form id="menuForm" action="/admin/header/menu/create" method="POST" class="w-100">
|
||
<input type="hidden" name="id" id="menuId">
|
||
<input type="hidden" name="parentId" id="parentId">
|
||
|
||
<div class="modal-content border-0 shadow-lg">
|
||
<div class="modal-header bg-light border-bottom-0 py-3">
|
||
<h5 class="modal-title fw-bold" id="modalTitle">Add Menu Item</h5>
|
||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||
</div>
|
||
<div class="modal-body p-4">
|
||
<div class="mb-3">
|
||
<label class="form-label fw-medium">Menu Title</label>
|
||
<input type="text" name="title" id="formTitle" class="form-control form-control-lg fs-6" placeholder="e.g. Home, Services, About" required>
|
||
<small class="text-muted">Display text for the menu item.</small>
|
||
</div>
|
||
<div class="mb-3">
|
||
<label class="form-label fw-medium">Navigation URL</label>
|
||
<div class="input-group">
|
||
<span class="input-group-text bg-light"><i class="fas fa-link"></i></span>
|
||
<input type="text" name="url" id="formUrl" class="form-control" required placeholder="/services or https://...">
|
||
</div>
|
||
<small class="text-muted">Use relative paths for internal links.</small>
|
||
</div>
|
||
<div class="row g-3">
|
||
<div class="col-md-6 mb-3">
|
||
<label class="form-label fw-medium">Display Order</label>
|
||
<input type="number" name="order" id="formOrder" class="form-control" value="0">
|
||
</div>
|
||
<div class="col-md-6 mb-3">
|
||
<label class="form-label fw-medium">Status</label>
|
||
<select name="status" id="formStatus" class="form-select">
|
||
<option value="active">Active</option>
|
||
<option value="inactive">Inactive</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
<div class="mb-0">
|
||
<label class="form-label fw-medium">Link Type</label>
|
||
<div class="d-flex gap-3">
|
||
<div class="form-check">
|
||
<input class="form-check-input" type="radio" name="type" id="typeInternal" value="internal" checked>
|
||
<label class="form-check-label" for="typeInternal">Internal</label>
|
||
</div>
|
||
<div class="form-check">
|
||
<input class="form-check-input" type="radio" name="type" id="typeExternal" value="external">
|
||
<label class="form-check-label" for="typeExternal">External</label>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="modal-footer bg-light border-top-0 py-3">
|
||
<button type="button" class="btn btn-white border px-4" data-bs-dismiss="modal">Cancel</button>
|
||
<button type="submit" class="btn btn-primary px-4" id="btnSaveMenu">
|
||
<i class="fas fa-save me-1"></i>Save Changes
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
// AJAX handler for menuForm
|
||
const menuForm = document.getElementById('menuForm');
|
||
if (menuForm) {
|
||
menuForm.addEventListener('submit', async function(e) {
|
||
e.preventDefault();
|
||
console.log('=== TRACE: menuForm AJAX Submission Start ===');
|
||
|
||
const submitBtn = document.getElementById('btnSaveMenu');
|
||
const originalText = submitBtn ? submitBtn.innerHTML : 'Save';
|
||
|
||
if (submitBtn) {
|
||
submitBtn.disabled = true;
|
||
submitBtn.innerHTML = '<i class="fas fa-spinner fa-spin me-1"></i>Saving...';
|
||
}
|
||
|
||
try {
|
||
const formData = new FormData(this);
|
||
const data = {};
|
||
formData.forEach((value, key) => data[key] = value);
|
||
|
||
console.log('Sending data:', data);
|
||
|
||
const response = await axios({
|
||
method: 'POST',
|
||
url: this.action,
|
||
data: data
|
||
});
|
||
|
||
console.log('Response:', response.data);
|
||
|
||
if (response.data.success || response.status === 200) {
|
||
showNotification('Menu item saved successfully', 'success');
|
||
|
||
// Hide modal
|
||
const modalElement = document.getElementById('modalAddMenu');
|
||
const modal = bootstrap.Modal.getOrCreateInstance(modalElement);
|
||
modal.hide();
|
||
|
||
// Mark as changed so user needs to click Save Changes
|
||
if (typeof window.markHeaderChanged === 'function') {
|
||
window.markHeaderChanged();
|
||
}
|
||
|
||
// Reload page to show updated menu structure, preserve current tab
|
||
const currentTab = document.getElementById('activeTabInput').value;
|
||
setTimeout(() => {
|
||
window.location.href = window.location.pathname + '?tab=' + currentTab;
|
||
}, 1000);
|
||
} else {
|
||
showNotification(response.data.message || 'Unable to save menu', 'error');
|
||
}
|
||
} catch (error) {
|
||
console.error('AJAX Error:', error);
|
||
showNotification('Server connection error: ' + (error.response?.data?.message || error.message), 'error');
|
||
} finally {
|
||
if (submitBtn) {
|
||
submitBtn.disabled = false;
|
||
submitBtn.innerHTML = originalText;
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
// TRACE: Click listener for btnSaveMenu
|
||
document.addEventListener('click', function(e) {
|
||
if (e.target && e.target.id === 'btnSaveMenu') {
|
||
console.log('=== TRACE: btnSaveMenu CLICKED ===');
|
||
}
|
||
});
|
||
</script>
|