Merge branch 'main' of https://gits.techvanguard.vn/UKSOURCE/cms.hailearning.edu.vn into fea/thanh-02022026-news

This commit is contained in:
Wini_Fy
2026-02-05 10:54:30 +07:00
33 changed files with 3889 additions and 1729 deletions

View File

@@ -8,6 +8,7 @@
</div>
<div class="card-body p-0">
<div class="row g-0">
<!-- Home -->
<div class="col-md-4 border-end">
<div class="p-4">
<div class="d-flex align-items-center mb-3">
@@ -26,6 +27,7 @@
</div>
</div>
<!-- Header & Menu -->
<div class="col-md-4 border-end">
<div class="p-4">
<div class="d-flex align-items-center mb-3">
@@ -44,6 +46,7 @@
</div>
</div>
<!-- Footer -->
<div class="col-md-4">
<div class="p-4">
<div class="d-flex align-items-center mb-3">
@@ -62,6 +65,7 @@
</div>
</div>
<!-- About Us -->
<div class="col-md-4 border-end border-top">
<div class="p-4">
<div class="d-flex align-items-center mb-3">
@@ -80,6 +84,7 @@
</div>
</div>
<!-- Contact -->
<div class="col-md-4 border-end border-top">
<div class="p-4">
<div class="d-flex align-items-center mb-3">
@@ -98,6 +103,7 @@
</div>
</div>
<!-- FAQ -->
<div class="col-md-4 border-top">
<div class="p-4">
<div class="d-flex align-items-center mb-3">
@@ -116,6 +122,7 @@
</div>
</div>
<!-- Appointment -->
<div class="col-md-4 border-end border-top">
<div class="p-4">
<div class="d-flex align-items-center mb-3">
@@ -134,6 +141,7 @@
</div>
</div>
<!-- Pricing -->
<div class="col-md-4 border-end border-top">
<div class="p-4">
<div class="d-flex align-items-center mb-3">
@@ -152,7 +160,8 @@
</div>
</div>
<div class="col-md-4 border-end border-top">
<!-- Terms & Conditions -->
<div class="col-md-4 border-top">
<div class="p-4">
<div class="d-flex align-items-center mb-3">
<div class="rounded-circle d-flex align-items-center justify-content-center me-3"
@@ -164,15 +173,13 @@
<p class="text-muted mb-0 small">Manage terms</p>
</div>
</div>
<a
href="/admin/terms-conditions"
class="btn btn-sm btn-primary w-100 mt-2"
>
<a href="/admin/terms-conditions" class="btn btn-sm btn-primary w-100 mt-2">
<i class="fas fa-edit me-2"></i>Edit
</a>
</div>
</div>
<!-- Travel -->
<div class="col-md-4 border-end border-top">
<div class="p-4">
<div class="d-flex align-items-center mb-3">
@@ -191,7 +198,8 @@
</div>
</div>
<div class="col-md-4 border-top">
<!-- Safety -->
<div class="col-md-4 border-end border-top">
<div class="p-4">
<div class="d-flex align-items-center mb-3">
<div class="rounded-circle d-flex align-items-center justify-content-center me-3"
@@ -209,7 +217,8 @@
</div>
</div>
<div class="col-md-4 border-end border-top">
<!-- Camp Location -->
<div class="col-md-4 border-top">
<div class="p-4">
<div class="d-flex align-items-center mb-3">
<div class="rounded-circle d-flex align-items-center justify-content-center me-3"
@@ -221,16 +230,14 @@
<p class="text-muted mb-0 small">Manage camp location</p>
</div>
</div>
<a
href="/admin/camp-location"
class="btn btn-sm btn-primary w-100 mt-2"
>
<a href="/admin/camp-location" class="btn btn-sm btn-primary w-100 mt-2">
<i class="fas fa-edit me-2"></i>Edit
</a>
</div>
</div>
<div class="col-md-4 border-top">
<!-- Activities -->
<div class="col-md-4 border-end border-top">
<div class="p-4">
<div class="d-flex align-items-center mb-3">
<div class="rounded-circle d-flex align-items-center justify-content-center me-3"
@@ -247,21 +254,14 @@
</a>
</div>
</div>
<!-- Services -->
<div class="col-md-4 border-end border-top">
<div class="p-4">
<div class="d-flex align-items-center mb-3">
<div
class="rounded-circle d-flex align-items-center justify-content-center me-3"
style="
width: 50px;
height: 50px;
background-color: rgba(184, 183, 106, 0.1);
"
>
<i
class="fas fa-running fa-lg"
style="color: var(--primary-color)"
></i>
<div class="rounded-circle d-flex align-items-center justify-content-center me-3"
style="width: 50px; height: 50px; background-color: rgba(184, 183, 106, 0.1);">
<i class="fas fa-concierge-bell fa-lg" style="color: var(--primary-color);"></i>
</div>
<div>
<h5 class="mb-0">Services</h5>
@@ -273,6 +273,8 @@
</a>
</div>
</div>
<!-- Blog -->
<div class="col-md-4 border-top">
<div class="p-4">
<div class="d-flex align-items-center mb-3">
@@ -638,12 +640,12 @@
<div class="d-flex align-items-center">
<div class="rounded-circle d-flex align-items-center justify-content-center me-2"
style="width: 32px; height: 32px; background-color: rgba(184, 183, 106, 0.1);">
<i class="fas fa-campground" style="color: var(--primary-color);"></i>
<i class="fas fa-blog" style="color: var(--primary-color);"></i>
</div>
<span>Camp Location API</span>
<span>Blog API</span>
</div>
</td>
<td><code>/api/camp-location</code></td>
<td><code>/api/blog</code></td>
<td>
<span
class="badge"
@@ -651,10 +653,10 @@
>GET</span
>
</td>
<td>API to get camp location data</td>
<td>API to get blog posts</td>
<td>
<a
href="/api/camp-location"
href="/api/blog"
class="btn btn-sm btn-outline-primary"
target="_blank"
>
@@ -687,6 +689,7 @@
style="width: 40px; height: 40px; background-color: rgba(184, 183, 106, 0.1);">
<i class="fas fa-info-circle" style="color: var(--primary-color);"></i>
</div>
</div>
<div>
<div class="text-muted small">Version</div>
<div class="fw-bold" style="color: var(--primary-color)">
@@ -770,4 +773,4 @@
background-color: var(--primary-color) !important;
color: white;
}
</style>
</style>

