"use client"; import { useState } from "react"; import { Card, CardContent } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table"; import { FileText, Eye, MessageCircle, Quote, TrendingUp, Lightbulb, AlertTriangle, Check, BarChart3, } from "lucide-react"; import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Line, ComposedChart, Legend, } from "recharts"; import { analyticsApi, type OverviewStatsResponse, type TopContentItem, type InsightResponse, } from "@/lib/api"; import { useApi } from "@/lib/hooks/use-api"; import { LoadingState, ErrorState } from "@/components/ui/api-states"; // ─── Types ─────────────────────────────────────────────────────────────────── interface InsightItem { type: "opportunity" | "suggestion" | "anomaly"; icon: string; title: string; description: string; recommendation: string; severity: "success" | "info" | "warning"; id: string; applied: boolean; } // ─── Static Options ─────────────────────────────────────────────────────────── const platformFilterOptions = [ { key: "wechat", label: "微信" }, { key: "zhihu", label: "知乎" }, { key: "xiaohongshu", label: "小红书" }, { key: "douyin", label: "抖音" }, { key: "baijiahao", label: "百家号" }, ]; const timeRangeOptions = [ { value: "7", label: "最近7天" }, { value: "30", label: "最近30天" }, { value: "90", label: "最近90天" }, ]; // ─── Helpers ───────────────────────────────────────────────────────────────── function mapInsightSeverity(severity: string): "success" | "info" | "warning" { if (severity === "high") return "warning"; if (severity === "low") return "success"; return "info"; } function mapInsightType(insightType: string): "opportunity" | "suggestion" | "anomaly" { if (insightType === "anomaly") return "anomaly"; if (insightType === "opportunity") return "opportunity"; return "suggestion"; } function mapInsightIcon(insightType: string): string { if (insightType === "anomaly") return "AlertTriangle"; if (insightType === "opportunity") return "TrendingUp"; return "Lightbulb"; } function mapApiInsights(apiInsights: InsightResponse[]): InsightItem[] { return apiInsights.map((ins) => ({ id: ins.id, type: mapInsightType(ins.insight_type), icon: mapInsightIcon(ins.insight_type), title: ins.title, description: ins.description, recommendation: ins.recommendation, severity: mapInsightSeverity(ins.severity), applied: ins.applied, })); } function getSeverityStyles(severity: InsightItem["severity"]) { switch (severity) { case "success": return { iconBg: "bg-emerald-50", iconColor: "text-emerald-500", border: "border-emerald-200", }; case "info": return { iconBg: "bg-blue-50", iconColor: "text-blue-500", border: "border-blue-200", }; case "warning": return { iconBg: "bg-amber-50", iconColor: "text-amber-500", border: "border-amber-200", }; } } function InsightIcon({ name, className }: { name: string; className?: string }) { switch (name) { case "TrendingUp": return ; case "Lightbulb": return ; case "AlertTriangle": return ; default: return ; } } function getPlatformBadgeClass(platform: string) { const map: Record = { 微信: "bg-green-50 text-green-700 border-green-200", 知乎: "bg-blue-50 text-blue-700 border-blue-200", 小红书: "bg-red-50 text-red-700 border-red-200", 抖音: "bg-slate-50 text-slate-700 border-slate-200", 百家号: "bg-orange-50 text-orange-700 border-orange-200", wechat: "bg-green-50 text-green-700 border-green-200", zhihu: "bg-blue-50 text-blue-700 border-blue-200", xiaohongshu: "bg-red-50 text-red-700 border-red-200", douyin: "bg-slate-50 text-slate-700 border-slate-200", baijiahao: "bg-orange-50 text-orange-700 border-orange-200", }; return map[platform] ?? "bg-gray-50 text-gray-600 border-gray-200"; } // ─── Sub-components ────────────────────────────────────────────────────────── function MetricCard({ label, value, icon, iconBg, highlight, subtext, }: { label: string; value: string | number; icon: React.ReactNode; iconBg: string; highlight?: boolean; subtext?: string; }) { return (

{label}

{icon}

{typeof value === "number" ? value.toLocaleString() : value}

