Files
AIPortPilot/frontend/src/app/(investor)/portfolio/page.tsx
T
selfrelease fad458b2a7 docs(uiux): UIUX 设计方案大改 + 5 份作业指导书对齐 + 开发任务文档
- UIUX 文档:填充 19 个缺口(多主体画像/健康度/AI+看板/增长域/洞察域/创始人端/OODA/助推/商密)
- UIUX 文档:插入 6 个新章节(十四~十九),旧章节重编号为二十~三十一,更新目录和交叉引用
- 作业指导书 x5:导航改为 6 域分组,新增 Context Bar/工作模式/Insight Rail/决策线程/多工作区等 UI 概念
- 新建 docs/2-task-uiux.md:50 个代码落地开发任务,按 P0-P6 分优先级 + 8 Sprint 规划
- 后端/前端:大量新增模型、路由、组件(来自之前 Phase 开发)
2026-07-19 11:53:38 +08:00

85 lines
3.1 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.
"use client";
import { useState } from "react";
import { BarChart3, Sparkles } from "lucide-react";
import { rebalancePortfolio, runMonteCarlo } from "@/lib/api-v2";
import { PageContainer, Card, Badge } from "@/components/shared/PageContainer";
import { toast } from "sonner";
export default function PortfolioPage() {
const [rebalanceResult, setRebalanceResult] = useState<Record<string, any> | null>(null);
const [mcResult, setMcResult] = useState<Record<string, any> | null>(null);
const [isLoading, setIsLoading] = useState(false);
const handleRebalance = async () => {
setIsLoading(true);
try {
const resp = await rebalancePortfolio([]);
setRebalanceResult(resp.data as Record<string, any>);
} catch {
toast.error("分析失败");
} finally {
setIsLoading(false);
}
};
const handleMonteCarlo = async () => {
setIsLoading(true);
try {
const resp = await runMonteCarlo([0.15, 0.22, 0.08, 0.35, 0.12]);
setMcResult(resp.data as Record<string, any>);
} catch {
toast.error("模拟失败");
} finally {
setIsLoading(false);
}
};
return (
<PageContainer title="组合管理" description="边际回报率计算 + 组合再平衡 + Monte Carlo 模拟">
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
<Card>
<h3 className="font-medium text-gray-900"></h3>
<p className="mt-1 text-sm text-gray-500">AI </p>
<button
onClick={handleRebalance}
disabled={isLoading}
className="mt-3 flex items-center gap-1 rounded-md bg-gray-900 px-3 py-1.5 text-sm text-white hover:bg-gray-700 disabled:opacity-50"
>
<Sparkles size={16} />
</button>
{rebalanceResult && (
<div className="mt-3 space-y-2 text-sm">
{rebalanceResult.irr_impact != null && (
<p>IRR <Badge color="green">+{rebalanceResult.irr_impact}%</Badge></p>
)}
{rebalanceResult.dpi_impact != null && (
<p>DPI <Badge color="green">+{rebalanceResult.dpi_impact}</Badge></p>
)}
</div>
)}
</Card>
<Card>
<h3 className="font-medium text-gray-900">Monte Carlo </h3>
<p className="mt-1 text-sm text-gray-500">10000 IRR/DPI </p>
<button
onClick={handleMonteCarlo}
disabled={isLoading}
className="mt-3 flex items-center gap-1 rounded-md bg-gray-900 px-3 py-1.5 text-sm text-white hover:bg-gray-700 disabled:opacity-50"
>
<Sparkles size={16} />
</button>
{mcResult && (
<div className="mt-3 space-y-2 text-sm">
<p>P5: <Badge color="red">{mcResult.percentile_p5}</Badge></p>
<p>P50: <Badge color="amber">{mcResult.percentile_p50}</Badge></p>
<p>P95: <Badge color="green">{mcResult.percentile_p95}</Badge></p>
</div>
)}
</Card>
</div>
</PageContainer>
);
}