mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-08 19:28:17 +00:00
141 lines
4.6 KiB
TypeScript
141 lines
4.6 KiB
TypeScript
import { useMemo } from "react";
|
|
import { useParams } from "react-router-dom";
|
|
import {
|
|
Badge,
|
|
Box,
|
|
Center,
|
|
Grid,
|
|
Group,
|
|
Loader,
|
|
Progress,
|
|
RingProgress,
|
|
Stack,
|
|
Text,
|
|
} from "@mantine/core";
|
|
import { Flag, ListChecks } from "lucide-react";
|
|
|
|
import { PageContainer } from "@/components/page";
|
|
import { PageHeader } from "@/components/page/PageHeader";
|
|
import { SectionCard } from "@/components/bookings/detail/SectionCard";
|
|
import { ClearanceMilestoneTimeline } from "@/components/contracts/ClearanceMilestoneTimeline";
|
|
import { GlActionsPanel } from "@/components/contracts/gl-actions/GlActionsPanel";
|
|
import {
|
|
useBookingMilestones,
|
|
useCompleteMilestone,
|
|
} from "@/hooks/contracts/useContracts";
|
|
import { useBookingDetail } from "@/hooks/bookings/useBookings";
|
|
|
|
export default function BookingMilestonesPage() {
|
|
const { id } = useParams<{ id: string }>();
|
|
const { data: booking } = useBookingDetail(id);
|
|
const { data: milestones, isLoading } = useBookingMilestones(id);
|
|
const complete = useCompleteMilestone(id ?? "");
|
|
|
|
const stats = useMemo(() => {
|
|
const list = milestones ?? [];
|
|
const total = list.length;
|
|
const completed = list.filter((m) => m.status === "COMPLETED").length;
|
|
const pct = total === 0 ? 0 : Math.round((completed / total) * 100);
|
|
return { total, completed, pct };
|
|
}, [milestones]);
|
|
|
|
const reference = booking?.reference ?? "Shipment";
|
|
|
|
return (
|
|
<PageContainer>
|
|
<Stack gap="lg">
|
|
<PageHeader
|
|
title={`${reference} milestones`}
|
|
subtitle="Track and advance the Global Logistics clearance milestones for this shipment."
|
|
backTo={id ? `/dashboard/booking-requests/${id}` : undefined}
|
|
breadcrumbs={[
|
|
{ label: "Booking requests", href: "/dashboard/booking-requests" },
|
|
{ label: reference },
|
|
{ label: "Milestones" },
|
|
]}
|
|
meta={
|
|
<Badge
|
|
variant="light"
|
|
color="edr-green"
|
|
radius="sm"
|
|
leftSection={<ListChecks size={13} />}
|
|
>
|
|
{stats.completed}/{stats.total} done
|
|
</Badge>
|
|
}
|
|
/>
|
|
|
|
<Grid gap="lg">
|
|
<Grid.Col span={{ base: 12, lg: 8 }}>
|
|
<Stack gap="lg">
|
|
<SectionCard icon={Flag} title="Clearance milestones">
|
|
{isLoading ? (
|
|
<Center py="xl">
|
|
<Loader color="edr-green" size="sm" />
|
|
</Center>
|
|
) : (
|
|
<ClearanceMilestoneTimeline
|
|
milestones={milestones ?? []}
|
|
busy={complete.isPending}
|
|
onComplete={(code, note) =>
|
|
complete.mutate({ code, note })
|
|
}
|
|
/>
|
|
)}
|
|
</SectionCard>
|
|
|
|
{id ? (
|
|
<GlActionsPanel
|
|
bookingId={id}
|
|
milestones={milestones ?? []}
|
|
/>
|
|
) : null}
|
|
</Stack>
|
|
</Grid.Col>
|
|
|
|
<Grid.Col span={{ base: 12, lg: 4 }}>
|
|
<Box style={{ position: "sticky", top: 24 }}>
|
|
<SectionCard icon={ListChecks} title="Progress" accent="edr-green">
|
|
<Stack align="center" gap="sm">
|
|
<RingProgress
|
|
size={140}
|
|
thickness={12}
|
|
roundCaps
|
|
sections={[{ value: stats.pct, color: "edr-green" }]}
|
|
label={
|
|
<Stack gap={0} align="center">
|
|
<Text fw={800} fz={26} lh={1}>
|
|
{stats.pct}%
|
|
</Text>
|
|
<Text size="xs" c="dimmed">
|
|
complete
|
|
</Text>
|
|
</Stack>
|
|
}
|
|
/>
|
|
<Box w="100%">
|
|
<Group justify="space-between" mb={6}>
|
|
<Text size="xs" c="dimmed" fw={600}>
|
|
Milestones
|
|
</Text>
|
|
<Text size="xs" c="dimmed">
|
|
{stats.completed}/{stats.total}
|
|
</Text>
|
|
</Group>
|
|
<Progress
|
|
value={stats.pct}
|
|
color="edr-green"
|
|
radius="xl"
|
|
size="md"
|
|
/>
|
|
</Box>
|
|
</Stack>
|
|
</SectionCard>
|
|
</Box>
|
|
</Grid.Col>
|
|
</Grid>
|
|
</Stack>
|
|
</PageContainer>
|
|
);
|
|
}
|