'use client'; import { useQuery } from '@tanstack/react-query'; import { useParams } from 'next/navigation'; import { useLocale, useTranslations } from 'next-intl'; import { ArrowLeft, ArrowRight, CreditCard, FileText, Package, Pencil } from 'lucide-react'; import { getCsvBooking } from '@/lib/api'; import { Link } from '@/i18n/navigation'; import { Button } from '@/components/ui/button'; import { Callout } from '@/components/ui/callout'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { PageHeader } from '@/components/ui/PageHeader'; import { EmptyState, ErrorState, PageSpinner } from '@/components/ui/states'; import { BookingStatusBadge } from '@/components/bookings/BookingStatusBadge'; /** * Detail d'une reservation. * * La page interrogeait `GET /bookings/:id` et affichait un modele — expediteur, * destinataire, conteneurs — qui n'existe pas dans ce produit : la table * `bookings` n'a jamais ete creee, les reservations vivent dans `csv_bookings`. * Elle repondait donc 500 et tournait indefiniment. C'est ce qui donnait * l'impression qu'un clic sur une notification « ne faisait rien » : la * navigation avait bien lieu, la destination ne s'affichait jamais. * * Elle lit desormais `GET /csv-bookings/:id`, la meme source que la liste. */ export default function BookingDetailPage() { const t = useTranslations('dashboard.bookingDetail'); const tList = useTranslations('dashboard.bookingsList'); 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 { data: booking, isPending, isError, error, refetch, } = useQuery({ queryKey: ['csv-booking', bookingId], queryFn: () => getCsvBooking(bookingId), enabled: Boolean(bookingId), // Une reservation introuvable ne le deviendra pas en reessayant : trois // tentatives ne faisaient que prolonger le tourniquet. retry: (failureCount, err) => (err as { statusCode?: number })?.statusCode === 404 ? false : failureCount < 1, }); const dateTime = (value?: string) => value ? new Date(value).toLocaleString(dateLocale, { dateStyle: 'long', timeStyle: 'short' }) : '—'; if (isPending) return ; if (isError) { const missing = (error as { statusCode?: number })?.statusCode === 404; return missing ? ( {t('back')} } /> ) : ( refetch()} /> ); } const unpaid = booking.status === 'PENDING_PAYMENT'; const price = booking.priceEUR ? `${booking.priceEUR} €` : booking.priceUSD ? `${booking.priceUSD} $` : null; return ( <> {unpaid && ( )} } /> {unpaid && ( {tList('detail.pendingPaymentNotice')} )} {booking.status === 'REJECTED' && booking.rejectionReason && ( {booking.rejectionReason} )}
{/* La route est ce qu'un transitaire regarde en premier : elle occupe la tete de page, pas une ligne de tableau parmi d'autres. */}
{booking.origin} {booking.destination}
{booking.carrierName && (

{t('carrier', { carrier: booking.carrierName })}

)}
{tList('detail.cargo')}
{booking.palletCount ? tList('units.palletsCount', { count: booking.palletCount }) : '—'} {booking.volumeCBM ? tList('units.cbm', { value: booking.volumeCBM }) : '—'} {booking.weightKG ? tList('units.kg', { value: booking.weightKG }) : '—'}
{booking.notes && (

{booking.notes}

)}
{t('documents', { count: booking.documents?.length ?? 0 })} {booking.documents?.length ? (
    {booking.documents.map((document, index) => (
  • ))}
) : ( )}
{price && ( {tList('detail.price')}

{price}

{booking.freightTotal ? ( {booking.freightTotal} {booking.freightCurrency} ) : null} {booking.fobTotal ? ( {booking.fobTotal} {booking.fobCurrency} ) : null} {booking.commissionAmountEur ? ( {booking.commissionAmountEur} € ) : null}
)} {t('timeline')}
{dateTime(booking.requestedAt)} {booking.respondedAt && ( {dateTime(booking.respondedAt)} )} {booking.id}
); } function Field({ label, children }: { label: string; children: React.ReactNode }) { return (
{label}
{children}
); }