'use client'; import { useState, useEffect, useCallback, useRef } from 'react'; import { listCsvBookings, CsvBookingResponse } from '@/lib/api/bookings'; interface Document { id: string; fileName: string; filePath: string; type: string; mimeType: string; size: number; uploadedAt?: Date; // Legacy fields for compatibility name?: string; url?: string; } interface DocumentWithBooking extends Document { bookingId: string; quoteNumber: string; route: string; status: string; carrierName: string; fileType?: string; } export default function UserDocumentsPage() { const [bookings, setBookings] = useState([]); const [documents, setDocuments] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [searchTerm, setSearchTerm] = useState(''); const [filterStatus, setFilterStatus] = useState('all'); const [filterQuoteNumber, setFilterQuoteNumber] = useState(''); const [currentPage, setCurrentPage] = useState(1); const [itemsPerPage, setItemsPerPage] = useState(10); // Modal state for adding documents const [showAddModal, setShowAddModal] = useState(false); const [selectedBookingId, setSelectedBookingId] = useState(null); const [uploadingFiles, setUploadingFiles] = useState(false); const fileInputRef = useRef(null); // Helper function to get formatted quote number const getQuoteNumber = (booking: CsvBookingResponse): string => { return `#${booking.bookingId || booking.id.slice(0, 8).toUpperCase()}`; }; // Get file extension and type const getFileType = (fileName: string): string => { const ext = fileName.split('.').pop()?.toLowerCase() || ''; const typeMap: Record = { pdf: 'PDF', doc: 'Word', docx: 'Word', xls: 'Excel', xlsx: 'Excel', jpg: 'Image', jpeg: 'Image', png: 'Image', gif: 'Image', txt: 'Text', csv: 'CSV', }; return typeMap[ext] || ext.toUpperCase(); }; const fetchBookingsAndDocuments = useCallback(async () => { try { setLoading(true); // Fetch all user's bookings (paginated, get all pages) const response = await listCsvBookings({ page: 1, limit: 1000 }); const allBookings = response.bookings || []; setBookings(allBookings); // Extract all documents from all bookings const allDocuments: DocumentWithBooking[] = []; allBookings.forEach((booking: CsvBookingResponse) => { if (booking.documents && booking.documents.length > 0) { booking.documents.forEach((doc: any, index: number) => { // Use the correct field names from the backend const actualFileName = doc.fileName || doc.name || 'document'; const actualFilePath = doc.filePath || doc.url || ''; const actualMimeType = doc.mimeType || doc.type || ''; // Extract clean file type from mimeType or fileName let fileType = ''; if (actualMimeType.includes('/')) { const parts = actualMimeType.split('/'); fileType = getFileType(parts[1]); } else { fileType = getFileType(actualFileName); } allDocuments.push({ id: doc.id || `${booking.id}-doc-${index}`, fileName: actualFileName, filePath: actualFilePath, type: doc.type || '', mimeType: actualMimeType, size: doc.size || 0, uploadedAt: doc.uploadedAt, bookingId: booking.id, quoteNumber: getQuoteNumber(booking), route: `${booking.origin || 'N/A'} → ${booking.destination || 'N/A'}`, status: booking.status, carrierName: booking.carrierName || 'N/A', fileType: fileType, }); }); } }); setDocuments(allDocuments); setError(null); } catch (err: any) { setError(err.message || 'Erreur lors du chargement des documents'); } finally { setLoading(false); } }, []); useEffect(() => { fetchBookingsAndDocuments(); }, [fetchBookingsAndDocuments]); // Filter documents const filteredDocuments = documents.filter(doc => { const matchesSearch = searchTerm === '' || (doc.fileName && doc.fileName.toLowerCase().includes(searchTerm.toLowerCase())) || (doc.type && doc.type.toLowerCase().includes(searchTerm.toLowerCase())) || doc.route.toLowerCase().includes(searchTerm.toLowerCase()) || doc.carrierName.toLowerCase().includes(searchTerm.toLowerCase()); const matchesStatus = filterStatus === 'all' || doc.status === filterStatus; const matchesQuote = filterQuoteNumber === '' || doc.quoteNumber.toLowerCase().includes(filterQuoteNumber.toLowerCase()); return matchesSearch && matchesStatus && matchesQuote; }); // Pagination const totalPages = Math.ceil(filteredDocuments.length / itemsPerPage); const startIndex = (currentPage - 1) * itemsPerPage; const endIndex = startIndex + itemsPerPage; const paginatedDocuments = filteredDocuments.slice(startIndex, endIndex); // Reset to page 1 when filters change useEffect(() => { setCurrentPage(1); }, [searchTerm, filterStatus, filterQuoteNumber]); const getDocumentIcon = (type: string) => { const typeLower = type.toLowerCase(); const icons: Record = { 'application/pdf': '📄', 'image/jpeg': '🖼️', 'image/png': '🖼️', 'image/jpg': '🖼️', pdf: '📄', jpeg: '🖼️', jpg: '🖼️', png: '🖼️', gif: '🖼️', image: '🖼️', word: '📝', doc: '📝', docx: '📝', excel: '📊', xls: '📊', xlsx: '📊', csv: '📊', text: '📄', txt: '📄', }; return icons[typeLower] || '📎'; }; 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', CANCELLED: 'bg-gray-100 text-gray-800', }; return colors[status] || 'bg-gray-100 text-gray-800'; }; const getStatusLabel = (status: string) => { const labels: Record = { PENDING: 'En attente', ACCEPTED: 'Accepté', REJECTED: 'Refusé', CANCELLED: 'Annulé', }; return labels[status] || status; }; const handleDownload = async (url: string, fileName: string) => { try { // Try direct download first const link = document.createElement('a'); link.href = url; link.download = fileName; link.target = '_blank'; link.setAttribute('download', fileName); document.body.appendChild(link); link.click(); document.body.removeChild(link); // If direct download doesn't work, try fetch with blob setTimeout(async () => { try { const response = await fetch(url, { mode: 'cors', credentials: 'include', }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const blob = await response.blob(); const blobUrl = window.URL.createObjectURL(blob); const link2 = document.createElement('a'); link2.href = blobUrl; link2.download = fileName; document.body.appendChild(link2); link2.click(); document.body.removeChild(link2); window.URL.revokeObjectURL(blobUrl); } catch (fetchError) { console.error('Fetch download failed:', fetchError); } }, 100); } catch (error) { console.error('Error downloading file:', error); alert( `Erreur lors du téléchargement du document: ${error instanceof Error ? error.message : 'Erreur inconnue'}` ); } }; // Get unique bookings for add document modal const bookingsWithPendingStatus = bookings.filter(b => b.status === 'PENDING'); const handleAddDocumentClick = () => { setShowAddModal(true); }; const handleCloseModal = () => { setShowAddModal(false); setSelectedBookingId(null); if (fileInputRef.current) { fileInputRef.current.value = ''; } }; const handleFileUpload = async () => { if (!selectedBookingId || !fileInputRef.current?.files?.length) { alert('Veuillez sélectionner une réservation et au moins un fichier'); return; } setUploadingFiles(true); try { const formData = new FormData(); const files = fileInputRef.current.files; for (let i = 0; i < files.length; i++) { formData.append('documents', files[i]); } const token = localStorage.getItem('access_token'); const response = await fetch( `${process.env.NEXT_PUBLIC_API_URL}/api/v1/csv-bookings/${selectedBookingId}/documents`, { method: 'POST', headers: { Authorization: `Bearer ${token}`, }, body: formData, } ); if (!response.ok) { throw new Error('Erreur lors de l\'ajout des documents'); } alert('Documents ajoutés avec succès!'); handleCloseModal(); fetchBookingsAndDocuments(); // Refresh the list } catch (error) { console.error('Error uploading documents:', error); alert( `Erreur lors de l'ajout des documents: ${error instanceof Error ? error.message : 'Erreur inconnue'}` ); } finally { setUploadingFiles(false); } }; const handleDeleteDocument = async (bookingId: string, documentId: string, fileName: string) => { if (!confirm(`Êtes-vous sûr de vouloir supprimer le document "${fileName}" ?`)) { return; } try { const token = localStorage.getItem('access_token'); const response = await fetch( `${process.env.NEXT_PUBLIC_API_URL}/api/v1/csv-bookings/${bookingId}/documents/${documentId}`, { method: 'DELETE', headers: { Authorization: `Bearer ${token}`, }, } ); if (!response.ok) { throw new Error('Erreur lors de la suppression du document'); } alert('Document supprimé avec succès!'); fetchBookingsAndDocuments(); // Refresh the list } catch (error) { console.error('Error deleting document:', error); alert( `Erreur lors de la suppression: ${error instanceof Error ? error.message : 'Erreur inconnue'}` ); } }; if (loading) { return (

Chargement des documents...

); } return (
{/* Header */}

Mes Documents

Gérez tous les documents de vos réservations

{/* Stats */}
Total Documents
{documents.length}
Réservations avec Documents
{bookings.filter(b => b.documents && b.documents.length > 0).length}
Documents Filtrés
{filteredDocuments.length}
{/* Filters */}
setSearchTerm(e.target.value)} className="block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:border-blue-500 focus:ring-blue-500 focus:outline-none" />
setFilterQuoteNumber(e.target.value)} className="block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:border-blue-500 focus:ring-blue-500 focus:outline-none" />
{/* Error Message */} {error && (
{error}
)} {/* Documents Table */}
{paginatedDocuments.length === 0 ? ( ) : ( paginatedDocuments.map((doc, index) => ( )) )}
Nom du Document Type N° de Devis Route Transporteur Statut Actions
{documents.length === 0 ? 'Aucun document trouvé. Ajoutez des documents à vos réservations.' : 'Aucun document ne correspond aux filtres sélectionnés.'}
{doc.fileName}
{getDocumentIcon(doc.fileType || doc.type)}
{doc.fileType || doc.type}
{doc.quoteNumber}
{doc.route}
{doc.carrierName}
{getStatusLabel(doc.status)}
{doc.status === 'PENDING' && ( )}
{/* Pagination Controls */} {filteredDocuments.length > 0 && (

Affichage de {startIndex + 1} à{' '} {Math.min(endIndex, filteredDocuments.length)} {' '} sur {filteredDocuments.length} résultats

)}
{/* Add Document Modal */} {showAddModal && (
{/* Background overlay */}
{/* Modal panel */}

Ajouter un document

Formats acceptés: PDF, Word, Excel, Images (max 10 fichiers)

)}
); }