mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-08 22:58:17 +00:00
merge conflict fix
This commit is contained in:
@@ -9,7 +9,9 @@ import {
|
||||
Warehouse,
|
||||
} from 'lucide-react';
|
||||
|
||||
import { useInventoryActivity } from '@/hooks/useWarehouses';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
|
||||
import { api } from '@/services/api';
|
||||
import type { ActivityType } from '@/types/warehouse';
|
||||
import { formatDate, humanizeEnum } from './options';
|
||||
|
||||
@@ -24,7 +26,12 @@ const activityIcon: Record<ActivityType, React.ReactNode> = {
|
||||
};
|
||||
|
||||
export function ActivityTimeline({ inventoryId }: { inventoryId: string }) {
|
||||
const { data, isLoading } = useInventoryActivity(inventoryId);
|
||||
const { data, isLoading } = useQuery(
|
||||
api.warehouses.activity.queryOptions({
|
||||
input: { id: inventoryId },
|
||||
enabled: Boolean(inventoryId),
|
||||
}),
|
||||
);
|
||||
const items = data ?? [];
|
||||
|
||||
if (isLoading) {
|
||||
|
||||
@@ -9,9 +9,10 @@ import {
|
||||
TextInput,
|
||||
} from '@mantine/core';
|
||||
|
||||
import { useMutation, useQuery } from '@tanstack/react-query';
|
||||
|
||||
import { api } from '@/services/api';
|
||||
import { useToast } from '@/hooks/use-toast';
|
||||
import { useStations } from '@/hooks/useStations';
|
||||
import { useCreateWarehouse, useUpdateWarehouse } from '@/hooks/useWarehouses';
|
||||
import type { SaveWarehousePayload, Warehouse, WarehouseType } from '@/types/warehouse';
|
||||
import { extractErrorMessage, statusOptions, warehouseTypeOptions } from './options';
|
||||
|
||||
@@ -48,9 +49,11 @@ const emptyForm = (): FormState => ({
|
||||
export function CreateWarehouseModal({ opened, onClose, warehouse }: CreateWarehouseModalProps) {
|
||||
const isEdit = Boolean(warehouse);
|
||||
const { toast } = useToast();
|
||||
const createMutation = useCreateWarehouse();
|
||||
const updateMutation = useUpdateWarehouse();
|
||||
const { data: stations } = useStations();
|
||||
const createMutation = useMutation(api.warehouses.create.mutationOptions());
|
||||
const updateMutation = useMutation(api.warehouses.update.mutationOptions());
|
||||
const { data: stations } = useQuery(
|
||||
api.stations.list.queryOptions({ staleTime: 5 * 60 * 1000 }),
|
||||
);
|
||||
const [form, setForm] = useState<FormState>(emptyForm());
|
||||
|
||||
const stationOptions = (stations ?? []).map((s) => ({ value: s.id, label: `${s.name} (${s.code})` }));
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Button, Group, Modal, NumberInput, Select, Stack, TextInput } from '@mantine/core';
|
||||
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
|
||||
import { api } from '@/services/api';
|
||||
import { useToast } from '@/hooks/use-toast';
|
||||
import { useCreateYard, useUpdateYard } from '@/hooks/useWarehouses';
|
||||
import type { SaveYardPayload, WarehouseYard, WarehouseYardType } from '@/types/warehouse';
|
||||
import { extractErrorMessage, statusOptions, yardTypeOptions } from './options';
|
||||
|
||||
@@ -36,8 +38,8 @@ const emptyForm = (): FormState => ({
|
||||
export function CreateYardModal({ opened, onClose, warehouseId, yard }: CreateYardModalProps) {
|
||||
const isEdit = Boolean(yard);
|
||||
const { toast } = useToast();
|
||||
const createMutation = useCreateYard();
|
||||
const updateMutation = useUpdateYard();
|
||||
const createMutation = useMutation(api.warehouses.createYard.mutationOptions());
|
||||
const updateMutation = useMutation(api.warehouses.updateYard.mutationOptions());
|
||||
const [form, setForm] = useState<FormState>(emptyForm());
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Button, Group, Modal, NumberInput, Select, Stack, TextInput } from '@mantine/core';
|
||||
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
|
||||
import { api } from '@/services/api';
|
||||
import { useToast } from '@/hooks/use-toast';
|
||||
import { useCreateZone, useUpdateZone } from '@/hooks/useWarehouses';
|
||||
import type { SaveZonePayload, WarehouseZone, WarehouseZoneType } from '@/types/warehouse';
|
||||
import { extractErrorMessage, statusOptions, zoneTypeOptions } from './options';
|
||||
|
||||
@@ -36,8 +38,8 @@ const emptyForm = (): FormState => ({
|
||||
export function CreateZoneModal({ opened, onClose, yardId, zone }: CreateZoneModalProps) {
|
||||
const isEdit = Boolean(zone);
|
||||
const { toast } = useToast();
|
||||
const createMutation = useCreateZone();
|
||||
const updateMutation = useUpdateZone();
|
||||
const createMutation = useMutation(api.warehouses.createZone.mutationOptions());
|
||||
const updateMutation = useMutation(api.warehouses.updateZone.mutationOptions());
|
||||
const [form, setForm] = useState<FormState>(emptyForm());
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -2,8 +2,10 @@ import { useEffect, useState } from 'react';
|
||||
import { Alert, Button, Group, Modal, Stack, Text, Textarea, TextInput } from '@mantine/core';
|
||||
import { Info } from 'lucide-react';
|
||||
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
|
||||
import { api } from '@/services/api';
|
||||
import { useToast } from '@/hooks/use-toast';
|
||||
import { useDeliverInventory } from '@/hooks/useWarehouses';
|
||||
import type { WarehouseInventoryItem } from '@/types/warehouse';
|
||||
import { extractErrorMessage } from './options';
|
||||
|
||||
@@ -15,7 +17,7 @@ interface DeliverInventoryModalProps {
|
||||
|
||||
export function DeliverInventoryModal({ opened, onClose, item }: DeliverInventoryModalProps) {
|
||||
const { toast } = useToast();
|
||||
const deliverMutation = useDeliverInventory();
|
||||
const deliverMutation = useMutation(api.warehouses.deliver.mutationOptions());
|
||||
const [receiverName, setReceiverName] = useState('');
|
||||
const [remarks, setRemarks] = useState('');
|
||||
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { Badge, Button, Card, Divider, Group, Loader, Modal, Stack, Text } from '@mantine/core';
|
||||
import { CalendarClock, Coins, DoorOpen, FileText } from 'lucide-react';
|
||||
|
||||
import { useMutation, useQuery } from '@tanstack/react-query';
|
||||
|
||||
import { api } from '@/services/api';
|
||||
import { useToast } from '@/hooks/use-toast';
|
||||
import {
|
||||
useFeePreview,
|
||||
@@ -17,7 +20,7 @@ const INVOICE_STATUS_COLOR: Record<WarehouseInvoiceStatus, string> = {
|
||||
DRAFT: 'gray',
|
||||
ISSUED: 'orange',
|
||||
PARTIALLY_PAID: 'yellow',
|
||||
PAID: 'green',
|
||||
PAID: 'edr-green',
|
||||
CANCELLED: 'gray',
|
||||
};
|
||||
|
||||
@@ -93,10 +96,20 @@ function Row({ label, value }: { label: string; value: string }) {
|
||||
export function FeePreviewModal({ opened, onClose, inventoryId }: FeePreviewModalProps) {
|
||||
const { toast } = useToast();
|
||||
const enabledId = opened ? inventoryId ?? undefined : undefined;
|
||||
const { data, isLoading } = useFeePreview(enabledId);
|
||||
const { data: invoices } = useInvoicesForInventory(enabledId);
|
||||
const generate = useGenerateInvoice();
|
||||
const gateClear = useGateClearance();
|
||||
const { data, isLoading } = useQuery(
|
||||
api.warehouses.feePreview.queryOptions({
|
||||
input: { inventoryId: enabledId ?? '' },
|
||||
enabled: Boolean(enabledId),
|
||||
}),
|
||||
);
|
||||
const { data: invoices } = useQuery(
|
||||
api.warehouses.invoicesForInventory.queryOptions({
|
||||
input: { inventoryId: enabledId ?? '' },
|
||||
enabled: Boolean(enabledId),
|
||||
}),
|
||||
);
|
||||
const generate = useMutation(api.warehouses.generateInvoice.mutationOptions());
|
||||
const gateClear = useMutation(api.warehouses.gateClearance.mutationOptions());
|
||||
|
||||
const activeInvoice = (invoices ?? []).find((i) => i.status !== 'CANCELLED');
|
||||
|
||||
@@ -191,7 +204,7 @@ export function FeePreviewModal({ opened, onClose, inventoryId }: FeePreviewModa
|
||||
|
||||
<Button
|
||||
variant="light"
|
||||
color="green"
|
||||
color="edr-green"
|
||||
leftSection={<DoorOpen size={16} />}
|
||||
loading={gateClear.isPending}
|
||||
onClick={handleGateClearance}
|
||||
|
||||
@@ -12,6 +12,9 @@ import {
|
||||
} from '@mantine/core';
|
||||
import { Upload } from 'lucide-react';
|
||||
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
|
||||
import { api } from '@/services/api';
|
||||
import { useToast } from '@/hooks/use-toast';
|
||||
import { useCreateInspectionReport, useInspectionReports, useUploadInspectionAttachments } from '@/hooks/useWarehouses';
|
||||
import {
|
||||
@@ -227,7 +230,7 @@ export function InspectionReportModal({ opened, onClose, inventoryId }: Inspecti
|
||||
<Button variant="default" onClick={onClose} disabled={submitting}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button color="green" onClick={handleSubmit} loading={submitting} disabled={!inventoryId}>
|
||||
<Button color="edr-green" onClick={handleSubmit} loading={submitting} disabled={!inventoryId}>
|
||||
Save report
|
||||
</Button>
|
||||
</Group>
|
||||
|
||||
@@ -1,12 +1,19 @@
|
||||
import { Center, Loader, Table, Text } from '@mantine/core';
|
||||
|
||||
import { useInventoryMovements } from '@/hooks/useWarehouses';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
|
||||
import { api } from '@/services/api';
|
||||
import { formatDate } from './options';
|
||||
|
||||
const shortId = (id?: string | null) => (id ? `${id.slice(0, 8)}…` : '—');
|
||||
|
||||
export function InventoryMovementHistoryTable({ inventoryId }: { inventoryId: string }) {
|
||||
const { data, isLoading } = useInventoryMovements(inventoryId);
|
||||
const { data, isLoading } = useQuery(
|
||||
api.warehouses.movements.queryOptions({
|
||||
input: { id: inventoryId },
|
||||
enabled: Boolean(inventoryId),
|
||||
}),
|
||||
);
|
||||
const movements = data ?? [];
|
||||
|
||||
if (isLoading) {
|
||||
|
||||
@@ -2,6 +2,9 @@ import { useState } from 'react';
|
||||
import { Button, Center, Group, Loader, Stack, Text } from '@mantine/core';
|
||||
import { ClipboardCheck } from 'lucide-react';
|
||||
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
|
||||
import { api } from '@/services/api';
|
||||
import { useToast } from '@/hooks/use-toast';
|
||||
import {
|
||||
useBulkMarkInspected,
|
||||
@@ -46,11 +49,17 @@ export function InventoryWorkbench({ items, isLoading, onLastMile }: InventoryWo
|
||||
const [releaseItem, setReleaseItem] = useState<WarehouseInventoryItem | null>(null);
|
||||
const [deliverItem, setDeliverItem] = useState<WarehouseInventoryItem | null>(null);
|
||||
|
||||
const storeMutation = useStoreInventory();
|
||||
const readyMutation = useMarkReadyForLoading();
|
||||
const pickupMutation = useMarkReadyForPickup();
|
||||
const dispatchMutation = useDispatchInventory();
|
||||
const inspectMutation = useBulkMarkInspected();
|
||||
const storeMutation = useMutation(api.warehouses.store.mutationOptions());
|
||||
const readyMutation = useMutation(
|
||||
api.warehouses.markReadyForLoading.mutationOptions(),
|
||||
);
|
||||
const pickupMutation = useMutation(
|
||||
api.warehouses.markReadyForPickup.mutationOptions(),
|
||||
);
|
||||
const dispatchMutation = useMutation(api.warehouses.dispatch.mutationOptions());
|
||||
const inspectMutation = useMutation(
|
||||
api.warehouses.bulkMarkInspected.mutationOptions(),
|
||||
);
|
||||
|
||||
const [selected, setSelected] = useState<Set<string>>(new Set());
|
||||
const allSelected = items.length > 0 && selected.size === items.length;
|
||||
@@ -70,10 +79,7 @@ export function InventoryWorkbench({ items, isLoading, onLastMile }: InventoryWo
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const res = (await inspectMutation.mutateAsync({ inventoryIds: [...selected] })) as {
|
||||
data: { inspectedCount: number; skippedCount: number };
|
||||
};
|
||||
const r = res.data;
|
||||
const r = await inspectMutation.mutateAsync({ inventoryIds: [...selected] });
|
||||
toast({
|
||||
title: `${r.inspectedCount} marked inspected`,
|
||||
description: r.skippedCount ? `${r.skippedCount} skipped` : undefined,
|
||||
|
||||
@@ -2,8 +2,10 @@ import { useEffect, useState } from 'react';
|
||||
import { Alert, Button, Group, Modal, NumberInput, Stack, Text, Textarea } from '@mantine/core';
|
||||
import { Info } from 'lucide-react';
|
||||
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
|
||||
import { api } from '@/services/api';
|
||||
import { useToast } from '@/hooks/use-toast';
|
||||
import { useLoadInventory } from '@/hooks/useWarehouses';
|
||||
import type { WarehouseInventoryItem } from '@/types/warehouse';
|
||||
import { WagonSelect } from './WagonSelect';
|
||||
import { extractErrorMessage } from './options';
|
||||
@@ -17,7 +19,7 @@ interface LoadInventoryModalProps {
|
||||
/** Load READY_FOR_LOADING inventory onto a wagon (creates a loading record). */
|
||||
export function LoadInventoryModal({ opened, onClose, item }: LoadInventoryModalProps) {
|
||||
const { toast } = useToast();
|
||||
const loadMutation = useLoadInventory();
|
||||
const loadMutation = useMutation(api.warehouses.load.mutationOptions());
|
||||
const [wagonId, setWagonId] = useState('');
|
||||
const [loadedWeight, setLoadedWeight] = useState<number | ''>('');
|
||||
const [notes, setNotes] = useState('');
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { Button, Group, Modal, Select, Stack, Textarea } from '@mantine/core';
|
||||
|
||||
import { useMutation, useQuery } from '@tanstack/react-query';
|
||||
|
||||
import { api } from '@/services/api';
|
||||
import { useToast } from '@/hooks/use-toast';
|
||||
import { useMoveInventory, useWarehouseYards, useWarehouseZones, useWarehouses } from '@/hooks/useWarehouses';
|
||||
import type { WarehouseInventoryItem } from '@/types/warehouse';
|
||||
import { extractErrorMessage } from './options';
|
||||
|
||||
@@ -14,7 +16,7 @@ interface MoveInventoryModalProps {
|
||||
|
||||
export function MoveInventoryModal({ opened, onClose, item }: MoveInventoryModalProps) {
|
||||
const { toast } = useToast();
|
||||
const moveMutation = useMoveInventory();
|
||||
const moveMutation = useMutation(api.warehouses.move.mutationOptions());
|
||||
const [warehouseId, setWarehouseId] = useState('');
|
||||
const [yardId, setYardId] = useState('');
|
||||
const [zoneId, setZoneId] = useState('');
|
||||
@@ -29,9 +31,21 @@ export function MoveInventoryModal({ opened, onClose, item }: MoveInventoryModal
|
||||
}
|
||||
}, [opened]);
|
||||
|
||||
const warehousesQuery = useWarehouses({ status: 'ACTIVE' });
|
||||
const yardsQuery = useWarehouseYards(warehouseId || undefined);
|
||||
const zonesQuery = useWarehouseZones(yardId || undefined);
|
||||
const warehousesQuery = useQuery(
|
||||
api.warehouses.list.queryOptions({ input: { filter: { status: 'ACTIVE' } } }),
|
||||
);
|
||||
const yardsQuery = useQuery(
|
||||
api.warehouses.listYards.queryOptions({
|
||||
input: { warehouseId },
|
||||
enabled: Boolean(warehouseId),
|
||||
}),
|
||||
);
|
||||
const zonesQuery = useQuery(
|
||||
api.warehouses.listZones.queryOptions({
|
||||
input: { yardId },
|
||||
enabled: Boolean(yardId),
|
||||
}),
|
||||
);
|
||||
|
||||
const warehouseOptions = useMemo(
|
||||
() => (warehousesQuery.data ?? []).map((w) => ({ value: w.id, label: `${w.name} (${w.code})` })),
|
||||
|
||||
@@ -18,34 +18,14 @@ import {
|
||||
} from '@mantine/core';
|
||||
import { ChevronDown, ChevronRight, ClipboardCheck, Info, PackageSearch, Train, Truck } from 'lucide-react';
|
||||
|
||||
import { useMutation, useQuery } from '@tanstack/react-query';
|
||||
|
||||
import { api } from '@/services/api';
|
||||
import { useToast } from '@/hooks/use-toast';
|
||||
import {
|
||||
useAutoUnloadArrivedBookings,
|
||||
useBulkDispatchExport,
|
||||
useBulkMarkInspected,
|
||||
useBulkReceive,
|
||||
useEligibleBookings,
|
||||
useImportArriveQueue,
|
||||
useImportTrainItems,
|
||||
useImportUnloadedQueue,
|
||||
useLoadPassedExport,
|
||||
useLoadedExport,
|
||||
useReadyToLoadExport,
|
||||
useReceiveInventory,
|
||||
useWarehouseInventory,
|
||||
useWarehouseYards,
|
||||
useWarehouseZones,
|
||||
useWarehouses,
|
||||
} from '@/hooks/useWarehouses';
|
||||
import type {
|
||||
AutoUnloadArrivedResult,
|
||||
BulkDispatchResult,
|
||||
BulkInspectResult,
|
||||
BulkReceiveResult,
|
||||
ImportTrain,
|
||||
ImportTrainItem,
|
||||
ImportUnloadedItem,
|
||||
LoadPassedExportResult,
|
||||
ReadyToLoadRow,
|
||||
ReceiveInventoryPayload,
|
||||
} from '@/types/warehouse';
|
||||
@@ -77,9 +57,21 @@ function LocationSelects({
|
||||
value: Location;
|
||||
onChange: (next: Location) => void;
|
||||
}) {
|
||||
const warehousesQuery = useWarehouses({ status: 'ACTIVE' });
|
||||
const yardsQuery = useWarehouseYards(value.warehouseId || undefined);
|
||||
const zonesQuery = useWarehouseZones(value.yardId || undefined);
|
||||
const warehousesQuery = useQuery(
|
||||
api.warehouses.list.queryOptions({ input: { filter: { status: 'ACTIVE' } } }),
|
||||
);
|
||||
const yardsQuery = useQuery(
|
||||
api.warehouses.listYards.queryOptions({
|
||||
input: { warehouseId: value.warehouseId ?? '' },
|
||||
enabled: Boolean(value.warehouseId),
|
||||
}),
|
||||
);
|
||||
const zonesQuery = useQuery(
|
||||
api.warehouses.listZones.queryOptions({
|
||||
input: { yardId: value.yardId ?? '' },
|
||||
enabled: Boolean(value.yardId),
|
||||
}),
|
||||
);
|
||||
|
||||
const warehouseOptions = useMemo(
|
||||
() => (warehousesQuery.data ?? []).map((w) => ({ value: w.id, label: `${w.name} (${w.code})` })),
|
||||
@@ -148,10 +140,12 @@ function EligibleTab({
|
||||
onChanged?: () => void;
|
||||
}) {
|
||||
const { toast } = useToast();
|
||||
const { data: allRows = [], isLoading } = useEligibleBookings(enabled);
|
||||
const { data: allRows = [], isLoading } = useQuery(
|
||||
api.warehouses.eligibleBookings.queryOptions({ enabled }),
|
||||
);
|
||||
const rows = useMemo(() => allRows.filter((r) => r.direction === direction), [allRows, direction]);
|
||||
const bulkReceive = useBulkReceive();
|
||||
const loadPassed = useLoadPassedExport();
|
||||
const bulkReceive = useMutation(api.warehouses.bulkReceive.mutationOptions());
|
||||
const loadPassed = useMutation(api.warehouses.loadPassedExport.mutationOptions());
|
||||
const [selected, setSelected] = useState<Set<string>>(new Set());
|
||||
|
||||
const locationReady = Boolean(location.warehouseId && location.yardId && location.zoneId);
|
||||
@@ -177,10 +171,7 @@ function EligibleTab({
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const res = (await bulkReceive.mutateAsync({ direction, ...location, bookingIds })) as {
|
||||
data: BulkReceiveResult;
|
||||
};
|
||||
const r = res.data;
|
||||
const r = await bulkReceive.mutateAsync({ direction, ...location, bookingIds });
|
||||
toast({
|
||||
title: `${r.receivedCount} received at ${direction === 'EXPORT' ? 'facility' : 'warehouse'}`,
|
||||
description: r.skippedCount ? `${r.skippedCount} skipped` : undefined,
|
||||
@@ -194,8 +185,7 @@ function EligibleTab({
|
||||
|
||||
const loadPassedExport = async () => {
|
||||
try {
|
||||
const res = (await loadPassed.mutateAsync(undefined)) as { data: LoadPassedExportResult };
|
||||
const r = res.data;
|
||||
const r = await loadPassed.mutateAsync(undefined);
|
||||
toast({
|
||||
title: `${r.loadedCount} loaded`,
|
||||
description: r.skippedCount ? `${r.skippedCount} skipped — inspection not passed` : undefined,
|
||||
@@ -353,8 +343,10 @@ function EligibleTab({
|
||||
/** Export items that passed inspection and are queued to be loaded onto a train. */
|
||||
function ReadyToLoadTab({ enabled, onChanged }: { enabled: boolean; onChanged?: () => void }) {
|
||||
const { toast } = useToast();
|
||||
const { data: rows = [], isLoading } = useReadyToLoadExport(enabled);
|
||||
const loadPassed = useLoadPassedExport();
|
||||
const { data: rows = [], isLoading } = useQuery(
|
||||
api.warehouses.readyToLoadExport.queryOptions({ enabled }),
|
||||
);
|
||||
const loadPassed = useMutation(api.warehouses.loadPassedExport.mutationOptions());
|
||||
const [selected, setSelected] = useState<Set<string>>(new Set());
|
||||
|
||||
const allSelected = rows.length > 0 && selected.size === rows.length;
|
||||
@@ -369,8 +361,7 @@ function ReadyToLoadTab({ enabled, onChanged }: { enabled: boolean; onChanged?:
|
||||
|
||||
const autoLoad = async () => {
|
||||
try {
|
||||
const res = (await loadPassed.mutateAsync(undefined)) as { data: LoadPassedExportResult };
|
||||
const r = res.data;
|
||||
const r = await loadPassed.mutateAsync(undefined);
|
||||
toast({
|
||||
title: `${r.loadedCount} items loaded`,
|
||||
description: r.skippedCount ? `${r.skippedCount} skipped` : undefined,
|
||||
@@ -494,8 +485,12 @@ function LoadedExportTab({
|
||||
onChanged?: () => void;
|
||||
}) {
|
||||
const { toast } = useToast();
|
||||
const { data: rows = [], isLoading } = useLoadedExport(enabled);
|
||||
const bulkDispatch = useBulkDispatchExport();
|
||||
const { data: rows = [], isLoading } = useQuery(
|
||||
api.warehouses.loadedExport.queryOptions({ enabled }),
|
||||
);
|
||||
const bulkDispatch = useMutation(
|
||||
api.warehouses.bulkDispatchExport.mutationOptions(),
|
||||
);
|
||||
const [selected, setSelected] = useState<Set<string>>(new Set());
|
||||
|
||||
const allSelected = rows.length > 0 && selected.size === rows.length;
|
||||
@@ -514,8 +509,7 @@ function LoadedExportTab({
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const res = (await bulkDispatch.mutateAsync(inventoryIds)) as { data: BulkDispatchResult };
|
||||
const r = res.data;
|
||||
const r = await bulkDispatch.mutateAsync(inventoryIds);
|
||||
toast({
|
||||
title: `${r.dispatchedCount} dispatched`,
|
||||
description: r.skippedCount ? `${r.skippedCount} skipped` : undefined,
|
||||
@@ -659,7 +653,12 @@ function LoadedExportTab({
|
||||
|
||||
/** Assigned bookings/items for an arrived import train (read-only detail view). */
|
||||
function ImportTrainDetailTable({ scheduleId }: { scheduleId: string }) {
|
||||
const { data: items = [], isLoading } = useImportTrainItems(scheduleId);
|
||||
const { data: items = [], isLoading } = useQuery(
|
||||
api.warehouses.importTrainItems.queryOptions({
|
||||
input: { scheduleId },
|
||||
enabled: Boolean(scheduleId),
|
||||
}),
|
||||
);
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
@@ -735,18 +734,19 @@ function ImportArriveQueueTab({
|
||||
onChanged?: () => void;
|
||||
}) {
|
||||
const { toast } = useToast();
|
||||
const { data: trains = [], isLoading } = useImportArriveQueue(enabled);
|
||||
const autoUnloadMutation = useAutoUnloadArrivedBookings();
|
||||
const { data: trains = [], isLoading } = useQuery(
|
||||
api.warehouses.importArriveQueue.queryOptions({ enabled }),
|
||||
);
|
||||
const autoUnloadMutation = useMutation(
|
||||
api.warehouses.autoUnloadArrivedBookings.mutationOptions(),
|
||||
);
|
||||
const [openId, setOpenId] = useState<string | null>(null);
|
||||
const [busyId, setBusyId] = useState<string | null>(null);
|
||||
|
||||
const autoUnload = async (train: ImportTrain) => {
|
||||
setBusyId(train.scheduleId);
|
||||
try {
|
||||
const res = (await autoUnloadMutation.mutateAsync(train.scheduleId)) as {
|
||||
data: AutoUnloadArrivedResult;
|
||||
};
|
||||
const r = res.data;
|
||||
const r = await autoUnloadMutation.mutateAsync(train.scheduleId);
|
||||
const extra = [
|
||||
r.skippedCount ? `${r.skippedCount} skipped` : '',
|
||||
r.failedCount ? `${r.failedCount} failed` : '',
|
||||
@@ -866,8 +866,12 @@ function ImportArriveQueueTab({
|
||||
*/
|
||||
function ImportUnloadedQueueTab({ enabled }: { enabled: boolean }) {
|
||||
const { toast } = useToast();
|
||||
const { data: rows = [], isLoading } = useImportUnloadedQueue(enabled);
|
||||
const inspectMutation = useBulkMarkInspected();
|
||||
const { data: rows = [], isLoading } = useQuery(
|
||||
api.warehouses.importUnloadedQueue.queryOptions({ enabled }),
|
||||
);
|
||||
const inspectMutation = useMutation(
|
||||
api.warehouses.bulkMarkInspected.mutationOptions(),
|
||||
);
|
||||
const [selected, setSelected] = useState<Set<string>>(new Set());
|
||||
const [inspectId, setInspectId] = useState<string | null>(null);
|
||||
|
||||
@@ -888,10 +892,7 @@ function ImportUnloadedQueueTab({ enabled }: { enabled: boolean }) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const res = (await inspectMutation.mutateAsync({ inventoryIds: [...selected] })) as {
|
||||
data: BulkInspectResult;
|
||||
};
|
||||
const r = res.data;
|
||||
const r = await inspectMutation.mutateAsync({ inventoryIds: [...selected] });
|
||||
toast({
|
||||
title: `${r.inspectedCount} marked inspected`,
|
||||
description: r.skippedCount ? `${r.skippedCount} skipped` : undefined,
|
||||
@@ -1033,8 +1034,10 @@ function ImportUnloadedQueueTab({ enabled }: { enabled: boolean }) {
|
||||
*/
|
||||
function ImportDispatchQueueTab({ enabled }: { enabled: boolean }) {
|
||||
const { toast } = useToast();
|
||||
const { data: items = [], isLoading } = useWarehouseInventory(
|
||||
enabled ? { status: 'READY_FOR_PICKUP' } : undefined,
|
||||
const { data: items = [], isLoading } = useQuery(
|
||||
api.warehouses.listInventory.queryOptions({
|
||||
input: { filter: enabled ? { status: 'READY_FOR_PICKUP' } : undefined },
|
||||
}),
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -1155,7 +1158,9 @@ function SingleBookingReceiveModal({
|
||||
onReceived,
|
||||
}: ReceiveInventoryModalProps) {
|
||||
const { toast } = useToast();
|
||||
const receiveMutation = useReceiveInventory();
|
||||
const receiveMutation = useMutation(
|
||||
api.warehouses.receiveInventory.mutationOptions(),
|
||||
);
|
||||
const [selectedBooking, setSelectedBooking] = useState(bookingId ?? '');
|
||||
const [form, setForm] = useState<SingleFormState>({
|
||||
warehouseId: '',
|
||||
|
||||
@@ -2,6 +2,9 @@ import { useEffect, useState } from 'react';
|
||||
import { Alert, Button, Group, Modal, Stack, Text, TextInput } from '@mantine/core';
|
||||
import { Info } from 'lucide-react';
|
||||
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
|
||||
import { api } from '@/services/api';
|
||||
import { useToast } from '@/hooks/use-toast';
|
||||
import { useReleaseInventory } from '@/hooks/useWarehouses';
|
||||
import { warehouseService } from '@/services/warehouse.service';
|
||||
@@ -17,7 +20,7 @@ interface ReleaseOrderModalProps {
|
||||
|
||||
export function ReleaseOrderModal({ opened, onClose, item }: ReleaseOrderModalProps) {
|
||||
const { toast } = useToast();
|
||||
const releaseMutation = useReleaseInventory();
|
||||
const releaseMutation = useMutation(api.warehouses.release.mutationOptions());
|
||||
const [reference, setReference] = useState('');
|
||||
const [downloading, setDownloading] = useState(false);
|
||||
|
||||
|
||||
@@ -2,8 +2,10 @@ import { useEffect, useState } from 'react';
|
||||
import { Alert, Button, Group, Modal, Stack, Text } from '@mantine/core';
|
||||
import { Info } from 'lucide-react';
|
||||
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
|
||||
import { api } from '@/services/api';
|
||||
import { useToast } from '@/hooks/use-toast';
|
||||
import { useReserveInventory } from '@/hooks/useWarehouses';
|
||||
import type { WarehouseInventoryItem } from '@/types/warehouse';
|
||||
import { BookingSelect } from './BookingSelect';
|
||||
import { extractErrorMessage } from './options';
|
||||
@@ -16,7 +18,7 @@ interface ReserveInventoryModalProps {
|
||||
|
||||
export function ReserveInventoryModal({ opened, onClose, item }: ReserveInventoryModalProps) {
|
||||
const { toast } = useToast();
|
||||
const reserveMutation = useReserveInventory();
|
||||
const reserveMutation = useMutation(api.warehouses.reserve.mutationOptions());
|
||||
const [bookingId, setBookingId] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Select } from '@mantine/core';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
|
||||
import { useLoadableWagons } from '@/hooks/useWarehouses';
|
||||
import { api } from '@/services/api';
|
||||
|
||||
interface WagonSelectProps {
|
||||
value: string;
|
||||
@@ -11,7 +12,9 @@ interface WagonSelectProps {
|
||||
|
||||
/** Searchable wagon picker. Lists wagons that are loadable (read-only from scheduling). */
|
||||
export function WagonSelect({ value, onChange, label = 'Wagon', required }: WagonSelectProps) {
|
||||
const { data, isLoading } = useLoadableWagons();
|
||||
const { data, isLoading } = useQuery(
|
||||
api.warehouses.loadableWagons.queryOptions(),
|
||||
);
|
||||
|
||||
const options = (data ?? []).map((w) => ({
|
||||
value: w.id,
|
||||
|
||||
@@ -2,7 +2,9 @@ import { useMemo } from 'react';
|
||||
import { ActionIcon, Box, Card, Divider, Group, Progress, SimpleGrid, Stack, Text, Tooltip } from '@mantine/core';
|
||||
import { Building2, Eye, MapPin, Package, Pencil, Weight } from 'lucide-react';
|
||||
|
||||
import { useStations } from '@/hooks/useStations';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
|
||||
import { api } from '@/services/api';
|
||||
import type { Warehouse } from '@/types/warehouse';
|
||||
import { WarehouseStatusBadge, WarehouseTypeBadge } from './badges';
|
||||
import { formatCapacity } from './options';
|
||||
@@ -14,7 +16,9 @@ interface WarehouseCardViewProps {
|
||||
}
|
||||
|
||||
export function WarehouseCardView({ warehouses, onView, onEdit }: WarehouseCardViewProps) {
|
||||
const { data: stations } = useStations();
|
||||
const { data: stations } = useQuery(
|
||||
api.stations.list.queryOptions({ staleTime: 5 * 60 * 1000 }),
|
||||
);
|
||||
const stationNameById = useMemo(
|
||||
() => new Map((stations ?? []).map((s) => [s.id, s.name])),
|
||||
[stations],
|
||||
|
||||
@@ -15,7 +15,9 @@ import {
|
||||
YAxis,
|
||||
} from 'recharts';
|
||||
|
||||
import { useWarehouseInventory } from '@/hooks/useWarehouses';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
|
||||
import { api } from '@/services/api';
|
||||
import type { WarehouseDashboard, WarehouseInventoryItem } from '@/types/warehouse';
|
||||
|
||||
interface WarehouseDashboardChartsProps {
|
||||
@@ -38,7 +40,9 @@ type Granularity = 'week' | 'month' | 'year';
|
||||
|
||||
export function WarehouseDashboardCharts({ data }: WarehouseDashboardChartsProps) {
|
||||
const [granularity, setGranularity] = useState<Granularity>('month');
|
||||
const { data: inventory } = useWarehouseInventory();
|
||||
const { data: inventory } = useQuery(
|
||||
api.warehouses.listInventory.queryOptions({ input: {} }),
|
||||
);
|
||||
|
||||
const statusData = STATUS_SERIES.map((s) => ({
|
||||
name: s.label,
|
||||
|
||||
@@ -2,7 +2,9 @@ import { useState } from 'react';
|
||||
import { Badge, Button, Card, Divider, Group, Stack, Text } from '@mantine/core';
|
||||
import { PackagePlus, Train as TrainIcon, Warehouse as WarehouseIcon } from 'lucide-react';
|
||||
|
||||
import { useBookingSchedule, useWarehouseInventory } from '@/hooks/useWarehouses';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
|
||||
import { api } from '@/services/api';
|
||||
import { InventoryStatusBadge } from './badges';
|
||||
import { FreightVisual } from './FreightVisual';
|
||||
import { formatDate } from './options';
|
||||
@@ -28,8 +30,15 @@ function Row({ label, value }: { label: string; value: React.ReactNode }) {
|
||||
|
||||
export function WarehouseInfoCard({ bookingId, bookingReference }: WarehouseInfoCardProps) {
|
||||
const [modalOpen, setModalOpen] = useState(false);
|
||||
const { data, isLoading } = useWarehouseInventory({ bookingId });
|
||||
const { data: scheduleView } = useBookingSchedule(bookingId);
|
||||
const { data, isLoading } = useQuery(
|
||||
api.warehouses.listInventory.queryOptions({ input: { filter: { bookingId } } }),
|
||||
);
|
||||
const { data: scheduleView } = useQuery(
|
||||
api.warehouses.bookingSchedule.queryOptions({
|
||||
input: { bookingId },
|
||||
enabled: Boolean(bookingId),
|
||||
}),
|
||||
);
|
||||
|
||||
const items = data ?? [];
|
||||
const latest = items[0];
|
||||
|
||||
@@ -10,12 +10,14 @@ interface WarehouseInquiryTableProps {
|
||||
onView?: (result: InventoryInquiryResult) => void;
|
||||
}
|
||||
|
||||
const dash = '-';
|
||||
|
||||
const itemDescriptor = (result: InventoryInquiryResult) => {
|
||||
if (result.containerNumber) return `Container ${result.containerNumber}`;
|
||||
if (result.cargoType) return `Cargo · ${result.cargoType}`;
|
||||
if (result.cargoDescription) return `Cargo · ${result.cargoDescription}`;
|
||||
if (result.cargoType) return `Cargo - ${result.cargoType}`;
|
||||
if (result.cargoDescription) return `Cargo - ${result.cargoDescription}`;
|
||||
if (result.goodsId) return 'Goods';
|
||||
return '—';
|
||||
return dash;
|
||||
};
|
||||
|
||||
export function WarehouseInquiryTable({ results, onView }: WarehouseInquiryTableProps) {
|
||||
@@ -44,7 +46,7 @@ export function WarehouseInquiryTable({ results, onView }: WarehouseInquiryTable
|
||||
<Table.Th>Weight</Table.Th>
|
||||
<Table.Th>Arrived</Table.Th>
|
||||
<Table.Th>Ready</Table.Th>
|
||||
{onView && <Table.Th ta="right">Actions</Table.Th>}
|
||||
{onView ? <Table.Th ta="right">Actions</Table.Th> : null}
|
||||
</Table.Tr>
|
||||
</Table.Thead>
|
||||
<Table.Tbody>
|
||||
@@ -52,32 +54,32 @@ export function WarehouseInquiryTable({ results, onView }: WarehouseInquiryTable
|
||||
<Table.Tr key={result.id}>
|
||||
<Table.Td>
|
||||
<Text size="sm" fw={600}>
|
||||
{result.bookingReference ?? result.bookingNumber ?? result.bookingId?.slice(0, 8) ?? '—'}
|
||||
{result.bookingReference ?? result.bookingNumber ?? result.bookingId?.slice(0, 8) ?? dash}
|
||||
</Text>
|
||||
</Table.Td>
|
||||
<Table.Td>{result.customerName ?? '—'}</Table.Td>
|
||||
<Table.Td>{result.customerName ?? dash}</Table.Td>
|
||||
<Table.Td>{itemDescriptor(result)}</Table.Td>
|
||||
<Table.Td>
|
||||
<Stack gap={0}>
|
||||
<Text size="sm">{result.warehouse?.name ?? '—'}</Text>
|
||||
{result.warehouse?.code && (
|
||||
<Text size="sm">{result.warehouse?.name ?? dash}</Text>
|
||||
{result.warehouse?.code ? (
|
||||
<Text size="xs" c="dimmed">
|
||||
{result.warehouse.code}
|
||||
</Text>
|
||||
)}
|
||||
) : null}
|
||||
</Stack>
|
||||
</Table.Td>
|
||||
<Table.Td>{result.yard?.name ?? '—'}</Table.Td>
|
||||
<Table.Td>{result.zone?.name ?? '—'}</Table.Td>
|
||||
<Table.Td>{result.yard?.name ?? dash}</Table.Td>
|
||||
<Table.Td>{result.zone?.name ?? dash}</Table.Td>
|
||||
<Table.Td>
|
||||
<Stack gap={0}>
|
||||
<Text size="sm">{result.locationSummary ?? '-'}</Text>
|
||||
{result.trainNumber && (
|
||||
<Text size="sm">{result.locationSummary ?? dash}</Text>
|
||||
{result.trainNumber ? (
|
||||
<Text size="xs" c="dimmed">
|
||||
{result.trainNumber}
|
||||
{result.route ? ` - ${result.route}` : ''}
|
||||
</Text>
|
||||
)}
|
||||
) : null}
|
||||
</Stack>
|
||||
</Table.Td>
|
||||
<Table.Td>
|
||||
@@ -97,7 +99,7 @@ export function WarehouseInquiryTable({ results, onView }: WarehouseInquiryTable
|
||||
<Table.Td>
|
||||
<Text size="xs">{formatDate(result.readyForLoadingAt)}</Text>
|
||||
</Table.Td>
|
||||
{onView && (
|
||||
{onView ? (
|
||||
<Table.Td>
|
||||
<Tooltip label="View details" withArrow>
|
||||
<ActionIcon variant="subtle" color="gray" onClick={() => onView(result)} ml="auto">
|
||||
@@ -105,7 +107,7 @@ export function WarehouseInquiryTable({ results, onView }: WarehouseInquiryTable
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
</Table.Td>
|
||||
)}
|
||||
) : null}
|
||||
</Table.Tr>
|
||||
))}
|
||||
</Table.Tbody>
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import { ActionIcon, Badge, Button, Checkbox, Group, Table, Text, Tooltip } from '@mantine/core';
|
||||
import { ArrowRightLeft, ClipboardList, Coins, Eye, FileText, History, MapPin } from 'lucide-react';
|
||||
|
||||
import type { InventoryAction, WarehouseInventoryItem } from '@/types/warehouse';
|
||||
import { getNextInventoryAction } from '@/types/warehouse';
|
||||
import {
|
||||
getNextInventoryAction,
|
||||
type InventoryAction,
|
||||
type WarehouseInventoryItem,
|
||||
} from '@/types/warehouse';
|
||||
import { InventoryStatusBadge } from './badges';
|
||||
import { formatDate, formatNumber, humanizeEnum } from './options';
|
||||
|
||||
@@ -16,9 +19,7 @@ interface WarehouseInventoryTableProps {
|
||||
onInspect?: (item: WarehouseInventoryItem) => void;
|
||||
onFeePreview?: (item: WarehouseInventoryItem) => void;
|
||||
onReleaseDocument?: (item: WarehouseInventoryItem) => void;
|
||||
// Optional Last Mile action — only rendered for items whose booking requested door delivery.
|
||||
onLastMile?: (item: WarehouseInventoryItem) => void;
|
||||
// Optional row selection (used for bulk Mark-as-Inspected).
|
||||
selectedIds?: Set<string>;
|
||||
onToggleSelect?: (id: string) => void;
|
||||
onToggleSelectAll?: () => void;
|
||||
@@ -38,7 +39,7 @@ const actionColor: Record<InventoryAction, string> = {
|
||||
reserve: 'grape',
|
||||
'ready-for-loading': 'cyan',
|
||||
load: 'teal',
|
||||
dispatch: 'green',
|
||||
dispatch: 'edr-green',
|
||||
'ready-for-pickup': 'orange',
|
||||
release: 'yellow',
|
||||
deliver: 'green',
|
||||
@@ -62,9 +63,10 @@ export function WarehouseInventoryTable({
|
||||
someSelected,
|
||||
}: WarehouseInventoryTableProps) {
|
||||
const selectable = Boolean(onToggleSelect);
|
||||
|
||||
if (items.length === 0) {
|
||||
return (
|
||||
<Text c="dimmed" ta="center" py="xl">
|
||||
<Text size="sm" c="dimmed" ta="center" py="xl">
|
||||
No inventory items found.
|
||||
</Text>
|
||||
);
|
||||
@@ -103,6 +105,7 @@ export function WarehouseInventoryTable({
|
||||
const kind = itemKind(item);
|
||||
const busy = busyId === item.id;
|
||||
const nextAction = getNextInventoryAction(item);
|
||||
|
||||
return (
|
||||
<Table.Tr key={item.id}>
|
||||
{selectable && (
|
||||
@@ -118,19 +121,19 @@ export function WarehouseInventoryTable({
|
||||
{item.bookingId ? (
|
||||
<Tooltip label={item.bookingId} withArrow>
|
||||
<Text size="sm" fw={600}>
|
||||
{item.bookingId.slice(0, 8)}…
|
||||
{item.bookingId.slice(0, 8)}...
|
||||
</Text>
|
||||
</Tooltip>
|
||||
) : (
|
||||
<Text size="sm" c="dimmed">
|
||||
—
|
||||
-
|
||||
</Text>
|
||||
)}
|
||||
</Table.Td>
|
||||
<Table.Td>{item.warehouse?.facility?.name ?? '—'}</Table.Td>
|
||||
<Table.Td>{item.warehouse?.code ?? '—'}</Table.Td>
|
||||
<Table.Td>{item.yard?.code ?? '—'}</Table.Td>
|
||||
<Table.Td>{item.zone?.code ?? '—'}</Table.Td>
|
||||
<Table.Td>{item.warehouse?.facility?.name ?? '-'}</Table.Td>
|
||||
<Table.Td>{item.warehouse?.code ?? '-'}</Table.Td>
|
||||
<Table.Td>{item.yard?.code ?? '-'}</Table.Td>
|
||||
<Table.Td>{item.zone?.code ?? '-'}</Table.Td>
|
||||
<Table.Td>
|
||||
<Badge color={kind.color} variant="light" size="sm" radius="md">
|
||||
{kind.label}
|
||||
@@ -164,8 +167,6 @@ export function WarehouseInventoryTable({
|
||||
{humanizeEnum(nextAction.replace(/-/g, '_'))}
|
||||
</Button>
|
||||
)}
|
||||
{/* Batch 10 — a PICKUP_READY import item can also be stored or dispatched,
|
||||
kept separate from the customer-pickup (release/deliver) next action. */}
|
||||
{item.status === 'READY_FOR_PICKUP' && (
|
||||
<>
|
||||
<Button
|
||||
@@ -211,7 +212,11 @@ export function WarehouseInventoryTable({
|
||||
)}
|
||||
{onReleaseDocument && item.releaseDate && (
|
||||
<Tooltip label="View release exit paper" withArrow>
|
||||
<ActionIcon variant="subtle" color="orange" onClick={() => onReleaseDocument(item)}>
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
color="orange"
|
||||
onClick={() => onReleaseDocument(item)}
|
||||
>
|
||||
<FileText size={16} />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import { useMemo } from 'react';
|
||||
import { ActionIcon, Anchor, Group, Table, Text } from '@mantine/core';
|
||||
import { ActionIcon, Group, Text } from '@mantine/core';
|
||||
import { Eye, Pencil } from 'lucide-react';
|
||||
import { DataTable, type ColumnDef } from '@edr/ui-common';
|
||||
|
||||
import { useStations } from '@/hooks/useStations';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
|
||||
import { api } from '@/services/api';
|
||||
import type { Warehouse } from '@/types/warehouse';
|
||||
import { WarehouseStatusBadge, WarehouseTypeBadge } from './badges';
|
||||
import { formatCapacity } from './options';
|
||||
@@ -14,79 +17,99 @@ interface WarehouseTableProps {
|
||||
}
|
||||
|
||||
export function WarehouseTable({ warehouses, onView, onEdit }: WarehouseTableProps) {
|
||||
const { data: stations } = useStations();
|
||||
const { data: stations } = useQuery(
|
||||
api.stations.list.queryOptions({ staleTime: 5 * 60 * 1000 }),
|
||||
);
|
||||
const stationNameById = useMemo(
|
||||
() => new Map((stations ?? []).map((s) => [s.id, s.name])),
|
||||
[stations],
|
||||
);
|
||||
|
||||
if (warehouses.length === 0) {
|
||||
return (
|
||||
<Text c="dimmed" ta="center" py="xl">
|
||||
No warehouses found.
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
const columns: ColumnDef<Warehouse>[] = [
|
||||
{
|
||||
id: 'code',
|
||||
header: 'Code',
|
||||
cell: ({ row }) => (
|
||||
<Text
|
||||
fw={600}
|
||||
size="sm"
|
||||
c="edr-green.7"
|
||||
style={{ cursor: 'pointer' }}
|
||||
onClick={() => onView(row.original)}
|
||||
>
|
||||
{row.original.code}
|
||||
</Text>
|
||||
),
|
||||
},
|
||||
{ id: 'name', header: 'Name', cell: ({ row }) => row.original.name },
|
||||
{
|
||||
id: 'facility',
|
||||
header: 'Facility',
|
||||
cell: ({ row }) => {
|
||||
const name = row.original.stationId
|
||||
? stationNameById.get(row.original.stationId)
|
||||
: undefined;
|
||||
return name ? (
|
||||
<Text size="sm" fw={500}>
|
||||
{name}
|
||||
</Text>
|
||||
) : (
|
||||
<Text size="sm" c="dimmed">
|
||||
—
|
||||
</Text>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'type',
|
||||
header: 'Type',
|
||||
cell: ({ row }) => <WarehouseTypeBadge type={row.original.type} />,
|
||||
},
|
||||
{
|
||||
id: 'location',
|
||||
header: 'Location',
|
||||
cell: ({ row }) => row.original.locationName ?? '—',
|
||||
},
|
||||
{
|
||||
id: 'weight',
|
||||
header: 'Weight (cur / cap)',
|
||||
cell: ({ row }) => formatCapacity(row.original.currentWeight, row.original.capacityWeight),
|
||||
},
|
||||
{
|
||||
id: 'containers',
|
||||
header: 'Containers (cur / cap)',
|
||||
cell: ({ row }) =>
|
||||
formatCapacity(row.original.currentContainers, row.original.capacityContainers),
|
||||
},
|
||||
{
|
||||
id: 'status',
|
||||
header: 'Status',
|
||||
cell: ({ row }) => <WarehouseStatusBadge status={row.original.status} />,
|
||||
},
|
||||
{
|
||||
id: 'actions',
|
||||
header: '',
|
||||
cell: ({ row }) => (
|
||||
<Group gap="xs" justify="flex-end" wrap="nowrap" onClick={(e) => e.stopPropagation()}>
|
||||
<ActionIcon variant="subtle" color="gray" onClick={() => onView(row.original)} title="View">
|
||||
<Eye size={16} />
|
||||
</ActionIcon>
|
||||
<ActionIcon variant="subtle" color="gray" onClick={() => onEdit(row.original)} title="Edit">
|
||||
<Pencil size={16} />
|
||||
</ActionIcon>
|
||||
</Group>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<Table.ScrollContainer minWidth={900}>
|
||||
<Table highlightOnHover verticalSpacing="sm" striped>
|
||||
<Table.Thead>
|
||||
<Table.Tr>
|
||||
<Table.Th>Code</Table.Th>
|
||||
<Table.Th>Name</Table.Th>
|
||||
<Table.Th>Facility</Table.Th>
|
||||
<Table.Th>Type</Table.Th>
|
||||
<Table.Th>Location</Table.Th>
|
||||
<Table.Th>Weight (cur / cap)</Table.Th>
|
||||
<Table.Th>Containers (cur / cap)</Table.Th>
|
||||
<Table.Th>Status</Table.Th>
|
||||
<Table.Th ta="right">Actions</Table.Th>
|
||||
</Table.Tr>
|
||||
</Table.Thead>
|
||||
<Table.Tbody>
|
||||
{warehouses.map((warehouse) => (
|
||||
<Table.Tr key={warehouse.id}>
|
||||
<Table.Td>
|
||||
<Anchor fw={600} size="sm" onClick={() => onView(warehouse)}>
|
||||
{warehouse.code}
|
||||
</Anchor>
|
||||
</Table.Td>
|
||||
<Table.Td>{warehouse.name}</Table.Td>
|
||||
<Table.Td>
|
||||
{warehouse.stationId && stationNameById.get(warehouse.stationId) ? (
|
||||
<Text size="sm" fw={500}>
|
||||
{stationNameById.get(warehouse.stationId)}
|
||||
</Text>
|
||||
) : (
|
||||
<Text size="sm" c="dimmed">
|
||||
—
|
||||
</Text>
|
||||
)}
|
||||
</Table.Td>
|
||||
<Table.Td>
|
||||
<WarehouseTypeBadge type={warehouse.type} />
|
||||
</Table.Td>
|
||||
<Table.Td>{warehouse.locationName ?? '—'}</Table.Td>
|
||||
<Table.Td>{formatCapacity(warehouse.currentWeight, warehouse.capacityWeight)}</Table.Td>
|
||||
<Table.Td>{formatCapacity(warehouse.currentContainers, warehouse.capacityContainers)}</Table.Td>
|
||||
<Table.Td>
|
||||
<WarehouseStatusBadge status={warehouse.status} />
|
||||
</Table.Td>
|
||||
<Table.Td>
|
||||
<Group gap="xs" justify="flex-end" wrap="nowrap">
|
||||
<ActionIcon variant="subtle" color="gray" onClick={() => onView(warehouse)} title="View">
|
||||
<Eye size={16} />
|
||||
</ActionIcon>
|
||||
<ActionIcon variant="subtle" color="gray" onClick={() => onEdit(warehouse)} title="Edit">
|
||||
<Pencil size={16} />
|
||||
</ActionIcon>
|
||||
</Group>
|
||||
</Table.Td>
|
||||
</Table.Tr>
|
||||
))}
|
||||
</Table.Tbody>
|
||||
</Table>
|
||||
</Table.ScrollContainer>
|
||||
<DataTable
|
||||
columns={columns}
|
||||
data={warehouses}
|
||||
status="success"
|
||||
onRowClick={(warehouse) => onView(warehouse)}
|
||||
emptyMessage="No warehouses found."
|
||||
containerClassName="border-0 shadow-none"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,54 +1,80 @@
|
||||
import { Badge } from '@mantine/core';
|
||||
import { Badge } from "@mantine/core";
|
||||
|
||||
import type { InventoryStatus, WarehouseStatus, WarehouseType } from '@/types/warehouse';
|
||||
import type {
|
||||
InventoryStatus,
|
||||
WarehouseStatus,
|
||||
WarehouseType,
|
||||
} from "@/types/warehouse";
|
||||
|
||||
const humanize = (value: string) =>
|
||||
value
|
||||
.toLowerCase()
|
||||
.split('_')
|
||||
.split("_")
|
||||
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
||||
.join(' ');
|
||||
.join(" ");
|
||||
|
||||
const badgeStyle = {
|
||||
fontSize: '0.7rem',
|
||||
letterSpacing: '0.04em',
|
||||
whiteSpace: 'nowrap' as const,
|
||||
fontSize: "0.7rem",
|
||||
letterSpacing: "0.04em",
|
||||
whiteSpace: "nowrap" as const,
|
||||
};
|
||||
|
||||
export function WarehouseTypeBadge({ type }: { type: WarehouseType }) {
|
||||
const color = type === 'CLOSED_WAREHOUSE' ? 'indigo' : 'teal';
|
||||
const color = type === "CLOSED_WAREHOUSE" ? "indigo" : "teal";
|
||||
return (
|
||||
<Badge color={color} variant="light" size="sm" radius="md" fw={600} style={badgeStyle}>
|
||||
<Badge
|
||||
color={color}
|
||||
variant="light"
|
||||
size="sm"
|
||||
radius="md"
|
||||
fw={600}
|
||||
style={badgeStyle}
|
||||
>
|
||||
{humanize(type)}
|
||||
</Badge>
|
||||
);
|
||||
}
|
||||
|
||||
export function WarehouseStatusBadge({ status }: { status: WarehouseStatus }) {
|
||||
const color = status === 'ACTIVE' ? 'green' : 'gray';
|
||||
const color = status === "ACTIVE" ? "edr-green" : "gray";
|
||||
return (
|
||||
<Badge color={color} variant="light" size="sm" radius="md" tt="uppercase" fw={600} style={badgeStyle}>
|
||||
<Badge
|
||||
color={color}
|
||||
variant="light"
|
||||
size="sm"
|
||||
radius="md"
|
||||
tt="uppercase"
|
||||
fw={600}
|
||||
style={badgeStyle}
|
||||
>
|
||||
{status}
|
||||
</Badge>
|
||||
);
|
||||
}
|
||||
|
||||
const inventoryStatusColor: Record<InventoryStatus, string> = {
|
||||
UNLOADED: 'indigo',
|
||||
RECEIVED: 'yellow',
|
||||
STORED: 'blue',
|
||||
RESERVED: 'grape',
|
||||
READY_FOR_LOADING: 'cyan',
|
||||
LOADED: 'teal',
|
||||
DISPATCHED: 'green',
|
||||
READY_FOR_PICKUP: 'orange',
|
||||
DELIVERED: 'green',
|
||||
UNLOADED: "indigo",
|
||||
RECEIVED: "yellow",
|
||||
STORED: "blue",
|
||||
RESERVED: "grape",
|
||||
READY_FOR_LOADING: "cyan",
|
||||
LOADED: "teal",
|
||||
READY_FOR_PICKUP: "teal",
|
||||
DISPATCHED: "edr-green",
|
||||
DELIVERED: "edr-green",
|
||||
};
|
||||
|
||||
export function InventoryStatusBadge({ status }: { status: InventoryStatus }) {
|
||||
const color = inventoryStatusColor[status] ?? 'gray';
|
||||
const color = inventoryStatusColor[status] ?? "gray";
|
||||
return (
|
||||
<Badge color={color} variant="light" size="sm" radius="md" fw={600} style={badgeStyle}>
|
||||
<Badge
|
||||
color={color}
|
||||
variant="light"
|
||||
size="sm"
|
||||
radius="md"
|
||||
fw={600}
|
||||
style={badgeStyle}
|
||||
>
|
||||
{humanize(status)}
|
||||
</Badge>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user