import { captureError } from "@agent-native/core/client/analytics"; import { useT } from "@agent-native/core/client/i18n"; import { IconAlertTriangle, IconChecks, IconCurrencyDollar, IconDatabase, IconMessageCircle, IconRefresh, IconTargetArrow, IconTrendingDown, IconTrophy, IconUsers, } from "@tabler/icons-react"; import { useEffect, type ComponentType, type ReactNode } from "react"; import { Badge } from "@/components/ui/badge"; import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from "@/components/ui/card"; import { Progress } from "@/components/ui/progress"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; type TablerIcon = ComponentType<{ className?: string }>; interface AnalysisLike { id: string; name: string; resultData: Record | null; } interface LegacyFusionBase { type: "closed-lost" | "closed-won"; generatedAt?: string; analysisAsOf?: string; summary?: Record; } interface LegacyDeal { dealId?: string; dealName: string; amount?: number; closeDate?: string; furthestStage?: string; closedLostReason?: string; primaryReason?: string; gongCallCount?: number; matchedCallCount?: number; contactCount?: number; emailCount?: number; slackMessageCount?: number; personaCount?: number; } interface PainTheme { rank?: number; number?: number; theme?: string; title?: string; pain?: string; detail?: string; description?: string; businessImpact?: string; deals?: number; dealCount?: number; value?: number; pct?: number; representativeDeals?: string[]; examples?: string[]; } interface PainDeal { dealId?: string; dealName: string; amount?: number; businessPain?: string; operationalPain?: string; assessedPainSummary?: string; painCategories?: string[]; } interface WinTheme { number: number; title: string; detail: string; deals: string[]; } interface ClosedLostPayload extends LegacyFusionBase { type: "closed-lost"; deals?: LegacyDeal[]; stageData?: Array<{ stage: string; deals: number; value: number; avgDeal?: number; }>; lossThemes?: PainTheme[]; businessPain?: { generatedAt?: string; totalDeals?: number; topPains?: PainTheme[]; businessPains?: PainTheme[]; deals?: PainDeal[]; }; } interface ClosedWonPayload extends LegacyFusionBase { type: "closed-won"; deals?: LegacyDeal[]; winThemes?: WinTheme[]; operationalThemes?: WinTheme[]; businessThemes?: WinTheme[]; personas?: Array<{ dealId?: string; dealName?: string; email?: string; name?: string; company?: string; }>; } type LegacyFusionPayload = ClosedLostPayload | ClosedWonPayload; const LEGACY_FUSION_ANALYSIS_IDS = new Set([ "fusion-closed-lost-analysis", "fusion-closed-won-analysis", ]); export function isLegacyFusionAnalysis(id: string): boolean { return LEGACY_FUSION_ANALYSIS_IDS.has(id); } export default function LegacyFusionAnalysis({ analysis, }: { analysis: AnalysisLike; }) { const t = useT(); const payload = parseLegacyFusionPayload(analysis.resultData); useEffect(() => { if (!isLegacyFusionAnalysis(analysis.id) || payload) return; captureError( new Error(`Missing legacy Fusion payload for analysis ${analysis.id}`), { tags: { surface: "analytics-analysis-detail", analysisId: analysis.id, issue: "missing-legacy-fusion-payload", }, extra: { analysisName: analysis.name, resultDataKeys: analysis.resultData ? Object.keys(analysis.resultData) : [], }, }, ); }, [analysis.id, analysis.name, analysis.resultData, payload]); if (!isLegacyFusionAnalysis(analysis.id)) return null; if (!payload) { return ( {t("legacyFusion.payloadMissingTitle")} {t("legacyFusion.payloadMissingDescription")} ); } return payload.type === "closed-lost" ? ( ) : ( ); } function parseLegacyFusionPayload( resultData: Record | null, ): LegacyFusionPayload | null { const value = resultData?.legacyFusion; if (!value || typeof value !== "object") return null; const payload = value as Partial; if (payload.type !== "closed-lost" && payload.type !== "closed-won") { return null; } return payload as LegacyFusionPayload; } function ClosedLostDashboard({ data }: { data: ClosedLostPayload }) { const t = useT(); const deals = data.deals ?? []; const totalValue = sumBy(deals, (deal) => deal.amount); const stageData = data.stageData?.length ? data.stageData : summarizeStages(deals, t("legacyFusion.unknown")); const topDeals = [...deals] .sort((a, b) => valueOf(b.amount) - valueOf(a.amount)) .slice(0, 12); const reEngageDeals = deals .filter((deal) => isLikelyReEngage(deal)) .sort((a, b) => valueOf(b.amount) - valueOf(a.amount)) .slice(0, 12); const lossThemes = data.lossThemes ?? []; const topPains = data.businessPain?.topPains ?? []; const businessPains = data.businessPain?.businessPains ?? []; const painDeals = data.businessPain?.deals ?? []; return (
{t("legacyFusion.overview")} {t("legacyFusion.lossThemes")} {t("legacyFusion.businessPain")} {t("legacyFusion.reengage")} ({ label: stage.stage, value: stage.deals, detail: t("legacyFusion.pipeline", { amount: formatCurrency(stage.value), }), percent: percentage(stage.value, totalValue), }))} />
); } function ClosedWonDashboard({ data }: { data: ClosedWonPayload }) { const t = useT(); const deals = data.deals ?? []; const totalValue = sumBy(deals, (deal) => deal.amount); const topDeals = [...deals] .sort((a, b) => valueOf(b.amount) - valueOf(a.amount)) .slice(0, 12); const personas = data.personas ?? []; const personaCompanies = countBy( personas, (persona) => persona.company || t("legacyFusion.unknown"), ); const winThemes = data.winThemes ?? []; return (
{t("legacyFusion.overview")} {t("legacyFusion.winThemes")} {t("legacyFusion.painThemes")} {t("legacyFusion.coverage")} ({ label, value: count, detail: t("legacyFusion.contacts", { count: formatNumber(count), }), percent: percentage(count, personas.length), }))} />
); } function DashboardHero({ eyebrow, title, description, icon: Icon, generatedAt, }: { eyebrow: string; title: string; description: string; icon: TablerIcon; generatedAt?: string; }) { const t = useT(); return (
{eyebrow}

{title}

{description}

{generatedAt && (
{t("legacyFusion.dataRefreshed", { date: formatShortDate(generatedAt), })}
)}
); } function MetricGrid({ children }: { children: ReactNode }) { return (
{children}
); } function MetricCard({ icon: Icon, label, value, detail, }: { icon: TablerIcon; label: string; value: string; detail: string; }) { return (

{label}

{value}

{detail}

); } function TwoColumn({ children }: { children: ReactNode }) { return
{children}
; } function SectionCard({ title, description, children, }: { title: string; description?: string; children: ReactNode; }) { return ( {title} {description && ( {description} )} {children} ); } function BarList({ rows, }: { rows: Array<{ label: string; value: number; detail: string; percent: number; }>; }) { const t = useT(); if (!rows.length) return ; return (
{rows.map((row) => (
{row.label} {row.detail}
))}
); } function ThemeGrid({ themes, variant, }: { themes: Array; variant: "loss" | "win"; }) { const t = useT(); if (!themes.length) return ; return (
{themes.map((theme, index) => { const title = getThemeTitle(theme, t("legacyFusion.untitledTheme")); const detail = getThemeDetail(theme); const examples = getThemeExamples(theme); const metric = "value" in theme && theme.value ? formatCurrency(theme.value) : "dealCount" in theme && theme.dealCount ? t("legacyFusion.deals", { count: formatNumber(theme.dealCount), }) : "deals" in theme && typeof theme.deals === "number" ? t("legacyFusion.deals", { count: formatNumber(theme.deals), }) : null; return (
{metric && ( {metric} )} {!!examples.length && (
    {examples.slice(0, 4).map((example) => (
  • {example}
  • ))}
)}
); })}
); } function ThemeStack({ themes }: { themes: Array }) { const t = useT(); if (!themes.length) return ; return (
{themes.slice(0, 6).map((theme, index) => { const title = getThemeTitle(theme, t("legacyFusion.untitledTheme")); const detail = getThemeDetail(theme); const count = "dealCount" in theme && theme.dealCount ? theme.dealCount : "deals" in theme && typeof theme.deals === "number" ? theme.deals : undefined; return (

{getThemeNumber(theme, index)}. {title}

{count ? ( {formatNumber(count)} ) : null}

{detail}

); })}
); } function DealTable({ deals, columns, }: { deals: LegacyDeal[]; columns: Array< | "deal" | "amount" | "stage" | "reason" | "closeDate" | "calls" | "emails" | "slack" | "contacts" >; }) { const t = useT(); const columnLabels = { deal: t("legacyFusion.deal"), amount: t("legacyFusion.amount"), stage: t("legacyFusion.stage"), reason: t("legacyFusion.reason"), closeDate: t("legacyFusion.closed"), calls: t("legacyFusion.calls"), emails: t("legacyFusion.emails"), slack: t("legacyFusion.slack"), contacts: t("legacyFusion.contactsColumn"), } as const; if (!deals.length) return ; return (
{columns.map((column) => ( ))} {deals.map((deal) => ( {columns.map((column) => ( ))} ))}
{columnLabels[column]}
{renderDealCell(deal, column, t("legacyFusion.unknown"))}
); } function PainDealTable({ deals }: { deals: PainDeal[] }) { const t = useT(); if (!deals.length) return ; return (
{deals.map((deal) => ( ))}
{t("legacyFusion.deal")} {t("legacyFusion.amount")} {t("legacyFusion.operationalPain")} {t("legacyFusion.businessPain")}
{deal.dealName} {formatCurrency(deal.amount)} {deal.operationalPain || deal.assessedPainSummary || t("legacyFusion.unknown")} {deal.businessPain || t("legacyFusion.unknown")}
); } function EmptyState({ label }: { label: string }) { return (
{label}
); } function renderDealCell( deal: LegacyDeal, column: | "deal" | "amount" | "stage" | "reason" | "closeDate" | "calls" | "emails" | "slack" | "contacts", unknown: string, ): ReactNode { switch (column) { case "deal": return {deal.dealName}; case "amount": return formatCurrency(deal.amount); case "stage": return deal.furthestStage || unknown; case "reason": return ( {deal.primaryReason || deal.closedLostReason || unknown} ); case "closeDate": return deal.closeDate ? formatShortDate(deal.closeDate) : unknown; case "calls": return formatNumber(deal.gongCallCount ?? deal.matchedCallCount ?? 0); case "emails": return formatNumber(deal.emailCount ?? 0); case "slack": return formatNumber(deal.slackMessageCount ?? 0); case "contacts": return formatNumber(deal.contactCount ?? 0); default: return null; } } function getMetric( summary: Record | undefined, key: string, ): number | undefined { const raw = summary?.[key]; if (typeof raw === "number" && Number.isFinite(raw)) return raw; if (typeof raw === "string") { const numeric = Number(raw.replace(/[%,$]/g, "")); return Number.isFinite(numeric) ? numeric : undefined; } return undefined; } function summarizeStages(deals: LegacyDeal[], unknown: string) { const byStage = new Map< string, { stage: string; deals: number; value: number } >(); for (const deal of deals) { const stage = deal.furthestStage || unknown; const existing = byStage.get(stage) ?? { stage, deals: 0, value: 0 }; existing.deals += 1; existing.value += valueOf(deal.amount); byStage.set(stage, existing); } return [...byStage.values()].sort((a, b) => b.value - a.value); } function isLikelyReEngage(deal: LegacyDeal): boolean { const text = `${deal.closedLostReason ?? ""} ${deal.primaryReason ?? ""}`.toLowerCase(); return [ "re-engage", "reengage", "revisit", "re-open", "reopen", "q3", "later", "security approval", "not ready", "timing", ].some((token) => text.includes(token)); } function getThemeTitle(theme: PainTheme | WinTheme, fallback: string): string { if ("title" in theme && theme.title) return theme.title; if ("theme" in theme && theme.theme) return theme.theme; if ("pain" in theme && theme.pain) return theme.pain; return fallback; } function getThemeDetail(theme: PainTheme | WinTheme): string { if ("detail" in theme && theme.detail) return theme.detail; if ("description" in theme && theme.description) return theme.description; if ("businessImpact" in theme && theme.businessImpact) return theme.businessImpact; return ""; } function getThemeExamples(theme: PainTheme | WinTheme): string[] { if ("examples" in theme && Array.isArray(theme.examples)) return theme.examples; if ( "representativeDeals" in theme && Array.isArray(theme.representativeDeals) ) { return theme.representativeDeals; } if ("deals" in theme && Array.isArray(theme.deals)) return theme.deals; return []; } function getThemeNumber(theme: PainTheme | WinTheme, index: number): number { if ("number" in theme && typeof theme.number === "number") return theme.number; if ("rank" in theme && typeof theme.rank === "number") return theme.rank; return index + 1; } function countBy(items: T[], keyFn: (item: T) => string) { const counts = new Map(); for (const item of items) { const key = keyFn(item); counts.set(key, (counts.get(key) ?? 0) + 1); } return [...counts.entries()].sort((a, b) => b[1] - a[1]); } function sumBy(items: T[], valueFn: (item: T) => number | undefined) { return items.reduce((sum, item) => sum + valueOf(valueFn(item)), 0); } function percentage(value: number, total: number): number { if (!total) return 0; return Math.max(0, Math.min(100, Math.round((value / total) * 100))); } function valueOf(value: unknown): number { return typeof value === "number" && Number.isFinite(value) ? value : 0; } function formatNumber(value: number): string { return new Intl.NumberFormat("en-US").format(Math.round(value)); } function formatCurrency(value: number | undefined): string { const amount = valueOf(value); if (Math.abs(amount) >= 1_000_000) { return `$${(amount / 1_000_000).toFixed(1)}M`; } if (Math.abs(amount) >= 1_000) { return `$${Math.round(amount / 1_000)}K`; } return new Intl.NumberFormat("en-US", { style: "currency", currency: "USD", maximumFractionDigits: 0, }).format(amount); } function formatShortDate(value: string): string { const date = new Date(value); if (Number.isNaN(date.getTime())) return value; return date.toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric", }); }