import { useState } from "react"; import { Download, RefreshCcw, Sparkles } from "lucide-react"; import { TabsContent } from "@/components/ui/tabs"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Card, CardContent } from "@/components/ui/card"; import { Input } from "@/components/ui/input"; import { Switch } from "@/components/ui/switch"; import type { AIStewardTab, BriefingTarget, DailyBriefingV2Payload, EngineMessage, ExecutiveBriefingConfig, ExecutiveBriefingPayload, ExecutiveBriefingSection, } from "@/pages/ai-steward/types"; import BriefingDetailDrawer from "../components/BriefingDetailDrawer"; import BriefingSectionCard from "../components/BriefingSectionCard"; import BriefingTab from "./BriefingTab"; interface OverviewTabProps { canManageExecutiveBriefing: boolean; dailyBriefing: DailyBriefingV2Payload | null; dailyBriefingError: string | null; dailyBriefingLoading: boolean; username?: string; executiveBriefing: ExecutiveBriefingPayload | null; executiveBriefingError: string | null; executiveBriefingMode: "snapshot" | "live"; executiveBriefingLoading: boolean; executiveBriefingConfigDraft: ExecutiveBriefingConfig; executiveBriefingConfigLoading: boolean; executiveBriefingConfigError: string | null; executiveBriefingSaveLoading: boolean; executiveBriefingSaveMessage: EngineMessage | null; onLoadSnapshot: () => void; onRefreshLive: () => void; onExportReport: () => void; onConfigChange: (patch: Partial) => void; onSaveConfig: () => void; onOpenTarget: (target?: BriefingTarget | null, fallbackTab?: AIStewardTab) => void; } const SECTION_STYLE: Record = { decisions: "border-amber-200 bg-amber-50/70 dark:border-amber-900 dark:bg-amber-950/30", input_health: "border-sky-200 bg-sky-50/70 dark:border-sky-900 dark:bg-sky-950/30", delivery_risk: "border-rose-200 bg-rose-50/70 dark:border-rose-900 dark:bg-rose-950/30", team_capacity: "border-emerald-200 bg-emerald-50/70 dark:border-emerald-900 dark:bg-emerald-950/30", ai_governance: "border-indigo-200 bg-indigo-50/70 dark:border-indigo-900 dark:bg-indigo-950/30", external_metrics: "border-slate-200 bg-slate-50/70 dark:border-slate-800 dark:bg-slate-950/30", }; function formatTimestamp(value?: string | null): string { if (!value) return "-"; const date = new Date(value); if (Number.isNaN(date.getTime())) return value; return date.toLocaleString("zh-CN", { month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", }); } function formatMetricValue(value: string | number): string { return typeof value === "number" ? String(value) : value; } function toneBadgeClass(tone: string): string { switch (tone) { case "critical": return "bg-rose-100 text-rose-700 dark:bg-rose-950 dark:text-rose-200"; case "warning": return "bg-amber-100 text-amber-700 dark:bg-amber-950 dark:text-amber-200"; case "positive": return "bg-emerald-100 text-emerald-700 dark:bg-emerald-950 dark:text-emerald-200"; case "info": return "bg-sky-100 text-sky-700 dark:bg-sky-950 dark:text-sky-200"; default: return "bg-slate-100 text-slate-700 dark:bg-slate-900 dark:text-slate-200"; } } export default function OverviewTab({ canManageExecutiveBriefing, dailyBriefing, dailyBriefingError, dailyBriefingLoading, username, executiveBriefing, executiveBriefingError, executiveBriefingMode, executiveBriefingLoading, executiveBriefingConfigDraft, executiveBriefingConfigLoading, executiveBriefingConfigError, executiveBriefingSaveLoading, executiveBriefingSaveMessage, onLoadSnapshot, onRefreshLive, onExportReport, onConfigChange, onSaveConfig, onOpenTarget, }: OverviewTabProps) { const [drawerSection, setDrawerSection] = useState(null); const [drawerProjectKey, setDrawerProjectKey] = useState(null); if (!canManageExecutiveBriefing) { return ( ); } const sections = executiveBriefing?.sections ?? []; const hero = executiveBriefing?.hero; const openDrawer = (section: ExecutiveBriefingSection, projectKey?: string) => { setDrawerSection(section); setDrawerProjectKey(projectKey || null); }; return (
{executiveBriefingMode === "live" ? "实时版" : "晨报快照"} {hero?.snapshot_state === "missing" && ( 今日快照未生成 )}

简报中心

先看异常,再按项目展开预览,右侧抽屉里看完整详情并决定下一步去项目、成员还是工作台。

需拍板事项

{hero?.decision_count ?? 0}

异常总数

{hero?.anomaly_count ?? 0}

查看日期

{executiveBriefing?.brief_date || "-"}

晨报快照 {formatTimestamp(hero?.baseline_generated_at)}
实时刷新 {formatTimestamp(hero?.live_generated_at)}
{(hero?.key_alerts ?? []).map((item) => ( {item.label} {formatMetricValue(item.value)} ))}
{executiveBriefingLoading && (

正在更新晨报数据...

)} {executiveBriefingError && (

{executiveBriefingError}

)}
{sections.map((section) => ( ))}

晨报调度配置

这份配置是租户级的。修改后,Web 快照、定时调度和管理群自动推送都会沿用同一套晨报口径。

自动发送 onConfigChange({ enabled: checked })} disabled={executiveBriefingConfigLoading || executiveBriefingSaveLoading} />

发送时间

onConfigChange({ send_time: event.target.value })} disabled={executiveBriefingConfigLoading || executiveBriefingSaveLoading} />

发送周期

管理群 Chat ID

onConfigChange({ delivery_target_ref: event.target.value || null })} placeholder="oc_management_group" disabled={executiveBriefingConfigLoading || executiveBriefingSaveLoading} />

时区

onConfigChange({ timezone: event.target.value })} placeholder="Asia/Shanghai" disabled={executiveBriefingConfigLoading || executiveBriefingSaveLoading} />
{executiveBriefingConfigError && (

{executiveBriefingConfigError}

)} {executiveBriefingSaveMessage && (

{executiveBriefingSaveMessage.text}

)}
{ if (!open) { setDrawerSection(null); setDrawerProjectKey(null); } }} onOpenTarget={onOpenTarget} />
); }