Files
star-erp/resources/js/Pages/Warehouse/EditInventory.tsx
sky121113 7367577f6a
All checks were successful
Koori-ERP-Deploy-System / deploy-demo (push) Successful in 59s
Koori-ERP-Deploy-System / deploy-production (push) Has been skipped
feat: 統一採購單與操作紀錄 UI、增強各模組操作紀錄功能
- 統一採購單篩選列與表單樣式 (移除舊元件、標準化 Input)
- 增強操作紀錄功能 (加入篩選、快照、詳細異動比對)
- 統一刪除確認視窗與按鈕樣式
- 修復庫存編輯頁面樣式
- 實作採購單品項異動紀錄
- 實作角色分配異動紀錄
- 擴充供應商與倉庫模組紀錄
2026-01-19 17:07:45 +08:00

264 lines
12 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 { useState } from "react";
import { Head, Link, useForm } from "@inertiajs/react";
import AuthenticatedLayout from "@/Layouts/AuthenticatedLayout";
import { Button } from "@/Components/ui/button";
import { Input } from "@/Components/ui/input";
import { Label } from "@/Components/ui/label";
import { ArrowLeft, Save, Trash2, Boxes } from "lucide-react";
import { Warehouse, WarehouseInventory } from "@/types/warehouse";
import { toast } from "sonner";
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from "@/Components/ui/alert-dialog";
import TransactionTable, { Transaction } from "@/Components/Warehouse/Inventory/TransactionTable";
import { getShowBreadcrumbs } from "@/utils/breadcrumb";
interface Props {
warehouse: Warehouse;
inventory: WarehouseInventory;
transactions: Transaction[];
}
export default function EditInventory({ warehouse, inventory, transactions = [] }: Props) {
const { data, setData, put, delete: destroy, processing, errors } = useForm({
quantity: inventory.quantity,
batchNumber: inventory.batchNumber || "",
expiryDate: inventory.expiryDate || "",
lastInboundDate: inventory.lastInboundDate || "",
lastOutboundDate: inventory.lastOutboundDate || "",
// 為了記錄異動原因,還是需要傳這兩個欄位,雖然 UI 上原本的 EditPage 沒有原因輸入框
// 但為了符合我們後端的交易紀錄邏輯,我們可能需要預設一個,或者偷加一個欄位?
// 原 source code 沒有原因欄位。
// 我們可以預設 reason 為 "手動編輯更新"
reason: "編輯頁面手動更新",
});
const [showDeleteDialog, setShowDeleteDialog] = useState(false);
const handleSave = () => {
if (data.quantity < 0) {
toast.error("庫存數量不可為負數");
return;
}
put(route("warehouses.inventory.update", { warehouse: warehouse.id, inventory: inventory.id }), {
onSuccess: () => {
toast.success("庫存資料已更新");
},
onError: () => {
toast.error("更新失敗,請檢查欄位");
}
});
};
const handleDelete = () => {
destroy(route("warehouses.inventory.destroy", { warehouse: warehouse.id, inventory: inventory.id }), {
onSuccess: () => {
toast.success("庫存品項已刪除");
setShowDeleteDialog(false);
},
onError: () => {
toast.error("刪除失敗");
}
});
};
return (
<AuthenticatedLayout breadcrumbs={getShowBreadcrumbs("warehouses", "修正庫存")}>
<Head title={`編輯庫存 - ${inventory.productName} `} />
<div className="container mx-auto p-6 max-w-4xl">
{/* 頁面標題與麵包屑 */}
<div className="mb-6">
<Link href={`/warehouses/${warehouse.id}/inventory`}>
<Button
variant="outline"
className="gap-2 button-outlined-primary mb-6"
>
<ArrowLeft className="h-4 w-4" />
</Button>
</Link >
<div className="flex items-center justify-between">
<div>
<h1 className="text-2xl font-bold text-grey-0 flex items-center gap-2">
<Boxes className="h-6 w-6 text-primary-main" />
</h1>
<p className="text-gray-500 mt-1">
<span className="font-medium text-gray-900">{warehouse.name}</span>
</p>
</div>
<div className="flex gap-3">
<Button
onClick={() => setShowDeleteDialog(true)}
variant="outline"
className="button-outlined-error"
>
<Trash2 className="mr-2 h-4 w-4" />
</Button>
<Button onClick={handleSave} className="button-filled-primary" disabled={processing}>
<Save className="mr-2 h-4 w-4" />
</Button>
</div>
</div>
</div >
{/* 表單內容 */}
< div className="bg-white rounded-lg shadow-sm border p-6 mb-6" >
<div className="space-y-6">
{/* 商品基本資訊 */}
<div className="space-y-4">
<h3 className="font-medium border-b pb-2 text-lg"></h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="space-y-2">
<Label htmlFor="productName">
<span className="text-red-500">*</span>
</Label>
<Input
id="productName"
value={inventory.productName}
disabled
className="bg-gray-100"
/>
<p className="text-sm text-gray-500">
</p>
</div>
<div className="space-y-2">
<Label htmlFor="batchNumber"></Label>
<Input
id="batchNumber"
type="text"
value={data.batchNumber}
onChange={(e) => setData("batchNumber", e.target.value)}
placeholder="例FL20251101"
className="border-gray-300"
// 目前後端可能尚未支援儲存,但依需求顯示
/>
</div>
</div>
</div>
{/* 庫存數量 */}
<div className="space-y-4">
<h3 className="font-medium border-b pb-2 text-lg"></h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="space-y-2">
<Label htmlFor="quantity">
<span className="text-red-500">*</span>
</Label>
<Input
id="quantity"
type="number"
min="0"
step="0.01"
value={data.quantity}
onChange={(e) =>
setData("quantity", parseFloat(e.target.value) || 0)
}
placeholder="0"
className={`border-gray-300 ${errors.quantity ? "border-red-500" : ""}`}
/>
{errors.quantity && <p className="text-xs text-red-500">{errors.quantity}</p>}
<p className="text-sm text-gray-500">
</p>
</div>
</div>
</div>
{/* 日期資訊 */}
<div className="space-y-4">
<h3 className="font-medium border-b pb-2 text-lg"></h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="space-y-2">
<Label htmlFor="expiryDate"></Label>
<Input
id="expiryDate"
type="date"
value={data.expiryDate}
onChange={(e) => setData("expiryDate", e.target.value)}
className="border-gray-300"
/>
</div>
<div className="space-y-2">
<Label htmlFor="lastInboundDate"></Label>
<Input
id="lastInboundDate"
type="date"
value={data.lastInboundDate}
onChange={(e) =>
setData("lastInboundDate", e.target.value)
}
className="border-gray-300"
/>
</div>
<div className="space-y-2">
<Label htmlFor="lastOutboundDate"></Label>
<Input
id="lastOutboundDate"
type="date"
value={data.lastOutboundDate}
onChange={(e) =>
setData("lastOutboundDate", e.target.value)
}
className="border-gray-300"
/>
</div>
</div>
</div>
</div>
</div >
{/* 庫存異動紀錄 */}
< div className="bg-white rounded-lg shadow-sm border p-6" >
<h3 className="font-medium text-lg border-b pb-4 mb-4"></h3>
<TransactionTable transactions={transactions} />
</div >
{/* 刪除確認對話框 */}
< AlertDialog open={showDeleteDialog} onOpenChange={setShowDeleteDialog} >
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle></AlertDialogTitle>
<AlertDialogDescription>
{inventory.productName}
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel className="button-outlined-primary">
</AlertDialogCancel>
<AlertDialogAction
onClick={handleDelete}
className="button-filled-error"
>
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog >
</div >
</AuthenticatedLayout >
);
}