feat: initial commit — local services (backend + manager dashboard + waiter PWA)

Includes all work to date:
- local_backend: FastAPI backend with products, orders, tables, shifts, cloud sync
- manager_dashboard: React manager UI with product/category management, reports, settings
- waiter_pwa: React PWA for waiter devices
- Category reparent endpoint and UI
- Waiter domain: local_ip sent on heartbeat, waiter_domain persisted from cloud response
- QR code modal in AppInfoTab for waiter domain
- Product form: number input spinners removed, category pre-selected on new product
- Category row: count badge moved to far right

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-20 14:04:38 +03:00
commit 8ba8c95ecd
209 changed files with 48017 additions and 0 deletions

View File

@@ -0,0 +1,85 @@
// Unified tab system — identical appearance across every page.
//
// TabGroup → primary nav row (supports lucide icons, used in Reports + Management + Settings)
// TabBar → secondary sub-tab row (pill style, used below TabGroup in Reports)
// TabRow → alias for TabGroup without icons — exact same markup and spacing
// TabCard → white rounded-xl card shell that wraps tab chrome + content
// Use this on any page that has tabs so it sits in the p-6 grey area correctly.
//
// Rule: ALL tab bars use px-6 pt-2 pb-0, border-b border-slate-200, transparent bg (inherits page bg).
// The sky-500 underline indicator is the ONLY active state difference.
const TAB_BTN_BASE =
'relative flex items-center gap-2 px-4 py-3 text-[13.5px] font-medium transition-colors whitespace-nowrap'
const TAB_BTN_ACTIVE = 'text-slate-900'
const TAB_BTN_INACTIVE = 'text-slate-500 hover:text-slate-700'
// ─── Primary tab row (with optional icons) ───────────────────────────────────
export function TabGroup({ tabs, active, onChange }) {
return (
<div className="flex-shrink-0 flex border-b border-slate-200 px-6 pt-2 overflow-x-auto">
{tabs.map((tab) => {
const isActive = tab.id === active
const Icon = tab.icon ?? tab.Icon
return (
<button
key={tab.id}
onClick={() => onChange(tab.id)}
className={`${TAB_BTN_BASE} ${isActive ? TAB_BTN_ACTIVE : TAB_BTN_INACTIVE}`}
>
{Icon && <Icon className={`h-4 w-4 flex-shrink-0 ${isActive ? 'text-sky-500' : 'text-slate-400'}`} />}
{tab.label}
{isActive && <span className="absolute inset-x-2 -bottom-px h-0.5 rounded-full bg-sky-500" />}
</button>
)
})}
</div>
)
}
// ─── Secondary sub-tab row (pill style) ──────────────────────────────────────
export function TabBar({ tabs, active, onChange }) {
return (
<div className="flex-shrink-0 flex items-center gap-1 border-b border-slate-200 px-6 py-2 overflow-x-auto">
{tabs.map((tab) => {
const isActive = tab.id === active
return (
<button
key={tab.id}
onClick={() => onChange(tab.id)}
className={`whitespace-nowrap rounded-md px-3 py-1.5 text-[12px] font-medium transition-colors ${
isActive
? 'bg-sky-100 text-sky-700 font-semibold'
: 'text-slate-500 hover:bg-slate-100 hover:text-slate-700'
}`}
>
{tab.label}
</button>
)
})}
</div>
)
}
// ─── TabRow: identical to TabGroup, kept as named alias ──────────────────────
export function TabRow({ tabs, active, onChange }) {
return <TabGroup tabs={tabs} active={active} onChange={onChange} />
}
// ─── TabCard: the white card shell for any tabbed page ───────────────────────
// Renders a rounded-xl bordered white card that fills the available space.
// Place TabGroup/TabBar as direct children, then a flex-1 overflow content area.
//
// Usage:
// <TabCard>
// <TabGroup ... />
// <TabBar ... /> {/* optional */}
// <div className="flex-1 overflow-y-auto p-6"> ... </div>
// </TabCard>
export function TabCard({ children, className = '' }) {
return (
<div className={`flex flex-col flex-1 min-h-0 rounded-xl border border-slate-200 bg-white shadow-sm overflow-hidden ${className}`}>
{children}
</div>
)
}