From a76ad9479fa4c1305fdeb9b5369830c2c9fb5c1f Mon Sep 17 00:00:00 2001 From: estifanos Date: Tue, 8 Sep 2026 09:52:24 +0000 Subject: [PATCH] feat: add read-only SeafarerScopeFields component to profile page for displaying certification scope details --- .../components/SeafarerScopeFields.tsx | 92 +++++++++++++++++++ .../features/profile/pages/ProfilePage.tsx | 7 ++ apps/portal/src/app/i18n/locales/am.ts | 15 +++ apps/portal/src/app/i18n/locales/en.ts | 16 ++++ 4 files changed, 130 insertions(+) create mode 100644 apps/portal/src/app/features/profile/components/SeafarerScopeFields.tsx diff --git a/apps/portal/src/app/features/profile/components/SeafarerScopeFields.tsx b/apps/portal/src/app/features/profile/components/SeafarerScopeFields.tsx new file mode 100644 index 000000000..c1ee599ac --- /dev/null +++ b/apps/portal/src/app/features/profile/components/SeafarerScopeFields.tsx @@ -0,0 +1,92 @@ +import { Paper, SimpleGrid, Stack, Text, TextInput, Title } from '@mantine/core'; +import { useTranslation } from 'react-i18next'; +import { + displaySeafarerAnswer, + useGetActiveDepartmentsQuery, + useGetMySeafarerRegistrationQuery, + useLocalized, +} from '@ema-platform/api'; +import { PORTAL_PERMISSIONS, usePermissions } from '@ema-platform/auth'; + +/** + * Department and STCW limitation, read-only. + * + * Answered once in the registration wizard and never again, yet between them + * they decide which certificates, examinations and services the portal offers + * — so a seafarer has to be able to see what they are without reopening a + * submitted registration. Not editable here on purpose: either answer moves + * the applicant onto a different CoC ladder, which is EMA's decision on + * review, not a profile edit. + * + * `tier` lives only on the registration record — the profile carries the + * department alone (`seafarerDepartment`) — so the registration is the source + * for both, with the profile's department as the fallback for an account whose + * registration is not readable. + */ +export function SeafarerScopeFields({ + fallbackDepartment, +}: { + /** `profile.seafarerDepartment`, used when no registration comes back. */ + fallbackDepartment?: string | null; +}) { + const { t } = useTranslation(); + const localized = useLocalized(); + // Same permission the registration page and `/seafarer-registrations/mine` + // are behind — asking without it would only 403. + const { can } = usePermissions(); + const mayRegister = can([PORTAL_PERMISSIONS.APPLY_SEAFARER_REGISTRATION]); + const { data, isLoading } = useGetMySeafarerRegistrationQuery(undefined, { + skip: !mayRegister, + }); + const registration = data?.registration ?? null; + + const department = registration?.department ?? fallbackDepartment ?? null; + const tier = registration?.tier ?? null; + + // Live department names, so a department configured after this release still + // shows its own name — the same list the registration wizard picks from. + const { data: departments } = useGetActiveDepartmentsQuery(undefined, { skip: !department }); + const departmentOptions = departments?.map((d) => ({ value: d.code, label: localized(d.name) })); + + // Nothing recorded yet — the registration wizard is where these are answered. + if (isLoading || (!department && !tier)) return null; + + return ( + + +
+ {t('profile.seafarerScope.title')} + + {t('profile.seafarerScope.subtitle')} + +
+ + + + + {registration && registration.status !== 'APPROVED' && ( + + {t('profile.seafarerScope.pending')} + + )} +
+
+ ); +} diff --git a/apps/portal/src/app/features/profile/pages/ProfilePage.tsx b/apps/portal/src/app/features/profile/pages/ProfilePage.tsx index 3041cbfa6..2fd71761e 100644 --- a/apps/portal/src/app/features/profile/pages/ProfilePage.tsx +++ b/apps/portal/src/app/features/profile/pages/ProfilePage.tsx @@ -68,6 +68,7 @@ import { import { useSaveMyAddressMutation } from '../api/address-api'; import { toAddressPayload } from '../types/address'; import { OperationsFormContent } from '../components/OperationsFormContent'; +import { SeafarerScopeFields } from '../components/SeafarerScopeFields'; import { MySignaturePad } from '../components/SignaturePad'; import { SEAFARER_PROFILE_REQUIREMENT } from '../components/RequireSeafarerProfile'; import classes from './ProfilePage.module.css'; @@ -768,6 +769,12 @@ export function ProfilePage() { )} + + {/* Department and STCW limitation — answered at registration, shown + here because they decide which certificates apply. */} + {/* ---- Address ---- */} diff --git a/apps/portal/src/app/i18n/locales/am.ts b/apps/portal/src/app/i18n/locales/am.ts index f4422507e..3cb4288cf 100644 --- a/apps/portal/src/app/i18n/locales/am.ts +++ b/apps/portal/src/app/i18n/locales/am.ts @@ -464,6 +464,21 @@ export const am: Translations = { noProfile: 'ምንም መገለጫ አልተገኘም። መጀመሪያ መገለጫዎን ያጠናቅቁ።', save: 'መገለጫ አስቀምጥ', }, + seafarerScope: { + title: 'የምስክር ወረቀት ወሰን', + subtitle: + 'እንደ መርከበኛ በተመዘገቡበት ጊዜ የተመዘገበ። አንድ ላይ የትኞቹ የምስክር ወረቀቶች፣ ፈተናዎችና አገልግሎቶች እንደሚመለከቱዎት ይወስናሉ — ለመቀየር EMAን ያነጋግሩ።', + department: 'ክፍል', + tier: 'የማዕረግ ደረጃ', + tiers: { + ABOVE: 'ከላይ', + BELOW: 'ከታች', + }, + tierHintDeck: 'ከላይ 3,000 ጠቅላላ ቶን እና ከዚያ በላይ የሆኑ መርከቦችን ይሸፍናል፤ ከታች ከ500–3,000 ጠቅላላ ቶን ይሸፍናል።', + tierHintEngine: + 'ከላይ 3,000 ኪ.ዋ እና ከዚያ በላይ የማንቀሳቀሻ ኃይል ያላቸውን መርከቦች ይሸፍናል፤ ከታች ከ750–3,000 ኪ.ዋ ይሸፍናል።', + pending: 'ገና ካልጸደቀው የመርከበኛ ምዝገባዎ የተወሰደ።', + }, addressSection: { title: 'አድራሻ እና መገናኛ', subtitle: 'የመታወቂያ ሰነዶችዎ፣ የመገናኛ ዝርዝሮችዎ እና የአደጋ ጊዜ ተጠሪ', diff --git a/apps/portal/src/app/i18n/locales/en.ts b/apps/portal/src/app/i18n/locales/en.ts index 9fa3204b8..e42942f78 100644 --- a/apps/portal/src/app/i18n/locales/en.ts +++ b/apps/portal/src/app/i18n/locales/en.ts @@ -470,6 +470,22 @@ export const en = { noProfile: 'No profile found. Complete your profile setup first.', save: 'Save Profile', }, + seafarerScope: { + title: 'Certification Scope', + subtitle: + 'Recorded when you registered as a seafarer. Together they decide which certificates, examinations and services apply to you — contact EMA to change either.', + department: 'Department', + tier: 'Certificate Limitation', + tiers: { + ABOVE: 'Above', + BELOW: 'Below', + }, + tierHintDeck: + 'Above covers ships of 3,000 gross tonnage or more; Below covers 500–3,000 GT.', + tierHintEngine: + 'Above covers ships of 3,000 kW propulsion power or more; Below covers 750–3,000 kW.', + pending: 'From your seafarer registration, which is not approved yet.', + }, addressSection: { title: 'Address & Contact', subtitle: 'Your identity documents, contact details and emergency contact',