Built out the full restaurant owner admin dashboard: - Dashboard overview with stats, recent trades, and top meals - Meals management with card grid, add/edit/delete modals, search & filter - Orders page with status tabs, accept/ready/complete workflow - Trades activity page with search and stats overview - Shared admin-app.js module (sidebar, API client, toast, auth helpers) - All pages use existing design tokens, mobile-first responsive layout - Demo data fallback when API endpoints aren't ready yet Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
315 lines
11 KiB
HTML
315 lines
11 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 — Dashboard</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">
|
|
<!-- Sidebar Overlay (mobile) -->
|
|
<div class="sidebar-overlay" id="sidebarOverlay"></div>
|
|
|
|
<!-- Sidebar -->
|
|
<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 active">
|
|
<span class="icon">📊</span> Dashboard
|
|
</a>
|
|
<a href="meals.html" class="sidebar-link">
|
|
<span class="icon">🍔</span> Meals
|
|
</a>
|
|
<a href="orders.html" class="sidebar-link">
|
|
<span class="icon">📦</span> Orders
|
|
</a>
|
|
<a href="trades.html" class="sidebar-link">
|
|
<span class="icon">🔄</span> Trades
|
|
</a>
|
|
|
|
<div class="sidebar-section-title">Settings</div>
|
|
<a href="profile.html" class="sidebar-link">
|
|
<span class="icon">🏪</span> Restaurant Profile
|
|
</a>
|
|
<a href="settings.html" class="sidebar-link">
|
|
<span class="icon">⚙️</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 Content -->
|
|
<main class="main-content">
|
|
<!-- Top Bar -->
|
|
<header class="topbar">
|
|
<div class="topbar-left">
|
|
<button class="menu-toggle" id="menuToggle" aria-label="Toggle menu">
|
|
☰
|
|
</button>
|
|
<h1 class="topbar-title">Dashboard</h1>
|
|
</div>
|
|
<div class="topbar-right">
|
|
<button class="topbar-btn" aria-label="Notifications" id="notifBtn">
|
|
🔔
|
|
<span class="badge-dot"></span>
|
|
</button>
|
|
<button class="topbar-btn" aria-label="Sign out" id="logoutBtn">
|
|
⏻
|
|
</button>
|
|
</div>
|
|
</header>
|
|
|
|
<!-- Page Content -->
|
|
<div class="page-content">
|
|
<div class="page-header">
|
|
<h1>Welcome back, <span id="greetingName">Demo Restaurant</span></h1>
|
|
<p class="subtitle">Here's what's happening with your meals today.</p>
|
|
</div>
|
|
|
|
<!-- Stats Grid -->
|
|
<div class="stats-grid">
|
|
<div class="stat-card">
|
|
<div class="stat-icon orange">🍔</div>
|
|
<div class="stat-info">
|
|
<h3>Active Meals</h3>
|
|
<div class="stat-value" id="statMeals">0</div>
|
|
<div class="stat-change up" id="statMealsChange">--</div>
|
|
</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-icon green">🔄</div>
|
|
<div class="stat-info">
|
|
<h3>Trades Today</h3>
|
|
<div class="stat-value" id="statTrades">0</div>
|
|
<div class="stat-change up" id="statTradesChange">--</div>
|
|
</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-icon yellow">⭐</div>
|
|
<div class="stat-info">
|
|
<h3>Avg Rating</h3>
|
|
<div class="stat-value" id="statRating">--</div>
|
|
<div class="stat-change up" id="statRatingChange">--</div>
|
|
</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-icon blue">👥</div>
|
|
<div class="stat-info">
|
|
<h3>Profile Views</h3>
|
|
<div class="stat-value" id="statViews">0</div>
|
|
<div class="stat-change up" id="statViewsChange">--</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Content Grid -->
|
|
<div class="dashboard-grid">
|
|
<!-- Recent Trades -->
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h2>Recent Trades</h2>
|
|
<a href="trades.html" class="btn btn-ghost">View all →</a>
|
|
</div>
|
|
<div id="recentTradesList">
|
|
<div class="loading-overlay"><div class="spinner"></div></div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Popular Meals -->
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h2>Top Meals</h2>
|
|
<a href="meals.html" class="btn btn-ghost">Manage →</a>
|
|
</div>
|
|
<div id="topMealsList">
|
|
<div class="loading-overlay"><div class="spinner"></div></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</main>
|
|
</div>
|
|
|
|
<!-- Toast Container -->
|
|
<div class="toast-container" id="toastContainer"></div>
|
|
|
|
<script src="js/admin-app.js"></script>
|
|
<script>
|
|
// --- Auth Guard ---
|
|
const token = localStorage.getItem('gf_token');
|
|
const user = JSON.parse(localStorage.getItem('gf_user') || '{}');
|
|
|
|
if (!token) {
|
|
window.location.href = 'index.html';
|
|
}
|
|
|
|
// --- Populate user info ---
|
|
if (user.name) {
|
|
document.getElementById('greetingName').textContent = user.name;
|
|
document.getElementById('userName').textContent = user.name;
|
|
document.getElementById('userAvatar').textContent = user.name.split(' ').map(w => w[0]).join('').slice(0, 2).toUpperCase();
|
|
}
|
|
if (user.role) {
|
|
document.getElementById('userRole').textContent = user.role.charAt(0).toUpperCase() + user.role.slice(1);
|
|
}
|
|
|
|
// --- Sidebar Toggle ---
|
|
AdminApp.initSidebar();
|
|
|
|
// --- Logout ---
|
|
document.getElementById('logoutBtn').addEventListener('click', () => {
|
|
localStorage.removeItem('gf_token');
|
|
localStorage.removeItem('gf_user');
|
|
window.location.href = 'index.html';
|
|
});
|
|
|
|
// --- Load Dashboard Data ---
|
|
async function loadDashboard() {
|
|
try {
|
|
const data = await AdminApp.api('/admin/dashboard');
|
|
if (data) {
|
|
document.getElementById('statMeals').textContent = data.active_meals || 0;
|
|
document.getElementById('statTrades').textContent = data.trades_today || 0;
|
|
document.getElementById('statRating').textContent = data.avg_rating ? data.avg_rating.toFixed(1) : '--';
|
|
document.getElementById('statViews').textContent = data.profile_views || 0;
|
|
|
|
if (data.meals_change) {
|
|
const el = document.getElementById('statMealsChange');
|
|
el.textContent = `${data.meals_change > 0 ? '+' : ''}${data.meals_change} this week`;
|
|
el.className = `stat-change ${data.meals_change >= 0 ? 'up' : 'down'}`;
|
|
}
|
|
}
|
|
} catch (err) {
|
|
// API not ready yet — load demo data
|
|
loadDemoData();
|
|
}
|
|
}
|
|
|
|
function loadDemoData() {
|
|
document.getElementById('statMeals').textContent = '12';
|
|
document.getElementById('statTrades').textContent = '8';
|
|
document.getElementById('statRating').textContent = '4.6';
|
|
document.getElementById('statViews').textContent = '342';
|
|
document.getElementById('statMealsChange').textContent = '+3 this week';
|
|
document.getElementById('statTradesChange').textContent = '+12% vs last week';
|
|
document.getElementById('statRatingChange').textContent = '+0.2 from last month';
|
|
document.getElementById('statViewsChange').textContent = '+18% this week';
|
|
|
|
// Recent trades
|
|
const tradesHtml = `
|
|
<div class="table-wrapper">
|
|
<table class="data-table">
|
|
<thead>
|
|
<tr>
|
|
<th>User</th>
|
|
<th>Meal</th>
|
|
<th>Status</th>
|
|
<th>Time</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr>
|
|
<td>Jamie L.</td>
|
|
<td>Spicy Ramen Bowl</td>
|
|
<td><span class="badge badge-success">Completed</span></td>
|
|
<td class="text-muted">2 min ago</td>
|
|
</tr>
|
|
<tr>
|
|
<td>Alex K.</td>
|
|
<td>Chicken Burrito</td>
|
|
<td><span class="badge badge-warning">Pending</span></td>
|
|
<td class="text-muted">15 min ago</td>
|
|
</tr>
|
|
<tr>
|
|
<td>Sam R.</td>
|
|
<td>Veggie Power Bowl</td>
|
|
<td><span class="badge badge-success">Completed</span></td>
|
|
<td class="text-muted">28 min ago</td>
|
|
</tr>
|
|
<tr>
|
|
<td>Morgan T.</td>
|
|
<td>BBQ Pulled Pork</td>
|
|
<td><span class="badge badge-info">In Progress</span></td>
|
|
<td class="text-muted">45 min ago</td>
|
|
</tr>
|
|
<tr>
|
|
<td>Riley W.</td>
|
|
<td>Greek Salad Wrap</td>
|
|
<td><span class="badge badge-success">Completed</span></td>
|
|
<td class="text-muted">1 hr ago</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
`;
|
|
document.getElementById('recentTradesList').innerHTML = tradesHtml;
|
|
|
|
// Top meals
|
|
const mealsHtml = `
|
|
<div class="top-meals-list">
|
|
<div class="top-meal-item">
|
|
<span class="top-meal-rank">1</span>
|
|
<div class="top-meal-info">
|
|
<strong>Spicy Ramen Bowl</strong>
|
|
<span class="text-muted text-sm">47 trades this week</span>
|
|
</div>
|
|
<span class="badge badge-primary">Hot</span>
|
|
</div>
|
|
<div class="top-meal-item">
|
|
<span class="top-meal-rank">2</span>
|
|
<div class="top-meal-info">
|
|
<strong>Chicken Burrito</strong>
|
|
<span class="text-muted text-sm">38 trades this week</span>
|
|
</div>
|
|
<span class="badge badge-accent">Trending</span>
|
|
</div>
|
|
<div class="top-meal-item">
|
|
<span class="top-meal-rank">3</span>
|
|
<div class="top-meal-info">
|
|
<strong>Veggie Power Bowl</strong>
|
|
<span class="text-muted text-sm">31 trades this week</span>
|
|
</div>
|
|
<span class="badge badge-success">Popular</span>
|
|
</div>
|
|
<div class="top-meal-item">
|
|
<span class="top-meal-rank">4</span>
|
|
<div class="top-meal-info">
|
|
<strong>BBQ Pulled Pork</strong>
|
|
<span class="text-muted text-sm">24 trades this week</span>
|
|
</div>
|
|
</div>
|
|
<div class="top-meal-item">
|
|
<span class="top-meal-rank">5</span>
|
|
<div class="top-meal-info">
|
|
<strong>Greek Salad Wrap</strong>
|
|
<span class="text-muted text-sm">19 trades this week</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
document.getElementById('topMealsList').innerHTML = mealsHtml;
|
|
}
|
|
|
|
loadDashboard();
|
|
</script>
|
|
</body>
|
|
</html>
|