mirror of
https://github.com/Sea-Haven-Industries/shoc-frontend-new.git
synced 2026-10-06 05:22:04 +00:00
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.
132 lines
5.4 KiB
TypeScript
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>
|
|
);
|
|
}
|