feat: 優化採購單操作紀錄與統一刪除確認 UI
- 優化採購單更新與刪除的活動紀錄邏輯 (PurchaseOrderController) - 整合更新異動為單一紀錄,包含品項差異 - 刪除時記錄當下品項快照 - 統一採購單刪除確認介面,使用 AlertDialog 取代原生 confirm (PurchaseOrderActions) - Refactor: 將 ActivityDetailDialog 移至 Components/ActivityLog 並優化樣式與大數據顯示 - 調整 UI 文字:將「總金額」統一為「小計」 - 其他模型與 Controller 的活動紀錄支援更新
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
*/
|
||||
|
||||
import { ArrowLeft, Plus, Info, ShoppingCart } from "lucide-react";
|
||||
import { useEffect } from "react";
|
||||
import { Button } from "@/Components/ui/button";
|
||||
import { Input } from "@/Components/ui/input";
|
||||
import { Textarea } from "@/Components/ui/textarea";
|
||||
@@ -58,11 +59,23 @@ export default function CreatePurchaseOrder({
|
||||
setInvoiceNumber,
|
||||
setInvoiceDate,
|
||||
setInvoiceAmount,
|
||||
taxAmount,
|
||||
setTaxAmount,
|
||||
isTaxManual,
|
||||
setIsTaxManual,
|
||||
} = usePurchaseOrderForm({ order, suppliers });
|
||||
|
||||
|
||||
const totalAmount = calculateTotalAmount(items);
|
||||
|
||||
// Auto-calculate tax if not manual
|
||||
useEffect(() => {
|
||||
if (!isTaxManual) {
|
||||
const calculatedTax = Math.round(totalAmount * 0.05);
|
||||
setTaxAmount(calculatedTax);
|
||||
}
|
||||
}, [totalAmount, isTaxManual]);
|
||||
|
||||
const handleSave = () => {
|
||||
if (!warehouseId) {
|
||||
toast.error("請選擇入庫倉庫");
|
||||
@@ -113,6 +126,7 @@ export default function CreatePurchaseOrder({
|
||||
invoice_number: invoiceNumber || null,
|
||||
invoice_date: invoiceDate || null,
|
||||
invoice_amount: invoiceAmount ? parseFloat(invoiceAmount) : null,
|
||||
tax_amount: Number(taxAmount) || 0,
|
||||
items: validItems.map(item => ({
|
||||
productId: item.productId,
|
||||
quantity: item.quantity,
|
||||
@@ -159,20 +173,20 @@ export default function CreatePurchaseOrder({
|
||||
return (
|
||||
<AuthenticatedLayout breadcrumbs={order ? getEditBreadcrumbs("purchaseOrders") : getCreateBreadcrumbs("purchaseOrders")}>
|
||||
<Head title={order ? "編輯採購單" : "建立採購單"} />
|
||||
<div className="container mx-auto p-6 max-w-5xl">
|
||||
<div className="container mx-auto p-6 max-w-7xl">
|
||||
{/* Header */}
|
||||
<div className="mb-8">
|
||||
<div className="mb-6">
|
||||
<Link href="/purchase-orders">
|
||||
<Button
|
||||
variant="outline"
|
||||
className="gap-2 button-outlined-primary mb-6"
|
||||
className="gap-2 button-outlined-primary mb-4"
|
||||
>
|
||||
<ArrowLeft className="h-4 w-4" />
|
||||
返回列表
|
||||
</Button>
|
||||
</Link>
|
||||
|
||||
<div className="mb-6">
|
||||
<div className="mb-4">
|
||||
<h1 className="text-2xl font-bold text-grey-0 flex items-center gap-2">
|
||||
<ShoppingCart className="h-6 w-6 text-primary-main" />
|
||||
{order ? "編輯採購單" : "建立採購單"}
|
||||
@@ -183,7 +197,7 @@ export default function CreatePurchaseOrder({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-8">
|
||||
<div className="space-y-6">
|
||||
{/* 步驟一:基本資訊 */}
|
||||
<div className="bg-white rounded-lg border shadow-sm overflow-hidden">
|
||||
<div className="p-6 bg-gray-50/50 border-b flex items-center gap-3">
|
||||
@@ -191,7 +205,7 @@ export default function CreatePurchaseOrder({
|
||||
<h2 className="text-lg font-bold">基本資訊</h2>
|
||||
</div>
|
||||
|
||||
<div className="p-8 space-y-8">
|
||||
<div className="p-6 space-y-6">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
|
||||
<div className="space-y-3">
|
||||
<label className="text-sm font-bold text-gray-700">
|
||||
@@ -267,7 +281,7 @@ export default function CreatePurchaseOrder({
|
||||
<span className="text-sm text-gray-500">(選填)</span>
|
||||
</div>
|
||||
|
||||
<div className="p-8 space-y-8">
|
||||
<div className="p-6 space-y-6">
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
|
||||
<div className="space-y-3">
|
||||
<label className="text-sm font-bold text-gray-700">
|
||||
@@ -335,7 +349,7 @@ export default function CreatePurchaseOrder({
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="p-8">
|
||||
<div className="p-6">
|
||||
{!hasSupplier && (
|
||||
<Alert className="mb-6 bg-amber-50 border-amber-200 text-amber-800">
|
||||
<Info className="h-4 w-4 text-amber-600" />
|
||||
@@ -355,10 +369,53 @@ export default function CreatePurchaseOrder({
|
||||
/>
|
||||
|
||||
{hasSupplier && items.length > 0 && (
|
||||
<div className="mt-8 flex justify-end">
|
||||
<div className="bg-primary/5 px-8 py-5 rounded-xl border border-primary/10 inline-flex flex-col items-end min-w-[240px]">
|
||||
<span className="text-sm text-gray-500 font-medium mb-1">採購預估總額</span>
|
||||
<span className="text-3xl font-black text-primary">{formatCurrency(totalAmount)}</span>
|
||||
<div className="mt-6 flex justify-end">
|
||||
<div className="w-full max-w-sm bg-primary/5 px-6 py-4 rounded-xl border border-primary/10 flex flex-col gap-3">
|
||||
<div className="flex justify-between items-center w-full">
|
||||
<span className="text-sm text-gray-500 font-medium">小計</span>
|
||||
<span className="text-lg font-bold text-gray-700">{formatCurrency(totalAmount)}</span>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between items-center w-full gap-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm text-gray-500 font-medium">稅額 (5%)</span>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-6 w-6 p-0 text-gray-400 hover:text-primary"
|
||||
title="重設為自動計算 (5%)"
|
||||
onClick={() => {
|
||||
const autoTax = Math.round(totalAmount * 0.05);
|
||||
setTaxAmount(autoTax);
|
||||
setIsTaxManual(false);
|
||||
toast.success("已重設為自動計算 (5%)");
|
||||
}}
|
||||
>
|
||||
↺
|
||||
</Button>
|
||||
</div>
|
||||
<div className="relative w-32">
|
||||
<Input
|
||||
type="number"
|
||||
value={taxAmount}
|
||||
onChange={(e) => {
|
||||
setTaxAmount(e.target.value);
|
||||
setIsTaxManual(true);
|
||||
}}
|
||||
className="text-right h-9 bg-white"
|
||||
placeholder="0"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="h-px bg-primary/10 w-full my-1"></div>
|
||||
|
||||
<div className="flex justify-between items-end w-full">
|
||||
<span className="text-sm text-gray-500 font-medium mb-1">總計 (含稅)</span>
|
||||
<span className="text-2xl font-black text-primary">
|
||||
{formatCurrency(totalAmount + (Number(taxAmount) || 0))}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
@@ -367,9 +424,9 @@ export default function CreatePurchaseOrder({
|
||||
</div>
|
||||
|
||||
{/* 底部按鈕 */}
|
||||
<div className="flex items-center justify-end gap-4 py-8 border-t border-gray-100 mt-8">
|
||||
<div className="flex items-center justify-end gap-4 py-6 border-t border-gray-100 mt-6">
|
||||
<Link href="/purchase-orders">
|
||||
<Button variant="ghost" className="h-12 px-8 text-gray-500 hover:text-gray-700">
|
||||
<Button variant="ghost" className="h-11 px-6 text-gray-500 hover:text-gray-700">
|
||||
取消
|
||||
</Button>
|
||||
</Link>
|
||||
|
||||
@@ -143,11 +143,21 @@ export default function ViewPurchaseOrderPage({ order }: Props) {
|
||||
items={order.items}
|
||||
isReadOnly={true}
|
||||
/>
|
||||
<div className="mt-4 flex justify-end items-center gap-4 border-t pt-4">
|
||||
<span className="text-gray-600 font-medium">總金額</span>
|
||||
<span className="text-xl font-bold text-primary">
|
||||
{formatCurrency(order.totalAmount)}
|
||||
</span>
|
||||
<div className="mt-4 flex flex-col items-end gap-2 border-t pt-4">
|
||||
<div className="flex items-center gap-8 text-gray-600">
|
||||
<span className="font-medium">小計</span>
|
||||
<span>{formatCurrency(order.totalAmount)}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-8 text-gray-600">
|
||||
<span className="font-medium">稅額</span>
|
||||
<span>{formatCurrency(order.tax_amount || 0)}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-8 pt-2 mt-2 border-t border-gray-100">
|
||||
<span className="font-bold text-lg">總計</span>
|
||||
<span className="text-xl font-bold text-primary">
|
||||
{formatCurrency(order.grand_total || (order.totalAmount + (order.tax_amount || 0)))}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user