mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-07 17:45:42 +00:00
refactor: migrated of the customer hooks for warehouse data fetching
This commit is contained in:
@@ -9,7 +9,9 @@ import {
|
|||||||
Warehouse,
|
Warehouse,
|
||||||
} from 'lucide-react';
|
} 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 type { ActivityType } from '@/types/warehouse';
|
||||||
import { formatDate, humanizeEnum } from './options';
|
import { formatDate, humanizeEnum } from './options';
|
||||||
|
|
||||||
@@ -24,7 +26,12 @@ const activityIcon: Record<ActivityType, React.ReactNode> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export function ActivityTimeline({ inventoryId }: { inventoryId: string }) {
|
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 ?? [];
|
const items = data ?? [];
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
|
|||||||
@@ -9,9 +9,11 @@ import {
|
|||||||
TextInput,
|
TextInput,
|
||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
|
|
||||||
|
import { useMutation } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import { api } from '@/services/api';
|
||||||
import { useToast } from '@/hooks/use-toast';
|
import { useToast } from '@/hooks/use-toast';
|
||||||
import { useStations } from '@/hooks/useStations';
|
import { useStations } from '@/hooks/useStations';
|
||||||
import { useCreateWarehouse, useUpdateWarehouse } from '@/hooks/useWarehouses';
|
|
||||||
import type { SaveWarehousePayload, Warehouse, WarehouseType } from '@/types/warehouse';
|
import type { SaveWarehousePayload, Warehouse, WarehouseType } from '@/types/warehouse';
|
||||||
import { extractErrorMessage, statusOptions, warehouseTypeOptions } from './options';
|
import { extractErrorMessage, statusOptions, warehouseTypeOptions } from './options';
|
||||||
|
|
||||||
@@ -48,8 +50,8 @@ const emptyForm = (): FormState => ({
|
|||||||
export function CreateWarehouseModal({ opened, onClose, warehouse }: CreateWarehouseModalProps) {
|
export function CreateWarehouseModal({ opened, onClose, warehouse }: CreateWarehouseModalProps) {
|
||||||
const isEdit = Boolean(warehouse);
|
const isEdit = Boolean(warehouse);
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
const createMutation = useCreateWarehouse();
|
const createMutation = useMutation(api.warehouses.create.mutationOptions());
|
||||||
const updateMutation = useUpdateWarehouse();
|
const updateMutation = useMutation(api.warehouses.update.mutationOptions());
|
||||||
const { data: stations } = useStations();
|
const { data: stations } = useStations();
|
||||||
const [form, setForm] = useState<FormState>(emptyForm());
|
const [form, setForm] = useState<FormState>(emptyForm());
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { Button, Group, Modal, NumberInput, Select, Stack, TextInput } from '@mantine/core';
|
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 { useToast } from '@/hooks/use-toast';
|
||||||
import { useCreateYard, useUpdateYard } from '@/hooks/useWarehouses';
|
|
||||||
import type { SaveYardPayload, WarehouseYard, WarehouseYardType } from '@/types/warehouse';
|
import type { SaveYardPayload, WarehouseYard, WarehouseYardType } from '@/types/warehouse';
|
||||||
import { extractErrorMessage, statusOptions, yardTypeOptions } from './options';
|
import { extractErrorMessage, statusOptions, yardTypeOptions } from './options';
|
||||||
|
|
||||||
@@ -36,8 +38,8 @@ const emptyForm = (): FormState => ({
|
|||||||
export function CreateYardModal({ opened, onClose, warehouseId, yard }: CreateYardModalProps) {
|
export function CreateYardModal({ opened, onClose, warehouseId, yard }: CreateYardModalProps) {
|
||||||
const isEdit = Boolean(yard);
|
const isEdit = Boolean(yard);
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
const createMutation = useCreateYard();
|
const createMutation = useMutation(api.warehouses.createYard.mutationOptions());
|
||||||
const updateMutation = useUpdateYard();
|
const updateMutation = useMutation(api.warehouses.updateYard.mutationOptions());
|
||||||
const [form, setForm] = useState<FormState>(emptyForm());
|
const [form, setForm] = useState<FormState>(emptyForm());
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { Button, Group, Modal, NumberInput, Select, Stack, TextInput } from '@mantine/core';
|
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 { useToast } from '@/hooks/use-toast';
|
||||||
import { useCreateZone, useUpdateZone } from '@/hooks/useWarehouses';
|
|
||||||
import type { SaveZonePayload, WarehouseZone, WarehouseZoneType } from '@/types/warehouse';
|
import type { SaveZonePayload, WarehouseZone, WarehouseZoneType } from '@/types/warehouse';
|
||||||
import { extractErrorMessage, statusOptions, zoneTypeOptions } from './options';
|
import { extractErrorMessage, statusOptions, zoneTypeOptions } from './options';
|
||||||
|
|
||||||
@@ -36,8 +38,8 @@ const emptyForm = (): FormState => ({
|
|||||||
export function CreateZoneModal({ opened, onClose, yardId, zone }: CreateZoneModalProps) {
|
export function CreateZoneModal({ opened, onClose, yardId, zone }: CreateZoneModalProps) {
|
||||||
const isEdit = Boolean(zone);
|
const isEdit = Boolean(zone);
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
const createMutation = useCreateZone();
|
const createMutation = useMutation(api.warehouses.createZone.mutationOptions());
|
||||||
const updateMutation = useUpdateZone();
|
const updateMutation = useMutation(api.warehouses.updateZone.mutationOptions());
|
||||||
const [form, setForm] = useState<FormState>(emptyForm());
|
const [form, setForm] = useState<FormState>(emptyForm());
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -2,8 +2,10 @@ import { useEffect, useState } from 'react';
|
|||||||
import { Alert, Button, Group, Modal, Stack, Text, Textarea, TextInput } from '@mantine/core';
|
import { Alert, Button, Group, Modal, Stack, Text, Textarea, TextInput } from '@mantine/core';
|
||||||
import { Info } from 'lucide-react';
|
import { Info } from 'lucide-react';
|
||||||
|
|
||||||
|
import { useMutation } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import { api } from '@/services/api';
|
||||||
import { useToast } from '@/hooks/use-toast';
|
import { useToast } from '@/hooks/use-toast';
|
||||||
import { useDeliverInventory } from '@/hooks/useWarehouses';
|
|
||||||
import type { WarehouseInventoryItem } from '@/types/warehouse';
|
import type { WarehouseInventoryItem } from '@/types/warehouse';
|
||||||
import { extractErrorMessage } from './options';
|
import { extractErrorMessage } from './options';
|
||||||
|
|
||||||
@@ -15,7 +17,7 @@ interface DeliverInventoryModalProps {
|
|||||||
|
|
||||||
export function DeliverInventoryModal({ opened, onClose, item }: DeliverInventoryModalProps) {
|
export function DeliverInventoryModal({ opened, onClose, item }: DeliverInventoryModalProps) {
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
const deliverMutation = useDeliverInventory();
|
const deliverMutation = useMutation(api.warehouses.deliver.mutationOptions());
|
||||||
const [receiverName, setReceiverName] = useState('');
|
const [receiverName, setReceiverName] = useState('');
|
||||||
const [remarks, setRemarks] = useState('');
|
const [remarks, setRemarks] = useState('');
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,10 @@
|
|||||||
import { Badge, Button, Card, Divider, Group, Loader, Modal, Stack, Text } from '@mantine/core';
|
import { Badge, Button, Card, Divider, Group, Loader, Modal, Stack, Text } from '@mantine/core';
|
||||||
import { CalendarClock, Coins, DoorOpen, FileText } from 'lucide-react';
|
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 { useToast } from '@/hooks/use-toast';
|
||||||
import {
|
|
||||||
useFeePreview,
|
|
||||||
useGateClearance,
|
|
||||||
useGenerateInvoice,
|
|
||||||
useInvoicesForInventory,
|
|
||||||
} from '@/hooks/useWarehouses';
|
|
||||||
import { extractErrorMessage } from './options';
|
import { extractErrorMessage } from './options';
|
||||||
import type { FeePreview, WarehouseInvoiceStatus } from '@/types/warehouse';
|
import type { FeePreview, WarehouseInvoiceStatus } from '@/types/warehouse';
|
||||||
|
|
||||||
@@ -88,10 +85,20 @@ function Row({ label, value }: { label: string; value: string }) {
|
|||||||
export function FeePreviewModal({ opened, onClose, inventoryId }: FeePreviewModalProps) {
|
export function FeePreviewModal({ opened, onClose, inventoryId }: FeePreviewModalProps) {
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
const enabledId = opened ? inventoryId ?? undefined : undefined;
|
const enabledId = opened ? inventoryId ?? undefined : undefined;
|
||||||
const { data, isLoading } = useFeePreview(enabledId);
|
const { data, isLoading } = useQuery(
|
||||||
const { data: invoices } = useInvoicesForInventory(enabledId);
|
api.warehouses.feePreview.queryOptions({
|
||||||
const generate = useGenerateInvoice();
|
input: { inventoryId: enabledId ?? '' },
|
||||||
const gateClear = useGateClearance();
|
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');
|
const activeInvoice = (invoices ?? []).find((i) => i.status !== 'CANCELLED');
|
||||||
|
|
||||||
|
|||||||
@@ -12,8 +12,10 @@ import {
|
|||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
import { Upload } from 'lucide-react';
|
import { Upload } from 'lucide-react';
|
||||||
|
|
||||||
|
import { useMutation } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import { api } from '@/services/api';
|
||||||
import { useToast } from '@/hooks/use-toast';
|
import { useToast } from '@/hooks/use-toast';
|
||||||
import { useCreateInspectionReport, useUploadInspectionAttachments } from '@/hooks/useWarehouses';
|
|
||||||
import {
|
import {
|
||||||
INSPECTION_REPORT_TYPES,
|
INSPECTION_REPORT_TYPES,
|
||||||
INSPECTION_STATUSES,
|
INSPECTION_STATUSES,
|
||||||
@@ -45,8 +47,12 @@ const STATUS_LABELS: Record<InspectionResultStatus, string> = {
|
|||||||
/** Batch 4.5 — record an inspection / damage report with optional image upload. */
|
/** Batch 4.5 — record an inspection / damage report with optional image upload. */
|
||||||
export function InspectionReportModal({ opened, onClose, inventoryId }: InspectionReportModalProps) {
|
export function InspectionReportModal({ opened, onClose, inventoryId }: InspectionReportModalProps) {
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
const createReport = useCreateInspectionReport();
|
const createReport = useMutation(
|
||||||
const uploadAttachments = useUploadInspectionAttachments();
|
api.warehouses.createInspectionReport.mutationOptions(),
|
||||||
|
);
|
||||||
|
const uploadAttachments = useMutation(
|
||||||
|
api.warehouses.uploadInspectionAttachments.mutationOptions(),
|
||||||
|
);
|
||||||
|
|
||||||
const [reportType, setReportType] = useState<InspectionReportType>('INSPECTION');
|
const [reportType, setReportType] = useState<InspectionReportType>('INSPECTION');
|
||||||
const [inspectionStatus, setInspectionStatus] = useState<InspectionResultStatus>('PASSED');
|
const [inspectionStatus, setInspectionStatus] = useState<InspectionResultStatus>('PASSED');
|
||||||
|
|||||||
@@ -1,12 +1,19 @@
|
|||||||
import { Center, Loader, Table, Text } from '@mantine/core';
|
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';
|
import { formatDate } from './options';
|
||||||
|
|
||||||
const shortId = (id?: string | null) => (id ? `${id.slice(0, 8)}…` : '—');
|
const shortId = (id?: string | null) => (id ? `${id.slice(0, 8)}…` : '—');
|
||||||
|
|
||||||
export function InventoryMovementHistoryTable({ inventoryId }: { inventoryId: string }) {
|
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 ?? [];
|
const movements = data ?? [];
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
|
|||||||
@@ -2,14 +2,10 @@ import { useState } from 'react';
|
|||||||
import { Button, Center, Group, Loader, Stack, Text } from '@mantine/core';
|
import { Button, Center, Group, Loader, Stack, Text } from '@mantine/core';
|
||||||
import { ClipboardCheck } from 'lucide-react';
|
import { ClipboardCheck } from 'lucide-react';
|
||||||
|
|
||||||
|
import { useMutation } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import { api } from '@/services/api';
|
||||||
import { useToast } from '@/hooks/use-toast';
|
import { useToast } from '@/hooks/use-toast';
|
||||||
import {
|
|
||||||
useBulkMarkInspected,
|
|
||||||
useDispatchInventory,
|
|
||||||
useMarkReadyForLoading,
|
|
||||||
useMarkReadyForPickup,
|
|
||||||
useStoreInventory,
|
|
||||||
} from '@/hooks/useWarehouses';
|
|
||||||
import type { InventoryAction, WarehouseInventoryItem } from '@/types/warehouse';
|
import type { InventoryAction, WarehouseInventoryItem } from '@/types/warehouse';
|
||||||
import { DeliverInventoryModal } from './DeliverInventoryModal';
|
import { DeliverInventoryModal } from './DeliverInventoryModal';
|
||||||
import { FeePreviewModal } from './FeePreviewModal';
|
import { FeePreviewModal } from './FeePreviewModal';
|
||||||
@@ -42,11 +38,17 @@ export function InventoryWorkbench({ items, isLoading, onLastMile }: InventoryWo
|
|||||||
const [releaseItem, setReleaseItem] = useState<WarehouseInventoryItem | null>(null);
|
const [releaseItem, setReleaseItem] = useState<WarehouseInventoryItem | null>(null);
|
||||||
const [deliverItem, setDeliverItem] = useState<WarehouseInventoryItem | null>(null);
|
const [deliverItem, setDeliverItem] = useState<WarehouseInventoryItem | null>(null);
|
||||||
|
|
||||||
const storeMutation = useStoreInventory();
|
const storeMutation = useMutation(api.warehouses.store.mutationOptions());
|
||||||
const readyMutation = useMarkReadyForLoading();
|
const readyMutation = useMutation(
|
||||||
const pickupMutation = useMarkReadyForPickup();
|
api.warehouses.markReadyForLoading.mutationOptions(),
|
||||||
const dispatchMutation = useDispatchInventory();
|
);
|
||||||
const inspectMutation = useBulkMarkInspected();
|
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 [selected, setSelected] = useState<Set<string>>(new Set());
|
||||||
const allSelected = items.length > 0 && selected.size === items.length;
|
const allSelected = items.length > 0 && selected.size === items.length;
|
||||||
@@ -66,10 +68,7 @@ export function InventoryWorkbench({ items, isLoading, onLastMile }: InventoryWo
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const res = (await inspectMutation.mutateAsync({ inventoryIds: [...selected] })) as {
|
const r = await inspectMutation.mutateAsync({ inventoryIds: [...selected] });
|
||||||
data: { inspectedCount: number; skippedCount: number };
|
|
||||||
};
|
|
||||||
const r = res.data;
|
|
||||||
toast({
|
toast({
|
||||||
title: `${r.inspectedCount} marked inspected`,
|
title: `${r.inspectedCount} marked inspected`,
|
||||||
description: r.skippedCount ? `${r.skippedCount} skipped` : undefined,
|
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 { Alert, Button, Group, Modal, NumberInput, Stack, Text, Textarea } from '@mantine/core';
|
||||||
import { Info } from 'lucide-react';
|
import { Info } from 'lucide-react';
|
||||||
|
|
||||||
|
import { useMutation } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import { api } from '@/services/api';
|
||||||
import { useToast } from '@/hooks/use-toast';
|
import { useToast } from '@/hooks/use-toast';
|
||||||
import { useLoadInventory } from '@/hooks/useWarehouses';
|
|
||||||
import type { WarehouseInventoryItem } from '@/types/warehouse';
|
import type { WarehouseInventoryItem } from '@/types/warehouse';
|
||||||
import { WagonSelect } from './WagonSelect';
|
import { WagonSelect } from './WagonSelect';
|
||||||
import { extractErrorMessage } from './options';
|
import { extractErrorMessage } from './options';
|
||||||
@@ -17,7 +19,7 @@ interface LoadInventoryModalProps {
|
|||||||
/** Load READY_FOR_LOADING inventory onto a wagon (creates a loading record). */
|
/** Load READY_FOR_LOADING inventory onto a wagon (creates a loading record). */
|
||||||
export function LoadInventoryModal({ opened, onClose, item }: LoadInventoryModalProps) {
|
export function LoadInventoryModal({ opened, onClose, item }: LoadInventoryModalProps) {
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
const loadMutation = useLoadInventory();
|
const loadMutation = useMutation(api.warehouses.load.mutationOptions());
|
||||||
const [wagonId, setWagonId] = useState('');
|
const [wagonId, setWagonId] = useState('');
|
||||||
const [loadedWeight, setLoadedWeight] = useState<number | ''>('');
|
const [loadedWeight, setLoadedWeight] = useState<number | ''>('');
|
||||||
const [notes, setNotes] = useState('');
|
const [notes, setNotes] = useState('');
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
import { useEffect, useMemo, useState } from 'react';
|
import { useEffect, useMemo, useState } from 'react';
|
||||||
import { Button, Group, Modal, Select, Stack, Textarea } from '@mantine/core';
|
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 { useToast } from '@/hooks/use-toast';
|
||||||
import { useMoveInventory, useWarehouseYards, useWarehouseZones, useWarehouses } from '@/hooks/useWarehouses';
|
|
||||||
import type { WarehouseInventoryItem } from '@/types/warehouse';
|
import type { WarehouseInventoryItem } from '@/types/warehouse';
|
||||||
import { extractErrorMessage } from './options';
|
import { extractErrorMessage } from './options';
|
||||||
|
|
||||||
@@ -14,7 +16,7 @@ interface MoveInventoryModalProps {
|
|||||||
|
|
||||||
export function MoveInventoryModal({ opened, onClose, item }: MoveInventoryModalProps) {
|
export function MoveInventoryModal({ opened, onClose, item }: MoveInventoryModalProps) {
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
const moveMutation = useMoveInventory();
|
const moveMutation = useMutation(api.warehouses.move.mutationOptions());
|
||||||
const [warehouseId, setWarehouseId] = useState('');
|
const [warehouseId, setWarehouseId] = useState('');
|
||||||
const [yardId, setYardId] = useState('');
|
const [yardId, setYardId] = useState('');
|
||||||
const [zoneId, setZoneId] = useState('');
|
const [zoneId, setZoneId] = useState('');
|
||||||
@@ -29,9 +31,21 @@ export function MoveInventoryModal({ opened, onClose, item }: MoveInventoryModal
|
|||||||
}
|
}
|
||||||
}, [opened]);
|
}, [opened]);
|
||||||
|
|
||||||
const warehousesQuery = useWarehouses({ status: 'ACTIVE' });
|
const warehousesQuery = useQuery(
|
||||||
const yardsQuery = useWarehouseYards(warehouseId || undefined);
|
api.warehouses.list.queryOptions({ input: { filter: { status: 'ACTIVE' } } }),
|
||||||
const zonesQuery = useWarehouseZones(yardId || undefined);
|
);
|
||||||
|
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(
|
const warehouseOptions = useMemo(
|
||||||
() => (warehousesQuery.data ?? []).map((w) => ({ value: w.id, label: `${w.name} (${w.code})` })),
|
() => (warehousesQuery.data ?? []).map((w) => ({ value: w.id, label: `${w.name} (${w.code})` })),
|
||||||
|
|||||||
@@ -18,34 +18,14 @@ import {
|
|||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
import { ChevronDown, ChevronRight, ClipboardCheck, Info, PackageSearch, Train, Truck } from 'lucide-react';
|
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 { 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 {
|
import type {
|
||||||
AutoUnloadArrivedResult,
|
|
||||||
BulkDispatchResult,
|
|
||||||
BulkInspectResult,
|
|
||||||
BulkReceiveResult,
|
|
||||||
ImportTrain,
|
ImportTrain,
|
||||||
ImportTrainItem,
|
ImportTrainItem,
|
||||||
ImportUnloadedItem,
|
ImportUnloadedItem,
|
||||||
LoadPassedExportResult,
|
|
||||||
ReadyToLoadRow,
|
ReadyToLoadRow,
|
||||||
ReceiveInventoryPayload,
|
ReceiveInventoryPayload,
|
||||||
} from '@/types/warehouse';
|
} from '@/types/warehouse';
|
||||||
@@ -77,9 +57,21 @@ function LocationSelects({
|
|||||||
value: Location;
|
value: Location;
|
||||||
onChange: (next: Location) => void;
|
onChange: (next: Location) => void;
|
||||||
}) {
|
}) {
|
||||||
const warehousesQuery = useWarehouses({ status: 'ACTIVE' });
|
const warehousesQuery = useQuery(
|
||||||
const yardsQuery = useWarehouseYards(value.warehouseId || undefined);
|
api.warehouses.list.queryOptions({ input: { filter: { status: 'ACTIVE' } } }),
|
||||||
const zonesQuery = useWarehouseZones(value.yardId || undefined);
|
);
|
||||||
|
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(
|
const warehouseOptions = useMemo(
|
||||||
() => (warehousesQuery.data ?? []).map((w) => ({ value: w.id, label: `${w.name} (${w.code})` })),
|
() => (warehousesQuery.data ?? []).map((w) => ({ value: w.id, label: `${w.name} (${w.code})` })),
|
||||||
@@ -148,10 +140,12 @@ function EligibleTab({
|
|||||||
onChanged?: () => void;
|
onChanged?: () => void;
|
||||||
}) {
|
}) {
|
||||||
const { toast } = useToast();
|
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 rows = useMemo(() => allRows.filter((r) => r.direction === direction), [allRows, direction]);
|
||||||
const bulkReceive = useBulkReceive();
|
const bulkReceive = useMutation(api.warehouses.bulkReceive.mutationOptions());
|
||||||
const loadPassed = useLoadPassedExport();
|
const loadPassed = useMutation(api.warehouses.loadPassedExport.mutationOptions());
|
||||||
const [selected, setSelected] = useState<Set<string>>(new Set());
|
const [selected, setSelected] = useState<Set<string>>(new Set());
|
||||||
|
|
||||||
const locationReady = Boolean(location.warehouseId && location.yardId && location.zoneId);
|
const locationReady = Boolean(location.warehouseId && location.yardId && location.zoneId);
|
||||||
@@ -177,10 +171,7 @@ function EligibleTab({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const res = (await bulkReceive.mutateAsync({ direction, ...location, bookingIds })) as {
|
const r = await bulkReceive.mutateAsync({ direction, ...location, bookingIds });
|
||||||
data: BulkReceiveResult;
|
|
||||||
};
|
|
||||||
const r = res.data;
|
|
||||||
toast({
|
toast({
|
||||||
title: `${r.receivedCount} received at ${direction === 'EXPORT' ? 'facility' : 'warehouse'}`,
|
title: `${r.receivedCount} received at ${direction === 'EXPORT' ? 'facility' : 'warehouse'}`,
|
||||||
description: r.skippedCount ? `${r.skippedCount} skipped` : undefined,
|
description: r.skippedCount ? `${r.skippedCount} skipped` : undefined,
|
||||||
@@ -194,8 +185,7 @@ function EligibleTab({
|
|||||||
|
|
||||||
const loadPassedExport = async () => {
|
const loadPassedExport = async () => {
|
||||||
try {
|
try {
|
||||||
const res = (await loadPassed.mutateAsync(undefined)) as { data: LoadPassedExportResult };
|
const r = await loadPassed.mutateAsync(undefined);
|
||||||
const r = res.data;
|
|
||||||
toast({
|
toast({
|
||||||
title: `${r.loadedCount} loaded`,
|
title: `${r.loadedCount} loaded`,
|
||||||
description: r.skippedCount ? `${r.skippedCount} skipped — inspection not passed` : undefined,
|
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. */
|
/** Export items that passed inspection and are queued to be loaded onto a train. */
|
||||||
function ReadyToLoadTab({ enabled, onChanged }: { enabled: boolean; onChanged?: () => void }) {
|
function ReadyToLoadTab({ enabled, onChanged }: { enabled: boolean; onChanged?: () => void }) {
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
const { data: rows = [], isLoading } = useReadyToLoadExport(enabled);
|
const { data: rows = [], isLoading } = useQuery(
|
||||||
const loadPassed = useLoadPassedExport();
|
api.warehouses.readyToLoadExport.queryOptions({ enabled }),
|
||||||
|
);
|
||||||
|
const loadPassed = useMutation(api.warehouses.loadPassedExport.mutationOptions());
|
||||||
const [selected, setSelected] = useState<Set<string>>(new Set());
|
const [selected, setSelected] = useState<Set<string>>(new Set());
|
||||||
|
|
||||||
const allSelected = rows.length > 0 && selected.size === rows.length;
|
const allSelected = rows.length > 0 && selected.size === rows.length;
|
||||||
@@ -369,8 +361,7 @@ function ReadyToLoadTab({ enabled, onChanged }: { enabled: boolean; onChanged?:
|
|||||||
|
|
||||||
const autoLoad = async () => {
|
const autoLoad = async () => {
|
||||||
try {
|
try {
|
||||||
const res = (await loadPassed.mutateAsync(undefined)) as { data: LoadPassedExportResult };
|
const r = await loadPassed.mutateAsync(undefined);
|
||||||
const r = res.data;
|
|
||||||
toast({
|
toast({
|
||||||
title: `${r.loadedCount} items loaded`,
|
title: `${r.loadedCount} items loaded`,
|
||||||
description: r.skippedCount ? `${r.skippedCount} skipped` : undefined,
|
description: r.skippedCount ? `${r.skippedCount} skipped` : undefined,
|
||||||
@@ -494,8 +485,12 @@ function LoadedExportTab({
|
|||||||
onChanged?: () => void;
|
onChanged?: () => void;
|
||||||
}) {
|
}) {
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
const { data: rows = [], isLoading } = useLoadedExport(enabled);
|
const { data: rows = [], isLoading } = useQuery(
|
||||||
const bulkDispatch = useBulkDispatchExport();
|
api.warehouses.loadedExport.queryOptions({ enabled }),
|
||||||
|
);
|
||||||
|
const bulkDispatch = useMutation(
|
||||||
|
api.warehouses.bulkDispatchExport.mutationOptions(),
|
||||||
|
);
|
||||||
const [selected, setSelected] = useState<Set<string>>(new Set());
|
const [selected, setSelected] = useState<Set<string>>(new Set());
|
||||||
|
|
||||||
const allSelected = rows.length > 0 && selected.size === rows.length;
|
const allSelected = rows.length > 0 && selected.size === rows.length;
|
||||||
@@ -514,8 +509,7 @@ function LoadedExportTab({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const res = (await bulkDispatch.mutateAsync(inventoryIds)) as { data: BulkDispatchResult };
|
const r = await bulkDispatch.mutateAsync(inventoryIds);
|
||||||
const r = res.data;
|
|
||||||
toast({
|
toast({
|
||||||
title: `${r.dispatchedCount} dispatched`,
|
title: `${r.dispatchedCount} dispatched`,
|
||||||
description: r.skippedCount ? `${r.skippedCount} skipped` : undefined,
|
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). */
|
/** Assigned bookings/items for an arrived import train (read-only detail view). */
|
||||||
function ImportTrainDetailTable({ scheduleId }: { scheduleId: string }) {
|
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) {
|
if (isLoading) {
|
||||||
return (
|
return (
|
||||||
@@ -735,18 +734,19 @@ function ImportArriveQueueTab({
|
|||||||
onChanged?: () => void;
|
onChanged?: () => void;
|
||||||
}) {
|
}) {
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
const { data: trains = [], isLoading } = useImportArriveQueue(enabled);
|
const { data: trains = [], isLoading } = useQuery(
|
||||||
const autoUnloadMutation = useAutoUnloadArrivedBookings();
|
api.warehouses.importArriveQueue.queryOptions({ enabled }),
|
||||||
|
);
|
||||||
|
const autoUnloadMutation = useMutation(
|
||||||
|
api.warehouses.autoUnloadArrivedBookings.mutationOptions(),
|
||||||
|
);
|
||||||
const [openId, setOpenId] = useState<string | null>(null);
|
const [openId, setOpenId] = useState<string | null>(null);
|
||||||
const [busyId, setBusyId] = useState<string | null>(null);
|
const [busyId, setBusyId] = useState<string | null>(null);
|
||||||
|
|
||||||
const autoUnload = async (train: ImportTrain) => {
|
const autoUnload = async (train: ImportTrain) => {
|
||||||
setBusyId(train.scheduleId);
|
setBusyId(train.scheduleId);
|
||||||
try {
|
try {
|
||||||
const res = (await autoUnloadMutation.mutateAsync(train.scheduleId)) as {
|
const r = await autoUnloadMutation.mutateAsync(train.scheduleId);
|
||||||
data: AutoUnloadArrivedResult;
|
|
||||||
};
|
|
||||||
const r = res.data;
|
|
||||||
const extra = [
|
const extra = [
|
||||||
r.skippedCount ? `${r.skippedCount} skipped` : '',
|
r.skippedCount ? `${r.skippedCount} skipped` : '',
|
||||||
r.failedCount ? `${r.failedCount} failed` : '',
|
r.failedCount ? `${r.failedCount} failed` : '',
|
||||||
@@ -866,8 +866,12 @@ function ImportArriveQueueTab({
|
|||||||
*/
|
*/
|
||||||
function ImportUnloadedQueueTab({ enabled }: { enabled: boolean }) {
|
function ImportUnloadedQueueTab({ enabled }: { enabled: boolean }) {
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
const { data: rows = [], isLoading } = useImportUnloadedQueue(enabled);
|
const { data: rows = [], isLoading } = useQuery(
|
||||||
const inspectMutation = useBulkMarkInspected();
|
api.warehouses.importUnloadedQueue.queryOptions({ enabled }),
|
||||||
|
);
|
||||||
|
const inspectMutation = useMutation(
|
||||||
|
api.warehouses.bulkMarkInspected.mutationOptions(),
|
||||||
|
);
|
||||||
const [selected, setSelected] = useState<Set<string>>(new Set());
|
const [selected, setSelected] = useState<Set<string>>(new Set());
|
||||||
const [inspectId, setInspectId] = useState<string | null>(null);
|
const [inspectId, setInspectId] = useState<string | null>(null);
|
||||||
|
|
||||||
@@ -888,10 +892,7 @@ function ImportUnloadedQueueTab({ enabled }: { enabled: boolean }) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const res = (await inspectMutation.mutateAsync({ inventoryIds: [...selected] })) as {
|
const r = await inspectMutation.mutateAsync({ inventoryIds: [...selected] });
|
||||||
data: BulkInspectResult;
|
|
||||||
};
|
|
||||||
const r = res.data;
|
|
||||||
toast({
|
toast({
|
||||||
title: `${r.inspectedCount} marked inspected`,
|
title: `${r.inspectedCount} marked inspected`,
|
||||||
description: r.skippedCount ? `${r.skippedCount} skipped` : undefined,
|
description: r.skippedCount ? `${r.skippedCount} skipped` : undefined,
|
||||||
@@ -1033,8 +1034,10 @@ function ImportUnloadedQueueTab({ enabled }: { enabled: boolean }) {
|
|||||||
*/
|
*/
|
||||||
function ImportDispatchQueueTab({ enabled }: { enabled: boolean }) {
|
function ImportDispatchQueueTab({ enabled }: { enabled: boolean }) {
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
const { data: items = [], isLoading } = useWarehouseInventory(
|
const { data: items = [], isLoading } = useQuery(
|
||||||
enabled ? { status: 'READY_FOR_PICKUP' } : undefined,
|
api.warehouses.listInventory.queryOptions({
|
||||||
|
input: { filter: enabled ? { status: 'READY_FOR_PICKUP' } : undefined },
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -1155,7 +1158,9 @@ function SingleBookingReceiveModal({
|
|||||||
onReceived,
|
onReceived,
|
||||||
}: ReceiveInventoryModalProps) {
|
}: ReceiveInventoryModalProps) {
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
const receiveMutation = useReceiveInventory();
|
const receiveMutation = useMutation(
|
||||||
|
api.warehouses.receiveInventory.mutationOptions(),
|
||||||
|
);
|
||||||
const [selectedBooking, setSelectedBooking] = useState(bookingId ?? '');
|
const [selectedBooking, setSelectedBooking] = useState(bookingId ?? '');
|
||||||
const [form, setForm] = useState<SingleFormState>({
|
const [form, setForm] = useState<SingleFormState>({
|
||||||
warehouseId: '',
|
warehouseId: '',
|
||||||
|
|||||||
@@ -2,8 +2,10 @@ import { useEffect, useState } from 'react';
|
|||||||
import { Alert, Button, Group, Modal, Stack, Text, TextInput } from '@mantine/core';
|
import { Alert, Button, Group, Modal, Stack, Text, TextInput } from '@mantine/core';
|
||||||
import { Info } from 'lucide-react';
|
import { Info } from 'lucide-react';
|
||||||
|
|
||||||
|
import { useMutation } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import { api } from '@/services/api';
|
||||||
import { useToast } from '@/hooks/use-toast';
|
import { useToast } from '@/hooks/use-toast';
|
||||||
import { useReleaseInventory } from '@/hooks/useWarehouses';
|
|
||||||
import type { WarehouseInventoryItem } from '@/types/warehouse';
|
import type { WarehouseInventoryItem } from '@/types/warehouse';
|
||||||
import { extractErrorMessage } from './options';
|
import { extractErrorMessage } from './options';
|
||||||
|
|
||||||
@@ -15,7 +17,7 @@ interface ReleaseOrderModalProps {
|
|||||||
|
|
||||||
export function ReleaseOrderModal({ opened, onClose, item }: ReleaseOrderModalProps) {
|
export function ReleaseOrderModal({ opened, onClose, item }: ReleaseOrderModalProps) {
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
const releaseMutation = useReleaseInventory();
|
const releaseMutation = useMutation(api.warehouses.release.mutationOptions());
|
||||||
const [reference, setReference] = useState('');
|
const [reference, setReference] = useState('');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -2,8 +2,10 @@ import { useEffect, useState } from 'react';
|
|||||||
import { Alert, Button, Group, Modal, Stack, Text } from '@mantine/core';
|
import { Alert, Button, Group, Modal, Stack, Text } from '@mantine/core';
|
||||||
import { Info } from 'lucide-react';
|
import { Info } from 'lucide-react';
|
||||||
|
|
||||||
|
import { useMutation } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import { api } from '@/services/api';
|
||||||
import { useToast } from '@/hooks/use-toast';
|
import { useToast } from '@/hooks/use-toast';
|
||||||
import { useReserveInventory } from '@/hooks/useWarehouses';
|
|
||||||
import type { WarehouseInventoryItem } from '@/types/warehouse';
|
import type { WarehouseInventoryItem } from '@/types/warehouse';
|
||||||
import { BookingSelect } from './BookingSelect';
|
import { BookingSelect } from './BookingSelect';
|
||||||
import { extractErrorMessage } from './options';
|
import { extractErrorMessage } from './options';
|
||||||
@@ -16,7 +18,7 @@ interface ReserveInventoryModalProps {
|
|||||||
|
|
||||||
export function ReserveInventoryModal({ opened, onClose, item }: ReserveInventoryModalProps) {
|
export function ReserveInventoryModal({ opened, onClose, item }: ReserveInventoryModalProps) {
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
const reserveMutation = useReserveInventory();
|
const reserveMutation = useMutation(api.warehouses.reserve.mutationOptions());
|
||||||
const [bookingId, setBookingId] = useState('');
|
const [bookingId, setBookingId] = useState('');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Select } from '@mantine/core';
|
import { Select } from '@mantine/core';
|
||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
|
||||||
import { useLoadableWagons } from '@/hooks/useWarehouses';
|
import { api } from '@/services/api';
|
||||||
|
|
||||||
interface WagonSelectProps {
|
interface WagonSelectProps {
|
||||||
value: string;
|
value: string;
|
||||||
@@ -11,7 +12,9 @@ interface WagonSelectProps {
|
|||||||
|
|
||||||
/** Searchable wagon picker. Lists wagons that are loadable (read-only from scheduling). */
|
/** Searchable wagon picker. Lists wagons that are loadable (read-only from scheduling). */
|
||||||
export function WagonSelect({ value, onChange, label = 'Wagon', required }: WagonSelectProps) {
|
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) => ({
|
const options = (data ?? []).map((w) => ({
|
||||||
value: w.id,
|
value: w.id,
|
||||||
|
|||||||
@@ -15,7 +15,9 @@ import {
|
|||||||
YAxis,
|
YAxis,
|
||||||
} from 'recharts';
|
} 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';
|
import type { WarehouseDashboard, WarehouseInventoryItem } from '@/types/warehouse';
|
||||||
|
|
||||||
interface WarehouseDashboardChartsProps {
|
interface WarehouseDashboardChartsProps {
|
||||||
@@ -38,7 +40,9 @@ type Granularity = 'week' | 'month' | 'year';
|
|||||||
|
|
||||||
export function WarehouseDashboardCharts({ data }: WarehouseDashboardChartsProps) {
|
export function WarehouseDashboardCharts({ data }: WarehouseDashboardChartsProps) {
|
||||||
const [granularity, setGranularity] = useState<Granularity>('month');
|
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) => ({
|
const statusData = STATUS_SERIES.map((s) => ({
|
||||||
name: s.label,
|
name: s.label,
|
||||||
|
|||||||
@@ -2,7 +2,9 @@ import { useState } from 'react';
|
|||||||
import { Badge, Button, Card, Divider, Group, Stack, Text } from '@mantine/core';
|
import { Badge, Button, Card, Divider, Group, Stack, Text } from '@mantine/core';
|
||||||
import { PackagePlus, Train as TrainIcon, Warehouse as WarehouseIcon } from 'lucide-react';
|
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 { InventoryStatusBadge } from './badges';
|
||||||
import { FreightVisual } from './FreightVisual';
|
import { FreightVisual } from './FreightVisual';
|
||||||
import { formatDate } from './options';
|
import { formatDate } from './options';
|
||||||
@@ -28,8 +30,15 @@ function Row({ label, value }: { label: string; value: React.ReactNode }) {
|
|||||||
|
|
||||||
export function WarehouseInfoCard({ bookingId, bookingReference }: WarehouseInfoCardProps) {
|
export function WarehouseInfoCard({ bookingId, bookingReference }: WarehouseInfoCardProps) {
|
||||||
const [modalOpen, setModalOpen] = useState(false);
|
const [modalOpen, setModalOpen] = useState(false);
|
||||||
const { data, isLoading } = useWarehouseInventory({ bookingId });
|
const { data, isLoading } = useQuery(
|
||||||
const { data: scheduleView } = useBookingSchedule(bookingId);
|
api.warehouses.listInventory.queryOptions({ input: { filter: { bookingId } } }),
|
||||||
|
);
|
||||||
|
const { data: scheduleView } = useQuery(
|
||||||
|
api.warehouses.bookingSchedule.queryOptions({
|
||||||
|
input: { bookingId },
|
||||||
|
enabled: Boolean(bookingId),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
const items = data ?? [];
|
const items = data ?? [];
|
||||||
const latest = items[0];
|
const latest = items[0];
|
||||||
|
|||||||
@@ -1,505 +0,0 @@
|
|||||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
|
||||||
|
|
||||||
import { warehouseService } from '@/services/warehouse.service';
|
|
||||||
import type {
|
|
||||||
InspectionReportPayload,
|
|
||||||
SaveAllocationRulePayload,
|
|
||||||
SaveFeeRulePayload,
|
|
||||||
WarehouseInvoiceFilter,
|
|
||||||
PayInvoicePayload,
|
|
||||||
InventoryFilter,
|
|
||||||
InventoryInquiryFilter,
|
|
||||||
LoadInventoryPayload,
|
|
||||||
MoveInventoryPayload,
|
|
||||||
ReceiveInventoryPayload,
|
|
||||||
ReleaseOrderPayload,
|
|
||||||
DeliverInventoryPayload,
|
|
||||||
BulkReceivePayload,
|
|
||||||
BulkInspectPayload,
|
|
||||||
ReserveInventoryPayload,
|
|
||||||
SaveWarehousePayload,
|
|
||||||
SaveYardPayload,
|
|
||||||
SaveZonePayload,
|
|
||||||
WarehouseFilter,
|
|
||||||
} from '@/types/warehouse';
|
|
||||||
|
|
||||||
export const warehouseKeys = {
|
|
||||||
all: ['warehouses'] as const,
|
|
||||||
list: (filter?: WarehouseFilter) => ['warehouses', 'list', filter ?? {}] as const,
|
|
||||||
detail: (id: string) => ['warehouses', 'detail', id] as const,
|
|
||||||
yards: (warehouseId: string) => ['warehouses', warehouseId, 'yards'] as const,
|
|
||||||
zones: (yardId: string) => ['warehouse-yards', yardId, 'zones'] as const,
|
|
||||||
inventory: (filter?: InventoryFilter) => ['warehouse-inventory', 'list', filter ?? {}] as const,
|
|
||||||
inquiry: (filter: InventoryInquiryFilter) => ['warehouse-inventory', 'inquiry', filter] as const,
|
|
||||||
};
|
|
||||||
|
|
||||||
// ── Warehouses ─────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
export function useWarehouses(filter?: WarehouseFilter) {
|
|
||||||
return useQuery({
|
|
||||||
queryKey: warehouseKeys.list(filter),
|
|
||||||
queryFn: () => warehouseService.list(filter).then((r) => r.data),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useWarehouse(id?: string) {
|
|
||||||
return useQuery({
|
|
||||||
queryKey: warehouseKeys.detail(id ?? ''),
|
|
||||||
queryFn: () => warehouseService.getById(id as string).then((r) => r.data),
|
|
||||||
enabled: Boolean(id),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useCreateWarehouse() {
|
|
||||||
const qc = useQueryClient();
|
|
||||||
return useMutation({
|
|
||||||
mutationFn: (payload: SaveWarehousePayload) => warehouseService.create(payload),
|
|
||||||
onSuccess: () => qc.invalidateQueries({ queryKey: warehouseKeys.all }),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useUpdateWarehouse() {
|
|
||||||
const qc = useQueryClient();
|
|
||||||
return useMutation({
|
|
||||||
mutationFn: ({ id, payload }: { id: string; payload: Partial<SaveWarehousePayload> }) =>
|
|
||||||
warehouseService.update(id, payload),
|
|
||||||
onSuccess: (_, { id }) => {
|
|
||||||
qc.invalidateQueries({ queryKey: warehouseKeys.all });
|
|
||||||
qc.invalidateQueries({ queryKey: warehouseKeys.detail(id) });
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Yards ────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
export function useWarehouseYards(warehouseId?: string) {
|
|
||||||
return useQuery({
|
|
||||||
queryKey: warehouseKeys.yards(warehouseId ?? ''),
|
|
||||||
queryFn: () => warehouseService.listYards(warehouseId as string).then((r) => r.data),
|
|
||||||
enabled: Boolean(warehouseId),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useCreateYard() {
|
|
||||||
const qc = useQueryClient();
|
|
||||||
return useMutation({
|
|
||||||
mutationFn: ({ warehouseId, payload }: { warehouseId: string; payload: SaveYardPayload }) =>
|
|
||||||
warehouseService.createYard(warehouseId, payload),
|
|
||||||
onSuccess: (_, { warehouseId }) => {
|
|
||||||
qc.invalidateQueries({ queryKey: warehouseKeys.yards(warehouseId) });
|
|
||||||
qc.invalidateQueries({ queryKey: warehouseKeys.detail(warehouseId) });
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useUpdateYard() {
|
|
||||||
const qc = useQueryClient();
|
|
||||||
return useMutation({
|
|
||||||
mutationFn: ({ id, payload }: { id: string; payload: Partial<SaveYardPayload> }) =>
|
|
||||||
warehouseService.updateYard(id, payload),
|
|
||||||
onSuccess: () => qc.invalidateQueries({ queryKey: warehouseKeys.all }),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Zones ──────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
export function useWarehouseZones(yardId?: string) {
|
|
||||||
return useQuery({
|
|
||||||
queryKey: warehouseKeys.zones(yardId ?? ''),
|
|
||||||
queryFn: () => warehouseService.listZones(yardId as string).then((r) => r.data),
|
|
||||||
enabled: Boolean(yardId),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useCreateZone() {
|
|
||||||
const qc = useQueryClient();
|
|
||||||
return useMutation({
|
|
||||||
mutationFn: ({ yardId, payload }: { yardId: string; payload: SaveZonePayload }) =>
|
|
||||||
warehouseService.createZone(yardId, payload),
|
|
||||||
onSuccess: (_, { yardId }) => qc.invalidateQueries({ queryKey: warehouseKeys.zones(yardId) }),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useUpdateZone() {
|
|
||||||
const qc = useQueryClient();
|
|
||||||
return useMutation({
|
|
||||||
mutationFn: ({ id, payload }: { id: string; payload: Partial<SaveZonePayload> }) =>
|
|
||||||
warehouseService.updateZone(id, payload),
|
|
||||||
onSuccess: () => qc.invalidateQueries({ queryKey: ['warehouse-yards'] }),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Inventory ──────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
export function useWarehouseInventory(filter?: InventoryFilter) {
|
|
||||||
return useQuery({
|
|
||||||
queryKey: warehouseKeys.inventory(filter),
|
|
||||||
queryFn: () => warehouseService.listInventory(filter).then((r) => r.data),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useReceiveInventory() {
|
|
||||||
const qc = useQueryClient();
|
|
||||||
return useMutation({
|
|
||||||
mutationFn: (payload: ReceiveInventoryPayload) => warehouseService.receiveInventory(payload),
|
|
||||||
onSuccess: () => {
|
|
||||||
qc.invalidateQueries({ queryKey: ['warehouse-inventory'] });
|
|
||||||
qc.invalidateQueries({ queryKey: warehouseKeys.all });
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function useInventoryMutation<TArgs>(fn: (args: TArgs) => Promise<unknown>) {
|
|
||||||
const qc = useQueryClient();
|
|
||||||
return useMutation({
|
|
||||||
mutationFn: fn,
|
|
||||||
onSuccess: () => {
|
|
||||||
qc.invalidateQueries({ queryKey: ['warehouse-inventory'] });
|
|
||||||
qc.invalidateQueries({ queryKey: ['warehouse-loadings'] });
|
|
||||||
qc.invalidateQueries({ queryKey: warehouseKeys.all });
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useStoreInventory = () => useInventoryMutation((id: string) => warehouseService.store(id));
|
|
||||||
export const useReserveInventory = () =>
|
|
||||||
useInventoryMutation((payload: ReserveInventoryPayload) => warehouseService.reserve(payload));
|
|
||||||
export const useMarkReadyForLoading = () =>
|
|
||||||
useInventoryMutation((id: string) => warehouseService.markReadyForLoading(id));
|
|
||||||
export const useLoadInventory = () =>
|
|
||||||
useInventoryMutation((args: { id: string; payload: LoadInventoryPayload }) =>
|
|
||||||
warehouseService.load(args.id, args.payload),
|
|
||||||
);
|
|
||||||
export const useDispatchInventory = () => useInventoryMutation((id: string) => warehouseService.dispatch(id));
|
|
||||||
export const useMoveInventory = () =>
|
|
||||||
useInventoryMutation((args: { id: string; payload: MoveInventoryPayload }) =>
|
|
||||||
warehouseService.move(args.id, args.payload),
|
|
||||||
);
|
|
||||||
|
|
||||||
// ── Import branch (READY_FOR_PICKUP → DELIVERED) ───────────────────────────
|
|
||||||
export const useMarkReadyForPickup = () =>
|
|
||||||
useInventoryMutation((id: string) => warehouseService.markReadyForPickup(id));
|
|
||||||
export const useReleaseInventory = () =>
|
|
||||||
useInventoryMutation((args: { id: string; payload: ReleaseOrderPayload }) =>
|
|
||||||
warehouseService.release(args.id, args.payload),
|
|
||||||
);
|
|
||||||
export const useDeliverInventory = () =>
|
|
||||||
useInventoryMutation((args: { id: string; payload: DeliverInventoryPayload }) =>
|
|
||||||
warehouseService.deliver(args.id, args.payload),
|
|
||||||
);
|
|
||||||
|
|
||||||
// ── Receive (Import/Export bulk) ───────────────────────────────────────────
|
|
||||||
/**
|
|
||||||
* All not-yet-received PAID bookings, classified IMPORT/EXPORT by route, in one call.
|
|
||||||
* Both Receive tabs share this single query (same key) — only one HTTP request fires —
|
|
||||||
* then filter client-side by direction.
|
|
||||||
*/
|
|
||||||
export function useEligibleBookings(enabled = true) {
|
|
||||||
return useQuery({
|
|
||||||
queryKey: ['warehouse-inventory', 'eligible-bookings'],
|
|
||||||
queryFn: () => warehouseService.eligibleBookings().then((r) => r.data),
|
|
||||||
enabled,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
export const useBulkReceive = () =>
|
|
||||||
useInventoryMutation((payload: BulkReceivePayload) => warehouseService.receiveBulk(payload));
|
|
||||||
export const useLoadPassedExport = () =>
|
|
||||||
useInventoryMutation(() => warehouseService.loadPassedExport());
|
|
||||||
export const useBulkMarkInspected = () =>
|
|
||||||
useInventoryMutation((payload: BulkInspectPayload) => warehouseService.bulkMarkInspected(payload));
|
|
||||||
|
|
||||||
export function useReadyToLoadExport(enabled = true) {
|
|
||||||
return useQuery({
|
|
||||||
queryKey: ['warehouse-inventory', 'ready-to-load-export'],
|
|
||||||
queryFn: () => warehouseService.readyToLoadExport().then((r) => r.data),
|
|
||||||
enabled,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useLoadedExport(enabled = true) {
|
|
||||||
return useQuery({
|
|
||||||
queryKey: ['warehouse-inventory', 'loaded-export'],
|
|
||||||
queryFn: () => warehouseService.loadedExport().then((r) => r.data),
|
|
||||||
enabled,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useBulkDispatchExport = () =>
|
|
||||||
useInventoryMutation((inventoryIds: string[]) => warehouseService.bulkDispatchExport(inventoryIds));
|
|
||||||
|
|
||||||
/** Arrived IMPORT trains (route-derived). Read-only. */
|
|
||||||
export function useImportArriveQueue(enabled = true) {
|
|
||||||
return useQuery({
|
|
||||||
queryKey: ['warehouse-inventory', 'import-arrive-queue'],
|
|
||||||
queryFn: () => warehouseService.importArriveQueue().then((r) => r.data),
|
|
||||||
enabled,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Assigned bookings/items for an arrived import train. Read-only. */
|
|
||||||
export function useImportTrainItems(scheduleId?: string) {
|
|
||||||
return useQuery({
|
|
||||||
queryKey: ['warehouse-inventory', 'import-train-items', scheduleId],
|
|
||||||
queryFn: () => warehouseService.importTrainItems(scheduleId as string).then((r) => r.data),
|
|
||||||
enabled: Boolean(scheduleId),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Unload all eligible assigned bookings of an ARRIVED import train (→ UNLOADED). */
|
|
||||||
export const useAutoUnloadArrivedBookings = () =>
|
|
||||||
useInventoryMutation((scheduleId: string) => warehouseService.autoUnloadArrivedBookings(scheduleId));
|
|
||||||
|
|
||||||
/** IMPORT inventory in the Unloaded Queue (UNLOADED / destination inspection). Read-only. */
|
|
||||||
export function useImportUnloadedQueue(enabled = true) {
|
|
||||||
return useQuery({
|
|
||||||
queryKey: ['warehouse-inventory', 'import-unloaded-queue'],
|
|
||||||
queryFn: () => warehouseService.importUnloadedQueue().then((r) => r.data),
|
|
||||||
enabled,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/** IMPORT inventory that is PICKUP_READY (READY_FOR_PICKUP) awaiting pickup/dispatch. Read-only. */
|
|
||||||
export function useImportPickupReadyQueue(enabled = true) {
|
|
||||||
return useQuery({
|
|
||||||
queryKey: ['warehouse-inventory', 'import-pickup-ready-queue'],
|
|
||||||
queryFn: () => warehouseService.importPickupReadyQueue().then((r) => r.data),
|
|
||||||
enabled,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Loading (Batch 3) ────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
export function useLoadableWagons(enabled = true) {
|
|
||||||
return useQuery({
|
|
||||||
queryKey: ['warehouse', 'loadable-wagons'],
|
|
||||||
queryFn: () => warehouseService.loadableWagons().then((r) => r.data),
|
|
||||||
enabled,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useWarehouseLoadings(params?: { bookingId?: string; wagonId?: string }) {
|
|
||||||
return useQuery({
|
|
||||||
queryKey: ['warehouse-loadings', params ?? {}],
|
|
||||||
queryFn: () => warehouseService.loadings(params).then((r) => r.data),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useBookingSchedule(bookingId?: string) {
|
|
||||||
return useQuery({
|
|
||||||
queryKey: ['warehouse', 'booking-schedule', bookingId ?? ''],
|
|
||||||
queryFn: () => warehouseService.bookingSchedule(bookingId as string).then((r) => r.data),
|
|
||||||
enabled: Boolean(bookingId),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useInventoryMovements(id?: string) {
|
|
||||||
return useQuery({
|
|
||||||
queryKey: ['warehouse-inventory', id, 'movements'],
|
|
||||||
queryFn: () => warehouseService.movements(id as string).then((r) => r.data),
|
|
||||||
enabled: Boolean(id),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useInventoryActivity(id?: string) {
|
|
||||||
return useQuery({
|
|
||||||
queryKey: ['warehouse-inventory', id, 'activity'],
|
|
||||||
queryFn: () => warehouseService.activity(id as string).then((r) => r.data),
|
|
||||||
enabled: Boolean(id),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useWarehouseDashboard() {
|
|
||||||
return useQuery({
|
|
||||||
queryKey: ['warehouses', 'dashboard'],
|
|
||||||
queryFn: () => warehouseService.dashboard().then((r) => r.data),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useInventoryInquiry(filter: InventoryInquiryFilter, enabled = true) {
|
|
||||||
return useQuery({
|
|
||||||
queryKey: warehouseKeys.inquiry(filter),
|
|
||||||
queryFn: () => warehouseService.inquiry(filter).then((r) => r.data),
|
|
||||||
enabled,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Batch 4.5: Arrival / Unload / Inspection ────────────────────────────────
|
|
||||||
|
|
||||||
export function useArrivalQueue() {
|
|
||||||
return useQuery({
|
|
||||||
queryKey: ['warehouse-inventory', 'arrival-queue'],
|
|
||||||
queryFn: () => warehouseService.arrivalQueue().then((r) => r.data),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function useArrivalInvalidation() {
|
|
||||||
const qc = useQueryClient();
|
|
||||||
return () => {
|
|
||||||
qc.invalidateQueries({ queryKey: ['warehouse-inventory'] });
|
|
||||||
qc.invalidateQueries({ queryKey: warehouseKeys.all });
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useAutoUnloadArrived() {
|
|
||||||
const onSuccess = useArrivalInvalidation();
|
|
||||||
return useMutation({ mutationFn: () => warehouseService.autoUnloadArrived(), onSuccess });
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useAutoLoadReady() {
|
|
||||||
const onSuccess = useArrivalInvalidation();
|
|
||||||
return useMutation({ mutationFn: () => warehouseService.autoLoadReady(), onSuccess });
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useUnloadBooking() {
|
|
||||||
const onSuccess = useArrivalInvalidation();
|
|
||||||
return useMutation({
|
|
||||||
mutationFn: (args: { bookingId: string; payload?: Record<string, unknown> }) =>
|
|
||||||
warehouseService.unloadBooking(args.bookingId, args.payload),
|
|
||||||
onSuccess,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useInspectionReports(inventoryId?: string) {
|
|
||||||
return useQuery({
|
|
||||||
queryKey: ['warehouse-inventory', inventoryId, 'inspection-reports'],
|
|
||||||
queryFn: () => warehouseService.listInspectionReports(inventoryId as string).then((r) => r.data),
|
|
||||||
enabled: Boolean(inventoryId),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useCreateInspectionReport() {
|
|
||||||
const qc = useQueryClient();
|
|
||||||
return useMutation({
|
|
||||||
mutationFn: ({ inventoryId, payload }: { inventoryId: string; payload: InspectionReportPayload }) =>
|
|
||||||
warehouseService.createInspectionReport(inventoryId, payload).then((r) => r.data),
|
|
||||||
onSuccess: (_, { inventoryId }) => {
|
|
||||||
qc.invalidateQueries({ queryKey: ['warehouse-inventory', inventoryId, 'inspection-reports'] });
|
|
||||||
qc.invalidateQueries({ queryKey: ['warehouse-inventory'] });
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useUploadInspectionAttachments() {
|
|
||||||
return useMutation({
|
|
||||||
mutationFn: ({ reportId, files }: { reportId: string; files: File[] }) =>
|
|
||||||
warehouseService.uploadInspectionAttachments(reportId, files),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Batch 5: Allocation + Fee rules / preview ───────────────────────────────
|
|
||||||
|
|
||||||
export function useAllocationRules() {
|
|
||||||
return useQuery({
|
|
||||||
queryKey: ['warehouse-allocation-rules'],
|
|
||||||
queryFn: () => warehouseService.listAllocationRules().then((r) => r.data),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useFeeRules() {
|
|
||||||
return useQuery({
|
|
||||||
queryKey: ['warehouse-fee-rules'],
|
|
||||||
queryFn: () => warehouseService.listFeeRules().then((r) => r.data),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function useRuleMutation<TArgs>(fn: (args: TArgs) => Promise<unknown>, keys: string[]) {
|
|
||||||
const qc = useQueryClient();
|
|
||||||
return useMutation({
|
|
||||||
mutationFn: fn,
|
|
||||||
onSuccess: () => keys.forEach((k) => qc.invalidateQueries({ queryKey: [k] })),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useCreateAllocationRule = () =>
|
|
||||||
useRuleMutation(
|
|
||||||
(payload: SaveAllocationRulePayload) => warehouseService.createAllocationRule(payload),
|
|
||||||
['warehouse-allocation-rules'],
|
|
||||||
);
|
|
||||||
export const useUpdateAllocationRule = () =>
|
|
||||||
useRuleMutation(
|
|
||||||
(args: { id: string; payload: Partial<SaveAllocationRulePayload> }) =>
|
|
||||||
warehouseService.updateAllocationRule(args.id, args.payload),
|
|
||||||
['warehouse-allocation-rules'],
|
|
||||||
);
|
|
||||||
export const useDeleteAllocationRule = () =>
|
|
||||||
useRuleMutation((id: string) => warehouseService.deleteAllocationRule(id), ['warehouse-allocation-rules']);
|
|
||||||
|
|
||||||
export const useCreateFeeRule = () =>
|
|
||||||
useRuleMutation((payload: SaveFeeRulePayload) => warehouseService.createFeeRule(payload), ['warehouse-fee-rules']);
|
|
||||||
export const useUpdateFeeRule = () =>
|
|
||||||
useRuleMutation(
|
|
||||||
(args: { id: string; payload: Partial<SaveFeeRulePayload> }) =>
|
|
||||||
warehouseService.updateFeeRule(args.id, args.payload),
|
|
||||||
['warehouse-fee-rules'],
|
|
||||||
);
|
|
||||||
export const useDeleteFeeRule = () =>
|
|
||||||
useRuleMutation((id: string) => warehouseService.deleteFeeRule(id), ['warehouse-fee-rules']);
|
|
||||||
|
|
||||||
export function useFeePreview(inventoryId?: string) {
|
|
||||||
return useQuery({
|
|
||||||
queryKey: ['warehouse-inventory', inventoryId, 'fee-preview'],
|
|
||||||
queryFn: () => warehouseService.feePreview(inventoryId as string).then((r) => r.data),
|
|
||||||
enabled: Boolean(inventoryId),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Batch 6: Warehouse fee invoices ─────────────────────────────────────────
|
|
||||||
|
|
||||||
export function useWarehouseInvoices(filter?: WarehouseInvoiceFilter) {
|
|
||||||
return useQuery({
|
|
||||||
queryKey: ['warehouse-fee-invoices', filter ?? {}],
|
|
||||||
queryFn: () => warehouseService.listInvoices(filter).then((r) => r.data),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useWarehouseInvoice(id?: string) {
|
|
||||||
return useQuery({
|
|
||||||
queryKey: ['warehouse-fee-invoices', 'detail', id],
|
|
||||||
queryFn: () => warehouseService.getInvoice(id as string).then((r) => r.data),
|
|
||||||
enabled: Boolean(id),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useInvoicesForInventory(inventoryId?: string) {
|
|
||||||
return useQuery({
|
|
||||||
queryKey: ['warehouse-inventory', inventoryId, 'fee-invoices'],
|
|
||||||
queryFn: () => warehouseService.invoicesForInventory(inventoryId as string).then((r) => r.data),
|
|
||||||
enabled: Boolean(inventoryId),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function useInvoiceInvalidation() {
|
|
||||||
const qc = useQueryClient();
|
|
||||||
return () => {
|
|
||||||
qc.invalidateQueries({ queryKey: ['warehouse-fee-invoices'] });
|
|
||||||
qc.invalidateQueries({ queryKey: ['warehouse-inventory'] });
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useGenerateInvoice() {
|
|
||||||
const onSuccess = useInvoiceInvalidation();
|
|
||||||
return useMutation({
|
|
||||||
mutationFn: ({ inventoryId, confirmZero }: { inventoryId: string; confirmZero?: boolean }) =>
|
|
||||||
warehouseService.generateInvoice(inventoryId, confirmZero).then((r) => r.data),
|
|
||||||
onSuccess,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useCancelInvoice() {
|
|
||||||
const onSuccess = useInvoiceInvalidation();
|
|
||||||
return useMutation({ mutationFn: (id: string) => warehouseService.cancelInvoice(id), onSuccess });
|
|
||||||
}
|
|
||||||
|
|
||||||
export function usePayInvoice() {
|
|
||||||
const onSuccess = useInvoiceInvalidation();
|
|
||||||
return useMutation({
|
|
||||||
mutationFn: ({ id, payload }: { id: string; payload: PayInvoicePayload }) =>
|
|
||||||
warehouseService.payInvoice(id, payload),
|
|
||||||
onSuccess,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useGateClearance() {
|
|
||||||
const onSuccess = useInvoiceInvalidation();
|
|
||||||
return useMutation({ mutationFn: (inventoryId: string) => warehouseService.gateClearance(inventoryId), onSuccess });
|
|
||||||
}
|
|
||||||
@@ -16,7 +16,9 @@ import {
|
|||||||
VisualEmptyState,
|
VisualEmptyState,
|
||||||
formatDate,
|
formatDate,
|
||||||
} from '@/components/warehouses';
|
} from '@/components/warehouses';
|
||||||
import { useArrivalQueue, useAutoUnloadArrived, useUnloadBooking } from '@/hooks/useWarehouses';
|
import { useMutation, useQuery } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import { api } from '@/services/api';
|
||||||
import { useToast } from '@/hooks/use-toast';
|
import { useToast } from '@/hooks/use-toast';
|
||||||
import type { ArrivalQueueItem } from '@/types/warehouse';
|
import type { ArrivalQueueItem } from '@/types/warehouse';
|
||||||
|
|
||||||
@@ -30,17 +32,16 @@ function inspectionBadge(status: string | null) {
|
|||||||
export default function ArrivalQueuePage() {
|
export default function ArrivalQueuePage() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
const { data, isLoading } = useArrivalQueue();
|
const { data, isLoading } = useQuery(api.warehouses.arrivalQueue.queryOptions());
|
||||||
const autoUnload = useAutoUnloadArrived();
|
const autoUnload = useMutation(api.warehouses.autoUnloadArrived.mutationOptions());
|
||||||
const unloadOne = useUnloadBooking();
|
const unloadOne = useMutation(api.warehouses.unloadBooking.mutationOptions());
|
||||||
const [inspectInventoryId, setInspectInventoryId] = useState<string | null>(null);
|
const [inspectInventoryId, setInspectInventoryId] = useState<string | null>(null);
|
||||||
|
|
||||||
const items = data ?? [];
|
const items = data ?? [];
|
||||||
|
|
||||||
const handleAutoUnload = async () => {
|
const handleAutoUnload = async () => {
|
||||||
try {
|
try {
|
||||||
const res = await autoUnload.mutateAsync();
|
const r = await autoUnload.mutateAsync();
|
||||||
const r = res.data;
|
|
||||||
toast({
|
toast({
|
||||||
title: 'Auto-unload complete',
|
title: 'Auto-unload complete',
|
||||||
description: `Processed ${r.processedCount}, skipped ${r.skippedCount}, failed ${r.failedCount}.`,
|
description: `Processed ${r.processedCount}, skipped ${r.skippedCount}, failed ${r.failedCount}.`,
|
||||||
|
|||||||
@@ -1,12 +1,16 @@
|
|||||||
import { Card } from '@mantine/core';
|
import { Card } from '@mantine/core';
|
||||||
|
|
||||||
import { PageContainer, PageHeader } from '@/components/page';
|
import { PageContainer, PageHeader } from '@/components/page';
|
||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
|
||||||
import { InventoryWorkbench, VisualEmptyState } from '@/components/warehouses';
|
import { InventoryWorkbench, VisualEmptyState } from '@/components/warehouses';
|
||||||
import { useWarehouseInventory } from '@/hooks/useWarehouses';
|
import { api } from '@/services/api';
|
||||||
|
|
||||||
/** Items that are LOADED and awaiting dispatch (train departure). */
|
/** Items that are LOADED and awaiting dispatch (train departure). */
|
||||||
export default function DispatchQueuePage() {
|
export default function DispatchQueuePage() {
|
||||||
const { data, isLoading } = useWarehouseInventory({ status: 'LOADED' });
|
const { data, isLoading } = useQuery(
|
||||||
|
api.warehouses.listInventory.queryOptions({ input: { filter: { status: 'LOADED' } } }),
|
||||||
|
);
|
||||||
const items = data ?? [];
|
const items = data ?? [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -3,24 +3,35 @@ import { Button, Card, Center, Group, Loader, Select, Stack, TextInput } from '@
|
|||||||
import { Search } from 'lucide-react';
|
import { Search } from 'lucide-react';
|
||||||
|
|
||||||
import { PageContainer, PageHeader } from '@/components/page';
|
import { PageContainer, PageHeader } from '@/components/page';
|
||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
|
||||||
import { VisualEmptyState, WarehouseInquiryTable, inventoryStatusOptions } from '@/components/warehouses';
|
import { VisualEmptyState, WarehouseInquiryTable, inventoryStatusOptions } from '@/components/warehouses';
|
||||||
import {
|
import { api } from '@/services/api';
|
||||||
useInventoryInquiry,
|
|
||||||
useWarehouseYards,
|
|
||||||
useWarehouseZones,
|
|
||||||
useWarehouses,
|
|
||||||
} from '@/hooks/useWarehouses';
|
|
||||||
import type { InventoryInquiryFilter, InventoryStatus } from '@/types/warehouse';
|
import type { InventoryInquiryFilter, InventoryStatus } from '@/types/warehouse';
|
||||||
|
|
||||||
export default function InventoryInquiryPage() {
|
export default function InventoryInquiryPage() {
|
||||||
const [draft, setDraft] = useState<InventoryInquiryFilter>({});
|
const [draft, setDraft] = useState<InventoryInquiryFilter>({});
|
||||||
const [applied, setApplied] = useState<InventoryInquiryFilter>({});
|
const [applied, setApplied] = useState<InventoryInquiryFilter>({});
|
||||||
|
|
||||||
const warehousesQuery = useWarehouses();
|
const warehousesQuery = useQuery(
|
||||||
const yardsQuery = useWarehouseYards(draft.warehouseId);
|
api.warehouses.list.queryOptions({ input: {} }),
|
||||||
const zonesQuery = useWarehouseZones(draft.yardId);
|
);
|
||||||
|
const yardsQuery = useQuery(
|
||||||
|
api.warehouses.listYards.queryOptions({
|
||||||
|
input: { warehouseId: draft.warehouseId ?? '' },
|
||||||
|
enabled: Boolean(draft.warehouseId),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
const zonesQuery = useQuery(
|
||||||
|
api.warehouses.listZones.queryOptions({
|
||||||
|
input: { yardId: draft.yardId ?? '' },
|
||||||
|
enabled: Boolean(draft.yardId),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
const { data, isFetching } = useInventoryInquiry(applied);
|
const { data, isFetching } = useQuery(
|
||||||
|
api.warehouses.inquiry.queryOptions({ input: { filter: applied } }),
|
||||||
|
);
|
||||||
const results = data ?? [];
|
const results = data ?? [];
|
||||||
|
|
||||||
const warehouseOptions = useMemo(
|
const warehouseOptions = useMemo(
|
||||||
|
|||||||
@@ -2,10 +2,13 @@ import { Badge, Card, Group, Text } from '@mantine/core';
|
|||||||
import { DataTable, type ColumnDef } from '@edr/ui-common';
|
import { DataTable, type ColumnDef } from '@edr/ui-common';
|
||||||
|
|
||||||
import { PageContainer, PageHeader } from '@/components/page';
|
import { PageContainer, PageHeader } from '@/components/page';
|
||||||
import { FreightVisual, VisualEmptyState, formatDate, formatNumber } from '@/components/warehouses';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { useWarehouseLoadings } from '@/hooks/useWarehouses';
|
|
||||||
|
|
||||||
type Loading = NonNullable<ReturnType<typeof useWarehouseLoadings>['data']>[number];
|
import { FreightVisual, VisualEmptyState, formatDate, formatNumber } from '@/components/warehouses';
|
||||||
|
import { api } from '@/services/api';
|
||||||
|
import type { WarehouseLoading } from '@/types/warehouse';
|
||||||
|
|
||||||
|
type Loading = WarehouseLoading;
|
||||||
|
|
||||||
const columns: ColumnDef<Loading>[] = [
|
const columns: ColumnDef<Loading>[] = [
|
||||||
{
|
{
|
||||||
@@ -60,7 +63,9 @@ const columns: ColumnDef<Loading>[] = [
|
|||||||
|
|
||||||
/** Record of every inventory item loaded onto a wagon. */
|
/** Record of every inventory item loaded onto a wagon. */
|
||||||
export default function LoadedInventoryPage() {
|
export default function LoadedInventoryPage() {
|
||||||
const { data, isLoading } = useWarehouseLoadings();
|
const { data, isLoading } = useQuery(
|
||||||
|
api.warehouses.loadings.queryOptions({ input: {} }),
|
||||||
|
);
|
||||||
const loadings = data ?? [];
|
const loadings = data ?? [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -10,7 +10,9 @@ import {
|
|||||||
VisualEmptyState,
|
VisualEmptyState,
|
||||||
formatNumber,
|
formatNumber,
|
||||||
} from '@/components/warehouses';
|
} from '@/components/warehouses';
|
||||||
import { useAutoLoadReady, useWarehouseInventory } from '@/hooks/useWarehouses';
|
import { useMutation, useQuery } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import { api } from '@/services/api';
|
||||||
import { useToast } from '@/hooks/use-toast';
|
import { useToast } from '@/hooks/use-toast';
|
||||||
import type { WarehouseInventoryItem } from '@/types/warehouse';
|
import type { WarehouseInventoryItem } from '@/types/warehouse';
|
||||||
|
|
||||||
@@ -27,16 +29,19 @@ const isPaid = (item: WarehouseInventoryItem) => item.booking?.status === 'PAID'
|
|||||||
export default function LoadingQueuePage() {
|
export default function LoadingQueuePage() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
const autoLoad = useAutoLoadReady();
|
const autoLoad = useMutation(api.warehouses.autoLoadReady.mutationOptions());
|
||||||
const { data: readyData, isLoading: readyLoading } = useWarehouseInventory({
|
const { data: readyData, isLoading: readyLoading } = useQuery(
|
||||||
status: 'READY_FOR_LOADING',
|
api.warehouses.listInventory.queryOptions({
|
||||||
});
|
input: { filter: { status: 'READY_FOR_LOADING' } },
|
||||||
const { data: loadedData, isLoading: loadedLoading } = useWarehouseInventory({ status: 'LOADED' });
|
}),
|
||||||
|
);
|
||||||
|
const { data: loadedData, isLoading: loadedLoading } = useQuery(
|
||||||
|
api.warehouses.listInventory.queryOptions({ input: { filter: { status: 'LOADED' } } }),
|
||||||
|
);
|
||||||
|
|
||||||
const handleAutoLoad = async () => {
|
const handleAutoLoad = async () => {
|
||||||
try {
|
try {
|
||||||
const res = await autoLoad.mutateAsync();
|
const r = await autoLoad.mutateAsync();
|
||||||
const r = res.data;
|
|
||||||
toast({
|
toast({
|
||||||
title: 'Auto-load complete',
|
title: 'Auto-load complete',
|
||||||
description: `Loaded ${r.loadedCount} PAID item(s); skipped ${r.skippedCount} (unpaid stay pending).`,
|
description: `Loaded ${r.loadedCount} PAID item(s); skipped ${r.skippedCount} (unpaid stay pending).`,
|
||||||
|
|||||||
@@ -16,8 +16,10 @@ import {
|
|||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
|
|
||||||
import { PageContainer, PageHeader } from '@/components/page';
|
import { PageContainer, PageHeader } from '@/components/page';
|
||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
|
||||||
import { WarehouseDashboardCharts } from '@/components/warehouses';
|
import { WarehouseDashboardCharts } from '@/components/warehouses';
|
||||||
import { useWarehouseDashboard } from '@/hooks/useWarehouses';
|
import { api } from '@/services/api';
|
||||||
import type { WarehouseDashboard } from '@/types/warehouse';
|
import type { WarehouseDashboard } from '@/types/warehouse';
|
||||||
|
|
||||||
interface Metric {
|
interface Metric {
|
||||||
@@ -49,7 +51,7 @@ const METRICS: Metric[] = [
|
|||||||
|
|
||||||
export default function WarehouseDashboardPage() {
|
export default function WarehouseDashboardPage() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { data, isLoading } = useWarehouseDashboard();
|
const { data, isLoading } = useQuery(api.warehouses.dashboard.queryOptions());
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageContainer>
|
<PageContainer>
|
||||||
|
|||||||
@@ -27,20 +27,27 @@ import {
|
|||||||
formatCapacity,
|
formatCapacity,
|
||||||
humanizeEnum,
|
humanizeEnum,
|
||||||
} from '@/components/warehouses';
|
} from '@/components/warehouses';
|
||||||
import {
|
import { useQuery } from '@tanstack/react-query';
|
||||||
useWarehouse,
|
|
||||||
useWarehouseInventory,
|
import { api } from '@/services/api';
|
||||||
useWarehouseYards,
|
|
||||||
useWarehouseZones,
|
|
||||||
} from '@/hooks/useWarehouses';
|
|
||||||
import type { WarehouseYard, WarehouseZone } from '@/types/warehouse';
|
import type { WarehouseYard, WarehouseZone } from '@/types/warehouse';
|
||||||
|
|
||||||
export default function WarehouseDetailPage() {
|
export default function WarehouseDetailPage() {
|
||||||
const { id } = useParams<{ id: string }>();
|
const { id } = useParams<{ id: string }>();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const { data: warehouse, isLoading } = useWarehouse(id);
|
const { data: warehouse, isLoading } = useQuery(
|
||||||
const yardsQuery = useWarehouseYards(id);
|
api.warehouses.getById.queryOptions({
|
||||||
|
input: { id: id ?? '' },
|
||||||
|
enabled: Boolean(id),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
const yardsQuery = useQuery(
|
||||||
|
api.warehouses.listYards.queryOptions({
|
||||||
|
input: { warehouseId: id ?? '' },
|
||||||
|
enabled: Boolean(id),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
const [yardModalOpen, setYardModalOpen] = useState(false);
|
const [yardModalOpen, setYardModalOpen] = useState(false);
|
||||||
const [editingYard, setEditingYard] = useState<WarehouseYard | null>(null);
|
const [editingYard, setEditingYard] = useState<WarehouseYard | null>(null);
|
||||||
@@ -49,9 +56,18 @@ export default function WarehouseDetailPage() {
|
|||||||
const [editingZone, setEditingZone] = useState<WarehouseZone | null>(null);
|
const [editingZone, setEditingZone] = useState<WarehouseZone | null>(null);
|
||||||
const [selectedYardId, setSelectedYardId] = useState<string | null>(null);
|
const [selectedYardId, setSelectedYardId] = useState<string | null>(null);
|
||||||
|
|
||||||
const zonesQuery = useWarehouseZones(selectedYardId ?? undefined);
|
const zonesQuery = useQuery(
|
||||||
|
api.warehouses.listZones.queryOptions({
|
||||||
|
input: { yardId: selectedYardId ?? '' },
|
||||||
|
enabled: Boolean(selectedYardId),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
const inventoryQuery = useWarehouseInventory(id ? { warehouseId: id } : undefined);
|
const inventoryQuery = useQuery(
|
||||||
|
api.warehouses.listInventory.queryOptions({
|
||||||
|
input: { filter: id ? { warehouseId: id } : undefined },
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
const yards = yardsQuery.data ?? [];
|
const yards = yardsQuery.data ?? [];
|
||||||
const yardOptions = useMemo(
|
const yardOptions = useMemo(
|
||||||
|
|||||||
@@ -10,12 +10,9 @@ import {
|
|||||||
ReceiveInventoryModal,
|
ReceiveInventoryModal,
|
||||||
inventoryStatusOptions,
|
inventoryStatusOptions,
|
||||||
} from '@/components/warehouses';
|
} from '@/components/warehouses';
|
||||||
import {
|
import { useQuery } from '@tanstack/react-query';
|
||||||
useWarehouseInventory,
|
|
||||||
useWarehouseYards,
|
import { api } from '@/services/api';
|
||||||
useWarehouseZones,
|
|
||||||
useWarehouses,
|
|
||||||
} from '@/hooks/useWarehouses';
|
|
||||||
import type { InventoryFilter, InventoryStatus } from '@/types/warehouse';
|
import type { InventoryFilter, InventoryStatus } from '@/types/warehouse';
|
||||||
|
|
||||||
export default function WarehouseInventoryPage() {
|
export default function WarehouseInventoryPage() {
|
||||||
@@ -33,10 +30,24 @@ export default function WarehouseInventoryPage() {
|
|||||||
[filter, debouncedSearch],
|
[filter, debouncedSearch],
|
||||||
);
|
);
|
||||||
|
|
||||||
const warehousesQuery = useWarehouses();
|
const warehousesQuery = useQuery(
|
||||||
const yardsQuery = useWarehouseYards(filter.warehouseId);
|
api.warehouses.list.queryOptions({ input: {} }),
|
||||||
const zonesQuery = useWarehouseZones(filter.yardId);
|
);
|
||||||
const inventoryQuery = useWarehouseInventory(queryFilter);
|
const yardsQuery = useQuery(
|
||||||
|
api.warehouses.listYards.queryOptions({
|
||||||
|
input: { warehouseId: filter.warehouseId ?? '' },
|
||||||
|
enabled: Boolean(filter.warehouseId),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
const zonesQuery = useQuery(
|
||||||
|
api.warehouses.listZones.queryOptions({
|
||||||
|
input: { yardId: filter.yardId ?? '' },
|
||||||
|
enabled: Boolean(filter.yardId),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
const inventoryQuery = useQuery(
|
||||||
|
api.warehouses.listInventory.queryOptions({ input: { filter: queryFilter } }),
|
||||||
|
);
|
||||||
|
|
||||||
const warehouseOptions = useMemo(
|
const warehouseOptions = useMemo(
|
||||||
() => (warehousesQuery.data ?? []).map((w) => ({ value: w.id, label: `${w.name} (${w.code})` })),
|
() => (warehousesQuery.data ?? []).map((w) => ({ value: w.id, label: `${w.name} (${w.code})` })),
|
||||||
|
|||||||
@@ -19,13 +19,10 @@ import { Ban, CreditCard, Eye, Search } from 'lucide-react';
|
|||||||
import { DataTable, type ColumnDef } from '@edr/ui-common';
|
import { DataTable, type ColumnDef } from '@edr/ui-common';
|
||||||
|
|
||||||
import { PageContainer, PageHeader } from '@/components/page';
|
import { PageContainer, PageHeader } from '@/components/page';
|
||||||
|
import { useMutation, useQuery } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import { api } from '@/services/api';
|
||||||
import { useToast } from '@/hooks/use-toast';
|
import { useToast } from '@/hooks/use-toast';
|
||||||
import {
|
|
||||||
useCancelInvoice,
|
|
||||||
usePayInvoice,
|
|
||||||
useWarehouseInvoice,
|
|
||||||
useWarehouseInvoices,
|
|
||||||
} from '@/hooks/useWarehouses';
|
|
||||||
import {
|
import {
|
||||||
WAREHOUSE_INVOICE_STATUSES,
|
WAREHOUSE_INVOICE_STATUSES,
|
||||||
type WarehouseFeeInvoice,
|
type WarehouseFeeInvoice,
|
||||||
@@ -48,7 +45,11 @@ export default function WarehouseInvoicesPage() {
|
|||||||
const [search, setSearch] = useState('');
|
const [search, setSearch] = useState('');
|
||||||
const [detailId, setDetailId] = useState<string | null>(null);
|
const [detailId, setDetailId] = useState<string | null>(null);
|
||||||
|
|
||||||
const { data, isLoading } = useWarehouseInvoices(status ? { status } : undefined);
|
const { data, isLoading } = useQuery(
|
||||||
|
api.warehouses.invoices.queryOptions({
|
||||||
|
input: { filter: status ? { status } : undefined },
|
||||||
|
}),
|
||||||
|
);
|
||||||
const invoices = data ?? [];
|
const invoices = data ?? [];
|
||||||
|
|
||||||
const filtered = useMemo(() => {
|
const filtered = useMemo(() => {
|
||||||
@@ -149,9 +150,14 @@ export default function WarehouseInvoicesPage() {
|
|||||||
|
|
||||||
function InvoiceDetailModal({ id, onClose }: { id: string | null; onClose: () => void }) {
|
function InvoiceDetailModal({ id, onClose }: { id: string | null; onClose: () => void }) {
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
const { data: inv, isLoading } = useWarehouseInvoice(id ?? undefined);
|
const { data: inv, isLoading } = useQuery(
|
||||||
const pay = usePayInvoice();
|
api.warehouses.invoice.queryOptions({
|
||||||
const cancel = useCancelInvoice();
|
input: { id: id ?? '' },
|
||||||
|
enabled: Boolean(id),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
const pay = useMutation(api.warehouses.payInvoice.mutationOptions());
|
||||||
|
const cancel = useMutation(api.warehouses.cancelInvoice.mutationOptions());
|
||||||
const [payAmount, setPayAmount] = useState<number | ''>('');
|
const [payAmount, setPayAmount] = useState<number | ''>('');
|
||||||
|
|
||||||
const canPay = inv && (inv.status === 'ISSUED' || inv.status === 'PARTIALLY_PAID');
|
const canPay = inv && (inv.status === 'ISSUED' || inv.status === 'PARTIALLY_PAID');
|
||||||
|
|||||||
@@ -12,7 +12,9 @@ import {
|
|||||||
WarehouseTable,
|
WarehouseTable,
|
||||||
type WarehouseView,
|
type WarehouseView,
|
||||||
} from '@/components/warehouses';
|
} from '@/components/warehouses';
|
||||||
import { useWarehouses } from '@/hooks/useWarehouses';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import { api } from '@/services/api';
|
||||||
import type { Warehouse, WarehouseFilter } from '@/types/warehouse';
|
import type { Warehouse, WarehouseFilter } from '@/types/warehouse';
|
||||||
|
|
||||||
export default function WarehouseListPage() {
|
export default function WarehouseListPage() {
|
||||||
@@ -28,7 +30,9 @@ export default function WarehouseListPage() {
|
|||||||
[filter, debouncedSearch],
|
[filter, debouncedSearch],
|
||||||
);
|
);
|
||||||
|
|
||||||
const { data, isLoading, isError } = useWarehouses(queryFilter);
|
const { data, isLoading, isError } = useQuery(
|
||||||
|
api.warehouses.list.queryOptions({ input: { filter: queryFilter } }),
|
||||||
|
);
|
||||||
const warehouses = data ?? [];
|
const warehouses = data ?? [];
|
||||||
|
|
||||||
const openCreate = () => {
|
const openCreate = () => {
|
||||||
|
|||||||
@@ -17,15 +17,10 @@ import { Plus, Trash2 } from 'lucide-react';
|
|||||||
import { DataTable, type ColumnDef } from '@edr/ui-common';
|
import { DataTable, type ColumnDef } from '@edr/ui-common';
|
||||||
|
|
||||||
import { PageContainer, PageHeader } from '@/components/page';
|
import { PageContainer, PageHeader } from '@/components/page';
|
||||||
|
import { useMutation, useQuery } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import { api } from '@/services/api';
|
||||||
import { useToast } from '@/hooks/use-toast';
|
import { useToast } from '@/hooks/use-toast';
|
||||||
import {
|
|
||||||
useAllocationRules,
|
|
||||||
useCreateAllocationRule,
|
|
||||||
useCreateFeeRule,
|
|
||||||
useDeleteAllocationRule,
|
|
||||||
useDeleteFeeRule,
|
|
||||||
useFeeRules,
|
|
||||||
} from '@/hooks/useWarehouses';
|
|
||||||
import { FEE_RULE_TYPES, type FeeRuleType } from '@/types/warehouse';
|
import { FEE_RULE_TYPES, type FeeRuleType } from '@/types/warehouse';
|
||||||
|
|
||||||
const FREIGHT = [
|
const FREIGHT = [
|
||||||
@@ -67,9 +62,11 @@ export default function WarehouseRulesPage() {
|
|||||||
|
|
||||||
function AllocationRules() {
|
function AllocationRules() {
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
const { data, isLoading } = useAllocationRules();
|
const { data, isLoading } = useQuery(
|
||||||
const create = useCreateAllocationRule();
|
api.warehouses.allocationRules.queryOptions(),
|
||||||
const remove = useDeleteAllocationRule();
|
);
|
||||||
|
const create = useMutation(api.warehouses.createAllocationRule.mutationOptions());
|
||||||
|
const remove = useMutation(api.warehouses.deleteAllocationRule.mutationOptions());
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const [form, setForm] = useState({
|
const [form, setForm] = useState({
|
||||||
name: '',
|
name: '',
|
||||||
@@ -181,9 +178,9 @@ function AllocationRules() {
|
|||||||
|
|
||||||
function FeeRules() {
|
function FeeRules() {
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
const { data, isLoading } = useFeeRules();
|
const { data, isLoading } = useQuery(api.warehouses.feeRules.queryOptions());
|
||||||
const create = useCreateFeeRule();
|
const create = useMutation(api.warehouses.createFeeRule.mutationOptions());
|
||||||
const remove = useDeleteFeeRule();
|
const remove = useMutation(api.warehouses.deleteFeeRule.mutationOptions());
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const [form, setForm] = useState({
|
const [form, setForm] = useState({
|
||||||
name: '',
|
name: '',
|
||||||
|
|||||||
Reference in New Issue
Block a user