View File

@@ -196,7 +196,7 @@
<div class="card border shadow-sm">
<div class="card-header bg-light d-flex justify-content-between align-items-center">
<h6 class="mb-0">Footer Columns</h6>
<button type="button" class="btn btn-outline-primary btn-sm" id="addColumn">
<button type="button" class="btn btn-primary btn-sm" id="addColumn">
<i class="fas fa-plus me-1"></i>Add Column
</button>
</div>
@@ -208,10 +208,11 @@
<div class="d-flex justify-content-between align-items-center">
<h6 class="mb-0">Column <%= columnIndex + 1 %>
</h6>
<button type="button" class="btn btn-outline-danger btn-sm remove-column"
data-column-index="<%= columnIndex %>">
<i class="fas fa-trash"></i>
</button>
<div class="btn-group-action">
<button type="button" class="btn btn-sm remove-column" data-column-index="<%= columnIndex %>">
<i class="fas fa-trash text-action-delete"></i>
</button>
</div>
</div>
</div>
<div class="card-body">
@@ -244,20 +245,20 @@
name="columns[<%= columnIndex %>][links][<%= linkIndex %>][url]"
value="<%= link.url %>" placeholder="/about-us/" />
</div>
<div class="col-md-1">
<label class="form-label form-label-sm">&nbsp;</label>
<button type="button"
class="btn btn-outline-danger btn-sm w-100 remove-link"
data-column-index="<%= columnIndex %>" data-link-index="<%= linkIndex %>">
<i class="fas fa-trash"></i>
</button>
<div class="col-md-1 d-flex justify-content-end align-items-end">
<div class="btn-group-action">
<button type="button" class="btn btn-sm remove-link"
data-column-index="<%= columnIndex %>" data-link-index="<%= linkIndex %>">
<i class="fas fa-trash text-action-delete"></i>
</button>
</div>
</div>
</div>
</div>
</div>
<% }); %>
</div>
<button type="button" class="btn btn-outline-primary btn-sm add-link"
<button type="button" class="btn btn-primary btn-sm add-link"
data-column-index="<%= columnIndex %>">
<i class="fas fa-plus me-1"></i>Add Link
</button>
@@ -276,7 +277,7 @@
<div class="card border shadow-sm">
<div class="card-header bg-light d-flex justify-content-between align-items-center">
<h6 class="mb-0">Social Media Links</h6>
<button type="button" class="btn btn-outline-primary btn-sm" id="addSocialLink">
<button type="button" class="btn btn-primary btn-sm" id="addSocialLink">
<i class="fas fa-plus me-1"></i>Add Social Link
</button>
</div>
@@ -301,12 +302,13 @@
<input type="text" class="form-control" name="social[links][<%= index %>][icon]"
value="<%= link.icon %>" />
</div>
<div class="col-md-1">
<label class="form-label">&nbsp;</label>
<button type="button" class="btn btn-outline-danger btn-sm w-100 remove-social-link"
data-social-index="<%= index %>">
<i class="fas fa-trash"></i>
</button>
<div class="col-md-1 d-flex justify-content-end align-items-end">
<div class="btn-group-action">
<button type="button" class="btn btn-sm remove-social-link"
data-social-index="<%= index %>">
<i class="fas fa-trash text-action-delete"></i>
</button>
</div>
</div>
</div>
</div>
@@ -341,13 +343,11 @@
<!-- Move buttons to fixed bottom -->
<div class="fixed-bottom-buttons">
<button type="reset" class="btn btn-secondary">
<i class="fas fa-undo"></i>
<span>Reset</span>
<button type="reset" class="btn btn-secondary px-4">
<i class="fas fa-undo me-1"></i>Reset
</button>
<button type="submit" class="btn btn-primary">
<i class="fas fa-save"></i>
<span>Save Changes</span>
<button type="submit" class="btn btn-primary px-4">
<i class="fas fa-save me-1"></i>Save Changes
</button>
</div>
</form>
@@ -433,9 +433,11 @@
<div class="card-header bg-light">
<div class="d-flex justify-content-between align-items-center">
<h6 class="mb-0">Column ${columnIndex + 1}</h6>
<button type="button" class="btn btn-outline-danger btn-sm remove-column" data-column-index="${columnIndex}">
<i class="fas fa-trash"></i>
</button>
<div class="btn-group-action">
<button type="button" class="btn btn-sm remove-column" data-column-index="${columnIndex}">
<i class="fas fa-trash text-action-delete"></i>
</button>
</div>
</div>
</div>
<div class="card-body">
@@ -451,7 +453,7 @@
<h6 class="fw-medium mb-3">Links</h6>
<div class="column-links-container" data-column-index="${columnIndex}">
</div>
<button type="button" class="btn btn-outline-primary btn-sm add-link" data-column-index="${columnIndex}">
<button type="button" class="btn btn-primary btn-sm add-link" data-column-index="${columnIndex}">
<i class="fas fa-plus me-1"></i>Add Link
</button>
</div>
@@ -485,11 +487,12 @@
<label class="form-label fw-medium">Icon Class</label>
<input type="text" class="form-control" name="social[links][${socialLinkIndex}][icon]" value="">
</div>
<div class="col-md-1">
<label class="form-label">&nbsp;</label>
<button type="button" class="btn btn-outline-danger btn-sm w-100 remove-social-link" data-social-index="${socialLinkIndex}">
<i class="fas fa-trash"></i>
</button>
<div class="col-md-1 d-flex justify-content-end align-items-end">
<div class="btn-group-action">
<button type="button" class="btn btn-sm remove-social-link" data-social-index="${socialLinkIndex}">
<i class="fas fa-trash text-action-delete"></i>
</button>
</div>
</div>
</div>
</div>
@@ -535,11 +538,12 @@
<label class="form-label form-label-sm">URL</label>
<input type="text" class="form-control form-control-sm" name="columns[${columnIndex}][links][${linkIndex}][url]" value="" placeholder="/about-us/">
</div>
<div class="col-md-1">
<label class="form-label form-label-sm">&nbsp;</label>
<button type="button" class="btn btn-outline-danger btn-sm w-100 remove-link" data-column-index="${columnIndex}" data-link-index="${linkIndex}">
<i class="fas fa-trash"></i>
</button>
<div class="col-md-1 d-flex justify-content-end align-items-end">
<div class="btn-group-action">
<button type="button" class="btn btn-sm remove-link" data-column-index="${columnIndex}" data-link-index="${linkIndex}">
<i class="fas fa-trash text-action-delete"></i>
</button>
</div>
</div>
</div>
</div>

