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

@@ -1,10 +1,12 @@
import { createContext, useContext, useCallback, useEffect, useRef } from 'react'
import { useQueryClient } from '@tanstack/react-query'
import useAuthStore from '../store/authStore'
import useConnectionStore from '../store/connectionStore'
import { useSSE } from '../hooks/useSSE'
import db from '../db/posdb'
import client from '../api/client'
import { flushOfflinePayments } from '../services/offlinePayments'
import { invalidateProductCache } from '../hooks/useProductCache'
const SSEContext = createContext(null)
@@ -17,6 +19,7 @@ const HEARTBEAT_INTERVAL = 30_000
export function SSEProvider({ children }) {
const { token } = useAuthStore()
const { setLost, setOnline } = useConnectionStore()
const queryClient = useQueryClient()
const sseAlive = useRef(false)
const heartbeatRef = useRef(null)
@@ -97,10 +100,14 @@ export function SSEProvider({ children }) {
await snapshotTables()
break
}
case 'products_changed': {
invalidateProductCache(queryClient)
break
}
default:
break
}
}, [snapshotTables])
}, [snapshotTables, queryClient])
// ── SSE connection lifecycle ─────────────────────────────────────────────────
@@ -175,6 +182,32 @@ export function SSEProvider({ children }) {
return () => window.removeEventListener('backend-offline', onBackendOffline)
}, [])
// ── Wake-up handshake — fires when tab/app returns from background ────────────
useEffect(() => {
if (!token) return
async function onVisible() {
if (document.visibilityState !== 'visible') return
try {
await client.get('/api/system/health')
const currentStatus = useConnectionStore.getState().status
if (currentStatus === 'lost' || currentStatus === 'emergency' || currentStatus === 'reconnecting') {
setOnlineRef.current()
reconnect()
await fullRefresh()
} else if (!sseAlive.current) {
// SSE dropped silently while sleeping — re-establish quietly
reconnect()
await fullRefresh()
}
} catch {
if (!sseAlive.current) setLostRef.current()
}
}
document.addEventListener('visibilitychange', onVisible)
return () => document.removeEventListener('visibilitychange', onVisible)
}, [token, reconnect, fullRefresh])
// ── Initial snapshot on login ─────────────────────────────────────────────────
useEffect(() => {