diff --git a/apps/edr-freight-api/src/modules/train-scheduling/train-scheduling.controller.ts b/apps/edr-freight-api/src/modules/train-scheduling/train-scheduling.controller.ts
index 3c191c713..3ef7693ab 100644
--- a/apps/edr-freight-api/src/modules/train-scheduling/train-scheduling.controller.ts
+++ b/apps/edr-freight-api/src/modules/train-scheduling/train-scheduling.controller.ts
@@ -8,87 +8,98 @@ import {
Patch,
Post,
Query,
-} from '@nestjs/common';
-import { CurrentUser } from '@edr/api-common';
-import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger';
-import type { AuthUserPayload } from '../../common/resolve-auth-user-id';
-import { resolveAuthUserId } from '../../common/resolve-auth-user-id';
+} from "@nestjs/common";
+import { CurrentUser } from "@edr/api-common";
+import { ApiBearerAuth, ApiOperation, ApiTags } from "@nestjs/swagger";
+import type { AuthUserPayload } from "../../common/resolve-auth-user-id";
+import { resolveAuthUserId } from "../../common/resolve-auth-user-id";
-import { TrainSchedulingManage, TrainSchedulingView } from '../../common/booking-guards';
-import { AssignBookingsDto } from './dto/assign-bookings.dto';
-import { AssignUnassignedBookingDto } from './dto/assign-unassigned-booking.dto';
-import { CreateContainerTrainScheduleDto } from './dto/create-container-train-schedule.dto';
-import { GetEligibleBookingsDto } from './dto/get-eligible-bookings.dto';
-import { GetEligibleBulkBookingsDto } from './dto/get-eligible-bulk-bookings.dto';
-import { GetEligibleContainerBookingsDto } from './dto/get-eligible-container-bookings.dto';
-import { PinWagonsDto } from './dto/pin-wagons.dto';
-import { UpdateContainerItemDto } from './dto/update-container-item.dto';
-import { PreviewBulkTrainScheduleDto } from './dto/preview-bulk-train-schedule.dto';
-import { PreviewContainerTrainScheduleDto } from './dto/preview-container-train-schedule.dto';
-import { PreviewTrainScheduleDto } from './dto/preview-train-schedule.dto';
-import { RecordCheckpointDto } from './dto/record-checkpoint.dto';
-import { AvailableLocomotivesQueryDto } from './dto/available-locomotives-query.dto';
-import { BookableSchedulesQueryDto } from './dto/bookable-schedules-query.dto';
-import { UpdateTrainSchedulingGlobalRulesDto } from './dto/update-train-scheduling-global-rules.dto';
-import { TrainSchedulingService } from './train-scheduling.service';
-import { BookingBatchService } from './booking-batch.service';
+import {
+ TrainSchedulingManage,
+ TrainSchedulingView,
+} from "../../common/booking-guards";
+import { AssignBookingsDto } from "./dto/assign-bookings.dto";
+import { AssignUnassignedBookingDto } from "./dto/assign-unassigned-booking.dto";
+import { CreateContainerTrainScheduleDto } from "./dto/create-container-train-schedule.dto";
+import { GetEligibleBookingsDto } from "./dto/get-eligible-bookings.dto";
+import { GetEligibleBulkBookingsDto } from "./dto/get-eligible-bulk-bookings.dto";
+import { GetEligibleContainerBookingsDto } from "./dto/get-eligible-container-bookings.dto";
+import { PinWagonsDto } from "./dto/pin-wagons.dto";
+import { UpdateContainerItemDto } from "./dto/update-container-item.dto";
+import { PreviewBulkTrainScheduleDto } from "./dto/preview-bulk-train-schedule.dto";
+import { PreviewContainerTrainScheduleDto } from "./dto/preview-container-train-schedule.dto";
+import { PreviewTrainScheduleDto } from "./dto/preview-train-schedule.dto";
+import { RecordCheckpointDto } from "./dto/record-checkpoint.dto";
+import { AvailableLocomotivesQueryDto } from "./dto/available-locomotives-query.dto";
+import { BookableSchedulesQueryDto } from "./dto/bookable-schedules-query.dto";
+import { UpdateTrainSchedulingGlobalRulesDto } from "./dto/update-train-scheduling-global-rules.dto";
+import { TrainSchedulingService } from "./train-scheduling.service";
+import { BookingBatchService } from "./booking-batch.service";
-@ApiTags('train-scheduling')
+@ApiTags("train-scheduling")
@ApiBearerAuth()
-@Controller('train-scheduling')
+@Controller("train-scheduling")
export class TrainSchedulingController {
constructor(
private readonly trainSchedulingService: TrainSchedulingService,
private readonly bookingBatchService: BookingBatchService,
- ) {}
+ ) { }
- @Get('global-rules')
+ @Get("global-rules")
@TrainSchedulingView()
- @ApiOperation({ summary: 'Get global train scheduling rules (singleton)' })
+ @ApiOperation({ summary: "Get global train scheduling rules (singleton)" })
getGlobalRules() {
return this.trainSchedulingService.getTrainSchedulingGlobalRules();
}
- @Patch('global-rules')
+ @Patch("global-rules")
@TrainSchedulingManage()
- @ApiOperation({ summary: 'Update global train scheduling rules (singleton)' })
+ @ApiOperation({ summary: "Update global train scheduling rules (singleton)" })
updateGlobalRules(@Body() dto: UpdateTrainSchedulingGlobalRulesDto) {
return this.trainSchedulingService.updateTrainSchedulingGlobalRules(dto);
}
- @Get('eligible-bookings')
+ @Get("eligible-bookings")
@TrainSchedulingView()
- @ApiOperation({ summary: 'List eligible bookings (container and/or bulk)' })
+ @ApiOperation({ summary: "List eligible bookings (container and/or bulk)" })
getEligibleBookings(@Query() query: GetEligibleBookingsDto) {
return this.trainSchedulingService.getEligibleBookings(query);
}
- @Get('batch-board')
+ @Get("batch-board")
@TrainSchedulingView()
- @ApiOperation({ summary: 'Batch monitoring board: schedules with bookings grouped by state' })
+ @ApiOperation({
+ summary: "Batch monitoring board: schedules with bookings grouped by state",
+ })
getBatchBoard() {
return this.bookingBatchService.getBatchBoard();
}
- @Get('batch-board/:scheduleId')
+ @Get("batch-board/:scheduleId")
@TrainSchedulingView()
- @ApiOperation({ summary: 'Batch board detail for one schedule with EAT 3h windows' })
- getBatchBoardDetail(@Param('scheduleId', ParseUUIDPipe) scheduleId: string) {
+ @ApiOperation({
+ summary: "Batch board detail for one schedule with EAT 3h windows",
+ })
+ getBatchBoardDetail(@Param("scheduleId", ParseUUIDPipe) scheduleId: string) {
return this.bookingBatchService.getBatchBoardDetail(scheduleId);
}
- @Get('available-locomotives')
+ @Get("available-locomotives")
@TrainSchedulingView()
@ApiOperation({
- summary: 'List AVAILABLE locomotives at the route origin yard',
+ summary: "List AVAILABLE locomotives at the route origin yard",
})
getAvailableLocomotives(@Query() query: AvailableLocomotivesQueryDto) {
- return this.trainSchedulingService.getAvailableLocomotivesForRoute(query.routeId);
+ return this.trainSchedulingService.getAvailableLocomotivesForRoute(
+ query.routeId,
+ );
}
- @Get('bookable-schedules')
- @TrainSchedulingView()
- @ApiOperation({ summary: 'OPEN same-route schedules a new booking can target' })
+ @Get("bookable-schedules")
+ // @TrainSchedulingView()
+ @ApiOperation({
+ summary: "OPEN same-route schedules a new booking can target",
+ })
getBookableSchedules(@Query() query: BookableSchedulesQueryDto) {
return this.trainSchedulingService.getBookableSchedules(
query.originYardId,
@@ -96,282 +107,323 @@ export class TrainSchedulingController {
);
}
- @Get('container/eligible-bookings')
+ @Get("container/eligible-bookings")
@TrainSchedulingView()
- @ApiOperation({ summary: 'List eligible container bookings' })
- getEligibleContainerBookings(@Query() query: GetEligibleContainerBookingsDto) {
+ @ApiOperation({ summary: "List eligible container bookings" })
+ getEligibleContainerBookings(
+ @Query() query: GetEligibleContainerBookingsDto,
+ ) {
return this.trainSchedulingService.getEligibleContainerBookings(query);
}
- @Get('bulk/eligible-bookings')
+ @Get("bulk/eligible-bookings")
@TrainSchedulingView()
- @ApiOperation({ summary: 'List eligible bulk bookings' })
+ @ApiOperation({ summary: "List eligible bulk bookings" })
getEligibleBulkBookings(@Query() query: GetEligibleBulkBookingsDto) {
return this.trainSchedulingService.getEligibleBulkBookings(query);
}
- @Post('preview')
+ @Post("preview")
@TrainSchedulingView()
- @ApiOperation({ summary: 'Preview a mixed-capable train schedule' })
+ @ApiOperation({ summary: "Preview a mixed-capable train schedule" })
previewTrainSchedule(@Body() dto: PreviewTrainScheduleDto) {
return this.trainSchedulingService.previewTrainSchedule(dto);
}
- @Post('container/preview')
+ @Post("container/preview")
@TrainSchedulingView()
- @ApiOperation({ summary: 'Preview a container train schedule' })
+ @ApiOperation({ summary: "Preview a container train schedule" })
previewContainerTrainSchedule(@Body() dto: PreviewContainerTrainScheduleDto) {
return this.trainSchedulingService.previewContainerTrainSchedule(dto);
}
- @Post('bulk/preview')
+ @Post("bulk/preview")
@TrainSchedulingView()
- @ApiOperation({ summary: 'Preview a bulk train schedule' })
+ @ApiOperation({ summary: "Preview a bulk train schedule" })
previewBulkTrainSchedule(@Body() dto: PreviewBulkTrainScheduleDto) {
return this.trainSchedulingService.previewBulkTrainSchedule(dto);
}
- @Post('container/schedules')
+ @Post("container/schedules")
@TrainSchedulingManage()
- @ApiOperation({ summary: 'Create a container train schedule' })
+ @ApiOperation({ summary: "Create a container train schedule" })
createContainerTrainSchedule(@Body() dto: CreateContainerTrainScheduleDto) {
return this.trainSchedulingService.createContainerTrainSchedule(dto);
}
- @Post('bulk/schedules')
+ @Post("bulk/schedules")
@TrainSchedulingManage()
- @ApiOperation({ summary: 'Create a bulk train schedule' })
+ @ApiOperation({ summary: "Create a bulk train schedule" })
createBulkTrainSchedule(@Body() dto: CreateContainerTrainScheduleDto) {
return this.trainSchedulingService.createContainerTrainSchedule(dto);
}
- @Post('schedules/:id/assign-bookings')
+ @Post("schedules/:id/assign-bookings")
@TrainSchedulingManage()
- @ApiOperation({ summary: 'Assign bookings to a train schedule (mixed-capable)' })
+ @ApiOperation({
+ summary: "Assign bookings to a train schedule (mixed-capable)",
+ })
assignBookings(
- @Param('id', ParseUUIDPipe) id: string,
+ @Param("id", ParseUUIDPipe) id: string,
@Body() dto: AssignBookingsDto,
) {
return this.trainSchedulingService.assignBookingsToSchedule(id, dto);
}
- @Post('container/schedules/:id/assign-bookings')
+ @Post("container/schedules/:id/assign-bookings")
@TrainSchedulingManage()
- @ApiOperation({ summary: 'Assign container bookings to a train schedule' })
+ @ApiOperation({ summary: "Assign container bookings to a train schedule" })
assignContainerBookings(
- @Param('id', ParseUUIDPipe) id: string,
+ @Param("id", ParseUUIDPipe) id: string,
@Body() dto: AssignBookingsDto,
) {
- return this.trainSchedulingService.assignBookingsToSchedule(id, dto, 'CONTAINER');
+ return this.trainSchedulingService.assignBookingsToSchedule(
+ id,
+ dto,
+ "CONTAINER",
+ );
}
- @Post('bulk/schedules/:id/assign-bookings')
+ @Post("bulk/schedules/:id/assign-bookings")
@TrainSchedulingManage()
- @ApiOperation({ summary: 'Assign bulk bookings to a train schedule' })
+ @ApiOperation({ summary: "Assign bulk bookings to a train schedule" })
assignBulkBookings(
- @Param('id', ParseUUIDPipe) id: string,
+ @Param("id", ParseUUIDPipe) id: string,
@Body() dto: AssignBookingsDto,
) {
- return this.trainSchedulingService.assignBookingsToSchedule(id, dto, 'BULK');
+ return this.trainSchedulingService.assignBookingsToSchedule(
+ id,
+ dto,
+ "BULK",
+ );
}
- @Delete('schedules/:id/bookings/:bookingId')
+ @Delete("schedules/:id/bookings/:bookingId")
@TrainSchedulingManage()
- @ApiOperation({ summary: 'Unassign a booking from a train schedule' })
+ @ApiOperation({ summary: "Unassign a booking from a train schedule" })
unassignBooking(
- @Param('id', ParseUUIDPipe) id: string,
- @Param('bookingId', ParseUUIDPipe) bookingId: string,
+ @Param("id", ParseUUIDPipe) id: string,
+ @Param("bookingId", ParseUUIDPipe) bookingId: string,
@CurrentUser() user: AuthUserPayload,
) {
- return this.trainSchedulingService.unassignBooking(id, bookingId, resolveAuthUserId(user));
+ return this.trainSchedulingService.unassignBooking(
+ id,
+ bookingId,
+ resolveAuthUserId(user),
+ );
}
- @Delete('schedules/:id/wagons/:trainSetWagonId')
+ @Delete("schedules/:id/wagons/:trainSetWagonId")
@TrainSchedulingManage()
- @ApiOperation({ summary: 'Remove an empty wagon slot from a train' })
+ @ApiOperation({ summary: "Remove an empty wagon slot from a train" })
removeWagonSlot(
- @Param('id', ParseUUIDPipe) id: string,
- @Param('trainSetWagonId', ParseUUIDPipe) trainSetWagonId: string,
+ @Param("id", ParseUUIDPipe) id: string,
+ @Param("trainSetWagonId", ParseUUIDPipe) trainSetWagonId: string,
) {
- return this.trainSchedulingService.removeTrainSetWagonSlot(id, trainSetWagonId);
+ return this.trainSchedulingService.removeTrainSetWagonSlot(
+ id,
+ trainSetWagonId,
+ );
}
- @Patch('schedules/:id/container-items/:itemId')
+ @Patch("schedules/:id/container-items/:itemId")
@TrainSchedulingManage()
- @ApiOperation({ summary: 'Update a container number on a wagon slot' })
+ @ApiOperation({ summary: "Update a container number on a wagon slot" })
updateContainerItem(
- @Param('id', ParseUUIDPipe) id: string,
- @Param('itemId', ParseUUIDPipe) itemId: string,
+ @Param("id", ParseUUIDPipe) id: string,
+ @Param("itemId", ParseUUIDPipe) itemId: string,
@Body() dto: UpdateContainerItemDto,
) {
return this.trainSchedulingService.updateContainerItem(id, itemId, dto);
}
- @Get('schedules/:id/unassigned-bookings')
+ @Get("schedules/:id/unassigned-bookings")
@TrainSchedulingView()
- @ApiOperation({ summary: 'Get unassigned bookings for a schedule' })
- getUnassignedBookings(@Param('id', ParseUUIDPipe) id: string) {
+ @ApiOperation({ summary: "Get unassigned bookings for a schedule" })
+ getUnassignedBookings(@Param("id", ParseUUIDPipe) id: string) {
return this.trainSchedulingService.getUnassignedBookings(id);
}
- @Post('schedules/:id/assign-unassigned-booking')
+ @Post("schedules/:id/assign-unassigned-booking")
@TrainSchedulingManage()
@ApiOperation({
- summary: 'Assign one linked unallocated booking to wagons (preserves existing assignments)',
+ summary:
+ "Assign one linked unallocated booking to wagons (preserves existing assignments)",
})
assignUnassignedBooking(
- @Param('id', ParseUUIDPipe) id: string,
+ @Param("id", ParseUUIDPipe) id: string,
@Body() dto: AssignUnassignedBookingDto,
) {
- return this.trainSchedulingService.assignUnassignedBookingToWagons(id, dto.bookingId);
+ return this.trainSchedulingService.assignUnassignedBookingToWagons(
+ id,
+ dto.bookingId,
+ );
}
- @Get('schedules/:id/composition-removals')
+ @Get("schedules/:id/composition-removals")
@TrainSchedulingView()
- @ApiOperation({ summary: 'Get removal log for a schedule' })
- getCompositionRemovals(@Param('id', ParseUUIDPipe) id: string) {
+ @ApiOperation({ summary: "Get removal log for a schedule" })
+ getCompositionRemovals(@Param("id", ParseUUIDPipe) id: string) {
return this.trainSchedulingService.getCompositionRemovals(id);
}
- @Post('schedules/:id/pin-wagons')
+ @Post("schedules/:id/pin-wagons")
@TrainSchedulingManage()
- @ApiOperation({ summary: 'Pin physical wagons to train set slots' })
- pinWagons(@Param('id', ParseUUIDPipe) id: string, @Body() dto: PinWagonsDto) {
+ @ApiOperation({ summary: "Pin physical wagons to train set slots" })
+ pinWagons(@Param("id", ParseUUIDPipe) id: string, @Body() dto: PinWagonsDto) {
return this.trainSchedulingService.pinWagons(id, dto);
}
- @Post('schedules/:id/finalize')
+ @Post("schedules/:id/finalize")
@TrainSchedulingManage()
- @ApiOperation({ summary: 'Finalize a draft train schedule' })
- finalizeSchedule(@Param('id', ParseUUIDPipe) id: string) {
+ @ApiOperation({ summary: "Finalize a draft train schedule" })
+ finalizeSchedule(@Param("id", ParseUUIDPipe) id: string) {
return this.trainSchedulingService.finalizeSchedule(id);
}
- @Post('schedules/:id/dispatch')
+ @Post("schedules/:id/dispatch")
@TrainSchedulingManage()
- @ApiOperation({ summary: 'Dispatch a scheduled train' })
- dispatchSchedule(@Param('id', ParseUUIDPipe) id: string) {
+ @ApiOperation({ summary: "Dispatch a scheduled train" })
+ dispatchSchedule(@Param("id", ParseUUIDPipe) id: string) {
return this.trainSchedulingService.dispatchSchedule(id);
}
// ---- batch / booking-window staff actions ----
- @Post('schedules/:id/run-batch')
+ @Post("schedules/:id/run-batch")
@TrainSchedulingManage()
- @ApiOperation({ summary: 'Manually run the batch fill for a schedule' })
- async runBatch(@Param('id', ParseUUIDPipe) id: string) {
+ @ApiOperation({ summary: "Manually run the batch fill for a schedule" })
+ async runBatch(@Param("id", ParseUUIDPipe) id: string) {
await this.bookingBatchService.fillSchedule(id);
return this.bookingBatchService.getBatchBoardDetail(id);
}
- @Post('schedules/:id/run-allocation')
+ @Post("schedules/:id/run-allocation")
@TrainSchedulingManage()
- @ApiOperation({ summary: 'Run wagon-level allocation for all eligible linked bookings' })
- async runAllocation(@Param('id', ParseUUIDPipe) id: string) {
+ @ApiOperation({
+ summary: "Run wagon-level allocation for all eligible linked bookings",
+ })
+ async runAllocation(@Param("id", ParseUUIDPipe) id: string) {
return this.bookingBatchService.runWagonAllocation(id);
}
- @Patch('schedules/:id/booking-window')
+ @Patch("schedules/:id/booking-window")
@TrainSchedulingManage()
- @ApiOperation({ summary: 'Open or close a schedule booking window' })
+ @ApiOperation({ summary: "Open or close a schedule booking window" })
async setBookingWindow(
- @Param('id', ParseUUIDPipe) id: string,
- @Body('status') status: 'OPEN' | 'CLOSED',
+ @Param("id", ParseUUIDPipe) id: string,
+ @Body("status") status: "OPEN" | "CLOSED",
) {
- await this.trainSchedulingService.setBookingWindow(id, status === 'CLOSED' ? 'CLOSED' : 'OPEN');
+ await this.trainSchedulingService.setBookingWindow(
+ id,
+ status === "CLOSED" ? "CLOSED" : "OPEN",
+ );
return this.trainSchedulingService.getContainerTrainScheduleById(id);
}
- @Post('bookings/:bookingId/mark-paid')
+ @Post("bookings/:bookingId/mark-paid")
@TrainSchedulingManage()
- @ApiOperation({ summary: 'Staff: mark a reserved booking paid and allocate it now' })
- async markBookingPaid(@Param('bookingId', ParseUUIDPipe) bookingId: string) {
+ @ApiOperation({
+ summary: "Staff: mark a reserved booking paid and allocate it now",
+ })
+ async markBookingPaid(@Param("bookingId", ParseUUIDPipe) bookingId: string) {
await this.bookingBatchService.markPaid(bookingId);
return { ok: true };
}
- @Post('bookings/:bookingId/expire')
+ @Post("bookings/:bookingId/expire")
@TrainSchedulingManage()
- @ApiOperation({ summary: 'Staff: expire a reservation and free its capacity' })
- async expireBooking(@Param('bookingId', ParseUUIDPipe) bookingId: string) {
+ @ApiOperation({
+ summary: "Staff: expire a reservation and free its capacity",
+ })
+ async expireBooking(@Param("bookingId", ParseUUIDPipe) bookingId: string) {
await this.bookingBatchService.expireReservation(bookingId);
return { ok: true };
}
- @Post('bookings/:bookingId/move-schedule')
+ @Post("bookings/:bookingId/move-schedule")
@TrainSchedulingManage()
- @ApiOperation({ summary: 'Re-point a booking to another OPEN same-route schedule' })
+ @ApiOperation({
+ summary: "Re-point a booking to another OPEN same-route schedule",
+ })
async moveBookingSchedule(
- @Param('bookingId', ParseUUIDPipe) bookingId: string,
- @Body('trainScheduleId', ParseUUIDPipe) trainScheduleId: string,
+ @Param("bookingId", ParseUUIDPipe) bookingId: string,
+ @Body("trainScheduleId", ParseUUIDPipe) trainScheduleId: string,
) {
await this.bookingBatchService.moveToSchedule(bookingId, trainScheduleId);
return { ok: true };
}
- @Get('schedules/:id/checkpoints')
+ @Get("schedules/:id/checkpoints")
@TrainSchedulingView()
- @ApiOperation({ summary: 'Get the tracking corridor + logged checkpoints for a train' })
- getScheduleCheckpoints(@Param('id', ParseUUIDPipe) id: string) {
+ @ApiOperation({
+ summary: "Get the tracking corridor + logged checkpoints for a train",
+ })
+ getScheduleCheckpoints(@Param("id", ParseUUIDPipe) id: string) {
return this.trainSchedulingService.getScheduleCheckpoints(id);
}
- @Post('schedules/:id/checkpoints')
+ @Post("schedules/:id/checkpoints")
@TrainSchedulingManage()
- @ApiOperation({ summary: 'Log the train passing a station (final station triggers arrival)' })
+ @ApiOperation({
+ summary: "Log the train passing a station (final station triggers arrival)",
+ })
recordCheckpoint(
- @Param('id', ParseUUIDPipe) id: string,
+ @Param("id", ParseUUIDPipe) id: string,
@Body() dto: RecordCheckpointDto,
) {
return this.trainSchedulingService.recordCheckpoint(id, dto);
}
- @Post('schedules/:id/arrive')
+ @Post("schedules/:id/arrive")
@TrainSchedulingManage()
- @ApiOperation({ summary: 'Mark a dispatched train arrived (move assets to destination yard, free assets)' })
- arriveSchedule(@Param('id', ParseUUIDPipe) id: string) {
+ @ApiOperation({
+ summary:
+ "Mark a dispatched train arrived (move assets to destination yard, free assets)",
+ })
+ arriveSchedule(@Param("id", ParseUUIDPipe) id: string) {
return this.trainSchedulingService.arriveSchedule(id);
}
- @Get('container/schedules')
+ @Get("container/schedules")
@TrainSchedulingView()
- @ApiOperation({ summary: 'List container train schedules' })
+ @ApiOperation({ summary: "List container train schedules" })
getContainerTrainSchedules() {
return this.trainSchedulingService.getContainerTrainSchedules();
}
- @Get('bulk/schedules')
+ @Get("bulk/schedules")
@TrainSchedulingView()
- @ApiOperation({ summary: 'List bulk train schedules' })
+ @ApiOperation({ summary: "List bulk train schedules" })
getBulkTrainSchedules() {
return this.trainSchedulingService.getContainerTrainSchedules();
}
- @Get('container/schedules/:id')
+ @Get("container/schedules/:id")
@TrainSchedulingView()
- @ApiOperation({ summary: 'Get container train schedule detail' })
- getContainerTrainScheduleById(@Param('id', ParseUUIDPipe) id: string) {
+ @ApiOperation({ summary: "Get container train schedule detail" })
+ getContainerTrainScheduleById(@Param("id", ParseUUIDPipe) id: string) {
return this.trainSchedulingService.getContainerTrainScheduleById(id);
}
- @Get('bulk/schedules/:id')
+ @Get("bulk/schedules/:id")
@TrainSchedulingView()
- @ApiOperation({ summary: 'Get bulk train schedule detail' })
- getBulkTrainScheduleById(@Param('id', ParseUUIDPipe) id: string) {
+ @ApiOperation({ summary: "Get bulk train schedule detail" })
+ getBulkTrainScheduleById(@Param("id", ParseUUIDPipe) id: string) {
return this.trainSchedulingService.getContainerTrainScheduleById(id);
}
- @Post('container/schedules/:id/cancel')
+ @Post("container/schedules/:id/cancel")
@TrainSchedulingManage()
- @ApiOperation({ summary: 'Cancel container train schedule' })
- cancelTrainSchedule(@Param('id', ParseUUIDPipe) id: string) {
+ @ApiOperation({ summary: "Cancel container train schedule" })
+ cancelTrainSchedule(@Param("id", ParseUUIDPipe) id: string) {
return this.trainSchedulingService.cancelTrainSchedule(id);
}
- @Post('bulk/schedules/:id/cancel')
+ @Post("bulk/schedules/:id/cancel")
@TrainSchedulingManage()
- @ApiOperation({ summary: 'Cancel bulk train schedule' })
- cancelBulkTrainSchedule(@Param('id', ParseUUIDPipe) id: string) {
+ @ApiOperation({ summary: "Cancel bulk train schedule" })
+ cancelBulkTrainSchedule(@Param("id", ParseUUIDPipe) id: string) {
return this.trainSchedulingService.cancelTrainSchedule(id);
}
}
diff --git a/apps/edr-freight-web/portal/package.json b/apps/edr-freight-web/portal/package.json
index ae79d7562..f5fddd009 100644
--- a/apps/edr-freight-web/portal/package.json
+++ b/apps/edr-freight-web/portal/package.json
@@ -38,6 +38,7 @@
"devDependencies": {
"@edr/eslint-config": "workspace:*",
"@edr/tsconfig": "workspace:*",
+ "@hookform/devtools": "^4.4.0",
"@tailwindcss/vite": "^4.3.0",
"@types/react": "^18.3.11",
"@types/react-dom": "^18.3.0",
diff --git a/apps/edr-freight-web/portal/src/pages/bookings/NewBookingPage.tsx b/apps/edr-freight-web/portal/src/pages/bookings/NewBookingPage.tsx
index 2ae53d576..7420f90d3 100644
--- a/apps/edr-freight-web/portal/src/pages/bookings/NewBookingPage.tsx
+++ b/apps/edr-freight-web/portal/src/pages/bookings/NewBookingPage.tsx
@@ -7,6 +7,7 @@ import { AlertCircle, Check, ChevronLeft, ChevronRight } from "lucide-react";
import { useMemo, useState } from "react";
import { useForm } from "react-hook-form";
import { useNavigate } from "react-router-dom";
+import { DevTool } from "@hookform/devtools";
import {
BookingFormInputValues,
STEPS,
@@ -22,6 +23,7 @@ import {
Step2ServiceType,
Step4Route,
Step5CargoDetails,
+ StepScheduling,
StepDocuments,
Step8Review,
} from "./new-booking-form/steps";
@@ -70,7 +72,11 @@ export default function NewBookingPage() {
const destinationYard = form.watch("destinationYard");
const direction = useMemo(
- () => getRouteDirection(referenceData?.yard.find((y) => y.id === originYard), referenceData?.yard.find((y) => y.id === destinationYard)),
+ () =>
+ getRouteDirection(
+ referenceData?.yard.find((y) => y.id === originYard),
+ referenceData?.yard.find((y) => y.id === destinationYard),
+ ),
[originYard, destinationYard],
);
@@ -100,15 +106,11 @@ export default function NewBookingPage() {
: Number(data.cargoWeight || 0);
// ── Reference data lookups ──────────────────────────────────────────
- const yards = referenceData?.yard ?? [];
const services = referenceData?.service ?? [];
const shippingLines = referenceData?.shipping_line ?? [];
const cargoTree = referenceData?.cargo_type ?? [];
const containerGroups = referenceData?.containers ?? [];
- const findYardId = (name: string): string =>
- yards.find((y) => y.name === name)?.id ?? "";
-
const findServiceTypeId = (): string => {
const code = data.serviceType === "rail" ? "RAIL" : "RAIL_AND_FORWARDING";
return services.find((s) => s.code === code)?.id ?? services[0]?.id ?? "";
@@ -117,13 +119,6 @@ export default function NewBookingPage() {
const findShippingLineId = (name: string): string | undefined =>
shippingLines.find((l) => l.name === name)?.id;
- const findContainerCargoTypeId = (): string => {
- const group = cargoTree.find(
- (g) => g.code === "CONTAINER" || /container/i.test(g.name),
- );
- return group?.id ?? "";
- };
-
const findContainerTypeId = (name: string): string => {
for (const group of containerGroups) {
const ct = group.types.find((t) => t.name === name);
@@ -139,21 +134,18 @@ export default function NewBookingPage() {
?.children?.find((c) => c.name === data.bulkCommoditytype)
: undefined;
- const cargoTypeId =
- data.cargoType === "container"
- ? findContainerCargoTypeId()
- : (selectedChild?.id ?? "");
+ const cargoTypeId = selectedChild?.id;
const cargoFreeText =
data.cargoType === "container"
? undefined
: selectedChild?.show_free_text_box
- ? data.bulkCommoditytype
+ ? data.cargoFreeText
: undefined;
// ── Build API payload ───────────────────────────────────────────────
const apiPayload: CreateBookingPayload = {
- scheduledDate: new Date().toISOString().slice(0, 10),
+ scheduledDate: new Date().toISOString(),
contractType:
data.contractType.toUpperCase() as CreateBookingPayload["contractType"],
serviceTypeId: findServiceTypeId(),
@@ -161,13 +153,13 @@ export default function NewBookingPage() {
data.equipmentReturn === "with_return"
? "WITH_RETURN"
: "WITHOUT_RETURN",
- originYardId: findYardId(data.originYard),
- destinationYardId: findYardId(data.destinationYard),
+ originYardId: data.originYard,
+ destinationYardId: data.destinationYard,
tradeDirection: direction!,
- cargoTypeId: data.cargoType === "container" ? undefined : cargoTypeId,
+ cargoTypeId,
+ trainScheduleId: data.trainScheduleId,
cargoTotalWeightVgm: totalWeight,
isHazardous: data.isHazardous,
- paymentCurrency: "USD",
allowConsolidation: data.consolidationEnabled,
// @ts-ignore
freightType:
@@ -243,67 +235,59 @@ export default function NewBookingPage() {
Back to Bookings
-
+ {/* */}
);
}
diff --git a/apps/edr-freight-web/portal/src/pages/bookings/new-booking-form/schema.ts b/apps/edr-freight-web/portal/src/pages/bookings/new-booking-form/schema.ts
index ea8611d9e..1e89b7be4 100644
--- a/apps/edr-freight-web/portal/src/pages/bookings/new-booking-form/schema.ts
+++ b/apps/edr-freight-web/portal/src/pages/bookings/new-booking-form/schema.ts
@@ -101,10 +101,12 @@ export const bookingFormSchema = z
destinationYard: z.string().min(1, "Select a destination yard."),
shippingLine: z.string(),
scheduledDate: z.string().min(1, "Select a shipment date."),
+ trainScheduleId: z.string().min(1, "Select a shipment date."),
cargoType: z.enum(["container", "bulk"], "Select a cargo type."),
cargoWeight: z.string(),
freightType: z.string(), // parent group
bulkCommoditytype: z.string(),
+ cargoFreeText: z.string(),
isHazardous: z.boolean(),
isRefrigerated: z.boolean(),
containers: z.array(
@@ -229,8 +231,10 @@ export const initialBookingFormValues: DeepPartial = {
destinationYard: "",
shippingLine: "",
scheduledDate: "",
+ trainScheduleId: "",
cargoWeight: "",
bulkCommoditytype: "",
+ cargoFreeText: "",
isHazardous: false,
isRefrigerated: false,
containers: [{ type: "20ft", containerType: "", qty: "1", vgm: "" }],
@@ -264,7 +268,7 @@ export const stepFields: Record>> = {
"containers",
"consolidationEnabled",
],
- 5: ["scheduledDate"],
+ 5: ["scheduledDate", "trainScheduleId"],
6: ["documents"],
7: ["notes", "termsAccepted"],
};
@@ -280,50 +284,32 @@ export interface WagonConfig {
type: "20ft" | "40ft";
}
-export interface WagonCalcResult {
- totalWagons: number;
- hasOddUnit: boolean;
- sharedWagons: number;
- wagonLayout: WagonConfig[];
- ft40Wagons: number;
- ft20Wagons: number;
-}
-
export function getRouteDirection(
origin: Freight.BookingReferenceYard | null | undefined,
dest: Freight.BookingReferenceYard | null | undefined,
-): Freight.ScheduleTradeDirection | null {
+): Freight.ScheduleTradeDirection | null {
if (!origin || !dest) return null;
-if(origin.country === 'ethiopia' && dest.country === 'ethiopia') {
- return 'DOMESTIC';
-}
- if(origin.country === 'ethiopia' && dest.country === 'djibouti') {
- return 'IMPORT';
+ if (origin.country === "ethiopia" && dest.country === "ethiopia") {
+ return "DOMESTIC";
}
- if(origin.country === 'djibouti' && dest.country === 'ethiopia') {
- return 'EXPORT';
+ if (origin.country === "ethiopia" && dest.country === "djibouti") {
+ return "IMPORT";
+ }
+ if (origin.country === "djibouti" && dest.country === "ethiopia") {
+ return "EXPORT";
}
return null;
}
-export function calcWagons(containers: ContainerConfig[]): WagonCalcResult {
- const Ft40Wagons = containers
- .filter((c) => c.type === "40ft")
- .reduce((sum, c) => sum + Number(c.qty), 0);
+export function calcWagons(containers: ContainerConfig[]) {
const Ft20Wagons = containers
.filter((c) => c.type === "20ft")
.reduce((sum, c) => sum + Number(c.qty), 0);
- const wagonLayout: WagonConfig[] = [];
let hasOddUnit = Ft20Wagons % 2 === 1;
- let sharedWagons = Math.floor(Ft20Wagons / 2);
return {
- totalWagons: sharedWagons + Ft40Wagons,
hasOddUnit,
- sharedWagons,
- ft40Wagons: Ft40Wagons,
ft20Wagons: Ft20Wagons,
- wagonLayout,
};
}
diff --git a/apps/edr-freight-web/portal/src/pages/bookings/new-booking-form/step-documents.tsx b/apps/edr-freight-web/portal/src/pages/bookings/new-booking-form/step-documents.tsx
index cd7db7b85..f600d10d7 100644
--- a/apps/edr-freight-web/portal/src/pages/bookings/new-booking-form/step-documents.tsx
+++ b/apps/edr-freight-web/portal/src/pages/bookings/new-booking-form/step-documents.tsx
@@ -5,12 +5,17 @@ import { Controller, type UseFormReturn } from "react-hook-form";
import {
BOOKING_DOCS_SETTING,
+ BookingFormInputValues,
type BookingDocuments,
type BookingFormValues,
} from "./schema";
import { StepHeader } from "./shared";
-type BookingForm = UseFormReturn;
+type BookingForm = UseFormReturn<
+ BookingFormInputValues,
+ any,
+ BookingFormValues
+>;
function countAttached(documents: BookingDocuments): number {
return BOOKING_DOCS_SETTING.fields.filter((f) => {
@@ -57,7 +62,11 @@ export function StepDocuments({ form }: { form: BookingForm }) {
color: attached === total ? "#0A6F4D" : "#2E5B96",
}}
>
- {attached === total ? : `${attached}/${total}`}
+ {attached === total ? (
+
+ ) : (
+ `${attached}/${total}`
+ )}
{attached === 0
diff --git a/apps/edr-freight-web/portal/src/pages/bookings/new-booking-form/step-scheduling.tsx b/apps/edr-freight-web/portal/src/pages/bookings/new-booking-form/step-scheduling.tsx
new file mode 100644
index 000000000..c403835c5
--- /dev/null
+++ b/apps/edr-freight-web/portal/src/pages/bookings/new-booking-form/step-scheduling.tsx
@@ -0,0 +1,389 @@
+import {
+ Box,
+ Button,
+ Card,
+ Grid,
+ Group,
+ Stack,
+ Text,
+ Title,
+ useMantineTheme,
+ Divider,
+} from "@mantine/core";
+import { UseFormReturn } from "react-hook-form";
+import { BookingFormInputValues, BookingFormValues } from "./schema";
+import {
+ ChevronLeft,
+ ChevronRight,
+ Info,
+ Calendar as CalendarIcon,
+} from "lucide-react";
+import type { Freight } from "@edr/types";
+import { useMemo, useState } from "react";
+import { useQuery } from "@tanstack/react-query";
+import { api } from "@/services/api";
+import {
+ format,
+ startOfMonth,
+ endOfMonth,
+ startOfWeek,
+ endOfWeek,
+ eachDayOfInterval,
+ isToday,
+ isSameMonth,
+ addMonths,
+} from "date-fns";
+
+interface StepSchedulingProps {
+ form: UseFormReturn;
+ referenceData?: Freight.BookingReferenceData;
+}
+
+export function StepScheduling({ form, referenceData }: StepSchedulingProps) {
+ const theme = useMantineTheme();
+ const [currentDate, setCurrentDate] = useState(new Date());
+ const selectedDate = form.watch("scheduledDate");
+ const originYardId = form.watch("originYard");
+ const destinationYardId = form.watch("destinationYard");
+ const cargoType = form.watch("cargoType");
+
+ const originName = useMemo(
+ () =>
+ referenceData?.yard.find((y) => y.id === originYardId)?.name ??
+ "Not selected",
+ [referenceData, originYardId],
+ );
+
+ const destinationName = useMemo(
+ () =>
+ referenceData?.yard.find((y) => y.id === destinationYardId)?.name ??
+ "Not selected",
+ [referenceData, destinationYardId],
+ );
+
+ const { data: bookableSchedules } = useQuery(
+ api.bookings.getBookableSchedules.queryOptions({
+ input: { originYardId, destinationYardId },
+ enabled: !!originYardId && !!destinationYardId,
+ }),
+ );
+
+ const scheduleMap = useMemo(() => {
+ const map = new Map();
+ if (bookableSchedules) {
+ for (const s of bookableSchedules) {
+ if (!map.has(s.scheduleDate)) {
+ map.set(s.scheduleDate, s);
+ }
+ }
+ }
+ return map;
+ }, [bookableSchedules]);
+
+ const days = useMemo(() => {
+ const monthStart = startOfMonth(currentDate);
+ const monthEnd = endOfMonth(currentDate);
+ const calStart = startOfWeek(monthStart, { weekStartsOn: 1 });
+ const calEnd = endOfWeek(monthEnd, { weekStartsOn: 1 });
+
+ return eachDayOfInterval({ start: calStart, end: calEnd }).map((date) => {
+ const dateString = format(date, "yyyy-MM-dd");
+ const schedule = scheduleMap.get(dateString);
+
+ return {
+ day: date.getDate(),
+ dateString,
+ isToday: isToday(date),
+ isSelected: selectedDate === dateString,
+ isCurrentMonth: isSameMonth(date, currentDate),
+ isFull: schedule ? schedule.remainingWagons <= 0 : false,
+ hasSchedule: !!schedule,
+ remainingWagons: schedule?.remainingWagons ?? 0,
+ scheduleId: schedule?.id ?? "",
+ };
+ });
+ }, [currentDate, scheduleMap, selectedDate]);
+
+ const legendItems = [
+ { label: "Available", color: theme.colors.gray[1] },
+ { label: "Full", color: theme.colors["edr-red-soft"][0] },
+ { label: "No Service", color: "transparent" },
+ { label: "Selected", color: theme.colors["edr-green"][5] },
+ ];
+
+ return (
+
+
+
+
+
+
+ Shipment Date
+
+
+ Pick a shipment date for your booking from the available
+ schedule.
+
+
+
+
+
+
+
+ {format(currentDate, "MMMM yyyy")}
+
+
+
+ setCurrentDate((d) => addMonths(d, -1))}
+ >
+
+
+ setCurrentDate((d) => addMonths(d, 1))}
+ >
+
+
+
+
+
+
+ {["MON", "TUE", "WED", "THU", "FRI", "SAT", "SUN"].map((d) => (
+
+ {d}
+
+ ))}
+ {days.map((d, i) => {
+ const canSelect =
+ d.isCurrentMonth && d.hasSchedule && !d.isFull;
+ return (
+ {
+ form.setValue("scheduledDate", d.dateString, {
+ shouldValidate: true,
+ });
+ form.setValue("trainScheduleId", d.scheduleId, {
+ shouldValidate: true,
+ });
+ }}
+ style={{
+ height: "80px",
+ display: "flex",
+ flexDirection: "column",
+ alignItems: "start",
+ justifyContent: "start",
+ gap: "4px",
+ borderRadius: theme.radius.md,
+ cursor: canSelect ? "pointer" : "default",
+ backgroundColor: d.isSelected
+ ? theme.colors["edr-green"][5]
+ : d.isFull
+ ? theme.colors["edr-red-soft"][0]
+ : d.hasSchedule
+ ? theme.colors.gray[0]
+ : "transparent",
+ border:
+ d.isToday && !d.isSelected
+ ? `2px solid ${theme.colors["edr-green"][5]}`
+ : "none",
+ opacity: d.isCurrentMonth ? 1 : 0,
+ }}
+ >
+
+ {d.day}
+
+ {d.isCurrentMonth && !d.isSelected && (
+ <>
+ {d.isFull && (
+
+ FULL
+
+ )}
+ {d.hasSchedule && !d.isFull && (
+
+ {d.remainingWagons} WGN
+
+ )}
+ >
+ )}
+
+ );
+ })}
+
+
+
+ {legendItems.map((item) => (
+
+
+
+ {item.label}
+
+
+ ))}
+
+
+
+
+
+
+
+
+
+ Booking Summary
+
+
+
+
+
+
+
+
+
+
+
+
+ Shipment Date
+
+
+ {selectedDate || "Not selected"}
+
+
+
+
+
+
+
+
+
+
+
+
+ Final confirmation of your selected date will be provided
+ after review of your booking details.
+
+
+
+
+
+
+
+
+ );
+}
+
+function SummaryRow({ label, value }: { label: string; value: string }) {
+ return (
+
+
+ {label}
+
+
+ {value}
+
+
+ );
+}
diff --git a/apps/edr-freight-web/portal/src/pages/bookings/new-booking-form/step5-cargo-details.tsx b/apps/edr-freight-web/portal/src/pages/bookings/new-booking-form/step5-cargo-details.tsx
index 7a7a0d888..574a51a01 100644
--- a/apps/edr-freight-web/portal/src/pages/bookings/new-booking-form/step5-cargo-details.tsx
+++ b/apps/edr-freight-web/portal/src/pages/bookings/new-booking-form/step5-cargo-details.tsx
@@ -1,7 +1,14 @@
import { useMemo } from "react";
import { Controller, useFieldArray, type UseFormReturn } from "react-hook-form";
import { MapPin, Package, Plus, Trash2, Weight } from "lucide-react";
-import { ActionIcon, Button, Skeleton, Stack, Text, TextInput } from "@mantine/core";
+import {
+ ActionIcon,
+ Button,
+ Skeleton,
+ Stack,
+ Text,
+ TextInput,
+} from "@mantine/core";
import type { Freight } from "@edr/types";
import {
BookingFormInputValues,
@@ -17,7 +24,11 @@ import {
StepLabel,
} from "./shared";
-type BookingForm = UseFormReturn;
+type BookingForm = UseFormReturn<
+ BookingFormInputValues,
+ any,
+ BookingFormValues
+>;
export function Step5CargoDetails({
form,
@@ -32,6 +43,7 @@ export function Step5CargoDetails({
}) {
const cargoType = form.watch("cargoType");
const freightType = form.watch("freightType");
+ const bulkCommoditytype = form.watch("bulkCommoditytype");
const containers = form.watch("containers");
const { fields, append, remove } = useFieldArray({
@@ -39,16 +51,28 @@ export function Step5CargoDetails({
name: "containers",
});
- const containerTypeOptions = useMemo(() => {
- if (!referenceData?.containers) return [];
- return referenceData.containers.flatMap((group) =>
- group.types.map((t) => t.name),
+ const containerTypeOptionsBySize = useMemo(() => {
+ if (!referenceData?.containers) return new Map();
+ return new Map(
+ referenceData.containers.map((g) => [g.size, g.types.map((t) => t.name)]),
);
}, [referenceData]);
+ const selectedCommodity = useMemo(() => {
+ if (!referenceData?.cargo_type || !freightType || !bulkCommoditytype) return null;
+ const group = referenceData.cargo_type.find(
+ (g) => g.code.toLowerCase() === freightType,
+ );
+ return group?.children?.find(
+ (c) => c.name === bulkCommoditytype,
+ ) ?? null;
+ }, [referenceData, freightType, bulkCommoditytype]);
+
const freightTypeGroups = useMemo(() => {
if (!referenceData?.cargo_type) return [];
- return referenceData.cargo_type.filter((g) => g.code !== "CONTAINER");
+ return referenceData.cargo_type.filter(
+ (g) => g.code !== "CONTAINER" && !/container/i.test(g.name),
+ );
}, [referenceData]);
const commodityOptions = useMemo(() => {
@@ -219,6 +243,22 @@ export function Step5CargoDetails({
)}
/>
)}
+
+ {selectedCommodity?.show_free_text_box && (
+ (
+
+ )}
+ />
+ )}
)}
@@ -253,7 +293,13 @@ export function Step5CargoDetails({
className="flex flex-col gap-3 rounded-xl border border-gray-200 bg-gray-50/50 p-4"
>
-
+
Container {index + 1}
{fields.length > 1 && (
@@ -300,7 +346,9 @@ export function Step5CargoDetails({
{ct.label}
- {ct.limit}
+
+ {ct.limit}
+
))}
@@ -324,7 +372,10 @@ export function Step5CargoDetails({
type="button"
onClick={() =>
qtyField.onChange(
- Math.max(1, Number(qtyField.value ?? 1) - 1).toString(),
+ Math.max(
+ 1,
+ Number(qtyField.value ?? 1) - 1,
+ ).toString(),
)
}
className="flex h-9 w-9 shrink-0 items-center justify-center rounded-lg border border-gray-200 bg-white text-sm font-medium transition hover:bg-gray-50"
@@ -333,7 +384,9 @@ export function Step5CargoDetails({
qtyField.onChange(e.target.value)}
+ onChange={(e) =>
+ qtyField.onChange(e.target.value)
+ }
onBlur={qtyField.onBlur}
type="number"
min={1}
@@ -388,7 +441,11 @@ export function Step5CargoDetails({
error={fieldState.error}
label="Container Type *"
placeholder="Select type..."
- data={containerTypeOptions}
+ data={
+ containerTypeOptionsBySize.get(
+ containers[index]?.type ?? "20ft",
+ ) ?? []
+ }
/>
)}
/>
diff --git a/apps/edr-freight-web/portal/src/pages/bookings/new-booking-form/step6-wagon-allocation.tsx b/apps/edr-freight-web/portal/src/pages/bookings/new-booking-form/step6-wagon-allocation.tsx
deleted file mode 100644
index c58b34241..000000000
--- a/apps/edr-freight-web/portal/src/pages/bookings/new-booking-form/step6-wagon-allocation.tsx
+++ /dev/null
@@ -1,111 +0,0 @@
-import { type UseFormReturn } from "react-hook-form";
-import { type BookingFormValues, type WagonCalcResult } from "./schema";
-import { AlertBox, StepHeader, StepLabel } from "./shared";
-
-type BookingForm = UseFormReturn;
-
-export function Step6WagonAllocation({
- form,
- wagons,
-}: {
- form: BookingForm;
- wagons: WagonCalcResult | null;
-}) {
- const containers = form.watch("containers") ?? [];
- const totalContainers = containers.reduce(
- (sum, c) => sum + Number(c.qty || 0),
- 0,
- );
- const containerSummary = containers
- .filter((c) => +c.qty > 0)
- .map((c) => `${c.qty} × ${c.type}`)
- .join(", ");
- return (
-
-
-
- {!wagons ? (
-
- Complete the container configuration in the previous step to see wagon
- allocation.
-
- ) : (
- <>
-
-
-
- {wagons.totalWagons}
-
-
- Wagons Required
-
-
-
-
{totalContainers}
-
- {containerSummary || "Containers"}
-
-
-
-
{wagons.sharedWagons}
-
Shared Slots
-
-
-
-
-
Wagon Layout
-
- {new Array(wagons.ft40Wagons).fill(0).map((_, index) => (
-
- 1 × 40ft
-
- ))}
- {new Array(wagons.sharedWagons).fill(0).map((_, index) => (
-
- 2 × 20ft
-
- ))}
- {wagons.hasOddUnit && (
-
- 1 × 20ft
-
- )}
-
-
-
- {wagons.hasOddUnit && (
- <>
-
-
-
-
Unpaired 20ft Container
-
- One 20ft container occupies only half a wagon. The wagon
- will depart once a co-loader is found to fill the
- remaining slot, which may delay departure {" "}
- beyond the standard lead time.
-
-
-
-
- >
- )}
- >
- )}
-
- );
-}
diff --git a/apps/edr-freight-web/portal/src/pages/bookings/new-booking-form/steps.tsx b/apps/edr-freight-web/portal/src/pages/bookings/new-booking-form/steps.tsx
index 9f1445f91..2532da237 100644
--- a/apps/edr-freight-web/portal/src/pages/bookings/new-booking-form/steps.tsx
+++ b/apps/edr-freight-web/portal/src/pages/bookings/new-booking-form/steps.tsx
@@ -2,5 +2,6 @@ export { Step1ContractType } from "./step1-contract-type";
export { Step2ServiceType } from "./step2-service-type";
export { Step4Route } from "./step4-route";
export { Step5CargoDetails } from "./step5-cargo-details";
+export { StepScheduling } from "./step-scheduling";
export { StepDocuments } from "./step-documents";
export { Step8Review } from "./step8-review";
diff --git a/packages/types/src/freight/index.ts b/packages/types/src/freight/index.ts
index 2ba2c9535..833a5d437 100644
--- a/packages/types/src/freight/index.ts
+++ b/packages/types/src/freight/index.ts
@@ -463,9 +463,9 @@ export interface CreateBookingContainerDto {
export interface CreateBookingDto {
reference?: string;
- customerId?: string;
companyId?: string;
trainId?: string;
+ trainScheduleId?: string;
scheduledDate: string;
contractType: "NEW" | "RENEWAL";
previousContractId?: string;
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index f918ecee4..c336eb8bf 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -206,7 +206,7 @@ importers:
version: 5.101.0(react@19.2.6)
'@tria-plc/iamui-common':
specifier: 1.1.2
- version: 1.1.2(9eda5000a9f7ac3b614e21a2a3a3f1e2)
+ version: 1.1.2(631ddfe3435b77e5a0893e986e0c71da)
axios:
specifier: ^1.7.7
version: 1.17.0
@@ -365,6 +365,9 @@ importers:
'@edr/tsconfig':
specifier: workspace:*
version: link:../../../packages/config/tsconfig
+ '@hookform/devtools':
+ specifier: ^4.4.0
+ version: 4.4.0(@types/react@18.3.31)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
'@tailwindcss/vite':
specifier: ^4.3.0
version: 4.3.0(vite@5.4.21(@types/node@24.13.1)(lightningcss@1.32.0)(terser@5.48.0))
@@ -2090,6 +2093,12 @@ packages:
peerDependencies:
hono: ^4
+ '@hookform/devtools@4.4.0':
+ resolution: {integrity: sha512-Mtlic+uigoYBPXlfvPBfiYYUZuyMrD3pTjDpVIhL6eCZTvQkHsKBSKeZCvXWUZr8fqrkzDg27N+ZuazLKq6Vmg==}
+ peerDependencies:
+ react: ^16.8.0 || ^17 || ^18 || ^19
+ react-dom: ^16.8.0 || ^17 || ^18 || ^19
+
'@hookform/resolvers@3.10.0':
resolution: {integrity: sha512-79Dv+3mDF7i+2ajj7SkypSKHhl1cbln1OGavqrsF7p6mbUv11xpqpacPsGDCTRvCSjEEIez2ef1NveSVL3b0Ag==}
peerDependencies:
@@ -9613,6 +9622,11 @@ packages:
resolution: {integrity: sha512-LWzX2KsqcB1wqQ4AHgYb4RsDXauQiqhjLk+6hjbaeHG4zpjjVAB6wC/gz6X0l+Du1cN3pUB5ZlrvTbhGSNnUQQ==}
engines: {node: '>=18.0.0'}
+ little-state-machine@4.8.1:
+ resolution: {integrity: sha512-liPHqaWMQ7rzZryQUDnbZ1Gclnnai3dIyaJ0nAgwZRXMzqbYrydrlCI0NDojRUbE5VYh5vu6hygEUZiH77nQkQ==}
+ peerDependencies:
+ react: ^16.8.0 || ^17 || ^18 || ^19
+
load-esm@1.0.3:
resolution: {integrity: sha512-v5xlu8eHD1+6r8EHTg6hfmO97LN8ugKtiXcy5e6oN72iD2r6u0RPfLl6fxM+7Wnh2ZRq15o0russMst44WauPA==}
engines: {node: '>=13.2.0'}
@@ -11382,6 +11396,11 @@ packages:
'@types/react':
optional: true
+ react-simple-animate@3.5.3:
+ resolution: {integrity: sha512-Ob+SmB5J1tXDEZyOe2Hf950K4M8VaWBBmQ3cS2BUnTORqHjhK0iKG8fB+bo47ZL15t8d3g/Y0roiqH05UBjG7A==}
+ peerDependencies:
+ react-dom: ^16.8.0 || ^17 || ^18 || ^19
+
react-smooth@4.0.4:
resolution: {integrity: sha512-gnGKTpYwqL0Iii09gHobNolvX4Kiq4PKx6eWBCYYix+8cdw+cGo3do906l1NBPKkSWx1DghC1dlWG9L2uGd61Q==}
peerDependencies:
@@ -13008,6 +13027,12 @@ packages:
'@types/react':
optional: true
+ use-deep-compare-effect@1.8.1:
+ resolution: {integrity: sha512-kbeNVZ9Zkc0RFGpfMN3MNfaKNvcLNyxOAAd9O4CBZ+kCBXXscn9s/4I+8ytUER4RDpEYs5+O6Rs4PqiZ+rHr5Q==}
+ engines: {node: '>=10', npm: '>=6'}
+ peerDependencies:
+ react: '>=16.13'
+
use-isomorphic-layout-effect@1.2.1:
resolution: {integrity: sha512-tpZZ+EX0gaghDAiFR37hj5MgY6ZN55kLiPkJsKxBMZ6GZdOSPJXiOzPM984oPYZ5AnehYx5WQp1+ME8I/P/pRA==}
peerDependencies:
@@ -14693,6 +14718,22 @@ snapshots:
dependencies:
hono: 4.12.23
+ '@hookform/devtools@4.4.0(@types/react@18.3.31)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
+ dependencies:
+ '@emotion/react': 11.14.0(@types/react@18.3.31)(react@19.2.6)
+ '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@18.3.31)(react@19.2.6))(@types/react@18.3.31)(react@19.2.6)
+ '@types/lodash': 4.17.24
+ little-state-machine: 4.8.1(react@19.2.6)
+ lodash: 4.18.1
+ react: 19.2.6
+ react-dom: 19.2.6(react@19.2.6)
+ react-simple-animate: 3.5.3(react-dom@19.2.6(react@19.2.6))
+ use-deep-compare-effect: 1.8.1(react@19.2.6)
+ uuid: 8.3.2
+ transitivePeerDependencies:
+ - '@types/react'
+ - supports-color
+
'@hookform/resolvers@3.10.0(react-hook-form@7.77.0(react@18.3.1))':
dependencies:
react-hook-form: 7.77.0(react@18.3.1)
@@ -15569,29 +15610,6 @@ snapshots:
transitivePeerDependencies:
- '@types/react'
- '@mui/x-date-pickers@6.20.2(@emotion/react@11.14.0(@types/react@18.3.31)(react@19.2.6))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.31)(react@19.2.6))(@types/react@18.3.31)(react@19.2.6))(@mui/material@5.18.0(@emotion/react@11.14.0(@types/react@18.3.31)(react@19.2.6))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.31)(react@19.2.6))(@types/react@18.3.31)(react@19.2.6))(@types/react@18.3.31)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(@mui/system@5.18.0(@emotion/react@11.14.0(@types/react@18.3.31)(react@19.2.6))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.31)(react@19.2.6))(@types/react@18.3.31)(react@19.2.6))(@types/react@18.3.31)(react@19.2.6))(@types/react@18.3.31)(date-fns@4.4.0)(dayjs@1.11.21)(luxon@3.7.2)(moment@2.30.1)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
- dependencies:
- '@babel/runtime': 7.29.7
- '@mui/base': 5.0.0-beta.70(@types/react@18.3.31)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- '@mui/material': 5.18.0(@emotion/react@11.14.0(@types/react@18.3.31)(react@19.2.6))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.31)(react@19.2.6))(@types/react@18.3.31)(react@19.2.6))(@types/react@18.3.31)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- '@mui/system': 5.18.0(@emotion/react@11.14.0(@types/react@18.3.31)(react@19.2.6))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.31)(react@19.2.6))(@types/react@18.3.31)(react@19.2.6))(@types/react@18.3.31)(react@19.2.6)
- '@mui/utils': 5.17.1(@types/react@18.3.31)(react@19.2.6)
- '@types/react-transition-group': 4.4.12(@types/react@18.3.31)
- clsx: 2.1.1
- prop-types: 15.8.1
- react: 19.2.6
- react-dom: 19.2.6(react@19.2.6)
- react-transition-group: 4.4.5(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- optionalDependencies:
- '@emotion/react': 11.14.0(@types/react@18.3.31)(react@19.2.6)
- '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@18.3.31)(react@19.2.6))(@types/react@18.3.31)(react@19.2.6)
- date-fns: 4.4.0
- dayjs: 1.11.21
- luxon: 3.7.2
- moment: 2.30.1
- transitivePeerDependencies:
- - '@types/react'
-
'@napi-rs/canvas-android-arm64@0.1.100':
optional: true
@@ -18783,146 +18801,6 @@ snapshots:
- webpack-command
- worker-loader
- '@tria-plc/iamui-common@1.1.2(9eda5000a9f7ac3b614e21a2a3a3f1e2)':
- dependencies:
- '@chakra-ui/react': 3.35.0(@emotion/react@11.14.0(@types/react@18.3.31)(react@19.2.6))(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- '@emotion/react': 11.14.0(@types/react@18.3.31)(react@19.2.6)
- '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@18.3.31)(react@19.2.6))(@types/react@18.3.31)(react@19.2.6)
- '@hookform/resolvers': 5.4.0(react-hook-form@7.77.0(react@19.2.6))
- '@lottiefiles/react-lottie-player': 3.6.0(react@19.2.6)
- '@mantine/core': 8.3.18(@mantine/hooks@8.3.18(react@19.2.6))(@types/react@18.3.31)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- '@mantine/dates': 8.3.18(@mantine/core@8.3.18(@mantine/hooks@8.3.18(react@19.2.6))(@types/react@18.3.31)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(@mantine/hooks@8.3.18(react@19.2.6))(dayjs@1.11.21)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- '@mantine/hooks': 8.3.18(react@19.2.6)
- '@onlyoffice/document-editor-react': 2.2.0(@onlyoffice/doceditor-types@9.4.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- '@radix-ui/react-accordion': 1.2.13(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- '@radix-ui/react-alert-dialog': 1.1.16(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- '@radix-ui/react-avatar': 1.1.12(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- '@radix-ui/react-checkbox': 1.3.4(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- '@radix-ui/react-collapsible': 1.1.13(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- '@radix-ui/react-context-menu': 2.3.0(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- '@radix-ui/react-dialog': 1.1.16(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- '@radix-ui/react-dropdown-menu': 2.1.17(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- '@radix-ui/react-hover-card': 1.1.16(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- '@radix-ui/react-label': 2.1.9(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- '@radix-ui/react-navigation-menu': 1.2.15(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- '@radix-ui/react-popover': 1.1.16(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- '@radix-ui/react-progress': 1.1.9(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- '@radix-ui/react-radio-group': 1.4.0(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- '@radix-ui/react-scroll-area': 1.2.11(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- '@radix-ui/react-select': 2.3.0(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- '@radix-ui/react-separator': 1.1.9(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- '@radix-ui/react-slider': 1.4.0(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- '@radix-ui/react-slot': 1.2.5(@types/react@18.3.31)(react@19.2.6)
- '@radix-ui/react-switch': 1.3.0(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- '@radix-ui/react-tabs': 1.1.14(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- '@radix-ui/react-toast': 1.2.16(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- '@radix-ui/react-toggle-group': 1.1.12(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- '@radix-ui/react-tooltip': 1.2.9(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- '@react-pdf-viewer/core': 3.12.0(pdfjs-dist@5.4.296)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- '@react-pdf-viewer/default-layout': 3.12.0(pdfjs-dist@5.4.296)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- '@react-pdf/renderer': 4.5.1(react@19.2.6)
- '@reduxjs/toolkit': 2.12.0(react-redux@9.3.0(@types/react@18.3.31)(react@19.2.6)(redux@5.0.1))(react@19.2.6)
- '@tabler/icons-react': 3.44.0(react@19.2.6)
- '@tailwindcss/vite': 4.3.0(vite@5.4.21(@types/node@24.13.1)(lightningcss@1.32.0)(terser@5.48.0))
- '@tanstack/react-query': 5.101.0(react@19.2.6)
- '@tanstack/react-query-devtools': 5.101.0(@tanstack/react-query@5.101.0(react@19.2.6))(react@19.2.6)
- '@tanstack/react-table': 8.21.3(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- '@tinymce/tinymce-react': 6.3.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(tinymce@7.9.3)
- '@tiptap/extension-color': 3.26.0(@tiptap/extension-text-style@3.26.0(@tiptap/core@3.26.0(@tiptap/pm@3.26.0)))
- '@tiptap/extension-highlight': 3.26.0(@tiptap/core@3.26.0(@tiptap/pm@3.26.0))
- '@tiptap/extension-image': 3.26.0(@tiptap/core@3.26.0(@tiptap/pm@3.26.0))
- '@tiptap/extension-link': 3.26.0(@tiptap/core@3.26.0(@tiptap/pm@3.26.0))(@tiptap/pm@3.26.0)
- '@tiptap/extension-text-align': 3.26.0(@tiptap/core@3.26.0(@tiptap/pm@3.26.0))
- '@tiptap/extension-text-style': 3.26.0(@tiptap/core@3.26.0(@tiptap/pm@3.26.0))
- '@tiptap/extension-underline': 3.26.0(@tiptap/core@3.26.0(@tiptap/pm@3.26.0))
- '@tiptap/react': 3.26.0(@floating-ui/dom@1.7.6)(@tiptap/core@3.26.0(@tiptap/pm@3.26.0))(@tiptap/pm@3.26.0)(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- '@tiptap/starter-kit': 3.26.0
- '@types/node': 24.13.1
- '@types/tinymce': 4.6.9
- axios: 1.17.0
- class-variance-authority: 0.7.1
- clsx: 2.1.1
- cmdk: 1.1.1(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- date-fns: 4.4.0
- dayjs: 1.11.21
- ethiopian-calendar-date-converter: 2.1.6
- ethiopian-calendar-new: 1.1.0
- file-type: 18.7.0
- force: 0.0.3
- framer-motion: 12.40.0(@emotion/is-prop-valid@1.4.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- html2canvas: 1.4.1
- i18next: 25.10.10(typescript@5.9.3)
- i18next-browser-languagedetector: 8.2.1
- jquery: 3.7.1
- js-cookie: 3.0.8
- jspdf: 3.0.4
- loadash: 1.0.0
- lodash: 4.18.1
- lucide-react: 0.513.0(react@19.2.6)
- mui-ethiopian-datepicker: 0.3.2(7d86988bc4fd0020aebaf3d4b373b1a0)
- next-themes: 0.4.6(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- path: 0.12.7
- qs: 6.15.2
- react: 19.2.6
- react-cookie: 8.1.2(@types/react@18.3.31)(react@19.2.6)
- react-datepicker: 8.10.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- react-day-picker: 9.14.0(react@19.2.6)
- react-dom: 19.2.6(react@19.2.6)
- react-dropzone: 14.4.1(react@19.2.6)
- react-hook-form: 7.77.0(react@19.2.6)
- react-i18next: 15.7.4(i18next@25.10.10(typescript@5.9.3))(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@5.9.3)
- react-icons: 5.6.0(react@19.2.6)
- react-image-crop: 11.0.10(react@19.2.6)
- react-intersection-observer: 9.16.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- react-is: 19.2.7
- react-joyride: 2.9.3(@types/react@18.3.31)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- react-pdf: 10.4.1(@types/react@18.3.31)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- react-pdf-html: 2.1.5(@react-pdf/renderer@4.5.1(react@19.2.6))(react@19.2.6)
- react-pdf-viewer: 0.1.0
- react-redux: 9.3.0(@types/react@18.3.31)(react@19.2.6)(redux@5.0.1)
- react-resizable-panels: 3.0.6(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- react-router-dom: 7.17.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- react-signature-canvas: 1.1.0-alpha.2(@types/prop-types@15.7.15)(@types/react@18.3.31)(prop-types@15.8.1)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- recharts: 3.8.1(@types/react@18.3.31)(react-dom@19.2.6(react@19.2.6))(react-is@19.2.7)(react@19.2.6)(redux@5.0.1)
- socket.io-client: 4.8.3
- sonner: 2.0.7(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- tailwind-merge: 3.6.0
- tailwind-scrollbar-hide: 4.0.0(tailwindcss@4.3.0)
- tailwindcss: 4.3.0
- tailwindcss-animate: 1.0.7(tailwindcss@4.3.0)
- tesseract.js: 7.0.0
- tinymce: 7.9.3
- url: 0.11.4
- vaul: 1.1.2(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
- xlsx: 0.18.5
- zod: 3.25.76
- transitivePeerDependencies:
- - '@emotion/is-prop-valid'
- - '@floating-ui/dom'
- - '@mui/icons-material'
- - '@mui/material'
- - '@mui/x-date-pickers'
- - '@onlyoffice/doceditor-types'
- - '@tiptap/core'
- - '@tiptap/pm'
- - '@types/prop-types'
- - '@types/react'
- - '@types/react-dom'
- - bufferutil
- - debug
- - encoding
- - pdfjs-dist
- - prop-types
- - react-native
- - redux
- - supports-color
- - typescript
- - utf-8-validate
- - vite
- - webpack-cli
- - webpack-command
- - worker-loader
-
'@ts-morph/common@0.27.0':
dependencies:
fast-glob: 3.3.3
@@ -25223,6 +25101,10 @@ snapshots:
rfdc: 1.4.1
wrap-ansi: 9.0.2
+ little-state-machine@4.8.1(react@19.2.6):
+ dependencies:
+ react: 19.2.6
+
load-esm@1.0.3: {}
load-json-file@1.1.0:
@@ -27387,6 +27269,10 @@ snapshots:
'@types/prop-types': 15.7.15
'@types/react': 18.3.31
+ react-simple-animate@3.5.3(react-dom@19.2.6(react@19.2.6)):
+ dependencies:
+ react-dom: 19.2.6(react@19.2.6)
+
react-smooth@4.0.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
dependencies:
fast-equals: 5.4.0
@@ -29433,11 +29319,11 @@ snapshots:
optionalDependencies:
'@types/react': 18.3.31
- use-isomorphic-layout-effect@1.2.1(@types/react@18.3.31)(react@18.3.1):
+ use-deep-compare-effect@1.8.1(react@19.2.6):
dependencies:
- react: 18.3.1
- optionalDependencies:
- '@types/react': 18.3.31
+ '@babel/runtime': 7.29.7
+ dequal: 2.0.3
+ react: 19.2.6
use-isomorphic-layout-effect@1.2.1(@types/react@18.3.31)(react@19.2.6):
dependencies: