import { useEffect, useState } from "react"; import type { BillingSummary, Product, Project } from "../types"; import type { Page } from "./route-config"; import { api } from "../api"; import { SkeletonRows } from "../components/loading"; import { money, stageMeta, statusPill } from "./stage-config"; import { Progress } from "../components/pipeline-stage"; import { IconKitSvg } from "../components/IconKitSvg"; export function Dashboard({ products, projects, productTotal, projectTotal, billing, userName, loading = false, navigate }: { products: Product[]; projects: Project[]; productTotal?: number; // 后端真实总数(分页 count),用于「N 个/SKU」展示 projectTotal?: number; billing: BillingSummary | null; userName?: string; loading?: boolean; navigate: (page: Page) => void; }) { const projCount = projectTotal ?? projects.length; const prodCount = productTotal ?? products.length; // 资产总数:走轻量 summary 接口(各 tab 计数之和),不再吃全局 assets 全量数组 const [assetCount, setAssetCount] = useState(null); useEffect(() => { let alive = true; api.assetSummary() .then((c) => { if (alive) setAssetCount(Object.values(c).reduce((a, b) => a + b, 0)); }) .catch(() => {}); return () => { alive = false; }; }, []); const completed = projects.filter((project) => project.status === "completed").length; const running = projects.filter((project) => !["completed", "failed"].includes(project.status)).length; const productTitle = (id: string) => products.find((product) => product.id === id)?.title || "商品"; // 日期 mono:`// 06.19 · 周四`(对齐 V1 mono 注释风,补零到两位) const now = new Date(); const pad2 = (n: number) => String(n).padStart(2, "0"); const weekdays = ["日", "一", "二", "三", "四", "五", "六"]; const dateMono = `${pad2(now.getMonth() + 1)}.${pad2(now.getDate())} · 周${weekdays[now.getDay()]}`; // 「总项目」delta = 本月新建数(真实,按 created_at 算)。 // 但 projects 仅首页分页数据;当存在未加载的项目(projCount > 已加载)时,本月数会少算 → 宁可不显示也不显假值。 const monthStart = new Date(now.getFullYear(), now.getMonth(), 1).getTime(); const newThisMonth = projects.filter((p) => p.created_at && new Date(p.created_at).getTime() >= monthStart).length; const allProjectsLoaded = projects.length >= projCount; const totalDelta = allProjectsLoaded && newThisMonth > 0 ? `↑ 本月 +${newThisMonth}` : undefined; // 余额 mini progress:真实冻结占比 = 冻结 /(可用 + 冻结);无 budget/已用字段,故不复刻 V1「已用 ¥X/¥Y」,改显真实「冻结 ¥X / 共 ¥Y」。 const balanceNum = Number(billing?.account.balance ?? 0); const reservedNum = Number(billing?.account.reserved_balance ?? 0); const fundsTotal = balanceNum + reservedNum; const reservedPct = fundsTotal > 0 ? Math.min(100, Math.round((reservedNum / fundsTotal) * 100)) : 0; return ( <>

欢迎回来{userName ? `,${userName.split("@")[0]}` : ""}

// {dateMono}·你有 {running} 个项目 正在进行中
++

最近项目

{loading && projects.length === 0 ? : projects.slice(0, 6).map((project) => )}

快捷入口

[ /shortcuts ]
navigate("products")} /> navigate("library")} /> navigate("account")} /> navigate("projects")} />

提示

[ FAQ ]
扣费规则生成失败、超时、用户重跑 — 均不扣费。仅在你点 [ 确认通过 ] 时按 token 实际结算。
); } export function KpiStat({ label, badge, value, delta }: { label: string; badge: string; value: string | number; delta?: string }) { return
{label} {badge}
{value}
{delta ?
{delta}
: null}
; } export function Shortcut({ name, title, desc, onClick }: { name: string; title: string; desc: string; onClick: () => void }) { return
{title}
{desc}
; }