feat: major dashboard & waiter PWA overhaul

- Manager dashboard: replaced monolithic DashboardTab/OperationsPage with new
  DashboardPage; added OrderDetailModal, ShiftDetailModal, DeleteConfirmModal,
  PaymentMethodModal; updated Sidebar routing and App navigation
- Reports: reworked WorkDaySummary, OrderHistory, ShiftsOverview with detail modals
- Backend routers: extended orders, reports, shifts, products, business_day endpoints;
  updated cloud_sync service
- Waiter PWA: refreshed app icons, improved ConnectionLostModal UX, updated
  TableCard, SSEContext, connectionStore; added useProductCache hook; vite config tweaks

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-21 15:24:54 +03:00
parent aa92623802
commit 5de89a722c
40 changed files with 1906 additions and 1171 deletions

View File

@@ -0,0 +1,69 @@
import { useQuery, useQueryClient } from '@tanstack/react-query'
import { useEffect } from 'react'
import client from '../api/client'
import db from '../db/posdb'
export const PRODUCTS_KEY = ['products']
export const CATEGORIES_KEY = ['categories']
async function fetchAndCacheProducts() {
const [prodRes, catRes] = await Promise.all([
client.get('/api/products/'),
client.get('/api/products/categories'),
])
const products = prodRes.data
const categories = catRes.data
// Write to IndexedDB in the background — don't await so UI isn't blocked
db.products.bulkPut(products).catch(() => {})
db.categories.bulkPut(categories).catch(() => {})
return { products, categories }
}
async function loadFromCache() {
const [products, categories] = await Promise.all([
db.products.toArray(),
db.categories.toArray(),
])
if (products.length === 0 && categories.length === 0) return null
return { products, categories }
}
export function useProductCache() {
const queryClient = useQueryClient()
const query = useQuery({
queryKey: PRODUCTS_KEY,
queryFn: fetchAndCacheProducts,
// Serve stale data instantly — products don't change every second
staleTime: 5 * 60 * 1000, // 5 min before background re-fetch
gcTime: 60 * 60 * 1000, // keep in memory for 1 hour
placeholderData: undefined,
})
// On mount, if the query has no data yet, seed it from IndexedDB immediately
// so the UI renders without waiting for the network round-trip
useEffect(() => {
const cached = queryClient.getQueryData(PRODUCTS_KEY)
if (cached) return
loadFromCache().then(idbData => {
if (idbData) {
// Set as placeholder — React Query will still fetch in background
queryClient.setQueryData(PRODUCTS_KEY, idbData)
}
})
}, [queryClient])
return {
products: query.data?.products ?? [],
categories: query.data?.categories ?? [],
isLoading: query.isLoading && !query.data,
}
}
// Call this from SSEContext when products_changed arrives
export function invalidateProductCache(queryClient) {
queryClient.invalidateQueries({ queryKey: PRODUCTS_KEY })
}