'use client'; import { useState, useEffect, useCallback } from 'react'; import { useTranslations, useLocale } from 'next-intl'; import { getAllBookings, getAllUsers, deleteAdminDocument } from '@/lib/api/admin'; import { FileText, Image as ImageIcon, FileEdit, FileSpreadsheet, Paperclip } from 'lucide-react'; import type { ReactNode } from 'react'; import { PageHeader } from '@/components/ui/PageHeader'; 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 Booking { id: string; bookingNumber?: string; bookingId?: string; type?: string; userId?: string; organizationId?: string; origin?: string; destination?: string; carrierName?: string; documents?: Document[]; requestedAt?: string; status: string; } interface DocumentWithBooking extends Document { bookingId: string; quoteNumber: string; userId: string; userName?: string; organizationId: string; route: string; status: string; fileType?: string; } export default function AdminDocumentsPage() { const t = useTranslations('dashboard.admin.documents'); const locale = useLocale(); const dateLocale = locale === 'fr' ? 'fr-FR' : 'en-US'; const [bookings, setBookings] = useState([]); const [documents, setDocuments] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [searchTerm, setSearchTerm] = useState(''); const [filterUserId, setFilterUserId] = useState('all'); const [filterQuoteNumber, setFilterQuoteNumber] = useState(''); const [currentPage, setCurrentPage] = useState(1); const [itemsPerPage, setItemsPerPage] = useState(10); const [openMenuId, setOpenMenuId] = useState(null); const [menuPosition, setMenuPosition] = useState<{ top: number; left: number } | null>(null); const [deletingId, setDeletingId] = useState(null); // Helper function to get formatted quote number const getQuoteNumber = (booking: Booking): string => { if (booking.type === 'csv') { return `#${booking.bookingId || booking.id.slice(0, 8).toUpperCase()}`; } return booking.bookingNumber || `#${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); const response = await getAllBookings(); const allBookings = response.bookings || []; setBookings(allBookings); const allDocuments: DocumentWithBooking[] = []; const userIds = new Set(); allBookings.forEach((booking: Booking) => { userIds.add(booking.userId); if (booking.documents && booking.documents.length > 0) { booking.documents.forEach((doc: Document) => { const actualFileName = doc.fileName || doc.name || 'document'; const actualFilePath = doc.filePath || doc.url || ''; const actualMimeType = doc.mimeType || doc.type || ''; let fileType = ''; if (actualMimeType.includes('/')) { const parts = actualMimeType.split('/'); fileType = getFileType(parts[1]); } else { fileType = getFileType(actualFileName); } allDocuments.push({ ...doc, bookingId: booking.id, quoteNumber: getQuoteNumber(booking), userId: booking.userId, organizationId: booking.organizationId, route: `${booking.origin || 'N/A'} → ${booking.destination || 'N/A'}`, status: booking.status, fileName: actualFileName, filePath: actualFilePath, fileType: fileType, }); }); } }); try { const usersData = await getAllUsers(); if (usersData && usersData.users) { const usersMap = new Map( usersData.users.map((u: any) => { const fullName = `${u.firstName || ''} ${u.lastName || ''}`.trim(); return [u.id, fullName || u.email || u.id.substring(0, 8)]; }) ); allDocuments.forEach(doc => { const userName = usersMap.get(doc.userId); doc.userName = userName || doc.userId.substring(0, 8) + '...'; }); } } catch (userError) { console.error('Failed to fetch user names:', userError); allDocuments.forEach(doc => { doc.userName = doc.userId.substring(0, 8) + '...'; }); } setDocuments(allDocuments); setError(null); } catch (err: any) { setError(err.message || t('loadError')); } finally { setLoading(false); } }, [t]); useEffect(() => { fetchBookingsAndDocuments(); }, [fetchBookingsAndDocuments]); const uniqueUsers = Array.from( new Map( documents.map(doc => [ doc.userId, { id: doc.userId, name: doc.userName || doc.userId.substring(0, 8) + '...' }, ]) ).values() ); const filteredDocuments = documents.filter(doc => { const matchesSearch = searchTerm === '' || (doc.fileName && doc.fileName.toLowerCase().includes(searchTerm.toLowerCase())) || (doc.name && doc.name.toLowerCase().includes(searchTerm.toLowerCase())) || (doc.type && doc.type.toLowerCase().includes(searchTerm.toLowerCase())) || doc.route.toLowerCase().includes(searchTerm.toLowerCase()); const matchesUser = filterUserId === 'all' || doc.userId === filterUserId; const matchesQuote = filterQuoteNumber === '' || doc.quoteNumber.toLowerCase().includes(filterQuoteNumber.toLowerCase()); return matchesSearch && matchesUser && matchesQuote; }); const totalPages = Math.ceil(filteredDocuments.length / itemsPerPage); const startIndex = (currentPage - 1) * itemsPerPage; const endIndex = startIndex + itemsPerPage; const paginatedDocuments = filteredDocuments.slice(startIndex, endIndex); useEffect(() => { setCurrentPage(1); }, [searchTerm, filterUserId, filterQuoteNumber]); const getDocumentIcon = (type: string): ReactNode => { const typeLower = type.toLowerCase(); const cls = 'h-6 w-6'; const iconMap: 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 iconMap[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.toLowerCase()] || 'bg-gray-100 text-gray-800'; }; const handleDeleteDocument = async (bookingId: string, documentId: string) => { if (!window.confirm(t('confirmDelete'))) return; setDeletingId(documentId); try { await deleteAdminDocument(bookingId, documentId); setDocuments(prev => prev.filter(d => d.id !== documentId)); } catch (err: any) { setError(err.message || t('deleteError')); } finally { setDeletingId(null); } }; const handleDownload = async (url: string, fileName: string) => { try { 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); 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); const message = error instanceof Error ? error.message : t('unknownError'); alert(t('downloadError', { message })); } }; if (loading) { return (

{t('loading')}

); } return (
{/* Stats */}
{t('stats.totalDocs')}
{documents.length}
{t('stats.bookingsWithDocs')}
{bookings.filter(b => b.documents && b.documents.length > 0).length}
{t('stats.filtered')}
{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) => ( )) )}
{t('table.name')} {t('table.type')} {t('table.quoteNumber')} {t('table.route')} {t('table.status')} {t('table.user')} {t('table.actions')}
{t('table.empty')}
{doc.fileName || doc.name}
{getDocumentIcon(doc.fileType || doc.type)}
{doc.fileType || doc.type}
{doc.quoteNumber}
{doc.route}
{doc.status}
{doc.userName || doc.userId.substring(0, 8) + '...'}
{/* Pagination Controls */} {filteredDocuments.length > 0 && (

{t('pagination.showing')} {startIndex + 1}{' '} {t('pagination.to')}{' '} {Math.min(endIndex, filteredDocuments.length)} {' '} {t('pagination.on')}{' '} {filteredDocuments.length}{' '} {t('pagination.results')}

)}
{/* Actions Dropdown Menu */} {openMenuId && menuPosition && ( <>
{ setOpenMenuId(null); setMenuPosition(null); }} />
{(() => { const [bookingId, documentId] = openMenuId.split('::'); const doc = documents.find(d => d.bookingId === bookingId && d.id === documentId); if (!doc) return null; return ( <> ); })()}
)}
); }