'use client'; import { useState, useEffect } from 'react'; import { useQuery } from '@tanstack/react-query'; import { listCsvBookings } from '@/lib/api'; import { Link } from '@/i18n/navigation'; import { Plus, Clock } from 'lucide-react'; import ExportButton from '@/components/ExportButton'; import { useSearchParams } from 'next/navigation'; import { PageHeader } from '@/components/ui/PageHeader'; import { useTranslations, useLocale } from 'next-intl'; type SearchType = 'pallets' | 'weight' | 'route' | 'status' | 'date' | 'quote'; export default function BookingsListPage() { const t = useTranslations('dashboard.bookingsList'); const locale = useLocale(); const dateLocale = locale === 'fr' ? 'fr-FR' : 'en-US'; const searchParams = useSearchParams(); const [searchTerm, setSearchTerm] = useState(''); const [searchType, setSearchType] = useState('route'); const [statusFilter, setStatusFilter] = useState(''); const [page, setPage] = useState(1); const [showTransferBanner, setShowTransferBanner] = useState(false); const ITEMS_PER_PAGE = 20; useEffect(() => { if (searchParams.get('transfer') === 'declared') { setShowTransferBanner(true); } }, [searchParams]); const { data: csvData, isLoading, error: csvError, } = useQuery({ queryKey: ['csv-bookings'], queryFn: () => listCsvBookings({ page: 1, limit: 1000, }), }); if (csvError) console.error('CSV bookings error:', csvError); const filterBookings = (bookings: any[]) => { let filtered = bookings; if (statusFilter) { filtered = filtered.filter((booking: any) => booking.status === statusFilter); } if (searchTerm.trim()) { const term = searchTerm.toLowerCase(); filtered = filtered.filter((booking: any) => { switch (searchType) { case 'pallets': return booking.palletCount?.toString().includes(term); case 'weight': return booking.weightKG?.toString().includes(term); case 'route': const origin = booking.originCity?.toLowerCase() || ''; const destination = booking.destinationCity?.toLowerCase() || ''; return origin.includes(term) || destination.includes(term); case 'status': return booking.status?.toLowerCase().includes(term); case 'date': const date = new Date( booking.requestedPickupDate || booking.requestedAt ).toLocaleDateString(dateLocale); return date.includes(term); case 'quote': return ( booking.id?.toLowerCase().includes(term) || booking.quoteNumber?.toLowerCase().includes(term) ); default: return true; } }); } return filtered; }; const filteredBookings = filterBookings( (csvData?.bookings || []).map(b => ({ ...b, type: 'csv' as const })) ); const totalBookings = filteredBookings.length; const totalPages = Math.ceil(totalBookings / ITEMS_PER_PAGE); const startIndex = (page - 1) * ITEMS_PER_PAGE; const endIndex = startIndex + ITEMS_PER_PAGE; const paginatedBookings = filteredBookings.slice(startIndex, endIndex); const resetPage = () => setPage(1); const statusOptions = [ { value: '', label: t('statusFilter.all') }, { value: 'PENDING', label: t('status.pending') }, { value: 'ACCEPTED', label: t('status.accepted') }, { value: 'REJECTED', label: t('status.rejected') }, ]; const searchTypeOptions: { value: SearchType; label: string }[] = [ { value: 'route', label: t('searchType.route') }, { value: 'pallets', label: t('searchType.pallets') }, { value: 'weight', label: t('searchType.weight') }, { value: 'status', label: t('searchType.status') }, { value: 'date', label: t('searchType.date') }, { value: 'quote', label: t('searchType.quote') }, ]; const getPlaceholder = () => { const keyMap: Record = { route: 'searchPlaceholder.route', pallets: 'searchPlaceholder.pallets', weight: 'searchPlaceholder.weight', status: 'searchPlaceholder.status', date: 'searchPlaceholder.date', quote: 'searchPlaceholder.quote', }; return t(keyMap[searchType] as any); }; const getStatusColor = (status: string) => { const colors: Record = { PENDING: 'bg-yellow-100 text-yellow-800', ACCEPTED: 'bg-green-100 text-green-800', REJECTED: 'bg-red-100 text-red-800', }; return colors[status] || 'bg-gray-100 text-gray-800'; }; const getStatusLabel = (status: string) => { const map: Record = { PENDING: t('status.pending'), ACCEPTED: t('status.accepted'), REJECTED: t('status.rejected'), }; return map[status] || status; }; return (
{showTransferBanner && (

{t('transferBanner.title')}

{t('transferBanner.message')}

)} `${v || 0}` }, { key: 'weightKG', label: t('export.weight'), format: v => `${v || 0}` }, { key: 'volumeCBM', label: t('export.volume'), format: v => `${v || 0}` }, { key: 'origin', label: t('export.origin') }, { key: 'destination', label: t('export.destination') }, { key: 'carrierName', label: t('export.carrier') }, { key: 'status', label: t('export.status'), format: v => getStatusLabel(v) }, { key: 'createdAt', label: t('export.createdAt'), format: v => (v ? new Date(v).toLocaleDateString(dateLocale) : ''), }, ]} /> {t('new')} } />
{ setSearchTerm(e.target.value); resetPage(); }} className="block w-full pl-10 pr-3 py-2 border border-gray-300 rounded-md leading-5 bg-white placeholder-gray-500 focus:outline-none focus:placeholder-gray-400 focus:ring-1 focus:ring-blue-500 focus:border-blue-500 sm:text-sm" placeholder={getPlaceholder()} />
{isLoading ? (
{t('loading')}
) : paginatedBookings && paginatedBookings.length > 0 ? ( <>
{paginatedBookings.map((booking: any) => (
{booking.type === 'csv' ? `${booking.origin} → ${booking.destination}` : booking.route || 'N/A'}
{booking.type === 'csv' ? booking.carrierName : booking.carrier || ''}
{getStatusLabel(booking.status)}
{t('mobile.pallets')}
{booking.type === 'csv' ? t('units.palletsShort', { count: booking.palletCount }) : t('units.containersShort', { count: booking.containers?.length || 0 })}
{t('mobile.weight')}
{booking.type === 'csv' ? t('units.kg', { value: booking.weightKG }) : booking.totalWeight ? t('units.kg', { value: booking.totalWeight }) : 'N/A'}
{t('mobile.date')}
{booking.createdAt || booking.requestedAt ? new Date(booking.createdAt || booking.requestedAt).toLocaleDateString( dateLocale, { day: '2-digit', month: '2-digit', year: '2-digit' } ) : 'N/A'}
{booking.type === 'csv' ? t('mobile.ref', { id: booking.bookingId || booking.id.slice(0, 8).toUpperCase(), }) : t('mobile.booking', { number: booking.bookingNumber || '-' })}
))}
{paginatedBookings.map((booking: any) => ( ))}
{t('columns.palletsPackages')} {t('columns.weight')} {t('columns.route')} {t('columns.status')} {t('columns.date')} {t('columns.quoteNumber')} {t('columns.bookingNumber')}
{booking.type === 'csv' ? t('units.palletsCount', { count: booking.palletCount }) : t('units.containersCount', { count: booking.containers?.length || 0, })}
{booking.type === 'csv' ? 'LCL' : booking.containerType || 'FCL'}
{booking.type === 'csv' ? t('units.kg', { value: booking.weightKG }) : booking.totalWeight ? t('units.kg', { value: booking.totalWeight }) : 'N/A'}
{booking.type === 'csv' ? t('units.cbm', { value: booking.volumeCBM }) : booking.totalVolume ? t('units.cbm', { value: booking.totalVolume }) : ''}
{booking.type === 'csv' ? `${booking.origin} → ${booking.destination}` : booking.route || 'N/A'}
{booking.type === 'csv' ? `${booking.carrierName}` : booking.carrier || ''}
{getStatusLabel(booking.status)} {booking.createdAt || booking.requestedAt ? new Date(booking.createdAt || booking.requestedAt).toLocaleDateString( dateLocale, { day: '2-digit', month: '2-digit', year: 'numeric', } ) : 'N/A'} {booking.type === 'csv' ? `#${booking.bookingId || booking.id.slice(0, 8).toUpperCase()}` : booking.bookingNumber || `#${booking.id.slice(0, 8).toUpperCase()}`} {booking.bookingNumber || '-'}
{totalPages > 1 && (

{t.rich('pagination.showing', { start: startIndex + 1, end: Math.min(endIndex, totalBookings), total: totalBookings, b: chunks => {chunks}, })}

)} ) : (

{t('empty.title')}

{searchTerm || statusFilter ? t('empty.hasFilters') : t('empty.noBookings')}

{t('new')}
)}
); }