Phase 3: scaffold Manager Dashboard — all pages, layout, routing

Includes: LoginPage (PIN pad), DashboardPage (30s polling table grid),
OrderDetailPage (full actions), ProductsPage (CRUD + printer zone),
WaitersPage (block/reset PIN/delete), TablesPage, ReportsPage
(shift summary + order history + CSV export), SettingsPage (printers
+ test print + sysadmin lock/unlock). TailwindCSS, React Query,
react-hot-toast. Docker Compose service on port 5174.
This commit is contained in:
2026-04-20 17:20:46 +03:00
parent 7f5bcfe4e1
commit 8f52156f5b
23 changed files with 1749 additions and 0 deletions

View File

@@ -0,0 +1,49 @@
import { Outlet } from 'react-router-dom'
import { useState, useEffect } from 'react'
import Sidebar from '../components/Sidebar'
import useAuthStore from '../store/authStore'
import client from '../api/client'
export default function AppLayout() {
const { user, token, login, logout } = useAuthStore()
const [clock, setClock] = useState(new Date())
// Fetch user profile once on mount if token exists but user isn't loaded
useEffect(() => {
if (token && !user) {
client.get('/auth/me').then(r => login(r.data, token)).catch(() => logout())
}
}, [token])
useEffect(() => {
const id = setInterval(() => setClock(new Date()), 1000)
return () => clearInterval(id)
}, [])
const timeStr = clock.toLocaleTimeString('el-GR', { hour: '2-digit', minute: '2-digit' })
return (
<div className="flex h-screen overflow-hidden">
<Sidebar />
<div className="flex flex-col flex-1 min-w-0">
{/* Top bar */}
<header className="flex items-center justify-between px-6 py-3 bg-white border-b border-gray-200 shrink-0">
<span className="text-lg font-semibold text-gray-700 tabular-nums">{timeStr}</span>
<div className="flex items-center gap-4">
<span className="text-sm text-gray-500">{user?.username}</span>
<button
onClick={logout}
className="text-sm text-red-600 hover:text-red-800 font-medium transition-colors"
>
Αποσύνδεση
</button>
</div>
</header>
{/* Page content */}
<main className="flex-1 overflow-y-auto p-6">
<Outlet />
</main>
</div>
</div>
)
}