317 lines
11 KiB
TypeScript
317 lines
11 KiB
TypeScript
"use client"
|
||
|
||
import * as React from "react"
|
||
import { CircleAlertIcon, Loader2Icon } from "lucide-react"
|
||
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"
|
||
import { Button } from "@/components/ui/button"
|
||
import {
|
||
Dialog,
|
||
DialogContent,
|
||
DialogDescription,
|
||
DialogFooter,
|
||
DialogHeader,
|
||
DialogTitle,
|
||
} from "@/components/ui/dialog"
|
||
import { Field, FieldGroup, FieldLabel } from "@/components/ui/field"
|
||
import { Input } from "@/components/ui/input"
|
||
import {
|
||
Select,
|
||
SelectContent,
|
||
SelectGroup,
|
||
SelectItem,
|
||
SelectTrigger,
|
||
SelectValue,
|
||
} from "@/components/ui/select"
|
||
import {
|
||
createAccountAction,
|
||
updateAccountAction,
|
||
type AccountInput,
|
||
type AccountWithChannels,
|
||
} from "@/lib/actions/account"
|
||
|
||
type AccountType = "BANK" | "E_WALLET" | "CASH"
|
||
const accountLabels = {
|
||
BANK: "银行账户",
|
||
E_WALLET: "电子钱包",
|
||
CASH: "现金",
|
||
} as const
|
||
const balanceLabels = {
|
||
ASSET: "资产类",
|
||
LIABILITY: "负债类",
|
||
EQUITY: "权益类",
|
||
} as const
|
||
|
||
export function AccountDialog({
|
||
open,
|
||
onOpenChange,
|
||
account,
|
||
onSuccess,
|
||
}: {
|
||
open: boolean
|
||
onOpenChange: (open: boolean) => void
|
||
account?: AccountWithChannels | null
|
||
onSuccess: () => void
|
||
}) {
|
||
const editing = Boolean(account)
|
||
const [accountType, setAccountType] = React.useState<AccountType>(
|
||
account?.accountType || "BANK"
|
||
)
|
||
const [balanceType, setBalanceType] = React.useState<
|
||
"ASSET" | "LIABILITY" | "EQUITY"
|
||
>(account?.balanceType || "ASSET")
|
||
const [name, setName] = React.useState(account?.name || "")
|
||
const [primaryCurrency, setPrimaryCurrency] = React.useState(
|
||
account?.primaryCurrency || ""
|
||
)
|
||
const [supportedCurrencies, setSupportedCurrencies] = React.useState(
|
||
account?.supportedCurrencies?.join(", ") || ""
|
||
)
|
||
const [remark, setRemark] = React.useState(account?.remark || "")
|
||
const [issuerName, setIssuerName] = React.useState(account?.issuerName || "")
|
||
const [accountNumber, setAccountNumber] = React.useState(
|
||
account?.accountNumber || ""
|
||
)
|
||
const [platform, setPlatform] = React.useState(account?.platform || "")
|
||
const [accountId, setAccountId] = React.useState(account?.accountId || "")
|
||
const [location, setLocation] = React.useState(account?.location || "")
|
||
const [isPending, setIsPending] = React.useState(false)
|
||
const [errorMessage, setErrorMessage] = React.useState<string | null>(null)
|
||
const submit = async (event: React.FormEvent<HTMLFormElement>) => {
|
||
event.preventDefault()
|
||
setErrorMessage(null)
|
||
if (!name.trim()) {
|
||
setErrorMessage("请输入账户名称")
|
||
return
|
||
}
|
||
const currencies = supportedCurrencies
|
||
.split(/[,,\s]+/)
|
||
.map((item) => item.trim().toUpperCase())
|
||
.filter(Boolean)
|
||
const payload: AccountInput = {
|
||
name: name.trim(),
|
||
accountType,
|
||
balanceType,
|
||
primaryCurrency: primaryCurrency.trim().toUpperCase() || null,
|
||
supportedCurrencies: currencies.length ? currencies : null,
|
||
remark: remark.trim() || null,
|
||
issuerName: accountType === "BANK" ? issuerName.trim() || null : null,
|
||
accountNumber:
|
||
accountType === "BANK" ? accountNumber.trim() || null : null,
|
||
platform: accountType === "E_WALLET" ? platform.trim() || null : null,
|
||
accountId: accountType === "E_WALLET" ? accountId.trim() || null : null,
|
||
location: accountType === "CASH" ? location.trim() || null : null,
|
||
}
|
||
setIsPending(true)
|
||
try {
|
||
const result =
|
||
editing && account
|
||
? await updateAccountAction(account.id, payload)
|
||
: await createAccountAction(payload)
|
||
if (!result.success) {
|
||
setErrorMessage(result.error || "保存失败,请检查输入")
|
||
return
|
||
}
|
||
onOpenChange(false)
|
||
onSuccess()
|
||
} catch {
|
||
setErrorMessage("网络异常,提交失败")
|
||
} finally {
|
||
setIsPending(false)
|
||
}
|
||
}
|
||
return (
|
||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||
<DialogContent className="sm:max-w-lg">
|
||
<DialogHeader>
|
||
<DialogTitle>{editing ? "编辑资金账户" : "新建资金账户"}</DialogTitle>
|
||
<DialogDescription>
|
||
填写账户主体、分类及其识别信息。
|
||
</DialogDescription>
|
||
</DialogHeader>
|
||
<form id="account-form" onSubmit={submit}>
|
||
<FieldGroup>
|
||
{errorMessage && (
|
||
<Alert variant="destructive">
|
||
<CircleAlertIcon />
|
||
<AlertTitle>无法保存账户</AlertTitle>
|
||
<AlertDescription>{errorMessage}</AlertDescription>
|
||
</Alert>
|
||
)}
|
||
<FieldGroup className="grid gap-3 sm:grid-cols-2">
|
||
<Field>
|
||
<FieldLabel htmlFor="accountType">账户类型</FieldLabel>
|
||
<Select
|
||
value={accountType}
|
||
onValueChange={(value) =>
|
||
value && setAccountType(value as AccountType)
|
||
}
|
||
>
|
||
<SelectTrigger id="accountType">
|
||
<SelectValue>{accountLabels[accountType]}</SelectValue>
|
||
</SelectTrigger>
|
||
<SelectContent>
|
||
<SelectGroup>
|
||
<SelectItem value="BANK">银行账户</SelectItem>
|
||
<SelectItem value="E_WALLET">电子钱包</SelectItem>
|
||
<SelectItem value="CASH">现金</SelectItem>
|
||
</SelectGroup>
|
||
</SelectContent>
|
||
</Select>
|
||
</Field>
|
||
<Field>
|
||
<FieldLabel htmlFor="balanceType">余额分类</FieldLabel>
|
||
<Select
|
||
value={balanceType}
|
||
onValueChange={(value) =>
|
||
value && setBalanceType(value as typeof balanceType)
|
||
}
|
||
>
|
||
<SelectTrigger id="balanceType">
|
||
<SelectValue>{balanceLabels[balanceType]}</SelectValue>
|
||
</SelectTrigger>
|
||
<SelectContent>
|
||
<SelectGroup>
|
||
<SelectItem value="ASSET">资产类</SelectItem>
|
||
<SelectItem value="LIABILITY">负债类</SelectItem>
|
||
<SelectItem value="EQUITY">权益类</SelectItem>
|
||
</SelectGroup>
|
||
</SelectContent>
|
||
</Select>
|
||
</Field>
|
||
</FieldGroup>
|
||
<FieldGroup>
|
||
<Field>
|
||
<FieldLabel htmlFor="accountName">账户名称</FieldLabel>
|
||
<Input
|
||
id="accountName"
|
||
value={name}
|
||
onChange={(event) => setName(event.target.value)}
|
||
placeholder="例如:招商银行个人消费卡账户"
|
||
disabled={isPending}
|
||
required
|
||
/>
|
||
</Field>
|
||
<FieldGroup className="grid gap-3 sm:grid-cols-2">
|
||
<Field>
|
||
<FieldLabel htmlFor="primaryCurrency">主币种</FieldLabel>
|
||
<Input
|
||
id="primaryCurrency"
|
||
value={primaryCurrency}
|
||
onChange={(event) => setPrimaryCurrency(event.target.value)}
|
||
placeholder="例如:HKD"
|
||
disabled={isPending}
|
||
/>
|
||
</Field>
|
||
<Field>
|
||
<FieldLabel htmlFor="supportedCurrencies">
|
||
支持币种
|
||
</FieldLabel>
|
||
<Input
|
||
id="supportedCurrencies"
|
||
value={supportedCurrencies}
|
||
onChange={(event) =>
|
||
setSupportedCurrencies(event.target.value)
|
||
}
|
||
placeholder="用逗号分隔"
|
||
disabled={isPending}
|
||
/>
|
||
</Field>
|
||
</FieldGroup>
|
||
</FieldGroup>
|
||
<FieldGroup className="grid gap-3 sm:grid-cols-2">
|
||
{accountType === "BANK" && (
|
||
<>
|
||
<Field>
|
||
<FieldLabel htmlFor="issuerName">银行机构</FieldLabel>
|
||
<Input
|
||
id="issuerName"
|
||
value={issuerName}
|
||
onChange={(event) => setIssuerName(event.target.value)}
|
||
placeholder="例如:汇丰银行"
|
||
disabled={isPending}
|
||
/>
|
||
</Field>
|
||
<Field>
|
||
<FieldLabel htmlFor="accountNumber">
|
||
账户号码或尾号
|
||
</FieldLabel>
|
||
<Input
|
||
id="accountNumber"
|
||
value={accountNumber}
|
||
onChange={(event) => setAccountNumber(event.target.value)}
|
||
placeholder="可填写完整账号或尾号"
|
||
disabled={isPending}
|
||
/>
|
||
</Field>
|
||
</>
|
||
)}
|
||
{accountType === "E_WALLET" && (
|
||
<>
|
||
<Field>
|
||
<FieldLabel htmlFor="platform">平台名称</FieldLabel>
|
||
<Input
|
||
id="platform"
|
||
value={platform}
|
||
onChange={(event) => setPlatform(event.target.value)}
|
||
placeholder="例如:支付宝"
|
||
disabled={isPending}
|
||
/>
|
||
</Field>
|
||
<Field>
|
||
<FieldLabel htmlFor="accountId">平台账户标识</FieldLabel>
|
||
<Input
|
||
id="accountId"
|
||
value={accountId}
|
||
onChange={(event) => setAccountId(event.target.value)}
|
||
placeholder="手机号、邮箱或会员号"
|
||
disabled={isPending}
|
||
/>
|
||
</Field>
|
||
</>
|
||
)}
|
||
{accountType === "CASH" && (
|
||
<Field>
|
||
<FieldLabel htmlFor="location">存放位置</FieldLabel>
|
||
<Input
|
||
id="location"
|
||
value={location}
|
||
onChange={(event) => setLocation(event.target.value)}
|
||
placeholder="例如:随身钱包"
|
||
disabled={isPending}
|
||
/>
|
||
</Field>
|
||
)}
|
||
</FieldGroup>
|
||
<Field>
|
||
<FieldLabel htmlFor="remark">备注</FieldLabel>
|
||
<Input
|
||
id="remark"
|
||
value={remark}
|
||
onChange={(event) => setRemark(event.target.value)}
|
||
placeholder="可选"
|
||
disabled={isPending}
|
||
/>
|
||
</Field>
|
||
</FieldGroup>
|
||
</form>
|
||
<DialogFooter>
|
||
<Button
|
||
type="button"
|
||
variant="outline"
|
||
onClick={() => onOpenChange(false)}
|
||
disabled={isPending}
|
||
>
|
||
取消
|
||
</Button>
|
||
<Button form="account-form" type="submit" disabled={isPending}>
|
||
{isPending && (
|
||
<Loader2Icon data-icon="inline-start" className="animate-spin" />
|
||
)}
|
||
{editing ? "保存修改" : "创建账户"}
|
||
</Button>
|
||
</DialogFooter>
|
||
</DialogContent>
|
||
</Dialog>
|
||
)
|
||
}
|