File diff suppressed because it is too large Load Diff

355
views/admin/header/menu.ejs Normal file
View File

@@ -0,0 +1,355 @@
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center">
<h6 class="mb-0">
<i class="fas fa-sitemap me-2"></i>Menu Structure
</h6>
<button class="btn btn-primary btn-sm" onclick="prepareAddMenu()">
<i class="fas fa-plus me-1"></i>Add Root Menu
</button>
</div>
<div class="card-body p-0">
<div id="nestedMenuContainer" class="nested-menu-container">
<ul id="menuRoot" class="list-unstyled menu-group" data-id="root">
<% function renderMenu(items) { %>
<% items.forEach(item => { %>
<li class="menu-item-wrapper mb-2" data-id="<%= item._id %>">
<div class="menu-item-row d-flex align-items-center p-3 rounded border hover-shadow-sm bg-white">
<div class="menu-drag-handle me-2 text-muted">
<i class="fas fa-grip-vertical"></i>
</div>
<div class="menu-toggle-icon me-2">
<% if (item.children && item.children.length > 0) { %>
<button class="btn btn-sm p-0 btn-toggle-nested" type="button">
<i class="fas fa-chevron-down transition-base text-muted" style="width: 12px;"></i>
</button>
<% } else { %>
<div style="width: 24px;"></div>
<% } %>
</div>
<div class="menu-icon me-3">
<i class="fas <%= (item.type === 'external' ? 'fa-external-link-alt text-info' : 'fa-link text-secondary') %>"></i>
</div>
<div class="flex-grow-1">
<div class="d-flex align-items-center">
<span class="fw-bold text-dark"><%= item.title %></span>
<% if (item.type === 'external') { %>
<span class="badge bg-light text-dark border ms-2" style="font-size: 0.7rem;">External</span>
<% } %>
<% if (item.status === 'inactive') { %>
<span class="badge bg-soft-secondary ms-2">Inactive</span>
<% } else { %>
<span class="badge bg-soft-success ms-2">Active</span>
<% } %>
</div>
<div class="text-muted small text-truncate" style="max-width: 300px;">
<i class="fas fa-link me-1" style="font-size: 0.75rem;"></i><%= item.url %>
</div>
</div>
<div class="menu-actions">
<div class="btn-group-action">
<button class="btn btn-sm btn-add-child" data-id="<%= item._id %>" title="Add Sub-menu">
<i class="fas fa-plus text-action-add"></i>
</button>
<button class="btn btn-sm btn-edit-menu" data-item='<%= JSON.stringify(item).replace(/'/g, "&apos;") %>' title="Edit">
<i class="fas fa-edit text-action-edit"></i>
</button>
<button type="button" class="btn btn-sm btn-delete-menu" data-id="<%= item._id %>" data-title="<%= item.title %>" title="Delete">
<i class="fas fa-trash text-action-delete"></i>
</button>
</div>
<form id="delete-form-<%= item._id %>" action="/admin/header/menu/delete" method="POST" class="d-none">
<input type="hidden" name="id" value="<%= item._id %>">
</form>
</div>
</div>
<ul class="list-unstyled menu-group mt-2 ps-4 ms-2 border-start nested-list" style="min-height: 5px;" data-id="<%= item._id %>">
<% if (item.children && item.children.length > 0) { %>
<% renderMenu(item.children) %>
<% } %>
</ul>
</li>
<% }) %>
<% } %>
<% if (menuData.tree && menuData.tree.length > 0) { %>
<% renderMenu(menuData.tree) %>
<% } %>
</ul>
<% if (!menuData.tree || menuData.tree.length === 0) { %>
<div class="text-center py-5">
<img src="/assets/img/icon/empty-state.svg" alt="Empty" style="width: 120px; opacity: 0.5;" class="mb-3">
<p class="text-muted">No menu items found. Start by adding a root menu.</p>
</div>
<% } %>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
console.log('Menu Tab JS Initialized');
initSortable();
function initSortable() {
const menuGroups = document.querySelectorAll('.menu-group');
menuGroups.forEach(group => {
new Sortable(group, {
group: 'nested-menu',
animation: 150,
fallbackOnBody: true,
swapThreshold: 0.65,
handle: '.menu-drag-handle',
ghostClass: 'menu-ghost',
chosenClass: 'menu-chosen',
dragClass: 'menu-dragging',
onEnd: function (evt) {
console.log('Drag ended', evt);
// Highlight that changes need saving
const saveBtn = document.getElementById('saveHeaderBtn');
if (saveBtn && typeof window.markHeaderChanged === 'function') {
window.markHeaderChanged();
}
}
});
});
}
// Bind Edit/Add/Delete buttons
bindMenuActions();
function bindMenuActions() {
// Use event delegation for better performance and to handle re-rendered items if any
const container = document.getElementById('nestedMenuContainer');
if (container) {
container.addEventListener('click', function(e) {
const editBtn = e.target.closest('.btn-edit-menu');
if (editBtn) {
try {
const item = JSON.parse(editBtn.dataset.item);
console.log('=== TRACE: Edit Menu Clicked ===', item);
prepareEditMenu(item);
} catch (e) {
console.error('Error parsing menu item data:', e);
}
return;
}
const addChildBtn = e.target.closest('.btn-add-child');
if (addChildBtn) {
const pid = addChildBtn.dataset.id;
console.log('=== TRACE: Add Child Clicked ===', { pid });
if (typeof prepareAddChild === 'function') {
prepareAddChild(pid);
} else {
console.error('prepareAddChild function not found');
}
return;
}
const deleteBtn = e.target.closest('.btn-delete-menu');
if (deleteBtn) {
const id = deleteBtn.dataset.id;
const title = deleteBtn.dataset.title;
console.log('=== TRACE: Delete Menu Clicked ===', { id, title });
if (confirm(`Are you sure you want to delete "${title}" and all its sub-menu items?`)) {
const form = document.getElementById('delete-form-' + id);
console.log('=== TRACE: Submitting Delete Form ===', form ? form.action : 'FORM NOT FOUND');
if (form) form.submit();
}
return;
}
const toggleBtn = e.target.closest('.btn-toggle-nested');
if (toggleBtn) {
const wrapper = toggleBtn.closest('.menu-item-wrapper');
const nestedUl = wrapper.querySelector('.nested-list');
const icon = toggleBtn.querySelector('i');
if (nestedUl) {
const isCollapsed = nestedUl.classList.toggle('collapsed');
if (isCollapsed) {
icon.style.transform = 'rotate(-90deg)';
} else {
icon.style.transform = 'rotate(0deg)';
}
}
}
});
}
}
});
function prepareAddMenu() {
console.log('=== TRACE: prepareAddMenu Called ===');
const form = document.getElementById('menuForm');
if (!form) return;
form.action = '/admin/header/menu/create';
document.getElementById('modalTitle').innerText = 'Add Root Menu';
document.getElementById('menuId').value = '';
document.getElementById('parentId').value = '';
document.getElementById('formTitle').value = '';
document.getElementById('formUrl').value = '';
document.getElementById('formOrder').value = '0';
document.getElementById('formStatus').value = 'active';
document.getElementById('typeInternal').checked = true;
const modalElement = document.getElementById('modalAddMenu');
const modal = bootstrap.Modal.getOrCreateInstance(modalElement);
modal.show();
}
function prepareAddChild(parentId) {
prepareAddMenu();
document.getElementById('parentId').value = parentId;
document.getElementById('modalTitle').innerText = 'Add Sub-Menu Item';
}
function prepareEditMenu(item) {
const form = document.getElementById('menuForm');
if (!form) return;
form.action = '/admin/header/menu/update/' + item._id;
document.getElementById('modalTitle').innerText = 'Edit Menu Item';
document.getElementById('menuId').value = item._id;
document.getElementById('parentId').value = item.parentId || '';
document.getElementById('formTitle').value = item.title;
document.getElementById('formUrl').value = item.url;
document.getElementById('formOrder').value = item.order;
document.getElementById('formStatus').value = item.status;
if (item.type === 'external') {
document.getElementById('typeExternal').checked = true;
} else {
document.getElementById('typeInternal').checked = true;
}
const modalElement = document.getElementById('modalAddMenu');
const modal = bootstrap.Modal.getOrCreateInstance(modalElement);
modal.show();
}
function collectMenuData() {
const items = [];
function traverse(element, parentId = null) {
const children = element.children;
for (let i = 0; i < children.length; i++) {
const li = children[i];
if (li.tagName !== 'LI') continue;
const id = li.dataset.id;
if (!id) continue;
items.push({
id: id,
order: i + 1,
parentId: parentId === 'root' ? null : parentId
});
const subUl = li.querySelector('.menu-group');
if (subUl) {
traverse(subUl, id);
}
}
}
const rootUl = document.getElementById('menuRoot');
if (rootUl) traverse(rootUl, 'root');
return items;
}
window.saveMenuChanges = function(showToastFlag = true) {
return new Promise((resolve, reject) => {
console.log('=== TRACE: saveMenuChanges Called (Reorder) ===');
const items = collectMenuData();
if (items.length === 0) {
console.warn('No menu items found to reorder');
return resolve({ success: true, message: 'No items' });
}
const saveBtn = document.getElementById('saveHeaderBtn');
const originalHtml = saveBtn ? saveBtn.innerHTML : '';
// Only manage button state if this is a direct call (not unified save)
const manageButton = showToastFlag && saveBtn;
if (manageButton) {
saveBtn.disabled = true;
saveBtn.innerHTML = '<i class="fas fa-spinner fa-spin me-1"></i>Saving Menu...';
}
fetch('/admin/header/menu/reorder', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ items: items })
})
.then(response => response.json())
.then(data => {
if (data.success) {
if (showToastFlag) showNotification('Menu structure saved', 'success');
resolve(data);
} else {
reject(new Error(data.message));
}
})
.catch(error => reject(error))
.finally(() => {
if (manageButton) {
saveBtn.disabled = false;
saveBtn.innerHTML = originalHtml;
}
});
});
};
window.collectMenuData = collectMenuData;
// Global reset fallback
window.prepareAddMenu = prepareAddMenu;
</script>
<!-- Redundant buttons removed to use global buttons in index.ejs -->
<style>
.nested-menu-container {
padding: var(--spacing-3);
}
.menu-group {
min-height: 10px;
}
.menu-item-row {
transition: var(--transition-base);
}
.menu-drag-handle { cursor: grab; padding: 5px; }
.menu-drag-handle:active { cursor: grabbing; }
/* SortableJS Classes mapped to our variables */
.menu-ghost {
opacity: 0.4;
border: 2px dashed var(--primary-color) !important;
}
.menu-chosen {
background-color: var(--primary-soft) !important;
}
.menu-dragging {
opacity: 0.9;
}
/* Collapse/Expand Styles */
.nested-list {
overflow: hidden;
transition: all 0.3s ease;
max-height: 2000px; /* Large enough for nested items */
}
.nested-list.collapsed {
max-height: 0;
margin-top: 0 !important;
opacity: 0;
pointer-events: none;
}
.btn-toggle-nested i {
transition: transform 0.2s ease;
}
.transition-base {
transition: all 0.2s ease-in-out;
}
</style>

