Train scheduling API,Routes and UI

This commit is contained in:
hagiye
2026-06-08 16:31:28 +03:00
parent 3d1f972e52
commit 7facbeda22
38 changed files with 1993 additions and 696 deletions

View File

@@ -36,21 +36,12 @@ import RuleEngineLegacyRedirect from "./pages/ruleEngine/RuleEngineLegacyRedirec
import RuleEngineResourcePage from "./pages/ruleEngine/RuleEngineResourcePage";
import TrainsPage from "./pages/trains/TrainsPage";
import {
<<<<<<< HEAD
CargoesCrudPage,
ContainersCrudPage,
TrainMasterDataPage,
WagonTypesCrudPage,
WagonsCrudPage,
} from "./pages/fleet/FleetCrudPages";
=======
CargoesCrudPage,
ContainersCrudPage,
LocomotivesCrudPage,
TrainMasterDataPage,
WagonsCrudPage,
} from "./pages/fleet/FleetCrudPages";
>>>>>>> b9cfce70fe17b5066ae5320cfcc595bf3c253467
import { getCategorySidebarChildren } from "./pages/ruleEngine/config/resources";
import TrainDetailPage from "./pages/trains/TrainDetailPage";
import RoutesPage from "./pages/fleet/RoutesPage";
@@ -101,15 +92,15 @@ const buildSidebarSections = (demoItems: SidebarItem[]): SidebarSection[] => [
href: "/dashboard/trains",
icon: <Train />,
},
{
label: "Wagon types",
href: "/dashboard/wagon-types",
icon: <Boxes />,
},
{
label: "Wagons",
href: "/dashboard/wagons",
icon: <Truck />,
{
label: "Wagon types",
href: "/dashboard/wagon-types",
icon: <Boxes />,
},
{
label: "Wagons",
href: "/dashboard/wagons",
icon: <Truck />,
},
{
label: "Containers",
@@ -255,12 +246,6 @@ const App = () => {
element={<BookingContractPage />}
/>
<Route path="operations/train-scheduling" element={<TrainsPage />} />
<<<<<<< HEAD
<Route path="trains" element={<TrainMasterDataPage />} />
<Route path="trains/:id" element={<TrainDetailPage />} />
<Route path="wagon-types" element={<WagonTypesCrudPage />} />
<Route path="wagons" element={<WagonsCrudPage />} />
=======
<Route path="trains" element={<TrainMasterDataPage />} />
<Route path="operations/train-scheduling" element={<TrainsPage />} />
<Route path="routes" element={<RoutesPage />} />
@@ -268,7 +253,6 @@ const App = () => {
<Route path="trains" element={<TrainMasterDataPage />} />
<Route path="trains/:id" element={<TrainDetailPage />} />
<Route path="wagons" element={<WagonsCrudPage />} />
>>>>>>> b9cfce70fe17b5066ae5320cfcc595bf3c253467
<Route path="containers" element={<ContainersCrudPage />} />
<Route path="cargoes" element={<CargoesCrudPage />} />

View File

@@ -127,6 +127,8 @@ export const URL_CONSTANTS = {
SCHEDULE_BY_ID: (id: string) => `/train-scheduling/container/schedules/${id}`,
CANCEL_SCHEDULE: (id: string) =>
`/train-scheduling/container/schedules/${id}/cancel`,
PUBLISH_SCHEDULE: (id: string) =>
`/train-scheduling/container/schedules/${id}/publish`,
},
RULE_ENGINE: {

View File

@@ -50,6 +50,7 @@ import {
useUpdateContainer,
} from '@/hooks/useContainers';
import { useCreateTrain, useDeleteTrain, useTrains, useUpdateTrain } from '@/hooks/useTrains';
import { useRouteYards } from '@/hooks/useRoutes';
import { useCreateWagon, useDeleteWagon, useUpdateWagon, useWagons } from '@/hooks/useWagons';
import {
useCreateLocomotive,
@@ -892,10 +893,15 @@ export function WagonTypesCrudPage() {
export function WagonsCrudPage() {
const query = useWagons();
const { data: wagonTypes = [] } = useWagonTypes();
const { data: yards = [] } = useRouteYards();
const wagonTypeOptions = wagonTypes.map((type: any) => ({
value: type.id,
label: `${type.code} - ${type.name}`,
}));
const yardOptions = yards.map((yard: any) => ({
value: yard.id,
label: `${yard.label ?? yard.code} (${yard.country ?? '-'})`,
}));
return (
<FleetCrudPage<Wagon>
title="Wagons"
@@ -906,10 +912,26 @@ export function WagonsCrudPage() {
create={useCreateWagon()}
update={useUpdateWagon()}
remove={useDeleteWagon()}
searchText={(wagon) => [wagon.wagonNumber, wagon.wagonTypeId, wagon.trainId, wagon.status].join(' ')}
searchText={(wagon) => [
wagon.wagonNumber,
wagon.wagonTypeId,
wagon.trainId,
wagon.status,
wagon.currentLocationYard?.label,
wagon.currentLocationYard?.code,
wagon.currentLocationYard?.country,
].join(' ')}
columns={[
{ key: 'wagonNumber', label: 'Number' },
{ key: 'wagonTypeId', label: 'Type', render: (wagon) => optionLabel(wagonTypeOptions, wagon.wagonTypeId) },
{
key: 'currentLocationYardId',
label: 'Location',
render: (wagon) =>
wagon.currentLocationYard
? `${wagon.currentLocationYard.label ?? wagon.currentLocationYard.code} (${wagon.currentLocationYard.country ?? '-'})`
: '-',
},
{ key: 'maxPayloadWeight', label: 'Max payload' },
{ key: 'status', label: 'Status', render: (wagon) => statusBadge(wagon.status) },
]}
@@ -927,12 +949,31 @@ export function WagonsCrudPage() {
return { maxPayloadWeight: Number(selectedType.capacityTons) };
},
},
{
key: 'currentLocationYardId',
label: 'Wagon location',
type: 'select',
required: true,
options: yardOptions,
},
{ key: 'tareWeight', label: 'Tare weight', type: 'number', required: true },
{ key: 'maxPayloadWeight', label: 'Max payload weight', type: 'number', required: true },
{ key: 'status', label: 'Status' },
{
key: 'status',
label: 'Status',
type: 'select',
options: [
{ value: 'AVAILABLE', label: 'Available' },
{ value: 'IMPORT_READY', label: 'Import ready' },
{ value: 'EXPORT_READY', label: 'Export ready' },
{ value: 'ASSIGNED', label: 'Assigned' },
{ value: 'MAINTENANCE', label: 'Maintenance' },
{ value: 'RETIRED', label: 'Retired' },
],
},
{ key: 'notes', label: 'Notes' },
]}
emptyValues={{ wagonNumber: '', wagonTypeId: '', tareWeight: 0, maxPayloadWeight: 0, status: 'AVAILABLE', notes: '' }}
emptyValues={{ wagonNumber: '', wagonTypeId: '', currentLocationYardId: '', tareWeight: 0, maxPayloadWeight: 0, status: 'AVAILABLE', notes: '' }}
/>
);
}

File diff suppressed because it is too large Load Diff

View File

@@ -5,6 +5,9 @@ import { URL_CONSTANTS } from '@/constants/URLS';
export type LocomotiveType = 'DIESEL' | 'ELECTRIC';
export type LocomotiveStatus =
| 'AVAILABLE'
| 'UNAVAILABLE'
| 'IMPORT_READY'
| 'EXPORT_READY'
| 'MAINTENANCE'
| 'ASSIGNED'
| 'OUT_OF_SERVICE';

View File

@@ -70,6 +70,14 @@ export const trainSchedulingService = {
return unwrap(response.data);
},
publishSchedule: async (id: string): Promise<TrainScheduleDetail> => {
const response = await client.post<TrainScheduleDetail>(
URL_CONSTANTS.TRAIN_SCHEDULING.PUBLISH_SCHEDULE(id),
{},
);
return unwrap(response.data);
},
getAvailableLocomotives: async (): Promise<LocomotiveRecord[]> => {
const response = await client.get<LocomotiveRecord[]>(URL_CONSTANTS.LOCOMOTIVES.BASE, {
params: { status: 'AVAILABLE' },

View File

@@ -6,6 +6,19 @@ export interface Wagon {
wagonTypeId: string;
trainId: string | null;
sequenceNumber: number | null;
currentLocationYardId: string | null;
currentLocationYard?: {
id: string;
code: string;
label: string;
country?: string;
} | null;
wagonType?: {
id: string;
code: string;
name: string;
supportedLoadTypes?: string[];
} | null;
tareWeight: number;
maxPayloadWeight: number;
status: string;

View File

@@ -6,6 +6,9 @@ export const BOOKING_STATUSES = [
"PENDING_APPROVAL",
"APPROVED_PENDING_SIGNATURE",
"APPROVED",
"READY_FOR_ASSIGNMENT",
"WAGON_ASSIGNED",
"INVOICED",
"CONTRACT_READY",
"SIGNED_CUSTOMER",
"FULLY_EXECUTED",

View File

@@ -23,6 +23,9 @@ export interface EligibleContainerBookingsResponse {
items: EligibleContainerBooking[];
}
export type AssignmentType = "CONTAINER" | "BULK";
export type TradeDirection = "IMPORT" | "EXPORT" | "DOMESTIC";
export interface WagonPlanAllocation {
bookingId: string;
bookingReference: string;
@@ -57,7 +60,14 @@ export interface LocomotiveRecord {
name?: string | null;
maxPullWeightTons: number;
maxTrainLengthMeters: number;
status: 'AVAILABLE' | 'ASSIGNED' | 'MAINTENANCE' | 'OUT_OF_SERVICE';
status:
| 'AVAILABLE'
| 'UNAVAILABLE'
| 'IMPORT_READY'
| 'EXPORT_READY'
| 'ASSIGNED'
| 'MAINTENANCE'
| 'OUT_OF_SERVICE';
locomotiveType?: 'DIESEL' | 'ELECTRIC';
}
@@ -125,6 +135,11 @@ export interface TrainScheduleDetail {
code: string;
name: string;
} | null;
physicalWagon?: {
id: string;
wagonNumber: string;
status: string;
} | null;
allocations: Array<{
id: string;
bookingId: string;
@@ -146,6 +161,8 @@ export interface TrainScheduleFilters {
originStationId?: string;
destinationStationId?: string;
scheduleDate?: string;
assignmentType?: AssignmentType;
tradeDirection?: TradeDirection;
}
export interface TrainSchedulePreviewPayload {
@@ -153,10 +170,15 @@ export interface TrainSchedulePreviewPayload {
scheduleDate: string;
originStationId: string;
destinationStationId: string;
assignmentType?: AssignmentType;
}
export interface CreateTrainSchedulePayload {
routeId: string;
scheduleDate: string;
arrivalDate?: string;
locomotiveId: string;
assignmentType?: AssignmentType;
bookingIds: string[];
wagonIds?: string[];
}