975 lines
26 KiB
HTML
975 lines
26 KiB
HTML
<!doctype html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
<title>Menu Manager - Sky Art Shop Admin</title>
|
|
<link
|
|
href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css"
|
|
rel="stylesheet"
|
|
/>
|
|
<link
|
|
rel="stylesheet"
|
|
href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css"
|
|
/>
|
|
<link rel="stylesheet" href="/admin/css/admin-style.css" />
|
|
<style>
|
|
:root {
|
|
--accent-gradient: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
--card-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
|
|
--hover-shadow: 0 8px 30px rgba(102, 126, 234, 0.2);
|
|
--border-radius: 16px;
|
|
--transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
}
|
|
|
|
/* Menu Container */
|
|
.menu-container {
|
|
display: grid;
|
|
grid-template-columns: 1fr 350px;
|
|
gap: 30px;
|
|
max-width: 1400px;
|
|
}
|
|
|
|
@media (max-width: 992px) {
|
|
.menu-container {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
}
|
|
|
|
/* Menu Items List */
|
|
.menu-list-card {
|
|
background: white;
|
|
border-radius: var(--border-radius);
|
|
box-shadow: var(--card-shadow);
|
|
overflow: hidden;
|
|
}
|
|
|
|
.menu-list-header {
|
|
background: var(--accent-gradient);
|
|
color: white;
|
|
padding: 20px 24px;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
}
|
|
|
|
.menu-list-header h5 {
|
|
margin: 0;
|
|
font-weight: 600;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
}
|
|
|
|
.menu-list-body {
|
|
padding: 20px;
|
|
min-height: 400px;
|
|
}
|
|
|
|
/* Sortable Menu Items */
|
|
.menu-items {
|
|
list-style: none;
|
|
padding: 0;
|
|
margin: 0;
|
|
}
|
|
|
|
.menu-item {
|
|
background: #f8fafc;
|
|
border: 2px solid #e2e8f0;
|
|
border-radius: 12px;
|
|
padding: 16px 20px;
|
|
margin-bottom: 12px;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 16px;
|
|
cursor: grab;
|
|
transition: var(--transition);
|
|
}
|
|
|
|
.menu-item:hover {
|
|
border-color: #667eea;
|
|
box-shadow: var(--card-shadow);
|
|
}
|
|
|
|
.menu-item:active {
|
|
cursor: grabbing;
|
|
}
|
|
|
|
.menu-item.dragging {
|
|
opacity: 0.5;
|
|
border-color: #667eea;
|
|
background: #eef2ff;
|
|
}
|
|
|
|
.menu-item.drag-over {
|
|
border-color: #667eea;
|
|
background: #eef2ff;
|
|
}
|
|
|
|
.drag-handle {
|
|
color: #94a3b8;
|
|
font-size: 1.2rem;
|
|
cursor: grab;
|
|
}
|
|
|
|
.menu-item-icon {
|
|
width: 40px;
|
|
height: 40px;
|
|
background: var(--accent-gradient);
|
|
color: white;
|
|
border-radius: 10px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 1rem;
|
|
}
|
|
|
|
.menu-item-content {
|
|
flex: 1;
|
|
}
|
|
|
|
.menu-item-name {
|
|
font-weight: 600;
|
|
color: #334155;
|
|
margin-bottom: 4px;
|
|
}
|
|
|
|
.menu-item-url {
|
|
font-size: 0.85rem;
|
|
color: #64748b;
|
|
font-family: "Monaco", "Consolas", monospace;
|
|
}
|
|
|
|
.menu-item-actions {
|
|
display: flex;
|
|
gap: 8px;
|
|
}
|
|
|
|
.menu-item-actions button {
|
|
width: 36px;
|
|
height: 36px;
|
|
border-radius: 8px;
|
|
border: none;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
cursor: pointer;
|
|
transition: var(--transition);
|
|
}
|
|
|
|
.btn-edit-item {
|
|
background: #e0e7ff;
|
|
color: #4f46e5;
|
|
}
|
|
|
|
.btn-edit-item:hover {
|
|
background: #4f46e5;
|
|
color: white;
|
|
}
|
|
|
|
.btn-delete-item {
|
|
background: #fee2e2;
|
|
color: #dc2626;
|
|
}
|
|
|
|
.btn-delete-item:hover {
|
|
background: #dc2626;
|
|
color: white;
|
|
}
|
|
|
|
.visibility-toggle {
|
|
width: 36px;
|
|
height: 36px;
|
|
border-radius: 8px;
|
|
border: none;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
cursor: pointer;
|
|
transition: var(--transition);
|
|
}
|
|
|
|
.visibility-toggle.visible {
|
|
background: #dcfce7;
|
|
color: #166534;
|
|
}
|
|
|
|
.visibility-toggle.hidden {
|
|
background: #f1f5f9;
|
|
color: #94a3b8;
|
|
}
|
|
|
|
/* Add/Edit Form Card */
|
|
.form-card {
|
|
background: white;
|
|
border-radius: var(--border-radius);
|
|
box-shadow: var(--card-shadow);
|
|
overflow: hidden;
|
|
position: sticky;
|
|
top: 100px;
|
|
}
|
|
|
|
.form-card-header {
|
|
background: var(--accent-gradient);
|
|
color: white;
|
|
padding: 20px 24px;
|
|
}
|
|
|
|
.form-card-header h5 {
|
|
margin: 0;
|
|
font-weight: 600;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
}
|
|
|
|
.form-card-body {
|
|
padding: 24px;
|
|
}
|
|
|
|
.form-label {
|
|
font-weight: 500;
|
|
color: #475569;
|
|
margin-bottom: 8px;
|
|
}
|
|
|
|
.form-control,
|
|
.form-select {
|
|
border-radius: 10px;
|
|
border: 2px solid #e2e8f0;
|
|
padding: 12px 16px;
|
|
transition: var(--transition);
|
|
}
|
|
|
|
.form-control:focus,
|
|
.form-select:focus {
|
|
border-color: #667eea;
|
|
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
|
|
}
|
|
|
|
/* Icon Picker */
|
|
.icon-picker {
|
|
display: grid;
|
|
grid-template-columns: repeat(6, 1fr);
|
|
gap: 8px;
|
|
max-height: 200px;
|
|
overflow-y: auto;
|
|
padding: 12px;
|
|
background: #f8fafc;
|
|
border-radius: 10px;
|
|
border: 2px solid #e2e8f0;
|
|
}
|
|
|
|
.icon-option {
|
|
width: 40px;
|
|
height: 40px;
|
|
border-radius: 8px;
|
|
border: 2px solid transparent;
|
|
background: white;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
cursor: pointer;
|
|
transition: var(--transition);
|
|
font-size: 1.1rem;
|
|
color: #64748b;
|
|
}
|
|
|
|
.icon-option:hover {
|
|
border-color: #667eea;
|
|
color: #667eea;
|
|
}
|
|
|
|
.icon-option.selected {
|
|
border-color: #667eea;
|
|
background: #667eea;
|
|
color: white;
|
|
}
|
|
|
|
/* Button Styles */
|
|
.btn-save {
|
|
background: var(--accent-gradient);
|
|
border: none;
|
|
color: white;
|
|
padding: 12px 24px;
|
|
border-radius: 10px;
|
|
font-weight: 600;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 8px;
|
|
transition: var(--transition);
|
|
width: 100%;
|
|
}
|
|
|
|
.btn-save:hover {
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4);
|
|
color: white;
|
|
}
|
|
|
|
.btn-cancel {
|
|
background: #f1f5f9;
|
|
border: none;
|
|
color: #64748b;
|
|
padding: 12px 24px;
|
|
border-radius: 10px;
|
|
font-weight: 500;
|
|
width: 100%;
|
|
transition: var(--transition);
|
|
}
|
|
|
|
.btn-cancel:hover {
|
|
background: #e2e8f0;
|
|
color: #334155;
|
|
}
|
|
|
|
/* Empty State */
|
|
.empty-state {
|
|
text-align: center;
|
|
padding: 60px 20px;
|
|
color: #64748b;
|
|
}
|
|
|
|
.empty-state i {
|
|
font-size: 4rem;
|
|
color: #cbd5e1;
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.empty-state h5 {
|
|
margin-bottom: 12px;
|
|
}
|
|
|
|
.empty-state p {
|
|
color: #94a3b8;
|
|
}
|
|
|
|
/* Info Card */
|
|
.info-card {
|
|
background: #eef2ff;
|
|
border: 1px solid #c7d2fe;
|
|
border-radius: 12px;
|
|
padding: 16px 20px;
|
|
margin-bottom: 20px;
|
|
display: flex;
|
|
align-items: flex-start;
|
|
gap: 12px;
|
|
}
|
|
|
|
.info-card i {
|
|
color: #667eea;
|
|
font-size: 1.2rem;
|
|
margin-top: 2px;
|
|
}
|
|
|
|
.info-card p {
|
|
margin: 0;
|
|
color: #4338ca;
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
/* Toast */
|
|
.toast-container {
|
|
z-index: 1100;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="sidebar">
|
|
<div class="sidebar-brand">Sky Art Shop</div>
|
|
<ul class="sidebar-menu">
|
|
<li>
|
|
<a href="/admin/dashboard"
|
|
><i class="bi bi-speedometer2"></i> Dashboard</a
|
|
>
|
|
</li>
|
|
<li>
|
|
<a href="/admin/homepage"
|
|
><i class="bi bi-house"></i> Homepage Editor</a
|
|
>
|
|
</li>
|
|
<li>
|
|
<a href="/admin/products"><i class="bi bi-box"></i> Products</a>
|
|
</li>
|
|
<li>
|
|
<a href="/admin/portfolio"><i class="bi bi-easel"></i> Portfolio</a>
|
|
</li>
|
|
<li>
|
|
<a href="/admin/blog"><i class="bi bi-newspaper"></i> Blog</a>
|
|
</li>
|
|
<li>
|
|
<a href="/admin/pages"
|
|
><i class="bi bi-file-text"></i> Custom Pages</a
|
|
>
|
|
</li>
|
|
<li>
|
|
<a href="/admin/media-library"
|
|
><i class="bi bi-images"></i> Media Library</a
|
|
>
|
|
</li>
|
|
<li>
|
|
<a href="/admin/menu" class="active"
|
|
><i class="bi bi-list"></i> Menu</a
|
|
>
|
|
</li>
|
|
<li>
|
|
<a href="/admin/settings"><i class="bi bi-gear"></i> Settings</a>
|
|
</li>
|
|
<li>
|
|
<a href="/admin/users"><i class="bi bi-people"></i> Users</a>
|
|
</li>
|
|
<li>
|
|
<a href="/admin/customers"
|
|
><i class="bi bi-person-hearts"></i> Customers</a
|
|
>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
|
|
<div class="main-content">
|
|
<div class="top-bar">
|
|
<div>
|
|
<h3><i class="bi bi-list me-2"></i>Menu Manager</h3>
|
|
<p class="mb-0 text-muted">Manage your website navigation menu</p>
|
|
</div>
|
|
<div>
|
|
<button
|
|
class="btn btn-outline-secondary me-2"
|
|
onclick="loadMenuItems()"
|
|
>
|
|
<i class="bi bi-arrow-clockwise"></i>
|
|
</button>
|
|
<button class="btn-logout" onclick="logout()">
|
|
<i class="bi bi-box-arrow-right"></i> Logout
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="info-card">
|
|
<i class="bi bi-info-circle"></i>
|
|
<p>
|
|
Drag and drop menu items to reorder them. Changes are saved
|
|
automatically when you reorder items.
|
|
</p>
|
|
</div>
|
|
|
|
<div class="menu-container">
|
|
<!-- Menu Items List -->
|
|
<div class="menu-list-card">
|
|
<div class="menu-list-header">
|
|
<h5><i class="bi bi-list-ul"></i> Navigation Menu</h5>
|
|
<span class="badge bg-white text-dark" id="menuCount">0 items</span>
|
|
</div>
|
|
<div class="menu-list-body">
|
|
<ul class="menu-items" id="menuItemsList">
|
|
<!-- Menu items will be loaded here -->
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Add/Edit Form -->
|
|
<div class="form-card">
|
|
<div class="form-card-header">
|
|
<h5 id="formTitle">
|
|
<i class="bi bi-plus-circle"></i> Add Menu Item
|
|
</h5>
|
|
</div>
|
|
<div class="form-card-body">
|
|
<form id="menuItemForm">
|
|
<input type="hidden" id="menuItemId" />
|
|
|
|
<div class="mb-3">
|
|
<label for="menuItemName" class="form-label"
|
|
>Menu Label *</label
|
|
>
|
|
<input
|
|
type="text"
|
|
class="form-control"
|
|
id="menuItemName"
|
|
placeholder="e.g., Products"
|
|
required
|
|
/>
|
|
</div>
|
|
|
|
<div class="mb-3">
|
|
<label for="menuItemUrl" class="form-label">URL / Link *</label>
|
|
<input
|
|
type="text"
|
|
class="form-control"
|
|
id="menuItemUrl"
|
|
placeholder="e.g., /products"
|
|
required
|
|
/>
|
|
<div class="form-text">
|
|
Use relative paths like /products or full URLs
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mb-3">
|
|
<label class="form-label">Icon</label>
|
|
<div class="icon-picker" id="iconPicker">
|
|
<!-- Icons will be loaded here -->
|
|
</div>
|
|
<input type="hidden" id="menuItemIcon" />
|
|
</div>
|
|
|
|
<div class="mb-4">
|
|
<div class="form-check form-switch">
|
|
<input
|
|
class="form-check-input"
|
|
type="checkbox"
|
|
id="menuItemVisible"
|
|
checked
|
|
style="width: 48px; height: 26px"
|
|
/>
|
|
<label class="form-check-label ms-2" for="menuItemVisible">
|
|
<strong>Visible</strong>
|
|
<span class="text-muted d-block" style="font-size: 0.85rem"
|
|
>Show in navigation menu</span
|
|
>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="d-grid gap-2">
|
|
<button type="submit" class="btn btn-save">
|
|
<i class="bi bi-check-lg"></i>
|
|
<span id="submitBtnText">Add Menu Item</span>
|
|
</button>
|
|
<button
|
|
type="button"
|
|
class="btn btn-cancel"
|
|
onclick="resetForm()"
|
|
id="cancelBtn"
|
|
style="display: none"
|
|
>
|
|
<i class="bi bi-x-lg me-1"></i>Cancel Edit
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Toast Container -->
|
|
<div
|
|
class="toast-container position-fixed bottom-0 end-0 p-3"
|
|
id="toastContainer"
|
|
></div>
|
|
|
|
<!-- Scripts -->
|
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
|
<script src="/admin/js/auth.js"></script>
|
|
<script>
|
|
// State
|
|
let menuItems = [];
|
|
let selectedIcon = "";
|
|
let draggedItem = null;
|
|
|
|
// Available icons
|
|
const availableIcons = [
|
|
"bi-house",
|
|
"bi-box",
|
|
"bi-cart",
|
|
"bi-bag",
|
|
"bi-shop",
|
|
"bi-grid",
|
|
"bi-collection",
|
|
"bi-star",
|
|
"bi-heart",
|
|
"bi-tag",
|
|
"bi-bookmark",
|
|
"bi-file-text",
|
|
"bi-newspaper",
|
|
"bi-camera",
|
|
"bi-images",
|
|
"bi-easel",
|
|
"bi-palette",
|
|
"bi-brush",
|
|
"bi-pencil",
|
|
"bi-person",
|
|
"bi-people",
|
|
"bi-envelope",
|
|
"bi-telephone",
|
|
"bi-geo-alt",
|
|
"bi-map",
|
|
"bi-globe",
|
|
"bi-info-circle",
|
|
"bi-question-circle",
|
|
"bi-shield-check",
|
|
"bi-truck",
|
|
"bi-arrow-return-left",
|
|
"bi-credit-card",
|
|
"bi-gift",
|
|
"bi-award",
|
|
"bi-trophy",
|
|
];
|
|
|
|
// Initialize
|
|
document.addEventListener("DOMContentLoaded", () => {
|
|
loadMenuItems();
|
|
setupIconPicker();
|
|
setupFormSubmit();
|
|
});
|
|
|
|
// Load menu items
|
|
async function loadMenuItems() {
|
|
try {
|
|
const response = await fetch("/api/menu", {
|
|
credentials: "include",
|
|
});
|
|
|
|
if (!response.ok) throw new Error("Failed to load menu items");
|
|
|
|
menuItems = await response.json();
|
|
renderMenuItems();
|
|
} catch (error) {
|
|
console.error("Error loading menu:", error);
|
|
showToast("Failed to load menu items", "error");
|
|
}
|
|
}
|
|
|
|
// Render menu items
|
|
function renderMenuItems() {
|
|
const list = document.getElementById("menuItemsList");
|
|
document.getElementById("menuCount").textContent =
|
|
`${menuItems.length} items`;
|
|
|
|
if (menuItems.length === 0) {
|
|
list.innerHTML = `
|
|
<div class="empty-state">
|
|
<i class="bi bi-menu-button-wide"></i>
|
|
<h5>No Menu Items</h5>
|
|
<p>Add your first menu item using the form</p>
|
|
</div>
|
|
`;
|
|
return;
|
|
}
|
|
|
|
list.innerHTML = menuItems
|
|
.map(
|
|
(item, index) => `
|
|
<li class="menu-item"
|
|
draggable="true"
|
|
data-id="${item.id}"
|
|
data-index="${index}"
|
|
ondragstart="handleDragStart(event, ${index})"
|
|
ondragover="handleDragOver(event)"
|
|
ondragenter="handleDragEnter(event)"
|
|
ondragleave="handleDragLeave(event)"
|
|
ondrop="handleDrop(event, ${index})"
|
|
ondragend="handleDragEnd(event)">
|
|
<span class="drag-handle">
|
|
<i class="bi bi-grip-vertical"></i>
|
|
</span>
|
|
<div class="menu-item-icon">
|
|
<i class="bi ${item.icon || "bi-link-45deg"}"></i>
|
|
</div>
|
|
<div class="menu-item-content">
|
|
<div class="menu-item-name">${escapeHtml(item.name)}</div>
|
|
<div class="menu-item-url">${escapeHtml(item.url)}</div>
|
|
</div>
|
|
<div class="menu-item-actions">
|
|
<button class="visibility-toggle ${item.isvisible ? "visible" : "hidden"}"
|
|
onclick="toggleVisibility(${item.id})"
|
|
title="${item.isvisible ? "Click to hide" : "Click to show"}">
|
|
<i class="bi ${item.isvisible ? "bi-eye" : "bi-eye-slash"}"></i>
|
|
</button>
|
|
<button class="btn-edit-item" onclick="editMenuItem(${item.id})" title="Edit">
|
|
<i class="bi bi-pencil"></i>
|
|
</button>
|
|
<button class="btn-delete-item" onclick="deleteMenuItem(${item.id})" title="Delete">
|
|
<i class="bi bi-trash"></i>
|
|
</button>
|
|
</div>
|
|
</li>
|
|
`,
|
|
)
|
|
.join("");
|
|
}
|
|
|
|
// Setup icon picker
|
|
function setupIconPicker() {
|
|
const picker = document.getElementById("iconPicker");
|
|
picker.innerHTML = availableIcons
|
|
.map(
|
|
(icon) => `
|
|
<div class="icon-option" data-icon="${icon}" onclick="selectIcon('${icon}')">
|
|
<i class="bi ${icon}"></i>
|
|
</div>
|
|
`,
|
|
)
|
|
.join("");
|
|
}
|
|
|
|
// Select icon
|
|
function selectIcon(icon) {
|
|
selectedIcon = icon;
|
|
document.getElementById("menuItemIcon").value = icon;
|
|
|
|
document.querySelectorAll(".icon-option").forEach((opt) => {
|
|
opt.classList.remove("selected");
|
|
});
|
|
document
|
|
.querySelector(`.icon-option[data-icon="${icon}"]`)
|
|
?.classList.add("selected");
|
|
}
|
|
|
|
// Setup form submit
|
|
function setupFormSubmit() {
|
|
document
|
|
.getElementById("menuItemForm")
|
|
.addEventListener("submit", async (e) => {
|
|
e.preventDefault();
|
|
|
|
const id = document.getElementById("menuItemId").value;
|
|
const data = {
|
|
name: document.getElementById("menuItemName").value,
|
|
url: document.getElementById("menuItemUrl").value,
|
|
icon: selectedIcon || "bi-link-45deg",
|
|
isvisible: document.getElementById("menuItemVisible").checked,
|
|
display_order: id
|
|
? menuItems.find((m) => m.id == id)?.display_order
|
|
: menuItems.length,
|
|
};
|
|
|
|
try {
|
|
const url = id ? `/api/menu/${id}` : "/api/menu";
|
|
const method = id ? "PUT" : "POST";
|
|
|
|
const response = await fetch(url, {
|
|
method,
|
|
headers: { "Content-Type": "application/json" },
|
|
credentials: "include",
|
|
body: JSON.stringify(data),
|
|
});
|
|
|
|
if (!response.ok) throw new Error("Failed to save menu item");
|
|
|
|
showToast(
|
|
`Menu item ${id ? "updated" : "added"} successfully!`,
|
|
"success",
|
|
);
|
|
resetForm();
|
|
loadMenuItems();
|
|
} catch (error) {
|
|
console.error("Error saving menu item:", error);
|
|
showToast("Failed to save menu item", "error");
|
|
}
|
|
});
|
|
}
|
|
|
|
// Edit menu item
|
|
function editMenuItem(id) {
|
|
const item = menuItems.find((m) => m.id === id);
|
|
if (!item) return;
|
|
|
|
document.getElementById("menuItemId").value = item.id;
|
|
document.getElementById("menuItemName").value = item.name;
|
|
document.getElementById("menuItemUrl").value = item.url;
|
|
document.getElementById("menuItemVisible").checked = item.isvisible;
|
|
|
|
if (item.icon) {
|
|
selectIcon(item.icon);
|
|
}
|
|
|
|
document.getElementById("formTitle").innerHTML =
|
|
'<i class="bi bi-pencil"></i> Edit Menu Item';
|
|
document.getElementById("submitBtnText").textContent =
|
|
"Update Menu Item";
|
|
document.getElementById("cancelBtn").style.display = "block";
|
|
|
|
// Scroll to form on mobile
|
|
document
|
|
.querySelector(".form-card")
|
|
.scrollIntoView({ behavior: "smooth" });
|
|
}
|
|
|
|
// Delete menu item
|
|
async function deleteMenuItem(id) {
|
|
if (!confirm("Are you sure you want to delete this menu item?")) return;
|
|
|
|
try {
|
|
const response = await fetch(`/api/menu/${id}`, {
|
|
method: "DELETE",
|
|
credentials: "include",
|
|
});
|
|
|
|
if (!response.ok) throw new Error("Failed to delete menu item");
|
|
|
|
showToast("Menu item deleted successfully!", "success");
|
|
loadMenuItems();
|
|
} catch (error) {
|
|
console.error("Error deleting menu item:", error);
|
|
showToast("Failed to delete menu item", "error");
|
|
}
|
|
}
|
|
|
|
// Toggle visibility
|
|
async function toggleVisibility(id) {
|
|
const item = menuItems.find((m) => m.id === id);
|
|
if (!item) return;
|
|
|
|
try {
|
|
const response = await fetch(`/api/menu/${id}`, {
|
|
method: "PUT",
|
|
headers: { "Content-Type": "application/json" },
|
|
credentials: "include",
|
|
body: JSON.stringify({ ...item, isvisible: !item.isvisible }),
|
|
});
|
|
|
|
if (!response.ok) throw new Error("Failed to update visibility");
|
|
|
|
showToast(
|
|
`Menu item ${!item.isvisible ? "shown" : "hidden"}`,
|
|
"success",
|
|
);
|
|
loadMenuItems();
|
|
} catch (error) {
|
|
console.error("Error toggling visibility:", error);
|
|
showToast("Failed to update visibility", "error");
|
|
}
|
|
}
|
|
|
|
// Reset form
|
|
function resetForm() {
|
|
document.getElementById("menuItemForm").reset();
|
|
document.getElementById("menuItemId").value = "";
|
|
selectedIcon = "";
|
|
document
|
|
.querySelectorAll(".icon-option")
|
|
.forEach((opt) => opt.classList.remove("selected"));
|
|
|
|
document.getElementById("formTitle").innerHTML =
|
|
'<i class="bi bi-plus-circle"></i> Add Menu Item';
|
|
document.getElementById("submitBtnText").textContent = "Add Menu Item";
|
|
document.getElementById("cancelBtn").style.display = "none";
|
|
}
|
|
|
|
// Drag and drop handlers
|
|
function handleDragStart(e, index) {
|
|
draggedItem = index;
|
|
e.target.classList.add("dragging");
|
|
e.dataTransfer.effectAllowed = "move";
|
|
}
|
|
|
|
function handleDragOver(e) {
|
|
e.preventDefault();
|
|
e.dataTransfer.dropEffect = "move";
|
|
}
|
|
|
|
function handleDragEnter(e) {
|
|
e.preventDefault();
|
|
const item = e.target.closest(".menu-item");
|
|
if (item) item.classList.add("drag-over");
|
|
}
|
|
|
|
function handleDragLeave(e) {
|
|
const item = e.target.closest(".menu-item");
|
|
if (item) item.classList.remove("drag-over");
|
|
}
|
|
|
|
function handleDrop(e, dropIndex) {
|
|
e.preventDefault();
|
|
const item = e.target.closest(".menu-item");
|
|
if (item) item.classList.remove("drag-over");
|
|
|
|
if (draggedItem === null || draggedItem === dropIndex) return;
|
|
|
|
// Reorder array
|
|
const [movedItem] = menuItems.splice(draggedItem, 1);
|
|
menuItems.splice(dropIndex, 0, movedItem);
|
|
|
|
// Update display orders
|
|
menuItems.forEach((item, index) => {
|
|
item.display_order = index;
|
|
});
|
|
|
|
renderMenuItems();
|
|
saveOrder();
|
|
}
|
|
|
|
function handleDragEnd(e) {
|
|
e.target.classList.remove("dragging");
|
|
document.querySelectorAll(".menu-item").forEach((item) => {
|
|
item.classList.remove("drag-over");
|
|
});
|
|
draggedItem = null;
|
|
}
|
|
|
|
// Save order
|
|
async function saveOrder() {
|
|
try {
|
|
const response = await fetch("/api/menu/reorder", {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
credentials: "include",
|
|
body: JSON.stringify(
|
|
menuItems.map((item, index) => ({
|
|
id: item.id,
|
|
display_order: index,
|
|
})),
|
|
),
|
|
});
|
|
|
|
if (!response.ok) throw new Error("Failed to save order");
|
|
|
|
showToast("Menu order saved!", "success");
|
|
} catch (error) {
|
|
console.error("Error saving order:", error);
|
|
showToast("Failed to save order", "error");
|
|
}
|
|
}
|
|
|
|
// Show toast
|
|
function showToast(message, type = "info") {
|
|
const container = document.getElementById("toastContainer");
|
|
|
|
const bgClass =
|
|
{
|
|
success: "bg-success",
|
|
error: "bg-danger",
|
|
warning: "bg-warning",
|
|
info: "bg-info",
|
|
}[type] || "bg-info";
|
|
|
|
const icon =
|
|
{
|
|
success: "bi-check-circle",
|
|
error: "bi-x-circle",
|
|
warning: "bi-exclamation-triangle",
|
|
info: "bi-info-circle",
|
|
}[type] || "bi-info-circle";
|
|
|
|
const toast = document.createElement("div");
|
|
toast.className = `toast show ${bgClass} text-white`;
|
|
toast.innerHTML = `
|
|
<div class="d-flex align-items-center p-3">
|
|
<i class="bi ${icon} me-2"></i>
|
|
<div class="flex-grow-1">${escapeHtml(message)}</div>
|
|
<button type="button" class="btn-close btn-close-white" onclick="this.parentElement.parentElement.remove()"></button>
|
|
</div>
|
|
`;
|
|
|
|
container.appendChild(toast);
|
|
setTimeout(() => toast.remove(), 4000);
|
|
}
|
|
|
|
// Escape HTML
|
|
function escapeHtml(text) {
|
|
if (!text) return "";
|
|
const div = document.createElement("div");
|
|
div.textContent = text;
|
|
return div.innerHTML;
|
|
}
|
|
|
|
// Logout
|
|
function logout() {
|
|
window.location.href = "/admin/logout";
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|