View File

@@ -11,7 +11,7 @@
</div>
<div class="row align-items-center">
<div class="col-lg-6">
<div class="col-lg-6 p-5">
<h1 class="fw-bold mb-4 text-white">API Management</h1>
<p class="lead mb-4 text-white-50">Simple dashboard to control your APIs</p>
<div class="d-flex gap-3">

View File

@@ -117,7 +117,40 @@
<!-- Bootstrap JS -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.bundle.min.js"></script>
<script>
// Global function to clean up any stuck modal backdrops
function forceCleanupModals() {
document.body.classList.remove('modal-open');
document.body.style.overflow = '';
document.body.style.paddingRight = '';
const backdrops = document.querySelectorAll('.modal-backdrop, .overlay, .loading');
if (backdrops.length > 0) {
console.warn('Force removing stuck backdrops:', backdrops.length);
backdrops.forEach(el => el.remove());
}
}
// Automatically clean up on every hide event
document.addEventListener('hidden.bs.modal', function() {
// Wait a tiny bit for the animation to finish
setTimeout(forceCleanupModals, 100);
});
// Watchdog: Check if backdrops exist without a visible modal every 2 seconds
setInterval(() => {
const visibleModals = document.querySelectorAll('.modal.show');
if (visibleModals.length === 0) {
const backdrops = document.querySelectorAll('.modal-backdrop');
if (backdrops.length > 0) {
forceCleanupModals();
}
}
}, 2000);
// Clean up on page load
window.addEventListener('load', forceCleanupModals);
</script>
<%- script %>
</body>
</html>
</html>

