mock data for test

This commit is contained in:
estifanos
2026-08-18 09:34:44 +00:00
parent fbe9129fb7
commit 02b9c73caf
2 changed files with 263 additions and 3 deletions

View File

@@ -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);

View File

@@ -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}`,
})),
},
};