/** * CSV Booking Creation Page * * Multi-step form for creating a CSV-based booking request */ 'use client'; import { useState, useEffect, Suspense } from 'react'; import { useRouter, useSearchParams } from 'next/navigation'; import type { CsvRateSearchResult } from '@/types/rates'; import { createCsvBooking } from '@/lib/api/bookings'; interface BookingForm { // Rate data (pre-filled from search results) carrierName: string; carrierEmail: string; origin: string; destination: string; volumeCBM: number; weightKG: number; palletCount: number; priceUSD: number; priceEUR: number; primaryCurrency: 'USD' | 'EUR'; transitDays: number; containerType: string; // Documents to upload documents: File[]; // Optional notes notes?: string; } const DOCUMENT_TYPES = [ { value: 'BILL_OF_LADING', label: 'Bill of Lading (Connaissement)' }, { value: 'PACKING_LIST', label: 'Packing List (Liste de colisage)' }, { value: 'COMMERCIAL_INVOICE', label: 'Commercial Invoice (Facture commerciale)' }, { value: 'CERTIFICATE_OF_ORIGIN', label: 'Certificate of Origin (Certificat d\'origine)' }, { value: 'OTHER', label: 'Autre document' }, ]; const MAX_FILE_SIZE = 5 * 1024 * 1024; // 5MB const ACCEPTED_FILE_TYPES = ['.pdf', '.doc', '.docx', '.jpg', '.jpeg', '.png']; function NewBookingPageContent() { const router = useRouter(); const searchParams = useSearchParams(); const [currentStep, setCurrentStep] = useState(1); const [isSubmitting, setIsSubmitting] = useState(false); const [error, setError] = useState(null); const [formData, setFormData] = useState({ carrierName: '', carrierEmail: '', origin: '', destination: '', volumeCBM: 0, weightKG: 0, palletCount: 0, priceUSD: 0, priceEUR: 0, primaryCurrency: 'EUR', transitDays: 0, containerType: '', documents: [], notes: '', }); // Load rate data from URL params useEffect(() => { const rateDataParam = searchParams.get('rateData'); if (rateDataParam) { try { const rateData: CsvRateSearchResult = JSON.parse(decodeURIComponent(rateDataParam)); setFormData(prev => ({ ...prev, carrierName: rateData.companyName, carrierEmail: rateData.companyEmail, origin: rateData.origin, destination: rateData.destination, volumeCBM: parseFloat(searchParams.get('volumeCBM') || '0'), weightKG: parseFloat(searchParams.get('weightKG') || '0'), palletCount: parseInt(searchParams.get('palletCount') || '0'), priceUSD: rateData.priceUSD, priceEUR: rateData.priceEUR, primaryCurrency: rateData.primaryCurrency as 'USD' | 'EUR', transitDays: rateData.transitDays, containerType: rateData.containerType, })); } catch (err) { console.error('Failed to parse rate data:', err); setError('Données de tarif invalides'); } } else { // No rate data - redirect back to search router.push('/dashboard/search-advanced'); } }, [searchParams, router]); const handleFileChange = (files: FileList | null) => { if (!files) return; const newFiles: File[] = []; const errors: string[] = []; Array.from(files).forEach(file => { // Check file size if (file.size > MAX_FILE_SIZE) { errors.push(`${file.name}: Fichier trop volumineux (max 5MB)`); return; } // Check file type const fileExtension = '.' + file.name.split('.').pop()?.toLowerCase(); if (!ACCEPTED_FILE_TYPES.includes(fileExtension)) { errors.push(`${file.name}: Type de fichier non accepté`); return; } newFiles.push(file); }); if (errors.length > 0) { setError(errors.join('\n')); } else { setError(null); } setFormData(prev => ({ ...prev, documents: [...prev.documents, ...newFiles], })); }; const removeDocument = (index: number) => { setFormData(prev => ({ ...prev, documents: prev.documents.filter((_, i) => i !== index), })); }; const handleSubmit = async () => { setIsSubmitting(true); setError(null); try { // Create FormData for multipart upload const formDataToSend = new FormData(); // Append all booking data formDataToSend.append('carrierName', formData.carrierName); formDataToSend.append('carrierEmail', formData.carrierEmail); formDataToSend.append('origin', formData.origin); formDataToSend.append('destination', formData.destination); formDataToSend.append('volumeCBM', formData.volumeCBM.toString()); formDataToSend.append('weightKG', formData.weightKG.toString()); formDataToSend.append('palletCount', formData.palletCount.toString()); formDataToSend.append('priceUSD', formData.priceUSD.toString()); formDataToSend.append('priceEUR', formData.priceEUR.toString()); formDataToSend.append('primaryCurrency', formData.primaryCurrency); formDataToSend.append('transitDays', formData.transitDays.toString()); formDataToSend.append('containerType', formData.containerType); if (formData.notes) { formDataToSend.append('notes', formData.notes); } // Append documents formData.documents.forEach((file) => { formDataToSend.append('documents', file); }); // Send to API using client function const result = await createCsvBooking(formDataToSend); // Redirect to success page router.push(`/dashboard/bookings?success=true&id=${result.id}`); } catch (err) { console.error('Booking creation error:', err); setError(err instanceof Error ? err.message : 'Une erreur est survenue'); } finally { setIsSubmitting(false); } }; const canProceedToStep2 = formData.carrierName && formData.origin && formData.destination; const canProceedToStep3 = formData.documents.length >= 1; const canSubmit = canProceedToStep3; const formatPrice = (price: number, currency: string) => { return new Intl.NumberFormat('fr-FR', { style: 'currency', currency: currency, }).format(price); }; return (
{/* Header */}

Nouvelle demande de réservation

Envoyez une demande de réservation directement au transporteur

{/* Progress Steps */}
{[1, 2, 3].map(step => (
= step ? 'bg-blue-600 text-white' : 'bg-gray-200 text-gray-600' }`} > {step}

= step ? 'text-blue-600' : 'text-gray-500' }`} > {step === 1 && 'Détails'} {step === 2 && 'Documents'} {step === 3 && 'Révision'}

{step < 3 && (
step ? 'bg-blue-600' : 'bg-gray-200' }`} /> )}
))}
{/* Error Message */} {error && (
⚠️

Erreur

{error}

)} {/* Step Content */}
{/* Step 1: Transport Details (Read-only) */} {currentStep === 1 && (

Détails du transport

{/* Carrier Info */}

Transporteur

Nom

{formData.carrierName}

Email

{formData.carrierEmail}

{/* Route Info */}

Trajet

Origine

{formData.origin}

{formData.transitDays} jours

Destination

{formData.destination}

{/* Shipment Details */}

Volume

{formData.volumeCBM} CBM

Poids

{formData.weightKG} kg

Palettes

{formData.palletCount || 'N/A'}

Type

{formData.containerType}

{/* Pricing */}

Prix estimé

Prix en EUR

{formatPrice(formData.priceEUR, 'EUR')}

{formData.priceUSD > 0 && (

Prix en USD

{formatPrice(formData.priceUSD, 'USD')}

)}
)} {/* Step 2: Document Upload */} {currentStep === 2 && (

Documents requis

📋

Information importante

Veuillez télécharger au moins 1 document pour continuer. Formats acceptés: PDF, DOC, DOCX, JPG, PNG (max 5MB par fichier)

{/* File Upload */}
handleFileChange(e.target.files)} className="hidden" />
{/* Document Type Suggestions */}

Documents recommandés :

    {DOCUMENT_TYPES.map(type => (
  • • {type.label}
  • ))}
{/* Uploaded Files List */} {formData.documents.length > 0 && (

Documents sélectionnés ({formData.documents.length})

{formData.documents.map((file, index) => (

{file.name}

{(file.size / 1024 / 1024).toFixed(2)} MB

))}
)} {/* Optional Notes */}