mirror of
https://github.com/Tria-plc/emaui.git
synced 2026-08-26 13:02:50 +00:00
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>
1095 lines
56 KiB
TypeScript
1095 lines
56 KiB
TypeScript
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<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: 10–15 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>
|
||
);
|
||
}
|