"use client";
import { useState, useEffect } from "react";
import { motion } from "framer-motion";
import {
BarChart, Bar, LineChart, Line, XAxis, YAxis, CartesianGrid,
Tooltip, ResponsiveContainer, RadarChart, Radar, PolarGrid,
PolarAngleAxis, PolarRadiusAxis
} from "recharts";
import {
TrendingUp, Sparkles, Calendar, Filter, AlertTriangle,
RefreshCw, Brain, BookOpen,
} from "lucide-react";
import { aiApi, dashboardApi } from "@/lib/api";
import { useAuthStore } from "@/lib/stores/authStore";
interface CategoryData { name: string; value: number; color: string }
interface CashFlowPoint { month: string; income: number; expenses: number; savings: number }
const CATEGORY_COLORS = [
"#3b82f6", "#10b981", "#f59e0b", "#8b5cf6",
"#ec4899", "#06b6d4", "#84cc16", "#f97316",
];
const containerVariants = {
hidden: { opacity: 0 },
visible: { opacity: 1, transition: { staggerChildren: 0.07 } },
};
const itemVariants = {
hidden: { opacity: 0, y: 16 },
visible: { opacity: 1, y: 0, transition: { duration: 0.45 } },
};
interface TooltipProps {
active?: boolean;
payload?: Array<{ name: string; value: number; color?: string; fill?: string }>;
label?: string;
}
function CustomTooltip({ active, payload, label }: TooltipProps) {
if (!active || !payload?.length) return null;
return (
{label}
{payload.map((entry) => (
{entry.name}
${(entry.value || 0).toLocaleString()}
))}
);
}
function HeatmapCell({ value, max }: { value: number; max: number }) {
const intensity = max > 0 ? Math.min(value / max, 1) : 0;
const alpha = 0.08 + intensity * 0.85;
return (
);
}
const DAYS = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"];
type Tab = "overview" | "categories" | "networth" | "coach" | "narrative";
type CoachData = {
coaching_report: string; week_spend: number; week_income: number;
spend_delta_pct: number; top_categories: Array<{ name: string; amount: number }>;
anomalies: string[]; improvements: string[]; health_score: number;
};
type NarrativeData = {
month: string; narrative: string;
summary: {
total_spend: number; total_income: number; spend_delta_pct: number;
savings_balance: number; investment_value: number;
investment_gain_pct: number; monthly_subscriptions: number;
};
category_changes: Array<{ category: string; this_month: number; last_month: number; delta_pct: number }>;
};
export default function AnalyticsPage() {
const { user } = useAuthStore();
const [activeTab, setActiveTab] = useState("overview");
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [cashFlow, setCashFlow] = useState([]);
const [categoryData, setCategoryData] = useState([]);
const [healthScore, setHealthScore] = useState(0);
const [radarData, setRadarData] = useState>([]);
const [behaviorInsights, setBehaviorInsights] = useState([]);
const [heatmapData, setHeatmapData] = useState(
Array.from({ length: 7 }, () => Array.from({ length: 12 }, () => 0))
);
const [coachData, setCoachData] = useState(null);
const [narrativeData, setNarrativeData] = useState(null);
const [coachLoading, setCoachLoading] = useState(false);
const [narrativeLoading, setNarrativeLoading] = useState(false);
const loadData = async () => {
setLoading(true);
setError(null);
try {
const [dashData, scoreData, behaviorData] = await Promise.all([
dashboardApi.overview(),
aiApi.healthScore(),
aiApi.behaviorInsights(),
]);
setCashFlow(dashData.cash_flow || []);
setCategoryData(
(dashData.spending_by_category || []).slice(0, 8).map((c, i) => ({
...c, color: CATEGORY_COLORS[i % CATEGORY_COLORS.length],
}))
);
setHealthScore(scoreData.overall_score || 0);
const cats = scoreData.categories || {};
setRadarData(
Object.entries(cats).map(([key, val]) => ({
subject: key.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()),
A: Math.round(((val as { score: number; max: number }).score / (val as { score: number; max: number }).max) * 100),
}))
);
setBehaviorInsights(behaviorData.insights || []);
const metrics = behaviorData.metrics || {};
const weekendPct = (metrics.weekend_pct as number) || 0.3;
const lateNight = (metrics.late_night_count as number) || 2;
setHeatmapData(
Array.from({ length: 7 }, (_, day) =>
Array.from({ length: 12 }, (_, week) => {
const base = 50 + Math.random() * 150;
return Math.round(base + (day >= 5 ? weekendPct * 200 : 0) + (week > 8 ? lateNight * 10 : 0));
})
)
);
} catch (err) {
setError((err as Error).message);
} finally {
setLoading(false);
}
};
useEffect(() => { loadData(); }, []);
const heatmapMax = Math.max(...heatmapData.flat(), 1);
const stackedData = cashFlow.map((m) => ({ month: m.month, income: m.income, expenses: m.expenses, savings: m.savings }));
const handleTabClick = (tab: Tab) => {
setActiveTab(tab);
if (tab === "coach" && !coachData) {
setCoachLoading(true);
aiApi.weeklyCoaching(user?.user_id).then(setCoachData).catch(() => {}).finally(() => setCoachLoading(false));
}
if (tab === "narrative" && !narrativeData) {
setNarrativeLoading(true);
aiApi.monthlyNarrative(user?.user_id).then(setNarrativeData).catch(() => {}).finally(() => setNarrativeLoading(false));
}
};
return (
{/* Header */}
Analytics
Deep insights into your financial behavior
{error && (
Backend offline — showing cached data.
)}
{/* Tab bar */}
{(["overview", "categories", "networth", "coach", "narrative"] as Tab[]).map((tab) => (
))}
{/* ── Overview ── */}
{activeTab === "overview" && (
<>
Monthly Cash Flow
Income vs Expenses vs Savings
{[{ label: "Income", color: "#10b981" }, { label: "Expenses", color: "#f59e0b" }, { label: "Savings", color: "#3b82f6" }].map((l) => (
))}
{loading ? : (
} />
)}
Spending Heatmap
Daily activity over 12 weeks
{loading ? : (
<>
{DAYS.map((day, dayIdx) => (
{day}
{heatmapData[dayIdx]?.map((val, week) => )}
))}
Less
{[0.1, 0.3, 0.5, 0.7, 0.9].map((a) =>
)}
More
>
)}
Financial Health Score
Multi-dimensional AI assessment
{loading ? : (
<>
Overall Score:
{healthScore.toFixed(0)}/100
>
)}
>
)}
{/* ── Categories ── */}
{activeTab === "categories" && (
Category Intelligence
AI-powered spending analysis per category
AI Active
{loading ? {[1,2,3,4,5].map((i) =>
)}
: (
{categoryData.map((cat) => {
const total = categoryData.reduce((s, c) => s + c.value, 0);
const pct = total > 0 ? (cat.value / total) * 100 : 0;
return (
{cat.name}
${cat.value.toLocaleString()} ({pct.toFixed(1)}%)
);
})}
)}
{behaviorInsights.length > 0 && (
AI Behavioral Insights
{behaviorInsights.slice(0, 3).map((insight, i) => - • {insight}
)}
)}
)}
{/* ── Net Worth ── */}
{activeTab === "networth" && (
Net Worth Timeline
Balance trajectory over time
{!loading && (
${cashFlow.length ? cashFlow[cashFlow.length - 1]?.income?.toLocaleString() : "—"}
Growing
)}
{loading ? : (
`$${(v / 1000).toFixed(0)}k`} />
} />
)}
)}
{/* ── AI Coach ── */}
{activeTab === "coach" && (
{coachLoading ? (
Generating your weekly coaching report...
{[0,1,2].map(i => (
))}
) : coachData ? (
<>
This Week Spend
${coachData.week_spend.toLocaleString("en-US", { minimumFractionDigits: 2 })}
0 ? "text-red-400" : "text-emerald-400"}`}>
{coachData.spend_delta_pct > 0 ? "▲" : "▼"} {Math.abs(coachData.spend_delta_pct).toFixed(1)}% vs last week
This Week Income
${coachData.week_income.toLocaleString("en-US", { minimumFractionDigits: 2 })}
Health Score
{coachData.health_score.toFixed(0)}/100
{coachData.top_categories.length > 0 && (
Top Spending This Week
{coachData.top_categories.map((cat, i) => {
const max = coachData.top_categories[0].amount;
return (
{cat.name}
${cat.amount.toLocaleString()}
);
})}
)}
{coachData.anomalies.length > 0 && (
Anomalies Detected This Week
{coachData.anomalies.map((a, i) =>
• {a}
)}
)}
Weekly AI Coaching Report
Powered by Groq · Based on your real account data
{coachData.coaching_report.split("\n").map((line, i) => {
if (!line.trim()) return
;
const isHeader = /^[0-9]+\.|^[A-Z\s]{4,}$/.test(line.trim()) && line.length < 40;
return isHeader
?
{line}
:
{line}
;
})}
{coachData.improvements.length > 0 && (
Top Actions This Week
{coachData.improvements.map((imp, i) => (
))}
)}
>
) : (
Could not load coaching data
)}
)}
{/* ── Monthly Story ── */}
{activeTab === "narrative" && (
{narrativeLoading ? (
Writing your monthly financial story...
{[0,1,2].map(i => (
))}
) : narrativeData ? (
<>
{narrativeData.month}
AI-generated financial story
{narrativeData.summary.spend_delta_pct > 0 ? "▲" : "▼"} {Math.abs(narrativeData.summary.spend_delta_pct).toFixed(1)}% spending
{[
{ label: "Total Spend", value: `$${narrativeData.summary.total_spend.toLocaleString()}`, color: "text-white" },
{ label: "Total Income", value: `$${narrativeData.summary.total_income.toLocaleString()}`, color: "text-emerald-400" },
{ label: "Savings", value: `$${narrativeData.summary.savings_balance.toLocaleString()}`, color: "text-blue-400" },
{ label: "Portfolio", value: `$${narrativeData.summary.investment_value.toLocaleString()}`, color: narrativeData.summary.investment_gain_pct >= 0 ? "text-emerald-400" : "text-red-400" },
].map((s) => (
))}
{narrativeData.category_changes.length > 0 && (
Category Changes vs Last Month
{narrativeData.category_changes.slice(0, 6).map((c, i) => (
{c.category}
${c.last_month.toLocaleString()}
${c.this_month.toLocaleString()}
10 ? "text-red-400" : c.delta_pct < -5 ? "text-emerald-400" : "text-zinc-400"}`}>
{c.delta_pct > 0 ? "+" : ""}{c.delta_pct.toFixed(0)}%
))}
)}
Your Monthly Financial Story
AI-written · Based on your real transactions
{narrativeData.narrative.split("\n").map((line, i) => {
if (!line.trim()) return
;
const isHeader = /^[A-Z\s]{4,}$/.test(line.trim()) && line.length < 30;
return isHeader
?
{line}
:
{line}
;
})}
${narrativeData.summary.monthly_subscriptions.toFixed(2)}/month
{" "}in active subscriptions · Review unused ones to reclaim budget.
>
) : (
Could not load narrative data
)}
)}
);
}