mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-30 06:28:12 +00:00
128 lines
4.4 KiB
TypeScript
128 lines
4.4 KiB
TypeScript
import { useState, type ReactElement } from "react";
|
|
import { AlertCircle } from "lucide-react";
|
|
import { Alert, Button, Skeleton, Stack } from "@mantine/core";
|
|
import { useQueryClient } from "@tanstack/react-query";
|
|
|
|
import { useAuth } from "@/auth/useAuth";
|
|
import { PageContainer } from "@/components/page";
|
|
import { ClearanceOverview } from "@/components/overview/layouts/ClearanceOverview";
|
|
import { ExecutiveOverview } from "@/components/overview/layouts/ExecutiveOverview";
|
|
import { FinanceOverview } from "@/components/overview/layouts/FinanceOverview";
|
|
import { MarketingOverview } from "@/components/overview/layouts/MarketingOverview";
|
|
import { OccOverview } from "@/components/overview/layouts/OccOverview";
|
|
import { OperationsOverview } from "@/components/overview/layouts/OperationsOverview";
|
|
import type { RoleOverviewProps } from "@/components/overview/layouts/layout-kit";
|
|
import {
|
|
OVERVIEW_LAYOUT_LABEL,
|
|
resolveOverviewLayout,
|
|
type OverviewLayoutKey,
|
|
} from "@/components/overview/role-dashboards.config";
|
|
import { OverviewHero } from "@/components/overview/summary/OverviewHero";
|
|
import { OverviewHeroKpis } from "@/components/overview/summary/OverviewHeroKpis";
|
|
import { QUERY_KEYS } from "@/constants/QUERY_KEYS";
|
|
import { useOverview } from "@/hooks/useOverview";
|
|
import type { OverviewRange } from "@/types/overview";
|
|
import "@/components/overview/summary/overview-summary.css";
|
|
|
|
const RANGE_LABEL: Record<OverviewRange, string> = { "7d": "7d", "30d": "30d", "90d": "90d" };
|
|
|
|
/** Which composition each role sees below the hero. */
|
|
const LAYOUTS: Record<OverviewLayoutKey, (props: RoleOverviewProps) => ReactElement> = {
|
|
executive: ExecutiveOverview,
|
|
operations: OperationsOverview,
|
|
occ: OccOverview,
|
|
marketing: MarketingOverview,
|
|
finance: FinanceOverview,
|
|
clearance: ClearanceOverview,
|
|
};
|
|
|
|
function OverviewSkeleton() {
|
|
return (
|
|
<Stack gap="lg">
|
|
<Skeleton height={92} radius="lg" />
|
|
<Skeleton height={340} radius="lg" />
|
|
<Skeleton height={340} radius="lg" />
|
|
<Skeleton height={340} radius="lg" />
|
|
</Stack>
|
|
);
|
|
}
|
|
|
|
const OverviewPage = () => {
|
|
const [range, setRange] = useState<OverviewRange>("30d");
|
|
const queryClient = useQueryClient();
|
|
const { user } = useAuth();
|
|
const { data, isLoading, isError, error, refetch, isFetching } = useOverview(range);
|
|
|
|
// Hero, range control and headline KPIs are role-neutral; everything below
|
|
// them is chosen by role key.
|
|
const layoutKey = resolveOverviewLayout(user);
|
|
const RoleLayout = LAYOUTS[layoutKey];
|
|
|
|
const accessDenied =
|
|
(error as { response?: { status?: number } } | null)?.response?.status === 403;
|
|
|
|
const handleRefresh = () => {
|
|
void refetch();
|
|
void queryClient.invalidateQueries({ queryKey: QUERY_KEYS.OVERVIEW.ROOT });
|
|
};
|
|
|
|
return (
|
|
<PageContainer>
|
|
{/* Gradient greeting hero; the KPI strip overlaps its bottom edge. */}
|
|
<div className="ov-band">
|
|
<OverviewHero
|
|
range={range}
|
|
onRangeChange={setRange}
|
|
generatedAt={data?.generatedAt}
|
|
onRefresh={handleRefresh}
|
|
isRefreshing={isFetching && !isLoading}
|
|
label={OVERVIEW_LAYOUT_LABEL[layoutKey]}
|
|
/>
|
|
{data ? (
|
|
<div style={{ marginTop: -52, paddingInline: 20, position: "relative" }}>
|
|
<OverviewHeroKpis
|
|
kpis={data.kpis}
|
|
current={data.current}
|
|
previous={data.previous}
|
|
rangeLabel={RANGE_LABEL[range]}
|
|
/>
|
|
</div>
|
|
) : null}
|
|
</div>
|
|
|
|
{isError && !accessDenied && (
|
|
<Alert
|
|
icon={<AlertCircle size={16} />}
|
|
color="red"
|
|
title="Unable to load dashboard summary"
|
|
variant="light"
|
|
mt="lg"
|
|
>
|
|
<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>
|
|
)}
|
|
|
|
{accessDenied && (
|
|
<Alert color="gray" variant="light" title="No dashboard access" mt="lg">
|
|
Your role has no access to the overview.
|
|
</Alert>
|
|
)}
|
|
|
|
{isLoading && !data ? (
|
|
<Stack mt="lg">
|
|
<OverviewSkeleton />
|
|
</Stack>
|
|
) : data ? (
|
|
<RoleLayout data={data} range={range} />
|
|
) : null}
|
|
</PageContainer>
|
|
);
|
|
};
|
|
|
|
export default OverviewPage;
|