first commit

This commit is contained in:
2025-12-30 15:03:19 +08:00
commit c735c36009
902 changed files with 83591 additions and 0 deletions

View File

@@ -0,0 +1,189 @@
/**
* 新增供貨商品對話框
*/
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>
);
}