Files
star-erp/resources/js/Components/Warehouse/Inventory/BatchAdjustmentModal.tsx
sky121113 1d134c9ad8
All checks were successful
Koori-ERP-Deploy-System / deploy-demo (push) Successful in 57s
Koori-ERP-Deploy-System / deploy-production (push) Has been skipped
生產工單BOM以及批號完善
2026-01-22 15:39:35 +08:00

171 lines
6.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useState, useEffect } from "react";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogFooter,
} from "@/Components/ui/dialog";
import { Button } from "@/Components/ui/button";
import { Input } from "@/Components/ui/input";
import { Label } from "@/Components/ui/label";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/Components/ui/select";
import { AlertCircle } from "lucide-react";
interface BatchAdjustmentModalProps {
isOpen: boolean;
onClose: () => void;
onConfirm: (data: {
operation: "add" | "subtract" | "set";
quantity: number;
reason: string;
}) => void;
batch?: {
id: string;
batchNumber: string;
currentQuantity: number;
productName: string;
};
processing?: boolean;
}
export default function BatchAdjustmentModal({
isOpen,
onClose,
onConfirm,
batch,
processing = false,
}: BatchAdjustmentModalProps) {
const [operation, setOperation] = useState<"add" | "subtract" | "set">("add");
const [quantity, setQuantity] = useState<string>("");
const [reason, setReason] = useState<string>("手動調整庫存");
// 當開啟時重置
useEffect(() => {
if (isOpen) {
setOperation("add");
setQuantity("");
setReason("手動調整庫存");
}
}, [isOpen]);
const handleConfirm = () => {
const numQty = parseFloat(quantity);
if (isNaN(numQty) || numQty <= 0 && operation !== "set") {
return;
}
onConfirm({
operation,
quantity: numQty,
reason,
});
};
const previewQuantity = () => {
if (!batch) return 0;
const numQty = parseFloat(quantity) || 0;
if (operation === "add") return batch.currentQuantity + numQty;
if (operation === "subtract") return Math.max(0, batch.currentQuantity - numQty);
if (operation === "set") return numQty;
return batch.currentQuantity;
};
if (!batch) return null;
return (
<Dialog open={isOpen} onOpenChange={(open) => !open && onClose()}>
<DialogContent className="sm:max-w-[425px]">
<DialogHeader>
<DialogTitle className="flex items-center gap-2">
調 - {batch.productName}
</DialogTitle>
</DialogHeader>
<div className="grid gap-4 py-4">
<div className="bg-gray-50 p-3 rounded-md border text-sm space-y-1">
<div className="flex justify-between">
<span className="text-gray-500"></span>
<span className="font-mono font-medium">{batch.batchNumber || "-"}</span>
</div>
<div className="flex justify-between">
<span className="text-gray-500"></span>
<span className="font-medium text-primary-main">{batch.currentQuantity}</span>
</div>
</div>
<div className="space-y-2">
<Label>調</Label>
<Select
value={operation}
onValueChange={(value: any) => setOperation(value)}
>
<SelectTrigger>
<SelectValue placeholder="選擇調整方式" />
</SelectTrigger>
<SelectContent>
<SelectItem value="add"> (+)</SelectItem>
<SelectItem value="subtract"> (-)</SelectItem>
<SelectItem value="set"> (=)</SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<Label htmlFor="adj-qty"></Label>
<Input
id="adj-qty"
type="number"
step="0.01"
min="0"
value={quantity}
onChange={(e) => setQuantity(e.target.value)}
placeholder="請輸入數量"
/>
</div>
<div className="space-y-2">
<Label htmlFor="adj-reason">調</Label>
<Input
id="adj-reason"
value={reason}
onChange={(e) => setReason(e.target.value)}
placeholder="例:手動盤點修正"
/>
</div>
<div className="flex items-center gap-2 mt-2 p-3 bg-primary/5 rounded-md border border-primary/20 text-sm">
<AlertCircle className="h-4 w-4 text-primary-main" />
<span>
調
<span className="font-bold text-primary-main ml-1">
{previewQuantity()}
</span>
</span>
</div>
</div>
<DialogFooter>
<Button variant="outline" onClick={onClose} disabled={processing}>
</Button>
<Button
onClick={handleConfirm}
disabled={processing || !quantity || parseFloat(quantity) < 0}
className="button-filled-primary"
>
調
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}