{subtext &&

{subtext}

}
); } function EmptyTopContent() { return (

暂无发布内容数据

发布内容后这里将展示表现排行榜

); } function EmptyInsights() { return (

暂无AI洞察

积累更多数据后,AI将为您生成优化建议

); } // ─── Main Page ─────────────────────────────────────────────────────────────── export default function AnalyticsPage() { const [selectedPlatforms, setSelectedPlatforms] = useState([]); const [timeRange, setTimeRange] = useState("30"); const [appliedInsights, setAppliedInsights] = useState>(new Set()); // SWR 数据获取 const { data: overview, isLoading: overviewLoading, error: overviewError, refresh: refreshOverview, } = useApi("/api/v1/analytics/overview"); const { data: topContentData, isLoading: topLoading, error: topError, refresh: refreshTop, } = useApi<{ items: TopContentItem[]; sort_by: string; total: number }>("/api/v1/analytics/top?limit=5"); const { data: insightsData, isLoading: insightsLoading, error: insightsError, refresh: refreshInsights, } = useApi("/api/v1/analytics/insights?limit=6"); const loading = overviewLoading || topLoading || insightsLoading; // "用户未关联组织" 类错误视为空状态 const isOrgError = (err: Error | undefined) => err?.message.includes("未关联组织") || err?.message.includes("No organization"); const hasOrgError = isOrgError(overviewError) || isOrgError(topError) || isOrgError(insightsError); const error = !hasOrgError && (overviewError || topError || insightsError) ? overviewError || topError || insightsError : undefined; const topContent: TopContentItem[] = topContentData?.items ?? []; const rawInsights: InsightResponse[] = insightsData ?? []; const insights = mapApiInsights(rawInsights).map((ins) => appliedInsights.has(ins.id) ? { ...ins, applied: true } : ins ); const handleRetry = () => { refreshOverview(); refreshTop(); refreshInsights(); }; const togglePlatform = (key: string) => { setSelectedPlatforms((prev) => prev.includes(key) ? prev.filter((k) => k !== key) : [...prev, key] ); }; const handleApplyInsight = async (insightId: string) => { try { await analyticsApi.applyInsight(undefined, insightId); setAppliedInsights((prev) => new Set(prev).add(insightId)); } catch (err) { console.error("Apply insight error:", err); } }; if (loading) { return (
); } if (error) { return (

数据监测中心

); } const platformDistribution = overview?.platform_distribution ?? {}; const trendData = Object.entries(platformDistribution).map(([key, count]) => ({ date: key, views: count, interactions: Math.floor(count * 0.08), })); return (
{/* Top Area */}

数据监测中心

全渠道内容表现追踪与AI引用洞察

{/* Filters */}
平台: {platformFilterOptions.map((opt) => { const selected = selectedPlatforms.includes(opt.key); return ( ); })}
时间: {timeRangeOptions.map((opt) => ( ))}
{/* Overview Metrics */}
} iconBg="bg-blue-50" /> } iconBg="bg-purple-50" /> } iconBg="bg-amber-50" /> } iconBg="bg-emerald-50" subtext={ overview?.avg_engagement_rate ? `互动率 ${(overview.avg_engagement_rate * 100).toFixed(1)}%` : undefined } />
{/* Trend Chart */}

平台内容分布

实时数据
{trendData.length > 0 ? ( ) : (

暂无平台分布数据

)}
{/* Performance Table */}

内容表现排行榜

Top {topContent.length}
{topContent.length === 0 ? ( ) : (
排名 标题 平台 曝光 互动率 AI引用数 {topContent.map((item, idx) => { const rank = idx + 1; const interactionRate = item.search_impressions > 0 ? ((item.search_clicks / item.search_impressions) * 100).toFixed(1) : "0.0"; return ( {rank} {item.content_title} {item.platform} {(item.views || item.search_impressions).toLocaleString()} {interactionRate}% {item.ai_citation_count} ); })}
)}
{/* AI Insights */}

AI 智能洞察

{insights.length === 0 ? (
) : (
{insights.map((insight) => { const styles = getSeverityStyles(insight.severity); return (

{insight.title}

{insight.description}

{insight.recommendation && (

建议:{insight.recommendation}

)}
); })}
)}
); }