fix(admin): sidebar hamburger menu for mobile, remove auto Cantaloupe sync
- Replace broken icon-only sidebar collapse with off-canvas hamburger menu that slides in from left (hidden by default on <768px) - Add sidebar overlay backdrop, auto-close on nav/logout - Rename 'Cantaloupe Sync' to 'Import' in sidebar and 'Excel Import' in page - Remove 'Sync Now' button and backend subprocess auto-export entirely - Keep manual Excel file upload as the only import path - Update empty-state text and upload handler to work without Sync Now btn
This commit is contained in:
+5
-37
@@ -386,7 +386,7 @@ async def run_sync(
|
||||
file_path: Optional[str] = Query(None, description="Server-side path to Excel file"),
|
||||
):
|
||||
"""
|
||||
Run a full Cantaloupe sync from an Excel file.
|
||||
Import an Excel file into a staged batch for review.
|
||||
|
||||
Accepts either:
|
||||
- An uploaded Excel file (multipart form)
|
||||
@@ -416,42 +416,10 @@ async def run_sync(
|
||||
excel_path = str(p)
|
||||
display_path = file_path
|
||||
else:
|
||||
# Try subprocess call to cantaloupe export
|
||||
try:
|
||||
import subprocess
|
||||
import sys
|
||||
cantaloupe_dir = os.path.expanduser("~/projects/cantaloupe-downloader")
|
||||
result = subprocess.run(
|
||||
[sys.executable, "-m", "cantaloupe", "export", "--scheduled"],
|
||||
capture_output=True, text=True, timeout=120,
|
||||
cwd=cantaloupe_dir,
|
||||
)
|
||||
if result.returncode != 0:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail=f"Cantaloupe export failed: {result.stderr[:500]}"
|
||||
)
|
||||
# Parse output to find the exported file path
|
||||
for line in result.stdout.splitlines() + result.stderr.splitlines():
|
||||
line = line.strip()
|
||||
if line and Path(line).exists() and line.endswith((".xlsx", ".xls")):
|
||||
excel_path = line
|
||||
display_path = line
|
||||
break
|
||||
else:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="Cantaloupe export ran but produced no recognizable file path. Provide file upload or file_path parameter."
|
||||
)
|
||||
except FileNotFoundError:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="No file provided and cantaloupe module not found. Upload an Excel file or provide file_path."
|
||||
)
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=f"Cantaloupe export error: {e}")
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="No file provided. Upload an Excel file to import asset data."
|
||||
)
|
||||
|
||||
conn = _get_db()
|
||||
try:
|
||||
|
||||
+68
-46
@@ -514,30 +514,51 @@
|
||||
.hidden { display: none !important; }
|
||||
.mono { font-family: 'Courier New', monospace; font-size: 12px; }
|
||||
|
||||
/* Responsive: collapse sidebar on narrow screens */
|
||||
/* ═══════════════════════════════════════════════════════════════════════
|
||||
HAMBURGER BUTTON & SIDEBAR OVERLAY (mobile)
|
||||
═══════════════════════════════════════════════════════════════════════ */
|
||||
.hamburger {
|
||||
display: none;
|
||||
background: none; border: none; color: var(--text); font-size: 24px;
|
||||
cursor: pointer; padding: 6px; margin-right: 10px;
|
||||
line-height: 1; flex-shrink: 0;
|
||||
}
|
||||
.hamburger:hover { color: var(--accent2); }
|
||||
.sidebar-overlay {
|
||||
display: none;
|
||||
position: fixed; inset: 0; background: rgba(0,0,0,0.5);
|
||||
z-index: 99; opacity: 0; transition: opacity 0.25s;
|
||||
pointer-events: none;
|
||||
}
|
||||
.sidebar-overlay.open { opacity: 1; pointer-events: auto; }
|
||||
|
||||
/* Responsive: off-canvas sidebar on narrow screens */
|
||||
@media (max-width: 768px) {
|
||||
.hamburger { display: inline-flex; align-items: center; }
|
||||
.sidebar-overlay { display: block; }
|
||||
|
||||
.sidebar {
|
||||
width: 56px;
|
||||
overflow: hidden;
|
||||
transform: translateX(-100%);
|
||||
transition: transform 0.25s ease;
|
||||
z-index: 100;
|
||||
}
|
||||
.sidebar .sidebar-header span,
|
||||
.sidebar .sidebar-user,
|
||||
.sidebar .nav-item span,
|
||||
.sidebar .nav-item .ni-badge,
|
||||
.sidebar .sidebar-footer span,
|
||||
.sidebar .sidebar-footer .logout-btn { display: none; }
|
||||
.sidebar .nav-item { justify-content: center; padding: 11px 0; }
|
||||
.sidebar .nav-item .ni-icon { font-size: 20px; width: auto; }
|
||||
.sidebar .sidebar-header { justify-content: center; }
|
||||
.sidebar .sidebar-header .sh-icon { font-size: 22px; }
|
||||
.main { margin-left: 56px; padding: 16px; max-width: calc(100vw - 56px); }
|
||||
.sidebar.open { transform: translateX(0); }
|
||||
|
||||
.main {
|
||||
margin-left: 0;
|
||||
padding: 16px;
|
||||
max-width: 100vw;
|
||||
}
|
||||
|
||||
.page-title .pt-actions .btn .btn-label { display: none; }
|
||||
.page-title .pt-actions .btn { min-width: 36px; justify-content: center; }
|
||||
|
||||
/* Stats grid: tighter cards */
|
||||
.stats-grid { grid-template-columns: repeat(auto-fill, minmax(100px, 1fr)); gap: 6px; }
|
||||
.stat-card { padding: 10px; }
|
||||
.stat-card .stat-value { font-size: 22px; }
|
||||
|
||||
/* Cantaloupe Sync batch rows stack */
|
||||
/* Import batch rows stack */
|
||||
.cs-batch-row .csb-status { width: 60px; }
|
||||
.cs-batch-row .csb-time { width: auto; font-size: 11px; }
|
||||
.cs-batch-row .csb-rows { width: auto; text-align: left; margin-left: auto; }
|
||||
@@ -567,7 +588,7 @@
|
||||
.page-title .pt-actions { width: 100%; }
|
||||
.page-title .pt-actions button { flex: 1; }
|
||||
|
||||
/* Cantaloupe diff badges stack 2x2 */
|
||||
/* Import diff badges stack 2x2 */
|
||||
.cs-diff-summary { grid-template-columns: repeat(2, 1fr); }
|
||||
.cs-diff-badge { padding: 8px; }
|
||||
.cs-diff-badge .csd-val { font-size: 18px; }
|
||||
@@ -586,7 +607,7 @@
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════════════════
|
||||
CANTALOUPE SYNC
|
||||
EXCEL IMPORT
|
||||
═══════════════════════════════════════════════════════════════════════ */
|
||||
.cs-diff-summary {
|
||||
display: grid;
|
||||
@@ -657,8 +678,11 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ═══ SIDEBAR OVERLAY (mobile) ═══ -->
|
||||
<div id="sidebarOverlay" class="sidebar-overlay" onclick="toggleSidebar()"></div>
|
||||
|
||||
<!-- ═══ SIDEBAR ═══ -->
|
||||
<div class="sidebar">
|
||||
<div class="sidebar" id="sidebar"> <!-- add hamburger to title bar-->
|
||||
<div class="sidebar-header">
|
||||
<span class="sh-icon">📊</span>
|
||||
<span>Canteen Admin</span>
|
||||
@@ -690,7 +714,7 @@
|
||||
<span class="ni-icon">📤</span><span>Export</span>
|
||||
</button>
|
||||
<button class="nav-item" data-page="cantaloupe" onclick="switchPage('cantaloupe')">
|
||||
<span class="ni-icon">🔄</span><span>Cantaloupe Sync</span>
|
||||
<span class="ni-icon">📥</span><span>Import</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="sidebar-footer">
|
||||
@@ -702,6 +726,11 @@
|
||||
<!-- ═══ MAIN CONTENT ═══ -->
|
||||
<div class="main">
|
||||
|
||||
<!-- Hamburger button (mobile) -->
|
||||
<div style="display:flex;align-items:center;margin-bottom:12px;" class="mobile-title-row">
|
||||
<button class="hamburger" onclick="toggleSidebar()" aria-label="Toggle menu">☰</button>
|
||||
</div>
|
||||
|
||||
<!-- ── DASHBOARD ── -->
|
||||
<div id="pageDashboard" class="page">
|
||||
<div class="page-title">
|
||||
@@ -858,14 +887,13 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── CANTALOUPE SYNC ── -->
|
||||
<!-- ── EXCEL IMPORT ── -->
|
||||
<div id="pageCantaloupe" class="page hidden">
|
||||
<div class="page-title">
|
||||
<span>Cantaloupe Sync</span>
|
||||
<span>Excel Import</span>
|
||||
<div class="pt-actions">
|
||||
<button class="btn btn-sm btn-primary" onclick="runSync()" id="csSyncBtn">🔄 Sync Now</button>
|
||||
<input type="file" id="csFileInput" accept=".xlsx,.xls" style="display:none" onchange="uploadSyncFile(event)">
|
||||
<button class="btn btn-sm btn-outline" onclick="document.getElementById('csFileInput').click()">📤 Upload Excel</button>
|
||||
<button class="btn btn-sm btn-primary" onclick="document.getElementById('csFileInput').click()">📤 Upload Excel</button>
|
||||
<button class="btn btn-sm btn-outline" onclick="loadCantaloupeSync()">↻ Refresh</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -969,6 +997,12 @@ function updateUserUI() {
|
||||
if (logoutBtn) logoutBtn.style.display = isLoggedIn() ? 'block' : 'none';
|
||||
}
|
||||
|
||||
// ── Sidebar toggle (mobile) ──
|
||||
function toggleSidebar() {
|
||||
document.getElementById('sidebar').classList.toggle('open');
|
||||
document.getElementById('sidebarOverlay').classList.toggle('open');
|
||||
}
|
||||
|
||||
// ═════════════════════════════════════════════════════════════════════════════
|
||||
// TOAST & MODAL
|
||||
// ═════════════════════════════════════════════════════════════════════════════
|
||||
@@ -1009,6 +1043,9 @@ function switchPage(page) {
|
||||
document.querySelectorAll('.nav-item').forEach(n => n.classList.remove('active'));
|
||||
const navItem = document.querySelector(`.nav-item[data-page="${page}"]`);
|
||||
if (navItem) navItem.classList.add('active');
|
||||
// Close sidebar on mobile after nav
|
||||
document.getElementById('sidebar')?.classList.remove('open');
|
||||
document.getElementById('sidebarOverlay')?.classList.remove('open');
|
||||
// Lazy load
|
||||
if (page === 'dashboard') loadDashboard();
|
||||
else if (page === 'settings') renderAllSettings();
|
||||
@@ -1062,6 +1099,9 @@ function doLogout() {
|
||||
localStorage.removeItem('adminToken');
|
||||
document.getElementById('loginOverlay').classList.remove('hidden');
|
||||
updateUserUI();
|
||||
// Close sidebar on mobile
|
||||
document.getElementById('sidebar')?.classList.remove('open');
|
||||
document.getElementById('sidebarOverlay')?.classList.remove('open');
|
||||
}
|
||||
|
||||
async function initAuth() {
|
||||
@@ -2099,7 +2139,7 @@ async function loadCantaloupeSync() {
|
||||
function renderCsOverview() {
|
||||
const el = document.getElementById('csOverviewContent');
|
||||
if (!csLatestBatch) {
|
||||
el.innerHTML = '<div class="empty-state"><div class="es-icon">🔄</div>No sync batches yet. Click "Sync Now" to import data.</div>';
|
||||
el.innerHTML = '<div class="empty-state"><div class="es-icon">📥</div>No imports yet. Upload an Excel file to import asset data.</div>';
|
||||
return;
|
||||
}
|
||||
const b = csLatestBatch;
|
||||
@@ -2305,32 +2345,15 @@ async function toggleBatchHistory(batchId) {
|
||||
}
|
||||
}
|
||||
|
||||
// ── Sync Now ──
|
||||
async function runSync() {
|
||||
const btn = document.getElementById('csSyncBtn');
|
||||
btn.disabled = true;
|
||||
btn.innerHTML = '<div class="spinner"></div> Syncing...';
|
||||
try {
|
||||
const result = await api('/api/admin/cantaloupe/sync', { method: 'POST' });
|
||||
showToast('Sync completed — batch #' + result.id);
|
||||
await loadCantaloupeSync();
|
||||
} catch (e) {
|
||||
showToast('Sync failed: ' + e.message, true);
|
||||
} finally {
|
||||
btn.disabled = false;
|
||||
btn.innerHTML = '🔄 Sync Now';
|
||||
}
|
||||
}
|
||||
|
||||
// ── Upload Excel File ──
|
||||
let _csUploading = false;
|
||||
async function uploadSyncFile(event) {
|
||||
const file = event.target.files?.[0];
|
||||
if (!file) return;
|
||||
if (_csUploading) return;
|
||||
_csUploading = true;
|
||||
const fd = new FormData();
|
||||
fd.append('file', file);
|
||||
const btn = document.getElementById('csSyncBtn');
|
||||
btn.disabled = true;
|
||||
btn.innerHTML = '<div class="spinner"></div> Uploading...';
|
||||
try {
|
||||
const result = await api('/api/admin/cantaloupe/sync', { method: 'POST', body: fd });
|
||||
showToast('Upload processed — batch #' + result.id);
|
||||
@@ -2338,8 +2361,7 @@ async function uploadSyncFile(event) {
|
||||
} catch (e) {
|
||||
showToast('Upload failed: ' + e.message, true);
|
||||
} finally {
|
||||
btn.disabled = false;
|
||||
btn.innerHTML = '🔄 Sync Now';
|
||||
_csUploading = false;
|
||||
event.target.value = ''; // reset so same file can be re-picked
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user