mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-30 07:38:10 +00:00
Marshaling document Receive Export and import handover to customer
This commit is contained in:
@@ -313,9 +313,15 @@ export default function TrainScheduleV2DetailPage() {
|
||||
const canDispatch = schedule.status === "SCHEDULED";
|
||||
const finalizeStep = hasContainerStep ? 3 : 2;
|
||||
const canModifyBookings = canEditBookings && !["DISPATCHED", "ARRIVED"].includes(schedule.status);
|
||||
const canPrintMarshalling = schedule.direction === "IMPORT" || schedule.direction === "EXPORT";
|
||||
const canPrintMarshalling =
|
||||
(schedule.direction === "IMPORT" || schedule.direction === "EXPORT") &&
|
||||
["DISPATCHED", "ARRIVED"].includes(schedule.status);
|
||||
|
||||
const openMarshallingDocument = async () => {
|
||||
const openMarshallingDocument = async (options?: {
|
||||
title?: string;
|
||||
successDescription?: string;
|
||||
errorTitle?: string;
|
||||
}) => {
|
||||
const pdfWindow = window.open("", "_blank");
|
||||
try {
|
||||
const blob = await downloadMarshalling.mutateAsync({
|
||||
@@ -326,15 +332,17 @@ export default function TrainScheduleV2DetailPage() {
|
||||
const filename = `${prefix}-${schedule.trainNumber ?? scheduleId}.pdf`;
|
||||
const opened = openPdfBlob(blob, filename, pdfWindow);
|
||||
toast({
|
||||
title: "Marshalling document ready",
|
||||
description: opened
|
||||
? "The PDF opened in a browser tab for printing or saving."
|
||||
: "The browser blocked the preview tab, so the PDF was downloaded.",
|
||||
title: options?.title ?? "Marshalling document ready",
|
||||
description:
|
||||
options?.successDescription ??
|
||||
(opened
|
||||
? "The PDF opened in a browser tab for printing or saving."
|
||||
: "The browser blocked the preview tab, so the PDF was downloaded."),
|
||||
});
|
||||
} catch (error) {
|
||||
pdfWindow?.close();
|
||||
toast({
|
||||
title: "Could not open marshalling document",
|
||||
title: options?.errorTitle ?? "Could not open marshalling document",
|
||||
description: parseError(error, "Make sure the train has wagon allocations, then try again."),
|
||||
variant: "destructive",
|
||||
});
|
||||
@@ -725,7 +733,11 @@ export default function TrainScheduleV2DetailPage() {
|
||||
onClick={async () => {
|
||||
try {
|
||||
await dispatch.mutateAsync(scheduleId);
|
||||
toast({ title: "Train dispatched" });
|
||||
await openMarshallingDocument({
|
||||
title: "Train dispatched",
|
||||
successDescription: "Marshalling document generated for the dispatched train.",
|
||||
errorTitle: "Train dispatched, but document could not open",
|
||||
});
|
||||
} catch (err) {
|
||||
toast({
|
||||
title: "Dispatch failed",
|
||||
|
||||
@@ -231,8 +231,8 @@ export default function ArrivalQueuePage() {
|
||||
<Table.Td ta="center">{train.totalCargoes}</Table.Td>
|
||||
<Table.Td>
|
||||
<Stack gap={2}>
|
||||
<Badge variant="light" color={fullyUnloaded ? 'green' : 'teal'} size="sm">
|
||||
{fullyUnloaded ? 'UNLOADED' : train.status}
|
||||
<Badge variant="light" color="teal" size="sm">
|
||||
{train.status}
|
||||
</Badge>
|
||||
<Text size="xs" c="dimmed">
|
||||
{Math.max(unloadedBookings, 0)}/{train.totalBookings} unloaded
|
||||
|
||||
@@ -66,14 +66,14 @@ const statusLabel = (status?: string | null) =>
|
||||
: (status ?? 'PENDING').replace(/_/g, ' ');
|
||||
|
||||
function ExportTrainDetailRows({
|
||||
scheduleId,
|
||||
train,
|
||||
onOpenHistory,
|
||||
}: {
|
||||
scheduleId: string;
|
||||
train: ExportTrain;
|
||||
onOpenHistory: (inventoryId: string) => void;
|
||||
}) {
|
||||
const navigate = useNavigate();
|
||||
const { data: items = [], isLoading } = useExportDjiboutiTrainItems(scheduleId);
|
||||
const { data: items = [], isLoading } = useExportDjiboutiTrainItems(train.scheduleId);
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
@@ -92,10 +92,11 @@ function ExportTrainDetailRows({
|
||||
}
|
||||
|
||||
return (
|
||||
<Table.ScrollContainer minWidth={1320}>
|
||||
<Table.ScrollContainer minWidth={1420}>
|
||||
<Table highlightOnHover verticalSpacing="xs">
|
||||
<Table.Thead>
|
||||
<Table.Tr>
|
||||
<Table.Th>Wagon</Table.Th>
|
||||
<Table.Th>Booking ID</Table.Th>
|
||||
<Table.Th>Booking Reference</Table.Th>
|
||||
<Table.Th>Customer ID</Table.Th>
|
||||
@@ -115,6 +116,11 @@ function ExportTrainDetailRows({
|
||||
<Table.Tbody>
|
||||
{items.map((item: ExportTrainItem) => (
|
||||
<Table.Tr key={`${item.bookingId}-${item.itemType}-${item.itemId ?? item.inventoryId ?? 'item'}`}>
|
||||
<Table.Td>
|
||||
<Text size="xs" fw={600}>
|
||||
{item.sequenceNo ? `#${item.sequenceNo}` : '-'} {item.wagonNumber ?? ''}
|
||||
</Text>
|
||||
</Table.Td>
|
||||
<Table.Td>{item.bookingId.slice(0, 8)}</Table.Td>
|
||||
<Table.Td>
|
||||
<Text size="sm" fw={600}>
|
||||
@@ -384,7 +390,7 @@ export default function ExportDjiboutiUnloadingQueuePage() {
|
||||
<Table.Tr>
|
||||
<Table.Td colSpan={12} bg="var(--mantine-color-gray-0)">
|
||||
<ExportTrainDetailRows
|
||||
scheduleId={train.scheduleId}
|
||||
train={train}
|
||||
onOpenHistory={setHistoryInventoryId}
|
||||
/>
|
||||
</Table.Td>
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import { Button, Card } from '@mantine/core';
|
||||
import { PackageSearch } from 'lucide-react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { PageContainer, PageHeader } from '@/components/page';
|
||||
import { WarehouseFlowWorkbench } from '@/components/warehouses';
|
||||
|
||||
export default function ExportWarehouseFlowPage() {
|
||||
const navigate = useNavigate();
|
||||
|
||||
return (
|
||||
<PageContainer>
|
||||
<PageHeader
|
||||
title="Export Operations"
|
||||
subtitle="Manage export receive, terminal inventory, loading readiness, loaded items, and dispatch flow."
|
||||
action={
|
||||
<Button variant="light" leftSection={<PackageSearch size={16} />} onClick={() => navigate('/dashboard/import-warehouse')}>
|
||||
Import Operations
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
|
||||
<Card>
|
||||
<WarehouseFlowWorkbench direction="EXPORT" />
|
||||
</Card>
|
||||
</PageContainer>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import { Button, Card } from '@mantine/core';
|
||||
import { Truck } from 'lucide-react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { PageContainer, PageHeader } from '@/components/page';
|
||||
import { WarehouseFlowWorkbench } from '@/components/warehouses';
|
||||
|
||||
export default function ImportWarehouseFlowPage() {
|
||||
const navigate = useNavigate();
|
||||
|
||||
return (
|
||||
<PageContainer>
|
||||
<PageHeader
|
||||
title="Import Operations"
|
||||
subtitle="Manage import gate flow, marshalling handoff, arrived trains, unloaded bookings, and dispatch-ready inventory."
|
||||
action={
|
||||
<Button variant="light" leftSection={<Truck size={16} />} onClick={() => navigate('/dashboard/export-warehouse')}>
|
||||
Export Operations
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
|
||||
<Card>
|
||||
<WarehouseFlowWorkbench direction="IMPORT" />
|
||||
</Card>
|
||||
</PageContainer>
|
||||
);
|
||||
}
|
||||
@@ -1,13 +1,12 @@
|
||||
import { useMemo, useState } from 'react';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
import { useNavigate, useSearchParams } from 'react-router-dom';
|
||||
import { Button, Card, Group, Select, Stack, TextInput } from '@mantine/core';
|
||||
import { useDebouncedValue } from '@mantine/hooks';
|
||||
import { PackagePlus, Search } from 'lucide-react';
|
||||
import { PackageOpen, Search, Truck } from 'lucide-react';
|
||||
|
||||
import { PageContainer, PageHeader } from '@/components/page';
|
||||
import {
|
||||
InventoryWorkbench,
|
||||
ReceiveInventoryModal,
|
||||
inventoryStatusOptions,
|
||||
} from '@/components/warehouses';
|
||||
import {
|
||||
@@ -19,13 +18,13 @@ import {
|
||||
import type { InventoryFilter, InventoryStatus } from '@/types/warehouse';
|
||||
|
||||
export default function WarehouseInventoryPage() {
|
||||
const navigate = useNavigate();
|
||||
const [searchParams] = useSearchParams();
|
||||
const initialStatus = (searchParams.get('status') as InventoryStatus | null) ?? undefined;
|
||||
const [filter, setFilter] = useState<InventoryFilter>(
|
||||
initialStatus ? { status: initialStatus } : {},
|
||||
);
|
||||
const [search, setSearch] = useState('');
|
||||
const [modalOpen, setModalOpen] = useState(false);
|
||||
|
||||
const [debouncedSearch] = useDebouncedValue(search, 300);
|
||||
const queryFilter = useMemo<InventoryFilter>(
|
||||
@@ -57,9 +56,18 @@ export default function WarehouseInventoryPage() {
|
||||
title="Warehouse Inventory"
|
||||
subtitle="Track received items through the storage, reservation, loading and dispatch lifecycle."
|
||||
action={
|
||||
<Button leftSection={<PackagePlus size={16} />} onClick={() => setModalOpen(true)}>
|
||||
Receive Inventory
|
||||
</Button>
|
||||
<Group gap="xs">
|
||||
<Button
|
||||
variant="light"
|
||||
leftSection={<PackageOpen size={16} />}
|
||||
onClick={() => navigate('/dashboard/import-warehouse')}
|
||||
>
|
||||
Import
|
||||
</Button>
|
||||
<Button leftSection={<Truck size={16} />} onClick={() => navigate('/dashboard/export-warehouse')}>
|
||||
Export
|
||||
</Button>
|
||||
</Group>
|
||||
}
|
||||
/>
|
||||
|
||||
@@ -126,8 +134,6 @@ export default function WarehouseInventoryPage() {
|
||||
<InventoryWorkbench items={inventoryQuery.data ?? []} isLoading={inventoryQuery.isLoading} />
|
||||
</Stack>
|
||||
</Card>
|
||||
|
||||
<ReceiveInventoryModal opened={modalOpen} onClose={() => setModalOpen(false)} />
|
||||
</PageContainer>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user