xpeditis2.0/apps/frontend/app/[locale]/dashboard/bookings/[id]/page.tsx
2026-08-13 17:43:11 +02:00

240 lines
8.3 KiB
TypeScript

'use client';
import { useQuery } from '@tanstack/react-query';
import { useParams } from 'next/navigation';
import { useLocale, useTranslations } from 'next-intl';
import { ArrowLeft, Download, FileText, Package } from 'lucide-react';
import { getBooking } from '@/lib/api';
import { Link } from '@/i18n/navigation';
import { Badge, type BadgeProps } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { PageHeader } from '@/components/ui/PageHeader';
import { EmptyState, PageSpinner } from '@/components/ui/states';
import { useToast } from '@/components/ui/toast';
import { DescriptionList } from '@/components/data/description-list';
const STATUS_VARIANT: Record<string, NonNullable<BadgeProps['variant']>> = {
draft: 'neutral',
pending: 'warning',
confirmed: 'info',
in_transit: 'info',
delivered: 'success',
cancelled: 'danger',
};
export default function BookingDetailPage() {
const t = useTranslations('dashboard.bookingDetail');
const tCommon = useTranslations('common');
const locale = useLocale();
const dateLocale = locale === 'fr' ? 'fr-FR' : 'en-US';
const params = useParams();
const bookingId = params.id as string;
const { toast } = useToast();
const { data: booking, isLoading } = useQuery({
queryKey: ['booking', bookingId],
queryFn: () => getBooking(bookingId),
enabled: !!bookingId,
});
const statusLabel = (status: string) => {
try {
return t(`status.${status}` as any);
} catch {
return status;
}
};
// Le telechargement PDF n'est pas encore branche cote back : on le signale
// par une notification plutot que par une alerte bloquante du navigateur.
const downloadPDF = () => toast.info(t('pdfNotImplemented'));
if (isLoading) return <PageSpinner label={tCommon('loading')} />;
if (!booking) {
return (
<EmptyState
icon={Package}
title={t('notFound')}
action={
<Button asChild variant="outline">
<Link href="/dashboard/bookings">
<ArrowLeft />
{t('back')}
</Link>
</Button>
}
/>
);
}
return (
<>
<PageHeader
title={booking.bookingNumber}
description={t('createdOn', {
date: new Date(booking.createdAt).toLocaleDateString(dateLocale),
})}
actions={
<>
<Badge variant={STATUS_VARIANT[booking.status] ?? 'neutral'}>
{statusLabel(booking.status)}
</Badge>
<Button variant="outline" onClick={downloadPDF}>
<Download />
{t('downloadPdf')}
</Button>
</>
}
/>
<div className="grid gap-4 lg:grid-cols-3">
<div className="flex flex-col gap-4 lg:col-span-2">
<Card>
<CardHeader className="pb-3">
<CardTitle>{t('cargo.title')}</CardTitle>
</CardHeader>
<CardContent>
<DescriptionList
columns={1}
items={[
{ label: t('cargo.description'), value: booking.cargoDescription },
...(booking.specialInstructions
? [
{
label: t('cargo.specialInstructions'),
value: booking.specialInstructions,
},
]
: []),
]}
/>
</CardContent>
</Card>
<Card>
<CardHeader className="pb-3">
<CardTitle>
{t('containers.title', { count: booking.containers?.length || 0 })}
</CardTitle>
</CardHeader>
<CardContent>
{booking.containers && booking.containers.length > 0 ? (
<ul className="flex flex-col gap-3">
{booking.containers.map((container, index) => (
<li
key={container.id || index}
className="rounded-md border border-border p-4"
>
<DescriptionList
items={[
{ label: t('containers.type'), value: container.type },
...(container.containerNumber
? [{ label: t('containers.number'), value: container.containerNumber }]
: []),
...(container.sealNumber
? [{ label: t('containers.seal'), value: container.sealNumber }]
: []),
...(container.vgm
? [{ label: t('containers.vgm'), value: container.vgm }]
: []),
]}
/>
</li>
))}
</ul>
) : (
<EmptyState size="sm" icon={Package} title={t('containers.title', { count: 0 })} />
)}
</CardContent>
</Card>
<div className="grid gap-4 md:grid-cols-2">
<Card>
<CardHeader className="pb-3">
<CardTitle>{t('shipper.title')}</CardTitle>
</CardHeader>
<CardContent>
<DescriptionList
columns={1}
items={[
{ label: t('shipper.name'), value: booking.shipper.name, emphasis: true },
{ label: t('shipper.contact'), value: booking.shipper.contactName },
{ label: t('shipper.email'), value: booking.shipper.contactEmail },
{ label: t('shipper.phone'), value: booking.shipper.contactPhone },
]}
/>
</CardContent>
</Card>
<Card>
<CardHeader className="pb-3">
<CardTitle>{t('consignee.title')}</CardTitle>
</CardHeader>
<CardContent>
<DescriptionList
columns={1}
items={[
{ label: t('consignee.name'), value: booking.consignee.name, emphasis: true },
{ label: t('consignee.contact'), value: booking.consignee.contactName },
{ label: t('consignee.email'), value: booking.consignee.contactEmail },
{ label: t('consignee.phone'), value: booking.consignee.contactPhone },
]}
/>
</CardContent>
</Card>
</div>
</div>
<div className="flex flex-col gap-4">
<Card>
<CardHeader className="pb-3">
<CardTitle>{t('timeline.title')}</CardTitle>
</CardHeader>
<CardContent>
<ol className="relative flex flex-col gap-4">
<li className="flex gap-3">
<span
aria-hidden="true"
className="mt-0.5 flex size-6 shrink-0 items-center justify-center rounded-full bg-brand-blue/10"
>
<FileText className="size-3 text-brand-blue" />
</span>
<div className="min-w-0">
<p className="text-body-sm font-medium text-brand-navy">
{t('timeline.created')}
</p>
<p className="mt-0.5 text-body-xs text-neutral-500">
{new Date(booking.createdAt).toLocaleString(dateLocale)}
</p>
</div>
</li>
</ol>
</CardContent>
</Card>
<Card>
<CardHeader className="pb-3">
<CardTitle>{t('info.title')}</CardTitle>
</CardHeader>
<CardContent>
<DescriptionList
columns={1}
items={[
{ label: t('info.bookingId'), value: booking.id },
{
label: t('info.lastUpdated'),
value: new Date(booking.updatedAt).toLocaleString(dateLocale),
},
]}
/>
</CardContent>
</Card>
</div>
</div>
</>
);
}