diff --git a/apps/web/src/router.tsx b/apps/web/src/router.tsx index fe997d5..1a13809 100644 --- a/apps/web/src/router.tsx +++ b/apps/web/src/router.tsx @@ -88,7 +88,6 @@ function SetupLayout() { {show("site:read") && } {show("user:read") && } {show("role:read") && } - {show("report:read") && } {show("log:read") && } @@ -378,7 +377,9 @@ function RootLayout() { {(show("subscription:read") || show("subscription:plan") || show("tariff:read")) && ( )} - {/* One Setup entry — its tabs hold devices/tariff/site/users/roles/shifts/logs. + {/* Reports — a standalone admin section (own header entry, route /reports). */} + {show("report:read") && } + {/* One Setup entry — its tabs hold devices/tariff/site/users/roles/logs. Shown if the user can reach ANY of those screens (an operator with only shift:read still gets in, landing on Shifts). */} {(show("site:update") || @@ -386,7 +387,6 @@ function RootLayout() { show("site:read") || show("user:read") || show("role:read") || - show("report:read") || show("shift:read")) && }
@@ -447,6 +447,7 @@ const legacyRedirects = ( ["/setup/plans", "/subscriptions/plans"], ["/setup/tariff-lab", "/subscriptions/tariff-lab"], ["/setup/shifts", "/shifts"], + ["/setup/reports", "/reports"], ] as const ).map(([from, to]) => createRoute({ @@ -458,6 +459,22 @@ const legacyRedirects = ( }), ); +// Admin reports/charts — a top-level section (own header nav entry), NOT a Setup tab. +// Gated by report:read. Lazy component (Recharts) in a Suspense so it stays out of the +// booth's initial bundle. +const reportsRoute = createRoute({ + getParentRoute: () => rootRoute, + path: "/reports", + beforeLoad: ({ context }) => requirePerm("report:read")(context), + component: function ReportsRoute() { + return ( + …
}> + + + ); + }, +}); + const shiftRoute = createRoute({ getParentRoute: () => rootRoute, path: "/shifts", @@ -496,7 +513,6 @@ const SETUP_TABS: { to: string; perm: Permission }[] = [ { to: "/setup/site", perm: "site:read" }, { to: "/setup/users", perm: "user:read" }, { to: "/setup/roles", perm: "role:read" }, - { to: "/setup/reports", perm: "report:read" }, { to: "/shifts", perm: "shift:read" }, { to: "/setup/logs", perm: "log:read" }, ]; @@ -594,20 +610,6 @@ const rolesRoute = createRoute({ // (Shift history lives at the standalone /shifts route — see shiftRoute. It was // removed as a Setup tab; /setup/shifts and the old /shift both redirect there.) -// Admin reports/charts. Gated by report:read. Lazy component (Recharts) in a Suspense. -const reportsRoute = createRoute({ - getParentRoute: () => setupRoute, - path: "reports", - beforeLoad: ({ context }) => requirePerm("report:read")(context), - component: function ReportsRoute() { - return ( - …}> - - - ); - }, -}); - // Diagnostic logs. Gated by log:read (an admin/diagnostic permission). const logsRoute = createRoute({ getParentRoute: () => setupRoute, @@ -621,6 +623,7 @@ const routeTree = rootRoute.addChildren([ boothRoute, ...legacyRedirects, shiftRoute, + reportsRoute, subscriptionsRoute.addChildren([ subscriptionsIndexRoute, subscriptionPlansRoute, @@ -632,7 +635,6 @@ const routeTree = rootRoute.addChildren([ siteRoute, usersRoute, rolesRoute, - reportsRoute, logsRoute, ]), ]); diff --git a/wiki/concepts/reporting-analytics.md b/wiki/concepts/reporting-analytics.md index 2c8e8e7..37f9b3b 100644 --- a/wiki/concepts/reporting-analytics.md +++ b/wiki/concepts/reporting-analytics.md @@ -14,8 +14,9 @@ derived and rebuildable, never a separate ledger. ## Built — admin Reports dashboard v1 (2026-06-22) -A first cut shipped: an admin **Reports** screen (`/setup/reports`, gated on `report:read`), an -on-demand **dashboard** (not a live feed). Server aggregates everything in **one call** +A first cut shipped: an admin **Reports** screen — a **top-level section** at **`/reports`** with its +own header nav entry (not nested under Setup), gated on `report:read` — an on-demand **dashboard** +(not a live feed). Server aggregates everything in **one call** (`GET /api/reports/summary?from&to&bucket`) so the SPA only renders; `…/summary.csv` exports the per-bucket series. Code: `apps/server/src/reports.ts` (+ `routes/reports.ts`), `apps/web/src/Reports.tsx`.