grubflip/admin/orders.html
Sarah 960391c4f4 Add admin restaurant profile, settings, and update trades/orders pages
Complete admin dashboard with all pages: profile editor (cover photo,
business hours, social links), settings (account, notifications, security),
enhanced trades view with detail modal, and orders with status workflow.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 15:41:45 +00:00

220 lines
10 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Grubflip Admin — Orders</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Plus+Jakarta+Sans:wght@600;700;800&display=swap" rel="stylesheet">
<link rel="stylesheet" href="css/tokens.css">
<link rel="stylesheet" href="css/admin.css">
</head>
<body>
<div class="admin-layout">
<div class="sidebar-overlay" id="sidebarOverlay"></div>
<aside class="sidebar" id="sidebar" role="navigation" aria-label="Admin navigation">
<div class="sidebar-brand">
<div class="logo-icon">GF</div>
<span>Grubflip</span>
</div>
<nav class="sidebar-nav">
<div class="sidebar-section-title">Main</div>
<a href="dashboard.html" class="sidebar-link"><span class="icon">&#x1f4ca;</span> Dashboard</a>
<a href="meals.html" class="sidebar-link"><span class="icon">&#x1f354;</span> Meals</a>
<a href="orders.html" class="sidebar-link active"><span class="icon">&#x1f4e6;</span> Orders</a>
<a href="trades.html" class="sidebar-link"><span class="icon">&#x1f504;</span> Trades</a>
<div class="sidebar-section-title">Settings</div>
<a href="profile.html" class="sidebar-link"><span class="icon">&#x1f3ea;</span> Restaurant Profile</a>
<a href="settings.html" class="sidebar-link"><span class="icon">&#x2699;&#xfe0f;</span> Settings</a>
</nav>
<div class="sidebar-footer">
<div class="sidebar-user">
<div class="avatar" id="userAvatar">DR</div>
<div class="user-info">
<div class="user-name" id="userName">Demo Restaurant</div>
<div class="user-role" id="userRole">Owner</div>
</div>
</div>
</div>
</aside>
<main class="main-content">
<header class="topbar">
<div class="topbar-left">
<button class="menu-toggle" id="menuToggle" aria-label="Toggle menu">&#9776;</button>
<h1 class="topbar-title">Orders</h1>
</div>
<div class="topbar-right">
<button class="topbar-btn" aria-label="Sign out" id="logoutBtn">&#x23fb;</button>
</div>
</header>
<div class="page-content">
<div class="page-header">
<h1>Orders</h1>
<p class="subtitle">Manage incoming meal orders from customers.</p>
</div>
<!-- Stats -->
<div class="stats-grid" style="margin-bottom:var(--gf-space-6)">
<div class="stat-card">
<div class="stat-icon orange">&#x1f4e6;</div>
<div class="stat-info">
<h3>Total Orders</h3>
<div class="stat-value" id="statTotal">--</div>
</div>
</div>
<div class="stat-card">
<div class="stat-icon yellow">&#x1f373;</div>
<div class="stat-info">
<h3>Preparing</h3>
<div class="stat-value" id="statPreparing">--</div>
</div>
</div>
<div class="stat-card">
<div class="stat-icon green">&#x2705;</div>
<div class="stat-info">
<h3>Fulfilled</h3>
<div class="stat-value" id="statFulfilled">--</div>
</div>
</div>
<div class="stat-card">
<div class="stat-icon blue">&#x1f4b0;</div>
<div class="stat-info">
<h3>Revenue Today</h3>
<div class="stat-value" id="statRevenue">--</div>
</div>
</div>
</div>
<!-- Toolbar -->
<div class="meals-toolbar">
<div class="search-box">
<span class="search-icon">&#x1f50d;</span>
<input type="text" class="form-input" id="searchInput" placeholder="Search orders...">
</div>
<select class="form-select" id="filterStatus" style="width:auto;min-width:8rem">
<option value="">All Status</option>
<option value="new">New</option>
<option value="preparing">Preparing</option>
<option value="ready">Ready</option>
<option value="fulfilled">Fulfilled</option>
<option value="cancelled">Cancelled</option>
</select>
</div>
<!-- Orders Table -->
<div class="card">
<div id="ordersContent">
<div class="loading-overlay"><div class="spinner"></div></div>
</div>
</div>
</div>
</main>
</div>
<div class="toast-container" id="toastContainer"></div>
<script src="js/admin-app.js"></script>
<script>
if (!AdminApp.requireAuth()) throw new Error('Not authenticated');
var user = AdminApp.getUser();
if (user.name) {
document.getElementById('userName').textContent = user.name;
document.getElementById('userAvatar').textContent = user.name.split(' ').map(function(w){ return w[0]; }).join('').slice(0,2).toUpperCase();
}
if (user.role) document.getElementById('userRole').textContent = user.role.charAt(0).toUpperCase() + user.role.slice(1);
AdminApp.initSidebar();
document.getElementById('logoutBtn').addEventListener('click', AdminApp.logout);
var orders = [];
var DEMO_ORDERS = [
{ id: 'ORD-001', customer: 'Jamie L.', meal: 'Spicy Ramen Bowl', qty: 1, total: 14.99, status: 'new', created_at: '2026-03-27T15:30:00Z' },
{ id: 'ORD-002', customer: 'Alex K.', meal: 'Chicken Burrito', qty: 2, total: 25.98, status: 'preparing', created_at: '2026-03-27T15:15:00Z' },
{ id: 'ORD-003', customer: 'Sam R.', meal: 'Veggie Power Bowl', qty: 1, total: 13.49, status: 'ready', created_at: '2026-03-27T14:45:00Z' },
{ id: 'ORD-004', customer: 'Morgan T.', meal: 'BBQ Pulled Pork', qty: 1, total: 11.99, status: 'fulfilled', created_at: '2026-03-27T13:20:00Z' },
{ id: 'ORD-005', customer: 'Riley W.', meal: 'Greek Salad Wrap', qty: 3, total: 31.47, status: 'fulfilled', created_at: '2026-03-27T12:00:00Z' },
{ id: 'ORD-006', customer: 'Casey H.', meal: 'Spicy Ramen Bowl', qty: 1, total: 14.99, status: 'fulfilled', created_at: '2026-03-27T11:30:00Z' },
{ id: 'ORD-007', customer: 'Drew N.', meal: 'Chicken Burrito', qty: 1, total: 12.99, status: 'cancelled', created_at: '2026-03-27T10:45:00Z' },
{ id: 'ORD-008', customer: 'Taylor P.', meal: 'Veggie Power Bowl', qty: 2, total: 26.98, status: 'fulfilled', created_at: '2026-03-26T16:00:00Z' },
];
async function loadOrders() {
try {
var data = await AdminApp.api('/admin/orders');
orders = data.orders || data;
} catch(e) {
orders = DEMO_ORDERS.slice();
}
updateStats();
renderOrders();
}
function updateStats() {
document.getElementById('statTotal').textContent = orders.length;
document.getElementById('statPreparing').textContent = orders.filter(function(o){ return o.status === 'preparing' || o.status === 'new'; }).length;
document.getElementById('statFulfilled').textContent = orders.filter(function(o){ return o.status === 'fulfilled'; }).length;
var todayStr = new Date().toDateString();
var revenue = orders.filter(function(o){ return o.status === 'fulfilled' && new Date(o.created_at).toDateString() === todayStr; })
.reduce(function(sum, o){ return sum + o.total; }, 0);
document.getElementById('statRevenue').textContent = '$' + revenue.toFixed(2);
}
function renderOrders() {
var container = document.getElementById('ordersContent');
var search = document.getElementById('searchInput').value.toLowerCase();
var statusFilter = document.getElementById('filterStatus').value;
var filtered = orders.filter(function(o) {
if (search && (o.customer + ' ' + o.meal + ' ' + o.id).toLowerCase().indexOf(search) === -1) return false;
if (statusFilter && o.status !== statusFilter) return false;
return true;
});
if (filtered.length === 0) {
container.innerHTML = '<div class="empty-state"><div class="icon">&#x1f4e6;</div><h3>No orders found</h3><p>Try adjusting your filters.</p></div>';
return;
}
var statusMap = { 'new': 'badge-info', preparing: 'badge-warning', ready: 'badge-primary', fulfilled: 'badge-success', cancelled: 'badge-default' };
container.innerHTML = '<div class="table-wrapper"><table class="data-table"><thead><tr><th>Order</th><th>Customer</th><th>Meal</th><th>Qty</th><th>Total</th><th>Status</th><th>Time</th><th></th></tr></thead><tbody>' +
filtered.map(function(o) {
var canUpdate = o.status !== 'fulfilled' && o.status !== 'cancelled';
return '<tr>' +
'<td class="font-mono text-muted">' + o.id + '</td>' +
'<td>' + AdminApp.escapeHtml(o.customer) + '</td>' +
'<td>' + AdminApp.escapeHtml(o.meal) + '</td>' +
'<td>' + o.qty + '</td>' +
'<td class="font-mono">$' + o.total.toFixed(2) + '</td>' +
'<td><span class="badge ' + (statusMap[o.status] || 'badge-default') + '">' + o.status + '</span></td>' +
'<td class="text-muted">' + AdminApp.timeAgo(o.created_at) + '</td>' +
'<td>' + (canUpdate ? '<button class="btn btn-ghost btn-sm" onclick="advanceOrder(\'' + o.id + '\')">Advance</button>' : '') + '</td>' +
'</tr>';
}).join('') +
'</tbody></table></div>';
}
function advanceOrder(id) {
var order = orders.find(function(o){ return o.id === id; });
if (!order) return;
var flow = { 'new': 'preparing', 'preparing': 'ready', 'ready': 'fulfilled' };
var next = flow[order.status];
if (!next) return;
order.status = next;
updateStats();
renderOrders();
AdminApp.toast('Order ' + id + ' → ' + next, 'success');
}
document.getElementById('searchInput').addEventListener('input', renderOrders);
document.getElementById('filterStatus').addEventListener('change', renderOrders);
loadOrders();
</script>
</body>
</html>