diff --git a/app/dashboard/assets/page.tsx b/app/dashboard/assets/page.tsx new file mode 100644 index 0000000..f1a457e --- /dev/null +++ b/app/dashboard/assets/page.tsx @@ -0,0 +1,66 @@ +import { getAssets } from '@/actions/asset'; +import { AddAssetDialog } from '@/components/assets/add-asset-dialog'; +import { + Table, + TableBody, + TableCaption, + TableCell, + TableHead, + TableHeader, + TableRow, +} from '@/components/ui/table'; + +export default async function AssetsPage() { + const assets = await getAssets(); + + const typeLabels: Record = { + STOCK: '股票', + CRYPTO: '加密货币', + CASH: '现金', + }; + + return ( +
+
+

资产列表

+ +
+ +
+ + 数据库中所有已录入的资产 + + + 资产代码 + 类型 + 基础币种 + 创建时间 + + + + {assets.length === 0 ? ( + + + 暂无资产,点击"添加资产"按钮录入第一个资产 + + + ) : ( + assets.map((asset) => ( + + {asset.symbol} + {typeLabels[asset.type] || asset.type} + {asset.baseCurrency} + + {asset.createdAt + ? new Date(asset.createdAt).toLocaleString('zh-CN') + : '-'} + + + )) + )} + +
+
+
+ ); +} diff --git a/app/dashboard/layout.tsx b/app/dashboard/layout.tsx index 82749c5..8a06828 100644 --- a/app/dashboard/layout.tsx +++ b/app/dashboard/layout.tsx @@ -1,6 +1,13 @@ import { LayoutGrid, Wallet, ArrowLeftRight } from 'lucide-react'; import { ThemeToggle } from '@/components/theme-toggle'; import { Button } from '@/components/ui/button'; +import Link from 'next/link'; + +const navItems = [ + { href: '/dashboard', label: '总览 (Overview)', icon: LayoutGrid }, + { href: '/dashboard/assets', label: '资产 (Assets)', icon: Wallet }, + { href: '/dashboard/transactions', label: '流水 (Transactions)', icon: ArrowLeftRight }, +]; export default function DashboardLayout({ children, @@ -15,18 +22,17 @@ export default function DashboardLayout({
diff --git a/src/components/assets/add-asset-dialog.tsx b/src/components/assets/add-asset-dialog.tsx new file mode 100644 index 0000000..0545ddd --- /dev/null +++ b/src/components/assets/add-asset-dialog.tsx @@ -0,0 +1,143 @@ +'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 { createAsset } from '@/actions/asset'; + +const addAssetSchema = z.object({ + symbol: z.string().min(1, '资产代码不能为空'), + type: z.enum(['STOCK', 'CRYPTO', 'CASH']), + baseCurrency: z.string().min(2, '基础币种至少2个字符'), +}); + +type AddAssetForm = z.infer; + +export function AddAssetDialog() { + const [open, setOpen] = useState(false); + const [isPending, startTransition] = useTransition(); + const router = useRouter(); + + const form = useForm({ + resolver: zodResolver(addAssetSchema), + defaultValues: { + symbol: '', + type: 'STOCK' as const, + baseCurrency: '', + }, + }); + + function onSubmit(values: AddAssetForm) { + startTransition(async () => { + const result = await createAsset(values); + if (result.success) { + setOpen(false); + form.reset(); + router.refresh(); + } + }); + } + + return ( + + + + + + + 添加新资产 + 录入一个新的资产到系统中 + +
+ + ( + + 资产代码 + + + + + + )} + /> + ( + + 资产类型 + + + + )} + /> + ( + + 基础币种 + + + + + + )} + /> + + + + + +
+
+ ); +}