automation

This commit is contained in:
Hagernesh
2026-06-17 22:00:53 +00:00
parent 1db1467ea1
commit 5b8cb16c1a
12 changed files with 705 additions and 1 deletions

View File

@@ -0,0 +1,214 @@
import { useState } from 'react';
import {
Button,
Divider,
FileInput,
Group,
Modal,
NumberInput,
Select,
Switch,
Textarea,
} from '@mantine/core';
import { Upload } from 'lucide-react';
import { useToast } from '@/hooks/use-toast';
import { useCreateInspectionReport, useUploadInspectionAttachments } from '@/hooks/useWarehouses';
import {
INSPECTION_REPORT_TYPES,
INSPECTION_STATUSES,
type InspectionReportType,
type InspectionResultStatus,
} from '@/types/warehouse';
import { extractErrorMessage } from './options';
interface InspectionReportModalProps {
opened: boolean;
onClose: () => void;
inventoryId: string | null;
}
const REPORT_TYPE_LABELS: Record<InspectionReportType, string> = {
INSPECTION: 'Inspection',
DAMAGE: 'Damage',
WEIGHT_LOSS: 'Weight loss',
MISSING_ITEM: 'Missing item',
GENERAL: 'General',
};
const STATUS_LABELS: Record<InspectionResultStatus, string> = {
PASSED: 'Passed',
FAILED: 'Failed',
NEEDS_REVIEW: 'Needs review',
};
/** Batch 4.5 — record an inspection / damage report with optional image upload. */
export function InspectionReportModal({ opened, onClose, inventoryId }: InspectionReportModalProps) {
const { toast } = useToast();
const createReport = useCreateInspectionReport();
const uploadAttachments = useUploadInspectionAttachments();
const [reportType, setReportType] = useState<InspectionReportType>('INSPECTION');
const [inspectionStatus, setInspectionStatus] = useState<InspectionResultStatus>('PASSED');
const [hasDamage, setHasDamage] = useState(false);
const [damageDescription, setDamageDescription] = useState('');
const [hasWeightLoss, setHasWeightLoss] = useState(false);
const [expectedWeight, setExpectedWeight] = useState<number | ''>('');
const [actualWeight, setActualWeight] = useState<number | ''>('');
const [hasMissingItems, setHasMissingItems] = useState(false);
const [missingItemsDescription, setMissingItemsDescription] = useState('');
const [remarks, setRemarks] = useState('');
const [files, setFiles] = useState<File[]>([]);
const submitting = createReport.isPending || uploadAttachments.isPending;
const reset = () => {
setReportType('INSPECTION');
setInspectionStatus('PASSED');
setHasDamage(false);
setDamageDescription('');
setHasWeightLoss(false);
setExpectedWeight('');
setActualWeight('');
setHasMissingItems(false);
setMissingItemsDescription('');
setRemarks('');
setFiles([]);
};
const handleSubmit = async () => {
if (!inventoryId) return;
try {
const report = await createReport.mutateAsync({
inventoryId,
payload: {
reportType,
inspectionStatus,
hasDamage,
damageDescription: damageDescription.trim() || undefined,
hasWeightLoss,
expectedWeight: expectedWeight === '' ? undefined : Number(expectedWeight),
actualWeight: actualWeight === '' ? undefined : Number(actualWeight),
hasMissingItems,
missingItemsDescription: missingItemsDescription.trim() || undefined,
remarks: remarks.trim() || undefined,
},
});
if (files.length > 0) {
await uploadAttachments.mutateAsync({ reportId: report.id, files });
}
toast({ title: 'Inspection report saved' });
reset();
onClose();
} catch (error) {
toast({ variant: 'destructive', title: 'Save failed', description: extractErrorMessage(error) });
}
};
return (
<Modal opened={opened} onClose={onClose} title="Inspection / Report" centered size="lg">
<Group grow>
<Select
label="Report type"
data={INSPECTION_REPORT_TYPES.map((t) => ({ value: t, label: REPORT_TYPE_LABELS[t] }))}
value={reportType}
onChange={(v) => setReportType((v as InspectionReportType) ?? 'INSPECTION')}
allowDeselect={false}
/>
<Select
label="Inspection status"
data={INSPECTION_STATUSES.map((s) => ({ value: s, label: STATUS_LABELS[s] }))}
value={inspectionStatus}
onChange={(v) => setInspectionStatus((v as InspectionResultStatus) ?? 'PASSED')}
allowDeselect={false}
/>
</Group>
<Divider my="md" label="Damage" labelPosition="left" />
<Switch
label="Has damage"
checked={hasDamage}
onChange={(e) => setHasDamage(e.currentTarget.checked)}
color="orange"
/>
{hasDamage && (
<Textarea
mt="xs"
label="Damage description"
value={damageDescription}
onChange={(e) => setDamageDescription(e.currentTarget.value)}
/>
)}
<Divider my="md" label="Weight loss" labelPosition="left" />
<Switch
label="Has weight loss"
checked={hasWeightLoss}
onChange={(e) => setHasWeightLoss(e.currentTarget.checked)}
color="orange"
/>
{hasWeightLoss && (
<Group grow mt="xs">
<NumberInput
label="Expected weight (kg)"
min={0}
value={expectedWeight}
onChange={(v) => setExpectedWeight(v === '' ? '' : Number(v))}
/>
<NumberInput
label="Actual weight (kg)"
min={0}
value={actualWeight}
onChange={(v) => setActualWeight(v === '' ? '' : Number(v))}
/>
</Group>
)}
<Divider my="md" label="Missing items" labelPosition="left" />
<Switch
label="Has missing items"
checked={hasMissingItems}
onChange={(e) => setHasMissingItems(e.currentTarget.checked)}
color="orange"
/>
{hasMissingItems && (
<Textarea
mt="xs"
label="Missing items description"
value={missingItemsDescription}
onChange={(e) => setMissingItemsDescription(e.currentTarget.value)}
/>
)}
<Divider my="md" />
<Textarea
label="Remarks"
value={remarks}
onChange={(e) => setRemarks(e.currentTarget.value)}
/>
<FileInput
mt="md"
label="Images / documents"
placeholder="Upload jpg, png or pdf"
accept="image/jpeg,image/png,application/pdf"
leftSection={<Upload size={16} />}
multiple
value={files}
onChange={setFiles}
clearable
/>
<Group justify="flex-end" mt="lg">
<Button variant="default" onClick={onClose} disabled={submitting}>
Cancel
</Button>
<Button color="green" onClick={handleSubmit} loading={submitting} disabled={!inventoryId}>
Save report
</Button>
</Group>
</Modal>
);
}

