feat(reports): admin Reports dashboard — ledger-first charts
Adds an admin Reports screen (/setup/reports, gated report:read) — an on-demand dashboard over the signed event log. Server (ledger-first): GET /api/reports/summary?from&to&bucket aggregates in one call — entry/exit counts + all money summed straight from ledger_events (same source the shift Z-report reconciles, so totals tie out to the drawer); revenue split into ticket / subscription-sale / out-of-window mirrors the Z-report. Duration stats come from the sessions cache (flagged). All bucketing is in the SITE timezone (siteTz). A .csv export of the per-bucket series. reports.ts + routes/reports.ts. Web: Reports.tsx — date-range presets (today/7d/30d/90d), hour/day/month grain, KPI cards, entry/exit line, revenue bar + cash/card split, revenue-mix pie, peak-hours histogram, numeric breakdown, subscription stats. Charts via Recharts (MIT), lazy-loaded into its own chunk (~111KB gz) so the booth bundle is untouched. New Setup tab + nav + i18n (sq + en parity). asc() exported from @parking/db; formatMinutes helper. Tests: reports.test.ts (10) pin the sums, tz bucketing, money split, duration stats, subscription counts. server 90/90; build+lint 14/14. Wiki: reporting-analytics.md "Built v1" section + log entry. Claude-Session: https://claude.ai/code/session_01Xcm6ikLgGoCxxHrxtjkk5V
This commit is contained in:
@@ -55,6 +55,7 @@ export const en: Catalog = {
|
||||
users: "Users",
|
||||
roles: "Roles",
|
||||
shifts: "Shifts",
|
||||
reports: "Reports",
|
||||
logs: "Logs",
|
||||
},
|
||||
status: {
|
||||
@@ -677,6 +678,40 @@ export const en: Catalog = {
|
||||
cashRemoved: "Cash removed",
|
||||
loadFailed: "Failed to load shifts.",
|
||||
},
|
||||
reports: {
|
||||
title: "Reports",
|
||||
groupBy: "Group by",
|
||||
exportCsv: "Export CSV",
|
||||
loadFailed: "Couldn't load the report: {{error}}",
|
||||
noData: "No data in this range.",
|
||||
footnote: "Counts and money are summed from the signed event log. Times shown in {{tz}}.",
|
||||
preset: { today: "Today", "7d": "7 days", "30d": "30 days", "90d": "90 days" },
|
||||
bucket: { hour: "Hour", day: "Day", month: "Month" },
|
||||
kpi: {
|
||||
entries: "Entries",
|
||||
exits: "Exits",
|
||||
revenue: "Revenue",
|
||||
payments: "Payments",
|
||||
avgStay: "Avg stay",
|
||||
subscribers: "Subscribers",
|
||||
},
|
||||
chart: {
|
||||
flow: "Entries & exits over time",
|
||||
revenue: "Revenue ({{currency}})",
|
||||
mix: "Revenue mix",
|
||||
peakHours: "Entries by hour of day",
|
||||
breakdown: "Breakdown",
|
||||
},
|
||||
mix: { ticket: "Transient", subSales: "Subscriptions", subWindow: "Out-of-window" },
|
||||
row: {
|
||||
cash: "Cash",
|
||||
card: "Card",
|
||||
closed: "Closed sessions",
|
||||
medianStay: "Median stay",
|
||||
subActive: "Active subscriptions",
|
||||
subCars: "Cars covered",
|
||||
},
|
||||
},
|
||||
logs: {
|
||||
title: "System logs",
|
||||
refresh: "Refresh",
|
||||
|
||||
Reference in New Issue
Block a user