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

307 lines
10 KiB
TypeScript
Raw Normal View History

2025-12-30 15:03:19 +08:00
/**
*
*
2025-12-30 15:03:19 +08:00
*/
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";
2025-12-30 15:03:19 +08:00
import { validateWarehouse } from "@/utils/validation";
import { toast } from "sonner";
import { SearchableSelect } from "@/Components/ui/searchable-select";
2025-12-30 15:03:19 +08:00
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" },
];
2025-12-30 15:03:19 +08:00
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;
2025-12-30 15:03:19 +08:00
}>({
code: "",
name: "",
address: "",
description: "",
type: "standard",
license_plate: "",
driver_name: "",
2025-12-30 15:03:19 +08:00
});
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 || "",
2025-12-30 15:03:19 +08:00
});
} else {
setFormData({
code: "",
name: "",
address: "",
description: "",
type: "standard",
license_plate: "",
driver_name: "",
2025-12-30 15:03:19 +08:00
});
}
}, [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">
</Label>
<Input
id="code"
value={warehouse ? formData.code : ""}
disabled={true}
placeholder={warehouse ? "" : "系統自動產生"}
className="bg-gray-100"
/>
</div>
{/* 倉庫類型 */}
2025-12-30 15:03:19 +08:00
<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">
2025-12-30 15:03:19 +08:00
<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"
2025-12-30 15:03:19 +08:00
/>
</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>
)}
2025-12-30 15:03:19 +08:00
{/* 區塊 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"
2025-12-30 15:03:19 +08:00
/>
</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"
2025-12-30 15:03:19 +08:00
>
<Trash2 className="mr-2 h-4 w-4" />
2025-12-30 15:03:19 +08:00
</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 >
2025-12-30 15:03:19 +08:00
{/* 刪除確認對話框 */}
< AlertDialog open={showDeleteDialog} onOpenChange={setShowDeleteDialog} >
2025-12-30 15:03:19 +08:00
<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 >
2025-12-30 15:03:19 +08:00
</>
);
}