653 lines
24 KiB
TypeScript
653 lines
24 KiB
TypeScript
/**
|
|
* 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: () => <div className="h-80 bg-gray-100 rounded-lg flex items-center justify-center">Chargement de la carte...</div>,
|
|
});
|
|
|
|
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<SearchForm>({
|
|
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<Port | null>(null);
|
|
const [selectedDestinationPort, setSelectedDestinationPort] = useState<Port | null>(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 = () => (
|
|
<div className="space-y-6">
|
|
<h2 className="text-xl font-semibold text-gray-900">1. Informations Générales</h2>
|
|
|
|
<div className="grid grid-cols-2 gap-4">
|
|
{/* Origin Port with Autocomplete */}
|
|
<div className="relative">
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|
Port d'origine * {searchForm.origin && <span className="text-green-600 text-xs">✓ Sélectionné</span>}
|
|
</label>
|
|
<input
|
|
type="text"
|
|
value={originSearch}
|
|
onChange={e => {
|
|
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 && (
|
|
<div className="absolute left-0 right-0 mt-2 bg-white border border-gray-200 rounded-md shadow-lg max-h-60 overflow-y-auto z-50">
|
|
{originPorts.map((port: Port) => (
|
|
<button
|
|
key={port.code}
|
|
type="button"
|
|
onClick={() => {
|
|
setSearchForm({ ...searchForm, origin: port.code });
|
|
setOriginSearch(port.displayName);
|
|
setSelectedOriginPort(port);
|
|
setShowOriginDropdown(false);
|
|
}}
|
|
className="w-full text-left px-4 py-3 hover:bg-blue-50 border-b border-gray-100 last:border-b-0"
|
|
>
|
|
<div className="font-medium text-gray-900">{port.name}</div>
|
|
<div className="text-gray-500 text-xs mt-1">
|
|
{port.code} - {port.city}, {port.countryName}
|
|
</div>
|
|
</button>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Destination Port with Autocomplete */}
|
|
<div className="relative">
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|
Port de destination * {searchForm.destination && <span className="text-green-600 text-xs">✓ Sélectionné</span>}
|
|
</label>
|
|
<input
|
|
type="text"
|
|
value={destinationSearch}
|
|
onChange={e => {
|
|
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 && (
|
|
<div className="absolute left-0 right-0 mt-2 bg-white border border-gray-200 rounded-md shadow-lg max-h-60 overflow-y-auto z-50">
|
|
{destinationPorts.map((port: Port) => (
|
|
<button
|
|
key={port.code}
|
|
type="button"
|
|
onClick={() => {
|
|
setSearchForm({ ...searchForm, destination: port.code });
|
|
setDestinationSearch(port.displayName);
|
|
setSelectedDestinationPort(port);
|
|
setShowDestinationDropdown(false);
|
|
}}
|
|
className="w-full text-left px-4 py-3 hover:bg-blue-50 border-b border-gray-100 last:border-b-0"
|
|
>
|
|
<div className="font-medium text-gray-900">{port.name}</div>
|
|
<div className="text-gray-500 text-xs mt-1">
|
|
{port.code} - {port.city}, {port.countryName}
|
|
</div>
|
|
</button>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Carte interactive de la route maritime */}
|
|
{selectedOriginPort && selectedDestinationPort && (
|
|
<div className="mt-6 border border-gray-200 rounded-lg overflow-hidden">
|
|
<div className="bg-gray-50 px-4 py-3 border-b border-gray-200">
|
|
<h3 className="text-sm font-semibold text-gray-900">
|
|
Route maritime : {selectedOriginPort.name} → {selectedDestinationPort.name}
|
|
</h3>
|
|
<p className="text-xs text-gray-500 mt-1">
|
|
Distance approximative et visualisation de la route
|
|
</p>
|
|
</div>
|
|
<PortRouteMap
|
|
portA={{
|
|
lat: selectedOriginPort.coordinates.latitude,
|
|
lng: selectedOriginPort.coordinates.longitude,
|
|
}}
|
|
portB={{
|
|
lat: selectedDestinationPort.coordinates.latitude,
|
|
lng: selectedDestinationPort.coordinates.longitude,
|
|
}}
|
|
height="400px"
|
|
/>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
|
|
const renderStep2 = () => (
|
|
<div className="space-y-6">
|
|
<div className="flex items-center justify-between">
|
|
<h2 className="text-xl font-semibold text-gray-900">2. Conditionnement</h2>
|
|
<button
|
|
type="button"
|
|
onClick={addPackage}
|
|
className="px-4 py-2 text-sm font-medium text-blue-700 bg-blue-50 rounded-md hover:bg-blue-100"
|
|
>
|
|
+ Ajouter un colis
|
|
</button>
|
|
</div>
|
|
|
|
{searchForm.packages.map((pkg, index) => (
|
|
<div key={index} className="border border-gray-200 rounded-lg p-4 space-y-4">
|
|
<div className="flex items-center justify-between">
|
|
<h3 className="font-medium text-gray-900">Colis #{index + 1}</h3>
|
|
{searchForm.packages.length > 1 && (
|
|
<button
|
|
type="button"
|
|
onClick={() => removePackage(index)}
|
|
className="text-sm text-red-600 hover:text-red-700"
|
|
>
|
|
Supprimer
|
|
</button>
|
|
)}
|
|
</div>
|
|
|
|
<div className="grid grid-cols-5 gap-3">
|
|
<div>
|
|
<label className="block text-xs font-medium text-gray-700 mb-1">Type</label>
|
|
<select
|
|
value={pkg.type}
|
|
onChange={e => updatePackage(index, 'type', e.target.value)}
|
|
className="w-full px-2 py-1.5 text-sm border border-gray-300 rounded-md"
|
|
>
|
|
<option value="caisse">Caisse</option>
|
|
<option value="colis">Colis</option>
|
|
<option value="palette">Palette</option>
|
|
<option value="autre">Autre</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-xs font-medium text-gray-700 mb-1">Quantité</label>
|
|
<input
|
|
type="number"
|
|
min="1"
|
|
value={pkg.quantity}
|
|
onChange={e => updatePackage(index, 'quantity', parseInt(e.target.value) || 1)}
|
|
className="w-full px-2 py-1.5 text-sm border border-gray-300 rounded-md"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-xs font-medium text-gray-700 mb-1">L (cm)</label>
|
|
<input
|
|
type="number"
|
|
min="1"
|
|
value={pkg.length}
|
|
onChange={e => updatePackage(index, 'length', parseInt(e.target.value) || 0)}
|
|
className="w-full px-2 py-1.5 text-sm border border-gray-300 rounded-md"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-xs font-medium text-gray-700 mb-1">l (cm)</label>
|
|
<input
|
|
type="number"
|
|
min="1"
|
|
value={pkg.width}
|
|
onChange={e => updatePackage(index, 'width', parseInt(e.target.value) || 0)}
|
|
className="w-full px-2 py-1.5 text-sm border border-gray-300 rounded-md"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-xs font-medium text-gray-700 mb-1">H (cm)</label>
|
|
<input
|
|
type="number"
|
|
min="1"
|
|
value={pkg.height}
|
|
onChange={e => updatePackage(index, 'height', parseInt(e.target.value) || 0)}
|
|
className="w-full px-2 py-1.5 text-sm border border-gray-300 rounded-md"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-2 gap-3">
|
|
<div>
|
|
<label className="block text-xs font-medium text-gray-700 mb-1">Poids (kg)</label>
|
|
<input
|
|
type="number"
|
|
min="1"
|
|
value={pkg.weight}
|
|
onChange={e => updatePackage(index, 'weight', parseInt(e.target.value) || 0)}
|
|
className="w-full px-2 py-1.5 text-sm border border-gray-300 rounded-md"
|
|
/>
|
|
</div>
|
|
|
|
<div className="flex items-center pt-6">
|
|
<input
|
|
type="checkbox"
|
|
checked={pkg.stackable}
|
|
onChange={e => updatePackage(index, 'stackable', e.target.checked)}
|
|
className="h-4 w-4 text-blue-600 border-gray-300 rounded"
|
|
/>
|
|
<label className="ml-2 text-sm text-gray-700">Gerbable</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
))}
|
|
|
|
<div className="bg-blue-50 border border-blue-200 rounded-md p-4">
|
|
<h3 className="text-sm font-medium text-blue-900 mb-2">Récapitulatif</h3>
|
|
<div className="text-sm text-blue-800 space-y-1">
|
|
<div>Volume total: {calculateTotals().totalVolumeCBM.toFixed(2)} m³</div>
|
|
<div>Poids total: {calculateTotals().totalWeightKG} kg</div>
|
|
<div>Palettes: {calculateTotals().totalPallets}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
|
|
const renderStep3 = () => (
|
|
<div className="space-y-6">
|
|
<h2 className="text-xl font-semibold text-gray-900">3. Options & Services</h2>
|
|
|
|
<div className="space-y-4">
|
|
<div className="border-b pb-4">
|
|
<h3 className="text-sm font-semibold text-gray-900 mb-3">Douane Import / Export</h3>
|
|
<div className="space-y-2">
|
|
<label className="flex items-center">
|
|
<input
|
|
type="checkbox"
|
|
checked={searchForm.eurDocument}
|
|
onChange={e => setSearchForm({ ...searchForm, eurDocument: e.target.checked })}
|
|
className="h-4 w-4 text-blue-600 border-gray-300 rounded"
|
|
/>
|
|
<span className="ml-2 text-sm text-gray-700">EUR 1</span>
|
|
</label>
|
|
<label className="flex items-center">
|
|
<input
|
|
type="checkbox"
|
|
checked={searchForm.t1Document}
|
|
onChange={e => setSearchForm({ ...searchForm, t1Document: e.target.checked })}
|
|
className="h-4 w-4 text-blue-600 border-gray-300 rounded"
|
|
/>
|
|
<span className="ml-2 text-sm text-gray-700">T1</span>
|
|
</label>
|
|
<label className="flex items-center">
|
|
<input
|
|
type="checkbox"
|
|
checked={searchForm.customsStop}
|
|
onChange={e => setSearchForm({ ...searchForm, customsStop: e.target.checked })}
|
|
className="h-4 w-4 text-blue-600 border-gray-300 rounded"
|
|
/>
|
|
<span className="ml-2 text-sm text-gray-700">Stop douane</span>
|
|
</label>
|
|
<label className="flex items-center">
|
|
<input
|
|
type="checkbox"
|
|
checked={searchForm.exportAssistance}
|
|
onChange={e =>
|
|
setSearchForm({ ...searchForm, exportAssistance: e.target.checked })
|
|
}
|
|
className="h-4 w-4 text-blue-600 border-gray-300 rounded"
|
|
/>
|
|
<span className="ml-2 text-sm text-gray-700">Assistance export</span>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="border-b pb-4">
|
|
<h3 className="text-sm font-semibold text-gray-900 mb-3">Marchandise</h3>
|
|
<div className="space-y-2">
|
|
<label className="flex items-center">
|
|
<input
|
|
type="checkbox"
|
|
checked={searchForm.dangerousGoods}
|
|
onChange={e => setSearchForm({ ...searchForm, dangerousGoods: e.target.checked })}
|
|
className="h-4 w-4 text-blue-600 border-gray-300 rounded"
|
|
/>
|
|
<span className="ml-2 text-sm text-gray-700">Marchandise Dangereuse</span>
|
|
</label>
|
|
<label className="flex items-center">
|
|
<input
|
|
type="checkbox"
|
|
checked={searchForm.regulatedProducts}
|
|
onChange={e =>
|
|
setSearchForm({ ...searchForm, regulatedProducts: e.target.checked })
|
|
}
|
|
className="h-4 w-4 text-blue-600 border-gray-300 rounded"
|
|
/>
|
|
<span className="ml-2 text-sm text-gray-700">Produits règlementés</span>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="border-b pb-4">
|
|
<h3 className="text-sm font-semibold text-gray-900 mb-3">Manutention particulière</h3>
|
|
<div className="space-y-2">
|
|
<label className="flex items-center">
|
|
<input
|
|
type="checkbox"
|
|
checked={searchForm.specialHandling}
|
|
onChange={e =>
|
|
setSearchForm({ ...searchForm, specialHandling: e.target.checked })
|
|
}
|
|
className="h-4 w-4 text-blue-600 border-gray-300 rounded"
|
|
/>
|
|
<span className="ml-2 text-sm text-gray-700">Manutention spéciale</span>
|
|
</label>
|
|
<label className="flex items-center">
|
|
<input
|
|
type="checkbox"
|
|
checked={searchForm.tailgate}
|
|
onChange={e => setSearchForm({ ...searchForm, tailgate: e.target.checked })}
|
|
className="h-4 w-4 text-blue-600 border-gray-300 rounded"
|
|
/>
|
|
<span className="ml-2 text-sm text-gray-700">Hayon</span>
|
|
</label>
|
|
<label className="flex items-center">
|
|
<input
|
|
type="checkbox"
|
|
checked={searchForm.straps}
|
|
onChange={e => setSearchForm({ ...searchForm, straps: e.target.checked })}
|
|
className="h-4 w-4 text-blue-600 border-gray-300 rounded"
|
|
/>
|
|
<span className="ml-2 text-sm text-gray-700">Sangles</span>
|
|
</label>
|
|
<label className="flex items-center">
|
|
<input
|
|
type="checkbox"
|
|
checked={searchForm.thermalCover}
|
|
onChange={e => setSearchForm({ ...searchForm, thermalCover: e.target.checked })}
|
|
className="h-4 w-4 text-blue-600 border-gray-300 rounded"
|
|
/>
|
|
<span className="ml-2 text-sm text-gray-700">Couverture thermique</span>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<h3 className="text-sm font-semibold text-gray-900 mb-3">Autres options</h3>
|
|
<div className="space-y-2">
|
|
<label className="flex items-center">
|
|
<input
|
|
type="checkbox"
|
|
checked={searchForm.appointment}
|
|
onChange={e => setSearchForm({ ...searchForm, appointment: e.target.checked })}
|
|
className="h-4 w-4 text-blue-600 border-gray-300 rounded"
|
|
/>
|
|
<span className="ml-2 text-sm text-gray-700">Rendez-vous livraison</span>
|
|
</label>
|
|
<label className="flex items-center">
|
|
<input
|
|
type="checkbox"
|
|
checked={searchForm.insurance}
|
|
onChange={e => setSearchForm({ ...searchForm, insurance: e.target.checked })}
|
|
className="h-4 w-4 text-blue-600 border-gray-300 rounded"
|
|
/>
|
|
<span className="ml-2 text-sm text-gray-700">Assurance</span>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
|
|
return (
|
|
<div className="max-w-7xl mx-auto space-y-6">
|
|
{/* Header */}
|
|
<div>
|
|
<h1 className="text-3xl font-bold text-gray-900">Recherche Avancée de Tarifs</h1>
|
|
<p className="text-sm text-gray-500 mt-1">
|
|
Formulaire complet avec toutes les options de transport
|
|
</p>
|
|
</div>
|
|
|
|
{/* Progress Steps */}
|
|
<div className="flex items-center justify-center space-x-4">
|
|
{[1, 2, 3].map(step => (
|
|
<div key={step} className="flex items-center">
|
|
<div
|
|
className={`flex items-center justify-center w-10 h-10 rounded-full ${
|
|
currentStep >= step ? 'bg-blue-600 text-white' : 'bg-gray-200 text-gray-600'
|
|
}`}
|
|
>
|
|
{step}
|
|
</div>
|
|
{step < 3 && (
|
|
<div
|
|
className={`w-20 h-1 mx-2 ${
|
|
currentStep > step ? 'bg-blue-600' : 'bg-gray-200'
|
|
}`}
|
|
/>
|
|
)}
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
{/* Form */}
|
|
<div className="bg-white rounded-lg shadow p-8">
|
|
{currentStep === 1 && renderStep1()}
|
|
{currentStep === 2 && renderStep2()}
|
|
{currentStep === 3 && renderStep3()}
|
|
|
|
{/* Navigation */}
|
|
<div className="mt-8 flex items-center justify-between pt-6 border-t">
|
|
<button
|
|
type="button"
|
|
onClick={() => setCurrentStep(Math.max(1, currentStep - 1))}
|
|
disabled={currentStep === 1}
|
|
className="px-6 py-2 text-sm font-medium text-gray-700 bg-gray-100 rounded-md hover:bg-gray-200 disabled:opacity-50 disabled:cursor-not-allowed"
|
|
>
|
|
Précédent
|
|
</button>
|
|
|
|
{currentStep < 3 ? (
|
|
<button
|
|
type="button"
|
|
onClick={() => setCurrentStep(currentStep + 1)}
|
|
disabled={!searchForm.origin || !searchForm.destination}
|
|
className="px-6 py-2 text-sm font-medium text-white bg-blue-600 rounded-md hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed"
|
|
>
|
|
Suivant
|
|
</button>
|
|
) : (
|
|
<button
|
|
type="button"
|
|
onClick={handleSearch}
|
|
disabled={!searchForm.origin || !searchForm.destination}
|
|
className="px-6 py-3 text-base font-medium text-white bg-green-600 rounded-md hover:bg-green-700 disabled:opacity-50 disabled:cursor-not-allowed flex items-center"
|
|
>
|
|
🔍 Rechercher les tarifs
|
|
</button>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
);
|
|
}
|