From 60ef3fc8b8bf97bc11d203d405753e3944c2fc07 Mon Sep 17 00:00:00 2001 From: estifanos Date: Thu, 23 Jul 2026 12:56:16 +0000 Subject: [PATCH] feat: implement vessel ownership transfer feature in portal and backoffice - Added VesselTransferReviewPage for reviewing and managing vessel transfer requests in backoffice. - Created VesselTransferApplicationPage for submitting new ownership transfer requests in portal. - Introduced mock data for vessels and transfer requests in both portal and backoffice. - Updated navigation and layout to include vessel transfer sections in both applications. - Added internationalization support for vessel transfer labels in English and Amharic. - Documented the implementation details and suggested API surface for future integration. --- .../pages/VesselTransferQueuePage.tsx | 349 ++++++++++++++++++ .../pages/VesselTransferReviewPage.tsx | 252 +++++++++++++ apps/backoffice/src/app/i18n/locales/am.ts | 1 + apps/backoffice/src/app/i18n/locales/en.ts | 1 + .../src/app/layouts/BackofficeLayout.tsx | 2 + apps/backoffice/src/app/router/index.tsx | 4 + .../src/app/features/vessel-transfer/mock.ts | 93 +++++ .../pages/VesselTransferApplicationPage.tsx | 199 ++++++++++ .../pages/VesselTransferPage.tsx | 81 ++++ apps/portal/src/app/i18n/locales/am.ts | 1 + apps/portal/src/app/i18n/locales/en.ts | 1 + apps/portal/src/app/layouts/PortalLayout.tsx | 3 + apps/portal/src/app/router.tsx | 8 + docs/vessel-ownership-transfer.md | 39 ++ 14 files changed, 1034 insertions(+) create mode 100644 apps/backoffice/src/app/features/vessel-transfer/pages/VesselTransferQueuePage.tsx create mode 100644 apps/backoffice/src/app/features/vessel-transfer/pages/VesselTransferReviewPage.tsx create mode 100644 apps/portal/src/app/features/vessel-transfer/mock.ts create mode 100644 apps/portal/src/app/features/vessel-transfer/pages/VesselTransferApplicationPage.tsx create mode 100644 apps/portal/src/app/features/vessel-transfer/pages/VesselTransferPage.tsx create mode 100644 docs/vessel-ownership-transfer.md diff --git a/apps/backoffice/src/app/features/vessel-transfer/pages/VesselTransferQueuePage.tsx b/apps/backoffice/src/app/features/vessel-transfer/pages/VesselTransferQueuePage.tsx new file mode 100644 index 000000000..fc7940b75 --- /dev/null +++ b/apps/backoffice/src/app/features/vessel-transfer/pages/VesselTransferQueuePage.tsx @@ -0,0 +1,349 @@ +import { useState } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { + ActionIcon, + Badge, + Box, + Button, + Card, + Drawer, + Group, + Paper, + Select, + SimpleGrid, + Stack, + Table, + Text, + TextInput, + ThemeIcon, + Title, + rem, +} from '@mantine/core'; +import { + IconAnchor, + IconCircleCheck, + IconClock, + IconEye, + IconSearch, + IconShip, + IconX, +} from '@tabler/icons-react'; + +// --------------------------------------------------------------------------- +// Types & mock data +// --------------------------------------------------------------------------- +export type TransferStatus = 'Pending' | 'Under Review' | 'Approved' | 'Rejected'; + +export const STATUS_COLOR: Record = { + Pending: 'gray', + 'Under Review': 'blue', + Approved: 'teal', + Rejected: 'red', +}; + +export interface TransferParty { + name: string; + idOrTin: string; + phone: string; + email: string; + address: string; +} + +export interface TransferRequest { + id: string; + vesselName: string; + vesselRegNo: string; + currentOwner: TransferParty; + newOwner: TransferParty; + reason: string; + status: TransferStatus; + submitted: string; + remarks?: string; + approvedOn?: string; + document: { fileName: string; fileType: 'pdf' | 'image' }; +} + +export const MOCK_REQUESTS: TransferRequest[] = [ + { + id: 'VT-2025-0001', + vesselName: 'MV Abay', + vesselRegNo: 'ET-VSL-2021-014', + currentOwner: { name: 'Solomon Bekele', idOrTin: 'ETH-ID-0012345', phone: '+251 911 234 567', email: 'solomon.b@email.com', address: 'Bole, Addis Ababa' }, + newOwner: { name: 'Bahir Dar Shipping PLC', idOrTin: 'TIN-0044556677', phone: '+251 58 220 1234', email: 'info@bdshipping.com', address: 'Bahir Dar, Ethiopia' }, + reason: 'Corporate Restructuring', + status: 'Under Review', + submitted: '2025-06-30', + document: { fileName: 'bill_of_sale_abay.pdf', fileType: 'pdf' }, + }, + { + id: 'VT-2025-0002', + vesselName: 'MV Tana', + vesselRegNo: 'ET-VSL-2022-031', + currentOwner: { name: 'Solomon Bekele', idOrTin: 'ETH-ID-0012345', phone: '+251 911 234 567', email: 'solomon.b@email.com', address: 'Bole, Addis Ababa' }, + newOwner: { name: 'Hana Girma', idOrTin: 'ETH-ID-0098765', phone: '+251 922 345 678', email: 'hana.girma@email.com', address: 'Piazza, Addis Ababa' }, + reason: 'Sale', + status: 'Approved', + submitted: '2025-05-12', + approvedOn: '2025-05-28', + document: { fileName: 'bill_of_sale_tana.pdf', fileType: 'pdf' }, + }, + { + id: 'VT-2025-0003', + vesselName: 'MV Abay', + vesselRegNo: 'ET-VSL-2021-014', + currentOwner: { name: 'Solomon Bekele', idOrTin: 'ETH-ID-0012345', phone: '+251 911 234 567', email: 'solomon.b@email.com', address: 'Bole, Addis Ababa' }, + newOwner: { name: 'Dawit Alemu', idOrTin: 'ETH-ID-0011223', phone: '+251 933 456 789', email: 'dawit.alemu@email.com', address: 'Mekelle, Ethiopia' }, + reason: 'Gift', + status: 'Rejected', + submitted: '2025-04-02', + remarks: 'Bill of Sale document illegible — please resubmit a clearer scan.', + document: { fileName: 'transfer_doc_scan.jpg', fileType: 'image' }, + }, + { + id: 'VT-2025-0004', + vesselName: 'MV Zeway', + vesselRegNo: 'ET-VSL-2020-007', + currentOwner: { name: 'Meskerem Assefa', idOrTin: 'ETH-ID-0055667', phone: '+251 944 567 890', email: 'meskerem.a@email.com', address: 'Hawassa, Ethiopia' }, + newOwner: { name: 'Yonas Tesfaye', idOrTin: 'ETH-ID-0077889', phone: '+251 955 678 901', email: 'yonas.t@email.com', address: 'Adama, Ethiopia' }, + reason: 'Inheritance', + status: 'Pending', + submitted: '2025-07-10', + document: { fileName: 'inheritance_transfer.pdf', fileType: 'pdf' }, + }, + { + id: 'VT-2025-0005', + vesselName: 'MV Awash', + vesselRegNo: 'ET-VSL-2023-009', + currentOwner: { name: 'Kidist Worku', idOrTin: 'ETH-ID-0033445', phone: '+251 966 789 012', email: 'kidist.w@email.com', address: 'Dire Dawa, Ethiopia' }, + newOwner: { name: 'Nile Logistics PLC', idOrTin: 'TIN-0099887766', phone: '+251 11 550 4321', email: 'contact@nilelogistics.com', address: 'Addis Ababa, Ethiopia' }, + reason: 'Court Order', + status: 'Pending', + submitted: '2025-07-15', + document: { fileName: 'court_order.pdf', fileType: 'pdf' }, + }, + { + id: 'VT-2025-0006', + vesselName: 'MV Genale', + vesselRegNo: 'ET-VSL-2019-002', + currentOwner: { name: 'Tesfaye Kebede', idOrTin: 'ETH-ID-0022334', phone: '+251 977 890 123', email: 'tesfaye.k@email.com', address: 'Gondar, Ethiopia' }, + newOwner: { name: 'Almaz Hailu', idOrTin: 'ETH-ID-0088990', phone: '+251 988 901 234', email: 'almaz.h@email.com', address: 'Bahir Dar, Ethiopia' }, + reason: 'Other', + status: 'Under Review', + submitted: '2025-07-05', + document: { fileName: 'transfer_agreement.pdf', fileType: 'pdf' }, + }, +]; + +// --------------------------------------------------------------------------- +// Quick-review drawer +// --------------------------------------------------------------------------- +function QuickReviewDrawer({ + request, + opened, + onClose, + onFullReview, +}: { + request: TransferRequest | null; + opened: boolean; + onClose: () => void; + onFullReview: (id: string) => void; +}) { + if (!request) return null; + + return ( + + + + + + + {request.vesselName} + {request.status} + + {request.vesselRegNo} + + + + From → To + + + Current Owner + {request.currentOwner.name} + + + New Owner + {request.newOwner.name} + + + Reason + {request.reason} + + + Submitted + {request.submitted} + + + + + {request.status === 'Rejected' && request.remarks && ( + + Rejection Reason + {request.remarks} + + )} + {request.status === 'Approved' && request.approvedOn && ( + + Transfer approved on {request.approvedOn}. New certificates issued to {request.newOwner.name}. + + )} + + + ); +} + +// --------------------------------------------------------------------------- +// Main page +// --------------------------------------------------------------------------- +export function VesselTransferQueuePage() { + const navigate = useNavigate(); + const [requests] = useState(MOCK_REQUESTS); + const [search, setSearch] = useState(''); + const [statusFilter, setStatusFilter] = useState(null); + const [selected, setSelected] = useState(null); + const [drawerOpen, setDrawerOpen] = useState(false); + + const stats = { + total: requests.length, + pending: requests.filter((r) => r.status === 'Pending').length, + underReview: requests.filter((r) => r.status === 'Under Review').length, + approved: requests.filter((r) => r.status === 'Approved').length, + }; + + const filtered = requests.filter((r) => { + const q = search.toLowerCase(); + const matchSearch = !q + || r.vesselName.toLowerCase().includes(q) + || r.id.toLowerCase().includes(q) + || r.currentOwner.name.toLowerCase().includes(q) + || r.newOwner.name.toLowerCase().includes(q); + const matchStatus = !statusFilter || r.status === statusFilter; + return matchSearch && matchStatus; + }); + + const openDrawer = (req: TransferRequest) => { + setSelected(req); + setDrawerOpen(true); + }; + + return ( + +
+ Vessel Ownership Transfers + Review and process vessel ownership transfer requests +
+ + {/* Stats */} + + {[ + { label: 'Total', value: stats.total, color: 'blue', icon: IconShip }, + { label: 'Pending', value: stats.pending, color: 'gray', icon: IconClock }, + { label: 'Under Review', value: stats.underReview, color: 'blue', icon: IconEye }, + { label: 'Approved', value: stats.approved, color: 'teal', icon: IconCircleCheck }, + ].map(({ label, value, color, icon: Icon }) => ( + + + +
{value}{label}
+
+
+ ))} +
+ + {/* Table */} + + + Transfer Queue + + } + value={search} + onChange={(e) => setSearch(e.currentTarget.value)} + size="sm" + style={{ minWidth: rem(280) }} + rightSection={search ? setSearch('')}> : null} + /> +