mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-08 05:25:41 +00:00
change price logic on the ,rule engine ui, auto generate the contrat
This commit is contained in:
@@ -1,11 +1,192 @@
|
||||
import FeaturePlaceholder from "@/components/FeaturePlaceholder";
|
||||
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 (
|
||||
<FeaturePlaceholder
|
||||
title="Overview"
|
||||
description="Track internal freight operations, monitor account administration, and review the latest backoffice activity from a single operational dashboard."
|
||||
/>
|
||||
<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>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user