// ── Admin Dashboard — The Roasted Bean ─────────────────────────────────────
let menuData = null;
let editingItemId = null;
document.addEventListener('DOMContentLoaded', () => checkAuth());
// ── Auth ────────────────────────────────────────────────────────────────────
async function checkAuth() {
try {
const res = await fetch('/api/auth');
const data = await res.json();
data.authenticated ? showDashboard() : showLogin();
} catch { showLogin(); }
}
function showLogin() {
document.getElementById('loginScreen').style.display = '';
document.getElementById('dashboard').style.display = 'none';
document.getElementById('loginForm').addEventListener('submit', handleLogin);
}
function showDashboard() {
document.getElementById('loginScreen').style.display = 'none';
document.getElementById('dashboard').style.display = '';
setupDashboard();
}
async function handleLogin(e) {
e.preventDefault();
const pw = document.getElementById('password').value;
const errEl = document.getElementById('loginError');
try {
const res = await fetch('/api/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ password: pw })
});
const data = await res.json();
if (data.success) { errEl.textContent = ''; showDashboard(); }
else { errEl.textContent = data.error || 'Invalid password'; }
} catch { errEl.textContent = 'Connection error. Server running?'; }
}
// ── Dashboard Setup ─────────────────────────────────────────────────────────
async function setupDashboard() {
await loadMenuData();
setupNav();
setupModal();
setupForms();
showSection('menu');
document.getElementById('logoutBtn').addEventListener('click', async () => {
await fetch('/api/logout', { method: 'POST' });
location.reload();
});
document.getElementById('addItemBtn').addEventListener('click', () => openModal());
}
async function loadMenuData() {
const res = await fetch('/api/menu');
menuData = await res.json();
}
// ── Navigation ──────────────────────────────────────────────────────────────
function setupNav() {
document.getElementById('topbarNav').addEventListener('click', (e) => {
const btn = e.target.closest('.tb-nav-item');
if (!btn) return;
document.querySelectorAll('.tb-nav-item').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
showSection(btn.dataset.section);
});
}
function showSection(section) {
document.querySelectorAll('.panel').forEach(p => p.style.display = 'none');
switch (section) {
case 'menu':
document.getElementById('menuSection').style.display = '';
renderTable();
break;
case 'categories':
document.getElementById('categoriesSection').style.display = '';
renderCategories();
break;
case 'info':
document.getElementById('infoSection').style.display = '';
renderInfo();
break;
case 'tables':
document.getElementById('tablesSection').style.display = '';
setupTablesSection();
break;
case 'settings':
document.getElementById('settingsSection').style.display = '';
break;
}
}
// ── Table ───────────────────────────────────────────────────────────────────
function renderTable() {
const tbody = document.getElementById('menuTableBody');
const empty = document.getElementById('menuEmpty');
const items = menuData?.items || [];
if (items.length === 0) { tbody.innerHTML = ''; empty.style.display = ''; return; }
empty.style.display = 'none';
const cats = {};
(menuData.categories || []).forEach(c => { cats[c.id] = c.name; });
tbody.innerHTML = items.map(item => {
const hasImg = item.image && item.image.startsWith('http');
const thumbImg = hasImg
? ``
: '';
const thumbSvg = adminSvgIcon(item.category);
return `
Generating QR codes…
'; try { const res = await fetch('/api/qr'); if (!res.ok) throw new Error('Failed'); const qrs = await res.json(); if (qrs.length === 0) { grid.innerHTML = 'No tables configured. Set the number above first.
'; return; } grid.innerHTML = qrs.map(q => `Failed to generate QR codes.
'; } } async function renderOrders() { const container = document.getElementById('ordersContainer'); try { const res = await fetch('/api/orders'); if (!res.ok) throw new Error('Failed'); const orders = await res.json(); // Only show active orders (hide cancelled & paid) const activeOrders = orders.filter(o => o.status === 'pending' || o.status === 'served'); if (activeOrders.length === 0) { container.innerHTML = 'No active orders.
'; return; } const statusLabel = { pending: 'New', served: 'Served', paid: 'Paid', cancelled: 'Cancelled' }; const statusClass = { pending: 'badge-order-new', served: 'badge-order-served', paid: 'badge-order-paid', cancelled: 'badge-order-cancelled' }; container.innerHTML = activeOrders.map(o => `📝 ${esc(o.note)}
` : ''}Total: ₹${o.total.toFixed(0)}
Failed to load orders.
'; } } function timeAgo(iso) { const diff = Math.floor((Date.now() - new Date(iso).getTime()) / 1000); if (diff < 60) return 'just now'; if (diff < 3600) return `${Math.floor(diff / 60)}m ago`; if (diff < 86400) return `${Math.floor(diff / 3600)}h ago`; const d = new Date(iso); return d.toLocaleDateString('en-US', { month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit' }); } // ── Toast ─────────────────────────────────────────────────────────────────── function showToast(msg, type = 'success') { const container = document.getElementById('toastContainer'); if (!container) return; const toast = document.createElement('div'); toast.className = `toast ${type}`; toast.innerHTML = `${type === 'success' ? '✓' : '✗'} ${msg}`; container.appendChild(toast); setTimeout(() => { toast.style.opacity = '0'; toast.style.transition = 'opacity 0.25s'; setTimeout(() => toast.remove(), 250); }, 2800); } // ── Utils ─────────────────────────────────────────────────────────────────── function esc(str) { const div = document.createElement('div'); div.textContent = str || ''; return div.innerHTML; } function adminSvgIcon(category) { const svgs = { 'coffee': '', 'tea': '', 'pastries': '', 'food': '' }; return svgs[category] || svgs['coffee']; }