From 5d8e749cf7617bfcd9b446121d57eea281243ed1 Mon Sep 17 00:00:00 2001 From: mengstabketemaw Date: Sat, 20 Jun 2026 09:44:41 +0300 Subject: [PATCH] ui componenet based on the requirements --- .../analytics/pages/AnalyticsPage.tsx | 255 ++++ .../pages/ApplicationReviewPage.tsx | 446 +++++++ .../features/coc-queue/pages/CoCQueuePage.tsx | 338 +++++ .../coc-queue/pages/CoCReviewPage.tsx | 621 ++++++++++ .../pages/EndorsementQueuePage.tsx | 318 +++++ .../pages/EndorsementReviewPage.tsx | 579 +++++++++ .../pages/MedicalVerificationPage.tsx | 381 ++++++ .../pages/PaymentConfigPage.tsx | 418 +++++++ .../pages/SeafarerRegistryPage.tsx | 325 +++++ .../pages/SeamanBookQueuePage.tsx | 377 ++++++ apps/backoffice/src/app/i18n/locales/am.ts | 10 + apps/backoffice/src/app/i18n/locales/en.ts | 10 + .../src/app/layouts/BackofficeLayout.tsx | 27 +- apps/backoffice/src/app/router/index.tsx | 22 + .../pages/BasicSafetyTrainingPage.tsx | 446 +++++++ .../certificates/pages/CertificatesPage.tsx | 215 ++++ .../certificates/pages/CoCApplicationPage.tsx | 1094 +++++++++++++++++ .../dashboard/pages/DashboardPage.tsx | 185 ++- .../documents/pages/DocumentVaultPage.tsx | 487 ++++++++ .../endorsement/pages/EndorsementPage.tsx | 436 +++++++ .../medical/pages/MedicalCertificatePage.tsx | 335 +++++ .../notifications/pages/NotificationsPage.tsx | 331 +++++ .../pages/SeamanBookApplicationPage.tsx | 655 ++++++++++ .../seaman-book/pages/SeamanBookPage.tsx | 310 +++++ apps/portal/src/app/i18n/locales/am.ts | 5 + apps/portal/src/app/i18n/locales/en.ts | 5 + apps/portal/src/app/layouts/PortalLayout.tsx | 32 +- apps/portal/src/app/router.tsx | 30 + 28 files changed, 8652 insertions(+), 41 deletions(-) create mode 100644 apps/backoffice/src/app/features/analytics/pages/AnalyticsPage.tsx create mode 100644 apps/backoffice/src/app/features/applications/pages/ApplicationReviewPage.tsx create mode 100644 apps/backoffice/src/app/features/coc-queue/pages/CoCQueuePage.tsx create mode 100644 apps/backoffice/src/app/features/coc-queue/pages/CoCReviewPage.tsx create mode 100644 apps/backoffice/src/app/features/endorsement/pages/EndorsementQueuePage.tsx create mode 100644 apps/backoffice/src/app/features/endorsement/pages/EndorsementReviewPage.tsx create mode 100644 apps/backoffice/src/app/features/medical-verification/pages/MedicalVerificationPage.tsx create mode 100644 apps/backoffice/src/app/features/payment-config/pages/PaymentConfigPage.tsx create mode 100644 apps/backoffice/src/app/features/seafarer-registry/pages/SeafarerRegistryPage.tsx create mode 100644 apps/backoffice/src/app/features/seaman-book-queue/pages/SeamanBookQueuePage.tsx create mode 100644 apps/portal/src/app/features/basic-safety-training/pages/BasicSafetyTrainingPage.tsx create mode 100644 apps/portal/src/app/features/certificates/pages/CertificatesPage.tsx create mode 100644 apps/portal/src/app/features/certificates/pages/CoCApplicationPage.tsx create mode 100644 apps/portal/src/app/features/documents/pages/DocumentVaultPage.tsx create mode 100644 apps/portal/src/app/features/endorsement/pages/EndorsementPage.tsx create mode 100644 apps/portal/src/app/features/medical/pages/MedicalCertificatePage.tsx create mode 100644 apps/portal/src/app/features/notifications/pages/NotificationsPage.tsx create mode 100644 apps/portal/src/app/features/seaman-book/pages/SeamanBookApplicationPage.tsx create mode 100644 apps/portal/src/app/features/seaman-book/pages/SeamanBookPage.tsx diff --git a/apps/backoffice/src/app/features/analytics/pages/AnalyticsPage.tsx b/apps/backoffice/src/app/features/analytics/pages/AnalyticsPage.tsx new file mode 100644 index 000000000..ff3d66a02 --- /dev/null +++ b/apps/backoffice/src/app/features/analytics/pages/AnalyticsPage.tsx @@ -0,0 +1,255 @@ +import { + Badge, + Button, + Card, + Divider, + Group, + Paper, + Progress, + SimpleGrid, + Stack, + Table, + Text, + ThemeIcon, + Title, + rem, +} from '@mantine/core'; +import { + IconBook2, + IconCertificate, + IconChartBar, + IconDownload, + IconHeart, + IconId, + IconShieldCheck, + IconTrendingUp, + IconUsers, +} from '@tabler/icons-react'; + +// --------------------------------------------------------------------------- +// Mock statistics +// --------------------------------------------------------------------------- +const KPI = [ + { label: 'Total Seafarers', value: 1284, delta: '+12 this month', color: 'blue', icon: IconUsers }, + { label: 'Seaman Books Issued', value: 1102, delta: '85.8% of total', color: 'teal', icon: IconBook2 }, + { label: 'BTC Issued', value: 1098, delta: '85.5% of total', color: 'teal', icon: IconCertificate }, + { label: 'BSID Issued', value: 874, delta: '68.1% of total', color: 'violet', icon: IconId }, + { label: 'CoC / CoP Issued', value: 342, delta: '26.6% of total', color: 'orange', icon: IconShieldCheck }, + { label: 'Medical Expiring (90d)',value: 41, delta: 'Action required', color: 'red', icon: IconHeart }, +]; + +const MONTHLY_APPS = [ + { month: 'Jan', seamanBook: 18, coc: 4 }, + { month: 'Feb', seamanBook: 22, coc: 6 }, + { month: 'Mar', seamanBook: 28, coc: 9 }, + { month: 'Apr', seamanBook: 31, coc: 12 }, + { month: 'May', seamanBook: 26, coc: 8 }, + { month: 'Jun', seamanBook: 35, coc: 14 }, +]; + +const CERT_DISTRIBUTION = [ + { label: 'Seaman Book', count: 1102, pct: 86, color: 'blue' }, + { label: 'Basic Training Cert', count: 1098, pct: 85, color: 'teal' }, + { label: 'BSID', count: 874, pct: 68, color: 'violet' }, + { label: 'CoC / CoP', count: 342, pct: 27, color: 'orange' }, +]; + +const REVENUE = [ + { cert: 'Seaman Book', apps: 1102, feePerApp: 700, revenue: 771400 }, + { cert: 'BTC', apps: 1098, feePerApp: 400, revenue: 439200 }, + { cert: 'BSID', apps: 874, feePerApp: 250, revenue: 218500 }, + { cert: 'CoC / CoP', apps: 342, feePerApp: 1400, revenue: 478800 }, +]; +const TOTAL_REVENUE = REVENUE.reduce((s, r) => s + r.revenue, 0); + +const EXPIRY_WATCH = [ + { cert: 'Seaman Book', expiring30: 8, expiring90: 23, total: 1102 }, + { cert: 'BTC', expiring30: 6, expiring90: 19, total: 1098 }, + { cert: 'Medical Cert', expiring30: 12, expiring90: 41, total: 1284 }, + { cert: 'CoC / CoP', expiring30: 4, expiring90: 17, total: 342 }, +]; + +// --------------------------------------------------------------------------- +// Components +// --------------------------------------------------------------------------- +function KpiCard({ label, value, delta, color, icon: Icon }: typeof KPI[0]) { + return ( + + + + {value.toLocaleString()} + + {label} + {delta} + + ); +} + +// Simple bar chart using divs +function BarChart() { + const max = Math.max(...MONTHLY_APPS.map((m) => m.seamanBook + m.coc)); + return ( + + {MONTHLY_APPS.map((m) => ( + + {m.month} +
+
+
+
+ {m.seamanBook + m.coc} + + ))} + +
Seaman Book +
CoC / CoP + + + ); +} + +// --------------------------------------------------------------------------- +// Main page +// --------------------------------------------------------------------------- +export function AnalyticsPage() { + return ( + + +
+ Analytics & Reports + Overview of seafarer registrations, certificate issuance, and revenue — mock data for demonstration +
+ +
+ + {/* KPIs */} + + {KPI.map((k) => )} + + + + {/* Monthly applications bar chart */} + + + + + Monthly Applications (2025) + + + + + + {/* Certificate distribution */} + + + + Certificate Distribution + + + {CERT_DISTRIBUTION.map((c) => ( +
+ + + {c.label} + + + {c.count.toLocaleString()} + {c.pct}% + + + +
+ ))} +
+
+
+ + {/* Revenue breakdown */} + + + Revenue Breakdown by Certificate Type + Total: ETB {TOTAL_REVENUE.toLocaleString()} + + + + + {['Certificate Type', 'Applications', 'Fee per App', 'Total Revenue', '% of Revenue'].map((h) => ( + {h} + ))} + + + + {REVENUE.map((r) => ( + + {r.cert} + {r.apps.toLocaleString()} + ETB {r.feePerApp.toLocaleString()} + ETB {r.revenue.toLocaleString()} + + + + {((r.revenue / TOTAL_REVENUE) * 100).toFixed(1)}% + + + + ))} + + + + Total + {REVENUE.reduce((s, r) => s + r.apps, 0).toLocaleString()} + + ETB {TOTAL_REVENUE.toLocaleString()} + + + +
+
+ + {/* Expiry watchlist */} + + + + Expiry Watchlist + + + + + {['Certificate', 'Total Issued', 'Expiring in 30 days', 'Expiring in 90 days', '% at Risk'].map((h) => ( + {h} + ))} + + + + {EXPIRY_WATCH.map((e) => ( + + {e.cert} + {e.total.toLocaleString()} + + 5 ? 'red' : 'orange'} variant="light" size="sm">{e.expiring30} + + + 20 ? 'orange' : 'yellow'} variant="light" size="sm">{e.expiring90} + + + + 0.1 ? 'red' : 'orange'} + size="sm" + radius="xl" + style={{ flex: 1, minWidth: 60 }} + /> + {((e.expiring90 / e.total) * 100).toFixed(1)}% + + + + ))} + +
+
+
+ ); +} diff --git a/apps/backoffice/src/app/features/applications/pages/ApplicationReviewPage.tsx b/apps/backoffice/src/app/features/applications/pages/ApplicationReviewPage.tsx new file mode 100644 index 000000000..1ec7115d0 --- /dev/null +++ b/apps/backoffice/src/app/features/applications/pages/ApplicationReviewPage.tsx @@ -0,0 +1,446 @@ +import { useState } from 'react'; +import { + ActionIcon, + Alert, + Badge, + Box, + Button, + Card, + Divider, + Group, + Modal, + Paper, + SimpleGrid, + Stack, + Tabs, + Text, + Textarea, + ThemeIcon, + Title, + rem, +} from '@mantine/core'; +import { + IconAlertTriangle, + IconArrowLeft, + IconBook2, + IconCertificate, + IconCheck, + IconCircleCheck, + IconCreditCard, + IconExternalLink, + IconEye, + IconFileDescription, + IconHeart, + IconInfoCircle, + IconShieldCheck, + IconUser, + IconX, +} from '@tabler/icons-react'; +import { useNavigate } from 'react-router-dom'; +import { notify } from '@ema-platform/ui'; + +// --------------------------------------------------------------------------- +// Mock data +// --------------------------------------------------------------------------- +const FEES = [ + { group: 'Seaman Book', label: 'Application Fee', amount: 500 }, + { group: 'Seaman Book', label: 'Document Verification Fee', amount: 200 }, + { group: 'Basic Training Certificate (BTC)', label: 'Application Fee', amount: 300 }, + { group: 'Basic Training Certificate (BTC)', label: 'Document Verification Fee',amount: 100 }, + { group: 'BSID', label: 'Application Fee', amount: 100 }, +]; +const TOTAL = FEES.reduce((s, f) => s + f.amount, 0); + +const MOCK_APPLICATION = { + id: 'SB-APP-2025-001', + submittedAt: '2025-05-10', + status: 'pending_review' as const, + applicant: { + fullName: 'Abebe Girma Tadesse', + dob: '1990-04-15', + nationality: 'Ethiopian', + idNumber: 'ID-ETH-2024-001', + phone: '+251 91 234 5678', + email: 'abebe.girma@email.com', + address: 'Bole, Addis Ababa, Ethiopia', + height: '178 cm', + bloodType: 'O+', + hairColor: 'Black', + eyeColor: 'Dark Brown', + seafarerNumber: 'SEAF-2024-00142', + }, + bst: [ + { key: 'pst', short: 'PST', label: 'Personal Survival Techniques', certNumber: 'PST-2024-001', issuer: 'Bahirdar Maritime Training School', issueDate: '2024-01-15', expiryDate: '2029-01-15', fileName: 'pst_cert.pdf', fileType: 'pdf' }, + { key: 'fpff', short: 'FPFF', label: 'Fire Prevention & Fire Fighting', certNumber: 'FPFF-2024-002', issuer: 'Bahirdar Maritime Training School', issueDate: '2024-01-16', expiryDate: '2029-01-16', fileName: 'fpff_cert.pdf', fileType: 'pdf' }, + { key: 'efa', short: 'EFA', label: 'Elementary First Aid', certNumber: 'EFA-2024-003', issuer: 'EMA Training Centre', issueDate: '2024-02-01', expiryDate: '', fileName: 'efa_cert.jpg', fileType: 'image' }, + { key: 'pssr', short: 'PSSR', label: 'Personal Safety & Social Responsibility', certNumber: 'PSSR-2024-004', issuer: 'EMA Training Centre', issueDate: '2024-02-02', expiryDate: '', fileName: 'pssr_cert.jpg', fileType: 'image' }, + { key: 'shpt', short: 'SHPT', label: 'Sexual Harassment Prevention Training', certNumber: 'SHPT-2024-005', issuer: 'EMA Training Centre', issueDate: '2024-02-03', expiryDate: '', fileName: 'shpt_cert.jpg', fileType: 'image' }, + ], + medical: { + certNumber: 'MC-2024-009', + issuer: 'EMA Medical Centre — Addis Ababa', + issueDate: '2024-03-20', + expiryDate: '2026-03-20', + fileName: 'medical_cert.pdf', + fileType: 'pdf', + }, + payment: { + method: 'CBE Bank Transfer', + ref: 'CBE-TXN-20240510-001', + date: '2025-05-10', + status: 'confirmed' as 'confirmed' | 'pending' | 'rejected', + receiptFileName: 'payment_receipt.pdf', + receiptFileType: 'pdf', + }, +}; + +// --------------------------------------------------------------------------- +// Helpers +// --------------------------------------------------------------------------- +const STATUS_COLOR: Record = { + pending_review: 'yellow', approved: 'teal', correction_required: 'orange', rejected: 'red', +}; +const STATUS_LABEL: Record = { + pending_review: 'Pending Review', approved: 'Approved', correction_required: 'Correction Required', rejected: 'Rejected', +}; +const PAYMENT_COLOR = { confirmed: 'teal', pending: 'yellow', rejected: 'red' } as const; + +const DEMO_PDF = 'data:application/pdf;base64,JVBERi0xLjQKJeLjz9MKMSAwIG9iago8PC9UeXBlL0NhdGFsb2cvUGFnZXMgMiAwIFI+PgplbmRvYmoKMiAwIG9iago8PC9UeXBlL1BhZ2VzL0tpZHNbMyAwIFJdL0NvdW50IDE+PgplbmRvYmoKMyAwIG9iago8PC9UeXBlL1BhZ2UvUGFyZW50IDIgMCBSL01lZGlhQm94WzAgMCA2MTIgNzkyXT4+CmVuZG9iagp4cmVmCjAgNAowMDAwMDAwMDAwIDY1NTM1IGYgCjAwMDAwMDAwMDkgMDAwMDAgbiAKMDAwMDAwMDA1OCAwMDAwMCBuIAowMDAwMDAwMTE1IDAwMDAwIG4gCnRyYWlsZXIKPDwvU2l6ZSA0L1Jvb3QgMSAwIFI+PgpzdGFydHhyZWYKMjIwCiUlRU9G'; + +function getDocUrl(fileName: string) { + return fileName.endsWith('.pdf') ? DEMO_PDF : `https://placehold.co/600x400/e9ecef/6c757d?text=${encodeURIComponent(fileName)}`; +} + +// --------------------------------------------------------------------------- +// Inline document viewer +// --------------------------------------------------------------------------- +function DocViewer({ fileName, fileType, label }: { fileName: string; fileType: string; label: string }) { + const [open, setOpen] = useState(false); + const url = getDocUrl(fileName); + return ( + <> + + + + + +
+ {label} + {fileName} +
+ {fileType.toUpperCase()} +
+ + {fileType === 'pdf' + ?