diff --git a/apps/backoffice/src/app/features/license-review/pages/LicenseReviewPage/index.tsx b/apps/backoffice/src/app/features/license-review/pages/LicenseReviewPage/index.tsx index d86070069..d29871597 100644 --- a/apps/backoffice/src/app/features/license-review/pages/LicenseReviewPage/index.tsx +++ b/apps/backoffice/src/app/features/license-review/pages/LicenseReviewPage/index.tsx @@ -39,6 +39,7 @@ import { useLocalized, useApproveDocumentsMutation, useAssignApplicationMutation, + useClaimApplicationMutation, useCompleteReviewMutation, useConfirmPaymentMutation, useScheduleIssuanceMutation, @@ -198,6 +199,7 @@ export function LicenseReviewPage() { [requirements], ); + const [claimApplication] = useClaimApplicationMutation(); const [completeReview] = useCompleteReviewMutation(); const [requestAdjustment] = useRequestAdjustmentMutation(); const [approveDocuments] = useApproveDocumentsMutation(); @@ -531,8 +533,12 @@ export function LicenseReviewPage() { try { switch (action.id) { case "claim": - // Claim is fired from the queue in practice; kept here for the case - // where an officer opens an unclaimed application directly. + // Usually fired from the queue, but an officer can also open an + // unclaimed application directly and claim it from here. + await run( + () => claimApplication(id).unwrap(), + t("review.done.claim", "Application claimed"), + ); break; case "complete-review": await run( diff --git a/apps/backoffice/src/app/features/seafarer-document-review/pages/SeafarerDocumentReviewPage.tsx b/apps/backoffice/src/app/features/seafarer-document-review/pages/SeafarerDocumentReviewPage.tsx index 271fae22e..d1825a99b 100644 --- a/apps/backoffice/src/app/features/seafarer-document-review/pages/SeafarerDocumentReviewPage.tsx +++ b/apps/backoffice/src/app/features/seafarer-document-review/pages/SeafarerDocumentReviewPage.tsx @@ -1,7 +1,7 @@ import { useState } from 'react'; import { Link, useNavigate, useParams } from 'react-router-dom'; -import {Alert, Badge, Button, Center, Container, Group, Loader, Modal, Paper, Stack, Table, Text, Textarea} from '@mantine/core'; -import { IconAlertTriangle, IconArrowLeft, IconCheck, IconDownload } from '@tabler/icons-react'; +import {Alert, Avatar, Badge, Button, Center, Container, Divider, Group, Loader, Modal, Paper, SimpleGrid, Stack, Text, Textarea, ThemeIcon, rem} from '@mantine/core'; +import { IconAlertTriangle, IconArrowLeft, IconCash, IconCheck, IconDownload, IconFileCertificate } from '@tabler/icons-react'; import { SEAFARER_DOCUMENT_KIND_LABELS, SEAFARER_DOCUMENT_STATUS_COLORS, @@ -20,20 +20,35 @@ import { LICENSE_PERMISSIONS, RequirePermission } from '@ema-platform/auth'; const QUEUE_PATH = { SEAMAN_BOOK: '/seaman-book-queue', BTC_BASIC_TRAINING: '/btc-queue' } as const; -function Row({ label, value }: { label: string; value: React.ReactNode }) { +function Stat({ label, value }: { label: string; value: React.ReactNode }) { return ( - - - - {label} - - - - - {value ?? '—'} - - - +
+ {label} + {value ?? '—'} +
+ ); +} + +function SectionCard({ + title, + icon, + color, + children, +}: { + title: string; + icon: React.ReactNode; + color: string; + children: React.ReactNode; +}) { + return ( + + + {icon} + {title} + + + {children} + ); } @@ -168,61 +183,49 @@ export function SeafarerDocumentReviewPage() { )} - - + + + + {(applicant?.name ?? '??').split(' ').filter(Boolean).slice(0, 2).map((p) => p[0]).join('').toUpperCase()} +
- - Seafarer - - - - - - - {applicant.registrationNumber} - - ) : ( - applicant?.registrationNumber - ) - } - /> - -
+ {applicant?.name ?? '—'} + + {applicant?.seafarerNumber ?? '—'} + {applicant?.registrationId && ( + <> + · + + {applicant.registrationNumber} + + + )} +
-
- - Payment - - - - - - - - - -
-
-
- - Issuance - - - - - - - - -
-
-
+
+ + } color="teal"> + + + + + + + + + + } color="violet"> + + + + + + + + + setScheduleOpen(false)} title="Schedule pickup" centered> diff --git a/apps/backoffice/src/app/features/seafarer-registry/pages/SeafarerRegistryPage.tsx b/apps/backoffice/src/app/features/seafarer-registry/pages/SeafarerRegistryPage.tsx index f451d101f..fc9349789 100644 --- a/apps/backoffice/src/app/features/seafarer-registry/pages/SeafarerRegistryPage.tsx +++ b/apps/backoffice/src/app/features/seafarer-registry/pages/SeafarerRegistryPage.tsx @@ -2,7 +2,7 @@ import { PageHeader, 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, rem} from '@mantine/core'; +import {ActionIcon, Avatar, Badge, Button, Card, Collapse, Divider, Group, Modal, Paper, Select, SimpleGrid, Stack, Table, Text, TextInput, ThemeIcon, rem} from '@mantine/core'; import { IconBook2, IconCertificate, @@ -15,6 +15,7 @@ import { IconShieldCheck, IconUser, IconUsers, + IconX, } from '@tabler/icons-react'; // --------------------------------------------------------------------------- @@ -47,66 +48,72 @@ const STATUS_TONE: Record = { Active: 'success', Inactive: ' // --------------------------------------------------------------------------- // Detail modal // --------------------------------------------------------------------------- +function Stat({ label, value }: { label: string; value: React.ReactNode }) { + return ( +
+ {label} + {value ?? '—'} +
+ ); +} + function DetailModal({ sf, opened, onClose }: { sf: Seafarer | null; opened: boolean; onClose: () => void }) { if (!sf) return null; + const initials = sf.name.split(' ').filter(Boolean).slice(0, 2).map((p) => p[0]).join('').toUpperCase(); 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}) + + + {/* Header */} + + + {initials} +
+ {sf.name} + + {sf.id} + · + {sf.rank} - - - +
+
+ + + + +
- {sf.cocCerts.length > 0 && ( - - CoC / CoP Certificates - - - - {['Type', 'Certificate No.', 'Expiry'].map((h) => ( - {h} - ))} - - - + + + + + + + Missing} /> + Missing} /> + {sf.medicalStatus}} /> + + + + {sf.cocCerts.length > 0 && ( + + CoC / CoP Certificates + {sf.cocCerts.map((c) => ( - - {c.type} - {c.no} - {c.expiry} - + + + +
+ {c.type} + {c.no} +
+
+ Expires {c.expiry} +
))} -
-
-
- )} +
+
+ )} +
);