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,14 @@
export default function ConfirmModal({ title, message, confirmLabel = 'Επιβεβαίωση', confirmClass = 'btn-danger', onConfirm, onCancel }) {
return (
<div className="fixed inset-0 bg-black/40 flex items-center justify-center z-50 p-4">
<div className="bg-white rounded-2xl shadow-xl w-full max-w-sm p-6 space-y-4">
<h2 className="text-lg font-bold text-gray-800">{title}</h2>
{message && <p className="text-gray-600 text-sm">{message}</p>}
<div className="flex gap-3 pt-2">
<button onClick={onCancel} className="flex-1 btn btn-secondary">Ακύρωση</button>
<button onClick={onConfirm} className={`flex-1 btn ${confirmClass}`}>{confirmLabel}</button>
</div>
</div>
</div>
)
}

View File

@@ -0,0 +1,47 @@
import { NavLink } from 'react-router-dom'
import { useState } from 'react'
const NAV = [
{ to: '/dashboard', icon: '📊', label: 'Dashboard' },
{ to: '/tables', icon: '🪑', label: 'Τραπέζια' },
{ to: '/products', icon: '📦', label: 'Προϊόντα' },
{ to: '/waiters', icon: '👥', label: 'Σερβιτόροι' },
{ to: '/reports', icon: '📋', label: 'Αναφορές' },
{ to: '/settings', icon: '⚙️', label: 'Ρυθμίσεις' },
]
export default function Sidebar() {
const [collapsed, setCollapsed] = useState(false)
return (
<aside className={`flex flex-col bg-primary-800 text-white shrink-0 transition-all duration-200 ${collapsed ? 'w-16' : 'w-56'}`}>
{/* Logo / collapse toggle */}
<div className="flex items-center justify-between px-4 py-4 border-b border-primary-700">
{!collapsed && <span className="font-bold text-lg tracking-wide">POS</span>}
<button
onClick={() => setCollapsed(c => !c)}
className="p-1 rounded hover:bg-primary-700 transition-colors ml-auto"
aria-label="Toggle sidebar"
>
{collapsed ? '→' : '←'}
</button>
</div>
<nav className="flex-1 py-4 space-y-1 px-2">
{NAV.map(({ to, icon, label }) => (
<NavLink
key={to}
to={to}
className={({ isActive }) =>
`flex items-center gap-3 px-3 py-3 rounded-lg font-medium transition-colors min-h-[44px] ` +
(isActive ? 'bg-primary-600 text-white' : 'text-primary-100 hover:bg-primary-700')
}
>
<span className="text-xl leading-none shrink-0">{icon}</span>
{!collapsed && <span className="text-sm">{label}</span>}
</NavLink>
))}
</nav>
</aside>
)
}

View File

@@ -0,0 +1,18 @@
const MAP = {
free: { label: 'Ελεύθερο', cls: 'bg-gray-100 text-gray-600' },
open: { label: 'Ανοιχτό', cls: 'bg-green-100 text-green-700' },
partially_paid: { label: 'Μερική πληρωμή', cls: 'bg-amber-100 text-amber-700' },
paid: { label: 'Πληρώθηκε', cls: 'bg-blue-100 text-blue-700' },
closed: { label: 'Κλειστό', cls: 'bg-gray-200 text-gray-500' },
cancelled: { label: 'Ακυρώθηκε', cls: 'bg-red-100 text-red-600' },
active: { label: 'Ενεργό', cls: 'bg-green-100 text-green-700' },
}
export default function StatusBadge({ status }) {
const { label, cls } = MAP[status] ?? { label: status, cls: 'bg-gray-100 text-gray-600' }
return (
<span className={`inline-block text-xs font-semibold px-2 py-0.5 rounded-full ${cls}`}>
{label}
</span>
)
}