automation

This commit is contained in:
Hagernesh
2026-06-17 22:01:47 +00:00
parent 5b8cb16c1a
commit 5a6e1ef363
2 changed files with 202 additions and 0 deletions

View File

@@ -55,6 +55,7 @@ import WarehouseDashboardPage from "./pages/warehouses/WarehouseDashboardPage";
import LoadingQueuePage from "./pages/warehouses/LoadingQueuePage";
import LoadedInventoryPage from "./pages/warehouses/LoadedInventoryPage";
import DispatchQueuePage from "./pages/warehouses/DispatchQueuePage";
import ArrivalQueuePage from "./pages/warehouses/ArrivalQueuePage";
const buildSidebarSections = (demoItems: SidebarItem[]): SidebarSection[] => [
{
@@ -309,6 +310,7 @@ const App = () => {
<Route path="warehouses" element={<WarehouseListPage />} />
<Route path="warehouses/:id" element={<WarehouseDetailPage />} />
<Route path="warehouse-inventory" element={<WarehouseInventoryPage />} />
<Route path="arrival-queue" element={<ArrivalQueuePage />} />
<Route path="loading-queue" element={<LoadingQueuePage />} />
<Route path="loaded-inventory" element={<LoadedInventoryPage />} />
<Route path="dispatch-queue" element={<DispatchQueuePage />} />

View File

@@ -0,0 +1,200 @@
import { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import {
Badge,
Button,
Card,
Container,
Group,
Loader,
Stack,
Table,
Text,
} from '@mantine/core';
import { ClipboardList, Eye, PackageOpen, Truck } from 'lucide-react';
import Breadcrumbs from '@/components/ui/Breadcrumbs';
import {
InspectionReportModal,
VisualEmptyState,
WarehouseHero,
formatDate,
} from '@/components/warehouses';
import { useArrivalQueue, useAutoUnloadArrived, useUnloadBooking } from '@/hooks/useWarehouses';
import { useToast } from '@/hooks/use-toast';
import type { ArrivalQueueItem } from '@/types/warehouse';
function inspectionBadge(status: string | null) {
if (!status) return <Badge variant="light" color="gray" size="sm">Not inspected</Badge>;
const color = status === 'PASSED' ? 'green' : status === 'FAILED' ? 'red' : 'orange';
return <Badge variant="light" color={color} size="sm">{status.replace(/_/g, ' ')}</Badge>;
}
/** Batch 4.5 — arrived bookings awaiting unload / inspection. */
export default function ArrivalQueuePage() {
const navigate = useNavigate();
const { toast } = useToast();
const { data, isLoading } = useArrivalQueue();
const autoUnload = useAutoUnloadArrived();
const unloadOne = useUnloadBooking();
const [inspectInventoryId, setInspectInventoryId] = useState<string | null>(null);
const items = data ?? [];
const handleAutoUnload = async () => {
try {
const res = await autoUnload.mutateAsync();
const r = res.data;
toast({
title: 'Auto-unload complete',
description: `Processed ${r.processedCount}, skipped ${r.skippedCount}, failed ${r.failedCount}.`,
});
} catch {
toast({ variant: 'destructive', title: 'Auto-unload failed' });
}
};
const handleUnloadOne = async (item: ArrivalQueueItem) => {
try {
await unloadOne.mutateAsync({ bookingId: item.bookingId });
toast({ title: 'Booking unloaded', description: `${item.bookingReference} stored as RECEIVED.` });
} catch {
toast({ variant: 'destructive', title: 'Unload failed' });
}
};
return (
<Container size="xxl" py="lg">
<Breadcrumbs items={[{ label: 'Arrival queue' }]} />
<Stack gap="lg" mt="sm">
<WarehouseHero
variant="container"
secondaryVariant="warehouse"
title="Arrival / Unloading Queue"
subtitle="Arrived bookings ready to unload, store and inspect."
/>
<Card withBorder radius="md" padding="lg">
<Group justify="space-between" mb="md">
<Text fw={600}>{items.length} arrived booking(s)</Text>
<Button
color="orange"
leftSection={<PackageOpen size={16} />}
loading={autoUnload.isPending}
onClick={handleAutoUnload}
>
Auto Unload Arrived Bookings
</Button>
</Group>
{isLoading ? (
<Group justify="center" py="xl">
<Loader />
</Group>
) : items.length === 0 ? (
<VisualEmptyState
variant="container"
title="No arrived bookings"
description="Bookings in transit that arrive appear here for unloading and inspection."
/>
) : (
<Table.ScrollContainer minWidth={1100}>
<Table verticalSpacing="sm" highlightOnHover striped>
<Table.Thead>
<Table.Tr>
<Table.Th>Booking</Table.Th>
<Table.Th>Customer</Table.Th>
<Table.Th>Cargo / Container</Table.Th>
<Table.Th>Arrival</Table.Th>
<Table.Th>Facility</Table.Th>
<Table.Th>Warehouse</Table.Th>
<Table.Th>Yard</Table.Th>
<Table.Th>Zone</Table.Th>
<Table.Th>Status</Table.Th>
<Table.Th>Inspection</Table.Th>
<Table.Th ta="right">Actions</Table.Th>
</Table.Tr>
</Table.Thead>
<Table.Tbody>
{items.map((item) => (
<Table.Tr key={item.bookingId}>
<Table.Td>
<Text fw={600} size="sm">{item.bookingReference}</Text>
</Table.Td>
<Table.Td>{item.customer ?? '—'}</Table.Td>
<Table.Td>{item.container ?? item.cargo ?? '—'}</Table.Td>
<Table.Td>
<Text size="xs">{formatDate(item.arrivalDate)}</Text>
</Table.Td>
<Table.Td>{item.facility ?? '—'}</Table.Td>
<Table.Td>{item.warehouse ?? '—'}</Table.Td>
<Table.Td>{item.yard ?? '—'}</Table.Td>
<Table.Td>{item.zone ?? '—'}</Table.Td>
<Table.Td>
{item.unloaded ? (
<Badge variant="light" color="green" size="sm">
{item.currentStatus ?? 'RECEIVED'}
</Badge>
) : (
<Badge variant="light" color="orange" size="sm">
Not unloaded
</Badge>
)}
</Table.Td>
<Table.Td>{inspectionBadge(item.inspectionStatus)}</Table.Td>
<Table.Td>
<Group gap="xs" justify="flex-end" wrap="nowrap">
{!item.unloaded && (
<Button
size="compact-xs"
variant="light"
color="orange"
leftSection={<Truck size={14} />}
loading={unloadOne.isPending}
onClick={() => handleUnloadOne(item)}
>
Unload
</Button>
)}
{item.inventoryId && (
<Button
size="compact-xs"
variant="light"
color="green"
leftSection={<ClipboardList size={14} />}
onClick={() => setInspectInventoryId(item.inventoryId)}
>
Inspect
</Button>
)}
{item.inventoryId && (
<Button
size="compact-xs"
variant="subtle"
color="gray"
leftSection={<Eye size={14} />}
onClick={() => navigate('/dashboard/warehouse-inventory')}
>
Inventory
</Button>
)}
</Group>
</Table.Td>
</Table.Tr>
))}
</Table.Tbody>
</Table>
</Table.ScrollContainer>
)}
</Card>
</Stack>
<InspectionReportModal
opened={Boolean(inspectInventoryId)}
onClose={() => setInspectInventoryId(null)}
inventoryId={inspectInventoryId}
/>
</Container>
);
}