feat(dashboard): add company filter — switch between portfolio overview and single company view

This commit is contained in:
selfrelease
2026-07-19 21:52:40 +08:00
parent a24ccd01c7
commit 0197f89018
2 changed files with 46 additions and 12 deletions
+38 -7
View File
@@ -4,6 +4,7 @@ import { useEffect, useState } from "react";
import { Building2, Heart, AlertTriangle, FileText, TrendingUp, TrendingDown, Minus } from "lucide-react"; import { Building2, Heart, AlertTriangle, FileText, TrendingUp, TrendingDown, Minus } from "lucide-react";
import Link from "next/link"; import Link from "next/link";
import { getDashboardSummary, type DashboardSummary } from "@/lib/dashboard"; import { getDashboardSummary, type DashboardSummary } from "@/lib/dashboard";
import { listCompanies, type Company } from "@/lib/companies";
import { LoadingSpinner } from "@/components/shared/LoadingSpinner"; import { LoadingSpinner } from "@/components/shared/LoadingSpinner";
import { EmptyState } from "@/components/shared/EmptyState"; import { EmptyState } from "@/components/shared/EmptyState";
import { HealthScoreBadge } from "@/components/shared/HealthScoreBadge"; import { HealthScoreBadge } from "@/components/shared/HealthScoreBadge";
@@ -14,13 +15,19 @@ import { HealthHeatmap, HealthTrends } from "@/components/dashboard/HealthHeatma
*/ */
export default function InvestorDashboardPage() { export default function InvestorDashboardPage() {
const [summary, setSummary] = useState<DashboardSummary | null>(null); const [summary, setSummary] = useState<DashboardSummary | null>(null);
const [companies, setCompanies] = useState<Company[]>([]);
const [selectedCompanyId, setSelectedCompanyId] = useState<string>("");
const [isLoading, setIsLoading] = useState(true); const [isLoading, setIsLoading] = useState(true);
useEffect(() => { useEffect(() => {
async function load() { async function load() {
try { try {
const resp = await getDashboardSummary(); const [summaryResp, companiesResp] = await Promise.all([
if (resp.data) setSummary(resp.data); getDashboardSummary(),
listCompanies({ page_size: 100 }),
]);
if (summaryResp.data) setSummary(summaryResp.data);
if (companiesResp.data?.items) setCompanies(companiesResp.data.items);
} catch { } catch {
// ignore // ignore
} finally { } finally {
@@ -76,11 +83,35 @@ export default function InvestorDashboardPage() {
}, },
]; ];
// 筛选后的评分列表
const filteredScores = selectedCompanyId
? summary?.recent_scores.filter((s) => s.company_id === selectedCompanyId) ?? []
: summary?.recent_scores ?? [];
// 选中的企业名称
const selectedCompany = companies.find((c) => c.id === selectedCompanyId);
return ( return (
<div className="space-y-6"> <div className="space-y-6">
<div className="flex items-center justify-between">
<div> <div>
<h1 className="text-2xl font-bold text-foreground"></h1> <h1 className="text-2xl font-bold text-foreground"></h1>
<p className="mt-1 text-sm text-muted-foreground">Portfolio </p> <p className="mt-1 text-sm text-muted-foreground">
{selectedCompany ? `${selectedCompany.name} — 企业健康度概览` : "Portfolio 全局健康度与风险概览"}
</p>
</div>
{/* 企业筛选器 */}
<select
value={selectedCompanyId}
onChange={(e) => setSelectedCompanyId(e.target.value)}
className="rounded-md border bg-white px-3 py-1.5 text-sm focus:outline-none focus:ring-2 focus:ring-[var(--investor-primary)]"
>
<option value=""></option>
{companies.map((c) => (
<option key={c.id} value={c.id}>{c.name}</option>
))}
</select>
</div> </div>
{/* KPI 卡片 */} {/* KPI 卡片 */}
@@ -113,9 +144,9 @@ export default function InvestorDashboardPage() {
</Link> </Link>
</div> </div>
{summary && summary.recent_scores.length > 0 ? ( {filteredScores.length > 0 ? (
<div className="space-y-3"> <div className="space-y-3">
{summary.recent_scores.map((score) => ( {filteredScores.map((score) => (
<div key={score.id} className="flex items-center justify-between border-b pb-3 last:border-0 last:pb-0"> <div key={score.id} className="flex items-center justify-between border-b pb-3 last:border-0 last:pb-0">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<HealthScoreBadge score={Math.round(score.total_score)} /> <HealthScoreBadge score={Math.round(score.total_score)} />
@@ -156,10 +187,10 @@ export default function InvestorDashboardPage() {
<div className="space-y-4"> <div className="space-y-4">
<div className="rounded-lg border bg-white p-5 shadow-sm"> <div className="rounded-lg border bg-white p-5 shadow-sm">
<h2 className="mb-4 text-lg font-semibold"></h2> <h2 className="mb-4 text-lg font-semibold"></h2>
<HealthHeatmap /> <HealthHeatmap companyId={selectedCompanyId || undefined} />
</div> </div>
<div className="rounded-lg border bg-white p-5 shadow-sm"> <div className="rounded-lg border bg-white p-5 shadow-sm">
<HealthTrends /> <HealthTrends companyId={selectedCompanyId || undefined} />
</div> </div>
</div> </div>
</div> </div>
@@ -155,16 +155,19 @@ export function HealthTrends({ companyId }: { companyId?: string }) {
</div> </div>
); );
} }
export function HealthHeatmap() { export function HealthHeatmap({ companyId }: { companyId?: string }) {
const [data, setData] = useState<HeatmapRow[]>([]); const [data, setData] = useState<HeatmapRow[]>([]);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
useEffect(() => { useEffect(() => {
apiFetch<HeatmapRow[]>("/dashboard/heatmap") apiFetch<HeatmapRow[]>("/dashboard/heatmap")
.then((res) => setData((res.data as HeatmapRow[]) || [])) .then((res) => {
const rows = (res.data as HeatmapRow[]) || [];
setData(companyId ? rows.filter((r) => r.company_id === companyId) : rows);
})
.catch(() => {}) .catch(() => {})
.finally(() => setLoading(false)); .finally(() => setLoading(false));
}, []); }, [companyId]);
if (loading) return <LoadingSpinner />; if (loading) return <LoadingSpinner />;
if (!data.length) return <EmptyState title="暂无热力图数据" />; if (!data.length) return <EmptyState title="暂无热力图数据" />;