Merge pull request #575 from Tria-plc/freight_feature/usermanagement

freight_feature/usermanagement
This commit is contained in:
marshal
2026-07-09 12:03:37 +03:00
committed by GitHub

View File

@@ -1,40 +1,100 @@
import { useState } from "react";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { Badge, Card, Group, Loader, Stack, Text } from "@mantine/core"; import {
import { ChevronRight, PackageCheck, Ship } from "lucide-react"; Badge,
Box,
Card,
Group,
Loader,
SegmentedControl,
Stack,
Text,
} from "@mantine/core";
import { ChevronRight, FileSignature, PackageCheck, Ship } from "lucide-react";
import { PageContainer } from "@/components/page/PageContainer"; import { PageContainer } from "@/components/page/PageContainer";
import { PageHeader } from "@/components/page/PageHeader"; import { PageHeader } from "@/components/page/PageHeader";
import { useDjClearanceQueue } from "@/hooks/contracts/useContracts"; import { useDjClearanceQueue } from "@/hooks/contracts/useContracts";
import { useBookingDjClearanceQueue } from "@/hooks/bookings/useBookings"; import { useBookingDjClearanceQueue } from "@/hooks/bookings/useBookings";
type QueueTab = "contracts" | "shipments";
const prettyStatus = (s?: string | null) =>
(s ?? "")
.toLowerCase()
.replace(/_/g, " ")
.replace(/^\w/, (c) => c.toUpperCase());
/**
* GL Djibouti clearance queues:
* - Contracts: ONE_TIME customs contracts in phased clearance (legacy flow).
* - Shipments: GENERAL-contract bookings in per-booking clearance awaiting a DJ
* action (DO collection after ET finalizes pre-clearance, RO for exports,
* loading milestones). Managed like the one-time flow, but per booking.
*/
export default function GlDjiboutiClearanceListPage() { export default function GlDjiboutiClearanceListPage() {
const navigate = useNavigate(); const navigate = useNavigate();
const [tab, setTab] = useState<QueueTab>("shipments");
const { data: contractQueue, isLoading: contractsLoading } = useDjClearanceQueue(); const { data: contractQueue, isLoading: contractsLoading } = useDjClearanceQueue();
const { data: bookingQueue, isLoading: bookingsLoading } = const { data: bookingQueue, isLoading: bookingsLoading } =
useBookingDjClearanceQueue(); useBookingDjClearanceQueue();
const contractItems = contractQueue?.items ?? []; const contractItems = contractQueue?.items ?? [];
const bookingItems = bookingQueue ?? []; const bookingItems = bookingQueue ?? [];
const isLoading = tab === "contracts" ? contractsLoading : bookingsLoading;
return ( return (
<PageContainer> <PageContainer>
<PageHeader <Stack gap="lg">
title="GL Djibouti — Clearance" <PageHeader
subtitle="Customs contracts and shipment bookings handed off to Djibouti GL." title="GL Djibouti — Clearance"
/> subtitle="Customs contracts and shipment bookings handed off to Djibouti GL."
{contractsLoading || bookingsLoading ? ( />
<Group justify="center" py={60}>
<Loader color="edr-green" /> <SegmentedControl
</Group> value={tab}
) : ( onChange={(v) => setTab(v as QueueTab)}
<Stack gap="sm"> radius="md"
{contractItems.length === 0 && bookingItems.length === 0 ? ( data={[
<Text c="dimmed" ta="center" py="xl"> {
No Djibouti customs work yet. value: "shipments",
</Text> label: (
) : ( <Group gap={6} wrap="nowrap">
<> <PackageCheck size={15} />
{contractItems.map((c) => ( <Box visibleFrom="sm">Shipments</Box>
<Badge size="sm" radius="sm" variant="light" color="edr-green">
{bookingItems.length}
</Badge>
</Group>
),
},
{
value: "contracts",
label: (
<Group gap={6} wrap="nowrap">
<FileSignature size={15} />
<Box visibleFrom="sm">Contracts</Box>
<Badge size="sm" radius="sm" variant="light" color="gray">
{contractItems.length}
</Badge>
</Group>
),
},
]}
/>
{isLoading ? (
<Group justify="center" py={60}>
<Loader color="edr-green" />
</Group>
) : tab === "contracts" ? (
<Stack gap="sm">
{contractItems.length === 0 ? (
<Text c="dimmed" ta="center" py="xl">
No Djibouti customs contracts yet.
</Text>
) : (
contractItems.map((c) => (
<Card <Card
key={c.id} key={c.id}
withBorder withBorder
@@ -49,7 +109,7 @@ export default function GlDjiboutiClearanceListPage() {
<div> <div>
<Text fw={700}>{c.reference}</Text> <Text fw={700}>{c.reference}</Text>
<Text size="sm" c="dimmed"> <Text size="sm" c="dimmed">
{c.tradeDirection} · {c.status} {c.tradeDirection} · {prettyStatus(c.status)}
</Text> </Text>
</div> </div>
</Group> </Group>
@@ -61,15 +121,24 @@ export default function GlDjiboutiClearanceListPage() {
</Group> </Group>
</Group> </Group>
</Card> </Card>
))} ))
{bookingItems.map((b) => ( )}
</Stack>
) : (
<Stack gap="sm">
{bookingItems.length === 0 ? (
<Text c="dimmed" ta="center" py="xl">
No shipment bookings awaiting a Djibouti action.
</Text>
) : (
bookingItems.map((b) => (
<Card <Card
key={b.id} key={b.id}
withBorder withBorder
radius="md" radius="md"
padding="md" padding="md"
style={{ cursor: "pointer" }} style={{ cursor: "pointer" }}
onClick={() => navigate(`/dashboard/clearance/${b.id}`)} onClick={() => navigate(`/dashboard/gl-djibouti/clearance/${b.id}`)}
> >
<Group justify="space-between" wrap="nowrap"> <Group justify="space-between" wrap="nowrap">
<Group gap="sm"> <Group gap="sm">
@@ -80,7 +149,8 @@ export default function GlDjiboutiClearanceListPage() {
<div> <div>
<Text fw={700}>{b.reference}</Text> <Text fw={700}>{b.reference}</Text>
<Text size="sm" c="dimmed"> <Text size="sm" c="dimmed">
{b.tradeDirection} · {b.status} {b.tradeDirection} · {prettyStatus(b.status)}
{b.company?.name ? ` · ${b.company.name}` : ""}
</Text> </Text>
</div> </div>
</Group> </Group>
@@ -92,11 +162,11 @@ export default function GlDjiboutiClearanceListPage() {
</Group> </Group>
</Group> </Group>
</Card> </Card>
))} ))
</> )}
)} </Stack>
</Stack> )}
)} </Stack>
</PageContainer> </PageContainer>
); );
} }