diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index bb4b47d..42e0a23 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -42,7 +42,6 @@ import MenuList from './components/MenuList' import MenuEditor from './components/MenuEditor' import BulkAllergens from './components/BulkAllergens' import PriceImpact from './components/PriceImpact' -import PurchasesCalendar from './pages/PurchasesCalendar' export default function App() { return ( @@ -60,7 +59,6 @@ export default function App() { } /> } /> } /> - } /> } /> } /> } /> diff --git a/frontend/src/components/Layout.tsx b/frontend/src/components/Layout.tsx index b166db0..62a1b06 100644 --- a/frontend/src/components/Layout.tsx +++ b/frontend/src/components/Layout.tsx @@ -46,7 +46,7 @@ export default function Layout() { - + {can(user, 'disputes') && } diff --git a/frontend/src/pages/PurchasesCalendar.tsx b/frontend/src/pages/PurchasesCalendar.tsx deleted file mode 100644 index 0ac998f..0000000 --- a/frontend/src/pages/PurchasesCalendar.tsx +++ /dev/null @@ -1,336 +0,0 @@ -import { useState } from 'react' -import { useQuery } from '@tanstack/react-query' -import { ChevronLeft, ChevronRight } from 'lucide-react' -import { useAuth } from '../App' - -interface PurchaseInvoice { - id: number - invoice_number: string | null - total: string | null - supplier_match_type: string | null -} - -interface SupplierRow { - supplier_id: number | null - supplier_name: string - is_unmatched: boolean - invoices_by_date: Record - total: string - percentage: string -} - -interface WeeklyPurchasesResponse { - week_start: string - week_end: string - dates: string[] - suppliers: SupplierRow[] - daily_totals: Record - week_total: string -} - -const DAY_LABELS = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] - -function fmt(val: string | null | undefined): string { - if (!val) return '—' - const n = parseFloat(val) - if (isNaN(n)) return '—' - return new Intl.NumberFormat('en-GB', { style: 'currency', currency: 'GBP' }).format(n) -} - -function fmtDate(iso: string): string { - const d = new Date(iso + 'T00:00:00') - return d.toLocaleDateString('en-GB', { day: '2-digit', month: 'short' }) -} - -function fmtWeekRange(start: string, end: string): string { - return `${fmtDate(start)} – ${fmtDate(end)}` -} - -export default function PurchasesCalendar() { - const { token } = useAuth() - const [weekOffset, setWeekOffset] = useState(0) - - const { data, isLoading, error } = useQuery({ - queryKey: ['purchases-weekly', weekOffset], - queryFn: async () => { - const res = await fetch(`/kitchen/api/reports/purchases/weekly?week_offset=${weekOffset}`, { - headers: { Authorization: `Bearer ${token}` }, - }) - if (!res.ok) throw new Error('Failed to fetch weekly purchases') - return res.json() - }, - enabled: !!token, - }) - - return ( -
-
-

Purchases Chart

-
- - - {data ? fmtWeekRange(data.week_start, data.week_end) : 'Loading…'} - - -
-
- - {isLoading &&
Loading…
} - {error &&
Failed to load weekly purchases.
} - - {data && ( -
- - - - - {data.dates.map((d, i) => ( - - ))} - - - - - - {data.suppliers.length === 0 && ( - - - - )} - {data.suppliers.map((row) => ( - - - {data.dates.map((d) => { - const invs = row.invoices_by_date[d] ?? [] - return ( - - ) - })} - - - - ))} - - - - - {data.dates.map((d) => ( - - ))} - - - -
Supplier -
{DAY_LABELS[i]}
-
{fmtDate(d)}
-
Total%
- No invoices this week -
- - {row.supplier_name} - - - {invs.map((inv) => ( - - - {inv.invoice_number ?? `#${inv.id}`} - - {fmt(inv.total)} - - ))} - {fmt(row.total)} - {parseFloat(row.percentage).toFixed(1)}% -
Daily Total - {fmt(data.daily_totals[d] ?? '0')} - - {fmt(data.week_total)} - -
-
- )} -
- ) -} - -const styles: Record = { - page: { - padding: '24px', - maxWidth: '100%', - overflowX: 'hidden', - }, - header: { - display: 'flex', - alignItems: 'center', - justifyContent: 'space-between', - flexWrap: 'wrap', - gap: '12px', - marginBottom: '20px', - }, - title: { - fontSize: '20px', - fontWeight: 600, - color: 'var(--text-primary)', - margin: 0, - }, - weekNav: { - display: 'flex', - alignItems: 'center', - gap: '8px', - }, - weekLabel: { - fontSize: '14px', - color: 'var(--text-secondary)', - minWidth: '180px', - textAlign: 'center', - }, - navBtn: { - display: 'flex', - alignItems: 'center', - gap: '4px', - padding: '6px 12px', - fontSize: '13px', - border: '1px solid var(--border)', - borderRadius: '6px', - background: 'var(--surface)', - color: 'var(--text-primary)', - cursor: 'pointer', - }, - navBtnDisabled: { - opacity: 0.4, - cursor: 'not-allowed', - }, - state: { - padding: '40px', - textAlign: 'center', - color: 'var(--text-secondary)', - }, - stateError: { - padding: '40px', - textAlign: 'center', - color: '#e53e3e', - }, - tableWrap: { - overflowX: 'auto', - borderRadius: '8px', - border: '1px solid var(--border)', - background: 'var(--surface)', - }, - table: { - width: '100%', - borderCollapse: 'collapse', - fontSize: '13px', - }, - th: { - padding: '10px 12px', - background: 'var(--surface-secondary, #f4f5f7)', - fontWeight: 600, - textAlign: 'left', - borderBottom: '2px solid var(--border)', - whiteSpace: 'nowrap', - color: 'var(--text-secondary)', - fontSize: '12px', - textTransform: 'uppercase', - letterSpacing: '0.04em', - }, - dayLabel: { - fontWeight: 700, - color: 'var(--text-primary)', - fontSize: '12px', - }, - dayDate: { - fontWeight: 400, - fontSize: '11px', - marginTop: '2px', - }, - supplierCol: { - minWidth: '160px', - }, - totalCol: { - minWidth: '100px', - textAlign: 'right' as const, - }, - pctCol: { - minWidth: '60px', - textAlign: 'right' as const, - }, - tr: { - borderBottom: '1px solid var(--border)', - }, - td: { - padding: '8px 12px', - verticalAlign: 'top', - color: 'var(--text-secondary)', - }, - supplierCell: { - color: 'var(--text-primary)', - fontWeight: 500, - whiteSpace: 'nowrap', - }, - totalCell: { - textAlign: 'right' as const, - whiteSpace: 'nowrap', - color: 'var(--text-primary)', - fontWeight: 500, - }, - pctCell: { - textAlign: 'right' as const, - whiteSpace: 'nowrap', - }, - unmatchedLabel: { - color: '#e53e3e', - fontStyle: 'italic', - }, - invoiceChip: { - display: 'flex', - flexDirection: 'column' as const, - gap: '1px', - padding: '3px 6px', - marginBottom: '4px', - borderRadius: '4px', - background: 'var(--surface-secondary, #f4f5f7)', - border: '1px solid var(--border)', - textDecoration: 'none', - cursor: 'pointer', - transition: 'background 0.15s', - }, - chipUnmatched: { - borderColor: '#fc8181', - background: '#fff5f5', - }, - chipNum: { - fontSize: '11px', - color: 'var(--text-primary)', - fontWeight: 500, - }, - chipAmt: { - fontSize: '12px', - color: 'var(--text-primary)', - fontWeight: 600, - }, - emptyRow: { - padding: '40px', - textAlign: 'center' as const, - color: 'var(--text-secondary)', - }, - totalsRow: { - borderTop: '2px solid var(--border)', - background: 'var(--surface-secondary, #f4f5f7)', - }, -}