diff --git a/app/dashboard/transactions/page.tsx b/app/dashboard/transactions/page.tsx new file mode 100644 index 0000000..1de16fe --- /dev/null +++ b/app/dashboard/transactions/page.tsx @@ -0,0 +1,88 @@ +import { getAssets } from '@/actions/asset'; +import { getTransactions } from '@/actions/transaction'; +import { AddTransactionDialog } from '@/components/transactions/add-transaction-dialog'; +import { + Table, + TableBody, + TableCaption, + TableCell, + TableHead, + TableHeader, + TableRow, +} from '@/components/ui/table'; + +export default async function TransactionsPage() { + const [assets, transactions] = await Promise.all([ + getAssets(), + getTransactions(), + ]); + + const typeLabels: Record = { + BUY: '买入', + SELL: '卖出', + DIVIDEND: '分红', + AIRDROP: '空投', + FEE: '手续费', + }; + + const assetMap = new Map(assets.map((a) => [a.id, a.symbol])); + + return ( +
+
+

交易流水

+ +
+ +
+ + + 共 {transactions.length} 条交易记录 + + + + 标的 + 类型 + 数量 + 价格 + 手续费 + 币种 + 执行时间 + + + + {transactions.length === 0 ? ( + + + 暂无流水,点击"添加流水"按钮录入第一笔交易 + + + ) : ( + transactions.map((tx) => { + const symbol = assetMap.get(tx.assetId) || tx.assetId; + return ( + + {symbol} + {typeLabels[tx.txType] || tx.txType} + {tx.quantity} + {tx.price} + {tx.fee} + {tx.txCurrency} + + {tx.executedAt + ? new Date(tx.executedAt).toLocaleString('zh-CN') + : '-'} + + + ); + }) + )} + +
+
+
+ ); +} diff --git a/src/components/transactions/add-transaction-dialog.tsx b/src/components/transactions/add-transaction-dialog.tsx new file mode 100644 index 0000000..44a960f --- /dev/null +++ b/src/components/transactions/add-transaction-dialog.tsx @@ -0,0 +1,263 @@ +'use client'; + +import { useState, useTransition } from 'react'; +import { useForm } from 'react-hook-form'; +import { zodResolver } from '@hookform/resolvers/zod'; +import { z } from 'zod'; +import { useRouter } from 'next/navigation'; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from '@/components/ui/dialog'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from '@/components/ui/form'; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@/components/ui/select'; +import { Plus } from 'lucide-react'; +import { createTransaction } from '@/actions/transaction'; + +const addTransactionSchema = z.object({ + assetId: z.string().uuid('请选择一个资产'), + txType: z.enum(['BUY', 'SELL', 'DIVIDEND', 'AIRDROP', 'FEE']), + quantity: z.string().regex(/^-?\d+(\.\d+)?$/, '数量必须是数字字符串'), + price: z.string().regex(/^-?\d+(\.\d+)?$/, '价格必须是数字字符串'), + fee: z.string().regex(/^-?\d+(\.\d+)?$/, '手续费必须是数字字符串'), + txCurrency: z.string().min(1, '交易币种不能为空'), + executedAt: z.date(), +}); + +type AddTransactionForm = z.infer; + +interface Asset { + id: string; + symbol: string; + type: string; + baseCurrency: string; +} + +interface AddTransactionDialogProps { + assets: Asset[]; +} + +const txTypeLabels: Record = { + BUY: '买入 (BUY)', + SELL: '卖出 (SELL)', + DIVIDEND: '分红 (DIVIDEND)', + AIRDROP: '空投 (AIRDROP)', + FEE: '手续费 (FEE)', +}; + +export function AddTransactionDialog({ assets }: AddTransactionDialogProps) { + const [open, setOpen] = useState(false); + const [isPending, startTransition] = useTransition(); + const router = useRouter(); + + const form = useForm({ + resolver: zodResolver(addTransactionSchema), + defaultValues: { + assetId: '', + txType: 'BUY' as const, + quantity: '', + price: '', + fee: '0', + txCurrency: 'USD', + executedAt: new Date(), + }, + }); + + function onSubmit(values: AddTransactionForm) { + startTransition(async () => { + const result = await createTransaction({ + assetId: values.assetId, + txType: values.txType, + quantity: String(values.quantity), + price: String(values.price), + fee: String(values.fee), + txCurrency: values.txCurrency, + executedAt: values.executedAt, + exchangeRate: '1', + }); + if (result.success) { + setOpen(false); + form.reset(); + router.refresh(); + } + }); + } + + return ( + + + + + + + 添加交易流水 + 录入一笔新的交易记录到系统中 + +
+ + ( + + 标的资产 + + + + )} + /> + ( + + 交易类型 + + + + )} + /> +
+ ( + + 数量 + + + + + + )} + /> + ( + + 价格 + + + + + + )} + /> +
+
+ ( + + 手续费 + + + + + + )} + /> + ( + + 交易币种 + + + + + + )} + /> +
+ ( + + 执行时间 + + { + field.onChange(new Date(e.target.value)); + }} + value={ + field.value + ? new Date(field.value).toISOString().slice(0, 16) + : '' + } + /> + + + + )} + /> + + + + + +
+
+ ); +}