Files
edr-platform/apps/edr-freight-web/portal/src/pages/consignments/ConsignmentDetailPage.tsx
2026-05-20 12:04:07 +03:00

288 lines
8.6 KiB
TypeScript

import { Link, useNavigate, useParams } from "react-router-dom";
import {
AlertTriangle,
ArrowLeft,
ArrowRight,
Building2,
Calendar,
Hash,
MapPin,
Package,
Ruler,
StickyNote,
Trash2,
Weight,
} from "lucide-react";
import Breadcrumbs from "@/components/Breadcrumbs";
import NewConsignmentPage from "./NewConsignmentPage";
import DeleteConsignmentDialog from "./DeleteConsignmentDialog";
import {
getConsignmentById,
type ConsignmentStatus,
} from "./consignments.mock";
import {
Button,
Card,
} from "@edr/ui-common";
export default function ConsignmentDetailPage() {
const { id } = useParams<{ id: string }>();
const navigate = useNavigate();
const consignment = id ? getConsignmentById(id) : undefined;
if (!consignment) {
return (
<div className="min-h-screen p-6">
<div className="space-y-6">
<Breadcrumbs
items={[
{ label: "Consignments", href: "/consignments" },
{ label: "Not found" },
]}
/>
<Card className="p-8 text-center">
<h1 className="text-2xl font-bold text-slate-900">
Consignment not found
</h1>
<p className="mt-2 text-sm text-muted-foreground">
The consignment you're looking for doesn't exist or has been removed.
</p>
<Link
to="/consignments"
className="mt-6 inline-flex items-center gap-2 rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground transition hover:bg-primary/90"
>
<ArrowLeft />
Back to Consignments
</Link>
</Card>
</div>
</div>
);
}
return (
<div className="min-h-screen p-6">
<div className="space-y-6">
<Breadcrumbs
items={[
{ label: "Consignments", href: "/consignments" },
{ label: consignment.trackingNumber },
]}
/>
<Card className="p-6">
<div className="flex flex-col gap-4 md:flex-row md:items-center md:justify-between">
<div className="flex items-center gap-4">
<div className="flex h-16 w-16 items-center justify-center rounded-2xl bg-primary text-primary-foreground">
<Package />
</div>
<div>
<h1 className="flex items-center gap-3 text-3xl font-bold tracking-tight text-slate-900">
{consignment.trackingNumber}
{consignment.hazardous ? (
<span className="inline-flex items-center gap-1 rounded-full bg-red-100 px-2.5 py-1 text-xs font-semibold text-red-700">
<AlertTriangle />
Hazardous
</span>
) : null}
</h1>
<div className="mt-1 flex items-center gap-3 text-sm text-muted-foreground">
<span>{consignment.customer}</span>
<span className="text-slate-300"></span>
<span>Booking {consignment.bookingReference}</span>
<span className="text-slate-300"></span>
<StatusBadge status={consignment.status} />
</div>
</div>
</div>
<div className="flex items-center gap-3">
<NewConsignmentPage
mode="edit"
consignment={{
trackingNumber: consignment.trackingNumber,
bookingId: consignment.bookingId,
bookingReference: consignment.bookingReference,
cargoType: consignment.cargoType,
description: consignment.description,
weightKg: consignment.weightKg,
volumeM3: consignment.volumeM3,
pieces: consignment.pieces,
hazardous: consignment.hazardous,
specialHandling: consignment.specialHandling,
status: consignment.status,
estimatedDelivery: consignment.estimatedDelivery,
}}
>
<Button>Edit Consignment</Button>
</NewConsignmentPage>
<DeleteConsignmentDialog
trackingNumber={consignment.trackingNumber}
onConfirm={() => navigate("/consignments")}
>
<Button variant="outline">
<Trash2 />
Remove
</Button>
</DeleteConsignmentDialog>
</div>
</div>
</Card>
<Card className="p-6">
<div className="flex flex-col items-center justify-between gap-4 md:flex-row">
<RouteEndpoint
label="Origin"
station={consignment.originStation}
/>
<ArrowRight className="text-primary" />
<RouteEndpoint
label="Destination"
station={consignment.destinationStation}
/>
</div>
</Card>
<div className="grid gap-6 md:grid-cols-2">
<DetailCard title="Cargo">
<DetailRow
icon={<Package />}
label="Cargo Type"
value={consignment.cargoType}
/>
<DetailRow
icon={<Hash />}
label="Pieces"
value={String(consignment.pieces)}
/>
<DetailRow
icon={<Weight />}
label="Weight"
value={`${consignment.weightKg.toLocaleString()} kg`}
/>
<DetailRow
icon={<Ruler />}
label="Volume"
value={`${consignment.volumeM3}`}
/>
</DetailCard>
<DetailCard title="References & Schedule">
<DetailRow
icon={<Building2 />}
label="Customer"
value={consignment.customer}
/>
<DetailRow
icon={<Hash />}
label="Booking"
value={consignment.bookingReference}
/>
<DetailRow
icon={<Calendar />}
label="Created"
value={consignment.createdAt}
/>
<DetailRow
icon={<Calendar />}
label="Estimated Delivery"
value={consignment.estimatedDelivery}
/>
</DetailCard>
<DetailCard title="Description">
<div className="flex items-start gap-3 text-sm text-slate-700">
<Package />
<p className="leading-relaxed">{consignment.description}</p>
</div>
</DetailCard>
<DetailCard title="Special Handling">
<div className="flex items-start gap-3 text-sm text-slate-700">
<StickyNote />
<p className="leading-relaxed">{consignment.specialHandling}</p>
</div>
</DetailCard>
</div>
</div>
</div>
);
}
function RouteEndpoint({
label,
station,
}: {
label: string;
station: string;
}) {
return (
<div className="flex items-center gap-3">
<div className="flex h-12 w-12 items-center justify-center rounded-2xl bg-primary text-primary-foreground">
<MapPin />
</div>
<div>
<p className="text-xs font-medium uppercase tracking-wide text-slate-500">
{label}
</p>
<p className="text-lg font-semibold text-slate-900">{station}</p>
</div>
</div>
);
}
function DetailCard({
title,
children,
}: {
title: string;
children: React.ReactNode;
}) {
return (
<Card className="p-6">
<h2 className="text-lg font-semibold text-slate-900">{title}</h2>
<div className="mt-4 space-y-3">{children}</div>
</Card>
);
}
function DetailRow({
icon,
label,
value,
}: {
icon: React.ReactNode;
label: string;
value: string;
}) {
return (
<div className="flex items-start gap-3">
<div className="mt-0.5 text-primary">{icon}</div>
<div className="flex-1">
<p className="text-xs font-medium text-slate-500">{label}</p>
<p className="mt-0.5 text-sm text-slate-900">{value}</p>
</div>
</div>
);
}
function StatusBadge({ status }: { status: ConsignmentStatus }) {
const styles: Record<ConsignmentStatus, string> = {
Pending: "bg-amber-100 text-amber-700",
"In Warehouse": "bg-sky-100 text-sky-700",
"In Transit": "bg-indigo-100 text-indigo-700",
Delivered: "bg-emerald-100 text-emerald-700",
Returned: "bg-red-100 text-red-700",
};
return (
<span
className={`inline-flex rounded-full px-3 py-1 text-xs font-medium ${styles[status]}`}
>
{status}
</span>
);
}