import { useEffect, useState } from "react" import spinnerSrc from "../../assets/Circular-indeterminate progress indicator.svg" import { Area, AreaChart, Bar, BarChart, CartesianGrid, Cell, // Legend, Pie, PieChart, ResponsiveContainer, Tooltip, XAxis, YAxis, } from "recharts" import alertSrc from "../../assets/Alert.svg" import { Users, BadgeCheck, DollarSign, BookOpen, HelpCircle, Bell, TicketCheck, UsersRound, TrendingUp, TrendingDown, CreditCard, Video, Layers, FolderOpen, RefreshCw, ChevronDown, } from "lucide-react" import { Card, CardContent, CardHeader, CardTitle } from "../../components/ui/card" import { Badge } from "../../components/ui/badge" import { Button } from "../../components/ui/button" import { cn } from "../../lib/utils" import { getDashboard } from "../../api/analytics.api" import { AnalyticsTimeRangeFilter, getDashboardFilterLabel } from "../../components/analytics/AnalyticsTimeRangeFilter" import { getPrimaryQuestionTypeSummary, getSeriesPeriodLabel, getVideoLessonsSummary, } from "../../lib/analytics" import type { DashboardData, DashboardFilters, LabelCount } from "../../types/analytics.types" const PIE_COLORS = ["#9E2891", "#FFD23F", "#1DE9B6", "#C26FC0", "#6366F1", "#F97316", "#14B8A6", "#EF4444", "#8B5CF6", "#EC4899", "#06B6D4", "#84CC16"] function formatDate(dateStr: string) { const d = new Date(dateStr) return d.toLocaleDateString("en-US", { month: "short", day: "numeric" }) } function formatNumber(n: number) { if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}M` if (n >= 1_000) return `${(n / 1_000).toFixed(1)}K` return n.toLocaleString() } function KpiCard({ icon: Icon, label, value, sub, trend, className, }: { icon: React.ElementType label: string value: string sub?: string trend?: "up" | "down" | "neutral" className?: string }) { return (
{label}
{value}
{sub && (
{trend === "up" && } {trend === "down" && } {sub}
)}
) } function BreakdownList({ title, data, total, }: { title: string data: LabelCount[] total?: number }) { const computedTotal = total ?? data.reduce((s, d) => s + d.count, 0) return ( {title} {data.length > 0 ? (
{data.map((item, i) => { const pct = computedTotal > 0 ? (item.count / computedTotal) * 100 : 0 return (
{item.label}
{item.count.toLocaleString()} ({pct.toFixed(0)}%)
) })}
) : (
No data available
)} ) } function DonutCard({ title, data, centerLabel, centerValue, }: { title: string data: { name: string; value: number; color: string }[] centerLabel?: string centerValue?: string }) { return ( {title} {data.length > 0 ? (
{data.map((entry) => ( ))} {centerLabel && (
{centerValue} {centerLabel}
)}
{data.map((s) => (
{s.name}
{s.value.toLocaleString()}
))}
) : (
No data available
)}
) } function Section({ title, icon: Icon, count, defaultOpen = true, children, }: { title: string icon: React.ElementType count?: number defaultOpen?: boolean children: React.ReactNode }) { const [open, setOpen] = useState(defaultOpen) return (
{children}
) } const DEFAULT_FILTERS: DashboardFilters = { mode: "all_time" } export function AnalyticsPage() { const [dashboard, setDashboard] = useState(null) const [loading, setLoading] = useState(true) const [error, setError] = useState(false) const [activeSummaryTab, setActiveSummaryTab] = useState<"key" | "content" | "operations">("key") const [filters, setFilters] = useState(DEFAULT_FILTERS) const fetchData = async (nextFilters: DashboardFilters = filters) => { setLoading(true) setError(false) try { const res = await getDashboard(nextFilters) setDashboard(res.data) } catch { setError(true) } finally { setLoading(false) } } useEffect(() => { fetchData(filters) // eslint-disable-next-line react-hooks/exhaustive-deps }, [filters]) if (!dashboard && loading) { return (
Analytics
Loading analytics…
) } if (error || !dashboard) { return (
Analytics
Failed to load analytics data.
) } const { users, subscriptions, payments, courses, content, notifications, issues, team } = dashboard const seriesPeriodLabel = getSeriesPeriodLabel(dashboard.date_filter) const lms = courses.lms const examPrep = courses.exam_prep const registrationData = users.registrations_last_30_days.map((d) => ({ date: formatDate(d.date), count: d.count, })) const subscriptionData = subscriptions.new_subscriptions_last_30_days.map((d) => ({ date: formatDate(d.date), count: d.count, })) const revenueData = payments.revenue_last_30_days.map((d) => ({ date: formatDate(d.date), revenue: d.revenue, })) const issueStatusPie = issues.by_status.map((s, i) => ({ name: s.label, value: s.count, color: PIE_COLORS[i % PIE_COLORS.length], })) const subscriptionStatusPie = subscriptions.by_status.map((s, i) => ({ name: s.label, value: s.count, color: PIE_COLORS[i % PIE_COLORS.length], })) const notifByTypePie = notifications.by_type.slice(0, 8).map((s, i) => ({ name: s.label, value: s.count, color: PIE_COLORS[i % PIE_COLORS.length], })) const generatedAt = new Date(dashboard.generated_at).toLocaleString("en-US", { month: "short", day: "numeric", year: "numeric", hour: "2-digit", minute: "2-digit", }) return (
{/* Header */}
Analytics

Platform Overview

{getDashboardFilterLabel(filters)} · Generated {generatedAt}
{loading && (
Updating analytics for {getDashboardFilterLabel(filters)}…
)} {/* Summary Tabs */}
{activeSummaryTab === "key" && ( <> {/* ─── Key Metrics ─── */}
0 ? "up" : "neutral"} /> 0 ? "up" : "neutral"} /> 0 ? "up" : "neutral"} /> = 0.5 ? "up" : "down"} />
)} {activeSummaryTab === "content" && ( <> {/* ─── Content & Platform ─── */}
0 ? "up" : "neutral"} /> 0 ? "up" : "neutral"} />
)} {activeSummaryTab === "operations" && ( <> {/* ─── Operations ─── */}
0 ? "up" : "neutral"} /> `${q.count} ${q.label.toLowerCase()}`).join(" · ")} trend="neutral" />
)} {/* ─── User Analytics ─── */}
User Registrations
{users.total_users.toLocaleString()} +{users.new_today} today
{seriesPeriodLabel}
{/* ─── Subscriptions & Revenue ─── */}
New Subscriptions
{subscriptions.total_subscriptions.toLocaleString()}
+{subscriptions.new_today} today · +{subscriptions.new_week} this week
{seriesPeriodLabel}
Revenue
ETB {payments.total_revenue.toLocaleString()}
Daily revenue over last 30 days
{seriesPeriodLabel}
[`ETB ${Number(v).toLocaleString()}`, "Revenue"]} contentStyle={{ borderRadius: 12, border: "1px solid #E0E0E0", boxShadow: "0 10px 30px rgba(0,0,0,0.08)", fontSize: 12, }} />
{/* ─── Issues & Support ─── */}
{/* ─── Notifications ─── */}
{/* ─── Course Management ─── */} {(lms || examPrep) && (
{lms && ( )} {examPrep && ( )}
)} {/* ─── Content Breakdown ─── */}
{/* ─── Team ─── */}
) }