Files
star-erp/source-code/ERP(B-aa)-管理採購單/src/components/purchase-order/PaymentDialog.tsx
2025-12-30 15:03:19 +08:00

351 lines
11 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, useEffect } from "react";
import { DollarSign } from "lucide-react";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from "../ui/dialog";
import { Button } from "../ui/button";
import { Label } from "../ui/label";
import { Input } from "../ui/input";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "../ui/select";
import { Switch } from "../ui/switch";
import { PAYMENT_METHODS, INVOICE_TYPES } from "../../constants/purchase-order";
import type { PurchaseOrder, PaymentMethod, InvoiceType, PaymentInfo } from "../../types/purchase-order";
interface PaymentDialogProps {
order: PurchaseOrder;
open: boolean;
onOpenChange: (open: boolean) => void;
onConfirm: (orderId: string, paymentInfo: PaymentInfo) => void;
}
export function PaymentDialog({
order,
open,
onOpenChange,
onConfirm,
}: PaymentDialogProps) {
// 付款資訊
const [paymentMethod, setPaymentMethod] = useState<PaymentMethod>("bank_transfer");
const [paymentDate, setPaymentDate] = useState(
new Date().toISOString().split("T")[0]
);
const [actualAmount, setActualAmount] = useState(order.totalAmount.toString());
// 發票資訊
const [hasInvoice, setHasInvoice] = useState(false);
const [invoiceNumber, setInvoiceNumber] = useState("");
const [invoiceAmount, setInvoiceAmount] = useState(order.totalAmount.toString());
const [invoiceDate, setInvoiceDate] = useState(
new Date().toISOString().split("T")[0]
);
const [invoiceType, setInvoiceType] = useState<InvoiceType>("duplicate");
const [companyName, setCompanyName] = useState("");
const [taxId, setTaxId] = useState("");
// 當訂單變更時重置表單
useEffect(() => {
if (open) {
setActualAmount(order.totalAmount.toString());
setInvoiceAmount(order.totalAmount.toString());
}
}, [order.totalAmount, open]);
const handleConfirm = () => {
const paymentInfo: PaymentInfo = {
paymentMethod,
paymentDate,
actualAmount: parseFloat(actualAmount),
paidBy: "付款人名稱", // 實際應從登入使用者取得
paidAt: new Date().toLocaleString("zh-TW"),
hasInvoice,
};
if (hasInvoice) {
paymentInfo.invoice = {
invoiceNumber,
invoiceAmount: parseFloat(invoiceAmount),
invoiceDate,
invoiceType,
};
if (invoiceType === "triplicate") {
paymentInfo.invoice.companyName = companyName;
paymentInfo.invoice.taxId = taxId;
}
}
onConfirm(order.id, paymentInfo);
handleClose();
};
const handleClose = () => {
// 重置表單
setPaymentMethod("bank_transfer");
setPaymentDate(new Date().toISOString().split("T")[0]);
setActualAmount(order.totalAmount.toString());
setHasInvoice(false);
setInvoiceNumber("");
setInvoiceAmount(order.totalAmount.toString());
setInvoiceDate(new Date().toISOString().split("T")[0]);
setInvoiceType("duplicate");
setCompanyName("");
setTaxId("");
onOpenChange(false);
};
const isValid = () => {
// 驗證付款資訊
if (!paymentMethod || !paymentDate || !actualAmount) {
return false;
}
const amount = parseFloat(actualAmount);
if (isNaN(amount) || amount <= 0) {
return false;
}
// 如果有發票,驗證發票資訊
if (hasInvoice) {
if (!invoiceNumber || !invoiceDate || !invoiceType) {
return false;
}
const invAmount = parseFloat(invoiceAmount);
if (isNaN(invAmount) || invAmount <= 0) {
return false;
}
// 如果是三聯式,必須填寫公司抬頭和統編
if (invoiceType === "triplicate") {
if (!companyName || !taxId) {
return false;
}
}
}
return true;
};
return (
<Dialog open={open} onOpenChange={handleClose}>
<DialogContent className="sm:max-w-[600px] max-h-[90vh] overflow-y-auto">
<DialogHeader>
<DialogTitle className="flex items-center gap-2">
<DollarSign className="h-5 w-5" />
</DialogTitle>
<DialogDescription>
{order.poNumber}
</DialogDescription>
</DialogHeader>
<div className="space-y-6 py-4">
{/* 採購單資訊摘要 */}
<div className="bg-muted p-4 rounded-lg space-y-2">
<div className="flex justify-between text-sm">
<span className="text-muted-foreground"></span>
<span className="font-medium">{order.supplierName}</span>
</div>
<div className="flex justify-between text-sm">
<span className="text-muted-foreground"></span>
<span className="font-medium">
${order.totalAmount.toLocaleString()}
</span>
</div>
</div>
{/* 付款資訊區塊 */}
<div className="space-y-4">
<h3 className="font-medium"></h3>
{/* 付款方式 */}
<div className="space-y-2">
<Label htmlFor="payment-method"> *</Label>
<Select value={paymentMethod} onValueChange={(value) => setPaymentMethod(value as PaymentMethod)}>
<SelectTrigger
id="payment-method"
className="h-11 border-2 border-input"
>
<SelectValue />
</SelectTrigger>
<SelectContent>
{Object.entries(PAYMENT_METHODS).map(([key, label]) => (
<SelectItem key={key} value={key}>
{label}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
{/* 付款日期 */}
<div className="space-y-2">
<Label htmlFor="payment-date"> *</Label>
<Input
id="payment-date"
type="date"
value={paymentDate}
onChange={(e) => setPaymentDate(e.target.value)}
className="h-11 border-2 border-input"
/>
</div>
{/* 實際付款金額 */}
<div className="space-y-2">
<Label htmlFor="actual-amount"> *</Label>
<Input
id="actual-amount"
type="number"
min="0"
step="0.01"
value={actualAmount}
onChange={(e) => setActualAmount(e.target.value)}
className="h-11 border-2 border-input"
/>
</div>
</div>
{/* 發票資訊區塊 */}
<div className="space-y-4 pt-4 border-t">
<div className="flex items-center justify-between">
<div className="space-y-1">
<h3 className="font-medium"></h3>
<p className="text-sm text-muted-foreground">
</p>
</div>
<Switch
checked={hasInvoice}
onCheckedChange={setHasInvoice}
/>
</div>
{/* 發票欄位(條件顯示) */}
{hasInvoice && (
<div className="space-y-4 bg-muted p-4 rounded-lg">
{/* 發票號碼 */}
<div className="space-y-2">
<Label htmlFor="invoice-number"> *</Label>
<Input
id="invoice-number"
value={invoiceNumber}
onChange={(e) => setInvoiceNumber(e.target.value)}
placeholder="例AA-12345678"
className="h-11 border-2 border-input bg-background"
/>
</div>
{/* 發票金額 */}
<div className="space-y-2">
<Label htmlFor="invoice-amount"> *</Label>
<Input
id="invoice-amount"
type="number"
min="0"
step="0.01"
value={invoiceAmount}
onChange={(e) => setInvoiceAmount(e.target.value)}
className="h-11 border-2 border-input bg-background"
/>
</div>
{/* 發票日期 */}
<div className="space-y-2">
<Label htmlFor="invoice-date"> *</Label>
<Input
id="invoice-date"
type="date"
value={invoiceDate}
onChange={(e) => setInvoiceDate(e.target.value)}
className="h-11 border-2 border-input bg-background"
/>
</div>
{/* 發票類型 */}
<div className="space-y-2">
<Label htmlFor="invoice-type"> *</Label>
<Select value={invoiceType} onValueChange={(value) => setInvoiceType(value as InvoiceType)}>
<SelectTrigger
id="invoice-type"
className="h-11 border-2 border-input bg-background"
>
<SelectValue />
</SelectTrigger>
<SelectContent>
{Object.entries(INVOICE_TYPES).map(([key, label]) => (
<SelectItem key={key} value={key}>
{label}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
{/* 三聯式專用欄位 */}
{invoiceType === "triplicate" && (
<>
<div className="space-y-2">
<Label htmlFor="company-name"> *</Label>
<Input
id="company-name"
value={companyName}
onChange={(e) => setCompanyName(e.target.value)}
placeholder="請輸入公司抬頭"
className="h-11 border-2 border-input bg-background"
/>
</div>
<div className="space-y-2">
<Label htmlFor="tax-id"> *</Label>
<Input
id="tax-id"
value={taxId}
onChange={(e) => setTaxId(e.target.value)}
placeholder="請輸入統一編號"
maxLength={8}
className="h-11 border-2 border-input bg-background"
/>
</div>
</>
)}
</div>
)}
</div>
</div>
<DialogFooter>
<Button
variant="outline"
onClick={handleClose}
className="button-outlined-primary"
>
</Button>
<Button
onClick={handleConfirm}
disabled={!isValid()}
className="button-filled-primary"
>
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}