ModulesDashboard

Dashboard

The per-project Dashboard at /admin/dashboard is a per-user view of curated charts. Each project has its own set of pinned charts, and each user’s selections are stored independently — what you pin does not affect your teammates.

How it works

The Dashboard is a library of engineer-authored charts. Pick the ones you want, add them, change their filters — your selection is saved per project, per user. Removing a chart resets its filters; adding it back uses the default filter for that chart.

Adding and removing charts

  1. Click + Add chart in the header (or the Add more charts tile in the dashboard grid).
  2. The library drawer slides in from the right. Click a chart to add it.
  3. Each chart has its own date filter and any toggles specific to that chart. Filters persist for you per project.
  4. Click the × on any chart card to remove it from your dashboard. Re-adding it later resets filters to defaults.

Available charts

The dashboard library currently ships with three charts. Pin any of them from the + Add chart drawer on the dashboard page.

Prestart Sign-Ons by Supervisor

A bar chart of prestart sign-on counts grouped by the supervisor who conducted each prestart. Toggle between Sign Ons (total signature count) and Unique Workers (distinct people).

  • When to pin: tracking which supervisors are running prestarts effectively; spotting supervisors with sparse coverage.
  • Default filter: last 30 days, all sign-ons.
  • Date filter: Last 7 / 30 / 90 days, This week / month / quarter / year, or a custom range. Presets and dates use the project’s timezone (defaults to Australia/Sydney), never your browser’s.
  • Data sources: prestarts.supervisor_id joined against prestart_sign_ons; all non-deleted prestart statuses (draft, saved, published) count.
  • Sort: bars are sorted descending by the selected metric.
  • Supervisors with zero sign-ons in the window are hidden.
  • Metric toggle: switching between Sign Ons and Unique Workers is a client-side re-sort of the already-fetched data — it does not trigger a new API call.

Cost by Day by Cost Code

Docket spend over time, broken down by cost code or stream. Three view modes: Stacked (default), Multi-line, and Total. Click any bar or segment to drill into the underlying dockets in a new tab.

  • When to pin: weekly cost-control reviews; spotting overruns by cost code or stream (labour vs. plant vs. materials).
  • Default filter: last 30 days, top 10 cost codes by spend, all streams (Labour + Plant + Materials), Reviewed + Confirmed dockets.
  • View modes: Stacked bar renders one stack column per period; Multi-line renders one line per cost code or stream; Total collapses to a single spend-per-period bar.
  • Stack-by toggle (hidden in Total mode): switch between grouping by Cost code (default) and Stream — gives both a per-code and a per-stream lens on the same data without an extra query.
  • Granularity: Day / Week / Month pill toggle controls the time bucket. Default: Day.
  • Cost-code picker: searchable by code and description. First paint seeds the top-10 codes by spend in the current window; once you make a selection it persists across date-range changes.
  • Unallocated bucket: docket lines with no cost code are grouped as “Unallocated” (zinc grey) so spend is never silently hidden.
  • Drilldown: clicking a bar opens /admin/dockets pre-filtered by date range and cost code in a new tab.
  • Currency format: compact y-axis ticks ($1.2M, $850K); full-precision tooltip ($1,234,567.89). AUD.

Logged Quantity by Day

Diary activity quantities over time, broken down by cost code. Because quantities across different units cannot be meaningfully summed, the chart uses a lane model — one Recharts panel per unit (hours, m³, tonnes, etc.), each with its own independent y-axis. The card grows vertically with the number of distinct units in the window.

  • When to pin: monitoring physical progress by cost code; spotting productivity dips on a specific code.
  • Default filter: last 30 days, top 10 cost codes by activity count, Reviewed + Confirmed diaries.
  • View modes: Stacked, Multi-line, and Total — applied uniformly across all unit lanes.
  • Granularity: Day / Week / Month toggle, same as Chart A.
  • Cost-code picker: same searchable multi-select as Chart A. First paint seeds the top-10 codes by activity count (unit-neutral ranking).
  • No drilldown in the current version — bars are not clickable. Drilldown to an activities workbench is planned as a future fast-follow.

For engineers — adding a new chart

Each chart is one library entry. Every library chart lives in its own folder under apps/web/src/app/admin/dashboard/_components/charts/<chart-key>/ with 6 files:

FilePurpose
schema.tsZod schema for the chart’s filter_state shape + TypeScript type export
schema.test.tsUnit tests for the Zod schema (valid + invalid payloads)
chart.tsxChart body — useQuery + Recharts rendering, "use client"
chart.test.tsxVitest component tests (@vitest-environment jsdom)
filter-controls.tsxFilters popover body (date preset, cost-code picker, status checkboxes, etc.)
chart-controls.tsxRight-rail control bar — wires ViewModeToggle, GranularityToggle, and FilterControls together

