From 02b9c73caf7df29c9af526cf806be226fdb0360d Mon Sep 17 00:00:00 2001 From: estifanos Date: Tue, 18 Aug 2026 09:34:44 +0000 Subject: [PATCH] mock data for test --- .../VesselRegistrationReportPage/index.tsx | 9 +- .../mock-report.ts | 257 ++++++++++++++++++ 2 files changed, 263 insertions(+), 3 deletions(-) create mode 100644 apps/backoffice/src/app/features/vessel-registration/pages/VesselRegistrationReportPage/mock-report.ts diff --git a/apps/backoffice/src/app/features/vessel-registration/pages/VesselRegistrationReportPage/index.tsx b/apps/backoffice/src/app/features/vessel-registration/pages/VesselRegistrationReportPage/index.tsx index e41c9b3fd..6ff318039 100644 --- a/apps/backoffice/src/app/features/vessel-registration/pages/VesselRegistrationReportPage/index.tsx +++ b/apps/backoffice/src/app/features/vessel-registration/pages/VesselRegistrationReportPage/index.tsx @@ -19,6 +19,7 @@ import { KpiTiles } from './KpiTiles'; import { ReportCharts } from './ReportCharts'; import { ReportFilters } from './ReportFilters'; import { ReportTables } from './ReportTables'; +import { MOCK_VESSEL_REPORT } from './mock-report'; import { queryToSearchParams, searchParamsToQuery } from './report-format'; /** @@ -51,9 +52,11 @@ export function VesselRegistrationReportPage() { [setSearchParams], ); - const { data: report, isLoading, isFetching, error } = useGetVesselReportQuery( - query, - ); + const { data, isLoading, isFetching, error } = useGetVesselReportQuery(query); + + // Dev-only stand-in so the dashboard can be reviewed before the endpoint is + // live. Production still shows the empty state and the error alert. + const report = data ?? (import.meta.env.DEV ? MOCK_VESSEL_REPORT : undefined); const exportCsv = useCallback(async () => { setExporting(true); diff --git a/apps/backoffice/src/app/features/vessel-registration/pages/VesselRegistrationReportPage/mock-report.ts b/apps/backoffice/src/app/features/vessel-registration/pages/VesselRegistrationReportPage/mock-report.ts new file mode 100644 index 000000000..72ebea83d --- /dev/null +++ b/apps/backoffice/src/app/features/vessel-registration/pages/VesselRegistrationReportPage/mock-report.ts @@ -0,0 +1,257 @@ +import type { BreakdownItem, VesselReport } from '@ema-platform/api'; + +/** + * A stand-in report for previewing the dashboard before `GET /vessels/report` + * is live. Only used in dev, and only when the real call gives nothing back. + * + * ponytail: hand-written fixture, delete once the endpoint answers. + */ + +const MONTHS = [ + '2025-09-01', '2025-10-01', '2025-11-01', '2025-12-01', + '2026-01-01', '2026-02-01', '2026-03-01', '2026-04-01', + '2026-05-01', '2026-06-01', '2026-07-01', '2026-08-01', +]; + +/** Turns `[label, count]` pairs into slices whose percentages total 100. */ +function breakdown(pairs: [string, number][]): BreakdownItem[] { + const total = pairs.reduce((sum, [, count]) => sum + count, 0) || 1; + return pairs.map(([label, count]) => ({ + key: label.toUpperCase().replace(/[^A-Z0-9]+/g, '_'), + label, + count, + percentage: Math.round((count / total) * 1000) / 10, + })); +} + +const REGISTRATIONS = [18, 24, 21, 30, 27, 33, 29, 38, 35, 41, 37, 26]; +const SUBMITTED = [26, 31, 28, 39, 34, 42, 37, 47, 44, 51, 46, 33]; + +export const MOCK_VESSEL_REPORT: VesselReport = { + generatedAt: '2026-08-18T09:15:00.000Z', + truncated: false, + filters: { + from: '2025-09-01', + to: '2026-08-18', + granularity: 'MONTH', + expiringWithinDays: 90, + topN: 8, + tableLimit: 10, + category: null, + status: null, + flagState: null, + portOfRegistry: null, + vesselType: null, + search: null, + }, + kpis: { + register: { + total: 1284, + registered: 1096, + suspended: 87, + deregistered: 101, + registeredInPeriod: 359, + registeredInPreviousPeriod: 302, + changePct: 18.9, + }, + fleet: { + totalGrossTonnage: 486_320, + avgGrossTonnage: 412.7, + grossTonnageKnownFor: 1178, + totalPassengerCapacity: 39_450, + avgLengthMeters: 28.4, + avgAgeYears: 16.2, + ageKnownFor: 1141, + seaGoing: 512, + inlandWaterway: 772, + }, + pipeline: { + total: 642, + draft: 48, + inProgress: 113, + approved: 351, + rejected: 62, + issued: 337, + submittedInPeriod: 458, + decidedInPeriod: 396, + newCount: 421, + renewalCount: 221, + approvalRatePct: 85, + avgProcessingDays: 11.4, + medianProcessingDays: 9, + avgAdjustmentRounds: 1.3, + }, + certificates: { + total: 1096, + active: 934, + expired: 118, + suspended: 44, + expiringIn30: 37, + expiringIn60: 71, + expiringIn90: 112, + missingCertificate: 23, + }, + incidents: { + total: 96, + inPeriod: 41, + reportedByOfficer: 58, + reportedByOwner: 38, + vesselsWithIncidents: 74, + }, + revenue: { + currency: 'ETB', + mixedCurrency: false, + paid: 4_812_500, + pending: 638_000, + paidCount: 389, + pendingCount: 54, + failedCount: 12, + }, + }, + timeSeries: { + registrations: MONTHS.map((bucket, i) => ({ + bucket, + count: REGISTRATIONS[i], + grossTonnage: REGISTRATIONS[i] * 380 + i * 260, + })), + applications: MONTHS.map((bucket, i) => ({ + bucket, + submitted: SUBMITTED[i], + approved: Math.round(SUBMITTED[i] * 0.72), + rejected: Math.round(SUBMITTED[i] * 0.11), + issued: Math.round(SUBMITTED[i] * 0.68), + })), + incidents: MONTHS.map((bucket, i) => ({ + bucket, + count: [4, 6, 3, 9, 5, 7, 4, 11, 8, 6, 9, 5][i], + })), + revenue: MONTHS.map((bucket, i) => ({ + bucket, + amount: 280_000 + REGISTRATIONS[i] * 9_400, + count: SUBMITTED[i], + })), + }, + breakdowns: { + byStatus: breakdown([ + ['Registered', 1096], + ['Suspended', 87], + ['Deregistered', 101], + ]), + byCategory: breakdown([ + ['Inland waterway', 772], + ['Sea-going', 512], + ]), + byFlagState: breakdown([ + ['Ethiopia', 918], ['Djibouti', 142], ['Panama', 88], ['Liberia', 54], + ['Marshall Islands', 37], ['Malta', 21], ['Other', 24], + ]), + byPortOfRegistry: breakdown([ + ['Bahir Dar', 286], ['Djibouti', 241], ['Gambella', 198], + ['Hawassa', 176], ['Arba Minch', 154], ['Tana Kirkos', 121], + ['Other', 108], + ]), + byVesselType: breakdown([ + ['Passenger ferry', 341], ['Cargo', 289], ['Fishing', 214], + ['Tug', 132], ['Barge', 118], ['Tanker', 76], ['Research', 41], + ['Other', 73], + ]), + byHullMaterial: breakdown([ + ['Steel', 612], ['Aluminium', 288], ['Fibreglass', 241], + ['Wood', 108], ['Composite', 35], + ]), + byEngineType: breakdown([ + ['Diesel', 874], ['Outboard petrol', 246], ['Electric', 87], + ['Hybrid', 44], ['Sail / none', 33], + ]), + byTonnageBand: breakdown([ + ['< 50 GT', 402], ['50-199 GT', 348], ['200-499 GT', 241], + ['500-999 GT', 152], ['1000+ GT', 141], + ]), + byLengthBand: breakdown([ + ['< 12 m', 318], ['12-24 m', 421], ['24-50 m', 336], + ['50-100 m', 148], ['100 m+', 61], + ]), + byAgeBand: breakdown([ + ['0-5 yrs', 214], ['6-10 yrs', 287], ['11-20 yrs', 396], + ['21-30 yrs', 241], ['30+ yrs', 146], + ]), + byBuildDecade: breakdown([ + ['1980s', 96], ['1990s', 187], ['2000s', 341], + ['2010s', 452], ['2020s', 208], + ]), + byApplicationStatus: breakdown([ + ['Issued', 337], ['Approved', 14], ['Under review', 113], + ['Draft', 48], ['Rejected', 62], ['Correction required', 68], + ]), + byApplicationKind: breakdown([ + ['New', 421], + ['Renewal', 221], + ]), + byOfficer: [ + { key: '5f1c1b8e-0001-4a11-9c31-0a1b2c3d4e01', label: 'Selam Bekele', count: 148, percentage: 23.1 }, + { key: '5f1c1b8e-0002-4a11-9c31-0a1b2c3d4e02', label: 'Dawit Tesfaye', count: 132, percentage: 20.6 }, + { key: '5f1c1b8e-0003-4a11-9c31-0a1b2c3d4e03', label: 'Hanna Girma', count: 117, percentage: 18.2 }, + { key: '5f1c1b8e-0004-4a11-9c31-0a1b2c3d4e04', label: 'Yonas Alemu', count: 94, percentage: 14.6 }, + { key: '5f1c1b8e-0005-4a11-9c31-0a1b2c3d4e05', label: 'Meron Assefa', count: 78, percentage: 12.1 }, + { key: 'UNASSIGNED', label: 'Unassigned', count: 73, percentage: 11.4 }, + ], + byIncidentSeverity: breakdown([ + ['Minor', 51], ['Moderate', 28], ['Major', 13], ['Critical', 4], + ]), + }, + tables: { + expiringCertificates: [ + { registrationNumber: 'ET-VR-004182', name: 'Abay Star', ownerName: 'Blue Nile Transport PLC', certificateNumber: 'CERT-2023-004182', expiryDate: '2026-08-24', daysToExpiry: 6 }, + { registrationNumber: 'ET-VR-003907', name: 'Tana Pearl', ownerName: 'Lake Tana Ferries', certificateNumber: 'CERT-2023-003907', expiryDate: '2026-09-02', daysToExpiry: 15 }, + { registrationNumber: 'ET-VR-004411', name: 'Gambella Express', ownerName: 'Baro River Logistics', certificateNumber: 'CERT-2023-004411', expiryDate: '2026-09-11', daysToExpiry: 24 }, + { registrationNumber: 'ET-VR-003655', name: 'Awash Carrier', ownerName: 'Awash Marine Services', certificateNumber: 'CERT-2023-003655', expiryDate: '2026-09-19', daysToExpiry: 32 }, + { registrationNumber: 'ET-VR-004098', name: 'Rift Valley II', ownerName: 'Hawassa Boat Union', certificateNumber: 'CERT-2023-004098', expiryDate: '2026-10-04', daysToExpiry: 47 }, + { registrationNumber: 'ET-VR-004320', name: 'Omo Trader', ownerName: 'Omo Cargo Co.', certificateNumber: 'CERT-2023-004320', expiryDate: '2026-10-18', daysToExpiry: 61 }, + { registrationNumber: 'ET-VR-003812', name: 'Zeway Runner', ownerName: 'Zeway Fisheries', certificateNumber: 'CERT-2023-003812', expiryDate: '2026-11-01', daysToExpiry: 75 }, + { registrationNumber: 'ET-VR-004501', name: 'Djibouti Link', ownerName: 'Horn Shipping Ltd', certificateNumber: 'CERT-2023-004501', expiryDate: '2026-11-13', daysToExpiry: 87 }, + ].map((row, i) => ({ + ...row, + vesselId: `9a0e0000-0000-4000-8000-0000000${1000 + i}`, + ownerUserId: `7b1f0000-0000-4000-8000-0000000${2000 + i}`, + certificateStatus: 'ACTIVE', + })), + recentRegistrations: ([ + { registrationNumber: 'ET-VR-004612', name: 'Blue Horizon', category: 'SEA_GOING', vesselType: 'Cargo', flagState: 'Ethiopia', grossTonnage: 1840, ownerName: 'Horn Shipping Ltd', status: 'REGISTERED', registeredAt: '2026-08-16T08:20:00.000Z' }, + { registrationNumber: 'ET-VR-004611', name: 'Tana Breeze', category: 'INLAND_WATERWAY', vesselType: 'Passenger ferry', flagState: 'Ethiopia', grossTonnage: 96, ownerName: 'Lake Tana Ferries', status: 'REGISTERED', registeredAt: '2026-08-15T13:05:00.000Z' }, + { registrationNumber: 'ET-VR-004610', name: 'Baro Voyager', category: 'INLAND_WATERWAY', vesselType: 'Barge', flagState: 'Ethiopia', grossTonnage: 240, ownerName: 'Baro River Logistics', status: 'REGISTERED', registeredAt: '2026-08-14T10:41:00.000Z' }, + { registrationNumber: 'ET-VR-004609', name: 'Red Sea Falcon', category: 'SEA_GOING', vesselType: 'Tanker', flagState: 'Djibouti', grossTonnage: 3210, ownerName: 'Horn Shipping Ltd', status: 'SUSPENDED', registeredAt: '2026-08-12T15:32:00.000Z' }, + { registrationNumber: 'ET-VR-004608', name: 'Hawassa Queen', category: 'INLAND_WATERWAY', vesselType: 'Passenger ferry', flagState: 'Ethiopia', grossTonnage: 74, ownerName: 'Hawassa Boat Union', status: 'REGISTERED', registeredAt: '2026-08-11T09:12:00.000Z' }, + { registrationNumber: 'ET-VR-004607', name: 'Omo Spirit', category: 'INLAND_WATERWAY', vesselType: 'Fishing', flagState: 'Ethiopia', grossTonnage: 32, ownerName: 'Omo Cargo Co.', status: 'REGISTERED', registeredAt: '2026-08-09T07:55:00.000Z' }, + { registrationNumber: 'ET-VR-004606', name: 'Awash Pioneer', category: 'SEA_GOING', vesselType: 'Cargo', flagState: 'Panama', grossTonnage: 2105, ownerName: 'Awash Marine Services', status: 'REGISTERED', registeredAt: '2026-08-07T11:26:00.000Z' }, + { registrationNumber: 'ET-VR-004605', name: 'Gibe Runner', category: 'INLAND_WATERWAY', vesselType: 'Tug', flagState: 'Ethiopia', grossTonnage: 118, ownerName: 'Gibe Marine PLC', status: 'DEREGISTERED', registeredAt: '2026-08-04T14:03:00.000Z' }, + ] as const).map((row, i) => ({ + ...row, + vesselId: `9a0e0000-0000-4000-8000-0000000${3000 + i}`, + })), + recentIncidents: [ + { registrationNumber: 'ET-VR-004182', vesselName: 'Abay Star', occurredAt: '2026-08-15T06:40:00.000Z', severity: 'Moderate', location: 'Lake Tana, near Gorgora', description: 'Engine room flooding after a seal failure; vessel towed to port under escort.', reportedByOfficer: true }, + { registrationNumber: 'ET-VR-004609', vesselName: 'Red Sea Falcon', occurredAt: '2026-08-10T18:12:00.000Z', severity: 'Major', location: 'Gulf of Tadjoura', description: 'Collision with a moored barge during night manoeuvring. Hull plating breached above the waterline.', reportedByOfficer: true }, + { registrationNumber: 'ET-VR-003907', vesselName: 'Tana Pearl', occurredAt: '2026-08-06T12:30:00.000Z', severity: 'Minor', location: 'Bahir Dar jetty', description: 'Passenger slipped on a wet gangway; treated on shore, no hospitalisation.', reportedByOfficer: false }, + { registrationNumber: 'ET-VR-004411', vesselName: 'Gambella Express', occurredAt: '2026-07-29T09:05:00.000Z', severity: 'Moderate', location: 'Baro River, km 42', description: 'Grounded on a sandbar for six hours at low water. No pollution reported.', reportedByOfficer: false }, + { registrationNumber: 'ET-VR-004098', vesselName: 'Rift Valley II', occurredAt: '2026-07-21T16:48:00.000Z', severity: 'Critical', location: 'Lake Hawassa, south shore', description: 'Fire in the galley spread to the passenger deck; 3 injured, vessel out of service.', reportedByOfficer: true }, + { registrationNumber: 'ET-VR-004320', vesselName: 'Omo Trader', occurredAt: '2026-07-14T05:15:00.000Z', severity: 'Minor', location: 'Omo River delta', description: 'Steering hydraulics lost pressure; repaired at berth the same day.', reportedByOfficer: false }, + ].map((row, i) => ({ + ...row, + id: `1c2d0000-0000-4000-8000-0000000${4000 + i}`, + vesselId: `9a0e0000-0000-4000-8000-0000000${1000 + i}`, + })), + pendingApplications: ([ + { applicationNumber: 'VR-2026-000841', status: 'UNDER_REVIEW', kind: 'NEW', submittedAt: '2026-08-01T08:00:00.000Z', adjustmentRound: 0, daysOpen: 17 }, + { applicationNumber: 'VR-2026-000836', status: 'CORRECTION_REQUIRED', kind: 'RENEWAL', submittedAt: '2026-07-28T10:22:00.000Z', adjustmentRound: 2, daysOpen: 21 }, + { applicationNumber: 'VR-2026-000829', status: 'UNDER_REVIEW', kind: 'NEW', submittedAt: '2026-07-24T09:14:00.000Z', adjustmentRound: 1, daysOpen: 25 }, + { applicationNumber: 'VR-2026-000818', status: 'RESUBMITTED', kind: 'NEW', submittedAt: '2026-07-19T13:40:00.000Z', adjustmentRound: 1, daysOpen: 30 }, + { applicationNumber: 'VR-2026-000804', status: 'UNDER_REVIEW', kind: 'RENEWAL', submittedAt: '2026-07-11T07:52:00.000Z', adjustmentRound: 0, daysOpen: 38 }, + { applicationNumber: 'VR-2026-000791', status: 'CORRECTION_REQUIRED', kind: 'NEW', submittedAt: '2026-07-02T15:31:00.000Z', adjustmentRound: 3, daysOpen: 47 }, + { applicationNumber: 'VR-2026-000783', status: 'SUBMITTED', kind: 'RENEWAL', submittedAt: '2026-06-26T11:09:00.000Z', adjustmentRound: 0, daysOpen: 53 }, + ] as const).map((row, i) => ({ + ...row, + assignedOfficerId: + i % 3 === 2 ? null : `5f1c1b8e-000${(i % 5) + 1}-4a11-9c31-0a1b2c3d4e0${(i % 5) + 1}`, + })), + }, +};