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 (
{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 (
)
}
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 (
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 ─── */}
)
}