This commit is contained in:
marshal
2026-07-01 20:55:17 +03:00
parent 612df8daff
commit 9c18d086d7
112 changed files with 5654 additions and 1370 deletions

View File

@@ -1122,8 +1122,14 @@ export const api = {
},
routes: {
list: endpoint<void, RouteRecord[]>("routes", "list", () =>
routesService.getAll().then((r) => r.data),
list: endpoint<{ status?: import("./routes.service").RouteStatus } | void, RouteRecord[]>(
"routes",
"list",
(input) =>
routesService
.getAll(input?.status ? { status: input.status } : undefined)
.then((r) => r.data),
(input) => ["routes", input?.status ?? "all"],
),
yards: endpoint<void, YardRef[]>(

View File

@@ -301,6 +301,100 @@ export const bookingsService = {
governmentExpedite: (id: string) =>
postBooking<BookingDetail>(B.GOVERNMENT_EXPEDITE(id)),
getClearance: async (id: string): Promise<Freight.ClearanceView> => {
const response = await client.get(B.CLEARANCE(id));
return unwrap(response.data) as Freight.ClearanceView;
},
uploadDeclaration: async (
id: string,
files: Record<string, File | null>,
): Promise<BookingDetail> => {
const form = new FormData();
for (const [key, file] of Object.entries(files)) {
if (file) form.append(key, file);
}
const response = await client.post(B.CLEARANCE_DECLARATION(id), form, {
headers: { "Content-Type": "multipart/form-data" },
});
return unwrap(response.data) as BookingDetail;
},
adviseDuty: async (
id: string,
payload: {
dutyRequired: boolean;
amount?: number;
currency?: string;
declarationSerial?: string;
attachment?: File | null;
},
): Promise<BookingDetail> => {
const form = new FormData();
form.append("dutyRequired", String(payload.dutyRequired));
if (payload.amount != null) form.append("amount", String(payload.amount));
if (payload.currency) form.append("currency", payload.currency);
if (payload.declarationSerial) {
form.append("declarationSerial", payload.declarationSerial);
}
if (payload.attachment) form.append("attachment", payload.attachment);
const response = await client.post(B.CLEARANCE_DUTY(id), form, {
headers: { "Content-Type": "multipart/form-data" },
});
return unwrap(response.data) as BookingDetail;
},
finalizePreClearance: (id: string) =>
postBooking<BookingDetail>(B.CLEARANCE_FINALIZE_PRE(id)),
uploadTransitPermit: async (id: string, file: File): Promise<BookingDetail> => {
const form = new FormData();
form.append("file", file);
const response = await client.post(B.CLEARANCE_TRANSIT_PERMIT(id), form, {
headers: { "Content-Type": "multipart/form-data" },
});
return unwrap(response.data) as BookingDetail;
},
uploadDeliveryOrder: async (id: string, file: File): Promise<BookingDetail> => {
const form = new FormData();
form.append("file", file);
const response = await client.post(B.CLEARANCE_DELIVERY_ORDER(id), form, {
headers: { "Content-Type": "multipart/form-data" },
});
return unwrap(response.data) as BookingDetail;
},
uploadReleaseOrder: async (
id: string,
file: File,
vesselDepartureDate: string,
): Promise<{ hold?: boolean; holdReason?: string }> => {
const form = new FormData();
form.append("file", file);
form.append("vesselDepartureDate", vesselDepartureDate);
const response = await client.post(B.CLEARANCE_RELEASE_ORDER(id), form, {
headers: { "Content-Type": "multipart/form-data" },
});
return unwrap(response.data) as { hold?: boolean; holdReason?: string };
},
requestRoAmendment: (id: string, note?: string) =>
postBooking<BookingDetail>(B.CLEARANCE_RO_AMENDMENT(id), { note }),
confirmExportRelease: (id: string) =>
postBooking<BookingDetail>(B.CLEARANCE_EXPORT_RELEASE(id), {}),
getEtClearanceQueue: async (): Promise<BookingDetail[]> => {
const response = await client.get(B.CLEARANCE_ET_QUEUE);
return (unwrap(response.data) ?? []) as BookingDetail[];
},
getDjClearanceQueue: async (): Promise<BookingDetail[]> => {
const response = await client.get(B.CLEARANCE_DJ_QUEUE);
return (unwrap(response.data) ?? []) as BookingDetail[];
},
};
async function ensurePdfBlob(blob: Blob): Promise<Blob> {

View File

@@ -239,15 +239,32 @@ export const contractsService = {
return unwrap(response.data) as Freight.IContract;
},
adviseContractDuty: (
adviseContractDuty: async (
id: string,
payload: {
dutyRequired: boolean;
amount?: number;
currency?: string;
declarationSerial?: string;
attachment?: File | null;
},
) => postContract<Freight.IContract>(C.CLEARANCE_DUTY(id), payload),
): Promise<Freight.IContract> => {
const form = new FormData();
form.append("dutyRequired", String(payload.dutyRequired));
if (payload.amount != null) form.append("amount", String(payload.amount));
if (payload.currency) form.append("currency", payload.currency);
if (payload.declarationSerial) {
form.append("declarationSerial", payload.declarationSerial);
}
if (payload.attachment) form.append("attachment", payload.attachment);
const response = await client.post(C.CLEARANCE_DUTY(id), form, {
headers: { "Content-Type": "multipart/form-data" },
});
return unwrap(response.data) as Freight.IContract;
},
finalizePreClearance: (id: string) =>
postContract<Freight.IContract>(C.CLEARANCE_FINALIZE_PRE(id)),
uploadContractTransitPermit: async (
id: string,

View File

@@ -2,6 +2,8 @@ import { api as apiClient } from '../auth/http';
import { URL_CONSTANTS } from '@/constants/URLS';
export type RouteStatus = 'AVAILABLE' | 'MAINTENANCE' | 'DAMAGED' | 'STOP_WORKING';
export interface YardRef {
id: string;
code: string;
@@ -14,32 +16,54 @@ export interface RouteMilestone {
routeId: string;
yardId: string;
sequenceNo: number;
distanceKm?: number | null;
yard?: YardRef | null;
}
export interface RouteRecord {
id: string;
name: string;
status: RouteStatus;
originYardId: string;
destinationYardId: string;
isActive: boolean;
originYard?: YardRef | null;
destinationYard?: YardRef | null;
milestones?: RouteMilestone[];
}
export interface SaveRoutePayload {
name: string;
milestones: Array<{ yardId: string }>;
isActive?: boolean;
milestones: Array<{ yardId: string; distanceKm?: number }>;
status?: RouteStatus;
}
export function formatRouteLabel(route: RouteRecord): string {
const origin =
route.originYard?.code ?? route.originYard?.label ?? 'Origin';
const dest =
route.destinationYard?.code ?? route.destinationYard?.label ?? 'Destination';
return `${origin}${dest}`;
}
export function totalRouteDistanceKm(route: RouteRecord): number {
return (route.milestones ?? []).reduce(
(sum, m) => sum + Number(m.distanceKm ?? 0),
0,
);
}
export const ROUTE_STATUS_OPTIONS: Array<{ value: RouteStatus; label: string }> = [
{ value: 'AVAILABLE', label: 'Available' },
{ value: 'MAINTENANCE', label: 'Maintenance' },
{ value: 'DAMAGED', label: 'Damaged' },
{ value: 'STOP_WORKING', label: 'Stop working' },
];
interface YardListResponse {
data: YardRef[];
}
export const routesService = {
getAll: () => apiClient.get<RouteRecord[]>(URL_CONSTANTS.ROUTES.BASE),
getAll: (params?: { status?: RouteStatus; search?: string }) =>
apiClient.get<RouteRecord[]>(URL_CONSTANTS.ROUTES.BASE, { params }),
getById: (id: string) => apiClient.get<RouteRecord>(URL_CONSTANTS.ROUTES.BY_ID(id)),
create: (data: SaveRoutePayload) => apiClient.post(URL_CONSTANTS.ROUTES.BASE, data),
update: (id: string, data: Partial<SaveRoutePayload>) =>