View File

@@ -12,16 +12,18 @@
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet" />
<!-- Font Awesome -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" />
<!-- Custom CSS -->
<style>
:root {
--primary-color: #0048b4;
--primary-light: #003a8f;
--primary-dark: #002a6f;
--secondary-color: #f5f5e8;
--text-light: #black;
}
<!-- Global CSS Variables -->
<link rel="stylesheet" href="/assets/css/variables.css" />
<!-- CMS Component System -->
<link rel="stylesheet" href="/assets/css/components/button.css" />
<link rel="stylesheet" href="/assets/css/components/card.css" />
<link rel="stylesheet" href="/assets/css/components/form.css" />
<link rel="stylesheet" href="/assets/css/components/modal.css" />
<link rel="stylesheet" href="/assets/css/components/table.css" />
<!-- Layout Styles -->
<link rel="stylesheet" href="/assets/css/layout.css" />
<style>
body {
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
position: relative;
@@ -861,16 +863,22 @@
<!-- Bootstrap JS -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.bundle.min.js"></script>
<!-- Axios for API calls -->
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<!-- SortableJS for drag and drop -->
<script src="https://cdn.jsdelivr.net/npm/sortablejs@1.15.0/Sortable.min.js"></script>
<!-- Toast JS -->
<script src="/js/toast.js"></script>
<!-- Flash Handler JS -->
<script src="/js/flash-handler.js"></script>
<!-- Custom JS -->
<!-- Custom JS Utilities -->
<script src="/js/main.js"></script>
<!-- Custom modal -->
<!-- Custom modal enhancement -->
<script src="/js/custom-modal.js"></script>
<script>