2025-12-30 15:03:19 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 新增庫存頁面(手動入庫)
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
import { useState } from "react";
|
2026-01-13 17:00:58 +08:00
|
|
|
|
import { Plus, Trash2, Calendar, ArrowLeft, Save, Boxes } from "lucide-react";
|
2025-12-30 15:03:19 +08:00
|
|
|
|
import { Button } from "@/Components/ui/button";
|
|
|
|
|
|
import { Input } from "@/Components/ui/input";
|
|
|
|
|
|
import { Label } from "@/Components/ui/label";
|
|
|
|
|
|
import { Textarea } from "@/Components/ui/textarea";
|
2026-01-09 10:18:52 +08:00
|
|
|
|
import { SearchableSelect } from "@/Components/ui/searchable-select";
|
2025-12-30 15:03:19 +08:00
|
|
|
|
import {
|
|
|
|
|
|
Table,
|
|
|
|
|
|
TableBody,
|
|
|
|
|
|
TableCell,
|
|
|
|
|
|
TableHead,
|
|
|
|
|
|
TableHeader,
|
|
|
|
|
|
TableRow,
|
|
|
|
|
|
} from "@/Components/ui/table";
|
|
|
|
|
|
import AuthenticatedLayout from "@/Layouts/AuthenticatedLayout";
|
|
|
|
|
|
import { Head, Link, router } from "@inertiajs/react";
|
|
|
|
|
|
import { Warehouse, InboundItem, InboundReason } from "@/types/warehouse";
|
|
|
|
|
|
import { getCurrentDateTime } from "@/utils/format";
|
|
|
|
|
|
import { toast } from "sonner";
|
2026-01-07 13:06:49 +08:00
|
|
|
|
import { getInventoryBreadcrumbs } from "@/utils/breadcrumb";
|
2025-12-30 15:03:19 +08:00
|
|
|
|
|
|
|
|
|
|
interface Product {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
name: string;
|
2026-01-08 16:32:10 +08:00
|
|
|
|
baseUnit: string;
|
|
|
|
|
|
largeUnit?: string;
|
|
|
|
|
|
conversionRate?: number;
|
2025-12-30 15:03:19 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
interface Props {
|
|
|
|
|
|
warehouse: Warehouse;
|
|
|
|
|
|
products: Product[];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const INBOUND_REASONS: InboundReason[] = [
|
|
|
|
|
|
"期初建檔",
|
|
|
|
|
|
"盤點調整",
|
|
|
|
|
|
"實際入庫未走採購流程",
|
|
|
|
|
|
"生產加工成品入庫",
|
|
|
|
|
|
"其他",
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
export default function AddInventoryPage({ warehouse, products }: Props) {
|
|
|
|
|
|
const [inboundDate, setInboundDate] = useState(getCurrentDateTime());
|
|
|
|
|
|
const [reason, setReason] = useState<InboundReason>("期初建檔");
|
|
|
|
|
|
const [notes, setNotes] = useState("");
|
|
|
|
|
|
const [items, setItems] = useState<InboundItem[]>([]);
|
|
|
|
|
|
const [errors, setErrors] = useState<Record<string, string>>({});
|
|
|
|
|
|
|
|
|
|
|
|
// 新增明細行
|
|
|
|
|
|
const handleAddItem = () => {
|
2026-01-08 16:32:10 +08:00
|
|
|
|
const defaultProduct = products.length > 0 ? products[0] : { id: "", name: "", baseUnit: "個" };
|
2025-12-30 15:03:19 +08:00
|
|
|
|
const newItem: InboundItem = {
|
|
|
|
|
|
tempId: `temp-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
|
|
|
|
|
|
productId: defaultProduct.id,
|
|
|
|
|
|
productName: defaultProduct.name,
|
|
|
|
|
|
quantity: 0,
|
2026-01-08 16:32:10 +08:00
|
|
|
|
unit: defaultProduct.baseUnit, // 僅用於顯示當前選擇單位的名稱
|
|
|
|
|
|
baseUnit: defaultProduct.baseUnit,
|
|
|
|
|
|
largeUnit: defaultProduct.largeUnit,
|
|
|
|
|
|
conversionRate: defaultProduct.conversionRate,
|
|
|
|
|
|
selectedUnit: 'base',
|
2025-12-30 15:03:19 +08:00
|
|
|
|
};
|
|
|
|
|
|
setItems([...items, newItem]);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 刪除明細行
|
|
|
|
|
|
const handleRemoveItem = (tempId: string) => {
|
|
|
|
|
|
setItems(items.filter((item) => item.tempId !== tempId));
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 更新明細行
|
|
|
|
|
|
const handleUpdateItem = (tempId: string, updates: Partial<InboundItem>) => {
|
|
|
|
|
|
setItems(
|
|
|
|
|
|
items.map((item) =>
|
|
|
|
|
|
item.tempId === tempId ? { ...item, ...updates } : item
|
|
|
|
|
|
)
|
|
|
|
|
|
);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 處理商品變更
|
|
|
|
|
|
const handleProductChange = (tempId: string, productId: string) => {
|
|
|
|
|
|
const product = products.find((p) => p.id === productId);
|
2026-01-08 16:32:10 +08:00
|
|
|
|
|
2025-12-30 15:03:19 +08:00
|
|
|
|
if (product) {
|
|
|
|
|
|
handleUpdateItem(tempId, {
|
|
|
|
|
|
productId,
|
|
|
|
|
|
productName: product.name,
|
2026-01-08 16:32:10 +08:00
|
|
|
|
unit: product.baseUnit,
|
|
|
|
|
|
baseUnit: product.baseUnit,
|
|
|
|
|
|
largeUnit: product.largeUnit,
|
|
|
|
|
|
conversionRate: product.conversionRate,
|
|
|
|
|
|
selectedUnit: 'base',
|
2025-12-30 15:03:19 +08:00
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 驗證表單
|
|
|
|
|
|
const validateForm = (): boolean => {
|
|
|
|
|
|
const newErrors: Record<string, string> = {};
|
|
|
|
|
|
|
|
|
|
|
|
if (!reason) {
|
|
|
|
|
|
newErrors.reason = "請選擇入庫原因";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (reason === "其他" && !notes.trim()) {
|
|
|
|
|
|
newErrors.notes = "原因為「其他」時,備註為必填";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (items.length === 0) {
|
|
|
|
|
|
newErrors.items = "請至少新增一筆庫存明細";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
items.forEach((item, index) => {
|
|
|
|
|
|
if (!item.productId) {
|
|
|
|
|
|
newErrors[`item-${index}-product`] = "請選擇商品";
|
|
|
|
|
|
}
|
|
|
|
|
|
if (item.quantity <= 0) {
|
|
|
|
|
|
newErrors[`item-${index}-quantity`] = "數量必須大於 0";
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
setErrors(newErrors);
|
|
|
|
|
|
return Object.keys(newErrors).length === 0;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 處理儲存
|
|
|
|
|
|
const handleSave = () => {
|
|
|
|
|
|
if (!validateForm()) {
|
|
|
|
|
|
toast.error("請檢查表單內容");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
router.post(`/warehouses/${warehouse.id}/inventory`, {
|
|
|
|
|
|
inboundDate,
|
|
|
|
|
|
reason,
|
|
|
|
|
|
notes,
|
2026-01-08 16:32:10 +08:00
|
|
|
|
items: items.map(item => {
|
|
|
|
|
|
// 如果選擇大單位,則換算為基本單位數量
|
|
|
|
|
|
const finalQuantity = item.selectedUnit === 'large' && item.conversionRate
|
|
|
|
|
|
? item.quantity * item.conversionRate
|
|
|
|
|
|
: item.quantity;
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
productId: item.productId,
|
|
|
|
|
|
quantity: finalQuantity
|
|
|
|
|
|
};
|
|
|
|
|
|
})
|
2025-12-30 15:03:19 +08:00
|
|
|
|
}, {
|
|
|
|
|
|
onSuccess: () => {
|
|
|
|
|
|
toast.success("庫存記錄已儲存");
|
|
|
|
|
|
router.get(`/warehouses/${warehouse.id}/inventory`);
|
|
|
|
|
|
},
|
|
|
|
|
|
onError: (err) => {
|
|
|
|
|
|
toast.error("儲存失敗,請檢查輸入內容");
|
|
|
|
|
|
console.error(err);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
2026-01-07 13:06:49 +08:00
|
|
|
|
<AuthenticatedLayout breadcrumbs={getInventoryBreadcrumbs(warehouse.id, warehouse.name, "手動入庫")}>
|
2025-12-30 15:03:19 +08:00
|
|
|
|
<Head title={`新增庫存 - ${warehouse.name}`} />
|
|
|
|
|
|
<div className="container mx-auto p-6 max-w-7xl">
|
|
|
|
|
|
{/* 頁面標題與導航 - 已於先前任務優化 */}
|
|
|
|
|
|
<div className="mb-6">
|
|
|
|
|
|
<div className="mb-6">
|
|
|
|
|
|
<Link href={`/warehouses/${warehouse.id}/inventory`}>
|
|
|
|
|
|
<Button
|
|
|
|
|
|
variant="outline"
|
|
|
|
|
|
className="gap-2 button-outlined-primary"
|
|
|
|
|
|
>
|
|
|
|
|
|
<ArrowLeft className="h-4 w-4" />
|
|
|
|
|
|
返回庫存管理
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
</Link>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div className="flex items-center justify-between">
|
|
|
|
|
|
<div>
|
2026-01-13 17:00:58 +08:00
|
|
|
|
<h1 className="text-2xl font-bold text-grey-0 flex items-center gap-2">
|
2026-01-16 14:36:24 +08:00
|
|
|
|
<Boxes className="h-6 w-6 text-primary-main" />
|
2026-01-13 17:00:58 +08:00
|
|
|
|
新增庫存(手動入庫)
|
|
|
|
|
|
</h1>
|
|
|
|
|
|
<p className="text-gray-500 mt-1">
|
2025-12-30 15:03:19 +08:00
|
|
|
|
為 <span className="font-semibold text-gray-900">{warehouse.name}</span> 新增庫存記錄
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<Button
|
|
|
|
|
|
onClick={handleSave}
|
|
|
|
|
|
className="button-filled-primary"
|
|
|
|
|
|
>
|
|
|
|
|
|
<Save className="mr-2 h-4 w-4" />
|
|
|
|
|
|
儲存
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 表單內容 */}
|
|
|
|
|
|
<div className="space-y-6">
|
|
|
|
|
|
{/* 基本資訊區塊 */}
|
|
|
|
|
|
<div className="bg-white rounded-lg shadow-sm border p-6 space-y-4">
|
|
|
|
|
|
<h3 className="font-semibold text-lg border-b pb-2">基本資訊</h3>
|
|
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
|
|
|
|
{/* 倉庫 */}
|
|
|
|
|
|
<div className="space-y-2">
|
|
|
|
|
|
<Label className="text-gray-700">倉庫</Label>
|
|
|
|
|
|
<Input
|
|
|
|
|
|
value={warehouse.name}
|
|
|
|
|
|
disabled
|
|
|
|
|
|
className="bg-gray-50 border-gray-200"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 入庫日期 */}
|
|
|
|
|
|
<div className="space-y-2">
|
|
|
|
|
|
<Label htmlFor="inbound-date" className="text-gray-700">
|
|
|
|
|
|
入庫日期 <span className="text-red-500">*</span>
|
|
|
|
|
|
</Label>
|
|
|
|
|
|
<div className="relative">
|
|
|
|
|
|
<Input
|
|
|
|
|
|
id="inbound-date"
|
|
|
|
|
|
type="datetime-local"
|
|
|
|
|
|
value={inboundDate}
|
|
|
|
|
|
onChange={(e) => setInboundDate(e.target.value)}
|
|
|
|
|
|
className="border-gray-300 pr-10"
|
|
|
|
|
|
/>
|
|
|
|
|
|
<Calendar className="absolute right-3 top-1/2 -translate-y-1/2 h-4 w-4 text-gray-400 pointer-events-none" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 入庫原因 */}
|
|
|
|
|
|
<div className="space-y-2">
|
|
|
|
|
|
<Label htmlFor="reason" className="text-gray-700">
|
|
|
|
|
|
入庫原因 <span className="text-red-500">*</span>
|
|
|
|
|
|
</Label>
|
2026-01-09 10:18:52 +08:00
|
|
|
|
<SearchableSelect
|
|
|
|
|
|
value={reason}
|
|
|
|
|
|
onValueChange={(value) => setReason(value as InboundReason)}
|
|
|
|
|
|
options={INBOUND_REASONS.map((r) => ({ label: r, value: r }))}
|
|
|
|
|
|
placeholder="選擇入庫原因"
|
|
|
|
|
|
className="border-gray-300"
|
|
|
|
|
|
/>
|
2025-12-30 15:03:19 +08:00
|
|
|
|
{errors.reason && (
|
|
|
|
|
|
<p className="text-sm text-red-500">{errors.reason}</p>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 備註 */}
|
|
|
|
|
|
<div className="space-y-2 md:col-span-2">
|
|
|
|
|
|
<Label htmlFor="notes" className="text-gray-700">
|
|
|
|
|
|
備註 {reason === "其他" && <span className="text-red-500">*</span>}
|
|
|
|
|
|
</Label>
|
|
|
|
|
|
<Textarea
|
|
|
|
|
|
id="notes"
|
|
|
|
|
|
value={notes}
|
|
|
|
|
|
onChange={(e) => setNotes(e.target.value)}
|
|
|
|
|
|
placeholder="請輸入備註說明..."
|
|
|
|
|
|
className="border-gray-300 resize-none min-h-[100px]"
|
|
|
|
|
|
/>
|
|
|
|
|
|
{errors.notes && (
|
|
|
|
|
|
<p className="text-sm text-red-500">{errors.notes}</p>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 庫存明細區塊 */}
|
|
|
|
|
|
<div className="bg-white rounded-lg shadow-sm border p-6 space-y-4">
|
|
|
|
|
|
<div className="flex items-center justify-between">
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<h3 className="font-semibold text-lg">庫存明細</h3>
|
|
|
|
|
|
<p className="text-sm text-gray-500">
|
|
|
|
|
|
請新增要入庫的商品明細
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<Button
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
onClick={handleAddItem}
|
|
|
|
|
|
variant="outline"
|
|
|
|
|
|
className="button-outlined-primary"
|
|
|
|
|
|
>
|
|
|
|
|
|
<Plus className="mr-2 h-4 w-4" />
|
|
|
|
|
|
新增明細
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{errors.items && (
|
|
|
|
|
|
<p className="text-sm text-red-500">{errors.items}</p>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
{items.length > 0 ? (
|
|
|
|
|
|
<div className="border rounded-lg overflow-hidden">
|
|
|
|
|
|
<Table>
|
|
|
|
|
|
<TableHeader>
|
|
|
|
|
|
<TableRow className="bg-gray-50/50">
|
|
|
|
|
|
<TableHead className="w-[280px]">
|
|
|
|
|
|
商品 <span className="text-red-500">*</span>
|
|
|
|
|
|
</TableHead>
|
|
|
|
|
|
<TableHead className="w-[120px]">
|
|
|
|
|
|
數量 <span className="text-red-500">*</span>
|
|
|
|
|
|
</TableHead>
|
|
|
|
|
|
<TableHead className="w-[100px]">單位</TableHead>
|
2026-01-08 16:32:10 +08:00
|
|
|
|
<TableHead className="w-[150px]">轉換數量</TableHead>
|
2025-12-30 15:03:19 +08:00
|
|
|
|
{/* <TableHead className="w-[180px]">效期</TableHead>
|
|
|
|
|
|
<TableHead className="w-[220px]">進貨編號</TableHead> */}
|
|
|
|
|
|
<TableHead className="w-[60px]"></TableHead>
|
|
|
|
|
|
</TableRow>
|
|
|
|
|
|
</TableHeader>
|
|
|
|
|
|
<TableBody>
|
2026-01-08 16:32:10 +08:00
|
|
|
|
{items.map((item, index) => {
|
|
|
|
|
|
// 計算轉換數量
|
|
|
|
|
|
const convertedQuantity = item.selectedUnit === 'large' && item.conversionRate
|
|
|
|
|
|
? item.quantity * item.conversionRate
|
|
|
|
|
|
: item.quantity;
|
2025-12-30 15:03:19 +08:00
|
|
|
|
|
2026-01-08 16:32:10 +08:00
|
|
|
|
return (
|
|
|
|
|
|
<TableRow key={item.tempId}>
|
|
|
|
|
|
{/* 商品 */}
|
|
|
|
|
|
<TableCell>
|
2026-01-09 10:18:52 +08:00
|
|
|
|
<SearchableSelect
|
2026-01-08 16:32:10 +08:00
|
|
|
|
value={item.productId}
|
|
|
|
|
|
onValueChange={(value) =>
|
|
|
|
|
|
handleProductChange(item.tempId, value)
|
|
|
|
|
|
}
|
2026-01-09 10:18:52 +08:00
|
|
|
|
options={products.map((p) => ({ label: p.name, value: p.id }))}
|
|
|
|
|
|
placeholder="選擇商品"
|
|
|
|
|
|
searchPlaceholder="搜尋商品..."
|
|
|
|
|
|
className="border-gray-300"
|
|
|
|
|
|
/>
|
2026-01-08 16:32:10 +08:00
|
|
|
|
{errors[`item-${index}-product`] && (
|
|
|
|
|
|
<p className="text-xs text-red-500 mt-1">
|
|
|
|
|
|
{errors[`item-${index}-product`]}
|
|
|
|
|
|
</p>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</TableCell>
|
2025-12-30 15:03:19 +08:00
|
|
|
|
|
2026-01-08 16:32:10 +08:00
|
|
|
|
{/* 數量 */}
|
|
|
|
|
|
<TableCell>
|
|
|
|
|
|
<Input
|
|
|
|
|
|
type="number"
|
|
|
|
|
|
min="1"
|
|
|
|
|
|
value={item.quantity || ""}
|
|
|
|
|
|
onChange={(e) =>
|
|
|
|
|
|
handleUpdateItem(item.tempId, {
|
|
|
|
|
|
quantity: parseFloat(e.target.value) || 0,
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
className="border-gray-300"
|
|
|
|
|
|
/>
|
|
|
|
|
|
{errors[`item-${index}-quantity`] && (
|
|
|
|
|
|
<p className="text-xs text-red-500 mt-1">
|
|
|
|
|
|
{errors[`item-${index}-quantity`]}
|
|
|
|
|
|
</p>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</TableCell>
|
2025-12-30 15:03:19 +08:00
|
|
|
|
|
2026-01-08 16:32:10 +08:00
|
|
|
|
{/* 單位 */}
|
|
|
|
|
|
<TableCell>
|
|
|
|
|
|
{item.largeUnit ? (
|
2026-01-09 10:18:52 +08:00
|
|
|
|
<SearchableSelect
|
|
|
|
|
|
value={item.selectedUnit || ""}
|
2026-01-08 16:32:10 +08:00
|
|
|
|
onValueChange={(value) =>
|
|
|
|
|
|
handleUpdateItem(item.tempId, {
|
|
|
|
|
|
selectedUnit: value as 'base' | 'large',
|
|
|
|
|
|
unit: value === 'base' ? item.baseUnit : item.largeUnit
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
2026-01-09 10:18:52 +08:00
|
|
|
|
options={[
|
|
|
|
|
|
{ label: item.baseUnit || "個", value: "base" },
|
|
|
|
|
|
{ label: item.largeUnit || "", value: "large" }
|
|
|
|
|
|
]}
|
|
|
|
|
|
className="border-gray-300"
|
|
|
|
|
|
/>
|
2026-01-08 16:32:10 +08:00
|
|
|
|
) : (
|
|
|
|
|
|
<Input
|
|
|
|
|
|
value={item.baseUnit || "個"}
|
|
|
|
|
|
disabled
|
|
|
|
|
|
className="bg-gray-50 border-gray-200"
|
|
|
|
|
|
/>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</TableCell>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 轉換數量 */}
|
|
|
|
|
|
<TableCell>
|
|
|
|
|
|
<div className="flex items-center text-gray-700 font-medium bg-gray-50 px-3 py-2 rounded-md border border-gray-200">
|
|
|
|
|
|
<span>{convertedQuantity}</span>
|
|
|
|
|
|
<span className="ml-1 text-gray-500 text-sm">{item.baseUnit || "個"}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</TableCell>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 效期 */}
|
|
|
|
|
|
{/* <TableCell>
|
2025-12-30 15:03:19 +08:00
|
|
|
|
<div className="relative">
|
|
|
|
|
|
<Input
|
|
|
|
|
|
type="date"
|
|
|
|
|
|
value={item.expiryDate}
|
|
|
|
|
|
onChange={(e) =>
|
|
|
|
|
|
handleUpdateItem(item.tempId, {
|
|
|
|
|
|
expiryDate: e.target.value,
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
className="border-gray-300"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</TableCell> */}
|
|
|
|
|
|
|
2026-01-08 16:32:10 +08:00
|
|
|
|
{/* 批號 */}
|
|
|
|
|
|
{/* <TableCell>
|
2025-12-30 15:03:19 +08:00
|
|
|
|
<Input
|
|
|
|
|
|
value={item.batchNumber}
|
|
|
|
|
|
onChange={(e) =>
|
|
|
|
|
|
handleBatchNumberChange(item.tempId, e.target.value)
|
|
|
|
|
|
}
|
|
|
|
|
|
className="border-gray-300"
|
|
|
|
|
|
placeholder="系統自動生成"
|
|
|
|
|
|
/>
|
|
|
|
|
|
{errors[`item-${index}-batch`] && (
|
|
|
|
|
|
<p className="text-xs text-red-500 mt-1">
|
|
|
|
|
|
{errors[`item-${index}-batch`]}
|
|
|
|
|
|
</p>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</TableCell> */}
|
|
|
|
|
|
|
2026-01-08 16:32:10 +08:00
|
|
|
|
{/* 刪除按鈕 */}
|
|
|
|
|
|
<TableCell>
|
|
|
|
|
|
<Button
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
variant="ghost"
|
|
|
|
|
|
size="icon"
|
|
|
|
|
|
onClick={() => handleRemoveItem(item.tempId)}
|
|
|
|
|
|
className="hover:bg-red-50 hover:text-red-600 h-8 w-8"
|
|
|
|
|
|
>
|
|
|
|
|
|
<Trash2 className="h-4 w-4" />
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
</TableCell>
|
|
|
|
|
|
</TableRow>
|
|
|
|
|
|
);
|
|
|
|
|
|
})}
|
2025-12-30 15:03:19 +08:00
|
|
|
|
</TableBody>
|
|
|
|
|
|
</Table>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
) : (
|
|
|
|
|
|
<div className="border border-dashed rounded-lg p-12 text-center text-gray-500 bg-gray-50/30">
|
|
|
|
|
|
<p className="text-base font-medium">尚無明細</p>
|
|
|
|
|
|
<p className="text-sm mt-1">請點擊右上方「新增明細」按鈕加入商品</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</AuthenticatedLayout>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|