Files
star-erp/source-code/ERP(A-b)-倉庫管理/src/components/WarehouseDialog.tsx
2025-12-30 15:03:19 +08:00

347 lines
12 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 "./ui/dialog";
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from "./ui/alert-dialog";
import { Input } from "./ui/input";
import { Label } from "./ui/label";
import { Textarea } from "./ui/textarea";
import { Button } from "./ui/button";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "./ui/select";
import { Trash2 } from "lucide-react";
import { Warehouse, WarehouseType, Store } from "../types/warehouse";
import { validateWarehouse } from "../utils/validation";
import { toast } from "sonner@2.0.3";
interface WarehouseDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
warehouse: Warehouse | null;
onSave: (warehouse: Omit<Warehouse, "id" | "createdAt">) => void;
onDelete?: (warehouseId: string) => void;
stores?: Store[]; // 門市列表
}
export default function WarehouseDialog({
open,
onOpenChange,
warehouse,
onSave,
onDelete,
stores = [], // 預設為空陣列
}: WarehouseDialogProps) {
const [formData, setFormData] = useState<{
name: string;
address: string;
manager: string;
phone: string;
description: string;
type: WarehouseType;
storeId?: string;
storeName?: string;
}>({
name: "",
address: "",
manager: "",
phone: "",
description: "",
type: "中央倉庫",
storeId: undefined,
storeName: undefined,
});
const [showDeleteDialog, setShowDeleteDialog] = useState(false);
useEffect(() => {
if (warehouse) {
setFormData({
name: warehouse.name,
address: warehouse.address,
manager: warehouse.manager,
phone: warehouse.phone,
description: warehouse.description,
type: warehouse.type,
storeId: warehouse.storeId,
storeName: warehouse.storeName,
});
} else {
setFormData({
name: "",
address: "",
manager: "",
phone: "",
description: "",
type: "中央倉庫",
storeId: undefined,
storeName: undefined,
});
}
}, [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="space-y-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
/>
</div>
{/* 倉庫類型 */}
<div className="space-y-2">
<Label htmlFor="type">
<span className="text-red-500">*</span>
</Label>
<Select
value={formData.type}
onValueChange={(value) => {
const newType = value as WarehouseType;
setFormData({
...formData,
type: newType,
// 切換到中央倉庫時清空門市綁定
storeId: newType === "中央倉庫" ? undefined : formData.storeId,
storeName: newType === "中央倉庫" ? undefined : formData.storeName,
});
}}
>
<SelectTrigger id="type">
<SelectValue placeholder="選擇倉庫類型">{formData.type}</SelectValue>
</SelectTrigger>
<SelectContent>
<SelectItem value="中央倉庫"></SelectItem>
<SelectItem value="門市"></SelectItem>
</SelectContent>
</Select>
</div>
{/* 綁定門市 - 僅當類型為門市時顯示 */}
{formData.type === "門市" && (
<div className="space-y-2">
<Label htmlFor="store"></Label>
<Select
value={formData.storeId || ""}
onValueChange={(value) => {
const selectedStore = stores.find((s) => s.id === value);
setFormData({
...formData,
storeId: value,
storeName: selectedStore?.name,
});
}}
>
<SelectTrigger id="store">
<SelectValue placeholder="選擇門市">
{formData.storeName || "請選擇門市"}
</SelectValue>
</SelectTrigger>
<SelectContent>
{stores.length === 0 ? (
<div className="px-2 py-1.5 text-sm text-gray-500">
</div>
) : (
stores.map((store) => (
<SelectItem key={store.id} value={store.id}>
{store.name}
</SelectItem>
))
)}
</SelectContent>
</Select>
{formData.storeId && (
<p className="text-sm text-gray-500">
{stores.find((s) => s.id === formData.storeId)?.address}
</p>
)}
</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
/>
</div>
</div>
{/* 區塊 C聯絡資訊 */}
<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="manager"></Label>
<Input
id="manager"
value={formData.manager}
onChange={(e) =>
setFormData({ ...formData, manager: e.target.value })
}
placeholder="例:張經理"
/>
</div>
<div className="space-y-2">
<Label htmlFor="phone"></Label>
<Input
id="phone"
value={formData.phone}
onChange={(e) => setFormData({ ...formData, phone: e.target.value })}
placeholder="例02-1234-5678"
/>
</div>
</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="group mr-auto border-destructive text-destructive hover:bg-destructive hover:text-white"
>
<Trash2 className="mr-2 h-4 w-4 group-hover:text-white" />
</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>
</>
);
}