import { useState } from "react";
import {
AlertCircle,
Banknote,
FileSignature,
FileText,
Train,
TrainFront,
UserCheck,
Users,
} from "lucide-react";
import {
Alert,
Badge,
Button,
Container,
Skeleton,
Stack,
Tabs,
} from "@mantine/core";
import { useQueryClient } from "@tanstack/react-query";
import { useAuth } from "@/auth/useAuth";
import { OverviewPageHeader } from "@/components/overview/OverviewPageHeader";
import { OverviewTabContent } from "@/components/overview/OverviewTabContent";
import "@/components/overview/overview.css";
import { QUERY_KEYS } from "@/constants/QUERY_KEYS";
import { useOverview } from "@/hooks/useOverview";
import { FREIGHT_PERMS, hasPermission } from "@/lib/permissions";
import type { OverviewRange, OverviewTabKey } from "@/types/overview";
const TAB_ITEMS: Array<{
value: OverviewTabKey;
label: string;
icon: typeof FileText;
kpiKey:
| "bookings"
| "contracts"
| "billing"
| "operations"
| "customers"
| "staff";
metricKey: string;
/** Any of these keys grants the tab. */
permission: string[];
}> = [
{
value: "bookings",
label: "Bookings",
icon: FileText,
kpiKey: "bookings",
metricKey: "totalActive",
permission: [FREIGHT_PERMS.bookings.view],
},
{
value: "contracts",
label: "Contracts",
icon: FileSignature,
kpiKey: "contracts",
metricKey: "totalActive",
permission: [FREIGHT_PERMS.contracts.view],
},
{
value: "billing",
label: "Billing",
icon: Banknote,
kpiKey: "billing",
metricKey: "successfulPaymentsMtd",
permission: [FREIGHT_PERMS.bookings.view, FREIGHT_PERMS.payments.view],
},
{
value: "operations",
label: "Operations",
icon: Train,
kpiKey: "operations",
metricKey: "trainsActive",
permission: [
FREIGHT_PERMS.trainScheduling.view,
FREIGHT_PERMS.warehouseInventory.view,
FREIGHT_PERMS.firstMile.view,
FREIGHT_PERMS.lastMile.view,
],
},
{
value: "fleet",
label: "Fleet",
icon: TrainFront,
kpiKey: "operations",
metricKey: "wagonsAvailable",
permission: [
FREIGHT_PERMS.fleet.view,
FREIGHT_PERMS.wagons.view,
FREIGHT_PERMS.trainScheduling.view,
],
},
{
value: "customers",
label: "Customers",
icon: Users,
kpiKey: "customers",
metricKey: "totalCustomers",
permission: [FREIGHT_PERMS.customers.view],
},
{
value: "staff",
label: "Staff",
icon: UserCheck,
kpiKey: "staff",
metricKey: "activeEmployees",
permission: [
FREIGHT_PERMS.admin,
FREIGHT_PERMS.staff.roles.view,
FREIGHT_PERMS.staff.employeeRegistration.view,
FREIGHT_PERMS.staff.roleAssignment.view,
],
},
];
function HeaderSkeleton() {
return (
);
}
const OverviewPage = () => {
const [range, setRange] = useState("30d");
const [activeTab, setActiveTab] = useState("bookings");
const queryClient = useQueryClient();
const { user } = useAuth();
const { data: summary, isLoading, isError, error, refetch, isFetching } = useOverview(range);
// Permission-scoped view: only tabs the user may see; a restricted role
// (e.g. operations) gets a summary 403 — that is not a connection problem.
const visibleTabs = TAB_ITEMS.filter((tab) =>
tab.permission.some((key) => hasPermission(user, key)),
);
const currentTab = visibleTabs.some((t) => t.value === activeTab)
? activeTab
: visibleTabs[0]?.value;
const accessDenied =
(error as { response?: { status?: number } } | null)?.response?.status === 403;
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 unknown as Record;
return group[tab.metricKey] ?? 0;
};
return (
{isLoading && !summary ? (
) : (
)}
{isError && !accessDenied && (
}
color="red"
title="Unable to load dashboard summary"
variant="light"
>
Check your connection and try again.
)}
{visibleTabs.length === 0 && !isLoading && !isError && (
Your role has no access to any overview section.
)}
{visibleTabs.length > 0 && (
setActiveTab((value as OverviewTabKey) ?? visibleTabs[0].value)
}
variant="pills"
color="edr-green"
keepMounted={false}
classNames={{ list: "ov-tablist", tab: "ov-tab" }}
>
{visibleTabs.map((tab) => {
const Icon = tab.icon;
const isActive = currentTab === tab.value;
return (
}
rightSection={
summary ? (
{getTabBadge(tab)}
) : undefined
}
>
{tab.label}
);
})}
{visibleTabs.map((tab) => (
))}
)}
);
};
export default OverviewPage;