From 481878e55305df71e556ecc3afd8a03ddaa4cf2c Mon Sep 17 00:00:00 2001 From: Marshal Date: Mon, 27 Jul 2026 21:27:36 +0000 Subject: [PATCH] paginate wagons, locomotives and routes lists server-side --- .../wagons/dto/list-wagons-query.dto.ts | 11 +-------- .../src/modules/wagons/wagons.controller.ts | 12 +++------- .../src/modules/wagons/wagons.service.ts | 21 ++++++----------- .../src/pages/fleet/FleetResourcePage.tsx | 10 ++++++-- .../backoffice/src/services/api.ts | 12 ++++++++-- .../src/services/fleet/fleet.service.ts | 4 ++-- .../backoffice/src/services/wagon.service.ts | 23 +++++++++++++++---- 7 files changed, 49 insertions(+), 44 deletions(-) diff --git a/apps/edr-freight-api/src/modules/wagons/dto/list-wagons-query.dto.ts b/apps/edr-freight-api/src/modules/wagons/dto/list-wagons-query.dto.ts index ae48a2713..328a6eaa8 100644 --- a/apps/edr-freight-api/src/modules/wagons/dto/list-wagons-query.dto.ts +++ b/apps/edr-freight-api/src/modules/wagons/dto/list-wagons-query.dto.ts @@ -72,16 +72,7 @@ export class ListWagonsQueryDto { @Min(1) page?: number; - @ApiPropertyOptional({ minimum: 1, maximum: 500 }) - @IsOptional() - @Type(() => Number) - @IsInt() - @Min(1) - @Max(500) - limit?: number; - - /** Page size for `GET /wagons/paged`; the legacy `limit` still drives `GET /wagons`. */ - @ApiPropertyOptional({ default: 20, minimum: 1, maximum: 100 }) + @ApiPropertyOptional({ default: 10, minimum: 1, maximum: 100 }) @IsOptional() @Type(() => Number) @IsInt() diff --git a/apps/edr-freight-api/src/modules/wagons/wagons.controller.ts b/apps/edr-freight-api/src/modules/wagons/wagons.controller.ts index 3b8a7077b..c792057d8 100644 --- a/apps/edr-freight-api/src/modules/wagons/wagons.controller.ts +++ b/apps/edr-freight-api/src/modules/wagons/wagons.controller.ts @@ -39,19 +39,13 @@ export class WagonsController { @Get() @StaffReference() - @ApiOperation({ summary: 'List all wagons' }) + @ApiOperation({ + summary: 'List wagons, paginated ({items, meta}) — 10 per page by default', + }) findAll(@Query() query: ListWagonsQueryDto) { return this.wagonsService.findAll(query); } - // Must be declared before @Get(':id') so the path isn't captured as an id. - @Get('paged') - @StaffReference() - @ApiOperation({ summary: 'List wagons, paginated ({items, meta})' }) - findAllPaged(@Query() query: ListWagonsQueryDto) { - return this.wagonsService.findAllPaged(query); - } - @Get(':id') @StaffReference() @ApiOperation({ summary: 'Get a wagon by ID' }) diff --git a/apps/edr-freight-api/src/modules/wagons/wagons.service.ts b/apps/edr-freight-api/src/modules/wagons/wagons.service.ts index 98f5c32b0..f51846e2d 100644 --- a/apps/edr-freight-api/src/modules/wagons/wagons.service.ts +++ b/apps/edr-freight-api/src/modules/wagons/wagons.service.ts @@ -115,20 +115,13 @@ export class WagonsService { return qb; } - async findAll(query: ListWagonsQueryDto = {}): Promise { - const qb = this.buildListQuery(query); - - if (query.page && query.limit) { - qb.skip((Number(query.page) - 1) * Number(query.limit)); - } - if (query.limit) qb.take(Number(query.limit)); - - return qb.getMany(); - } - - /** Same filters as `findAll`, on the shared `{items, meta}` list envelope. */ - findAllPaged(query: ListWagonsQueryDto = {}): Promise> { - return paginateQuery(this.buildListQuery(query), query); + /** + * The wagon list is always a page. Callers that genuinely need every row + * (yard workspace, coupling pickers) walk the pages client-side — see + * `wagonService.listAll` in the backoffice. + */ + findAll(query: ListWagonsQueryDto = {}): Promise> { + return paginateQuery(this.buildListQuery(query), query, { defaultPageSize: 10 }); } async findById(id: string): Promise { diff --git a/apps/edr-freight-web/backoffice/src/pages/fleet/FleetResourcePage.tsx b/apps/edr-freight-web/backoffice/src/pages/fleet/FleetResourcePage.tsx index 83a931b18..213501d74 100644 --- a/apps/edr-freight-web/backoffice/src/pages/fleet/FleetResourcePage.tsx +++ b/apps/edr-freight-web/backoffice/src/pages/fleet/FleetResourcePage.tsx @@ -155,9 +155,15 @@ const FleetResourcePage = () => { const { data: cargoTypes = [], isLoading: cargoTypesLoading } = useQuery( api.cargoTypes.list.queryOptions({ staleTime: Infinity }), ); - const { data: wagons = [], isLoading: wagonsLoading } = useQuery( - api.wagons.list.queryOptions({ input: {} }), + // Whole-fleet list for the "Wagon" form select — page-walked, so only fetch it + // where a form actually offers that select (containers), not on every slug. + const needsWagonOptions = Boolean( + config?.formFields.some((field) => field.dynamicOptions === "wagons"), ); + const { data: wagons = [], isLoading: wagonsLoading } = useQuery({ + ...api.wagons.list.queryOptions({ input: {} }), + enabled: needsWagonOptions, + }); const { data: containers = [], isLoading: containersLoading } = useQuery( api.containers.list.queryOptions(), ); diff --git a/apps/edr-freight-web/backoffice/src/services/api.ts b/apps/edr-freight-web/backoffice/src/services/api.ts index a28e15cd2..ef84826aa 100644 --- a/apps/edr-freight-web/backoffice/src/services/api.ts +++ b/apps/edr-freight-web/backoffice/src/services/api.ts @@ -1632,17 +1632,25 @@ export const api = { }, wagons: { + /** Every match, page-walked — for pickers and yard views. Lists use `listPaged`. */ list: endpoint<{ filters?: WagonListFilters }, Wagon[]>( "wagons", "list", - ({ filters }) => wagonService.getAll(filters ?? {}).then((r) => r.data), + ({ filters }) => wagonService.listAll(filters ?? {}), ({ filters }) => ["wagons", "list", filters ?? {}], ), + listPaged: endpoint<{ filters?: WagonListFilters }, PaginatedResponse>( + "wagons", + "listPaged", + ({ filters }) => wagonService.getAll(filters ?? {}).then((r) => r.data), + ({ filters }) => ["wagons", "listPaged", filters ?? {}], + ), + listByTrain: endpoint<{ trainId: string }, Wagon[]>( "wagons", "listByTrain", - ({ trainId }) => wagonService.getByTrain(trainId).then((r) => r.data), + ({ trainId }) => wagonService.getByTrain(trainId), ({ trainId }) => ["wagons", "train", trainId], ), diff --git a/apps/edr-freight-web/backoffice/src/services/fleet/fleet.service.ts b/apps/edr-freight-web/backoffice/src/services/fleet/fleet.service.ts index 8d4e60dd3..e47160176 100644 --- a/apps/edr-freight-web/backoffice/src/services/fleet/fleet.service.ts +++ b/apps/edr-freight-web/backoffice/src/services/fleet/fleet.service.ts @@ -23,7 +23,7 @@ const listHandlers: Record< > = { locomotives: (filters) => locomotivesService.getAll(filters ?? {}).then((r) => r.data), trains: () => trainService.getAll().then((r) => r.data), - wagons: (filters) => wagonService.getAll(filters ?? {}).then((r) => r.data), + wagons: (filters) => wagonService.listAll(filters ?? {}), containers: () => containerService.getAll().then((r) => r.data), cargoes: () => cargoService.getAll().then((r) => r.data), vehicles: (filters) => vehiclesService.getAll(filters ?? {}).then((r) => r.data), @@ -38,7 +38,7 @@ const pagedHandlers: Partial< Record Promise>> > = { locomotives: (filters) => locomotivesService.getPaged(filters).then((r) => r.data), - wagons: (filters) => wagonService.getPaged(filters).then((r) => r.data), + wagons: (filters) => wagonService.getAll(filters).then((r) => r.data), }; export const isFleetServerPaginated = (slug: FleetResourceSlug): boolean => diff --git a/apps/edr-freight-web/backoffice/src/services/wagon.service.ts b/apps/edr-freight-web/backoffice/src/services/wagon.service.ts index 96791e13d..9a5127f2b 100644 --- a/apps/edr-freight-web/backoffice/src/services/wagon.service.ts +++ b/apps/edr-freight-web/backoffice/src/services/wagon.service.ts @@ -95,15 +95,28 @@ export interface WagonMovementRecord { } export const wagonService = { + /** One page ({items, meta}); 10 rows unless `pageSize` says otherwise. */ getAll: (filters: WagonListFilters = {}) => - apiClient.get(`/wagons${wagonListQuery(filters)}`), - /** Same filters as `getAll`, server-paginated ({items, meta}). */ - getPaged: (filters: WagonListFilters = {}) => - apiClient.get>(`/wagons/paged${wagonListQuery(filters)}`), + apiClient.get>(`/wagons${wagonListQuery(filters)}`), + /** + * Every matching wagon, page-walked at the API's 100-row cap. For the pickers + * and yard views that filter the whole fleet in the browser — a list page + * should use `getAll` and show the real page controls instead. + */ + listAll: async (filters: WagonListFilters = {}): Promise => { + const pageSize = 100; + const first = await wagonService.getAll({ ...filters, page: 1, pageSize }); + const items = [...first.data.items]; + for (let page = 2; page <= (first.data.meta.totalPages ?? 1); page += 1) { + const next = await wagonService.getAll({ ...filters, page, pageSize }); + items.push(...next.data.items); + } + return items; + }, getById: (id: string) => apiClient.get(`/wagons/${id}`), getMovements: (id: string) => apiClient.get(`/wagons/${id}/movements`), - getByTrain: (trainId: string) => apiClient.get(`/wagons?trainId=${trainId}`), + getByTrain: (trainId: string) => wagonService.listAll({ trainId }), assignToTrain: (wagonId: string, trainId: string, sequenceNumber?: number) => apiClient.post(`/wagons/${wagonId}/assign-train`, { trainId, sequenceNumber }), unassign: (wagonId: string) => apiClient.post(`/wagons/${wagonId}/unassign-train`),