'use client'; import { useState } from 'react'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { Download, Eye, Trash2, ShieldCheck, ShieldOff, Star, Wallet } from 'lucide-react'; import DataTable from '@/components/ui/DataTable'; import Badge from '@/components/ui/Badge'; import Pagination from '@/components/ui/Pagination'; import ActionButton from '@/components/ui/ActionButton'; import Modal from '@/components/ui/Modal'; import ConfirmDialog from '@/components/ui/ConfirmDialog'; import { passengersApi, apiClient } from '@/lib/api'; import { formatDate, formatDateTime } from '@/lib/utils'; import { PassengerFilters } from '@/types'; const Field = ({ label, value, mono = false, truncate = false }: { label: string; value: string; mono?: boolean; truncate?: boolean }) => (

{label}

{value || '—'}

); const SectionHeader = ({ title }: { title: string }) => (

{title}

); const TIER_COLORS: Record = { BRONZE: 'bg-orange-100 dark:bg-orange-900/30 text-orange-700 dark:text-orange-400 border-orange-200 dark:border-orange-800', SILVER: 'bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 border-gray-200 dark:border-gray-600', GOLD: 'bg-yellow-100 dark:bg-yellow-900/30 text-yellow-700 dark:text-yellow-400 border-yellow-200 dark:border-yellow-800', PLATINUM: 'bg-indigo-100 dark:bg-indigo-900/30 text-indigo-700 dark:text-indigo-400 border-indigo-200 dark:border-indigo-800', }; export default function PassengersPage() { const [filters, setFilters] = useState({ page: 1, pageSize: 20, search: '', role: 'PASSENGER' }); const [showExtraFilters, setShowExtraFilters] = useState(false); const [extraFilters, setExtraFilters] = useState({ gender: '', nationality: '', dateFrom: '', dateTo: '' }); const [selectedPassenger, setSelectedPassenger] = useState(null); const [deleteConfirm, setDeleteConfirm] = useState<{ isOpen: boolean; passenger: any | null }>({ isOpen: false, passenger: null }); const [deleteError, setDeleteError] = useState(null); const [exportModalOpen, setExportModalOpen] = useState(false); const [exportFormat, setExportFormat] = useState<'csv' | 'excel' | 'pdf'>('csv'); const [exportDateFrom, setExportDateFrom] = useState(''); const [exportDateTo, setExportDateTo] = useState(''); const [exportColumns, setExportColumns] = useState>({ fullName: true, email: true, phone: true, gender: true, nationality: true, verified: true, }); const queryClient = useQueryClient(); const deleteMutation = useMutation({ mutationFn: (id: string) => apiClient.delete(`/passengers/${id}`), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['passengers'] }); setDeleteConfirm({ isOpen: false, passenger: null }); setDeleteError(null); }, onError: (error: any) => { setDeleteError(error?.response?.data?.message || error?.message || 'Failed to delete passenger'); }, }); const { data, isLoading, error } = useQuery({ queryKey: ['passengers', filters], queryFn: () => passengersApi.getAll(filters), }); const PASSENGER_COLS = [ { key: 'fullName', label: 'Full Name' }, { key: 'email', label: 'Email' }, { key: 'phone', label: 'Phone' }, { key: 'dateOfBirth', label: 'Date of Birth' }, { key: 'gender', label: 'Gender' }, { key: 'nationality', label: 'Nationality' }, { key: 'verified', label: 'Verified' }, ]; const confirmExportPassengers = async () => { const cols = Object.entries(exportColumns).filter(([, v]) => v).map(([k]) => k); if (!cols.length) { alert('Please select at least one column'); return; } // Fetch all records const allData = await passengersApi.getAll({ ...filters, page: 1, pageSize: 9999 }); const exportItems = (allData?.items || []).filter((p: any) => { if (!exportDateFrom && !exportDateTo) return true; const d = p.createdAt ? new Date(p.createdAt).toISOString().split('T')[0] : null; if (exportDateFrom && (!d || d < exportDateFrom)) return false; if (exportDateTo && (!d || d > exportDateTo)) return false; return true; }); const headers = PASSENGER_COLS.filter(c => cols.includes(c.key)).map(c => c.label); const rows = exportItems.map((p: any) => PASSENGER_COLS.filter(c => cols.includes(c.key)).map(({ key }) => { switch (key) { case 'fullName': return p.fullName || ''; case 'email': return p.email || ''; case 'phone': return p.phone || ''; case 'dateOfBirth': return p.dateOfBirth ? formatDate(p.dateOfBirth) : ''; case 'gender': return p.gender || ''; case 'nationality': return p.nationality || ''; case 'verified': return p.faydaVerified ? 'Yes' : 'No'; default: return ''; } }) ); const dateStr = new Date().toISOString().split('T')[0]; if (exportFormat === 'pdf') { const w = window.open('', '_blank')!; w.document.write(`Passengers Export`); w.document.write(`

Passengers Export — ${dateStr}

${headers.map(h => ``).join('')}`); rows.forEach((r: string[]) => { w.document.write(`${r.map((v: string) => ``).join('')}`); }); w.document.write('
${h}
${v}
'); w.document.close(); w.print(); } else if (exportFormat === 'excel') { const tsv = [headers.join('\t'), ...rows.map((r: string[]) => r.join('\t'))].join('\n'); const blob = new Blob([tsv], { type: 'application/vnd.ms-excel' }); const url = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `passengers-${dateStr}.xls`; a.click(); } else { const csv = [headers.map(h => `"${h}"`).join(','), ...rows.map((r: string[]) => r.map((v: string) => `"${v}"`).join(','))].join('\n'); const blob = new Blob([csv], { type: 'text/csv' }); const url = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `passengers-${dateStr}.csv`; a.click(); } setExportModalOpen(false); }; const columns = [ { key: 'fullName', label: 'Name', sortable: true, render: (p: any) => (
{p.fullName}
{p.email}
), }, { key: 'phone', label: 'Phone', sortable: true, render: (p: any) => p.phone || 'N/A' }, { key: 'nationality', label: 'Nationality', sortable: true, render: (p: any) => p.nationality || 'N/A' }, { key: 'gender', label: 'Gender', sortable: true, render: (p: any) => p.gender || 'N/A' }, { key: 'dateOfBirth', label: 'Date of Birth', sortable: true, render: (p: any) => p.dateOfBirth ? formatDate(p.dateOfBirth) : 'N/A' }, { key: 'verified', label: 'Status', render: (p: any) => ( {p.faydaVerified ? 'Verified' : 'Unverified'} ), }, ]; const actions = [ { label: 'View Details', onClick: (p: any) => setSelectedPassenger(p), variant: 'secondary' as const, icon: Eye }, { label: 'Delete', onClick: (p: any) => { setDeleteError(null); setDeleteConfirm({ isOpen: true, passenger: p }); }, variant: 'danger' as const, icon: Trash2 }, ]; return (

Passengers

Manage passenger profiles and verification

setExportModalOpen(true)}>Export
{error && (
Error loading passengers: {error instanceof Error ? error.message : 'Unknown error'}
)}
setFilters({ ...filters, search: e.target.value, page: 1 })} />
{showExtraFilters && (
setExtraFilters({ ...extraFilters, nationality: e.target.value })} />
setExtraFilters({ ...extraFilters, dateFrom: e.target.value })} />
setExtraFilters({ ...extraFilters, dateTo: e.target.value })} />
)}
{data?.meta && ( setFilters({ ...filters, page })} /> )}
{ setDeleteConfirm({ isOpen: false, passenger: null }); setDeleteError(null); }} onConfirm={async () => { if (deleteConfirm.passenger) { await deleteMutation.mutateAsync(deleteConfirm.passenger.id); } }} title="Delete Passenger" message={`Are you sure you want to delete ${deleteConfirm.passenger?.fullName}?`} confirmText="Delete" isDanger isLoading={deleteMutation.isPending} warning="This passenger may have active bookings, loyalty points, and wallet balance. Deleting will impact these systems and records." error={deleteError ?? undefined} /> {/* Passenger Details Modal */} setSelectedPassenger(null)} title="Passenger Details" size="xl"> {selectedPassenger && (() => { const p = selectedPassenger; const isVerified = !!p.faydaVerified || !!p.nationalId; const tier = p.loyalty?.tier || p.loyaltyTier; const tierColor = TIER_COLORS[tier] || TIER_COLORS.BRONZE; return (
{/* Gradient header with avatar */}
{(p.fullName || p.email || '?')[0].toUpperCase()}

{p.fullName}

{p.email}

{isVerified ? '✓ Verified' : 'Unverified'}
{tier && ( {tier} )}
{/* Quick stats */}
{[ { label: 'Loyalty Points', value: (p.passenger?.loyalty?.pointsBalance ?? p.loyalty?.pointsBalance ?? 0).toLocaleString() }, { label: 'Wallet Balance', value: p.passenger?.wallet || p.wallet ? `ETB ${((p.passenger?.wallet?.balanceMinor ?? p.wallet?.balanceMinor ?? 0) / 100).toFixed(2)}` : '—' }, { label: 'Nationality', value: p.nationality || '—' }, ].map(({ label, value }) => (

{label}

{value}

))}
{/* Personal */}
{/* Contact */}
{/* Identification */}

Fayda (National ID)

{isVerified ? : } {isVerified ? 'Verified' : 'Not verified'}
{p.faydaVerifiedAt &&

{formatDateTime(p.faydaVerifiedAt)}

}
{/* Loyalty & Wallet */} {(p.passenger?.loyalty || p.loyalty || p.passenger?.wallet || p.wallet) && (
{(p.passenger?.loyalty || p.loyalty) && (() => { const loyalty = p.passenger?.loyalty || p.loyalty; return ( <>

Tier

{loyalty.tier}
); })()} {(p.passenger?.wallet || p.wallet) && (() => { const wallet = p.passenger?.wallet || p.wallet; return (

Wallet Balance

ETB {((wallet.balanceMinor ?? 0) / 100).toFixed(2)}

); })()}
)} {/* Account */}
{/* Timestamps */}
setSelectedPassenger(null)}>Close
); })()}
setExportModalOpen(false)} title="Export Passengers" size="md">
setExportDateFrom(e.target.value)} />
setExportDateTo(e.target.value)} />

Select Columns

{PASSENGER_COLS.map((col) => ( ))}

Export Format

{(['csv', 'excel', 'pdf'] as const).map(fmt => ( ))}
setExportModalOpen(false)}>Cancel Export
); }