- SH-336: add the dispatcher-scope picker (All / My WOs / individual) for
Scheduler/Admin via a new viewAllDispatchersOnDashboard role gate, a static
'My WOs' label for Dispatcher, and hide the dispatcher tables (and skip their
queries) when the viewer cannot see all dispatchers. Scope drives dispatcherId
on stats, workload, performance, and regions (SH-347 contract).
- SH-352: add the Status Distribution module with its empty state and status
drill-down.
- SH-347: show Assigned/Completed counts and colour the completion rate
(green >=90, amber 70-89, red <70) on Dispatcher Performance.
- SH-348: drill a region bar into the board region filter; hold the
unconfirmed 'Unmapped/Other' bucket off the chart.
- SH-349: label Vendor Insights as all-time, company-wide.
- Make the drilldown-filters effect fire only on URL change; add a deterministic
clock to the status-drilldown test; add the dashboard visual regression spec
and baselines.
Move the two paged dispatcher cards and their paging view-model derivation out
of DashboardPage into DispatcherTablesSection (a fragment, so both cards remain
direct children of the dashboard grid). This keeps DashboardPage under the
project complexity threshold after the pager additions, with no change to the
rendered layout or behaviour.
Workload and Performance page server-side at 10 rows and now return Page,
PageSize, and TotalDispatchers on dev (backend#126/#127), so wire the pager
that SH-346 and SH-347 require: getWorkload/getPerformance send a 1-based
`page` and parse the paging envelope, the hooks and query keys carry the page,
and each dispatcher table renders a 10-row pager. Page state is per-table and
resets on a range change so switching range never lands on an out-of-range
empty page that reads as a false "no dispatchers" state.
Drop `docs=No` from the Aveta Pending drilldown. `docs` filters on
completion-document status, not the Aveta clearance document the tile counts
(SH-294 / backend#138), so it narrowed to the wrong rows. The board has no
missing-Aveta-document filter, so the drilldown is now the Aveta-required work
in the two-day open window — a deliberate superset of the count.
The Work Order Breakdown object from the backend serializes its keys
without JsonPropertyName, so they arrive lowercase (pm/emergency/reactive/
overdue/other). The mapper copied them straight into `status`, which the
card uses as both label and the `types` drill-down id, and
`workOrderTypeDrilldownSearch` only accepts canonical WOType values — so
every breakdown row rendered lowercase and was inert (SH-354). Route both
mapping branches through a normalizer that maps type keys back to their
canonical labels case-insensitively; `other` is not a WOType, so it stays a
labelled, non-drillable "Other" row, and unknown values (legacy array-shape
lifecycle labels) pass through unchanged.
Dispatcher Performance gated its drill-down on `completionRate > 0`, so a
dispatcher with assigned work but no completions yet was inert here while
drillable from Workload. The drill-down filters by dispatcher + range and
never uses the rate, so gate on identity instead.
Also mock useDashboardTrend in the dashboard page test so it no longer
issues a real request that failed quietly in jsdom.
Swap the inline fontSize/color on the week-scope status label for the
text-(length:--text-xs) and text-muted-foreground classes already used
across the header, so it tracks future token changes. --text-xs is 11px
and text-muted-foreground resolves to --muted-foreground, so rendering is
unchanged.