Files
emaui/apps/portal/src/app/features/certificates/pages/CoCApplicationPage.tsx
fitse-yotor eabaae36a0 feat(portal): restore Mengestab's client-approved seafarer and vessel UI
Copied verbatim from the pre-override branch so the approved screens are
recoverable at this exact commit before any wiring changes them.

Brings back the richer flows the client signed off: a four-step seafarer
registration wizard with bilingual inputs and an Ethiopic date picker,
the vessel-owner portal (its own register/login/dashboard), ownership
transfer, and the seaman book, certificate, medical and endorsement
screens.

Six of these pages already call an API; ten are mockups carrying
hardcoded data. Both are committed as-is here -- the wiring that follows
is a separate commit so the diff shows exactly what changed from what
the client approved.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 23:19:08 +03:00

1095 lines
56 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 5003,000 GT (Management Level)',
eligibleRanks: 'Chief Mate on ships 5003,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 5003,000 GT',
eligibleRanks: 'Master on ships 5003,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 5003,000 GT CoC with adequate service on vessels of 3,000 GT or more, as determined by the Administration.',
mandatoryTraining: ['Master 5003,000 GT CoC (prerequisite)', 'Approved management-level programme — IMO MC 7.01'],
competencies: [
{ area: 'All Master competencies', items: ['Same as Master 5003,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 7503,000 kW',
eligibleRanks: 'Second Engineer on ships 7503,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 7503,000 kW',
eligibleRanks: 'Chief Engineer on ships 7503,000 kW',
prerequisiteId: 'second-engineer-750',
minAge: 18,
seaService: 'Must hold Second Engineer CoC (7503,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<string, string> = { Support: 'gray', Operational: 'blue', Management: 'violet' };
// ---------------------------------------------------------------------------
// Competency accordion
// ---------------------------------------------------------------------------
function CompetencyPanel({ cert }: { cert: CertDef }) {
const [open, setOpen] = useState(false);
return (
<Paper withBorder radius="md" p="md">
<Group justify="space-between" style={{ cursor: 'pointer' }} onClick={() => setOpen((o) => !o)}>
<Group gap="xs">
<ThemeIcon size={22} radius="sm" color={LEVEL_COLOR[cert.level]} variant="light">
<IconAward size={13} />
</ThemeIcon>
<Text fz="sm" fw={700}>{cert.label}</Text>
<Badge size="xs" color={LEVEL_COLOR[cert.level]} variant="light">{cert.level}</Badge>
<Badge size="xs" color={cert.type === 'CoC' ? 'blue' : 'teal'} variant="dot">{cert.type}</Badge>
</Group>
{open ? <IconChevronUp size={14} /> : <IconChevronDown size={14} />}
</Group>
<Collapse in={open}>
<Stack gap="sm" mt="md">
<SimpleGrid cols={{ base: 1, sm: 2 }} spacing="sm">
<Box>
<Text fz="xs" c="dimmed" fw={600} style={{ textTransform: 'uppercase' }}>STCW Reference</Text>
<Text fz="xs" mt={2}>{cert.stcwRef}</Text>
</Box>
<Box>
<Text fz="xs" c="dimmed" fw={600} style={{ textTransform: 'uppercase' }}>Eligible Ranks</Text>
<Text fz="xs" mt={2}>{cert.eligibleRanks}</Text>
</Box>
<Box>
<Text fz="xs" c="dimmed" fw={600} style={{ textTransform: 'uppercase' }}>Minimum Age</Text>
<Text fz="xs" mt={2}>{cert.minAge} years</Text>
</Box>
<Box>
<Text fz="xs" c="dimmed" fw={600} style={{ textTransform: 'uppercase' }}>Validity</Text>
<Text fz="xs" mt={2}>{cert.validityYears} years {cert.revalidationRule}</Text>
</Box>
</SimpleGrid>
<Divider />
<Box>
<Text fz="xs" fw={700} mb={4}>Sea Service Requirement</Text>
<Text fz="xs" c="dimmed" lh={1.6}>{cert.seaService}</Text>
</Box>
<Box>
<Text fz="xs" fw={700} mb={4}>Mandatory Training</Text>
<List size="xs" spacing={2}>
{cert.mandatoryTraining.map((t) => <List.Item key={t}>{t}</List.Item>)}
</List>
</Box>
<Divider />
<Box>
<Text fz="xs" fw={700} mb="xs">Competency Areas (STCW Tables)</Text>
<SimpleGrid cols={{ base: 1, sm: 2 }} spacing="xs">
{cert.competencies.map((comp) => (
<Card key={comp.area} withBorder radius="sm" p="sm">
<Text fz="xs" fw={700} mb={4} c="blue.7">{comp.area}</Text>
<List size="xs" spacing={1}>
{comp.items.map((item) => <List.Item key={item}>{item}</List.Item>)}
</List>
</Card>
))}
</SimpleGrid>
</Box>
{cert.notes && (
<Alert variant="light" color="yellow" icon={<IconInfoCircle size={13} />} p="xs">
<Text fz="xs">{cert.notes}</Text>
</Alert>
)}
</Stack>
</Collapse>
</Paper>
);
}
// ---------------------------------------------------------------------------
// 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<string | null>(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<Record<string, File | null>>({});
const setDoc = (key: string, f: File | null) => setDocs((p) => ({ ...p, [key]: f }));
// Step 2 — payment
const [paymentMethod, setPaymentMethod] = useState<string | null>(null);
const [paymentRef, setPaymentRef] = useState('');
const [paymentDate, setPaymentDate] = useState('');
const [paymentFile, setPaymentFile] = useState<File | null>(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<Department, string> = {
deck: 'Deck', engine: 'Engine', electro: 'Electro-Technical', catering: 'Catering',
};
return (
<Stack gap="md">
<Group gap="xs">
<Button variant="subtle" size="xs" leftSection={<IconArrowLeft size={13} />} onClick={() => navigate('/certificates')}>
Back to Certificates
</Button>
</Group>
<div>
<Title order={3}>Certificate Application {deptLabel[MOCK_PROFILE.department]} Department</Title>
<Text fz="sm" c="dimmed">STCW Certificate of Competency / Certificate of Proficiency Ethiopian Maritime Authority</Text>
</div>
{/* Profile info pulled from system */}
<Paper withBorder radius="md" p="md" bg="var(--mantine-color-blue-light)">
<Group gap="md" wrap="wrap">
<Group gap="xs">
<ThemeIcon size={20} radius="sm" color="blue" variant="light"><IconBook2 size={12} /></ThemeIcon>
<Text fz="xs" fw={600}>Seaman Book:</Text>
<Text fz="xs">{MOCK_PROFILE.seamanBookNo}</Text>
<Badge size="xs" color="blue" variant="light">from system</Badge>
</Group>
<Group gap="xs">
<ThemeIcon size={20} radius="sm" color="teal" variant="light"><IconFileDescription size={12} /></ThemeIcon>
<Text fz="xs" fw={600}>Photo:</Text>
<Text fz="xs">{MOCK_PROFILE.photoRef}</Text>
<Badge size="xs" color="teal" variant="light">from system</Badge>
</Group>
</Group>
<Text fz="xs" c="dimmed" mt="xs">Your Seaman Book number and passport-size photo are automatically included from your profile. No need to upload them again.</Text>
</Paper>
<Stepper active={step} onStepClick={(s) => { if (s < step) setStep(s); }} size="sm" color="blue">
<Stepper.Step label="Select Certificate" description="Choose type" icon={<IconShieldCheck size={14} />} />
<Stepper.Step label="Upload Documents" description="Sea time & certs" icon={<IconFileDescription size={14} />} />
<Stepper.Step label="Payment" description="Pay fees" icon={<IconCreditCard size={14} />} />
<Stepper.Step label="Review & Submit" description="Final check" icon={<IconCircleCheck size={14} />} />
</Stepper>
{/* ── STEP 0 — Select certificate ── */}
{step === 0 && (
<Stack gap="md">
<Paper withBorder radius="lg" p="xl">
<Stack gap="lg">
<Text fw={700}>Select the Certificate You Are Applying For</Text>
{eligible.length === 0 ? (
<Alert variant="light" color="orange" icon={<IconAlertCircle size={15} />}>
<Text fz="sm" fw={600}>No certificates available at this time.</Text>
<Text fz="xs" c="dimmed" mt={4}>
You have either completed all certificates for your department, or you need to obtain a prerequisite certificate first. Contact EMA for guidance.
</Text>
</Alert>
) : (
<Select
label="Certificate Type"
placeholder="Select a certificate…"
data={selectOptions}
value={selectedId}
onChange={setSelectedId}
searchable
size="sm"
/>
)}
{/* Held certificates */}
{held.size > 0 && (
<Box>
<Text fz="xs" fw={700} c="dimmed" mb="xs" style={{ textTransform: 'uppercase' }}>Already Issued to You</Text>
<Group gap="xs" wrap="wrap">
{[...held].map((hid) => {
const c = CERT_CATALOG.find((x) => x.id === hid);
if (!c) return null;
return <Badge key={hid} color="teal" variant="light" size="sm">{c.label}</Badge>;
})}
</Group>
</Box>
)}
{/* Competency detail for selected */}
{selectedCert && (
<>
<Divider label="Certificate Details & STCW Requirements" labelPosition="left" />
<CompetencyPanel cert={selectedCert} />
</>
)}
{/* All department certificates reference */}
<Divider label="All Certificates in Your Department (for reference)" labelPosition="left" />
<Text fz="xs" c="dimmed">The full career pathway for the {deptLabel[MOCK_PROFILE.department]} department. You must progress in order each certificate requires the previous one.</Text>
<Stack gap="xs">
{deptCerts.map((c) => (
<CompetencyPanel key={c.id} cert={c} />
))}
</Stack>
<Group justify="flex-end">
<Button
rightSection={<IconArrowRight size={15} />}
disabled={!selectedCert}
onClick={() => setStep(1)}
>
Next: Documents & Exam
</Button>
</Group>
</Stack>
</Paper>
</Stack>
)}
{/* ── STEP 1 — Upload Documents ── */}
{step === 1 && selectedCert && (
<Paper withBorder radius="lg" p="xl">
<Stack gap="xl">
<Text fw={700}>Upload Documents</Text>
<Alert variant="light" color="blue" icon={<IconInfoCircle size={15} />} p="sm">
<Text fz="xs">
Your Seaman Book and passport-size photo are already on file no need to re-upload them.
Upload one certificate file per competency area plus your sea service record and Training Record Book.
PDF preferred. Max 5 MB each.
</Text>
</Alert>
{MOCK_PROFILE.medicalExpired && (
<Alert variant="light" color="orange" icon={<IconAlertCircle size={15} />} p="sm">
<Text fz="xs" fw={600}>Your medical fitness certificate on file has expired.</Text>
<Text fz="xs" c="dimmed" mt={2}>Upload a current valid medical fitness certificate to proceed.</Text>
</Alert>
)}
{/* Sea service */}
{(() => {
const svc = docSlots.find((d) => d.key === 'sea-service')!;
return (
<Box>
<Group gap={4} mb={4}>
<Text fz="sm" fw={700}>{svc.label}</Text>
<Text span c="red" fz="sm"> *</Text>
</Group>
<Text fz="xs" c="dimmed" mb="xs">{svc.description}</Text>
<Group gap="sm" align="center" wrap="nowrap">
<FileInput placeholder="Click to upload…" accept=".pdf,.jpg,.jpeg,.png" leftSection={<IconUpload size={14} />} value={docs[svc.key] ?? null} onChange={(f) => setDoc(svc.key, f)} style={{ flex: 1 }} size="sm" clearable />
{docs[svc.key] && <ThemeIcon size={28} radius="xl" color="teal" variant="light" style={{ flexShrink: 0 }}><IconCheck size={13} /></ThemeIcon>}
</Group>
</Box>
);
})()}
{/* Competency certificates — one per area */}
<Box>
<Text fz="sm" fw={700} mb={4}>Competency Certificates <Text span c="red">*</Text></Text>
<Text fz="xs" c="dimmed" mb="md">
Upload one certificate or documentary evidence file for each competency area listed below.
These must directly correspond to the STCW competency items for <strong>{selectedCert.label}</strong>.
</Text>
<Stack gap="lg">
{docSlots.filter((d) => d.isCompetency).map((doc) => (
<Box key={doc.key} style={{ borderLeft: '3px solid var(--mantine-color-blue-3)', paddingLeft: 12 }}>
<Group gap={4} mb={2}>
<Text fz="sm" fw={600}>{doc.label}</Text>
<Text span c="red" fz="sm"> *</Text>
</Group>
<Text fz="xs" c="dimmed" mb="xs" lh={1.5}>{doc.description}</Text>
<Group gap="sm" align="center" wrap="nowrap">
<FileInput placeholder="Upload certificate for this area…" accept=".pdf,.jpg,.jpeg,.png" leftSection={<IconUpload size={14} />} value={docs[doc.key] ?? null} onChange={(f) => setDoc(doc.key, f)} style={{ flex: 1 }} size="sm" clearable />
{docs[doc.key] && <ThemeIcon size={28} radius="xl" color="teal" variant="light" style={{ flexShrink: 0 }}><IconCheck size={13} /></ThemeIcon>}
</Group>
</Box>
))}
</Stack>
</Box>
{/* TRB */}
{(() => {
const trb = docSlots.find((d) => d.key === 'trb')!;
return (
<Box>
<Group gap={4} mb={4}>
<Text fz="sm" fw={700}>{trb.label}</Text>
{trb.required ? <Text span c="red" fz="sm"> *</Text> : <Badge size="xs" color="gray" variant="light">optional for first cert</Badge>}
</Group>
<Text fz="xs" c="dimmed" mb="xs" lh={1.5}>{trb.description}</Text>
{trb.required && (
<Alert variant="light" color="yellow" icon={<IconAlertCircle size={13} />} p="xs" mb="xs">
<Text fz="xs">
An EMA officer will physically inspect your original TRB at the office before scheduling your examination.
Upload a scanned copy here you will be asked to bring the original when called.
</Text>
</Alert>
)}
<Group gap="sm" align="center" wrap="nowrap">
<FileInput placeholder="Click to upload…" accept=".pdf,.jpg,.jpeg,.png" leftSection={<IconUpload size={14} />} value={docs[trb.key] ?? null} onChange={(f) => setDoc(trb.key, f)} style={{ flex: 1 }} size="sm" clearable />
{docs[trb.key] && <ThemeIcon size={28} radius="xl" color="teal" variant="light" style={{ flexShrink: 0 }}><IconCheck size={13} /></ThemeIcon>}
</Group>
</Box>
);
})()}
{/* Medical if expired */}
{MOCK_PROFILE.medicalExpired && (() => {
const med = docSlots.find((d) => d.key === 'medical');
if (!med) return null;
return (
<Box>
<Group gap={4} mb={4}><Text fz="sm" fw={700}>{med.label}</Text><Text span c="red" fz="sm"> *</Text></Group>
<Text fz="xs" c="dimmed" mb="xs">{med.description}</Text>
<Group gap="sm" align="center" wrap="nowrap">
<FileInput placeholder="Click to upload…" accept=".pdf,.jpg,.jpeg,.png" leftSection={<IconUpload size={14} />} value={docs[med.key] ?? null} onChange={(f) => setDoc(med.key, f)} style={{ flex: 1 }} size="sm" clearable />
{docs[med.key] && <ThemeIcon size={28} radius="xl" color="teal" variant="light" style={{ flexShrink: 0 }}><IconCheck size={13} /></ThemeIcon>}
</Group>
</Box>
);
})()}
{/* Upload progress summary */}
<Box>
<Text fz="xs" fw={600} c="dimmed" mb="xs" style={{ textTransform: 'uppercase' }}>Upload Progress</Text>
<Stack gap={4}>
{docSlots.map((d) => (
<Group key={d.key} gap="xs">
{docs[d.key]
? <IconCheck size={13} color="var(--mantine-color-teal-6)" />
: <Box style={{ width: 13, height: 13, borderRadius: '50%', border: '2px solid var(--mantine-color-gray-3)', flexShrink: 0 }} />}
<Text fz="xs" c={docs[d.key] ? 'teal.7' : d.required ? 'dimmed' : 'dimmed'} fw={docs[d.key] ? 600 : 400}>
{d.label}
{!docs[d.key] && d.required && <Text span c="red"> *</Text>}
</Text>
</Group>
))}
</Stack>
</Box>
<Alert variant="light" color="teal" icon={<IconInfoCircle size={15} />} p="sm">
<Text fz="xs">
After submission, EMA officers will review your documents and TRB. If approved, they will contact you with an examination date and venue.
Your original TRB must be brought to the office for physical inspection before the exam is scheduled.
</Text>
</Alert>
<Group justify="space-between">
<Button variant="default" leftSection={<IconArrowLeft size={15} />} onClick={() => setStep(0)}>Back</Button>
<Button rightSection={<IconArrowRight size={15} />} disabled={!docsStepOk} onClick={() => setStep(2)}>
Next: Payment
</Button>
</Group>
</Stack>
</Paper>
)}
{/* ── STEP 2 — Payment ── */}
{step === 2 && selectedCert && (
<Paper withBorder radius="lg" p="xl">
<Stack gap="lg">
<Text fw={700}>Fee Payment</Text>
<Paper withBorder radius="md" p="md" bg="gray.0">
<Text fz="sm" fw={700} mb="sm">Fee Breakdown {selectedCert.type}: {selectedCert.label}</Text>
<Stack gap={4}>
{FEES.map((f) => (
<Group key={f.label} justify="space-between">
<Text fz="sm" c="dimmed">{f.label}</Text>
<Text fz="sm" fw={500}>ETB {f.amount.toFixed(2)}</Text>
</Group>
))}
</Stack>
<Divider my="sm" />
<Group justify="space-between">
<Text fw={700}>Total</Text>
<Text fw={700} c="blue" fz="lg">ETB {TOTAL.toFixed(2)}</Text>
</Group>
</Paper>
<Text fw={600} fz="sm">Select Payment Method</Text>
<SimpleGrid cols={{ base: 1, sm: 2 }} spacing="sm">
{[
{ key: 'cbe', label: 'CBE Bank Transfer', acct: '1000123456789', name: 'EMA Maritime Authority', hint: 'Use your full name and certificate type as the transfer description.' },
{ key: 'telebirr', label: 'Telebirr', acct: '+251 11 551 0000', name: 'EMA Maritime Authority', hint: 'Screenshot your confirmation and upload below.' },
].map((m) => (
<Card
key={m.key}
withBorder
radius="md"
p="md"
style={{
cursor: 'pointer',
borderColor: paymentMethod === m.key ? 'var(--mantine-color-blue-6)' : undefined,
background: paymentMethod === m.key ? 'var(--mantine-color-blue-light)' : undefined,
}}
onClick={() => setPaymentMethod(m.key)}
>
<Group justify="space-between" mb="xs">
<Text fw={700} fz="sm">{m.label}</Text>
{paymentMethod === m.key && <ThemeIcon size={20} radius="xl" color="blue"><IconCheck size={11} /></ThemeIcon>}
</Group>
<Text fz="xs" c="dimmed">Account: {m.acct}</Text>
<Text fz="xs" c="dimmed">Name: {m.name}</Text>
<Text fz="xs" c="dimmed" mt={4}>{m.hint}</Text>
</Card>
))}
</SimpleGrid>
{paymentMethod && (
<Stack gap="sm">
<Divider label="Payment Confirmation" labelPosition="left" />
<SimpleGrid cols={{ base: 1, sm: 2 }} spacing="sm">
<TextInput
label="Transaction / Reference Number"
placeholder="e.g. TRN-20250619-001"
value={paymentRef}
onChange={(e) => setPaymentRef(e.currentTarget.value)}
size="sm"
required
/>
<TextInput
label="Payment Date"
type="date"
value={paymentDate}
onChange={(e) => setPaymentDate(e.currentTarget.value)}
size="sm"
required
/>
</SimpleGrid>
<FileInput
label="Payment Receipt / Screenshot"
placeholder="Upload receipt"
accept=".pdf,.jpg,.jpeg,.png"
leftSection={<IconUpload size={14} />}
value={paymentFile}
onChange={setPaymentFile}
size="sm"
required
clearable
/>
</Stack>
)}
<Group justify="space-between">
<Button variant="default" leftSection={<IconArrowLeft size={15} />} onClick={() => setStep(1)}>Back</Button>
<Button rightSection={<IconArrowRight size={15} />} disabled={!payOk} onClick={() => setStep(3)}>
Next: Review
</Button>
</Group>
</Stack>
</Paper>
)}
{/* ── STEP 3 — Review & Submit ── */}
{step === 3 && selectedCert && (
<Paper withBorder radius="lg" p="xl">
<Stack gap="lg">
<Text fw={700}>Review & Submit</Text>
<SimpleGrid cols={{ base: 1, sm: 2 }} spacing="md">
<Box>
<Text fz="xs" c="dimmed" fw={600} mb="xs" style={{ textTransform: 'uppercase' }}>Certificate Applying For</Text>
<Badge color={selectedCert.type === 'CoC' ? 'blue' : 'teal'} variant="light" mb={4}>{selectedCert.type}</Badge>
<Text fz="sm" fw={600}>{selectedCert.label}</Text>
<Text fz="xs" c="dimmed">{selectedCert.stcwRef}</Text>
</Box>
<Box>
<Text fz="xs" c="dimmed" fw={600} mb="xs" style={{ textTransform: 'uppercase' }}>Level</Text>
<Badge color={LEVEL_COLOR[selectedCert.level]} variant="light">{selectedCert.level} Level</Badge>
</Box>
<Box>
<Text fz="xs" c="dimmed" fw={600} mb="xs" style={{ textTransform: 'uppercase' }}>Documents from System</Text>
<Group gap="xs" wrap="wrap">
<Badge size="sm" color="blue" variant="light">Seaman Book {MOCK_PROFILE.seamanBookNo}</Badge>
<Badge size="sm" color="teal" variant="light">Passport Photo on file</Badge>
</Group>
</Box>
<Box>
<Text fz="xs" c="dimmed" fw={600} mb="xs" style={{ textTransform: 'uppercase' }}>Uploaded Documents</Text>
<Stack gap={2}>
{docSlots.map((d) => (
<Group key={d.key} gap="xs">
{docs[d.key]
? <IconCircleCheck size={13} color="var(--mantine-color-teal-6)" />
: <IconAlertCircle size={13} color="var(--mantine-color-gray-4)" />}
<Text fz="xs" c={docs[d.key] ? undefined : 'dimmed'}>{d.label}</Text>
{!docs[d.key] && !d.required && <Badge size="xs" color="gray" variant="light">not uploaded</Badge>}
</Group>
))}
</Stack>
</Box>
<Box>
<Text fz="xs" c="dimmed" fw={600} mb="xs" style={{ textTransform: 'uppercase' }}>Payment</Text>
<Badge variant="light" color="teal" size="sm">{paymentMethod === 'cbe' ? 'CBE Bank Transfer' : 'Telebirr'}</Badge>
<Text fz="xs" c="dimmed" mt={2}>Ref: {paymentRef}</Text>
<Text fz="xs" c="dimmed">ETB {TOTAL.toFixed(2)}</Text>
</Box>
</SimpleGrid>
<Divider />
<Alert variant="light" color="blue" icon={<IconClock size={15} />}>
<Text fz="sm" fw={600}>Processing time: 1015 working days after examination</Text>
<Text fz="xs" c="dimmed" mt={2}>
EMA will notify you at each stage: document verification payment confirmation examination invitation result certificate issuance.
</Text>
</Alert>
<Group justify="space-between">
<Button variant="default" leftSection={<IconArrowLeft size={15} />} onClick={() => setStep(2)}>Back</Button>
<Button leftSection={<IconCheck size={15} />} color="teal" onClick={handleSubmit}>
Submit Application
</Button>
</Group>
</Stack>
</Paper>
)}
</Stack>
);
}