mirror of
https://github.com/Tria-plc/emaui.git
synced 2026-08-26 13:02:50 +00:00
feat: consolidate CoC and GOC endorsement workflows into a new ENDORSEMENT_SEAFARER type and add anyOf support to conditional form field logic.
This commit is contained in:
@@ -116,11 +116,21 @@ const PRESENTATION: Record<string, LicenseTypePresentation> = {
|
||||
// no capital threshold, no staff roles.
|
||||
detailSections: ['overview', 'documents'],
|
||||
},
|
||||
ENDORSEMENT_SEAFARER: {
|
||||
key: 'ENDORSEMENT_SEAFARER',
|
||||
icon: IconRubberStamp,
|
||||
// Person-centric, same as seafarer registration: no company entity, no
|
||||
// capital threshold, no staff roles, no inspection. Covers CoC and GOC
|
||||
// together — the `endorsementScope` field on the application decides
|
||||
// which certificate section(s) actually have data.
|
||||
detailSections: ['overview', 'documents'],
|
||||
},
|
||||
// Retired by ENDORSEMENT_SEAFARER (see endorsements.seed-data.ts). Kept so
|
||||
// an application filed before the switch still renders with the right
|
||||
// presentation instead of falling back to the generic company layout.
|
||||
ENDORSEMENT_COC: {
|
||||
key: 'ENDORSEMENT_COC',
|
||||
icon: IconRubberStamp,
|
||||
// Person-centric, same as seafarer registration: no company entity, no
|
||||
// capital threshold, no staff roles, no inspection.
|
||||
detailSections: ['overview', 'documents'],
|
||||
},
|
||||
ENDORSEMENT_GOC: {
|
||||
|
||||
@@ -48,6 +48,7 @@ export const am: Translations = {
|
||||
typeVESSEL_OWNERSHIP_TRANSFER: "የመርከብ ባለቤትነት ዝውውር",
|
||||
typeCERTIFICATE_OF_COMPETENCY: "የብቃት ማረጋገጫ ምስክር ወረቀት",
|
||||
typeCERTIFICATE_OF_PROFICIENCY: "የብቃት ምስክር ወረቀት",
|
||||
typeENDORSEMENT_SEAFARER: "የባህረኛ ማስተያየት (CoC / GOC)",
|
||||
typeENDORSEMENT_COC: "የCoC ማረጋገጫ",
|
||||
typeENDORSEMENT_GOC: "የGOC ማረጋገጫ",
|
||||
primary: "ዋና",
|
||||
@@ -87,8 +88,7 @@ export const am: Translations = {
|
||||
btcQueue: "የBTC ወረፋ",
|
||||
cocQueue: "የCoC ወረፋ",
|
||||
copQueue: "የCoP ወረፋ",
|
||||
endorsementCocQueue: "የCoC ማረጋገጫ ወረፋ",
|
||||
endorsementGocQueue: "የGOC ማረጋገጫ ወረፋ",
|
||||
endorsementQueue: "የማስተያየት ወረፋ",
|
||||
vesselRegistrations: "የመርከብ ምዝገባ",
|
||||
// vesselRegistrationReport: 'የምዝገባ ሪፖርት',
|
||||
vesselTransfers: "የመርከብ ባለቤትነት ዝውውር",
|
||||
|
||||
@@ -48,6 +48,7 @@ export const en = {
|
||||
typeVESSEL_OWNERSHIP_TRANSFER: 'Vessel Ownership Transfer',
|
||||
typeCERTIFICATE_OF_COMPETENCY: 'Certificate of Competency',
|
||||
typeCERTIFICATE_OF_PROFICIENCY: 'Certificate of Proficiency',
|
||||
typeENDORSEMENT_SEAFARER: 'Seafarer Endorsement (CoC / GOC)',
|
||||
typeENDORSEMENT_COC: 'CoC Endorsement',
|
||||
typeENDORSEMENT_GOC: 'GOC Endorsement',
|
||||
primary: 'Primary',
|
||||
@@ -87,8 +88,7 @@ export const en = {
|
||||
postWaiverQueue: 'Post-Waiver Queue',
|
||||
cocQueue: 'CoC Queue',
|
||||
copQueue: 'CoP Queue',
|
||||
endorsementCocQueue: 'CoC Endorsement Queue',
|
||||
endorsementGocQueue: 'GOC Endorsement Queue',
|
||||
endorsementQueue: 'Endorsement Queue',
|
||||
vesselRegistrations: 'Vessel Registration',
|
||||
vesselTransfers: 'Vessel Ownership Transfer',
|
||||
seafarerRegistry: 'Seafarer Registry',
|
||||
|
||||
@@ -101,8 +101,7 @@ export const NAV_SECTIONS: NavSection[] = [
|
||||
{ to: '/licence-review/type/CERTIFICATE_OF_PROFICIENCY', label: 'nav.copQueue', icon: IconShieldCheck, permissions: APPLICATION_QUEUE },
|
||||
{ to: '/seaman-book-queue', label: 'nav.seamanBookQueue', icon: IconBook2, permissions: APPLICATION_QUEUE },
|
||||
{ to: '/btc-queue', label: 'nav.btcQueue', icon: IconShieldCheck, permissions: APPLICATION_QUEUE },
|
||||
{ to: '/licence-review/type/ENDORSEMENT_COC', label: 'nav.endorsementCocQueue', icon: IconRubberStamp, permissions: APPLICATION_QUEUE },
|
||||
{ to: '/licence-review/type/ENDORSEMENT_GOC', label: 'nav.endorsementGocQueue', icon: IconRubberStamp, permissions: APPLICATION_QUEUE },
|
||||
{ to: '/licence-review/type/ENDORSEMENT_SEAFARER', label: 'nav.endorsementQueue', icon: IconRubberStamp, permissions: APPLICATION_QUEUE },
|
||||
{ to: '/sea-service-verification', label: 'nav.seaServiceVerification', icon: IconAnchor, permissions: [P.VERIFY_SEAFARER_RECORDS] },
|
||||
{ to: '/medical-verification', label: 'nav.medicalVerification', icon: IconHeart, permissions: [P.VERIFY_SEAFARER_RECORDS] },
|
||||
],
|
||||
|
||||
@@ -90,7 +90,11 @@ const router = createBrowserRouter([
|
||||
{ path: 'coc-queue', element: <Navigate to="/licence-review/type/CERTIFICATE_OF_COMPETENCY" replace /> },
|
||||
{ path: 'coc-queue/:id', element: <Navigate to="/licence-review" replace /> },
|
||||
// Endorsement review happens in the config-driven licence queue.
|
||||
{ path: 'endorsement-queue', element: <Navigate to="/licence-review/type/ENDORSEMENT_COC" replace /> },
|
||||
// CoC and GOC endorsement now share one combined type; the two old
|
||||
// type queues (ENDORSEMENT_COC / ENDORSEMENT_GOC) are left routing
|
||||
// through the generic `:typeCode` queue below rather than redirected,
|
||||
// so an application filed before the switch stays reachable there.
|
||||
{ path: 'endorsement-queue', element: <Navigate to="/licence-review/type/ENDORSEMENT_SEAFARER" replace /> },
|
||||
{ path: 'endorsement-queue/:id', element: <Navigate to="/licence-review" replace /> },
|
||||
{ path: 'medical-verification', element: guard([P.VERIFY_SEAFARER_RECORDS], <MedicalVerificationPage />) },
|
||||
{ path: 'sea-service-verification', element: guard([P.VERIFY_SEAFARER_RECORDS], <SeaServiceVerificationPage />) },
|
||||
|
||||
@@ -33,7 +33,14 @@ import { AdvancedTable, PageLoader, notify, useServerTable } from '@ema-platform
|
||||
import { useDateDisplayer } from '@ema-platform/shared';
|
||||
import { endorsementColumns } from './columns';
|
||||
|
||||
const ENDORSEMENT_TYPE_KEYS = ['ENDORSEMENT_COC', 'ENDORSEMENT_GOC'];
|
||||
// ENDORSEMENT_SEAFARER covers CoC and GOC together and is the only type new
|
||||
// applications file against; the other two stay listed so an application or
|
||||
// licence filed before the switch keeps showing up here.
|
||||
const ENDORSEMENT_TYPE_KEYS = [
|
||||
'ENDORSEMENT_SEAFARER',
|
||||
'ENDORSEMENT_COC',
|
||||
'ENDORSEMENT_GOC',
|
||||
];
|
||||
|
||||
function EligibilityItem({ ok, label }: { ok: boolean; label: string }) {
|
||||
return (
|
||||
@@ -132,21 +139,13 @@ export function EndorsementPage() {
|
||||
/>
|
||||
</List>
|
||||
</div>
|
||||
<Stack gap="xs">
|
||||
<Button
|
||||
rightSection={<IconArrowRight size={16} />}
|
||||
onClick={() => navigate('/licensing/ENDORSEMENT_COC/apply')}
|
||||
>
|
||||
{t('endorsement.endorseCoc', 'Endorse a CoC')}
|
||||
</Button>
|
||||
<Button
|
||||
variant="light"
|
||||
rightSection={<IconArrowRight size={16} />}
|
||||
onClick={() => navigate('/licensing/ENDORSEMENT_GOC/apply')}
|
||||
>
|
||||
{t('endorsement.endorseGoc', 'Endorse a GOC')}
|
||||
</Button>
|
||||
</Stack>
|
||||
<Button
|
||||
disabled={!registered}
|
||||
rightSection={<IconArrowRight size={16} />}
|
||||
onClick={() => navigate('/licensing/ENDORSEMENT_SEAFARER/apply')}
|
||||
>
|
||||
{t('endorsement.apply', 'Apply for an endorsement')}
|
||||
</Button>
|
||||
</Group>
|
||||
{!registered && (
|
||||
<Alert color="orange" mt="md" icon={<IconInfoCircle size={16} />}>
|
||||
|
||||
@@ -55,6 +55,10 @@ const MODE_FREE_TYPE_KEYS = [
|
||||
'VESSEL_OWNERSHIP_TRANSFER',
|
||||
'CERTIFICATE_OF_COMPETENCY',
|
||||
'CERTIFICATE_OF_PROFICIENCY',
|
||||
'ENDORSEMENT_SEAFARER',
|
||||
// Retired by ENDORSEMENT_SEAFARER but kept mode-free: an in-flight
|
||||
// application filed against one of these before the switch must still be
|
||||
// reachable to view, correct or resubmit.
|
||||
'ENDORSEMENT_COC',
|
||||
'ENDORSEMENT_GOC',
|
||||
'PRE_WAIVER',
|
||||
|
||||
@@ -16,10 +16,16 @@ import { OperationsFormContent } from "../../profile/components/OperationsFormCo
|
||||
* Seafarer goes to its own registration page, whose Identity Details step
|
||||
* collects the profile answers itself — no detour via `/profile`. Seafarer
|
||||
* wins when both are ticked; the other form is one nav click away.
|
||||
*
|
||||
* SEAFARER_REGISTRATION is listed before ENDORSEMENT_SEAFARER deliberately:
|
||||
* `nextStepFor` takes the first key that matches, and an applicant who ticked
|
||||
* both belongs in registration first — the endorsement application refuses
|
||||
* submission until that registration is accepted.
|
||||
*/
|
||||
const NEXT_STEP: Record<string, string> = {
|
||||
SEAFARER_REGISTRATION: "/seafarer-registration",
|
||||
VESSEL_REGISTRATION: "/vessel-registration",
|
||||
ENDORSEMENT_SEAFARER: "/endorsements",
|
||||
};
|
||||
|
||||
function nextStepFor(selectedKeys: string[]): string {
|
||||
|
||||
@@ -25,15 +25,21 @@ import { notify, ModalFooter } from '@ema-platform/ui';
|
||||
import { useDateDisplayer } from '@ema-platform/shared';
|
||||
|
||||
/**
|
||||
* Registrations an applicant makes for themselves rather than for a company.
|
||||
* Registrations and seafarer-only services an applicant declares for
|
||||
* themselves rather than for a company.
|
||||
*
|
||||
* Named explicitly rather than inferred from `requiresOperatorMode: false`,
|
||||
* because that flag is also false for things nobody declares up front — a
|
||||
* waiver is requested per shipment, not adopted as an identity.
|
||||
* ENDORSEMENT_SEAFARER belongs here for the same reason as the two
|
||||
* registrations: a seafarer requesting one is not declaring a logistics mode
|
||||
* of operation, and without this key `accountTypeFor` below would fall the
|
||||
* account through to the company-representative type instead of `SEAFARER`.
|
||||
*/
|
||||
const PERSONAL_REGISTRATION_KEYS = [
|
||||
'SEAFARER_REGISTRATION',
|
||||
'VESSEL_REGISTRATION',
|
||||
'ENDORSEMENT_SEAFARER',
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -56,6 +62,7 @@ function accountTypeFor(keys: string[]): string | null {
|
||||
}
|
||||
if (keys.includes('VESSEL_REGISTRATION')) return 'VESSEL_OWNER';
|
||||
if (keys.includes('SEAFARER_REGISTRATION')) return 'SEAFARER';
|
||||
if (keys.includes('ENDORSEMENT_SEAFARER')) return 'SEAFARER';
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -212,7 +219,7 @@ export function OperationsFormContent({
|
||||
{personalOptions.length > 0 && (
|
||||
<>
|
||||
<Text size="xs" fw={600} c="dimmed" mt="sm" tt="uppercase">
|
||||
Registering as an individual or vessel owner
|
||||
Registering or applying as an individual or vessel owner
|
||||
</Text>
|
||||
{personalOptions.map((type) => (
|
||||
<Checkbox
|
||||
|
||||
@@ -63,7 +63,7 @@ export const am: Translations = {
|
||||
certificates: 'የምስክር ወረቀቶች',
|
||||
seamanBook: 'የመርከበኛ መጽሐፍ እና BTC',
|
||||
btc: 'መሠረታዊ ሥልጠና ምስክር ወረቀት',
|
||||
endorsements: 'ማረጋገጫዎች',
|
||||
endorsements: 'የባህረኛ ማስተያየት',
|
||||
vesselRegistrations: 'የመርከብ ምዝገባ',
|
||||
vesselTransfers: 'የመርከብ ባለቤትነት ዝውውር',
|
||||
documents: 'ሰነዶቼ',
|
||||
@@ -1008,8 +1008,7 @@ export const am: Translations = {
|
||||
registered: "የተመዘገበ መርከበኛ ({{number}})",
|
||||
registrationRequired: "ንቁ የመርከበኛ ምዝገባ ያስፈልጋል",
|
||||
},
|
||||
endorseCoc: "CoC ያረጋግጡ",
|
||||
endorseGoc: "GOC ያረጋግጡ",
|
||||
apply: "ለማስተያየት ያመልክቱ",
|
||||
registrationNotice: {
|
||||
prefix: "መጀመሪያ የ",
|
||||
link: "መርከበኛ ምዝገባዎን",
|
||||
|
||||
@@ -63,7 +63,7 @@ export const en = {
|
||||
certificates: 'Certificates',
|
||||
seamanBook: 'SeamanBook and BTC',
|
||||
btc: 'Basic Training Certificate',
|
||||
endorsements: 'Endorsements',
|
||||
endorsements: 'Endorsement Seafarer',
|
||||
vesselRegistrations: 'Vessel Registration',
|
||||
vesselTransfers: 'Vessel Transfers',
|
||||
documents: 'My Documents',
|
||||
@@ -1010,8 +1010,7 @@ export const en = {
|
||||
registered: 'Registered seafarer ({{number}})',
|
||||
registrationRequired: 'Active seafarer registration required',
|
||||
},
|
||||
endorseCoc: 'Endorse a CoC',
|
||||
endorseGoc: 'Endorse a GOC',
|
||||
apply: 'Apply for an endorsement',
|
||||
registrationNotice: {
|
||||
prefix: 'Complete your',
|
||||
link: 'seafarer registration',
|
||||
|
||||
@@ -140,7 +140,7 @@ const NAV_SECTIONS: { label?: string; items: PortalNavItem[] }[] = [
|
||||
},
|
||||
{
|
||||
to: "/endorsements",
|
||||
label: "Endorsements",
|
||||
label: "Endorsement Seafarer",
|
||||
i18nKey: "nav.endorsements",
|
||||
icon: IconRubberStamp,
|
||||
permissions: [P.VIEW_OWN_CERTIFICATES],
|
||||
|
||||
Reference in New Issue
Block a user