mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-29 14:08:11 +00:00
194 lines
5.0 KiB
TypeScript
194 lines
5.0 KiB
TypeScript
import { useState } from "react";
|
|
import {
|
|
AlertCircle,
|
|
Banknote,
|
|
FileText,
|
|
Train,
|
|
UserCheck,
|
|
Users,
|
|
} from "lucide-react";
|
|
import {
|
|
Alert,
|
|
Badge,
|
|
Button,
|
|
Container,
|
|
Paper,
|
|
Skeleton,
|
|
Stack,
|
|
Tabs,
|
|
} from "@mantine/core";
|
|
import { useQueryClient } from "@tanstack/react-query";
|
|
|
|
import { OverviewPageHeader } from "@/components/overview/OverviewPageHeader";
|
|
import { OverviewQuickLinks } from "@/components/overview/OverviewQuickLinks";
|
|
import { OverviewTabContent } from "@/components/overview/OverviewTabContent";
|
|
import { QUERY_KEYS } from "@/constants/QUERY_KEYS";
|
|
import { useOverview } from "@/hooks/useOverview";
|
|
import type { OverviewRange, OverviewTabKey } from "@/types/overview";
|
|
|
|
const TAB_ITEMS: Array<{
|
|
value: OverviewTabKey;
|
|
label: string;
|
|
icon: typeof FileText;
|
|
kpiKey: "bookings" | "billing" | "operations" | "customers" | "staff";
|
|
metricKey: string;
|
|
}> = [
|
|
{
|
|
value: "bookings",
|
|
label: "Bookings",
|
|
icon: FileText,
|
|
kpiKey: "bookings",
|
|
metricKey: "totalActive",
|
|
},
|
|
{
|
|
value: "billing",
|
|
label: "Billing",
|
|
icon: Banknote,
|
|
kpiKey: "billing",
|
|
metricKey: "successfulPaymentsMtd",
|
|
},
|
|
{
|
|
value: "operations",
|
|
label: "Operations",
|
|
icon: Train,
|
|
kpiKey: "operations",
|
|
metricKey: "trainsActive",
|
|
},
|
|
{
|
|
value: "customers",
|
|
label: "Customers",
|
|
icon: Users,
|
|
kpiKey: "customers",
|
|
metricKey: "totalCustomers",
|
|
},
|
|
{
|
|
value: "staff",
|
|
label: "Staff",
|
|
icon: UserCheck,
|
|
kpiKey: "staff",
|
|
metricKey: "activeEmployees",
|
|
},
|
|
];
|
|
|
|
function HeaderSkeleton() {
|
|
return (
|
|
<Stack gap="md">
|
|
<Skeleton height={48} radius="md" />
|
|
<Skeleton height={52} radius="lg" />
|
|
</Stack>
|
|
);
|
|
}
|
|
|
|
const OverviewPage = () => {
|
|
const [range, setRange] = useState<OverviewRange>("30d");
|
|
const [activeTab, setActiveTab] = useState<OverviewTabKey>("bookings");
|
|
const queryClient = useQueryClient();
|
|
const { data: summary, isLoading, isError, refetch, isFetching } = useOverview(range);
|
|
|
|
const handleRefresh = () => {
|
|
void refetch();
|
|
void queryClient.invalidateQueries({ queryKey: QUERY_KEYS.OVERVIEW.ROOT });
|
|
};
|
|
|
|
const getTabBadge = (tab: (typeof TAB_ITEMS)[number]) => {
|
|
if (!summary?.kpis) return 0;
|
|
const group = summary.kpis[tab.kpiKey] as Record<string, number>;
|
|
return group[tab.metricKey] ?? 0;
|
|
};
|
|
|
|
return (
|
|
<Container fluid px="md" py="md">
|
|
<Stack gap="lg">
|
|
{isLoading && !summary ? (
|
|
<HeaderSkeleton />
|
|
) : (
|
|
<OverviewPageHeader
|
|
range={range}
|
|
onRangeChange={setRange}
|
|
generatedAt={summary?.generatedAt}
|
|
onRefresh={handleRefresh}
|
|
isRefreshing={isFetching && !isLoading}
|
|
/>
|
|
)}
|
|
|
|
{isError && (
|
|
<Alert
|
|
icon={<AlertCircle size={16} />}
|
|
color="red"
|
|
title="Unable to load dashboard summary"
|
|
variant="light"
|
|
>
|
|
<Stack gap="sm" align="flex-start">
|
|
<span>Check your connection and try again.</span>
|
|
<Button size="xs" variant="light" color="red" onClick={() => void refetch()}>
|
|
Retry
|
|
</Button>
|
|
</Stack>
|
|
</Alert>
|
|
)}
|
|
|
|
<Paper
|
|
radius="lg"
|
|
withBorder
|
|
p="md"
|
|
style={{
|
|
background: "white",
|
|
border: "1px solid var(--mantine-color-gray-2)",
|
|
}}
|
|
>
|
|
<Tabs
|
|
value={activeTab}
|
|
onChange={(value) => setActiveTab((value as OverviewTabKey) ?? "bookings")}
|
|
variant="pills"
|
|
color="green"
|
|
keepMounted={false}
|
|
>
|
|
<Tabs.List
|
|
style={{
|
|
flexWrap: "wrap",
|
|
gap: 8,
|
|
background: "var(--freight-brand-muted, #f0fdf4)",
|
|
padding: 8,
|
|
borderRadius: 12,
|
|
}}
|
|
>
|
|
{TAB_ITEMS.map((tab) => {
|
|
const Icon = tab.icon;
|
|
return (
|
|
<Tabs.Tab
|
|
key={tab.value}
|
|
value={tab.value}
|
|
leftSection={<Icon size={16} />}
|
|
rightSection={
|
|
summary ? (
|
|
<Badge size="sm" variant="light" color="green">
|
|
{getTabBadge(tab)}
|
|
</Badge>
|
|
) : undefined
|
|
}
|
|
style={{ fontWeight: 600 }}
|
|
>
|
|
{tab.label}
|
|
</Tabs.Tab>
|
|
);
|
|
})}
|
|
</Tabs.List>
|
|
|
|
{TAB_ITEMS.map((tab) => (
|
|
<Tabs.Panel key={tab.value} value={tab.value} pt="lg">
|
|
<OverviewTabContent tab={tab.value} range={range} />
|
|
</Tabs.Panel>
|
|
))}
|
|
</Tabs>
|
|
</Paper>
|
|
|
|
<Paper p="lg" radius="lg" withBorder>
|
|
<OverviewQuickLinks />
|
|
</Paper>
|
|
</Stack>
|
|
</Container>
|
|
);
|
|
};
|
|
|
|
export default OverviewPage;
|