first commit
This commit is contained in:
269
resources/js/Pages/Warehouse/EditInventory.tsx
Normal file
269
resources/js/Pages/Warehouse/EditInventory.tsx
Normal file
@@ -0,0 +1,269 @@
|
||||
|
||||
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 } 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";
|
||||
|
||||
|
||||
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>
|
||||
<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 gap-2 text-sm text-gray-500 mb-2">
|
||||
<span>商品與庫存管理</span>
|
||||
<span>/</span>
|
||||
<span>倉庫管理</span>
|
||||
<span>/</span>
|
||||
<span>庫存管理</span>
|
||||
<span>/</span>
|
||||
<span className="text-gray-900">編輯庫存品項</span>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 className="mb-2">編輯庫存品項</h1>
|
||||
<p className="text-gray-600">
|
||||
倉庫:<span className="font-medium text-gray-900">{warehouse.name}</span>
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex gap-3">
|
||||
<Button
|
||||
onClick={() => setShowDeleteDialog(true)}
|
||||
variant="outline"
|
||||
className="group border-red-200 text-red-600 hover:bg-red-50 hover:text-red-700 hover:border-red-300"
|
||||
>
|
||||
<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="button-outlined-primary"
|
||||
// 目前後端可能尚未支援儲存,但依需求顯示
|
||||
/>
|
||||
</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={`button-outlined-primary ${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="button-outlined-primary"
|
||||
/>
|
||||
</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="button-outlined-primary"
|
||||
/>
|
||||
</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="button-outlined-primary"
|
||||
/>
|
||||
</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="bg-red-600 text-white hover:bg-red-700"
|
||||
>
|
||||
確認刪除
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog >
|
||||
</div >
|
||||
</AuthenticatedLayout >
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user