From 818964a6945e877f53683968dbc2c1abb72f22c6 Mon Sep 17 00:00:00 2001 From: Nati Date: Thu, 20 Aug 2026 07:14:57 +0000 Subject: [PATCH] feat: add seafarer registration feature with multi-step form - Implemented SeafarerRegistrationPage component with five-step registration process. - Added API endpoints for seafarer registration including start, save, and submit functionalities. - Created necessary types and constants for seafarer registration. - Updated router to include new registration paths and permissions. - Integrated profile defaults to pre-fill registration fields where applicable. - Added validation and error handling for registration steps. - Enhanced document upload functionality specific to seafarer registration. --- .../pages/SeafarerRegistrationQueuePage.tsx | 147 +++++ .../pages/SeafarerRegistrationReviewPage.tsx | 283 +++++++++ apps/backoffice/src/app/layouts/nav-config.ts | 2 +- apps/backoffice/src/app/router/index.tsx | 6 + apps/e2e/src/seafarer-registration.spec.ts | 563 ++++++++---------- apps/e2e/src/support/applicant.ts | 5 +- apps/e2e/src/support/db.ts | 7 + apps/e2e/src/support/officer.ts | 4 +- apps/e2e/src/support/workflow.ts | 51 +- .../pages/OperationsOnboardingPage.tsx | 10 +- .../components/RequireSeafarerProfile.tsx | 89 +-- .../components/RegistrationDocuments.tsx | 140 +++++ .../components/RegistrationSummary.tsx | 87 +++ .../components/fields.tsx | 143 +++++ .../components/steps.tsx | 173 ++++++ .../pages/SeafarerRegistrationPage.tsx | 425 +++++++++++++ apps/portal/src/app/router.tsx | 33 +- libs/api/src/index.ts | 1 + .../features/licensing/licensing.helpers.ts | 3 +- .../features/seafarer-registration/index.ts | 3 + .../seafarer-registration-api.ts | 124 ++++ .../seafarer-registration.constants.ts | 211 +++++++ .../seafarer-registration.types.ts | 78 +++ .../src/lib/features/seafarer/seafarer-api.ts | 4 +- 24 files changed, 2147 insertions(+), 445 deletions(-) create mode 100644 apps/backoffice/src/app/features/seafarer-registration-review/pages/SeafarerRegistrationQueuePage.tsx create mode 100644 apps/backoffice/src/app/features/seafarer-registration-review/pages/SeafarerRegistrationReviewPage.tsx create mode 100644 apps/portal/src/app/features/seafarer-registration/components/RegistrationDocuments.tsx create mode 100644 apps/portal/src/app/features/seafarer-registration/components/RegistrationSummary.tsx create mode 100644 apps/portal/src/app/features/seafarer-registration/components/fields.tsx create mode 100644 apps/portal/src/app/features/seafarer-registration/components/steps.tsx create mode 100644 apps/portal/src/app/features/seafarer-registration/pages/SeafarerRegistrationPage.tsx create mode 100644 libs/api/src/lib/features/seafarer-registration/index.ts create mode 100644 libs/api/src/lib/features/seafarer-registration/seafarer-registration-api.ts create mode 100644 libs/api/src/lib/features/seafarer-registration/seafarer-registration.constants.ts create mode 100644 libs/api/src/lib/features/seafarer-registration/seafarer-registration.types.ts diff --git a/apps/backoffice/src/app/features/seafarer-registration-review/pages/SeafarerRegistrationQueuePage.tsx b/apps/backoffice/src/app/features/seafarer-registration-review/pages/SeafarerRegistrationQueuePage.tsx new file mode 100644 index 000000000..26858983f --- /dev/null +++ b/apps/backoffice/src/app/features/seafarer-registration-review/pages/SeafarerRegistrationQueuePage.tsx @@ -0,0 +1,147 @@ +import { useMemo, useState } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { Badge, Container, Group, Select, Text, TextInput, Title } from '@mantine/core'; +import { IconSearch } from '@tabler/icons-react'; +import { useDebouncedValue } from '@mantine/hooks'; +import { + SEAFARER_REGISTRATION_STATUS_COLORS, + SEAFARER_REGISTRATION_STATUS_LABELS, + displaySeafarerAnswer, + useListSeafarerRegistrationsQuery, + type SeafarerRegistration, + type SeafarerRegistrationStatus, +} from '@ema-platform/api'; +import { AdvancedTable, type AdvancedColumn } from '@ema-platform/ui'; +import { useDateDisplayer } from '@ema-platform/shared'; + +const PAGE_SIZE = 10; + +const STATUS_FILTERS = (Object.keys(SEAFARER_REGISTRATION_STATUS_LABELS) as SeafarerRegistrationStatus[]) + .filter((s) => s !== 'DRAFT') + .map((value) => ({ value, label: SEAFARER_REGISTRATION_STATUS_LABELS[value] })); + +export function applicantName(r: Pick): string { + return [r.firstName, r.middleName, r.lastName].filter(Boolean).join(' ') || '—'; +} + +/** Submitted seafarer registrations, oldest first — click a row to review it. */ +export function SeafarerRegistrationQueuePage() { + const navigate = useNavigate(); + const showDate = useDateDisplayer(); + const [status, setStatus] = useState(null); + const [search, setSearch] = useState(''); + const [debouncedSearch] = useDebouncedValue(search, 300); + const [page, setPage] = useState(0); + const [pageSize, setPageSize] = useState(PAGE_SIZE); + + const { data, isLoading, isFetching, refetch } = useListSeafarerRegistrationsQuery({ + status: status ?? undefined, + search: debouncedSearch || undefined, + take: pageSize, + skip: page * pageSize, + }); + + const columns: AdvancedColumn[] = useMemo( + () => [ + { + header: 'Registration №', + accessorKey: 'registrationNumber', + cell: ({ row }) => ( + + {row.original.registrationNumber} + + ), + }, + { + header: 'Applicant', + accessorKey: 'lastName', + cell: ({ row }) => ( +
+ + {applicantName(row.original)} + + + {row.original.nationalIdNumber ?? '—'} + +
+ ), + }, + { + header: 'Department', + accessorKey: 'department', + cell: ({ row }) => {displaySeafarerAnswer('department', row.original.department)}, + }, + { + header: 'Submitted', + accessorKey: 'submittedAt', + cell: ({ row }) => ( + {row.original.submittedAt ? showDate(row.original.submittedAt) : '—'} + ), + }, + { + header: 'Status', + accessorKey: 'status', + cell: ({ row }) => ( + + {SEAFARER_REGISTRATION_STATUS_LABELS[row.original.status]} + + ), + }, + ], + [showDate], + ); + + return ( + + + Seafarer Registration Queue + + + Registrations awaiting review. Approval numbers the seafarer and opens their Seaman Book and + BTC applications. + + + } + value={search} + onChange={(e) => { + setSearch(e.currentTarget.value); + setPage(0); + }} + w={280} + /> +