/** * Advanced Rate Search Page * * Complete search form with all filters and best options display */ 'use client'; import { useState, useMemo } from 'react'; import { useRouter } from 'next/navigation'; import { useQuery } from '@tanstack/react-query'; import { searchPorts, Port } from '@/lib/api/ports'; import dynamic from 'next/dynamic'; // Import dynamique pour éviter les erreurs SSR avec Leaflet const PortRouteMap = dynamic(() => import('@/components/PortRouteMap'), { ssr: false, loading: () =>
Chargement de la carte...
, }); interface Package { type: 'caisse' | 'colis' | 'palette' | 'autre'; quantity: number; length: number; width: number; height: number; weight: number; stackable: boolean; } interface SearchForm { // General origin: string; destination: string; // Conditionnement packages: Package[]; // Douane eurDocument: boolean; customsStop: boolean; exportAssistance: boolean; // Marchandise dangerousGoods: boolean; specialHandling: boolean; // Manutention tailgate: boolean; straps: boolean; thermalCover: boolean; // Autres regulatedProducts: boolean; appointment: boolean; insurance: boolean; t1Document: boolean; } export default function AdvancedSearchPage() { const router = useRouter(); const [searchForm, setSearchForm] = useState({ origin: '', destination: '', packages: [ { type: 'palette', quantity: 1, length: 120, width: 80, height: 100, weight: 500, stackable: true, }, ], eurDocument: false, customsStop: false, exportAssistance: false, dangerousGoods: false, specialHandling: false, tailgate: false, straps: false, thermalCover: false, regulatedProducts: false, appointment: false, insurance: false, t1Document: false, }); const [currentStep, setCurrentStep] = useState(1); const [originSearch, setOriginSearch] = useState(''); const [destinationSearch, setDestinationSearch] = useState(''); const [showOriginDropdown, setShowOriginDropdown] = useState(false); const [showDestinationDropdown, setShowDestinationDropdown] = useState(false); const [selectedOriginPort, setSelectedOriginPort] = useState(null); const [selectedDestinationPort, setSelectedDestinationPort] = useState(null); // Port autocomplete queries const { data: originPortsData } = useQuery({ queryKey: ['ports', originSearch], queryFn: () => searchPorts({ query: originSearch, limit: 10 }), enabled: originSearch.length >= 2 && showOriginDropdown, }); const { data: destinationPortsData } = useQuery({ queryKey: ['ports', destinationSearch], queryFn: () => searchPorts({ query: destinationSearch, limit: 10 }), enabled: destinationSearch.length >= 2 && showDestinationDropdown, }); const originPorts = originPortsData?.ports || []; const destinationPorts = destinationPortsData?.ports || []; // Calculate total volume and weight const calculateTotals = () => { let totalVolumeCBM = 0; let totalWeightKG = 0; let totalPallets = 0; searchForm.packages.forEach(pkg => { const volumeM3 = (pkg.length * pkg.width * pkg.height) / 1000000; totalVolumeCBM += volumeM3 * pkg.quantity; totalWeightKG += pkg.weight * pkg.quantity; if (pkg.type === 'palette') { totalPallets += pkg.quantity; } }); return { totalVolumeCBM, totalWeightKG, totalPallets }; }; const handleSearch = () => { const { totalVolumeCBM, totalWeightKG, totalPallets } = calculateTotals(); // Build query parameters const params = new URLSearchParams({ origin: searchForm.origin, destination: searchForm.destination, volumeCBM: totalVolumeCBM.toString(), weightKG: totalWeightKG.toString(), palletCount: totalPallets.toString(), hasDangerousGoods: searchForm.dangerousGoods.toString(), requiresSpecialHandling: searchForm.specialHandling.toString(), requiresTailgate: searchForm.tailgate.toString(), requiresStraps: searchForm.straps.toString(), requiresThermalCover: searchForm.thermalCover.toString(), hasRegulatedProducts: searchForm.regulatedProducts.toString(), requiresAppointment: searchForm.appointment.toString(), }); // Redirect to results page router.push(`/dashboard/search-advanced/results?${params.toString()}`); }; const addPackage = () => { setSearchForm({ ...searchForm, packages: [ ...searchForm.packages, { type: 'palette', quantity: 1, length: 120, width: 80, height: 100, weight: 500, stackable: true, }, ], }); }; const removePackage = (index: number) => { setSearchForm({ ...searchForm, packages: searchForm.packages.filter((_, i) => i !== index), }); }; const updatePackage = (index: number, field: keyof Package, value: any) => { const newPackages = [...searchForm.packages]; newPackages[index] = { ...newPackages[index], [field]: value }; setSearchForm({ ...searchForm, packages: newPackages }); }; const renderStep1 = () => (

1. Informations Générales

{/* Origin Port with Autocomplete */}
{ setOriginSearch(e.target.value); setShowOriginDropdown(true); if (e.target.value.length < 2) { setSearchForm({ ...searchForm, origin: '' }); } }} onFocus={() => setShowOriginDropdown(true)} placeholder="ex: Rotterdam, Paris, FRPAR" className={`w-full px-3 py-2 border rounded-md focus:ring-blue-500 focus:border-blue-500 ${ searchForm.origin ? 'border-green-500 bg-green-50' : 'border-gray-300' }`} /> {showOriginDropdown && originPorts && originPorts.length > 0 && (
{originPorts.map((port: Port) => ( ))}
)}
{/* Destination Port with Autocomplete */}
{ setDestinationSearch(e.target.value); setShowDestinationDropdown(true); if (e.target.value.length < 2) { setSearchForm({ ...searchForm, destination: '' }); } }} onFocus={() => setShowDestinationDropdown(true)} placeholder="ex: Shanghai, New York, CNSHA" className={`w-full px-3 py-2 border rounded-md focus:ring-blue-500 focus:border-blue-500 ${ searchForm.destination ? 'border-green-500 bg-green-50' : 'border-gray-300' }`} /> {showDestinationDropdown && destinationPorts && destinationPorts.length > 0 && (
{destinationPorts.map((port: Port) => ( ))}
)}
{/* Carte interactive de la route maritime */} {selectedOriginPort && selectedDestinationPort && (

Route maritime : {selectedOriginPort.name} → {selectedDestinationPort.name}

Distance approximative et visualisation de la route

)}
); const renderStep2 = () => (

2. Conditionnement

{searchForm.packages.map((pkg, index) => (

Colis #{index + 1}

{searchForm.packages.length > 1 && ( )}
updatePackage(index, 'quantity', parseInt(e.target.value) || 1)} className="w-full px-2 py-1.5 text-sm border border-gray-300 rounded-md" />
updatePackage(index, 'length', parseInt(e.target.value) || 0)} className="w-full px-2 py-1.5 text-sm border border-gray-300 rounded-md" />
updatePackage(index, 'width', parseInt(e.target.value) || 0)} className="w-full px-2 py-1.5 text-sm border border-gray-300 rounded-md" />
updatePackage(index, 'height', parseInt(e.target.value) || 0)} className="w-full px-2 py-1.5 text-sm border border-gray-300 rounded-md" />
updatePackage(index, 'weight', parseInt(e.target.value) || 0)} className="w-full px-2 py-1.5 text-sm border border-gray-300 rounded-md" />
updatePackage(index, 'stackable', e.target.checked)} className="h-4 w-4 text-blue-600 border-gray-300 rounded" />
))}

Récapitulatif

Volume total: {calculateTotals().totalVolumeCBM.toFixed(2)} m³
Poids total: {calculateTotals().totalWeightKG} kg
Palettes: {calculateTotals().totalPallets}
); const renderStep3 = () => (

3. Options & Services

Douane Import / Export

Marchandise

Manutention particulière

Autres options

); return (
{/* Header */}

Recherche Avancée de Tarifs

Formulaire complet avec toutes les options de transport

{/* Progress Steps */}
{[1, 2, 3].map(step => (
= step ? 'bg-blue-600 text-white' : 'bg-gray-200 text-gray-600' }`} > {step}
{step < 3 && (
step ? 'bg-blue-600' : 'bg-gray-200' }`} /> )}
))}
{/* Form */}
{currentStep === 1 && renderStep1()} {currentStep === 2 && renderStep2()} {currentStep === 3 && renderStep3()} {/* Navigation */}
{currentStep < 3 ? ( ) : ( )}
); }