diff --git a/apps/backoffice/src/app/features/biometric-enrollment/pages/BiometricEnrollmentPage.tsx b/apps/backoffice/src/app/features/biometric-enrollment/pages/BiometricEnrollmentPage.tsx index 5ab70f38e..3577cb379 100644 --- a/apps/backoffice/src/app/features/biometric-enrollment/pages/BiometricEnrollmentPage.tsx +++ b/apps/backoffice/src/app/features/biometric-enrollment/pages/BiometricEnrollmentPage.tsx @@ -14,11 +14,10 @@ import { TextInput, ThemeIcon, } from '@mantine/core'; -import { IconAlertTriangle, IconFingerprint, IconPrinter, IconScan, IconSearch, IconX } from '@tabler/icons-react'; +import { IconAlertTriangle, IconFingerprint, IconScan, IconSearch, IconX } from '@tabler/icons-react'; import { useDebouncedValue } from '@mantine/hooks'; import { extractErrorMessage, - openAuthedDocument, useEnrollBiometricMutation, useGenerateBsidMutation, useGetBiometricEnrollmentsQuery, @@ -52,12 +51,19 @@ function fakeTemplate(): string { return btoa(String.fromCharCode(...bytes)); } -/** Pick a registered seafarer to enroll — approved registrations carry a profileId. */ +/** + * Pick a seafarer waiting on enrolment. + * + * AWAITING_BIOMETRICS only: enrolment is the step that unblocks the review, so + * this queue is exactly the registrations held for it. An approved seafarer has + * already been through here — listing them would invite a second capture of + * someone who is finished. + */ function ProfilePicker({ onPick }: { onPick: (r: SeafarerRegistration) => void }) { const [search, setSearch] = useState(''); const [debounced] = useDebouncedValue(search, 300); const { data, isFetching } = useListSeafarerRegistrationsQuery({ - status: 'APPROVED', + status: 'AWAITING_BIOMETRICS', search: debounced || undefined, take: 10, }); @@ -65,7 +71,7 @@ function ProfilePicker({ onPick }: { onPick: (r: SeafarerRegistration) => void } return ( } value={search} onChange={(e) => setSearch(e.currentTarget.value)} @@ -78,14 +84,16 @@ function ProfilePicker({ onPick }: { onPick: (r: SeafarerRegistration) => void } onPick(r)} style={{ cursor: 'pointer' }}> {applicantName(r)} - {r.seafarerNumber} + {/* Not seafarerNumber: that is only issued on approval, which + is downstream of this screen, so it is always blank here. */} + {r.registrationNumber} ))} {!isFetching && (data?.items ?? []).length === 0 && ( - No registered seafarer matches. + No seafarer is waiting on enrolment. )} @@ -116,7 +124,6 @@ export function BiometricEnrollmentPage() { const [bsid, setBsid] = useState(null); const [enroll, { isLoading: enrolling }] = useEnrollBiometricMutation(); const [revoke, { isLoading: revoking }] = useRevokeBiometricEnrollmentMutation(); - const [printing, setPrinting] = useState(false); const hasActive = useMemo( () => (m: BiometricModality) => (enrollments ?? []).some((e) => e.modality === m), @@ -161,26 +168,11 @@ export function BiometricEnrollmentPage() { } } - async function handlePrint() { - if (!profileId) return; - setPrinting(true); - try { - await openAuthedDocument( - `/biometric-enrollments/profile/${profileId}/certificate`, - `biometric-enrollment-${profileId}.pdf`, - ); - } catch (err) { - notify.error(extractErrorMessage(err, 'Could not open the certificate.')); - } finally { - setPrinting(false); - } - } - return ( {!selected ? ( @@ -196,7 +188,7 @@ export function BiometricEnrollmentPage() {
{applicantName(selected)} - {selected.seafarerNumber} + {selected.registrationNumber}
-
+ On file {isLoading ? ( ) : ( diff --git a/apps/backoffice/src/app/features/seafarer-registration-review/pages/SeafarerRegistrationReviewPage.tsx b/apps/backoffice/src/app/features/seafarer-registration-review/pages/SeafarerRegistrationReviewPage.tsx index 536367886..e6863fb11 100644 --- a/apps/backoffice/src/app/features/seafarer-registration-review/pages/SeafarerRegistrationReviewPage.tsx +++ b/apps/backoffice/src/app/features/seafarer-registration-review/pages/SeafarerRegistrationReviewPage.tsx @@ -63,8 +63,13 @@ export function SeafarerRegistrationReviewPage() { } const { registration, attachments } = data; - // Decided straight off the queue — no claim step. - const canDecide = registration.status === 'SUBMITTED'; + // Decided straight off the queue — no claim step. AWAITING_BIOMETRICS is + // deliberately excluded: approval is blocked on a BSID that only exists once + // the applicant has been enrolled, so the decision is not the reviewer's to + // take yet. SUBMITTED stays decidable for files that predate the gate. + const awaitingBiometrics = registration.status === 'AWAITING_BIOMETRICS'; + const canDecide = + registration.status === 'UNDER_REVIEW' || registration.status === 'SUBMITTED'; const busy = approving || rejecting || requesting; async function run(action: () => Promise, done: string) { @@ -141,6 +146,18 @@ export function SeafarerRegistrationReviewPage() { } /> + {awaitingBiometrics && ( + } + title="Awaiting biometric enrolment" + mb="md" + > + This registration cannot be decided yet. The applicant has to be + enrolled at a counter and issued a BSID first — the registration moves + to Under Review automatically once that happens. + + )} {registration.status === 'RESUBMIT_REQUIRED' && ( } title="Awaiting the applicant's corrections" mb="md"> {registration.reviewRemark} diff --git a/libs/api/src/lib/features/seafarer-registration/seafarer-registration.constants.ts b/libs/api/src/lib/features/seafarer-registration/seafarer-registration.constants.ts index f2fbf7c1d..1bc3c1103 100644 --- a/libs/api/src/lib/features/seafarer-registration/seafarer-registration.constants.ts +++ b/libs/api/src/lib/features/seafarer-registration/seafarer-registration.constants.ts @@ -130,6 +130,8 @@ export const SEAFARER_REGISTRATION_DOCUMENTS: { export const SEAFARER_REGISTRATION_STATUS_LABELS: Record = { DRAFT: 'Draft', SUBMITTED: 'Submitted', + AWAITING_BIOMETRICS: 'Awaiting Biometrics', + UNDER_REVIEW: 'Under Review', RESUBMIT_REQUIRED: 'Corrections Requested', APPROVED: 'Approved', REJECTED: 'Rejected', @@ -152,6 +154,8 @@ export const SEAFARER_REGISTRATION_STATUS_TONES: Record< > = { DRAFT: 'neutral', SUBMITTED: 'info', + AWAITING_BIOMETRICS: 'pending', + UNDER_REVIEW: 'info', RESUBMIT_REQUIRED: 'pending', APPROVED: 'success', REJECTED: 'danger', diff --git a/libs/api/src/lib/features/seafarer-registration/seafarer-registration.types.ts b/libs/api/src/lib/features/seafarer-registration/seafarer-registration.types.ts index be6414af5..76fb0ca1d 100644 --- a/libs/api/src/lib/features/seafarer-registration/seafarer-registration.types.ts +++ b/libs/api/src/lib/features/seafarer-registration/seafarer-registration.types.ts @@ -5,6 +5,12 @@ export type RankTier = 'ABOVE' | 'BELOW'; export type SeafarerRegistrationStatus = | 'DRAFT' + // Filed, waiting on the counter-side biometric capture that produces the + // BSID approval is blocked on. + | 'AWAITING_BIOMETRICS' + // BSID issued — the file is a reviewer's to decide. + | 'UNDER_REVIEW' + // Retained for registrations filed before biometrics moved ahead of review. | 'SUBMITTED' | 'RESUBMIT_REQUIRED' | 'APPROVED'