import { StatusBadge } from '@ema-platform/ui'; import { type StatusTone } from '@ema-platform/shared'; import { useState } from 'react'; import { useApiQuery } from '@ema-platform/api'; import { ActionIcon, Badge, Button, Card, Collapse, Divider, Group, Modal, Paper, Select, SimpleGrid, Stack, Table, Text, TextInput, ThemeIcon, Title, rem, } from '@mantine/core'; import { IconBook2, IconCertificate, IconChevronDown, IconChevronUp, IconEye, IconHeart, IconId, IconSearch, IconShieldCheck, IconUser, IconUsers, } from '@tabler/icons-react'; // --------------------------------------------------------------------------- // Mock data // --------------------------------------------------------------------------- interface Seafarer { id: string; name: string; nationality: string; dob: string; rank: string; seamanBookNo: string; seamanBookExpiry: string; btcNo: string | null; bsidNo: string | null; medicalExpiry: string; medicalStatus: 'Valid' | 'Expiring' | 'Expired'; cocCerts: { type: string; no: string; expiry: string }[]; status: 'Active' | 'Inactive' | 'Suspended'; } const RANK_OPTIONS = ['All', 'Master', 'Chief Engineer', 'Officer of the Watch', 'Able Seaman', 'Deck Rating']; const STATUS_OPTIONS = ['All', 'Active', 'Inactive', 'Suspended']; const MEDICAL_OPTIONS = ['All', 'Valid', 'Expiring', 'Expired']; const MEDICAL_COLOR: Record = { Valid: 'teal', Expiring: 'orange', Expired: 'red' }; const STATUS_TONE: Record = { Active: 'success', Inactive: 'neutral', Suspended: 'danger' }; // --------------------------------------------------------------------------- // Detail modal // --------------------------------------------------------------------------- function DetailModal({ sf, opened, onClose }: { sf: Seafarer | null; opened: boolean; onClose: () => void }) { if (!sf) return null; return ( {sf.name} — {sf.id}} size="xl" radius="lg" > Personal Full Name{sf.name} Nationality{sf.nationality} Date of Birth{sf.dob} Rank{sf.rank} Status Documents Seaman Book{sf.seamanBookNo} SB Expiry{sf.seamanBookExpiry} BTC No.{sf.btcNo ?? '—'} BSID No.{sf.bsidNo ?? '—'} Medical Expiry {sf.medicalExpiry} ({sf.medicalStatus}) {sf.cocCerts.length > 0 && ( CoC / CoP Certificates {['Type', 'Certificate No.', 'Expiry'].map((h) => ( {h} ))} {sf.cocCerts.map((c) => ( {c.type} {c.no} {c.expiry} ))}
)}
); } // --------------------------------------------------------------------------- // Main page // --------------------------------------------------------------------------- export function SeafarerRegistryPage() { // The register is served whole and filtered in the browser: the filters are // instant facets over a page-sized list, and a round trip per keystroke // would make the search feel slower than the data it is searching. const { data } = useApiQuery<{ total: number; items: Seafarer[] }>({ url: '/seafarer-registry', method: 'GET', }); const seafarers = data?.items ?? []; const [search, setSearch] = useState(''); const [rankFilter, setRankFilter] = useState('All'); const [statusFilter, setStatusFilter] = useState('All'); const [medicalFilter, setMedicalFilter] = useState('All'); const [selected, setSelected] = useState(null); const [filtersOpen, setFiltersOpen] = useState(true); const filtered = seafarers.filter((sf) => { const q = search.toLowerCase(); const matchSearch = !q || sf.name.toLowerCase().includes(q) || sf.id.toLowerCase().includes(q) || sf.seamanBookNo.toLowerCase().includes(q); const matchRank = rankFilter === 'All' || sf.rank === rankFilter; const matchStatus = statusFilter === 'All' || sf.status === statusFilter; const matchMed = medicalFilter === 'All' || sf.medicalStatus === medicalFilter; return matchSearch && matchRank && matchStatus && matchMed; }); const KPI = [ { label: 'Total Seafarers', value: seafarers.length, color: 'blue', icon: IconUsers }, { label: 'Active', value: seafarers.filter((s) => s.status === 'Active').length, color: 'teal', icon: IconUser }, { label: 'Medical Expiring',value: seafarers.filter((s) => s.medicalStatus === 'Expiring').length, color: 'orange', icon: IconHeart }, { label: 'Medical Expired', value: seafarers.filter((s) => s.medicalStatus === 'Expired').length, color: 'red', icon: IconHeart }, { label: 'With CoC', value: seafarers.filter((s) => s.cocCerts.length > 0).length, color: 'violet', icon: IconShieldCheck }, { label: 'Without BSID', value: seafarers.filter((s) => !s.bsidNo).length, color: 'yellow', icon: IconId }, ]; return (
Seafarer Registry Search and view all registered seafarers, their documents, and certificate status
{/* KPIs */} {KPI.map((k) => { const KIcon = k.icon; return (
{k.value} {k.label}
); })}
{/* Search + filters */} } value={search} onChange={(e) => setSearch(e.currentTarget.value)} style={{ flex: 1, minWidth: 200 }} size="sm" /> setStatusFilter(v ?? 'All')} size="sm" />