/** * Edit Booking Page (before payment) * * Lets the owner adjust cargo details (volume, weight, pallets, notes) and manage * documents of a booking that is still awaiting payment (PENDING_PAYMENT), then * continue to the payment page. Carrier, route and price derive from the selected * rate and are shown read-only. */ 'use client'; import { useState, useEffect, useRef } from 'react'; import { useRouter, useParams } from 'next/navigation'; import { ArrowLeft, Loader2, AlertTriangle, Save, CreditCard, FileText, Trash2, Upload, Package, } from 'lucide-react'; import { getCsvBooking, updateCsvBookingDetails } from '@/lib/api/bookings'; import { upload, del } from '@/lib/api/client'; interface BookingDoc { id?: string; type: string; fileName: string; url: string; } interface BookingData { id: string; bookingNumber?: string; carrierName: string; origin: string; destination: string; volumeCBM: number; weightKG: number; palletCount: number; containerType: string; status: string; notes?: string; documents: BookingDoc[]; } export default function EditBookingPage() { const router = useRouter(); const params = useParams(); const bookingId = params.id as string; const [booking, setBooking] = useState(null); const [loading, setLoading] = useState(true); const [saving, setSaving] = useState(false); const [error, setError] = useState(null); const [success, setSuccess] = useState(null); const [volumeCBM, setVolumeCBM] = useState(''); const [weightKG, setWeightKG] = useState(''); const [palletCount, setPalletCount] = useState(''); const [notes, setNotes] = useState(''); const [uploadingDocs, setUploadingDocs] = useState(false); const [deletingDocId, setDeletingDocId] = useState(null); const fileInputRef = useRef(null); const applyBooking = (data: BookingData) => { setBooking(data); setVolumeCBM(String(data.volumeCBM ?? '')); setWeightKG(String(data.weightKG ?? '')); setPalletCount(String(data.palletCount ?? '')); setNotes(data.notes ?? ''); }; const loadBooking = async () => { try { const data = (await getCsvBooking(bookingId)) as any as BookingData; if (data.status !== 'PENDING_PAYMENT') { // Only bookings awaiting payment can be edited. router.replace('/dashboard/bookings'); return; } applyBooking(data); } catch (err) { setError('Impossible de charger la réservation'); } finally { setLoading(false); } }; useEffect(() => { if (bookingId) loadBooking(); // eslint-disable-next-line react-hooks/exhaustive-deps }, [bookingId]); const persistDetails = async (): Promise => { setError(null); const vol = parseFloat(volumeCBM); const wgt = parseFloat(weightKG); const plt = parseInt(palletCount, 10); if (!Number.isFinite(vol) || vol <= 0) { setError('Le volume doit être un nombre positif.'); return false; } if (!Number.isFinite(wgt) || wgt <= 0) { setError('Le poids doit être un nombre positif.'); return false; } if (!Number.isFinite(plt) || plt < 0) { setError('Le nombre de palettes est invalide.'); return false; } const updated = (await updateCsvBookingDetails(bookingId, { volumeCBM: vol, weightKG: wgt, palletCount: plt, notes: notes.trim() || undefined, })) as any as BookingData; applyBooking(updated); return true; }; const handleSave = async () => { setSaving(true); setSuccess(null); try { const ok = await persistDetails(); if (ok) setSuccess('Modifications enregistrées.'); } catch (err) { setError(err instanceof Error ? err.message : "Erreur lors de l'enregistrement"); } finally { setSaving(false); } }; const handleContinueToPayment = async () => { setSaving(true); setSuccess(null); try { const ok = await persistDetails(); if (ok) router.push(`/dashboard/booking/${bookingId}/pay`); } catch (err) { setError(err instanceof Error ? err.message : "Erreur lors de l'enregistrement"); } finally { setSaving(false); } }; const handleAddDocuments = async (files: FileList | null) => { if (!files || files.length === 0) return; setUploadingDocs(true); setError(null); try { const formData = new FormData(); Array.from(files).forEach(file => formData.append('documents', file)); await upload(`/api/v1/csv-bookings/${bookingId}/documents`, formData); await loadBooking(); } catch (err) { setError(err instanceof Error ? err.message : "Erreur lors de l'ajout des documents"); } finally { setUploadingDocs(false); if (fileInputRef.current) fileInputRef.current.value = ''; } }; const handleDeleteDocument = async (docId?: string) => { if (!docId) return; if (!confirm('Supprimer ce document ?')) return; setDeletingDocId(docId); setError(null); try { await del(`/api/v1/csv-bookings/${bookingId}/documents/${docId}`); await loadBooking(); } catch (err) { setError(err instanceof Error ? err.message : 'Erreur lors de la suppression du document'); } finally { setDeletingDocId(null); } }; if (loading) { return (
Chargement...
); } if (!booking) { return (

{error || 'Réservation introuvable'}

); } return (

Modifier la réservation

Corrigez les informations de votre réservation avant de procéder au paiement.

{error && (

{error}

)} {success && (

{success}

)} {/* Read-only rate summary */}

Réservation

Transporteur

{booking.carrierName}

Trajet

{booking.origin} → {booking.destination}

Type

{booking.containerType}

{booking.bookingNumber && (

Numéro

{booking.bookingNumber}

)}

Le transporteur, le trajet et le prix proviennent du tarif sélectionné et ne sont pas modifiables ici.

{/* Editable cargo details */}

Caractéristiques de la marchandise

setVolumeCBM(e.target.value)} className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" />
setWeightKG(e.target.value)} className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" />
setPalletCount(e.target.value)} className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" />