xpeditis2.0/apps/frontend/src/app/rates/csv-search/page.tsx
David 08787c89c8
Some checks failed
Dev CI / Unit Tests (${{ matrix.app }}) (backend) (push) Blocked by required conditions
Dev CI / Unit Tests (${{ matrix.app }}) (frontend) (push) Blocked by required conditions
Dev CI / Notify Failure (push) Blocked by required conditions
Dev CI / Quality (${{ matrix.app }}) (backend) (push) Has been cancelled
Dev CI / Quality (${{ matrix.app }}) (frontend) (push) Has been cancelled
chore: sync full codebase from cicd branch
Aligns dev with the complete application codebase (cicd branch).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 12:56:16 +02:00

233 lines
8.0 KiB
TypeScript

/**
* CSV Rate Search Page
*
* Complete rate search page with:
* - Volume/Weight/Pallet input
* - Advanced filters panel
* - Results table with CSV/API source badges
*/
'use client';
import { useState } from 'react';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Label } from '@/components/ui/label';
import { Input } from '@/components/ui/input';
import { Alert, AlertDescription } from '@/components/ui/alert';
import { Loader2, Search } from 'lucide-react';
import { VolumeWeightInput } from '@/components/rate-search/VolumeWeightInput';
import { RateFiltersPanel } from '@/components/rate-search/RateFiltersPanel';
import { RateResultsTable } from '@/components/rate-search/RateResultsTable';
import { useCsvRateSearch } from '@/hooks/useCsvRateSearch';
import type { RateSearchFilters } from '@/types/rate-filters';
export default function CsvRateSearchPage() {
// Search parameters
const [origin, setOrigin] = useState('NLRTM');
const [destination, setDestination] = useState('USNYC');
const [volumeCBM, setVolumeCBM] = useState(25.5);
const [weightKG, setWeightKG] = useState(3500);
const [palletCount, setPalletCount] = useState(10);
const [filters, setFilters] = useState<RateSearchFilters>({});
const [currency, setCurrency] = useState<'USD' | 'EUR'>('USD');
const { data, loading, error, search } = useCsvRateSearch();
const handleSearch = async () => {
await search({
origin,
destination,
volumeCBM,
weightKG,
palletCount,
containerType: 'LCL',
filters,
});
};
const handleResetFilters = () => {
setFilters({});
};
const handleBooking = (result: any) => {
alert(`Booking pour ${result.companyName}: ${result.origin}${result.destination}`);
// TODO: Implement actual booking flow
};
return (
<div className="container mx-auto py-8 space-y-6">
{/* Page Header */}
<div>
<h1 className="text-3xl font-bold tracking-tight">Recherche de tarifs CSV</h1>
<p className="text-muted-foreground mt-2">
Recherchez des tarifs de transport maritime avec filtres avancés
</p>
</div>
<div className="grid grid-cols-1 lg:grid-cols-4 gap-6">
{/* Left Column: Filters */}
<div className="lg:col-span-1">
<RateFiltersPanel
filters={filters}
onFiltersChange={setFilters}
resultsCount={data?.totalResults || 0}
onReset={handleResetFilters}
/>
</div>
{/* Right Column: Search Form + Results */}
<div className="lg:col-span-3 space-y-6">
{/* Search Form */}
<Card>
<CardHeader>
<CardTitle>Paramètres de recherche</CardTitle>
<CardDescription>
Indiquez votre trajet et les dimensions de votre envoi
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
{/* Origin and Destination */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="origin">
Port d'origine <span className="text-red-500">*</span>
</Label>
<Input
id="origin"
value={origin}
onChange={e => setOrigin(e.target.value.toUpperCase())}
placeholder="NLRTM"
maxLength={5}
required
/>
<p className="text-xs text-muted-foreground">Code UN/LOCODE (5 caractères)</p>
</div>
<div className="space-y-2">
<Label htmlFor="destination">
Port de destination <span className="text-red-500">*</span>
</Label>
<Input
id="destination"
value={destination}
onChange={e => setDestination(e.target.value.toUpperCase())}
placeholder="USNYC"
maxLength={5}
required
/>
<p className="text-xs text-muted-foreground">Code UN/LOCODE (5 caractères)</p>
</div>
</div>
{/* Volume, Weight, Pallets */}
<VolumeWeightInput
volumeCBM={volumeCBM}
weightKG={weightKG}
palletCount={palletCount}
onVolumeChange={setVolumeCBM}
onWeightChange={setWeightKG}
onPalletChange={setPalletCount}
disabled={loading}
/>
{/* Currency Selection */}
<div className="space-y-2">
<Label>Devise d'affichage</Label>
<div className="flex gap-2">
<Button
type="button"
variant={currency === 'USD' ? 'default' : 'outline'}
onClick={() => setCurrency('USD')}
disabled={loading}
>
USD ($)
</Button>
<Button
type="button"
variant={currency === 'EUR' ? 'default' : 'outline'}
onClick={() => setCurrency('EUR')}
disabled={loading}
>
EUR ()
</Button>
</div>
</div>
{/* Search Button */}
<Button
onClick={handleSearch}
disabled={loading || !origin || !destination || volumeCBM <= 0 || weightKG <= 0}
className="w-full"
size="lg"
>
{loading ? (
<>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
Recherche en cours...
</>
) : (
<>
<Search className="mr-2 h-4 w-4" />
Rechercher des tarifs
</>
)}
</Button>
</CardContent>
</Card>
{/* Error Alert */}
{error && (
<Alert variant="destructive">
<AlertDescription>{error}</AlertDescription>
</Alert>
)}
{/* Search Info */}
{data && (
<Alert>
<AlertDescription>
Recherche effectuée le {new Date(data.searchedAt).toLocaleString('fr-FR')} {' '}
{data.searchedFiles.length} fichier(s) CSV analysé(s) {data.totalResults} tarif(s)
trouvé(s)
</AlertDescription>
</Alert>
)}
{/* Results Table */}
{data && data.results.length > 0 && (
<Card>
<CardHeader>
<CardTitle>Résultats de recherche</CardTitle>
<CardDescription>
{data.totalResults} tarif{data.totalResults > 1 ? 's' : ''} correspondant à vos
critères
</CardDescription>
</CardHeader>
<CardContent>
<RateResultsTable
results={data.results}
currency={currency}
onBooking={handleBooking}
/>
</CardContent>
</Card>
)}
{/* No Results */}
{data && data.results.length === 0 && (
<Card>
<CardContent className="py-12 text-center">
<p className="text-muted-foreground">Aucun tarif trouvé pour cette recherche.</p>
<p className="text-sm text-muted-foreground mt-2">
Essayez d'ajuster vos critères de recherche ou vos filtres.
</p>
</CardContent>
</Card>
)}
</div>
</div>
</div>
);
}