To ship a new chart end-to-end:

  1. OpenAPI — add GET /v1/orgs/{orgId}/projects/{projectId}/dashboard/<chart-name> plus its response schema to openapi.yaml. Run pnpm api:codegen.
  2. SQL — add a named sqlc query in the appropriate backend/db/queries/*.sql file. Run sqlc generate from backend/db/.
  3. Go handler — add a method to backend/api/internal/dashboard/handler.go and wire it in routes.go.
  4. Go filter validator — add an entry to chartKeyValidators in backend/api/internal/dashboard/filter_schemas.go.
  5. Server Action — add a function to apps/web/src/actions/dashboard.ts.
  6. 6-file chart folder — create the folder under _components/charts/<chart-key>/ with the files listed above.
  7. Registry — extend the ChartKey union and append to chartLibrary in apps/web/src/app/admin/dashboard/_lib/registry.ts.
  8. Tests — Go handler test (cross-tenant + happy path), Vitest for the chart component, a new Playwright spec at apps/web/e2e/dashboard-<chart-key>.spec.ts.
  9. Docs — append a sub-section here under “Available charts”.

The narrow ChartKey TypeScript union and the Go-side chartKeyValidators map are the two enforcement points that prevent drift between the frontend registry and the backend.

Architecture

  • Catalogue lives in code, not data. apps/web/src/app/admin/dashboard/_lib/registry.ts is the source of truth for which charts exist.
  • State lives in Postgres. user_dashboard_charts (migration 089) holds (user_id, project_id, chart_key) rows with filter_state (JSONB) and display_order. A UNIQUE constraint on (user_id, project_id, chart_key) enforces single-instance pinning.
  • Per-chart dedicated endpoint — each chart has its own typed OpenAPI route, sqlc query, and Go handler. There is no generic chart-execute dispatch endpoint.
  • Filter persistence — debounced 500 ms; the preset name is stored (not absolute dates) so dynamic ranges such as “Last 30 days” always resolve against the project’s today when the page loads.
  • Bucket timezone — chart SQL uses AT TIME ZONE projects.timezone for date bucketing. Default: Australia/Sydney.
  • Cross-project isolation — user_dashboard_charts rows are scoped to (user_id, project_id). Switching project context (by updating the lacroo.project_id cookie) shows only that project’s pinned set.

Spec & plan

  • Spec: docs/superpowers/specs/2026-04-26-dashboard-framework-design.md
  • Brainstorm notes: docs/superpowers/specs/2026-04-26-dashboard-framework-brainstorm-notes.md

Organisation dashboard

Users with access to the org console also get an organisation-wide dashboard at /org/dashboard (Dashboard entry in the org sidebar). It carries the same three-chart library as the project dashboard, aggregated across every project in your organisation, with your pinned set and filters saved per user — independent of any project dashboard.

Differences from the project dashboard:

  • Group by project. Each chart can flip its grouping to Project — the Cost by Day chart adds a Project stack option, Logged Quantity adds a Group by pill, and Prestart Sign-Ons adds a Supervisor / Project toggle. This is also how you separate cost codes that collide across projects: the same code string can mean different things on different projects, so the merged per-code view is only meaningful per project.
  • Timezones. Every data point is bucketed in its own project’s timezone. The date presets (“Last 30 days” etc.) use Sydney time, the same for everyone. See Dates & Times.
  • No drilldown. Clicking chart segments is disabled org-wide (the dockets workbench needs an active project).
  • No cost-code picker. The org charts show the top-10 codes by default; use the Project grouping to separate per-project spend. (An org-wide cost-code filter is planned.)
  • Access. The org dashboard requires org-console access (the web_org avenue) — enforced server-side on every chart read, not just in the UI.
  • Copilot charts cannot be pinned to the org dashboard yet — pin them to a project dashboard instead.

Saved Copilot screens

Below the charts, Saved screens shows the Copilot screens you saved with Save to dashboard (see Copilot). The project dashboard shows screens made in that project; the org dashboard shows screens made on the org Copilot.

  • What it is. Each card is the screen itself, with your live data: it re-reads when you open the dashboard and updates when the records change. Filters and picks you change stay on it.
  • Only you. Nobody else sees your saved screens.
  • 12 per dashboard. Remove one to save another.
  • Changes. A button that changes records shows a confirm card inside the screen’s card, and nothing changes until you confirm. The result and its Undo stay on the card.
  • Rename, Move up, Move down, Remove. Use the menu (⋯) on a card. Removing a screen cancels its changes waiting for approval.
  • Needs to be made again. If a screen can no longer be shown, its card says “This screen needs to be made again in Copilot.” Ask Copilot for it again and save it.
  • Guide. “How saved screens work” at the top of the section opens a short guide with an Open Copilot button.

The dashboard is empty only when it has no charts and no saved screens.