shoc-frontend-new/src/app/(protected)/dashboard/index.tsx
Codex Review Integration 7679c28682 refactor(dashboard): extract dispatcher tables into a section component
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.
2026-09-17 00:46:39 -03:00

132 lines
5.4 KiB
TypeScript

import { useState } from "react";
import { useNavigate } from "react-router";
import EventOutlinedIcon from "@mui/icons-material/EventOutlined";
import PendingActionsIcon from "@mui/icons-material/PendingActions";
import ShieldOutlinedIcon from "@mui/icons-material/ShieldOutlined";
import { Box, Button, CircularProgress } from "@mui/material";
import { PageHeader } from "@/components/ui/page-header";
import { DashboardKpiCard } from "@/app/(protected)/dashboard/_components/dashboard-kpi-card";
import { DashboardRangeToggle } from "@/app/(protected)/dashboard/_components/dashboard-range-toggle";
import { DashboardTrendCard } from "@/app/(protected)/dashboard/_components/dashboard-trend-card";
import { DispatcherTablesSection } from "@/app/(protected)/dashboard/_components/dispatcher-tables-section";
import { VendorInsightsCard } from "@/app/(protected)/dashboard/_components/vendor-insights-card";
import { WorkOrderBreakdownCard } from "@/app/(protected)/dashboard/_components/work-order-breakdown-card";
import { WorkOrdersByRegionCard } from "@/app/(protected)/dashboard/_components/work-orders-by-region-card";
import {
DEFAULT_DASHBOARD_RANGE,
type DashboardRangeKey,
} from "@/domain/dashboard/types/dashboard-range";
import { useDashboardPerformance } from "@/domain/dashboard/use-cases/use-dashboard-performance";
import { useDashboardRegions } from "@/domain/dashboard/use-cases/use-dashboard-regions";
import { useDashboardStats } from "@/domain/dashboard/use-cases/use-dashboard-stats";
import { useDashboardVendorInsights } from "@/domain/dashboard/use-cases/use-dashboard-vendor-insights";
import { useDashboardWorkload } from "@/domain/dashboard/use-cases/use-dashboard-workload";
import {
avetaPendingDrilldownSearch,
scheduledTomorrowDrilldownSearch,
UPLIFTS_ROUTE,
workOrderDrilldownUrl,
} from "@/domain/dashboard/utils/work-order-drilldown-links";
export default function DashboardPage() {
const navigate = useNavigate();
const [range, setRange] = useState<DashboardRangeKey>(DEFAULT_DASHBOARD_RANGE);
const [workloadPage, setWorkloadPage] = useState(1);
const [performancePage, setPerformancePage] = useState(1);
const stats = useDashboardStats(range);
const workload = useDashboardWorkload(range, workloadPage);
const performance = useDashboardPerformance(range, performancePage);
const regions = useDashboardRegions(range);
const vendors = useDashboardVendorInsights();
// A range change re-scopes both dispatcher tables, so their page must reset —
// otherwise a switch while on page 3 lands on an out-of-range empty page that
// reads as a false "no dispatchers" state.
const changeRange = (next: DashboardRangeKey) => {
setRange(next);
setWorkloadPage(1);
setPerformancePage(1);
};
const resetFilters = () => changeRange(DEFAULT_DASHBOARD_RANGE);
const isRefreshing =
[stats, workload, performance, regions, vendors].some((query) => query.isFetching) &&
!stats.isLoading;
return (
<Box className="flex flex-col gap-4 p-4">
<PageHeader
title="Dashboard"
actions={
<Box className="flex items-center gap-2">
{isRefreshing && <CircularProgress size={16} />}
{range !== DEFAULT_DASHBOARD_RANGE && (
<Button size="small" onClick={resetFilters}>
Reset filters
</Button>
)}
<DashboardRangeToggle value={range} onChange={changeRange} />
</Box>
}
/>
<Box className="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3">
<DashboardKpiCard
title="Scheduled Tomorrow"
icon={<EventOutlinedIcon />}
tone="primary"
value={stats.data?.scheduledTomorrow}
loading={stats.isLoading}
active={(stats.data?.scheduledTomorrow ?? 0) > 0}
onSelect={() => navigate(workOrderDrilldownUrl(scheduledTomorrowDrilldownSearch()))}
/>
<DashboardKpiCard
title="Pending Uplifts"
icon={<PendingActionsIcon />}
tone="info"
value={stats.data?.pendingUplifts}
loading={stats.isLoading}
active={(stats.data?.pendingUplifts ?? 0) > 0}
onSelect={() => navigate(UPLIFTS_ROUTE)}
/>
<DashboardKpiCard
title="Aveta Pending"
icon={<ShieldOutlinedIcon />}
tone="warning"
value={stats.data?.avetaPending}
loading={stats.isLoading}
active={(stats.data?.avetaPending ?? 0) > 0}
onSelect={() => navigate(workOrderDrilldownUrl(avetaPendingDrilldownSearch()))}
/>
</Box>
<Box className="grid grid-cols-1 gap-4 lg:grid-cols-2">
<WorkOrderBreakdownCard
breakdown={stats.data?.breakdown ?? []}
range={range}
isLoading={stats.isLoading}
error={stats.error}
/>
<WorkOrdersByRegionCard
rows={regions.data ?? []}
isLoading={regions.isLoading}
error={regions.error}
/>
<DispatcherTablesSection
range={range}
workload={workload}
performance={performance}
workloadPage={workloadPage}
performancePage={performancePage}
onWorkloadPageChange={setWorkloadPage}
onPerformancePageChange={setPerformancePage}
/>
</Box>
<DashboardTrendCard range={range} />
<VendorInsightsCard
rows={vendors.data ?? []}
isLoading={vendors.isLoading}
error={vendors.error}
/>
</Box>
);
}