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}
+
+ >
+ )}
+
-
-
-
+
+
+ } 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}
+
))}
-
-
-
- )}
+
+
+ )}
+
);