From 6901e985fc972d1dff99be388daa2bd43adec9a1 Mon Sep 17 00:00:00 2001 From: Nati Date: Thu, 20 Aug 2026 12:41:46 +0000 Subject: [PATCH] feat: update exam stage actions and payment flow - Refactor ExamStageActions component to handle eligibility payment and retake exam actions. - Update MyApplicationsPage to use new retakeExam mutation instead of requestExamPayment. - Modify licensing API to include retakeExam mutation for handling exam fee requests for failed candidates. - Adjust mock data to reflect new application statuses and ensure consistency in eligibility and exam payment states. - Update licensing helpers to include new status labels and colors for eligibility payment states. - Revise licensing types to replace ELIGIBILITY_APPROVED with ELIGIBILITY_PAYMENT_PENDING and ELIGIBILITY_PAID for clarity in application status flow. --- .../features/license-review/config/actions.ts | 21 +- .../pages/LicenseQueuePage/actions.tsx | 6 +- .../pages/LicenseQueuePage/index.tsx | 14 + .../pages/LicenseReviewPage/index.tsx | 76 ++ .../certificates/pages/CertificatesPage.tsx | 32 +- .../certificates/pages/CoCApplicationPage.tsx | 1094 ----------------- .../licensing/components/ExamStageActions.tsx | 53 +- .../pages/MyApplicationsPage/actions.tsx | 9 +- .../pages/MyApplicationsPage/index.tsx | 15 +- apps/portal/src/app/router.tsx | 12 +- libs/api/src/lib/base-api/mock-data.ts | 2 +- .../lib/features/licensing/licensing-api.ts | 31 +- .../features/licensing/licensing.helpers.ts | 12 +- .../lib/features/licensing/licensing.types.ts | 8 +- 14 files changed, 231 insertions(+), 1154 deletions(-) delete mode 100644 apps/portal/src/app/features/certificates/pages/CoCApplicationPage.tsx diff --git a/apps/backoffice/src/app/features/license-review/config/actions.ts b/apps/backoffice/src/app/features/license-review/config/actions.ts index c5532deec..8653938e7 100644 --- a/apps/backoffice/src/app/features/license-review/config/actions.ts +++ b/apps/backoffice/src/app/features/license-review/config/actions.ts @@ -29,6 +29,7 @@ export type ActionId = | 'request-adjustment' | 'reject' | 'schedule-exam' + | 'record-exam-outcome' | 'confirm-payment' | 'schedule-issuance' | 'issue-certificate' @@ -68,7 +69,9 @@ export const ACTIONS: ActionDefinition[] = [ id: 'claim', tier: 'workflow', labelKey: 'review.actions.claim', - from: ['SUBMITTED'], + // Mirrors the CLAIM transition's `from` list: an examined cert (CoC/CoP) + // sits in ELIGIBILITY_PAID once its eligibility fee clears, not SUBMITTED. + from: ['SUBMITTED', 'ELIGIBILITY_PAID'], permissions: ['can:claim:license-application'], emphasis: 'light', }, @@ -198,7 +201,21 @@ export const ACTIONS: ActionDefinition[] = [ // Only after the examination fee clears — scheduling an unpaid candidate // is what the EXAM_PAID gate exists to prevent. from: ['EXAM_PAID'], - permissions: ['can:schedule:exam-candidate'], + // Matches the controller's guard on `:id/exam-scheduled` + // (`LICENSE_PERMISSIONS.MANAGE_EXAMS`) — the previous string didn't + // correspond to any real permission constant, so this button could never + // actually be granted to anyone. + permissions: ['can:manage:exams'], + emphasis: 'filled', + color: 'cyan', + }, + { + id: 'record-exam-outcome', + tier: 'primary', + labelKey: 'review.actions.recordExamOutcome', + // Only once the candidate has actually sat the exam. + from: ['EXAM_SCHEDULED'], + permissions: ['can:publish:exam-result'], emphasis: 'filled', color: 'cyan', }, diff --git a/apps/backoffice/src/app/features/license-review/pages/LicenseQueuePage/actions.tsx b/apps/backoffice/src/app/features/license-review/pages/LicenseQueuePage/actions.tsx index 4362f2416..bd3eb3753 100644 --- a/apps/backoffice/src/app/features/license-review/pages/LicenseQueuePage/actions.tsx +++ b/apps/backoffice/src/app/features/license-review/pages/LicenseQueuePage/actions.tsx @@ -22,7 +22,11 @@ export function licenseQueueActionsColumn( cell: ({ row }) => handlers.claimable !== false && row.original.assignedOfficerId === null && - row.original.status === "SUBMITTED" ? ( + // Mirrors the CLAIM transition's `from` list: an examined cert + // (CoC/CoP) sits in ELIGIBILITY_PAID once its eligibility fee clears, + // not SUBMITTED. + (row.original.status === "SUBMITTED" || + row.original.status === "ELIGIBILITY_PAID") ? ( (); const [findings, setFindings] = useState(""); const [checklist, setChecklist] = useState< Record @@ -490,6 +494,11 @@ export function LicenseReviewPage() { case "schedule-exam": setScheduleExamOpen(true); return; + // Pass/fail plus an optional score, same reasoning as schedule-exam: + // needs its own inputs before anything is sent. + case "record-exam-outcome": + setExamOutcomeOpen(true); + return; case "copy-link": navigator.clipboard.writeText(window.location.href); notifications.show({ @@ -1173,6 +1182,73 @@ export function LicenseReviewPage() { }} /> + setExamOutcomeOpen(false)} + title={t("review.actions.recordExamOutcome", "Record exam outcome")} + > + + + {t( + "review.examOutcome.intro", + "Record the published result. A pass makes the certificate fee due; a fail leaves the application open for a retake.", + )} + + setExamScore(typeof v === "number" ? v : undefined)} + min={0} + /> + + + run( + async () => { + await recordExamOutcome({ + id, + passed: true, + score: examScore, + }).unwrap(); + setExamOutcomeOpen(false); + setExamScore(undefined); + }, + t("review.done.examPassed", "Exam result recorded — passed"), + ) + } + > + + + + run( + async () => { + await recordExamOutcome({ + id, + passed: false, + score: examScore, + }).unwrap(); + setExamOutcomeOpen(false); + setExamScore(undefined); + }, + t("review.done.examFailed", "Exam result recorded — not passed"), + ) + } + > + + + + + + setInspectionOpen(false)} diff --git a/apps/portal/src/app/features/certificates/pages/CertificatesPage.tsx b/apps/portal/src/app/features/certificates/pages/CertificatesPage.tsx index 9b7295f94..9e8879996 100644 --- a/apps/portal/src/app/features/certificates/pages/CertificatesPage.tsx +++ b/apps/portal/src/app/features/certificates/pages/CertificatesPage.tsx @@ -72,7 +72,8 @@ const STATUS_COLOR: Record = { RESUBMIT_REQUIRED: 'orange', INSPECTION_PENDING: 'grape', INSPECTION_COMPLETED: 'grape', - ELIGIBILITY_APPROVED: 'teal', + ELIGIBILITY_PAYMENT_PENDING: 'orange', + ELIGIBILITY_PAID: 'blue', EXAM_PAYMENT_PENDING: 'orange', EXAM_PAID: 'blue', EXAM_SCHEDULED: 'indigo', @@ -220,14 +221,25 @@ export function CertificatesPage() { {/* Tooltip needs a hoverable child even while the button itself is disabled, so the reason still shows on hover. */} - + + + + @@ -269,7 +281,7 @@ export function CertificatesPage() { My Applications {applications.length === 0 ? ( }> - No active CoC/CoP applications. Click "Apply for CoC / CoP" to start. + No active CoC/CoP applications. Click "Apply for CoC" or "Apply for CoP" to start. ) : ( diff --git a/apps/portal/src/app/features/certificates/pages/CoCApplicationPage.tsx b/apps/portal/src/app/features/certificates/pages/CoCApplicationPage.tsx deleted file mode 100644 index 667077cc3..000000000 --- a/apps/portal/src/app/features/certificates/pages/CoCApplicationPage.tsx +++ /dev/null @@ -1,1094 +0,0 @@ -import { useState } from 'react'; -import { useNavigate } from 'react-router-dom'; -import { - Alert, - Badge, - Box, - Button, - Card, - Collapse, - Divider, - FileInput, - Group, - List, - Paper, - Select, - SimpleGrid, - Stack, - Stepper, - Text, - TextInput, - ThemeIcon, - Title, -} from '@mantine/core'; -import { - IconAlertCircle, - IconArrowLeft, - IconArrowRight, - IconAward, - IconBook2, - IconCheck, - IconChevronDown, - IconChevronUp, - IconCircleCheck, - IconClock, - IconCreditCard, - IconFileDescription, - IconInfoCircle, - IconShieldCheck, - IconUpload, -} from '@tabler/icons-react'; -import { notify } from '@ema-platform/ui'; - -// --------------------------------------------------------------------------- -// STCW certificate catalog — sourced from the STCW Convention & Code -// --------------------------------------------------------------------------- - -export type Department = 'deck' | 'engine' | 'electro' | 'catering'; - -interface Competency { - area: string; - items: string[]; -} - -interface CertDef { - id: string; - dept: Department[]; - type: 'CoC' | 'CoP'; - stcwRef: string; - level: 'Support' | 'Operational' | 'Management'; - label: string; - eligibleRanks: string; - prerequisiteId: string | null; // must hold this cert first - minAge: number; - seaService: string; // human-readable sea-service requirement - mandatoryTraining: string[]; - competencies: Competency[]; - validityYears: number; - revalidationRule: string; - notes: string; -} - -const CERT_CATALOG: CertDef[] = [ - // ── DECK ────────────────────────────────────────────────────────────────── - { - id: 'rfpnw', - dept: ['deck'], - type: 'CoP', - stcwRef: 'Reg. II/4 — Code A-II/4', - level: 'Support', - label: 'Rating Forming Part of a Navigational Watch (RFPNW)', - eligibleRanks: 'Lookout, Helmsman, Watch Rating', - prerequisiteId: null, - minAge: 16, - seaService: 'At least 6 months approved sea-service/training; OR special training plus 2 months approved sea service with direct supervision.', - mandatoryTraining: ['Basic Safety Training (STCW VI/1)', 'Approved national RFPNW programme'], - competencies: [ - { area: 'Navigational Watch', items: ['Keep a safe lookout by sight and hearing', 'Helm orders and steering', 'Handover/relief procedures', 'Vessel communications and signals', 'Anchor watch routines'] }, - { area: 'Safety', items: ['Personal survival, fire prevention & fighting, elementary first aid, personal safety (VI/1)', 'Pollution prevention awareness'] }, - ], - validityYears: 5, - revalidationRule: 'No mandatory STCW periodic revalidation; national practice varies. Maintain Basic Safety evidence every 5 years (PST & FPFF elements).', - notes: 'Duties must be directly supervised by a qualified officer.', - }, - { - id: 'ab-deck', - dept: ['deck'], - type: 'CoP', - stcwRef: 'Reg. II/5 — Code A-II/5', - level: 'Support', - label: 'Able Seafarer Deck (AB)', - eligibleRanks: 'Able Seafarer, Deck Rating, Bosun candidate', - prerequisiteId: 'rfpnw', - minAge: 18, - seaService: 'Must already hold RFPNW CoP. Then: at least 18 months approved deck sea service while qualified as RFPNW; OR at least 12 months plus approved AB training (IMO MC 7.10).', - mandatoryTraining: ['RFPNW CoP (prerequisite)', 'Approved AB Deck training — IMO Model Course 7.10'], - competencies: [ - { area: 'Navigation', items: ['Maintain a safe navigational watch', 'Use of navigational aids including ECDIS', 'Determine compass error by celestial and terrestrial means', 'Contribute to monitoring and controlling vessel position'] }, - { area: 'Cargo Operations', items: ['Handle, stow and secure cargo', 'Use deck equipment and machinery', 'Rig and operate equipment used in cargo operations'] }, - { area: 'Ship Operations', items: ['Mooring and anchoring operations', 'Maintenance of the ship and equipment', 'Fuel and ballast transfers awareness'] }, - { area: 'Safety & Emergency', items: ['Operate survival craft and rescue boats', 'Fight and extinguish fires', 'Apply first aid', 'Contribute to prevention of marine pollution'] }, - ], - validityYears: 5, - revalidationRule: 'No mandatory STCW stand-alone periodic revalidation; maintain BST evidence every 5 years.', - notes: 'IMO Model Course 7.10 is the implementation guidance for this CoP.', - }, - { - id: 'oicnw', - dept: ['deck'], - type: 'CoC', - stcwRef: 'Reg. II/1 — Code A-II/1', - level: 'Operational', - label: 'Officer in Charge of a Navigational Watch — 500 GT or more (OICNW)', - eligibleRanks: 'Third Officer, Second Officer, Watch-keeping Officer', - prerequisiteId: null, - minAge: 18, - seaService: 'Option A: Approved training programme with approved Training Record Book plus at least 12 months approved sea service (including 6 months bridge watchkeeping under supervision). Option B: 36 months approved sea service (including 6 months supervised bridge watchkeeping).', - mandatoryTraining: [ - 'Approved deck officer education programme (or equivalent sea service)', - 'GMDSS training applicable under Chapter IV', - 'Bridge Resource Management — IMO MC 1.22', - 'ECDIS familiarization — IMO MC 1.27', - 'Leadership and Teamwork — IMO MC 1.39', - ], - competencies: [ - { area: 'Navigation at Operational Level', items: ['Plan and conduct a passage; determine position', 'Maintain a safe watch under COLREGS', 'Use of radar/ARPA, ECDIS and all bridge equipment', 'Respond to navigational emergencies'] }, - { area: 'Cargo Handling & Stowage', items: ['Cargo handling at operational level', 'Stowage planning and securing awareness', 'Load and stability calculations'] }, - { area: 'Control of Ship Operations', items: ['Monitor and control compliance with legal requirements', 'Prevent, control and fight fire', 'Operate life-saving appliances'] }, - { area: 'Marine Engineering', items: ['Basic knowledge of main and auxiliary machinery', 'Knowledge of electrical systems'] }, - { area: 'Radio Communication', items: ['Transmit and receive voice messages under GMDSS', 'Distress and safety procedures'] }, - { area: 'Leadership & Teamwork', items: ['Assign and prioritize resources', 'Effective communication with bridge team', 'Apply task and workload management'] }, - ], - validityYears: 5, - revalidationRule: 'Revalidation every 5 years under Regulation I/11: demonstrate continued competence and hold valid medical certificate.', - notes: 'Near-coastal limitations may restrict the scope of the CoC. Assessment against table A-II/1 methods and criteria.', - }, - { - id: 'chief-mate-500', - dept: ['deck'], - type: 'CoC', - stcwRef: 'Reg. II/2 — Code A-II/2', - level: 'Management', - label: 'Chief Mate — Ships 500–3,000 GT (Management Level)', - eligibleRanks: 'Chief Mate on ships 500–3,000 GT', - prerequisiteId: 'oicnw', - minAge: 18, - seaService: 'Must hold OICNW CoC. Then: at least 12 months approved sea service as an officer in charge of a navigational watch.', - mandatoryTraining: ['OICNW CoC (prerequisite)', 'Leadership and Managerial Skills — IMO MC 1.40', 'Approved management-level education programme — IMO MC 7.01'], - competencies: [ - { area: 'Navigation at Management Level', items: ['Plan voyages and conduct navigation at management level', 'Determine vessel position using all available means', 'Monitor bridge team performance'] }, - { area: 'Cargo Handling at Management Level', items: ['Plan and ensure safe loading, stowage, securing, care and unloading of cargo', 'Stability, trim and stress calculations and control'] }, - { area: 'Control of Ship Operations', items: ['Monitor and control compliance with legislative requirements', 'Ensure the safety of personnel, vessel, cargo and marine environment'] }, - { area: 'Leadership & Management', items: ['Manage and supervise deck department', 'Initiate and manage emergency procedures', 'Manage crew performance and well-being'] }, - ], - validityYears: 5, - revalidationRule: 'Revalidation every 5 years under Regulation I/11.', - notes: 'Assessment against table A-II/2.', - }, - { - id: 'master-500', - dept: ['deck'], - type: 'CoC', - stcwRef: 'Reg. II/2 — Code A-II/2', - level: 'Management', - label: 'Master — Ships 500–3,000 GT', - eligibleRanks: 'Master on ships 500–3,000 GT', - prerequisiteId: 'chief-mate-500', - minAge: 18, - seaService: 'Must hold Chief Mate CoC. Then: at least 36 months approved sea service in the deck department, reducible to 24 months if 12 months served as chief mate.', - mandatoryTraining: ['Chief Mate CoC (prerequisite)', 'Leadership and Managerial Skills — IMO MC 1.40', 'Approved management-level programme — IMO MC 7.01'], - competencies: [ - { area: 'Command & Navigation', items: ['Full command responsibility for the safety of ship, crew, cargo and the environment', 'Management-level passage planning and execution', 'Emergency command during distress, fire, collision, grounding'] }, - { area: 'Cargo & Stability Management', items: ['Full responsibility for cargo operations and stability', 'Damage stability and emergency procedures'] }, - { area: 'Legal & Administrative', items: ['Compliance with international and flag-state law', 'Ship documentation, protest, log maintenance', 'Crew management and discipline'] }, - { area: 'Ship Management', items: ['Operate and manage all shipboard systems at management level', 'Crisis and emergency management'] }, - ], - validityYears: 5, - revalidationRule: 'Revalidation every 5 years under Regulation I/11.', - notes: 'Highest deck certificate. Assessment against table A-II/2.', - }, - { - id: 'master-3000', - dept: ['deck'], - type: 'CoC', - stcwRef: 'Reg. II/2 — Code A-II/2', - level: 'Management', - label: 'Master / Chief Mate — Ships 3,000 GT or more', - eligibleRanks: 'Master, Chief Mate on ships 3,000 GT or more', - prerequisiteId: 'master-500', - minAge: 20, - seaService: 'Must hold Master 500–3,000 GT CoC with adequate service on vessels of 3,000 GT or more, as determined by the Administration.', - mandatoryTraining: ['Master 500–3,000 GT CoC (prerequisite)', 'Approved management-level programme — IMO MC 7.01'], - competencies: [ - { area: 'All Master competencies', items: ['Same as Master 500–3,000 GT but for vessels of 3,000 GT or more and all trade areas'] }, - ], - validityYears: 5, - revalidationRule: 'Revalidation every 5 years under Regulation I/11.', - notes: 'Endorsement may be required for near-coastal limitations.', - }, - - // ── ENGINE ──────────────────────────────────────────────────────────────── - { - id: 'rfpew', - dept: ['engine'], - type: 'CoP', - stcwRef: 'Reg. III/4 — Code A-III/4', - level: 'Support', - label: 'Rating Forming Part of an Engineering Watch (RFPEW)', - eligibleRanks: 'Engine-room Rating, Watchkeeper', - prerequisiteId: null, - minAge: 16, - seaService: 'At least 6 months approved sea-service/training; OR special training plus 2 months approved sea service.', - mandatoryTraining: ['Basic Safety Training (STCW VI/1)', 'Approved RFPEW national programme — IMO MC 7.09'], - competencies: [ - { area: 'Engineering Watch Support', items: ['Understand orders and be understood', 'Use appropriate tools and equipment safely', 'Handover/relief procedures in engine room', 'Operate machinery under supervision', 'Maintain engineering watch routines'] }, - { area: 'Safety', items: ['Personal survival, fire prevention & fighting, elementary first aid, personal safety (VI/1)', 'Pollution prevention awareness'] }, - ], - validityYears: 5, - revalidationRule: 'No mandatory STCW periodic revalidation; maintain BST evidence every 5 years.', - notes: 'IMO Model Course 7.09 is the implementation guidance.', - }, - { - id: 'ab-engine', - dept: ['engine'], - type: 'CoP', - stcwRef: 'Reg. III/5 — Code A-III/5', - level: 'Support', - label: 'Able Seafarer Engine (AB Engine)', - eligibleRanks: 'AB Engine, Motorman, Senior Rating', - prerequisiteId: 'rfpew', - minAge: 18, - seaService: 'Must hold RFPEW CoP. Then: at least 12 months approved sea service in engine department; OR at least 6 months plus approved training (IMO MC 7.16).', - mandatoryTraining: ['RFPEW CoP (prerequisite)', 'Approved AB Engine training — IMO Model Course 7.16'], - competencies: [ - { area: 'Marine Engineering', items: ['Monitor and control propulsion machinery and auxiliaries', 'Maintain and repair mechanical systems', 'Safe use of workshop tools and equipment'] }, - { area: 'Electrical & Control Systems', items: ['Monitor electrical systems and distribution panels', 'Basic fault-finding in electrical/electronic control circuits'] }, - { area: 'Ship Operations', items: ['Contribute to fuelling operations', 'Bilge and ballast system operations', 'Safe transfer of liquids'] }, - { area: 'Safety & Emergency', items: ['Respond to engineering emergencies', 'Fire prevention and firefighting in engine room', 'Pollution prevention from engineering systems'] }, - ], - validityYears: 5, - revalidationRule: 'No mandatory STCW stand-alone periodic revalidation; maintain BST evidence every 5 years.', - notes: 'IMO Model Course 7.16 is the implementation guidance.', - }, - { - id: 'oicew', - dept: ['engine'], - type: 'CoC', - stcwRef: 'Reg. III/1 — Code A-III/1', - level: 'Operational', - label: 'Officer in Charge of an Engineering Watch (OICEW)', - eligibleRanks: 'Junior Engineer, Third/Second Engineer (watchkeeping)', - prerequisiteId: null, - minAge: 18, - seaService: 'Option A: Approved engineer training programme with approved Training Record Book plus at least 12 months sea service (including 6 months supervised engine-room watchkeeping). Option B: 36 months combined workshop/sea service, at least 30 months at sea with 6 months supervised watchkeeping.', - mandatoryTraining: [ - 'Approved engineering education programme (or equivalent)', - 'Engine-Room Resource Management — IMO MC 7.17', - 'Leadership and Teamwork — IMO MC 1.39', - ], - competencies: [ - { area: 'Marine Engineering at Operational Level', items: ['Safe engineering watch; monitor propulsion plant and auxiliaries', 'Operate, monitor and control boiler systems', 'Operate fuel, lubricating oil and bilge/ballast systems'] }, - { area: 'Electrical, Electronic & Control Engineering', items: ['Operate generators, switchboards and distribution systems', 'Maintain and repair electrical machinery', 'Monitor and operate automated systems'] }, - { area: 'Maintenance & Repair', items: ['Maintain and repair machinery and equipment', 'Perform planned maintenance', 'Safe use of workshop and maintenance tools'] }, - { area: 'Controlling Ship Operations', items: ['Apply pollution-prevention procedures', 'Maintain safety of personnel and machinery'] }, - { area: 'Leadership & Teamwork', items: ['Assign and prioritize resources in engine room', 'Effective communication with engineering team', 'Task and workload management'] }, - ], - validityYears: 5, - revalidationRule: 'Revalidation every 5 years under Regulation I/11.', - notes: 'Assessment against table A-III/1. Propulsion-type limitations may be endorsed nationally.', - }, - { - id: 'second-engineer-750', - dept: ['engine'], - type: 'CoC', - stcwRef: 'Reg. III/3 — Code A-III/3', - level: 'Management', - label: 'Second Engineer Officer — Ships 750–3,000 kW', - eligibleRanks: 'Second Engineer on ships 750–3,000 kW', - prerequisiteId: 'oicew', - minAge: 18, - seaService: 'Must hold OICEW CoC. Then: at least 12 months approved sea service as an assistant or qualified engineer officer.', - mandatoryTraining: ['OICEW CoC (prerequisite)', 'Leadership and Managerial Skills — IMO MC 1.40', 'Approved management engineering programme — IMO MC 7.02'], - competencies: [ - { area: 'Management of Engine Room', items: ['Plan and schedule maintenance of propulsion plant and auxiliaries', 'Manage fuel, stores and spare parts'] }, - { area: 'Electrical Engineering at Management Level', items: ['Manage electrical systems including emergency power', 'High-voltage systems safety awareness'] }, - { area: 'Leadership & Management', items: ['Supervise and manage engine department personnel', 'Initiate and implement emergency procedures affecting engineering'] }, - ], - validityYears: 5, - revalidationRule: 'Revalidation every 5 years under Regulation I/11.', - notes: 'Assessment against table A-III/3.', - }, - { - id: 'chief-engineer-750', - dept: ['engine'], - type: 'CoC', - stcwRef: 'Reg. III/3 — Code A-III/3', - level: 'Management', - label: 'Chief Engineer Officer — Ships 750–3,000 kW', - eligibleRanks: 'Chief Engineer on ships 750–3,000 kW', - prerequisiteId: 'second-engineer-750', - minAge: 18, - seaService: 'Must hold Second Engineer CoC (750–3,000 kW). Then: at least 24 months approved sea service, of which at least 12 months served as qualified second engineer officer.', - mandatoryTraining: ['Second Engineer CoC (prerequisite)', 'Leadership and Managerial Skills — IMO MC 1.40'], - competencies: [ - { area: 'Full Engineering Department Management', items: ['Full responsibility for engineering plant operations, maintenance and repair', 'Fuel, lubricant and consumable management', 'Budget, records and documentation management'] }, - { area: 'Ship Operation & Safety', items: ['Ensure compliance with all engineering-related legal requirements', 'Emergency equipment maintenance readiness', 'Environmental compliance'] }, - ], - validityYears: 5, - revalidationRule: 'Revalidation every 5 years under Regulation I/11.', - notes: 'A Second Engineer qualified for 3,000 kW+ may serve as Chief on ships below 3,000 kW if endorsed.', - }, - { - id: 'second-engineer-3000', - dept: ['engine'], - type: 'CoC', - stcwRef: 'Reg. III/2 — Code A-III/2', - level: 'Management', - label: 'Second Engineer Officer — Ships 3,000 kW or more', - eligibleRanks: 'Second Engineer on ships 3,000 kW or more', - prerequisiteId: 'chief-engineer-750', - minAge: 18, - seaService: 'Must hold OICEW CoC. Then: at least 12 months approved sea service as a qualified engineer officer.', - mandatoryTraining: ['OICEW CoC (prerequisite)', 'Leadership and Managerial Skills — IMO MC 1.40', 'Approved management engineering programme — IMO MC 7.02'], - competencies: [ - { area: 'Management-Level Marine Engineering (≥3,000 kW)', items: ['Plan, operate and maintain large propulsion plants', 'High-voltage management systems', 'Control engineering resource management at management level'] }, - ], - validityYears: 5, - revalidationRule: 'Revalidation every 5 years under Regulation I/11.', - notes: 'Assessment against table A-III/2.', - }, - { - id: 'chief-engineer-3000', - dept: ['engine'], - type: 'CoC', - stcwRef: 'Reg. III/2 — Code A-III/2', - level: 'Management', - label: 'Chief Engineer Officer — Ships 3,000 kW or more', - eligibleRanks: 'Chief Engineer on ships 3,000 kW or more', - prerequisiteId: 'second-engineer-3000', - minAge: 18, - seaService: 'Must hold Second Engineer (3,000 kW) CoC. Then: at least 36 months approved sea service, reducible to 24 months if 12 months served as second engineer.', - mandatoryTraining: ['Second Engineer (≥3,000 kW) CoC (prerequisite)', 'Leadership and Managerial Skills — IMO MC 1.40'], - competencies: [ - { area: 'Full Engineering Department — Large Ships', items: ['All competencies of Second Engineer plus full command of engineering department', 'Interface with shore management on technical matters', 'Crew resource management and team leadership'] }, - ], - validityYears: 5, - revalidationRule: 'Revalidation every 5 years under Regulation I/11.', - notes: 'Highest engine department certificate.', - }, - - // ── ELECTRO-TECHNICAL ───────────────────────────────────────────────────── - { - id: 'etr', - dept: ['electro'], - type: 'CoP', - stcwRef: 'Reg. III/7 — Code A-III/7', - level: 'Support', - label: 'Electro-Technical Rating (ETR)', - eligibleRanks: 'ETR, Electrician Rating', - prerequisiteId: null, - minAge: 18, - seaService: 'Either 12 months approved sea-service training/experience; OR approved training with at least 6 months sea service; OR technical qualifications meeting A-III/7 plus at least 3 months approved sea service.', - mandatoryTraining: ['Basic Safety Training (STCW VI/1)', 'Approved ETR training — IMO Model Course 7.15'], - competencies: [ - { area: 'Electrical Systems', items: ['Contribute to maintenance and repair of electrical/electronic systems', 'Operate and monitor electrical distribution panels', 'Cable and wiring maintenance'] }, - { area: 'Electronic & Control', items: ['Assist in maintenance of instrumentation and control systems', 'Basic fault identification in electronic circuits'] }, - { area: 'Safety & Environment', items: ['Prevent and extinguish fires (especially electrical fires)', 'Basic electrical safety; isolation and lock-out procedures', 'Pollution prevention awareness'] }, - ], - validityYears: 5, - revalidationRule: 'No mandatory STCW stand-alone periodic revalidation; maintain BST evidence every 5 years.', - notes: 'IMO Model Course 7.15 is the implementation guidance.', - }, - { - id: 'eto', - dept: ['electro'], - type: 'CoC', - stcwRef: 'Reg. III/6 — Code A-III/6', - level: 'Operational', - label: 'Electro-Technical Officer (ETO)', - eligibleRanks: 'Electro-Technical Officer', - prerequisiteId: 'etr', - minAge: 18, - seaService: 'Option A: Approved programme with Training Record Book plus at least 12 months sea service (at least 6 months in engine department). Option B: 36 months combined workshop/sea service, at least 30 months at sea in engine department.', - mandatoryTraining: [ - 'ETR CoP (prerequisite)', - 'Approved ETO programme — IMO Model Course 7.08', - 'Leadership and Teamwork — IMO MC 1.39', - ], - competencies: [ - { area: 'Electrical Systems at Operational Level', items: ['Monitor and control main electrical power systems', 'Operate and maintain generators, transformers, switchboards', 'High-voltage system safety and control', 'Emergency power systems'] }, - { area: 'Electronic Systems', items: ['Maintain and repair navigation and communication electronic systems', 'Instrumentation and measuring systems maintenance', 'Computer systems and ship networks management'] }, - { area: 'Automation & Control', items: ['Monitor and operate automated control systems', 'Fault diagnosis in electronic/control systems', 'Maintain programmable controllers and automation'] }, - { area: 'Maintenance & Repair', items: ['Plan and execute planned maintenance schedules', 'Safe use of test equipment and diagnostic tools'] }, - { area: 'Leadership & Teamwork', items: ['Communicate effectively with electro-technical team', 'Assign tasks and manage workload', 'Supervise ETR and other ratings'] }, - ], - validityYears: 5, - revalidationRule: 'Revalidation every 5 years under Regulation I/11.', - notes: 'Assessment against table A-III/6. Some administrations may specify high-voltage endorsements.', - }, - - // ── CATERING ───────────────────────────────────────────────────────────── - // STCW does not create department-specific CoC/CoP for catering. - // Catering seafarers qualify through basic safety + passenger-ship training. - // Ship's Cook is an MLC qualification. - { - id: 'basic-safety-catering', - dept: ['catering'], - type: 'CoP', - stcwRef: 'Reg. VI/1 — Code A-VI/1', - level: 'Support', - label: 'Basic Safety Training for Catering Personnel', - eligibleRanks: 'All catering crew', - prerequisiteId: null, - minAge: 16, - seaService: 'No sea-service prerequisite; training programme completion required.', - mandatoryTraining: [ - 'Personal Survival Techniques — IMO MC 1.19', - 'Fire Prevention and Fire Fighting — IMO MC 1.20', - 'Elementary First Aid — IMO MC 1.13', - 'Personal Safety and Social Responsibilities — IMO MC 1.21', - ], - competencies: [ - { area: 'Personal Survival Techniques', items: ['Don and use a lifejacket', 'Survive in the water', 'Board a liferaft from water', 'Take initial actions upon abandoning ship'] }, - { area: 'Fire Prevention & Fighting', items: ['Minimize risk of fire', 'Operate a fire extinguisher', 'Apply precautions when working with flammable materials'] }, - { area: 'Elementary First Aid', items: ['Administer first aid for burns, wounds, fractures', 'Perform CPR', 'Recognize and respond to medical emergencies'] }, - { area: 'Personal Safety & Social Responsibilities', items: ['Follow safe working practices', 'Report accidents and hazards', 'Environmental protection awareness'] }, - ], - validityYears: 5, - revalidationRule: 'PST and FPFF competence evidence required every 5 years (STCW A-VI/1). EFA and PSSR do not carry the same explicit 5-year refresh mandate.', - notes: 'Note: STCW does not create a catering-specific CoC or CoP. The Ship\'s Cook qualification is governed by MLC 2006 Standard A3.2 (national law), not STCW.', - }, - { - id: 'passenger-direct-service', - dept: ['catering'], - type: 'CoP', - stcwRef: 'Reg. V/2 para. 6 — Code A-V/2 para. 2', - level: 'Support', - label: 'Passenger Direct-Service Training', - eligibleRanks: 'Catering staff serving passengers on passenger ships', - prerequisiteId: 'basic-safety-catering', - minAge: 16, - seaService: 'No specific sea-service requirement; approved training completion required.', - mandatoryTraining: ['Basic Safety Training (prerequisite)', 'Passenger direct-service approved training — IMO MC 1.44'], - competencies: [ - { area: 'Passenger Communication & Assistance', items: ['Communicate emergency instructions to passengers', 'Demonstrate use of life-saving appliances', 'Assist passengers during embarkation, disembarkation and muster', 'Provide assistance to passengers with special needs'] }, - ], - validityYears: 5, - revalidationRule: 'Refresher/evidence every 5 years.', - notes: 'Required for catering personnel directly serving passengers on passenger ships.', - }, -]; - -// --------------------------------------------------------------------------- -// Mock — what the seafarer currently holds (simulated from profile) -// --------------------------------------------------------------------------- -const MOCK_PROFILE = { - department: 'deck' as Department, - seamanBookNo: 'SB-2024-0001', - photoRef: 'Photo on file (Passport Size)', - heldCertIds: ['rfpnw', 'ab-deck'], // already issued certificates - medicalExpired: false, // true = medical cert expired → user must upload fresh one -}; - -// --------------------------------------------------------------------------- -// Documents: sea service + one upload per competency area + TRB + optional medical -// --------------------------------------------------------------------------- -interface DocSlot { key: string; label: string; description: string; required: boolean; isCompetency?: boolean } - -const FIRST_CERT_IDS = new Set(['rfpnw', 'rfpew', 'etr', 'basic-safety-catering']); - -function buildDocSlots(cert: CertDef | null, medicalExpired: boolean): DocSlot[] { - if (!cert) return []; - - const isFirst = FIRST_CERT_IDS.has(cert.id); - - const slots: DocSlot[] = [ - { - key: 'sea-service', - label: 'Sea Service Record / Discharge Book', - description: 'Certified copy showing total approved sea time satisfying the STCW requirement for this certificate.', - required: true, - }, - ]; - - // One upload slot per competency area - cert.competencies.forEach((comp) => { - slots.push({ - key: `comp-${comp.area.toLowerCase().replace(/[^a-z0-9]/g, '-')}`, - label: `Certificate — ${comp.area}`, - description: `Upload the certificate(s) or documentary evidence proving competency in: ${comp.items.slice(0, 3).join('; ')}${comp.items.length > 3 ? '; and more.' : '.'}`, - required: true, - isCompetency: true, - }); - }); - - // TRB — mandatory for all certs except the very first entry-level ones - slots.push({ - key: 'trb', - label: 'Training Record Book (TRB)', - description: isFirst - ? 'Your Training Record Book if available. Not mandatory for this entry-level certificate.' - : 'Your completed and signed Training Record Book. An EMA officer will physically inspect this document. Mandatory.', - required: !isFirst, - }); - - if (medicalExpired) { - slots.push({ - key: 'medical', - label: 'Valid Medical Fitness Certificate', - description: 'Your medical certificate on file has expired. Upload a current valid medical fitness certificate.', - required: true, - }); - } - - return slots; -} - -// --------------------------------------------------------------------------- -// Level badge -// --------------------------------------------------------------------------- -const LEVEL_COLOR: Record = { Support: 'gray', Operational: 'blue', Management: 'violet' }; - -// --------------------------------------------------------------------------- -// Competency accordion -// --------------------------------------------------------------------------- -function CompetencyPanel({ cert }: { cert: CertDef }) { - const [open, setOpen] = useState(false); - return ( - - setOpen((o) => !o)}> - - - - - {cert.label} - {cert.level} - {cert.type} - - {open ? : } - - - - - - - STCW Reference - {cert.stcwRef} - - - Eligible Ranks - {cert.eligibleRanks} - - - Minimum Age - {cert.minAge} years - - - Validity - {cert.validityYears} years — {cert.revalidationRule} - - - - - - - Sea Service Requirement - {cert.seaService} - - - - Mandatory Training - - {cert.mandatoryTraining.map((t) => {t})} - - - - - - - Competency Areas (STCW Tables) - - {cert.competencies.map((comp) => ( - - {comp.area} - - {comp.items.map((item) => {item})} - - - ))} - - - - {cert.notes && ( - } p="xs"> - {cert.notes} - - )} - - - - ); -} - -// --------------------------------------------------------------------------- -// Main page -// --------------------------------------------------------------------------- -export function CoCApplicationPage() { - const navigate = useNavigate(); - const [step, setStep] = useState(0); - - // Derive available certs for this department - const deptCerts = CERT_CATALOG.filter((c) => c.dept.includes(MOCK_PROFILE.department)); - - // Which are already held - const held = new Set(MOCK_PROFILE.heldCertIds); - - // Eligible: prerequisite must be held (or null) - const eligible = deptCerts.filter((c) => { - if (held.has(c.id)) return false; // already issued - if (c.prerequisiteId && !held.has(c.prerequisiteId)) return false; // prereq not met - return true; - }); - - const selectOptions = eligible.map((c) => ({ - value: c.id, - label: `[${c.type}] ${c.label}`, - })); - - // Step 0 - const [selectedId, setSelectedId] = useState(null); - const selectedCert = CERT_CATALOG.find((c) => c.id === selectedId) ?? null; - - // Step 1 — documents (recomputed when selected cert changes) - const docSlots = buildDocSlots(selectedCert, MOCK_PROFILE.medicalExpired); - const [docs, setDocs] = useState>({}); - const setDoc = (key: string, f: File | null) => setDocs((p) => ({ ...p, [key]: f })); - - // Step 2 — payment - const [paymentMethod, setPaymentMethod] = useState(null); - const [paymentRef, setPaymentRef] = useState(''); - const [paymentDate, setPaymentDate] = useState(''); - const [paymentFile, setPaymentFile] = useState(null); - - const docsStepOk = docSlots.filter((d) => d.required).every((d) => !!docs[d.key]); - const payOk = !!paymentMethod && paymentRef.trim().length > 0 && paymentDate.length > 0 && !!paymentFile; - - const FEES = [ - { label: 'Application Fee', amount: selectedCert?.type === 'CoC' ? 800 : 300 }, - { label: 'Examination Fee', amount: selectedCert?.type === 'CoC' ? 400 : 0 }, - { label: 'Certificate Issuance Fee', amount: 200 }, - ].filter((f) => f.amount > 0); - const TOTAL = FEES.reduce((s, f) => s + f.amount, 0); - - const EXAM_VENUES = [ - { value: 'addis', label: 'EMA HQ — Addis Ababa' }, - { value: 'djibouti-link', label: 'EMA Regional Office — Djibouti Liaison' }, - ]; - - const handleSubmit = () => { - notify.success(`Application submitted! Reference: ${selectedCert?.type}-APP-2025-${Math.floor(Math.random() * 9000 + 1000)}`); - navigate('/certificates'); - }; - - const deptLabel: Record = { - deck: 'Deck', engine: 'Engine', electro: 'Electro-Technical', catering: 'Catering', - }; - - return ( - - - - - -
- Certificate Application — {deptLabel[MOCK_PROFILE.department]} Department - STCW Certificate of Competency / Certificate of Proficiency — Ethiopian Maritime Authority -
- - {/* Profile info pulled from system */} - - - - - Seaman Book: - {MOCK_PROFILE.seamanBookNo} - from system - - - - Photo: - {MOCK_PROFILE.photoRef} - from system - - - Your Seaman Book number and passport-size photo are automatically included from your profile. No need to upload them again. - - - { if (s < step) setStep(s); }} size="sm" color="blue"> - } /> - } /> - } /> - } /> - - - {/* ── STEP 0 — Select certificate ── */} - {step === 0 && ( - - - - Select the Certificate You Are Applying For - - {eligible.length === 0 ? ( - }> - No certificates available at this time. - - You have either completed all certificates for your department, or you need to obtain a prerequisite certificate first. Contact EMA for guidance. - - - ) : ( -