639 lines
24 KiB
TypeScript
639 lines
24 KiB
TypeScript
/**
|
|
* Track & Trace Page
|
|
*
|
|
* Allows users to track their shipments by entering tracking numbers
|
|
* and selecting the carrier. Includes search history and vessel position map.
|
|
*/
|
|
|
|
'use client';
|
|
|
|
import { useState, useEffect } from 'react';
|
|
import { Card, CardHeader, CardTitle, CardDescription, CardContent } from '@/components/ui/card';
|
|
import { Input } from '@/components/ui/input';
|
|
import { Button } from '@/components/ui/button';
|
|
import {
|
|
Search,
|
|
Package,
|
|
FileText,
|
|
ClipboardList,
|
|
Lightbulb,
|
|
History,
|
|
MapPin,
|
|
X,
|
|
Clock,
|
|
Ship,
|
|
ExternalLink,
|
|
Maximize2,
|
|
Minimize2,
|
|
Globe,
|
|
Anchor,
|
|
} from 'lucide-react';
|
|
|
|
// Search history item type
|
|
interface SearchHistoryItem {
|
|
id: string;
|
|
trackingNumber: string;
|
|
carrierId: string;
|
|
carrierName: string;
|
|
timestamp: Date;
|
|
}
|
|
|
|
// Carrier tracking URLs with official brand colors
|
|
const carriers = [
|
|
{
|
|
id: 'maersk',
|
|
name: 'Maersk',
|
|
color: '#00243D', // Maersk dark blue
|
|
textColor: 'text-white',
|
|
trackingUrl: 'https://www.maersk.com/tracking/',
|
|
placeholder: 'Ex: MSKU1234567',
|
|
description: 'N° conteneur ou B/L',
|
|
logo: '/assets/logos/carriers/maersk.svg',
|
|
},
|
|
{
|
|
id: 'msc',
|
|
name: 'MSC',
|
|
color: '#002B5C', // MSC blue
|
|
textColor: 'text-white',
|
|
trackingUrl: 'https://www.msc.com/track-a-shipment?query=',
|
|
placeholder: 'Ex: MSCU1234567',
|
|
description: 'N° conteneur, B/L ou réservation',
|
|
logo: '/assets/logos/carriers/msc.svg',
|
|
},
|
|
{
|
|
id: 'cma-cgm',
|
|
name: 'CMA CGM',
|
|
color: '#E30613', // CMA CGM red
|
|
textColor: 'text-white',
|
|
trackingUrl: 'https://www.cma-cgm.com/ebusiness/tracking/search?SearchBy=Container&Reference=',
|
|
placeholder: 'Ex: CMAU1234567',
|
|
description: 'N° conteneur ou B/L',
|
|
logo: '/assets/logos/carriers/cmacgm.svg',
|
|
},
|
|
{
|
|
id: 'hapag-lloyd',
|
|
name: 'Hapag-Lloyd',
|
|
color: '#FF6600', // Hapag orange
|
|
textColor: 'text-white',
|
|
trackingUrl: 'https://www.hapag-lloyd.com/en/online-business/track/track-by-container-solution.html?container=',
|
|
placeholder: 'Ex: HLCU1234567',
|
|
description: 'N° conteneur',
|
|
logo: '/assets/logos/carriers/hapag.svg',
|
|
},
|
|
{
|
|
id: 'cosco',
|
|
name: 'COSCO',
|
|
color: '#003A70', // COSCO blue
|
|
textColor: 'text-white',
|
|
trackingUrl: 'https://elines.coscoshipping.com/ebusiness/cargoTracking?trackingNumber=',
|
|
placeholder: 'Ex: COSU1234567',
|
|
description: 'N° conteneur ou B/L',
|
|
logo: '/assets/logos/carriers/cosco.svg',
|
|
},
|
|
{
|
|
id: 'one',
|
|
name: 'ONE',
|
|
color: '#FF00FF', // ONE magenta
|
|
textColor: 'text-white',
|
|
trackingUrl: 'https://ecomm.one-line.com/one-ecom/manage-shipment/cargo-tracking?trkNoParam=',
|
|
placeholder: 'Ex: ONEU1234567',
|
|
description: 'N° conteneur ou B/L',
|
|
logo: '/assets/logos/carriers/one.svg',
|
|
},
|
|
{
|
|
id: 'evergreen',
|
|
name: 'Evergreen',
|
|
color: '#006633', // Evergreen green
|
|
textColor: 'text-white',
|
|
trackingUrl: 'https://www.shipmentlink.com/servlet/TDB1_CargoTracking.do?BL=',
|
|
placeholder: 'Ex: EGHU1234567',
|
|
description: 'N° conteneur ou B/L',
|
|
logo: '/assets/logos/carriers/evergreen.svg',
|
|
},
|
|
{
|
|
id: 'yangming',
|
|
name: 'Yang Ming',
|
|
color: '#FFD700', // Yang Ming yellow
|
|
textColor: 'text-gray-900',
|
|
trackingUrl: 'https://www.yangming.com/e-service/Track_Trace/track_trace_cargo_tracking.aspx?rdolType=CT&str=',
|
|
placeholder: 'Ex: YMLU1234567',
|
|
description: 'N° conteneur',
|
|
logo: '/assets/logos/carriers/yangming.svg',
|
|
},
|
|
{
|
|
id: 'zim',
|
|
name: 'ZIM',
|
|
color: '#1E3A8A', // ZIM blue
|
|
textColor: 'text-white',
|
|
trackingUrl: 'https://www.zim.com/tools/track-a-shipment?consnumber=',
|
|
placeholder: 'Ex: ZIMU1234567',
|
|
description: 'N° conteneur ou B/L',
|
|
logo: '/assets/logos/carriers/zim.svg',
|
|
},
|
|
{
|
|
id: 'hmm',
|
|
name: 'HMM',
|
|
color: '#E65100', // HMM orange
|
|
textColor: 'text-white',
|
|
trackingUrl: 'https://www.hmm21.com/cms/business/ebiz/trackTrace/trackTrace/index.jsp?type=1&number=',
|
|
placeholder: 'Ex: HDMU1234567',
|
|
description: 'N° conteneur ou B/L',
|
|
logo: '/assets/logos/carriers/hmm.svg',
|
|
},
|
|
];
|
|
|
|
// Local storage keys
|
|
const HISTORY_KEY = 'xpeditis_track_history';
|
|
|
|
export default function TrackTracePage() {
|
|
const [trackingNumber, setTrackingNumber] = useState('');
|
|
const [selectedCarrier, setSelectedCarrier] = useState('');
|
|
const [error, setError] = useState('');
|
|
const [searchHistory, setSearchHistory] = useState<SearchHistoryItem[]>([]);
|
|
const [showMap, setShowMap] = useState(false);
|
|
const [isMapFullscreen, setIsMapFullscreen] = useState(false);
|
|
const [isMapLoading, setIsMapLoading] = useState(true);
|
|
|
|
// Load history from localStorage on mount
|
|
useEffect(() => {
|
|
const savedHistory = localStorage.getItem(HISTORY_KEY);
|
|
if (savedHistory) {
|
|
try {
|
|
const parsed = JSON.parse(savedHistory);
|
|
setSearchHistory(parsed.map((item: any) => ({
|
|
...item,
|
|
timestamp: new Date(item.timestamp)
|
|
})));
|
|
} catch (e) {
|
|
console.error('Failed to parse search history:', e);
|
|
}
|
|
}
|
|
}, []);
|
|
|
|
// Save to localStorage
|
|
const saveHistory = (history: SearchHistoryItem[]) => {
|
|
localStorage.setItem(HISTORY_KEY, JSON.stringify(history));
|
|
setSearchHistory(history);
|
|
};
|
|
|
|
const handleTrack = () => {
|
|
if (!trackingNumber.trim()) {
|
|
setError('Veuillez entrer un numéro de tracking');
|
|
return;
|
|
}
|
|
if (!selectedCarrier) {
|
|
setError('Veuillez sélectionner un transporteur');
|
|
return;
|
|
}
|
|
|
|
setError('');
|
|
|
|
const carrier = carriers.find(c => c.id === selectedCarrier);
|
|
if (carrier) {
|
|
// Add to history
|
|
const newHistoryItem: SearchHistoryItem = {
|
|
id: Date.now().toString(),
|
|
trackingNumber: trackingNumber.trim(),
|
|
carrierId: carrier.id,
|
|
carrierName: carrier.name,
|
|
timestamp: new Date(),
|
|
};
|
|
|
|
// Keep only last 10 unique searches
|
|
const updatedHistory = [newHistoryItem, ...searchHistory.filter(
|
|
h => !(h.trackingNumber === newHistoryItem.trackingNumber && h.carrierId === newHistoryItem.carrierId)
|
|
)].slice(0, 10);
|
|
|
|
saveHistory(updatedHistory);
|
|
|
|
const trackingUrl = carrier.trackingUrl + encodeURIComponent(trackingNumber.trim());
|
|
window.open(trackingUrl, '_blank', 'noopener,noreferrer');
|
|
}
|
|
};
|
|
|
|
const handleHistoryClick = (item: SearchHistoryItem) => {
|
|
setTrackingNumber(item.trackingNumber);
|
|
setSelectedCarrier(item.carrierId);
|
|
};
|
|
|
|
const handleDeleteHistory = (id: string) => {
|
|
const updatedHistory = searchHistory.filter(h => h.id !== id);
|
|
saveHistory(updatedHistory);
|
|
};
|
|
|
|
const handleClearHistory = () => {
|
|
saveHistory([]);
|
|
};
|
|
|
|
const handleKeyPress = (e: React.KeyboardEvent) => {
|
|
if (e.key === 'Enter') {
|
|
handleTrack();
|
|
}
|
|
};
|
|
|
|
const selectedCarrierData = carriers.find(c => c.id === selectedCarrier);
|
|
|
|
const formatTimeAgo = (date: Date) => {
|
|
const now = new Date();
|
|
const diffMs = now.getTime() - date.getTime();
|
|
const diffMins = Math.floor(diffMs / 60000);
|
|
const diffHours = Math.floor(diffMs / 3600000);
|
|
const diffDays = Math.floor(diffMs / 86400000);
|
|
|
|
if (diffMins < 1) return 'À l\'instant';
|
|
if (diffMins < 60) return `Il y a ${diffMins}min`;
|
|
if (diffHours < 24) return `Il y a ${diffHours}h`;
|
|
if (diffDays < 7) return `Il y a ${diffDays}j`;
|
|
return date.toLocaleDateString('fr-FR');
|
|
};
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
{/* Header */}
|
|
<div className="mb-8">
|
|
<h1 className="text-3xl font-bold text-gray-900">Suivi des expéditions</h1>
|
|
<p className="mt-2 text-gray-600">
|
|
Suivez vos expéditions en temps réel. Entrez votre numéro de tracking et sélectionnez le transporteur.
|
|
</p>
|
|
</div>
|
|
|
|
{/* Search Form */}
|
|
<Card className="bg-white shadow-lg border-blue-100">
|
|
<CardHeader>
|
|
<CardTitle className="text-xl flex items-center gap-2">
|
|
<Search className="h-5 w-5 text-blue-600" />
|
|
Rechercher une expédition
|
|
</CardTitle>
|
|
<CardDescription>
|
|
Entrez votre numéro de conteneur, connaissement (B/L) ou référence de réservation
|
|
</CardDescription>
|
|
</CardHeader>
|
|
<CardContent className="space-y-6">
|
|
{/* Carrier Selection - US 5.1: Professional carrier cards with brand colors */}
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-3">
|
|
Sélectionnez le transporteur
|
|
</label>
|
|
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-5 gap-3">
|
|
{carriers.map(carrier => (
|
|
<button
|
|
key={carrier.id}
|
|
type="button"
|
|
onClick={() => {
|
|
setSelectedCarrier(carrier.id);
|
|
setError('');
|
|
}}
|
|
className={`flex flex-col items-center justify-center p-4 rounded-xl border-2 transition-all hover:scale-105 ${
|
|
selectedCarrier === carrier.id
|
|
? 'border-blue-500 shadow-lg ring-2 ring-blue-200'
|
|
: 'border-gray-200 hover:border-gray-300 hover:shadow-md'
|
|
}`}
|
|
>
|
|
{/* Carrier logo/badge with brand color */}
|
|
<div
|
|
className={`w-12 h-12 rounded-lg flex items-center justify-center text-sm font-bold mb-2 shadow-sm ${carrier.textColor}`}
|
|
style={{ backgroundColor: carrier.color }}
|
|
>
|
|
{carrier.name.length <= 3 ? carrier.name : carrier.name.substring(0, 2).toUpperCase()}
|
|
</div>
|
|
<span className="text-xs font-semibold text-gray-800 text-center leading-tight">{carrier.name}</span>
|
|
</button>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Tracking Number Input */}
|
|
<div>
|
|
<label htmlFor="tracking-number" className="block text-sm font-medium text-gray-700 mb-2">
|
|
Numéro de tracking
|
|
</label>
|
|
<div className="flex gap-3">
|
|
<div className="flex-1">
|
|
<Input
|
|
id="tracking-number"
|
|
type="text"
|
|
value={trackingNumber}
|
|
onChange={e => {
|
|
setTrackingNumber(e.target.value.toUpperCase());
|
|
setError('');
|
|
}}
|
|
onKeyPress={handleKeyPress}
|
|
placeholder={selectedCarrierData?.placeholder || 'Ex: MSKU1234567'}
|
|
className="text-lg font-mono border-gray-300 focus:border-blue-500 h-12"
|
|
/>
|
|
{selectedCarrierData && (
|
|
<p className="mt-1 text-xs text-gray-500">{selectedCarrierData.description}</p>
|
|
)}
|
|
</div>
|
|
{/* US 5.2: Harmonized button color */}
|
|
<Button
|
|
onClick={handleTrack}
|
|
size="lg"
|
|
className="bg-blue-600 hover:bg-blue-700 text-white px-8 h-12 font-semibold shadow-md"
|
|
>
|
|
<Search className="mr-2 h-5 w-5" />
|
|
Rechercher
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Action Button - Map */}
|
|
<div className="flex flex-wrap gap-3 pt-2">
|
|
<Button
|
|
variant={showMap ? "default" : "outline"}
|
|
onClick={() => {
|
|
setShowMap(!showMap);
|
|
if (!showMap) setIsMapLoading(true);
|
|
}}
|
|
className={showMap
|
|
? "bg-blue-600 hover:bg-blue-700 text-white"
|
|
: "text-gray-700 border-gray-300 hover:bg-blue-50 hover:border-blue-300 hover:text-blue-700"
|
|
}
|
|
>
|
|
<Globe className="mr-2 h-4 w-4" />
|
|
{showMap ? 'Masquer la carte maritime' : 'Afficher la carte maritime'}
|
|
</Button>
|
|
</div>
|
|
|
|
{/* Error Message */}
|
|
{error && (
|
|
<div className="p-3 bg-red-50 border border-red-200 rounded-lg">
|
|
<p className="text-sm text-red-600">{error}</p>
|
|
</div>
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* Vessel Position Map - Large immersive display */}
|
|
{showMap && (
|
|
<div className={`${isMapFullscreen ? 'fixed inset-0 z-50 bg-gray-900' : ''}`}>
|
|
<Card className={`bg-white shadow-xl overflow-hidden ${isMapFullscreen ? 'h-full rounded-none' : ''}`}>
|
|
{/* Map Header */}
|
|
<div className={`flex items-center justify-between px-6 py-4 bg-gradient-to-r from-blue-600 to-blue-700 text-white ${isMapFullscreen ? '' : 'rounded-t-lg'}`}>
|
|
<div className="flex items-center gap-3">
|
|
<div className="p-2 bg-white/20 rounded-lg">
|
|
<Globe className="h-6 w-6" />
|
|
</div>
|
|
<div>
|
|
<h3 className="text-lg font-semibold">Carte Maritime Mondiale</h3>
|
|
<p className="text-blue-100 text-sm">Position des navires en temps réel</p>
|
|
</div>
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
{/* Fullscreen Toggle */}
|
|
<Button
|
|
variant="ghost"
|
|
size="sm"
|
|
onClick={() => setIsMapFullscreen(!isMapFullscreen)}
|
|
className="text-white hover:bg-white/20"
|
|
>
|
|
{isMapFullscreen ? (
|
|
<>
|
|
<Minimize2 className="h-4 w-4 mr-2" />
|
|
Réduire
|
|
</>
|
|
) : (
|
|
<>
|
|
<Maximize2 className="h-4 w-4 mr-2" />
|
|
Plein écran
|
|
</>
|
|
)}
|
|
</Button>
|
|
{/* Close Button */}
|
|
<Button
|
|
variant="ghost"
|
|
size="sm"
|
|
onClick={() => {
|
|
setShowMap(false);
|
|
setIsMapFullscreen(false);
|
|
}}
|
|
className="text-white hover:bg-white/20"
|
|
>
|
|
<X className="h-4 w-4" />
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Map Container */}
|
|
<div className={`relative w-full ${isMapFullscreen ? 'h-[calc(100vh-80px)]' : 'h-[70vh] min-h-[500px] max-h-[800px]'}`}>
|
|
{/* Loading State */}
|
|
{isMapLoading && (
|
|
<div className="absolute inset-0 bg-gradient-to-br from-blue-50 to-blue-100 flex items-center justify-center z-10">
|
|
<div className="text-center">
|
|
<div className="relative">
|
|
<Ship className="h-16 w-16 text-blue-600 animate-pulse" />
|
|
<div className="absolute -bottom-1 left-1/2 transform -translate-x-1/2">
|
|
<div className="flex gap-1">
|
|
<div className="w-2 h-2 bg-blue-600 rounded-full animate-bounce" style={{ animationDelay: '0ms' }} />
|
|
<div className="w-2 h-2 bg-blue-600 rounded-full animate-bounce" style={{ animationDelay: '150ms' }} />
|
|
<div className="w-2 h-2 bg-blue-600 rounded-full animate-bounce" style={{ animationDelay: '300ms' }} />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<p className="mt-4 text-blue-700 font-medium">Chargement de la carte...</p>
|
|
<p className="text-blue-500 text-sm">Connexion à MarineTraffic</p>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* MarineTraffic Map */}
|
|
<iframe
|
|
src="https://www.marinetraffic.com/en/ais/embed/zoom:3/centery:25/centerx:0/maptype:4/shownames:true/mmsi:0/shipid:0/fleet:/fleet_id:/vtypes:/showmenu:true/remember:false"
|
|
className="w-full h-full border-0"
|
|
title="Carte maritime en temps réel"
|
|
loading="lazy"
|
|
onLoad={() => setIsMapLoading(false)}
|
|
/>
|
|
|
|
{/* Map Legend Overlay */}
|
|
<div className={`absolute bottom-4 left-4 bg-white/95 backdrop-blur-sm rounded-xl shadow-lg p-4 ${isMapFullscreen ? 'max-w-xs' : 'max-w-[280px]'}`}>
|
|
<h4 className="font-semibold text-gray-800 text-sm mb-3 flex items-center gap-2">
|
|
<Anchor className="h-4 w-4 text-blue-600" />
|
|
Légende
|
|
</h4>
|
|
<div className="space-y-2 text-xs">
|
|
<div className="flex items-center gap-2">
|
|
<div className="w-3 h-3 rounded-full bg-green-500" />
|
|
<span className="text-gray-600">Cargos</span>
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
<div className="w-3 h-3 rounded-full bg-red-500" />
|
|
<span className="text-gray-600">Tankers</span>
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
<div className="w-3 h-3 rounded-full bg-blue-500" />
|
|
<span className="text-gray-600">Passagers</span>
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
<div className="w-3 h-3 rounded-full bg-yellow-500" />
|
|
<span className="text-gray-600">High Speed</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Quick Stats Overlay */}
|
|
<div className={`absolute top-4 right-4 bg-white/95 backdrop-blur-sm rounded-xl shadow-lg p-4 ${isMapFullscreen ? '' : 'hidden lg:block'}`}>
|
|
<div className="flex items-center gap-4 text-sm">
|
|
<div className="text-center">
|
|
<p className="text-2xl font-bold text-blue-600">90K+</p>
|
|
<p className="text-gray-500 text-xs">Navires actifs</p>
|
|
</div>
|
|
<div className="w-px h-10 bg-gray-200" />
|
|
<div className="text-center">
|
|
<p className="text-2xl font-bold text-green-600">3,500+</p>
|
|
<p className="text-gray-500 text-xs">Ports mondiaux</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Map Footer */}
|
|
<div className="px-6 py-3 bg-gray-50 border-t border-gray-200 flex items-center justify-between">
|
|
<p className="text-xs text-gray-500 flex items-center gap-1">
|
|
<ExternalLink className="h-3 w-3" />
|
|
Données fournies par MarineTraffic - Mise à jour en temps réel
|
|
</p>
|
|
<a
|
|
href="https://www.marinetraffic.com"
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
className="text-xs text-blue-600 hover:text-blue-800 font-medium flex items-center gap-1"
|
|
>
|
|
Ouvrir sur MarineTraffic
|
|
<ExternalLink className="h-3 w-3" />
|
|
</a>
|
|
</div>
|
|
</Card>
|
|
</div>
|
|
)}
|
|
|
|
{/* Search History */}
|
|
<Card className="bg-white shadow">
|
|
<CardHeader className="pb-3">
|
|
<div className="flex items-center justify-between">
|
|
<CardTitle className="text-lg flex items-center gap-2">
|
|
<History className="h-5 w-5 text-gray-600" />
|
|
Historique des recherches
|
|
</CardTitle>
|
|
{searchHistory.length > 0 && (
|
|
<Button
|
|
variant="ghost"
|
|
size="sm"
|
|
onClick={handleClearHistory}
|
|
className="text-gray-500 hover:text-red-600 text-xs"
|
|
>
|
|
Effacer tout
|
|
</Button>
|
|
)}
|
|
</div>
|
|
</CardHeader>
|
|
<CardContent>
|
|
{searchHistory.length === 0 ? (
|
|
<div className="text-center py-8 text-gray-500">
|
|
<Clock className="h-10 w-10 mx-auto mb-3 text-gray-300" />
|
|
<p className="text-sm">Aucune recherche récente</p>
|
|
<p className="text-xs text-gray-400 mt-1">Vos recherches apparaîtront ici</p>
|
|
</div>
|
|
) : (
|
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3">
|
|
{searchHistory.map(item => {
|
|
const carrier = carriers.find(c => c.id === item.carrierId);
|
|
return (
|
|
<div
|
|
key={item.id}
|
|
className="flex items-center justify-between p-3 rounded-lg border border-gray-100 hover:bg-gray-50 group cursor-pointer transition-colors"
|
|
onClick={() => handleHistoryClick(item)}
|
|
>
|
|
<div className="flex items-center gap-3">
|
|
<div
|
|
className={`w-8 h-8 rounded flex items-center justify-center text-xs font-bold ${carrier?.textColor || 'text-white'}`}
|
|
style={{ backgroundColor: carrier?.color || '#666' }}
|
|
>
|
|
{item.carrierName.substring(0, 2).toUpperCase()}
|
|
</div>
|
|
<div>
|
|
<p className="font-mono text-sm font-medium text-gray-900">{item.trackingNumber}</p>
|
|
<p className="text-xs text-gray-500">{item.carrierName} • {formatTimeAgo(item.timestamp)}</p>
|
|
</div>
|
|
</div>
|
|
<button
|
|
onClick={(e) => {
|
|
e.stopPropagation();
|
|
handleDeleteHistory(item.id);
|
|
}}
|
|
className="opacity-0 group-hover:opacity-100 p-1 hover:bg-red-50 rounded transition-opacity"
|
|
>
|
|
<X className="h-4 w-4 text-gray-400 hover:text-red-500" />
|
|
</button>
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* Help Section */}
|
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
|
<Card className="bg-white">
|
|
<CardHeader>
|
|
<CardTitle className="text-lg flex items-center gap-2">
|
|
<Package className="h-5 w-5 text-blue-600" />
|
|
Numéro de conteneur
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<p className="text-sm text-gray-600">
|
|
Format standard: 4 lettres + 7 chiffres (ex: MSKU1234567).
|
|
Le préfixe indique généralement le propriétaire du conteneur.
|
|
</p>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card className="bg-white">
|
|
<CardHeader>
|
|
<CardTitle className="text-lg flex items-center gap-2">
|
|
<FileText className="h-5 w-5 text-blue-600" />
|
|
Connaissement (B/L)
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<p className="text-sm text-gray-600">
|
|
Le numéro de connaissement (Bill of Lading) est fourni par le transporteur lors de la confirmation de réservation.
|
|
Le format varie selon le transporteur.
|
|
</p>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card className="bg-white">
|
|
<CardHeader>
|
|
<CardTitle className="text-lg flex items-center gap-2">
|
|
<ClipboardList className="h-5 w-5 text-blue-600" />
|
|
Référence de réservation
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<p className="text-sm text-gray-600">
|
|
Numéro de réservation attribué par le transporteur lors de la réservation initiale de l'espace sur le navire.
|
|
</p>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
|
|
{/* Info Box */}
|
|
<div className="p-4 bg-blue-50 rounded-lg border border-blue-100">
|
|
<div className="flex items-start gap-3">
|
|
<Lightbulb className="h-5 w-5 text-blue-600 flex-shrink-0" />
|
|
<div>
|
|
<p className="text-sm font-medium text-blue-800">Comment fonctionne le suivi ?</p>
|
|
<p className="text-sm text-blue-700 mt-1">
|
|
Cette fonctionnalité vous redirige vers le site officiel du transporteur pour obtenir les informations
|
|
de suivi les plus récentes. Les données affichées proviennent directement du système du transporteur.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|