Files
star-erp/resources/js/Components/Product/ProductDialog.tsx

293 lines
11 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 } from "react";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/Components/ui/dialog";
import { Button } from "@/Components/ui/button";
import { Input } from "@/Components/ui/input";
import { Label } from "@/Components/ui/label";
import { Textarea } from "@/Components/ui/textarea";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/Components/ui/select";
import { useForm } from "@inertiajs/react";
import { toast } from "sonner";
import type { Product, Category } from "@/Pages/Product/Index";
import type { Unit } from "@/Components/Unit/UnitManagerDialog";
interface ProductDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
product: Product | null;
categories: Category[];
units: Unit[];
onSave?: (product: any) => void; // Legacy prop, can be removed if fully switching to Inertia submit within dialog
}
export default function ProductDialog({
open,
onOpenChange,
product,
categories,
units,
}: ProductDialogProps) {
const { data, setData, post, put, processing, errors, reset, clearErrors } = useForm({
name: "",
category_id: "",
brand: "",
specification: "",
base_unit_id: "",
large_unit_id: "",
conversion_rate: "",
purchase_unit_id: "",
});
useEffect(() => {
if (open) {
clearErrors();
if (product) {
setData({
name: product.name,
category_id: product.category_id.toString(),
brand: product.brand || "",
specification: product.specification || "",
base_unit_id: product.base_unit_id?.toString() || "",
large_unit_id: product.large_unit_id?.toString() || "",
conversion_rate: product.conversion_rate ? product.conversion_rate.toString() : "",
purchase_unit_id: product.purchase_unit_id?.toString() || "",
});
} else {
reset();
// Set default category if available
if (categories.length > 0) {
setData("category_id", categories[0].id.toString());
}
}
}
}, [open, product, categories]);
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (product) {
put(route("products.update", product.id), {
onSuccess: () => {
toast.success("商品已更新");
onOpenChange(false);
reset();
},
onError: () => {
toast.error("更新失敗,請檢查輸入資料");
}
});
} else {
post(route("products.store"), {
onSuccess: () => {
toast.success("商品已新增");
onOpenChange(false);
reset();
},
onError: () => {
toast.error("新增失敗,請檢查輸入資料");
}
});
}
};
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-w-3xl max-h-[90vh] overflow-y-auto">
<DialogHeader>
<DialogTitle>{product ? "編輯商品" : "新增商品"}</DialogTitle>
<DialogDescription>
{product ? "修改商品資料" : "建立新的商品資料"}
</DialogDescription>
</DialogHeader>
<form onSubmit={handleSubmit} className="space-y-6 py-4">
{/* 基本資訊區塊 */}
<div className="space-y-4">
<h3 className="text-lg font-medium border-b pb-2"></h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="category_id">
<span className="text-red-500">*</span>
</Label>
<Select
value={data.category_id}
onValueChange={(value) => setData("category_id", value)}
>
<SelectTrigger id="category_id" className={errors.category_id ? "border-red-500" : ""}>
<SelectValue placeholder="選擇分類" />
</SelectTrigger>
<SelectContent>
{categories.map((category) => (
<SelectItem key={category.id} value={category.id.toString()}>
{category.name}
</SelectItem>
))}
</SelectContent>
</Select>
{errors.category_id && <p className="text-sm text-red-500">{errors.category_id}</p>}
</div>
<div className="space-y-2">
<Label htmlFor="name">
<span className="text-red-500">*</span>
</Label>
<Input
id="name"
value={data.name}
onChange={(e) => setData("name", e.target.value)}
placeholder="例:法國麵粉"
className={errors.name ? "border-red-500" : ""}
/>
{errors.name && <p className="text-sm text-red-500">{errors.name}</p>}
</div>
<div className="space-y-2">
<Label htmlFor="brand"></Label>
<Input
id="brand"
value={data.brand}
onChange={(e) => setData("brand", e.target.value)}
placeholder="例:鳥越製粉"
/>
{errors.brand && <p className="text-sm text-red-500">{errors.brand}</p>}
</div>
<div className="space-y-2 col-span-2">
<Label htmlFor="specification"></Label>
<Textarea
id="specification"
value={data.specification}
onChange={(e) => setData("specification", e.target.value)}
placeholder="例25kg/袋灰分0.45%"
className="resize-none"
/>
{errors.specification && <p className="text-sm text-red-500">{errors.specification}</p>}
</div>
</div>
</div>
{/* 單位設定區塊 */}
<div className="space-y-4">
<h3 className="text-lg font-medium border-b pb-2"></h3>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<div className="space-y-2">
<Label htmlFor="base_unit_id">
<span className="text-red-500">*</span>
</Label>
<Select
value={data.base_unit_id}
onValueChange={(value) => setData("base_unit_id", value)}
>
<SelectTrigger id="base_unit_id" className={errors.base_unit_id ? "border-red-500" : ""}>
<SelectValue placeholder="選擇單位" />
</SelectTrigger>
<SelectContent>
{units.map((unit) => (
<SelectItem key={unit.id} value={unit.id.toString()}>
{unit.name}
</SelectItem>
))}
</SelectContent>
</Select>
{errors.base_unit_id && <p className="text-sm text-red-500">{errors.base_unit_id}</p>}
</div>
<div className="space-y-2">
<Label htmlFor="large_unit_id"></Label>
<Select
value={data.large_unit_id}
onValueChange={(value) => setData("large_unit_id", value)}
>
<SelectTrigger id="large_unit_id" className={errors.large_unit_id ? "border-red-500" : ""}>
<SelectValue placeholder="無" />
</SelectTrigger>
<SelectContent>
<SelectItem value="none"></SelectItem>
{units.map((unit) => (
<SelectItem key={unit.id} value={unit.id.toString()}>
{unit.name}
</SelectItem>
))}
</SelectContent>
</Select>
{errors.large_unit_id && <p className="text-sm text-red-500">{errors.large_unit_id}</p>}
</div>
<div className="space-y-2">
<Label htmlFor="conversion_rate">
{data.large_unit_id && <span className="text-red-500">*</span>}
</Label>
<Input
id="conversion_rate"
type="number"
step="0.0001"
value={data.conversion_rate}
onChange={(e) => setData("conversion_rate", e.target.value)}
placeholder={data.large_unit_id && data.base_unit_id ? `1 ${units.find(u => u.id.toString() === data.large_unit_id)?.name} = ? ${units.find(u => u.id.toString() === data.base_unit_id)?.name}` : ""}
disabled={!data.large_unit_id}
/>
{errors.conversion_rate && <p className="text-sm text-red-500">{errors.conversion_rate}</p>}
</div>
<div className="space-y-2">
<Label htmlFor="purchase_unit_id"></Label>
<Select
value={data.purchase_unit_id}
onValueChange={(value) => setData("purchase_unit_id", value)}
>
<SelectTrigger id="purchase_unit_id" className={errors.purchase_unit_id ? "border-red-500" : ""}>
<SelectValue placeholder="通常同大單位" />
</SelectTrigger>
<SelectContent>
<SelectItem value="none"></SelectItem>
{units.map((unit) => (
<SelectItem key={unit.id} value={unit.id.toString()}>
{unit.name}
</SelectItem>
))}
</SelectContent>
</Select>
{errors.purchase_unit_id && <p className="text-sm text-red-500">{errors.purchase_unit_id}</p>}
</div>
</div>
{data.large_unit_id && data.base_unit_id && data.conversion_rate && (
<div className="bg-blue-50 p-3 rounded text-sm text-blue-700">
1 {units.find(u => u.id.toString() === data.large_unit_id)?.name} = {data.conversion_rate} {units.find(u => u.id.toString() === data.base_unit_id)?.name}
</div>
)}
</div>
<DialogFooter>
<Button
type="button"
variant="outline"
onClick={() => onOpenChange(false)}
className="button-outlined-primary"
>
</Button>
<Button type="submit" className="button-filled-primary" disabled={processing}>
{processing ? "儲存... " : (product ? "儲存變更" : "新增")}
</Button>
</DialogFooter>
</form>
</DialogContent>
</Dialog >
);
}