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`.