mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-29 03:40:56 +00:00
Merge pull request #768 from Tria-plc/intercity-load-unload
feat(intercity): show intercity cargo across every train
This commit is contained in:
@@ -40,6 +40,68 @@ export class IntercityService {
|
|||||||
* remaining capacity along all three axes (wagons, weight, length) and each
|
* remaining capacity along all three axes (wagons, weight, length) and each
|
||||||
* booking's need, so staff can pick what fits.
|
* booking's need, so staff can pick what fits.
|
||||||
*/
|
*/
|
||||||
|
/**
|
||||||
|
* Every intercity booking and where it is in its ride-along, across all trains.
|
||||||
|
*
|
||||||
|
* The per-schedule candidate list answers "what can THIS train carry"; this
|
||||||
|
* answers "what is happening to intercity cargo" — which is what a yard
|
||||||
|
* operator needs when the work is spread over whichever trains happen to pass.
|
||||||
|
*
|
||||||
|
* Carries each end's facility state, because a booking whose origin or
|
||||||
|
* destination has no facility can never be loaded or unloaded there and the
|
||||||
|
* operator should see that before the train arrives, not when the load is
|
||||||
|
* refused.
|
||||||
|
*/
|
||||||
|
async listBookings() {
|
||||||
|
return this.dataSource.query(
|
||||||
|
`SELECT b.id AS "bookingId",
|
||||||
|
b.reference AS "reference",
|
||||||
|
b.status AS "status",
|
||||||
|
b.freight_type AS "freightType",
|
||||||
|
b.cargo_total_weight_vgm AS "weightTons",
|
||||||
|
b.loaded_at AS "loadedAt",
|
||||||
|
b.arrived_at AS "arrivedAt",
|
||||||
|
company.name AS "customer",
|
||||||
|
b.train_schedule_id AS "trainScheduleId",
|
||||||
|
ts.train_number AS "trainNumber",
|
||||||
|
ts.status AS "scheduleStatus",
|
||||||
|
oy.id AS "originYardId",
|
||||||
|
COALESCE(oy.label, oy.code) AS "origin",
|
||||||
|
oy.has_facility AS "originHasFacility",
|
||||||
|
dy.id AS "destinationYardId",
|
||||||
|
COALESCE(dy.label, dy.code) AS "destination",
|
||||||
|
dy.has_facility AS "destinationHasFacility",
|
||||||
|
-- Where the train actually is, so the operator knows if the cargo
|
||||||
|
-- can be worked right now.
|
||||||
|
cp.yard_id AS "trainAtYardId",
|
||||||
|
-- Most recent GRN raised for this booking at a facility.
|
||||||
|
fh.grn_number AS "grnNumber"
|
||||||
|
FROM freight.bookings b
|
||||||
|
LEFT JOIN freight.companies company ON company.id = b.company_id
|
||||||
|
LEFT JOIN freight.yards oy ON oy.id = b.origin_yard_id
|
||||||
|
LEFT JOIN freight.yards dy ON dy.id = b.destination_yard_id
|
||||||
|
LEFT JOIN freight.train_schedules ts
|
||||||
|
ON ts.id = b.train_schedule_id AND ts.deleted_at IS NULL
|
||||||
|
LEFT JOIN LATERAL (
|
||||||
|
SELECT c.yard_id
|
||||||
|
FROM freight.train_checkpoint_events c
|
||||||
|
WHERE c.train_schedule_id = b.train_schedule_id
|
||||||
|
ORDER BY c.occurred_at DESC, c.created_at DESC
|
||||||
|
LIMIT 1
|
||||||
|
) cp ON true
|
||||||
|
LEFT JOIN LATERAL (
|
||||||
|
SELECT e.grn_number
|
||||||
|
FROM freight.facility_handling_events e
|
||||||
|
WHERE e.booking_id = b.id AND e.deleted_at IS NULL
|
||||||
|
ORDER BY e.occurred_at DESC
|
||||||
|
LIMIT 1
|
||||||
|
) fh ON true
|
||||||
|
WHERE b.deleted_at IS NULL
|
||||||
|
AND b.trade_direction = 'DOMESTIC'
|
||||||
|
ORDER BY b.created_at DESC`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
async listCandidates(scheduleId: string) {
|
async listCandidates(scheduleId: string) {
|
||||||
const schedule = await this.getSchedule(scheduleId);
|
const schedule = await this.getSchedule(scheduleId);
|
||||||
const milestoneSeq = await this.routeMilestoneSequence(schedule);
|
const milestoneSeq = await this.routeMilestoneSequence(schedule);
|
||||||
|
|||||||
@@ -459,6 +459,16 @@ export class TrainSchedulingController {
|
|||||||
return this.trainSchedulingService.dispatchSchedule(id);
|
return this.trainSchedulingService.dispatchSchedule(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Get("intercity/bookings")
|
||||||
|
@TrainSchedulingView()
|
||||||
|
@ApiOperation({
|
||||||
|
summary:
|
||||||
|
"Every intercity booking with its ride-along state, both yards' facility status, and where its train is",
|
||||||
|
})
|
||||||
|
listIntercityBookings() {
|
||||||
|
return this.intercityService.listBookings();
|
||||||
|
}
|
||||||
|
|
||||||
@Get("schedules/:id/intercity-candidates")
|
@Get("schedules/:id/intercity-candidates")
|
||||||
@TrainSchedulingView()
|
@TrainSchedulingView()
|
||||||
@ApiOperation({
|
@ApiOperation({
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import {
|
|||||||
Users,
|
Users,
|
||||||
Wallet,
|
Wallet,
|
||||||
LifeBuoy,
|
LifeBuoy,
|
||||||
|
TrainFront,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import {
|
import {
|
||||||
@@ -122,6 +123,7 @@ import InterchangeDocumentsPage from "./pages/warehouses/InterchangeDocumentsPag
|
|||||||
import InventoryInquiryPage from "./pages/warehouses/InventoryInquiryPage";
|
import InventoryInquiryPage from "./pages/warehouses/InventoryInquiryPage";
|
||||||
import LoadedInventoryPage from "./pages/warehouses/LoadedInventoryPage";
|
import LoadedInventoryPage from "./pages/warehouses/LoadedInventoryPage";
|
||||||
import LoadingQueuePage from "./pages/warehouses/LoadingQueuePage";
|
import LoadingQueuePage from "./pages/warehouses/LoadingQueuePage";
|
||||||
|
import IntercityPage from "./pages/warehouses/IntercityPage";
|
||||||
import WarehouseDashboardPage from "./pages/warehouses/WarehouseDashboardPage";
|
import WarehouseDashboardPage from "./pages/warehouses/WarehouseDashboardPage";
|
||||||
import WarehouseDetailPage from "./pages/warehouses/WarehouseDetailPage";
|
import WarehouseDetailPage from "./pages/warehouses/WarehouseDetailPage";
|
||||||
import WarehouseInventoryPage from "./pages/warehouses/WarehouseInventoryPage";
|
import WarehouseInventoryPage from "./pages/warehouses/WarehouseInventoryPage";
|
||||||
@@ -441,6 +443,18 @@ const buildSidebarSections = (demoItems: SidebarItem[]): SidebarSection[] => [
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: "Intercity",
|
||||||
|
href: "/dashboard/intercity",
|
||||||
|
icon: <TrainFront />,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
label: "Intercity Cargo",
|
||||||
|
href: "/dashboard/intercity",
|
||||||
|
icon: <TrainFront />,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: "Warehouse Management",
|
label: "Warehouse Management",
|
||||||
icon: <Container />,
|
icon: <Container />,
|
||||||
@@ -933,6 +947,7 @@ const App = () => {
|
|||||||
<Route path="export-warehouse" element={<ExportWarehouseFlowPage />} />
|
<Route path="export-warehouse" element={<ExportWarehouseFlowPage />} />
|
||||||
<Route path="arrival-queue" element={<ArrivalQueuePage />} />
|
<Route path="arrival-queue" element={<ArrivalQueuePage />} />
|
||||||
<Route path="loading-queue" element={<LoadingQueuePage />} />
|
<Route path="loading-queue" element={<LoadingQueuePage />} />
|
||||||
|
<Route path="intercity" element={<IntercityPage />} />
|
||||||
<Route path="loaded-inventory" element={<LoadedInventoryPage />} />
|
<Route path="loaded-inventory" element={<LoadedInventoryPage />} />
|
||||||
<Route path="dispatch-queue" element={<DispatchQueuePage />} />
|
<Route path="dispatch-queue" element={<DispatchQueuePage />} />
|
||||||
<Route
|
<Route
|
||||||
|
|||||||
@@ -344,6 +344,7 @@ export const URL_CONSTANTS = {
|
|||||||
`/train-scheduling/schedules/${id}/bookings/${bookingId}/load`,
|
`/train-scheduling/schedules/${id}/bookings/${bookingId}/load`,
|
||||||
BOOKING_UNLOAD: (id: string, bookingId: string) =>
|
BOOKING_UNLOAD: (id: string, bookingId: string) =>
|
||||||
`/train-scheduling/schedules/${id}/bookings/${bookingId}/unload`,
|
`/train-scheduling/schedules/${id}/bookings/${bookingId}/unload`,
|
||||||
|
INTERCITY_BOOKINGS: "/train-scheduling/intercity/bookings",
|
||||||
INTERCITY_CANDIDATES: (id: string) =>
|
INTERCITY_CANDIDATES: (id: string) =>
|
||||||
`/train-scheduling/schedules/${id}/intercity-candidates`,
|
`/train-scheduling/schedules/${id}/intercity-candidates`,
|
||||||
INTERCITY_ACCEPT: (id: string) =>
|
INTERCITY_ACCEPT: (id: string) =>
|
||||||
|
|||||||
@@ -0,0 +1,291 @@
|
|||||||
|
import { useMemo, useState } from "react";
|
||||||
|
import {
|
||||||
|
Alert,
|
||||||
|
Badge,
|
||||||
|
Card,
|
||||||
|
Center,
|
||||||
|
Group,
|
||||||
|
Loader,
|
||||||
|
SimpleGrid,
|
||||||
|
Table,
|
||||||
|
Tabs,
|
||||||
|
Text,
|
||||||
|
Tooltip,
|
||||||
|
} from "@mantine/core";
|
||||||
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
import { AlertTriangle, PackageCheck, TrainFront, Warehouse } from "lucide-react";
|
||||||
|
|
||||||
|
import { PageContainer, PageHeader } from "@/components/page";
|
||||||
|
import { api } from "@/services/api";
|
||||||
|
import type { IntercityRideAlongRow } from "@/types/trainScheduling";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Intercity cargo across every train.
|
||||||
|
*
|
||||||
|
* Intercity bookings never get their own train — they ride whichever
|
||||||
|
* import/export train passes through their corridor — so the work is spread over
|
||||||
|
* other people's schedules. This is the one place it's all visible.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const fmtTons = (t: number | null) => (t == null ? "—" : `${t} t`);
|
||||||
|
|
||||||
|
/** A booking can only be worked where the train actually is. */
|
||||||
|
const atOrigin = (r: IntercityRideAlongRow) =>
|
||||||
|
Boolean(r.trainAtYardId) && r.trainAtYardId === r.originYardId;
|
||||||
|
const atDestination = (r: IntercityRideAlongRow) =>
|
||||||
|
Boolean(r.trainAtYardId) && r.trainAtYardId === r.destinationYardId;
|
||||||
|
|
||||||
|
const isWaiting = (r: IntercityRideAlongRow) =>
|
||||||
|
!r.loadedAt && r.status !== "IN_TRANSIT" && r.status !== "COMPLETED";
|
||||||
|
const isRiding = (r: IntercityRideAlongRow) => r.status === "IN_TRANSIT";
|
||||||
|
const isDone = (r: IntercityRideAlongRow) => r.status === "COMPLETED";
|
||||||
|
|
||||||
|
/** Yards with no equipment can never load/unload — surface it before the train arrives. */
|
||||||
|
function FacilityCell({ yard, has }: { yard: string | null; has: boolean | null }) {
|
||||||
|
if (!yard) return <Text size="sm">—</Text>;
|
||||||
|
if (has) return <Text size="sm">{yard}</Text>;
|
||||||
|
return (
|
||||||
|
<Tooltip
|
||||||
|
label="This yard has no load/unload facility — cargo cannot be handled here"
|
||||||
|
withArrow
|
||||||
|
multiline
|
||||||
|
w={240}
|
||||||
|
>
|
||||||
|
<Group gap={4} wrap="nowrap">
|
||||||
|
<AlertTriangle size={13} color="var(--mantine-color-red-6)" />
|
||||||
|
<Text size="sm" c="red">
|
||||||
|
{yard}
|
||||||
|
</Text>
|
||||||
|
</Group>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Rows({ rows }: { rows: IntercityRideAlongRow[] }) {
|
||||||
|
if (rows.length === 0) {
|
||||||
|
return (
|
||||||
|
<Alert variant="light" color="gray">
|
||||||
|
Nothing here.
|
||||||
|
</Alert>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Table.ScrollContainer minWidth={900}>
|
||||||
|
<Table striped highlightOnHover verticalSpacing="xs">
|
||||||
|
<Table.Thead>
|
||||||
|
<Table.Tr>
|
||||||
|
<Table.Th>Booking</Table.Th>
|
||||||
|
<Table.Th>Customer</Table.Th>
|
||||||
|
<Table.Th>Load at</Table.Th>
|
||||||
|
<Table.Th>Unload at</Table.Th>
|
||||||
|
<Table.Th>Train</Table.Th>
|
||||||
|
<Table.Th ta="right">Weight</Table.Th>
|
||||||
|
<Table.Th>GRN</Table.Th>
|
||||||
|
<Table.Th>Status</Table.Th>
|
||||||
|
</Table.Tr>
|
||||||
|
</Table.Thead>
|
||||||
|
<Table.Tbody>
|
||||||
|
{rows.map((r) => (
|
||||||
|
<Table.Tr key={r.bookingId}>
|
||||||
|
<Table.Td>
|
||||||
|
<Text fw={600} size="sm">
|
||||||
|
{r.reference ?? r.bookingId.slice(0, 8)}
|
||||||
|
</Text>
|
||||||
|
</Table.Td>
|
||||||
|
<Table.Td>{r.customer ?? "—"}</Table.Td>
|
||||||
|
<Table.Td>
|
||||||
|
<Group gap={6} wrap="nowrap">
|
||||||
|
<FacilityCell yard={r.origin} has={r.originHasFacility} />
|
||||||
|
{atOrigin(r) && isWaiting(r) && (
|
||||||
|
<Badge size="xs" color="edr-green" variant="light">
|
||||||
|
train here
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
|
</Group>
|
||||||
|
</Table.Td>
|
||||||
|
<Table.Td>
|
||||||
|
<Group gap={6} wrap="nowrap">
|
||||||
|
<FacilityCell yard={r.destination} has={r.destinationHasFacility} />
|
||||||
|
{atDestination(r) && isRiding(r) && (
|
||||||
|
<Badge size="xs" color="edr-green" variant="light">
|
||||||
|
train here
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
|
</Group>
|
||||||
|
</Table.Td>
|
||||||
|
<Table.Td>
|
||||||
|
{r.trainNumber ? (
|
||||||
|
<Group gap={4} wrap="nowrap">
|
||||||
|
<TrainFront size={13} />
|
||||||
|
<Text size="sm">{r.trainNumber}</Text>
|
||||||
|
</Group>
|
||||||
|
) : (
|
||||||
|
<Text size="sm" c="dimmed">
|
||||||
|
not on a train
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</Table.Td>
|
||||||
|
<Table.Td ta="right">
|
||||||
|
<Text size="sm">{fmtTons(r.weightTons)}</Text>
|
||||||
|
</Table.Td>
|
||||||
|
<Table.Td>
|
||||||
|
<Text size="sm" c={r.grnNumber ? undefined : "dimmed"}>
|
||||||
|
{r.grnNumber ?? "—"}
|
||||||
|
</Text>
|
||||||
|
</Table.Td>
|
||||||
|
<Table.Td>
|
||||||
|
<Badge size="sm" variant="light" color="gray">
|
||||||
|
{r.status}
|
||||||
|
</Badge>
|
||||||
|
</Table.Td>
|
||||||
|
</Table.Tr>
|
||||||
|
))}
|
||||||
|
</Table.Tbody>
|
||||||
|
</Table>
|
||||||
|
</Table.ScrollContainer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Stat({
|
||||||
|
icon,
|
||||||
|
label,
|
||||||
|
value,
|
||||||
|
color,
|
||||||
|
}: {
|
||||||
|
icon: React.ReactNode;
|
||||||
|
label: string;
|
||||||
|
value: React.ReactNode;
|
||||||
|
color?: string;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<Card withBorder radius="md" padding="md">
|
||||||
|
<Group gap="sm" wrap="nowrap">
|
||||||
|
{icon}
|
||||||
|
<div>
|
||||||
|
<Text size="xs" c="dimmed" tt="uppercase" fw={700}>
|
||||||
|
{label}
|
||||||
|
</Text>
|
||||||
|
<Text fw={800} fz={22} lh={1.1} c={color}>
|
||||||
|
{value}
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
</Group>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function IntercityPage() {
|
||||||
|
const [tab, setTab] = useState("waiting");
|
||||||
|
const { data: rows = [], isLoading } = useQuery(
|
||||||
|
api.trainScheduling.intercityBookings.queryOptions({ input: undefined }),
|
||||||
|
);
|
||||||
|
|
||||||
|
const waiting = useMemo(() => rows.filter(isWaiting), [rows]);
|
||||||
|
const riding = useMemo(() => rows.filter(isRiding), [rows]);
|
||||||
|
const done = useMemo(() => rows.filter(isDone), [rows]);
|
||||||
|
// A booking whose end has no equipment is stuck until someone flags the yard.
|
||||||
|
const blocked = useMemo(
|
||||||
|
() =>
|
||||||
|
rows.filter(
|
||||||
|
(r) => !isDone(r) && (!r.originHasFacility || !r.destinationHasFacility),
|
||||||
|
),
|
||||||
|
[rows],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PageContainer>
|
||||||
|
<PageHeader
|
||||||
|
title="Intercity"
|
||||||
|
subtitle="Domestic cargo riding passing trains — loaded at its origin facility, unloaded at its destination."
|
||||||
|
/>
|
||||||
|
|
||||||
|
{isLoading ? (
|
||||||
|
<Center py="xl">
|
||||||
|
<Loader />
|
||||||
|
</Center>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<SimpleGrid cols={{ base: 1, xs: 2, md: 4 }} spacing="sm" mb="md">
|
||||||
|
<Stat
|
||||||
|
icon={<PackageCheck size={18} />}
|
||||||
|
label="Waiting to load"
|
||||||
|
value={waiting.length}
|
||||||
|
/>
|
||||||
|
<Stat icon={<TrainFront size={18} />} label="On a train" value={riding.length} />
|
||||||
|
<Stat icon={<Warehouse size={18} />} label="Completed" value={done.length} />
|
||||||
|
<Stat
|
||||||
|
icon={<AlertTriangle size={18} />}
|
||||||
|
label="No facility"
|
||||||
|
value={blocked.length}
|
||||||
|
color={blocked.length > 0 ? "red" : undefined}
|
||||||
|
/>
|
||||||
|
</SimpleGrid>
|
||||||
|
|
||||||
|
{blocked.length > 0 && (
|
||||||
|
<Alert
|
||||||
|
variant="light"
|
||||||
|
color="red"
|
||||||
|
icon={<AlertTriangle size={16} />}
|
||||||
|
title={`${blocked.length} booking${blocked.length === 1 ? "" : "s"} cannot be handled`}
|
||||||
|
mb="md"
|
||||||
|
>
|
||||||
|
Their origin or destination yard has no load/unload facility. Mark the yard as
|
||||||
|
a facility in Configuration → Yards, or the cargo can never be worked there.
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Card withBorder radius="md" padding="md">
|
||||||
|
<Tabs value={tab} onChange={(v) => setTab(v ?? "waiting")}>
|
||||||
|
<Tabs.List mb="md">
|
||||||
|
<Tabs.Tab
|
||||||
|
value="waiting"
|
||||||
|
rightSection={
|
||||||
|
<Badge size="xs" variant="light">
|
||||||
|
{waiting.length}
|
||||||
|
</Badge>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Waiting to load
|
||||||
|
</Tabs.Tab>
|
||||||
|
<Tabs.Tab
|
||||||
|
value="riding"
|
||||||
|
rightSection={
|
||||||
|
<Badge size="xs" variant="light">
|
||||||
|
{riding.length}
|
||||||
|
</Badge>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
On a train
|
||||||
|
</Tabs.Tab>
|
||||||
|
<Tabs.Tab
|
||||||
|
value="done"
|
||||||
|
rightSection={
|
||||||
|
<Badge size="xs" variant="light">
|
||||||
|
{done.length}
|
||||||
|
</Badge>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Completed
|
||||||
|
</Tabs.Tab>
|
||||||
|
</Tabs.List>
|
||||||
|
|
||||||
|
<Tabs.Panel value="waiting">
|
||||||
|
<Rows rows={waiting} />
|
||||||
|
</Tabs.Panel>
|
||||||
|
<Tabs.Panel value="riding">
|
||||||
|
<Rows rows={riding} />
|
||||||
|
</Tabs.Panel>
|
||||||
|
<Tabs.Panel value="done">
|
||||||
|
<Rows rows={done} />
|
||||||
|
</Tabs.Panel>
|
||||||
|
</Tabs>
|
||||||
|
<Text size="xs" c="dimmed" mt="sm">
|
||||||
|
Loading and unloading happen on the train's schedule page, where the ride-along
|
||||||
|
panel confirms the train is at the yard.
|
||||||
|
</Text>
|
||||||
|
</Card>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</PageContainer>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -702,6 +702,16 @@ export const api = {
|
|||||||
() => TRAIN_SCHEDULING_INVALIDATIONS,
|
() => TRAIN_SCHEDULING_INVALIDATIONS,
|
||||||
),
|
),
|
||||||
|
|
||||||
|
intercityBookings: endpoint<
|
||||||
|
void,
|
||||||
|
import("@/types/trainScheduling").IntercityRideAlongRow[]
|
||||||
|
>(
|
||||||
|
"train-scheduling",
|
||||||
|
"intercity-bookings",
|
||||||
|
() => trainSchedulingService.listIntercityBookings(),
|
||||||
|
() => ["train-scheduling", "intercity-bookings"],
|
||||||
|
),
|
||||||
|
|
||||||
intercityCandidates: endpoint<
|
intercityCandidates: endpoint<
|
||||||
{ scheduleId: string },
|
{ scheduleId: string },
|
||||||
import("@/types/trainScheduling").IntercityCandidatesResult
|
import("@/types/trainScheduling").IntercityCandidatesResult
|
||||||
|
|||||||
@@ -393,6 +393,15 @@ export const trainSchedulingService = {
|
|||||||
return unwrap(response.data);
|
return unwrap(response.data);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
listIntercityBookings: async (): Promise<
|
||||||
|
import("@/types/trainScheduling").IntercityRideAlongRow[]
|
||||||
|
> => {
|
||||||
|
const response = await client.get<
|
||||||
|
import("@/types/trainScheduling").IntercityRideAlongRow[]
|
||||||
|
>(URL_CONSTANTS.TRAIN_SCHEDULING.INTERCITY_BOOKINGS);
|
||||||
|
return response.data ?? [];
|
||||||
|
},
|
||||||
|
|
||||||
getIntercityCandidates: async (
|
getIntercityCandidates: async (
|
||||||
scheduleId: string,
|
scheduleId: string,
|
||||||
): Promise<IntercityCandidatesResult> => {
|
): Promise<IntercityCandidatesResult> => {
|
||||||
|
|||||||
@@ -956,3 +956,27 @@ export interface BookingUnloadResult {
|
|||||||
status: string;
|
status: string;
|
||||||
arrivedAt: string;
|
arrivedAt: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** An intercity booking's ride-along state, with both ends' facility status. */
|
||||||
|
export interface IntercityRideAlongRow {
|
||||||
|
bookingId: string;
|
||||||
|
reference: string | null;
|
||||||
|
status: string;
|
||||||
|
freightType: string | null;
|
||||||
|
weightTons: number | null;
|
||||||
|
loadedAt: string | null;
|
||||||
|
arrivedAt: string | null;
|
||||||
|
customer: string | null;
|
||||||
|
trainScheduleId: string | null;
|
||||||
|
trainNumber: string | null;
|
||||||
|
scheduleStatus: string | null;
|
||||||
|
originYardId: string | null;
|
||||||
|
origin: string | null;
|
||||||
|
originHasFacility: boolean | null;
|
||||||
|
destinationYardId: string | null;
|
||||||
|
destination: string | null;
|
||||||
|
destinationHasFacility: boolean | null;
|
||||||
|
/** Yard the train was last recorded at — cargo can only be worked there. */
|
||||||
|
trainAtYardId: string | null;
|
||||||
|
grnNumber: string | null;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user