shoc-frontend-new/src/domain/dashboard/utils/work-order-drilldown-links.ts
Codex Review Integration 1271d3c11e feat(dashboard): dispatcher scope picker, status distribution, region drilldown, perf counts
- 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.
2026-09-17 02:56:31 -03:00

96 lines
3.2 KiB
TypeScript

import { ALL_WIZARD_STATUSES } from "@/domain/work-orders/types/work-order-wizard";
import { WO_TYPES, type WOType } from "@/domain/work-orders/types/work-order-wizard";
import type { DashboardRangeKey } from "@/domain/dashboard/types/dashboard-range";
import {
addDaysIso,
businessTodayIso,
dashboardRangeParams,
} from "@/domain/dashboard/utils/dashboard-range-utils";
export const WORK_ORDERS_ROUTE = "/workorders";
export const UPLIFTS_ROUTE = "/uplifts";
export const OPEN_WIZARD_STATUSES: readonly string[] = ALL_WIZARD_STATUSES.filter(
(status) => status !== "Completed",
);
export function workOrderDrilldownUrl(search: URLSearchParams): string {
const query = search.toString();
return query ? `${WORK_ORDERS_ROUTE}?${query}` : WORK_ORDERS_ROUTE;
}
function setDateRange(search: URLSearchParams, range: DashboardRangeKey): void {
const params = dashboardRangeParams(range);
if (params.dateFrom && params.dateTo) {
search.set("dateFrom", params.dateFrom);
search.set("dateTo", params.dateTo);
}
}
export function workOrderRangeDrilldownSearch(range: DashboardRangeKey): URLSearchParams {
const search = new URLSearchParams();
setDateRange(search, range);
return search;
}
export function workOrderStatusDrilldownSearch(
status: string,
range: DashboardRangeKey,
): URLSearchParams {
const search = workOrderRangeDrilldownSearch(range);
search.set("statuses", status);
return search;
}
export function workOrderTypeDrilldownSearch(
type: string,
range: DashboardRangeKey,
): URLSearchParams | null {
if (!WO_TYPES.includes(type as WOType)) return null;
const search = workOrderRangeDrilldownSearch(range);
search.set("types", type);
return search;
}
export function workOrderDispatcherDrilldownSearch(
dispatcherId: string,
range: DashboardRangeKey,
): URLSearchParams {
const search = workOrderRangeDrilldownSearch(range);
search.set("dispatchers", dispatcherId);
return search;
}
export function workOrderRegionDrilldownSearch(
region: string,
range: DashboardRangeKey,
): URLSearchParams {
const search = workOrderRangeDrilldownSearch(range);
search.set("regions", region);
return search;
}
export function scheduledTomorrowDrilldownSearch(
today: string = businessTodayIso(),
): URLSearchParams {
const tomorrow = addDaysIso(today, 1);
const search = new URLSearchParams();
search.set("dateFrom", tomorrow);
search.set("dateTo", tomorrow);
return search;
}
export function avetaPendingDrilldownSearch(today: string = businessTodayIso()): URLSearchParams {
// The Aveta Pending count (backend#138 / SH-294) is: AvetaRequired, scheduled
// today or tomorrow, open, and missing its Aveta-category document. The board
// has no missing-Aveta-document filter, so the closest reachable set is the
// Aveta-required work in the two-day open window. `docs=No` is deliberately
// NOT sent: it filters on completion-document status, a different document
// from the Aveta clearance one, so it would narrow to the wrong rows.
const search = new URLSearchParams();
search.set("dateFrom", today);
search.set("dateTo", addDaysIso(today, 1));
search.set("aveta", "1");
search.set("statuses", OPEN_WIZARD_STATUSES.join(","));
return search;
}