mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-08 12:28:21 +00:00
288 lines
8.6 KiB
TypeScript
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} m³`}
|
|
/>
|
|
</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>
|
|
);
|
|
}
|