190 lines
8.5 KiB
TypeScript
190 lines
8.5 KiB
TypeScript
/**
|
||
* 新增供貨商品對話框
|
||
*/
|
||
|
||
import { useState, useMemo } from "react";
|
||
import { Button } from "@/Components/ui/button";
|
||
import { Input } from "@/Components/ui/input";
|
||
import { Label } from "@/Components/ui/label";
|
||
import {
|
||
Dialog,
|
||
DialogContent,
|
||
DialogHeader,
|
||
DialogTitle,
|
||
DialogDescription,
|
||
DialogFooter,
|
||
} from "@/Components/ui/dialog";
|
||
import {
|
||
Command,
|
||
CommandEmpty,
|
||
CommandGroup,
|
||
CommandInput,
|
||
CommandItem,
|
||
CommandList,
|
||
} from "@/Components/ui/command";
|
||
import {
|
||
Popover,
|
||
PopoverContent,
|
||
PopoverTrigger,
|
||
} from "@/Components/ui/popover";
|
||
import { Check, ChevronsUpDown } from "lucide-react";
|
||
import { cn } from "@/lib/utils";
|
||
import type { Product } from "@/types/product";
|
||
import type { SupplyProduct } from "@/types/vendor";
|
||
|
||
interface AddSupplyProductDialogProps {
|
||
open: boolean;
|
||
products: Product[];
|
||
existingSupplyProducts: SupplyProduct[];
|
||
onClose: () => void;
|
||
onAdd: (productId: string, lastPrice?: number) => void;
|
||
}
|
||
|
||
export default function AddSupplyProductDialog({
|
||
open,
|
||
products,
|
||
existingSupplyProducts,
|
||
onClose,
|
||
onAdd,
|
||
}: AddSupplyProductDialogProps) {
|
||
const [selectedProductId, setSelectedProductId] = useState<string>("");
|
||
const [lastPrice, setLastPrice] = useState<string>("");
|
||
const [openCombobox, setOpenCombobox] = useState(false);
|
||
|
||
// 過濾掉已經在供貨列表中的商品
|
||
const availableProducts = useMemo(() => {
|
||
const existingIds = new Set(existingSupplyProducts.map(sp => sp.productId));
|
||
return products.filter(p => !existingIds.has(p.id));
|
||
}, [products, existingSupplyProducts]);
|
||
|
||
const selectedProduct = availableProducts.find(p => p.id === selectedProductId);
|
||
|
||
const handleAdd = () => {
|
||
if (!selectedProductId) return;
|
||
|
||
const price = lastPrice ? parseFloat(lastPrice) : undefined;
|
||
onAdd(selectedProductId, price);
|
||
|
||
// 重置表單
|
||
setSelectedProductId("");
|
||
setLastPrice("");
|
||
};
|
||
|
||
const handleCancel = () => {
|
||
setSelectedProductId("");
|
||
setLastPrice("");
|
||
onClose();
|
||
};
|
||
|
||
return (
|
||
<Dialog open={open} onOpenChange={onClose}>
|
||
<DialogContent className="max-w-xl">
|
||
<DialogHeader>
|
||
<DialogTitle>新增供貨商品</DialogTitle>
|
||
<DialogDescription>選擇該廠商可供應的商品並設定採購價格。</DialogDescription>
|
||
</DialogHeader>
|
||
|
||
<div className="space-y-4">
|
||
{/* 商品選擇 */}
|
||
<div className="flex flex-col gap-2">
|
||
<Label className="text-sm font-medium">商品名稱</Label>
|
||
<Popover open={openCombobox} onOpenChange={setOpenCombobox}>
|
||
<PopoverTrigger asChild>
|
||
<button
|
||
type="button"
|
||
role="combobox"
|
||
aria-expanded={openCombobox}
|
||
className="flex h-9 w-full items-center justify-between rounded-md border-2 border-grey-3 !bg-grey-5 px-3 py-1 text-sm font-normal text-grey-0 text-left outline-none transition-colors hover:!bg-grey-5 hover:border-primary/50 focus-visible:border-[var(--primary-main)] focus-visible:ring-[3px] focus-visible:ring-[var(--primary-main)]/20"
|
||
onClick={() => setOpenCombobox(!openCombobox)}
|
||
>
|
||
{selectedProduct ? (
|
||
<span className="font-medium text-gray-900">{selectedProduct.name}</span>
|
||
) : (
|
||
<span className="text-gray-400">請選擇商品...</span>
|
||
)}
|
||
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
||
</button>
|
||
</PopoverTrigger>
|
||
<PopoverContent className="w-[450px] p-0 shadow-lg border-2" align="start">
|
||
<Command>
|
||
<CommandInput placeholder="搜尋商品名稱..." />
|
||
<CommandList className="max-h-[300px]">
|
||
<CommandEmpty className="py-6 text-center text-sm text-gray-500">
|
||
找不到符合的商品
|
||
</CommandEmpty>
|
||
<CommandGroup>
|
||
{availableProducts.map((product) => (
|
||
<CommandItem
|
||
key={product.id}
|
||
value={product.name}
|
||
onSelect={() => {
|
||
setSelectedProductId(product.id);
|
||
setOpenCombobox(false);
|
||
}}
|
||
className="cursor-pointer aria-selected:bg-primary/5 aria-selected:text-primary py-3"
|
||
>
|
||
<Check
|
||
className={cn(
|
||
"mr-2 h-4 w-4 text-primary",
|
||
selectedProductId === product.id ? "opacity-100" : "opacity-0"
|
||
)}
|
||
/>
|
||
<div className="flex items-center justify-between flex-1">
|
||
<span className="font-medium">{product.name}</span>
|
||
<span className="text-xs text-gray-400 bg-gray-50 px-2 py-1 rounded">
|
||
{product.purchase_unit || product.base_unit || "個"}
|
||
</span>
|
||
</div>
|
||
</CommandItem>
|
||
))}
|
||
</CommandGroup>
|
||
</CommandList>
|
||
</Command>
|
||
</PopoverContent>
|
||
</Popover>
|
||
</div>
|
||
|
||
{/* 單位(自動帶入) */}
|
||
<div className="flex flex-col gap-2">
|
||
<Label className="text-sm font-medium text-gray-500">採購單位</Label>
|
||
<div className="h-10 px-3 py-2 bg-gray-50 border border-gray-200 rounded-md text-gray-600 font-medium text-sm flex items-center">
|
||
{selectedProduct ? (selectedProduct.purchase_unit || selectedProduct.base_unit || "個") : "-"}
|
||
</div>
|
||
</div>
|
||
|
||
{/* 上次採購價格 */}
|
||
<div>
|
||
<Label className="text-muted-foreground text-xs">上次採購單價(選填)</Label>
|
||
<Input
|
||
type="number"
|
||
placeholder="輸入價格"
|
||
value={lastPrice}
|
||
onChange={(e) => setLastPrice(e.target.value)}
|
||
className="mt-1"
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
<DialogFooter>
|
||
<Button
|
||
variant="outline"
|
||
size="sm"
|
||
onClick={handleCancel}
|
||
className="gap-2 button-outlined-primary"
|
||
>
|
||
取消
|
||
</Button>
|
||
<Button
|
||
size="sm"
|
||
onClick={handleAdd}
|
||
disabled={!selectedProductId}
|
||
className="gap-2 button-filled-primary"
|
||
>
|
||
新增
|
||
</Button>
|
||
</DialogFooter>
|
||
</DialogContent>
|
||
</Dialog>
|
||
);
|
||
}
|