View File

@@ -8,6 +8,7 @@ import {
useStoreInventory,
} from '@/hooks/useWarehouses';
import type { InventoryAction, WarehouseInventoryItem } from '@/types/warehouse';
import { InspectionReportModal } from './InspectionReportModal';
import { InventoryHistoryModal } from './InventoryHistoryModal';
import { LoadInventoryModal } from './LoadInventoryModal';
import { MoveInventoryModal } from './MoveInventoryModal';
@@ -28,6 +29,7 @@ export function InventoryWorkbench({ items, isLoading }: InventoryWorkbenchProps
const [reserveItem, setReserveItem] = useState<WarehouseInventoryItem | null>(null);
const [loadItem, setLoadItem] = useState<WarehouseInventoryItem | null>(null);
const [historyItem, setHistoryItem] = useState<WarehouseInventoryItem | null>(null);
const [inspectItem, setInspectItem] = useState<WarehouseInventoryItem | null>(null);
const storeMutation = useStoreInventory();
const readyMutation = useMarkReadyForLoading();
@@ -80,6 +82,7 @@ export function InventoryWorkbench({ items, isLoading }: InventoryWorkbenchProps
onAdvance={advance}
onMove={setMoveItem}
onHistory={setHistoryItem}
onInspect={setInspectItem}
/>
<MoveInventoryModal opened={Boolean(moveItem)} onClose={() => setMoveItem(null)} item={moveItem} />
@@ -94,6 +97,11 @@ export function InventoryWorkbench({ items, isLoading }: InventoryWorkbenchProps
onClose={() => setHistoryItem(null)}
item={historyItem}
/>
<InspectionReportModal
opened={Boolean(inspectItem)}
onClose={() => setInspectItem(null)}
inventoryId={inspectItem?.id ?? null}
/>
</>
);
}

View File

@@ -1,5 +1,5 @@
import { ActionIcon, Badge, Button, Group, Table, Text, Tooltip } from '@mantine/core';
import { ArrowRightLeft, History } from 'lucide-react';
import { ArrowRightLeft, ClipboardList, History } from 'lucide-react';
import type { InventoryAction, WarehouseInventoryItem } from '@/types/warehouse';
import { INVENTORY_NEXT_ACTION } from '@/types/warehouse';
@@ -12,6 +12,7 @@ interface WarehouseInventoryTableProps {
onAdvance: (item: WarehouseInventoryItem, action: InventoryAction) => void;
onMove: (item: WarehouseInventoryItem) => void;
onHistory: (item: WarehouseInventoryItem) => void;
onInspect?: (item: WarehouseInventoryItem) => void;
}
const itemKind = (item: WarehouseInventoryItem) => {
@@ -35,6 +36,7 @@ export function WarehouseInventoryTable({
onAdvance,
onMove,
onHistory,
onInspect,
}: WarehouseInventoryTableProps) {
if (items.length === 0) {
return (
@@ -119,6 +121,13 @@ export function WarehouseInventoryTable({
</ActionIcon>
</Tooltip>
)}
{onInspect && (
<Tooltip label="Inspection / Report" withArrow>
<ActionIcon variant="subtle" color="orange" onClick={() => onInspect(item)}>
<ClipboardList size={16} />
</ActionIcon>
</Tooltip>
)}
<Tooltip label="History" withArrow>
<ActionIcon variant="subtle" color="gray" onClick={() => onHistory(item)}>
<History size={16} />

View File

@@ -25,3 +25,4 @@ export type { FreightVisualVariant } from './FreightVisual';
export { WarehouseHero } from './WarehouseHero';
export { VisualEmptyState } from './VisualEmptyState';
export { WarehouseDashboardCharts } from './WarehouseDashboardCharts';
export { InspectionReportModal } from './InspectionReportModal';