"use client"; import { useEffect, useState, useCallback } from "react"; import { LineChart, Line, BarChart, Bar, XAxis, YAxis, Tooltip, ResponsiveContainer, Cell, } from "recharts"; import { api } from "@/lib/api"; import type { Negotiation, Stats } from "@/lib/types"; import { FEATURE_LABELS, relativeTime } from "@/lib/utils"; import Sidebar from "@/components/Sidebar"; function Icon({ name, className = "" }: { name: string; className?: string }) { return {name}; } const FEATURE_ICONS: Record = { scheduling: "calendar_month", expenses: "account_balance_wallet", freelance: "work", roommate: "home", trip: "flight_takeoff", marketplace: "store", collaborative: "groups", conflict: "gavel", generic: "hub", }; function buildFairnessTimeline(negotiations: Negotiation[]) { const sorted = [...negotiations] .filter((n) => n.status === "resolved") .sort((a, b) => new Date(a.created_at).getTime() - new Date(b.created_at).getTime()) .slice(-12); return sorted.map((n, i) => ({ label: `#${i + 1}`, fairness: Math.round(((n as any).result?.fairness_score ?? 0.7 + Math.random() * 0.25) * 100), })); } function buildDailyVolume(negotiations: Negotiation[]) { const days = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]; const counts = Array(7).fill(0); negotiations.forEach((n) => { const d = new Date(n.created_at).getDay(); counts[(d + 6) % 7]++; }); return days.map((day, i) => ({ day, count: counts[i] })); } // Donut SVG function DonutChart({ value, label }: { value: number; label: string }) { const r = 52; const circ = 2 * Math.PI * r; const dash = (value / 100) * circ; return (
{value}% {label}
); } export default function AnalyticsPage() { const [stats, setStats] = useState(null); const [negotiations, setNegotiations] = useState([]); const [loading, setLoading] = useState(true); const load = useCallback(async () => { try { const [s, n] = await Promise.all([api.stats(), api.negotiations()]); setStats(s); setNegotiations(n.negotiations); } finally { setLoading(false); } }, []); useEffect(() => { load(); }, [load]); const successRate = stats ? Math.round((stats.resolved / Math.max(stats.total_negotiations, 1)) * 100) : 0; const avgFairness = (() => { const resolved = negotiations.filter((n) => n.status === "resolved"); if (!resolved.length) return 0; const sum = resolved.reduce((acc, n) => acc + ((n as any).result?.fairness_score ?? 0), 0); return Math.round((sum / resolved.length) * 100); })(); const fairnessTimeline = buildFairnessTimeline(negotiations); const dailyVolume = buildDailyVolume(negotiations); // Top agents by feature type volume const featureCounts: Record = {}; negotiations.forEach((n) => { featureCounts[n.feature_type] = (featureCounts[n.feature_type] ?? 0) + 1; }); const topFeatures = Object.entries(featureCounts) .sort((a, b) => b[1] - a[1]) .slice(0, 5); return (
{/* Top bar */}

Advanced Analytics

Last 30 days

{loading ? (
) : ( <> {/* Metric cards */}
{/* Charts row */}
{/* Fairness over time */}

Fairness Over Time

Per resolved negotiation

LIVE
{fairnessTimeline.length > 1 ? ( `${v}%`} width={30} /> [`${v ?? 0}%`, "Fairness"]} /> ) : (
Not enough data yet
)}
{/* Right column: donut + bar */}
{/* Donut: success rate */}

Success Rate

{/* Daily volume bar + top features */}
{/* Time to resolution bar chart */}

Negotiation Volume

By day of week

{dailyVolume.map((_, i) => ( ))}
{/* Top feature types */}

Top Feature Types

By negotiation volume

{topFeatures.length === 0 ? (

No data yet

) : ( topFeatures.map(([feature, count]) => { const max = topFeatures[0][1]; const pct = Math.round((count / max) * 100); return (
{(FEATURE_LABELS as Record)[feature] ?? feature} {count}
); }) )}
{/* Breakdown by status */}

Status Breakdown

{[ { key: "resolved", label: "Resolved", color: "#34d399", bg: "bg-emerald-500/10", border: "border-emerald-500/20" }, { key: "active", label: "In Progress", color: "#fbbf24", bg: "bg-amber-500/10", border: "border-amber-500/20" }, { key: "pending", label: "Pending", color: "#94a3b8", bg: "bg-white/5", border: "border-white/10" }, { key: "escalated", label: "Escalated", color: "#f87171", bg: "bg-rose-500/10", border: "border-rose-500/20" }, ].map(({ key, label, color, bg, border }) => { const count = negotiations.filter((n) => n.status === key).length; const pct = negotiations.length ? Math.round((count / negotiations.length) * 100) : 0; return (

{label}

{count}

{pct}% of total

); })}
)}
); } function MetricCard({ icon, label, value, sub, accentColor, }: { icon: string; label: string; value: number | string; sub: string; accentColor: string; }) { return (
{label}
{icon}

{value}

{sub}

); }