update locaomotive and fix wagon transfer issue

This commit is contained in:
Marshal
2026-07-27 20:34:14 +00:00
parent deadc59277
commit 496c66017f
23 changed files with 811 additions and 230 deletions

View File

@@ -170,6 +170,7 @@ import {
} from "./payments.service";
import {
routesService,
type RouteListFilters,
type RouteRecord,
type SaveRoutePayload,
type YardRef,
@@ -1507,6 +1508,13 @@ export const api = {
(input) => ["routes", input?.status ?? "all"],
),
listPaged: endpoint<RouteListFilters, PaginatedResponse<RouteRecord>>(
"routes",
"listPaged",
(filters) => routesService.getPaged(filters).then((r) => r.data),
(filters) => ["routes", "paged", filters],
),
yards: endpoint<void, YardRef[]>(
"routes",
"yards",
@@ -2082,6 +2090,16 @@ export const api = {
({ slug, filters }) => [...QUERY_KEYS.FLEET.list(slug), filters ?? {}],
),
listPaged: endpoint<
{ slug: FleetResourceSlug; filters?: FleetListFilters },
PaginatedResponse<FleetRecord>
>(
"fleet",
"listPaged",
({ slug, filters }) => fleetService.listPaged(slug, filters),
({ slug, filters }) => [...QUERY_KEYS.FLEET.list(slug), "paged", filters ?? {}],
),
create: endpoint<
{ slug: FleetResourceSlug; data: Record<string, unknown> },
unknown

View File

@@ -1,3 +1,5 @@
import type { PaginatedResponse } from "@edr/types";
import { cargoService, type Cargo } from "@/services/cargoService";
import { containerService, type Container } from "@/services/containerService";
import {
@@ -28,6 +30,20 @@ const listHandlers: Record<
drivers: (filters) => driversService.getAll(filters ?? {}).then((r) => r.data),
};
/**
* Server-paginated slugs. Everything else still lists in full and pages in the
* browser — add an entry here once its API grows a `/paged` endpoint.
*/
const pagedHandlers: Partial<
Record<FleetResourceSlug, (filters: FleetListFilters) => Promise<PaginatedResponse<FleetRecord>>>
> = {
locomotives: (filters) => locomotivesService.getPaged(filters).then((r) => r.data),
wagons: (filters) => wagonService.getPaged(filters).then((r) => r.data),
};
export const isFleetServerPaginated = (slug: FleetResourceSlug): boolean =>
slug in pagedHandlers;
const createHandlers: Record<FleetResourceSlug, (data: Record<string, unknown>) => Promise<unknown>> = {
locomotives: (data) => locomotivesService.create(data),
trains: (data) => trainService.create(data),
@@ -63,6 +79,12 @@ const removeHandlers: Record<FleetResourceSlug, (id: string) => Promise<unknown>
export const fleetService = {
list: (slug: FleetResourceSlug, filters?: FleetListFilters) => listHandlers[slug](filters),
/** Only for slugs in `pagedHandlers` — guard with `isFleetServerPaginated`. */
listPaged: (slug: FleetResourceSlug, filters: FleetListFilters = {}) => {
const handler = pagedHandlers[slug];
if (!handler) throw new Error(`Fleet resource "${slug}" has no paginated list endpoint`);
return handler(filters);
},
create: (slug: FleetResourceSlug, data: Record<string, unknown>) => createHandlers[slug](data),
update: (slug: FleetResourceSlug, id: string, data: Record<string, unknown>) =>
updateHandlers[slug](id, data),

View File

@@ -1,3 +1,5 @@
import type { PaginatedResponse } from '@edr/types';
import { api as apiClient } from '../auth/http';
import { URL_CONSTANTS } from '@/constants/URLS';
@@ -19,8 +21,31 @@ export interface LocomotiveListFilters {
excludeCoupled?: boolean;
/** With excludeCoupled: keep THIS train's own coupled locos in the list. */
excludeTrainId?: string;
/** Free-text over code + name — only honoured by `getPaged`. */
search?: string;
/** Registration day range (YYYY-MM-DD), both ends inclusive. */
createdFrom?: string;
createdTo?: string;
/** Only read by `getPaged`. */
page?: number;
pageSize?: number;
}
const locomotiveListQuery = (filters: LocomotiveListFilters): string => {
const params = new URLSearchParams();
if (filters.status) params.set('status', filters.status);
if (filters.currentYardId) params.set('currentYardId', filters.currentYardId);
if (filters.excludeCoupled) params.set('excludeCoupled', 'true');
if (filters.excludeTrainId) params.set('excludeTrainId', filters.excludeTrainId);
if (filters.search?.trim()) params.set('search', filters.search.trim());
if (filters.createdFrom) params.set('createdFrom', filters.createdFrom);
if (filters.createdTo) params.set('createdTo', filters.createdTo);
if (filters.page) params.set('page', String(filters.page));
if (filters.pageSize) params.set('pageSize', String(filters.pageSize));
const qs = params.toString();
return qs ? `?${qs}` : '';
};
export interface Locomotive {
id: string;
code: string;
@@ -45,17 +70,15 @@ export type SaveLocomotivePayload = Omit<
>;
export const locomotivesService = {
getAll: (filters: LocomotiveListFilters = {}) => {
const params = new URLSearchParams();
if (filters.status) params.set('status', filters.status);
if (filters.currentYardId) params.set('currentYardId', filters.currentYardId);
if (filters.excludeCoupled) params.set('excludeCoupled', 'true');
if (filters.excludeTrainId) params.set('excludeTrainId', filters.excludeTrainId);
const qs = params.toString();
return apiClient.get<Locomotive[]>(
`${URL_CONSTANTS.LOCOMOTIVES.BASE}${qs ? `?${qs}` : ''}`,
);
},
getAll: (filters: LocomotiveListFilters = {}) =>
apiClient.get<Locomotive[]>(
`${URL_CONSTANTS.LOCOMOTIVES.BASE}${locomotiveListQuery(filters)}`,
),
/** Same filters as `getAll` plus search, server-paginated ({items, meta}). */
getPaged: (filters: LocomotiveListFilters = {}) =>
apiClient.get<PaginatedResponse<Locomotive>>(
`${URL_CONSTANTS.LOCOMOTIVES.BASE}/paged${locomotiveListQuery(filters)}`,
),
getById: (id: string) => apiClient.get<Locomotive>(URL_CONSTANTS.LOCOMOTIVES.BY_ID(id)),
create: (data: Partial<SaveLocomotivePayload>) =>
apiClient.post(URL_CONSTANTS.LOCOMOTIVES.BASE, data),

View File

@@ -1,3 +1,5 @@
import type { PaginatedResponse } from '@edr/types';
import { api as apiClient } from '../auth/http';
import { URL_CONSTANTS } from '@/constants/URLS';
@@ -78,9 +80,22 @@ export const ROUTE_STATUS_OPTIONS: Array<{ value: RouteStatus; label: string }>
{ value: 'STOP_WORKING', label: 'Stop working' },
];
export interface RouteListFilters {
status?: RouteStatus;
search?: string;
page?: number;
pageSize?: number;
}
export const routesService = {
getAll: (params?: { status?: RouteStatus; search?: string }) =>
apiClient.get<RouteRecord[]>(URL_CONSTANTS.ROUTES.BASE, { params }),
/** Same filters as `getAll`, server-paginated ({items, meta}). */
getPaged: (params: RouteListFilters = {}) =>
apiClient.get<PaginatedResponse<RouteRecord>>(
`${URL_CONSTANTS.ROUTES.BASE}/paged`,
{ 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>) =>

View File

@@ -41,10 +41,35 @@ export interface WagonListFilters {
currentYardId?: string;
wagonTypeId?: string;
trainId?: string;
/** Drop wagons already coupled to a built train — only loose ones can be taken. */
unassigned?: boolean;
/** Run number — matches a wagon whose export OR import run equals it. */
trainNumber?: string;
/** Registration day range (YYYY-MM-DD), both ends inclusive. */
createdFrom?: string;
createdTo?: string;
/** Only read by `getPaged`. */
page?: number;
pageSize?: number;
}
const wagonListQuery = (filters: WagonListFilters): string => {
const params = new URLSearchParams();
if (filters.search?.trim()) params.set('search', filters.search.trim());
if (filters.status) params.set('status', filters.status);
if (filters.currentYardId) params.set('currentYardId', filters.currentYardId);
if (filters.wagonTypeId) params.set('wagonTypeId', filters.wagonTypeId);
if (filters.trainId) params.set('trainId', filters.trainId);
if (filters.unassigned) params.set('unassigned', 'true');
if (filters.trainNumber) params.set('trainNumber', filters.trainNumber);
if (filters.createdFrom) params.set('createdFrom', filters.createdFrom);
if (filters.createdTo) params.set('createdTo', filters.createdTo);
if (filters.page) params.set('page', String(filters.page));
if (filters.pageSize) params.set('pageSize', String(filters.pageSize));
const qs = params.toString();
return qs ? `?${qs}` : '';
};
/**
* One row of the wagon_movements ledger: every physical relocation between
* yards — a booking's loaded leg, an empty reposition ride, or a manual staff
@@ -70,17 +95,11 @@ export interface WagonMovementRecord {
}
export const wagonService = {
getAll: (filters: WagonListFilters = {}) => {
const params = new URLSearchParams();
if (filters.search?.trim()) params.set('search', filters.search.trim());
if (filters.status) params.set('status', filters.status);
if (filters.currentYardId) params.set('currentYardId', filters.currentYardId);
if (filters.wagonTypeId) params.set('wagonTypeId', filters.wagonTypeId);
if (filters.trainId) params.set('trainId', filters.trainId);
if (filters.trainNumber) params.set('trainNumber', filters.trainNumber);
const qs = params.toString();
return apiClient.get<Wagon[]>(`/wagons${qs ? `?${qs}` : ''}`);
},
getAll: (filters: WagonListFilters = {}) =>
apiClient.get<Wagon[]>(`/wagons${wagonListQuery(filters)}`),
/** Same filters as `getAll`, server-paginated ({items, meta}). */
getPaged: (filters: WagonListFilters = {}) =>
apiClient.get<PaginatedResponse<Wagon>>(`/wagons/paged${wagonListQuery(filters)}`),
getById: (id: string) => apiClient.get<Wagon>(`/wagons/${id}`),
getMovements: (id: string) =>
apiClient.get<WagonMovementRecord[]>(`/wagons/${id}/movements`),