mirror of
https://github.com/Tria-plc/emaui.git
synced 2026-08-26 19:12:50 +00:00
Adding more functionalities for all the license types
This commit is contained in:
@@ -2,4 +2,7 @@ export * from './lib/base-api';
|
||||
export * from './lib/query-and-mutation';
|
||||
export * from './lib/session';
|
||||
export * from './lib/features/licensing';
|
||||
export * from './lib/features/seafarer';
|
||||
export * from './lib/features/vessel';
|
||||
export { baseQueryWithReauth, configureTokenRefresh } from './lib/base-api/base-query-with-reauth';
|
||||
export { openAuthedDocument } from './lib/base-api/download';
|
||||
|
||||
@@ -2,7 +2,7 @@ import { fetchBaseQuery, type BaseQueryFn } from '@reduxjs/toolkit/query/react';
|
||||
import type { FetchArgs, FetchBaseQueryError } from '@reduxjs/toolkit/query';
|
||||
import { resolveSessionContext } from '../session';
|
||||
|
||||
const BASE_API_URL =
|
||||
export const BASE_API_URL =
|
||||
(import.meta as { env?: Record<string, string> }).env?.['VITE_BASE_API_URL'] ??
|
||||
'http://localhost:3001/api';
|
||||
|
||||
|
||||
43
libs/api/src/lib/base-api/download.ts
Normal file
43
libs/api/src/lib/base-api/download.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import { resolveTokenFromStorage } from '../session';
|
||||
import { BASE_API_URL } from './base-query-with-reauth';
|
||||
|
||||
/**
|
||||
* Opens an authenticated binary endpoint (a rendered PDF) in a new tab.
|
||||
*
|
||||
* RTK Query is not used here: `fetchBaseQuery` parses responses as JSON, and
|
||||
* a plain `window.open` sends no Authorization header, so a guarded document
|
||||
* endpoint would answer 401. Fetching to a blob keeps the bearer token on the
|
||||
* request and still gives the browser something it can display.
|
||||
*/
|
||||
export async function openAuthedDocument(
|
||||
path: string,
|
||||
fallbackName = 'document.pdf',
|
||||
): Promise<void> {
|
||||
const token = resolveTokenFromStorage();
|
||||
const response = await fetch(`${BASE_API_URL}${path}`, {
|
||||
headers: token ? { Authorization: `Bearer ${token}` } : {},
|
||||
});
|
||||
if (!response.ok) {
|
||||
// The body carries the API's error key, which callers surface verbatim.
|
||||
let message = `${response.status}`;
|
||||
try {
|
||||
const body = await response.json();
|
||||
message = body?.message ?? message;
|
||||
} catch {
|
||||
/* non-JSON error body — the status is all we have */
|
||||
}
|
||||
throw new Error(message);
|
||||
}
|
||||
const blob = await response.blob();
|
||||
const url = URL.createObjectURL(blob);
|
||||
const opened = window.open(url, '_blank', 'noopener');
|
||||
if (!opened) {
|
||||
// Pop-up blocked: fall back to a direct download so the click still works.
|
||||
const anchor = document.createElement('a');
|
||||
anchor.href = url;
|
||||
anchor.download = fallbackName;
|
||||
anchor.click();
|
||||
}
|
||||
// Revoking immediately would race the new tab's load.
|
||||
setTimeout(() => URL.revokeObjectURL(url), 60_000);
|
||||
}
|
||||
@@ -308,6 +308,39 @@ export const licensingApi = baseApi
|
||||
}),
|
||||
|
||||
// ------------------------------------------------------------ licences
|
||||
/**
|
||||
* Public QR-code verification (US-PORTAL-008). No auth required —
|
||||
* the endpoint returns only what a verifier needs to trust the
|
||||
* document, never the holder's contact details.
|
||||
*/
|
||||
verifyCertificate: builder.query<
|
||||
{
|
||||
valid: boolean;
|
||||
reason?: string;
|
||||
certificateNumber?: string;
|
||||
licenseType?: string | null;
|
||||
companyName?: string | null;
|
||||
issueDate?: string;
|
||||
expiryDate?: string;
|
||||
status?: string;
|
||||
},
|
||||
string
|
||||
>({
|
||||
query: (code) => ({ url: `/licenses/verify/${code}` }),
|
||||
}),
|
||||
|
||||
/** The licence register for enforcement officers. */
|
||||
getLicenses: builder.query<
|
||||
Paginated<IssuedLicense>,
|
||||
{ search?: string } | void
|
||||
>({
|
||||
query: (args) => ({
|
||||
url: '/licenses',
|
||||
params: args?.search ? { search: args.search } : undefined,
|
||||
}),
|
||||
providesTags: () => [listTag('License')],
|
||||
}),
|
||||
|
||||
getMyLicenses: builder.query<Paginated<IssuedLicense>, void>({
|
||||
query: () => ({ url: '/licenses/mine' }),
|
||||
providesTags: () => [listTag('License')],
|
||||
@@ -693,12 +726,24 @@ export const licensingApi = baseApi
|
||||
|
||||
recordInspectionResult: builder.mutation<
|
||||
Inspection,
|
||||
{ inspectionId: string; applicationId: string; result: 'PASSED' | 'FAILED'; findings: string }
|
||||
{
|
||||
inspectionId: string;
|
||||
applicationId: string;
|
||||
result: 'PASSED' | 'FAILED';
|
||||
findings: string;
|
||||
/** Structured per-item outcomes (office, warehouse, vehicles, …). */
|
||||
checklist?: {
|
||||
key: string;
|
||||
label: string;
|
||||
outcome: 'PASS' | 'FAIL' | 'NEEDS_CORRECTION';
|
||||
note?: string;
|
||||
}[];
|
||||
}
|
||||
>({
|
||||
query: ({ inspectionId, result, findings }) => ({
|
||||
query: ({ inspectionId, result, findings, checklist }) => ({
|
||||
url: `/inspections/${inspectionId}/result`,
|
||||
method: 'PATCH',
|
||||
body: { result, findings },
|
||||
body: { result, findings, ...(checklist?.length ? { checklist } : {}) },
|
||||
}),
|
||||
invalidatesTags: (_r, error, { applicationId }) =>
|
||||
error ? [] : [itemTag('LicenseApplication', applicationId), listTag('Inspection')],
|
||||
@@ -724,6 +769,7 @@ export const licensingApi = baseApi
|
||||
});
|
||||
|
||||
export const {
|
||||
useVerifyCertificateQuery,
|
||||
useGetLicenseTypesQuery,
|
||||
useGetLicenseCategoriesQuery,
|
||||
useUpdateLicenseFeesMutation,
|
||||
@@ -736,6 +782,7 @@ export const {
|
||||
useBypassPaymentMutation,
|
||||
useGetPaymentCapabilitiesQuery,
|
||||
useGetMyLicensesQuery,
|
||||
useGetLicensesQuery,
|
||||
useGetCertificateUrlMutation,
|
||||
useGetApplicationPaymentQuery,
|
||||
usePatchSectionMutation,
|
||||
|
||||
@@ -18,7 +18,13 @@ const BASE_API_URL =
|
||||
* attach them to, and nothing can be lost if the browser closes mid-wizard.
|
||||
*/
|
||||
export async function uploadDocument(params: {
|
||||
ownerType: 'APPLICATION' | 'APPLICATION_STAFF' | 'INSPECTION' | 'LICENSE';
|
||||
ownerType:
|
||||
| 'APPLICATION'
|
||||
| 'APPLICATION_STAFF'
|
||||
| 'INSPECTION'
|
||||
| 'LICENSE'
|
||||
| 'SEA_SERVICE_RECORD'
|
||||
| 'MEDICAL_CERTIFICATE';
|
||||
ownerId: string;
|
||||
documentKey: string;
|
||||
file: File;
|
||||
@@ -210,6 +216,14 @@ export interface WizardStep {
|
||||
export function buildWizardSteps(
|
||||
sections: FormSectionConfig[],
|
||||
formData: Record<string, Record<string, unknown>>,
|
||||
options?: {
|
||||
/**
|
||||
* Whether this licence type has staff-role requirements at all. Types
|
||||
* without any (seafarer registration) skip the Staff step entirely
|
||||
* instead of showing an empty page.
|
||||
*/
|
||||
hasStaff?: boolean;
|
||||
},
|
||||
): WizardStep[] {
|
||||
const visible = [...sections]
|
||||
.filter((section) => conditionHolds(section.showWhen, formData))
|
||||
@@ -259,7 +273,9 @@ export function buildWizardSteps(
|
||||
|
||||
return [
|
||||
...formSteps,
|
||||
{ key: 'staff', label: 'Staff', kind: 'staff', sections: [] },
|
||||
...(options?.hasStaff === false
|
||||
? []
|
||||
: [{ key: 'staff', label: 'Staff', kind: 'staff', sections: [] } as WizardStep]),
|
||||
{ key: 'documents', label: 'Documents', kind: 'documents', sections: [] },
|
||||
{ key: 'review', label: 'Review', kind: 'review', sections: reviewSections },
|
||||
];
|
||||
|
||||
@@ -77,7 +77,8 @@ export interface FormSectionConfig {
|
||||
export type LicenseCategory =
|
||||
| 'CARGO_FREIGHT'
|
||||
| 'SHIPPING_AGENCY'
|
||||
| 'INVESTMENT';
|
||||
| 'INVESTMENT'
|
||||
| 'MARITIME_PERSONNEL';
|
||||
|
||||
export interface LicenseCategoryDefinition {
|
||||
key: LicenseCategory;
|
||||
@@ -119,6 +120,12 @@ export interface LicenseType {
|
||||
inspectionRequired: boolean;
|
||||
issuesCertificate: boolean;
|
||||
renewalEnabled: boolean;
|
||||
/**
|
||||
* False for person-centric registrations (seafarer): they are open to any
|
||||
* authenticated applicant, live outside the operator catalogue, and have
|
||||
* their own entry points.
|
||||
*/
|
||||
requiresOperatorMode: boolean;
|
||||
formSchema: { sections: FormSectionConfig[] };
|
||||
isActive: boolean;
|
||||
/** Display order set by EMA; lower comes first. */
|
||||
|
||||
2
libs/api/src/lib/features/seafarer/index.ts
Normal file
2
libs/api/src/lib/features/seafarer/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './seafarer.types';
|
||||
export * from './seafarer-api';
|
||||
188
libs/api/src/lib/features/seafarer/seafarer-api.ts
Normal file
188
libs/api/src/lib/features/seafarer/seafarer-api.ts
Normal file
@@ -0,0 +1,188 @@
|
||||
import { baseApi } from '../../base-api';
|
||||
import type {
|
||||
CreateMedicalCertificate,
|
||||
CreateSeaServiceRecord,
|
||||
MedicalCertificate,
|
||||
SeaServiceRecord,
|
||||
SeaTimeSummary,
|
||||
SeafarerStatus,
|
||||
} from './seafarer.types';
|
||||
|
||||
const TAGS = ['SeaServiceRecord', 'MedicalCertificate'] as const;
|
||||
|
||||
const listTag = (type: (typeof TAGS)[number]) => ({ type, id: 'LIST' }) as const;
|
||||
|
||||
/**
|
||||
* The seafarer's evidence shelf: sea-service records and medical
|
||||
* certificates (modules 05/06). Registration itself rides the licensing
|
||||
* endpoints — a SEAFARER_REGISTRATION application through `licensingApi`.
|
||||
*/
|
||||
export const seafarerApi = baseApi
|
||||
.enhanceEndpoints({ addTagTypes: TAGS })
|
||||
.injectEndpoints({
|
||||
endpoints: (builder) => ({
|
||||
// ---------------------------------------------------------- sea service
|
||||
getMySeaServiceRecords: builder.query<SeaServiceRecord[], void>({
|
||||
query: () => ({ url: '/sea-service-records/mine' }),
|
||||
providesTags: () => [listTag('SeaServiceRecord')],
|
||||
}),
|
||||
|
||||
getSeaServiceForProfile: builder.query<SeaServiceRecord[], string>({
|
||||
query: (profileId) => ({
|
||||
url: `/sea-service-records/profile/${profileId}`,
|
||||
}),
|
||||
providesTags: () => [listTag('SeaServiceRecord')],
|
||||
}),
|
||||
|
||||
createSeaServiceRecord: builder.mutation<
|
||||
SeaServiceRecord,
|
||||
CreateSeaServiceRecord
|
||||
>({
|
||||
query: (body) => ({ url: '/sea-service-records', method: 'POST', body }),
|
||||
invalidatesTags: (_r, error) =>
|
||||
error ? [] : [listTag('SeaServiceRecord')],
|
||||
}),
|
||||
|
||||
updateSeaServiceRecord: builder.mutation<
|
||||
SeaServiceRecord,
|
||||
{ id: string; body: Partial<CreateSeaServiceRecord> }
|
||||
>({
|
||||
query: ({ id, body }) => ({
|
||||
url: `/sea-service-records/${id}`,
|
||||
method: 'PUT',
|
||||
body,
|
||||
}),
|
||||
invalidatesTags: (_r, error) =>
|
||||
error ? [] : [listTag('SeaServiceRecord')],
|
||||
}),
|
||||
|
||||
deleteSeaServiceRecord: builder.mutation<unknown, string>({
|
||||
query: (id) => ({ url: `/sea-service-records/${id}`, method: 'DELETE' }),
|
||||
invalidatesTags: (_r, error) =>
|
||||
error ? [] : [listTag('SeaServiceRecord')],
|
||||
}),
|
||||
|
||||
// -------------------------------------------------------------- medical
|
||||
getMyMedicalCertificates: builder.query<MedicalCertificate[], void>({
|
||||
query: () => ({ url: '/medical-certificates/mine' }),
|
||||
providesTags: () => [listTag('MedicalCertificate')],
|
||||
}),
|
||||
|
||||
getMedicalForProfile: builder.query<MedicalCertificate[], string>({
|
||||
query: (profileId) => ({
|
||||
url: `/medical-certificates/profile/${profileId}`,
|
||||
}),
|
||||
providesTags: () => [listTag('MedicalCertificate')],
|
||||
}),
|
||||
|
||||
createMedicalCertificate: builder.mutation<
|
||||
MedicalCertificate,
|
||||
CreateMedicalCertificate
|
||||
>({
|
||||
query: (body) => ({ url: '/medical-certificates', method: 'POST', body }),
|
||||
invalidatesTags: (_r, error) =>
|
||||
error ? [] : [listTag('MedicalCertificate')],
|
||||
}),
|
||||
|
||||
updateMedicalCertificate: builder.mutation<
|
||||
MedicalCertificate,
|
||||
{ id: string; body: Partial<CreateMedicalCertificate> }
|
||||
>({
|
||||
query: ({ id, body }) => ({
|
||||
url: `/medical-certificates/${id}`,
|
||||
method: 'PUT',
|
||||
body,
|
||||
}),
|
||||
invalidatesTags: (_r, error) =>
|
||||
error ? [] : [listTag('MedicalCertificate')],
|
||||
}),
|
||||
|
||||
deleteMedicalCertificate: builder.mutation<unknown, string>({
|
||||
query: (id) => ({ url: `/medical-certificates/${id}`, method: 'DELETE' }),
|
||||
invalidatesTags: (_r, error) =>
|
||||
error ? [] : [listTag('MedicalCertificate')],
|
||||
}),
|
||||
|
||||
// -------------------------------------------------------- verification
|
||||
getPendingSeaService: builder.query<SeaServiceRecord[], void>({
|
||||
query: () => ({ url: '/sea-service-records/pending' }),
|
||||
providesTags: () => [listTag('SeaServiceRecord')],
|
||||
}),
|
||||
|
||||
getPendingMedical: builder.query<MedicalCertificate[], void>({
|
||||
query: () => ({ url: '/medical-certificates/pending' }),
|
||||
providesTags: () => [listTag('MedicalCertificate')],
|
||||
}),
|
||||
|
||||
verifySeaServiceRecord: builder.mutation<
|
||||
SeaServiceRecord,
|
||||
{ id: string; outcome: 'VERIFIED' | 'REJECTED'; remark?: string }
|
||||
>({
|
||||
query: ({ id, ...body }) => ({
|
||||
url: `/sea-service-records/${id}/verify`,
|
||||
method: 'POST',
|
||||
body,
|
||||
}),
|
||||
invalidatesTags: (_r, error) =>
|
||||
error ? [] : [listTag('SeaServiceRecord')],
|
||||
}),
|
||||
|
||||
verifyMedicalCertificate: builder.mutation<
|
||||
MedicalCertificate,
|
||||
{ id: string; outcome: 'VERIFIED' | 'REJECTED'; remark?: string }
|
||||
>({
|
||||
query: ({ id, ...body }) => ({
|
||||
url: `/medical-certificates/${id}/verify`,
|
||||
method: 'POST',
|
||||
body,
|
||||
}),
|
||||
invalidatesTags: (_r, error) =>
|
||||
error ? [] : [listTag('MedicalCertificate')],
|
||||
}),
|
||||
|
||||
getMySeaTime: builder.query<SeaTimeSummary, void>({
|
||||
query: () => ({ url: '/sea-service-records/mine/sea-time' }),
|
||||
providesTags: () => [listTag('SeaServiceRecord')],
|
||||
}),
|
||||
|
||||
getSeaTimeForProfile: builder.query<SeaTimeSummary, string>({
|
||||
query: (profileId) => ({
|
||||
url: `/sea-service-records/profile/${profileId}/sea-time`,
|
||||
}),
|
||||
providesTags: () => [listTag('SeaServiceRecord')],
|
||||
}),
|
||||
|
||||
// ----------------------------------------------- registry (backoffice)
|
||||
/** US-SEA-013: suspend, reinstate or close with a mandatory reason. */
|
||||
updateSeafarerStatus: builder.mutation<
|
||||
unknown,
|
||||
{ profileId: string; status: SeafarerStatus; reason: string }
|
||||
>({
|
||||
query: ({ profileId, ...body }) => ({
|
||||
url: `/profiles/${profileId}/seafarer-status`,
|
||||
method: 'POST',
|
||||
body,
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
export const {
|
||||
useGetPendingSeaServiceQuery,
|
||||
useGetPendingMedicalQuery,
|
||||
useVerifySeaServiceRecordMutation,
|
||||
useVerifyMedicalCertificateMutation,
|
||||
useGetMySeaTimeQuery,
|
||||
useGetSeaTimeForProfileQuery,
|
||||
useGetMySeaServiceRecordsQuery,
|
||||
useGetSeaServiceForProfileQuery,
|
||||
useCreateSeaServiceRecordMutation,
|
||||
useUpdateSeaServiceRecordMutation,
|
||||
useDeleteSeaServiceRecordMutation,
|
||||
useGetMyMedicalCertificatesQuery,
|
||||
useGetMedicalForProfileQuery,
|
||||
useCreateMedicalCertificateMutation,
|
||||
useUpdateMedicalCertificateMutation,
|
||||
useDeleteMedicalCertificateMutation,
|
||||
useUpdateSeafarerStatusMutation,
|
||||
} = seafarerApi;
|
||||
78
libs/api/src/lib/features/seafarer/seafarer.types.ts
Normal file
78
libs/api/src/lib/features/seafarer/seafarer.types.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
export type SeafarerRecordStatus = 'SUBMITTED' | 'VERIFIED' | 'REJECTED';
|
||||
export type MedicalFitness = 'FIT' | 'FIT_WITH_RESTRICTIONS' | 'UNFIT';
|
||||
export type SeafarerDepartment = 'DECK' | 'ENGINE' | 'CATERING';
|
||||
export type SeafarerStatus = 'ACTIVE' | 'INACTIVE' | 'PENDING' | 'SUSPENDED';
|
||||
|
||||
/** One engagement aboard one vessel (US-SSM-001). */
|
||||
/** Denormalised owner summary, present on the verification queues. */
|
||||
export interface SeafarerProfileSummary {
|
||||
id: string;
|
||||
firstName: string | null;
|
||||
middleName: string | null;
|
||||
lastName: string | null;
|
||||
seafarerNumber?: string | null;
|
||||
}
|
||||
|
||||
export interface SeaServiceRecord {
|
||||
id: string;
|
||||
profileId: string;
|
||||
profile?: SeafarerProfileSummary;
|
||||
vesselName: string;
|
||||
imoNumber: string | null;
|
||||
vesselType: string | null;
|
||||
flagState: string | null;
|
||||
grossTonnage: string | number | null;
|
||||
rank: string;
|
||||
engagementDate: string;
|
||||
dischargeDate: string;
|
||||
dutiesDescription: string | null;
|
||||
status: SeafarerRecordStatus;
|
||||
verifiedById: string | null;
|
||||
verifiedAt: string | null;
|
||||
verificationRemark: string | null;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
export interface CreateSeaServiceRecord {
|
||||
vesselName: string;
|
||||
imoNumber?: string;
|
||||
vesselType?: string;
|
||||
flagState?: string;
|
||||
grossTonnage?: number;
|
||||
rank: string;
|
||||
engagementDate: string;
|
||||
dischargeDate: string;
|
||||
dutiesDescription?: string;
|
||||
}
|
||||
|
||||
/** An STCW medical fitness certificate (US-SSM-006). */
|
||||
export interface MedicalCertificate {
|
||||
id: string;
|
||||
profileId: string;
|
||||
profile?: SeafarerProfileSummary;
|
||||
issuerName: string;
|
||||
certificateNumber: string | null;
|
||||
issueDate: string;
|
||||
expiryDate: string;
|
||||
fitnessStatus: MedicalFitness;
|
||||
restrictions: string | null;
|
||||
status: SeafarerRecordStatus;
|
||||
verifiedById: string | null;
|
||||
verifiedAt: string | null;
|
||||
verificationRemark: string | null;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
export interface CreateMedicalCertificate {
|
||||
issuerName: string;
|
||||
certificateNumber?: string;
|
||||
issueDate: string;
|
||||
expiryDate: string;
|
||||
fitnessStatus?: MedicalFitness;
|
||||
restrictions?: string;
|
||||
}
|
||||
|
||||
export interface SeaTimeSummary {
|
||||
totalDays: number;
|
||||
verifiedRecords: number;
|
||||
}
|
||||
2
libs/api/src/lib/features/vessel/index.ts
Normal file
2
libs/api/src/lib/features/vessel/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './vessel.types';
|
||||
export * from './vessel-api';
|
||||
84
libs/api/src/lib/features/vessel/vessel-api.ts
Normal file
84
libs/api/src/lib/features/vessel/vessel-api.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
import { baseApi } from '../../base-api';
|
||||
import type {
|
||||
CreateVesselIncident,
|
||||
Vessel,
|
||||
VesselIncident,
|
||||
VesselStatus,
|
||||
} from './vessel.types';
|
||||
|
||||
const TAGS = ['Vessel', 'VesselIncident'] as const;
|
||||
|
||||
const listTag = (type: (typeof TAGS)[number]) => ({ type, id: 'LIST' }) as const;
|
||||
|
||||
/**
|
||||
* The vessel register (module 11). Registration applications themselves go
|
||||
* through `licensingApi` as the VESSEL_REGISTRATION type; these endpoints
|
||||
* serve the register the certificates produce.
|
||||
*/
|
||||
export const vesselApi = baseApi
|
||||
.enhanceEndpoints({ addTagTypes: TAGS })
|
||||
.injectEndpoints({
|
||||
endpoints: (builder) => ({
|
||||
getMyVessels: builder.query<Vessel[], void>({
|
||||
query: () => ({ url: '/vessels/mine' }),
|
||||
providesTags: () => [listTag('Vessel')],
|
||||
}),
|
||||
|
||||
getVessels: builder.query<
|
||||
{ total: number; items: Vessel[] },
|
||||
{ search?: string } | void
|
||||
>({
|
||||
query: (args) => ({
|
||||
url: '/vessels',
|
||||
params: args?.search ? { search: args.search } : undefined,
|
||||
}),
|
||||
providesTags: () => [listTag('Vessel')],
|
||||
}),
|
||||
|
||||
getVessel: builder.query<Vessel, string>({
|
||||
query: (id) => ({ url: `/vessels/${id}` }),
|
||||
providesTags: (_r, _e, id) => [{ type: 'Vessel', id }],
|
||||
}),
|
||||
|
||||
/** Suspend / deregister / reinstate with a mandatory reason. */
|
||||
updateVesselStatus: builder.mutation<
|
||||
Vessel,
|
||||
{ vesselId: string; status: VesselStatus; reason: string }
|
||||
>({
|
||||
query: ({ vesselId, ...body }) => ({
|
||||
url: `/vessels/${vesselId}/status`,
|
||||
method: 'POST',
|
||||
body,
|
||||
}),
|
||||
invalidatesTags: (_r, error, { vesselId }) =>
|
||||
error ? [] : [listTag('Vessel'), { type: 'Vessel', id: vesselId }],
|
||||
}),
|
||||
|
||||
getVesselIncidents: builder.query<VesselIncident[], string>({
|
||||
query: (vesselId) => ({ url: `/vessels/${vesselId}/incidents` }),
|
||||
providesTags: () => [listTag('VesselIncident')],
|
||||
}),
|
||||
|
||||
createVesselIncident: builder.mutation<
|
||||
VesselIncident,
|
||||
{ vesselId: string; body: CreateVesselIncident }
|
||||
>({
|
||||
query: ({ vesselId, body }) => ({
|
||||
url: `/vessels/${vesselId}/incidents`,
|
||||
method: 'POST',
|
||||
body,
|
||||
}),
|
||||
invalidatesTags: (_r, error) =>
|
||||
error ? [] : [listTag('VesselIncident')],
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
export const {
|
||||
useGetMyVesselsQuery,
|
||||
useGetVesselsQuery,
|
||||
useGetVesselQuery,
|
||||
useUpdateVesselStatusMutation,
|
||||
useGetVesselIncidentsQuery,
|
||||
useCreateVesselIncidentMutation,
|
||||
} = vesselApi;
|
||||
51
libs/api/src/lib/features/vessel/vessel.types.ts
Normal file
51
libs/api/src/lib/features/vessel/vessel.types.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
export type VesselCategory = 'INLAND_WATERWAY' | 'SEA_GOING';
|
||||
export type VesselStatus = 'REGISTERED' | 'SUSPENDED' | 'DEREGISTERED';
|
||||
|
||||
/** A vessel-register entry, written when a registration certificate issues. */
|
||||
export interface Vessel {
|
||||
id: string;
|
||||
registrationNumber: string;
|
||||
name: string;
|
||||
category: VesselCategory;
|
||||
vesselType: string | null;
|
||||
imoNumber: string | null;
|
||||
hullNumber: string | null;
|
||||
flagState: string | null;
|
||||
portOfRegistry: string | null;
|
||||
grossTonnage: string | number | null;
|
||||
passengerCapacity: number | null;
|
||||
lengthMeters: string | number | null;
|
||||
yearBuilt: number | null;
|
||||
engineType: string | null;
|
||||
enginePowerKw: string | number | null;
|
||||
numberOfEngines: number | null;
|
||||
hullMaterial: string | null;
|
||||
ownerUserId: string;
|
||||
ownerProfileId: string | null;
|
||||
ownerName: string | null;
|
||||
applicationId: string;
|
||||
licenseId: string;
|
||||
status: VesselStatus;
|
||||
statusReason: string | null;
|
||||
statusChangedAt: string | null;
|
||||
registeredAt: string;
|
||||
}
|
||||
|
||||
export interface VesselIncident {
|
||||
id: string;
|
||||
vesselId: string;
|
||||
occurredAt: string;
|
||||
location: string | null;
|
||||
description: string;
|
||||
severity: string | null;
|
||||
reportedById: string;
|
||||
reportedByOfficer: boolean;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
export interface CreateVesselIncident {
|
||||
occurredAt: string;
|
||||
location?: string;
|
||||
description: string;
|
||||
severity?: string;
|
||||
}
|
||||
Reference in New Issue
Block a user