1. UI 標準化: - 針對全系統數值輸入欄位統一加上 step='any' 以支援小數點。 - 表格形式 (Table) 的數值輸入欄位統一加上 text-right 靠右對齊。 - 修正 Components 與 Pages 中所有涉及金額與數量的輸入框。 2. 功能擴充與修正: - 擴充 Product 模型與相關 Dialog 以支援多種價格設定。 - 修正 Inventory/GoodsReceipt/Create.tsx 未使用的變數錯誤。 - 優化庫存相關頁面的 UI 一致性。 3. 其他: - 更新相關的 Type 定義與 Controller 邏輯。
194 lines
8.9 KiB
TypeScript
194 lines
8.9 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 => String(sp.productId)));
|
|
return products.filter(p => !existingIds.has(String(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 z-[9999]" 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.baseUnit?.name || (product.base_unit as any)?.name || 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.baseUnit?.name || (selectedProduct.base_unit as any)?.name || selectedProduct.base_unit || "個") : "-"}
|
|
</div>
|
|
</div>
|
|
|
|
{/* 上次採購價格 */}
|
|
<div>
|
|
<Label className="text-muted-foreground text-xs">
|
|
上次採購單價 / {selectedProduct ? (selectedProduct.baseUnit?.name || (selectedProduct.base_unit as any)?.name || selectedProduct.base_unit || "個") : "單位"}
|
|
</Label>
|
|
<Input
|
|
type="number"
|
|
min="0"
|
|
step="any"
|
|
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>
|
|
);
|
|
}
|