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
co-authored by Claude Sonnet 4.6
commit 8ba8c95ecd
209 changed files with 48017 additions and 0 deletions
+46
View File
@@ -0,0 +1,46 @@
import { create } from 'zustand'
const useAuthStore = create((set) => ({
user: null,
token: localStorage.getItem('manager_token') || null,
savedUsername: localStorage.getItem('manager_username') || null,
locked: sessionStorage.getItem('manager_locked') === 'true',
login(user, token) {
localStorage.setItem('manager_token', token)
localStorage.setItem('manager_username', user.username)
sessionStorage.removeItem('manager_locked')
set({ user, token, savedUsername: user.username, locked: false })
},
// Restores user after a page refresh without touching the lock state.
rehydrate(user, token) {
set({ user, token, savedUsername: user.username })
},
logout() {
localStorage.removeItem('manager_token')
localStorage.removeItem('manager_username')
localStorage.removeItem('manager_lock_timeout')
sessionStorage.removeItem('manager_locked')
set({ user: null, token: null, savedUsername: null, locked: false })
},
lock() {
sessionStorage.setItem('manager_locked', 'true')
set({ locked: true })
},
unlock(user, token) {
localStorage.setItem('manager_token', token)
sessionStorage.removeItem('manager_locked')
set({ user, token, locked: false })
},
updateUser(updatedUser) {
localStorage.setItem('manager_username', updatedUser.username)
set({ user: updatedUser })
},
}))
export default useAuthStore
@@ -0,0 +1,94 @@
import { create } from 'zustand'
import { persist } from 'zustand/middleware'
// Mirrors waiter_pwa/src/store/tableColourStore.js — same localStorage key so both apps share state.
export const DEFAULT_COLOURS = {
light: {
free: {
cardBg: '#d6d6d6',
badgeBg: '#e3e3e3',
nameText: '#3b485e',
badgeText: '#adadad',
},
mine: {
cardBg: '#e83030',
badgeBg: 'rgba(255,255,255,0.40)',
nameText: '#ffffff',
badgeText: '#ffffff',
},
open: {
cardBg: '#ffbb29',
badgeBg: 'rgba(255,255,255,0.25)',
nameText: '#ffffff',
badgeText: '#ffffff',
},
partially_paid: {
cardBg: '#e89230',
badgeBg: 'rgba(255,255,255,0.25)',
nameText: '#ffffff',
badgeText: '#ffffff',
},
paid: {
cardBg: '#79ad38',
badgeBg: 'rgba(255,255,255,0.25)',
nameText: '#ffffff',
badgeText: '#ffffff',
},
},
dark: {
free: {
cardBg: '#243044',
badgeBg: 'rgba(26,35,50,0.50)',
nameText: '#ffffff',
badgeText: '#adadad',
},
mine: {
cardBg: '#e83030',
badgeBg: 'rgba(255,255,255,0.40)',
nameText: '#ffffff',
badgeText: '#ffffff',
},
open: {
cardBg: '#ffbb29',
badgeBg: 'rgba(255,255,255,0.25)',
nameText: '#ffffff',
badgeText: '#ffffff',
},
partially_paid: {
cardBg: '#e89230',
badgeBg: 'rgba(255,255,255,0.25)',
nameText: '#ffffff',
badgeText: '#ffffff',
},
paid: {
cardBg: '#79ad38',
badgeBg: 'rgba(255,255,255,0.25)',
nameText: '#ffffff',
badgeText: '#ffffff',
},
},
}
const useTableColourStore = create(persist(
(set) => ({
colours: DEFAULT_COLOURS,
setColour: (mode, status, slot, value) =>
set(s => ({
colours: {
...s.colours,
[mode]: {
...s.colours[mode],
[status]: {
...s.colours[mode][status],
[slot]: value,
},
},
},
})),
resetAll: () => set({ colours: DEFAULT_COLOURS }),
}),
{ name: 'pos-table-colours' }
))
export default useTableColourStore