import type { ReactNode } from "react"; import { Icon, type IconName } from "../../../components/Icon"; import { Tag, type TagTone } from "../../../components/primitives"; import { navigateAdmin, type AdminSectionId } from "../../router"; /** Honest per-card state: real data landed / still loading / endpoint failed / * no endpoint exists in lib/api.ts (a placeholder, never a fabricated value). */ export type MetricState = "ok" | "loading" | "error" | "placeholder"; /** One derived overview card (design/10 §4: 卡片多而全, 详情在各独立页面). */ export interface MiniMetricSpec { icon: IconName; label: string; value: string; hint: string; /** 可选的补充详情行:逐行渲染在 `hint` 下方。**不传时卡片与既有形状 * 逐字等价**(其它卡片因此一字未改);存储卡用它把 PG 增强状态单独 * 摆一行,而不是挤进 `hint`。 */ extra?: string[]; state: MetricState; /** Admin module this card drills down to; absent = not a link. */ section?: AdminSectionId; } const STATE_BADGE: Record = { ok: { label: "已接入", tone: "ok" }, loading: { label: "加载中", tone: "neutral" }, error: { label: "端点报错", tone: "err" }, placeholder: { label: "占位", tone: "warn" }, }; export function MiniMetric({ icon, label, value, hint, extra, state, section }: MiniMetricSpec): ReactNode { const badge = STATE_BADGE[state]; const body = ( <>
{label} {badge.label}
{value}
{hint}
{extra?.map((line) => (
{line}
))} ); const base = "rounded-[var(--mu-radius)] border border-[var(--mu-glass-border)] bg-[var(--mu-surface-3)]/60 p-3"; if (!section) return
{body}
; return ( ); }