Files
star-erp/resources/js/Components/Warehouse/WarehouseDialog.tsx

308 lines
10 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 { useEffect, useState } from "react";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/Components/ui/dialog";
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from "@/Components/ui/alert-dialog";
import { Input } from "@/Components/ui/input";
import { Label } from "@/Components/ui/label";
import { Textarea } from "@/Components/ui/textarea";
import { Button } from "@/Components/ui/button";
import { Trash2 } from "lucide-react";
import { Warehouse, WarehouseType } from "@/types/warehouse";
import { validateWarehouse } from "@/utils/validation";
import { toast } from "sonner";
import { SearchableSelect } from "@/Components/ui/searchable-select";
interface WarehouseDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
warehouse: Warehouse | null;
onSave: (warehouse: Omit<Warehouse, "id" | "createdAt" | "updatedAt">) => void;
onDelete?: (warehouseId: string) => void;
}
const WAREHOUSE_TYPE_OPTIONS: { label: string; value: WarehouseType }[] = [
{ label: "標準倉 (總倉)", value: "standard" },
{ label: "生產倉 (廚房/加工)", value: "production" },
{ label: "門市倉 (前台销售)", value: "retail" },
{ label: "販賣機 (IoT設備)", value: "vending" },
{ label: "在途倉 (物流車)", value: "transit" },
{ label: "瑕疵倉 (報廢/檢驗)", value: "quarantine" },
];
export default function WarehouseDialog({
open,
onOpenChange,
warehouse,
onSave,
onDelete,
}: WarehouseDialogProps) {
const [formData, setFormData] = useState<{
code: string;
name: string;
address: string;
description: string;
type: WarehouseType;
license_plate: string;
driver_name: string;
}>({
code: "",
name: "",
address: "",
description: "",
type: "standard",
license_plate: "",
driver_name: "",
});
const [showDeleteDialog, setShowDeleteDialog] = useState(false);
useEffect(() => {
if (warehouse) {
setFormData({
code: warehouse.code,
name: warehouse.name,
address: warehouse.address || "",
description: warehouse.description || "",
type: warehouse.type || "standard",
license_plate: warehouse.license_plate || "",
driver_name: warehouse.driver_name || "",
});
} else {
setFormData({
code: "",
name: "",
address: "",
description: "",
type: "standard",
license_plate: "",
driver_name: "",
});
}
}, [warehouse, open]);
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
const validation = validateWarehouse(formData);
if (!validation.isValid) {
toast.error(validation.error);
return;
}
onSave(formData);
};
const handleDelete = () => {
if (warehouse && onDelete) {
onDelete(warehouse.id);
setShowDeleteDialog(false);
onOpenChange(false);
}
};
return (
<>
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-w-2xl max-h-[90vh] overflow-y-auto">
<DialogHeader>
<DialogTitle>{warehouse ? "編輯倉庫" : "新增倉庫"}</DialogTitle>
<DialogDescription>
{warehouse ? "修改倉庫資訊" : "建立新的倉庫資訊"}
</DialogDescription>
</DialogHeader>
<form onSubmit={handleSubmit}>
<div className="space-y-6 py-4">
{/* 區塊 A基本資訊 */}
<div className="space-y-4">
<div className="border-b pb-2">
<h4 className="text-sm text-gray-700"></h4>
</div>
<div className="grid grid-cols-2 gap-4">
{/* 倉庫編號 */}
<div className="space-y-2">
<Label htmlFor="code">
<span className="text-red-500">*</span>
</Label>
<Input
id="code"
value={formData.code}
onChange={(e) => setFormData({ ...formData, code: e.target.value })}
placeholder="請輸入倉庫編號"
required
className="h-9"
/>
</div>
{/* 倉庫類型 */}
<div className="space-y-2">
<Label> <span className="text-red-500">*</span></Label>
<SearchableSelect
value={formData.type}
onValueChange={(val) => setFormData({ ...formData, type: val as WarehouseType })}
options={WAREHOUSE_TYPE_OPTIONS}
placeholder="選擇倉庫類型"
className="h-9"
showSearch={false}
/>
</div>
{/* 倉庫名稱 */}
<div className="space-y-2 col-span-2">
<Label htmlFor="name">
<span className="text-red-500">*</span>
</Label>
<Input
id="name"
value={formData.name}
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
placeholder="例:中央倉庫"
required
className="h-9"
/>
</div>
</div>
</div>
{/* 移動倉專屬資訊 */}
{formData.type === 'transit' && (
<div className="space-y-4 bg-yellow-50 p-4 rounded-lg border border-yellow-100">
<div className="border-b border-yellow-200 pb-2">
<h4 className="text-sm text-yellow-800 font-medium"> ()</h4>
</div>
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="license_plate"></Label>
<Input
id="license_plate"
value={formData.license_plate}
onChange={(e) => setFormData({ ...formData, license_plate: e.target.value })}
placeholder="例ABC-1234"
className="h-9 bg-white"
/>
</div>
<div className="space-y-2">
<Label htmlFor="driver_name"></Label>
<Input
id="driver_name"
value={formData.driver_name}
onChange={(e) => setFormData({ ...formData, driver_name: e.target.value })}
placeholder="例:王小明"
className="h-9 bg-white"
/>
</div>
</div>
</div>
)}
{/* 區塊 B位置 */}
<div className="space-y-4">
<div className="border-b pb-2">
<h4 className="text-sm text-gray-700"></h4>
</div>
{/* 倉庫地址 */}
<div className="space-y-2">
<Label htmlFor="address">
<span className="text-red-500">*</span>
</Label>
<Input
id="address"
value={formData.address}
onChange={(e) => setFormData({ ...formData, address: e.target.value })}
placeholder="例台北市信義區信義路五段7號"
required
className="h-9"
/>
</div>
{/* 備註說明 */}
<div className="space-y-2">
<Label htmlFor="description"></Label>
<Textarea
id="description"
value={formData.description}
onChange={(e) =>
setFormData({ ...formData, description: e.target.value })
}
placeholder="其他說明"
rows={2}
className="resize-none"
/>
</div>
</div>
</div>
<DialogFooter className="gap-2">
{warehouse && onDelete && (
<Button
type="button"
onClick={() => setShowDeleteDialog(true)}
variant="outline"
className="mr-auto button-outlined-error"
>
<Trash2 className="mr-2 h-4 w-4" />
</Button>
)}
<Button
type="button"
onClick={() => onOpenChange(false)}
className="button-outlined-primary"
>
</Button>
<Button type="submit" className="button-filled-primary">
{warehouse ? "更新" : "新增"}
</Button>
</DialogFooter>
</form>
</DialogContent>
</Dialog >
{/* 刪除確認對話框 */}
< AlertDialog open={showDeleteDialog} onOpenChange={setShowDeleteDialog} >
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle></AlertDialogTitle>
<AlertDialogDescription>
{warehouse?.name}
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel></AlertDialogCancel>
<AlertDialogAction
onClick={handleDelete}
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
>
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog >
</>
);
}