'use client'; import React, { useState, useEffect, useCallback } from 'react'; import Link from 'next/link'; import { useAuth } from '@/contexts/AuthContext'; import { useRouter } from 'next/navigation'; import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, LineChart, Line, PieChart, Pie, Cell, Legend, } from 'recharts'; // ─── Types ──────────────────────────────────────────────────────────────────── interface ArticleMetrics { totalArticles: number; publishedArticles: number; draftArticles: number; pendingArticles: number; archivedArticles: number; importedArticles: number; nativeArticles: number; } interface AuthorStat { author_name: string; total: number; published: number; draft: number; pending: number; } interface CategoryStat { category: string; count: number; published: number; } interface ImportStat { label: string; total: number; success: number; failed: number; rate: number; } interface MonthlyTrend { month: string; published: number; imported: number; native: number; } // ─── Mock / derived data helpers ────────────────────────────────────────────── const CATEGORY_COLORS = [ '#3b82f6', '#f59e0b', '#10b981', '#8b5cf6', '#ef4444', '#06b6d4', '#f97316', '#84cc16', ]; // ─── Stat Card ──────────────────────────────────────────────────────────────── interface StatCardProps { label: string; value: number | string; sub?: string; accent?: string; icon: React.ReactNode; } function StatCard({ label, value, sub, accent = '#3b82f6', icon }: StatCardProps) { return (
{icon}

{label}

{value}

{sub &&

{sub}

}
); } // ─── Section Header ─────────────────────────────────────────────────────────── function SectionHeader({ title, subtitle }: { title: string; subtitle?: string }) { return (

{title}

{subtitle &&

{subtitle}

}
); } // ─── Custom Tooltip ─────────────────────────────────────────────────────────── function ChartTooltip({ active, payload, label }: any) { if (!active || !payload?.length) return null; return (
{label &&

{label}

} {payload.map((entry: any, i: number) => (

{entry.name}: {entry.value}

))}
); } // ─── Main Page ──────────────────────────────────────────────────────────────── export default function AdminAnalyticsPage() { const { user, loading } = useAuth(); const router = useRouter(); const [metrics, setMetrics] = useState(null); const [authorStats, setAuthorStats] = useState([]); const [categoryStats, setCategoryStats] = useState([]); const [importStats, setImportStats] = useState([]); const [monthlyTrends, setMonthlyTrends] = useState([]); const [loadingData, setLoadingData] = useState(true); const [error, setError] = useState(null); useEffect(() => { if (!loading && !user) router.replace('/login?next=' + encodeURIComponent(window.location.pathname)); }, [user, loading, router]); const fetchAnalytics = useCallback(async () => { setLoadingData(true); setError(null); try { // Fetch all articles to derive analytics const res = await fetch('/api/admin/articles'); if (!res.ok) throw new Error('Failed to fetch articles'); const data = await res.json(); const articles: any[] = data.articles ?? []; // ── Article Metrics ────────────────────────────────────────────────── const m: ArticleMetrics = { totalArticles: articles.length, publishedArticles: articles.filter((a) => a.status === 'published').length, draftArticles: articles.filter((a) => a.status === 'draft').length, pendingArticles: articles.filter((a) => a.status === 'pending').length, archivedArticles: articles.filter((a) => a.status === 'archived').length, importedArticles: articles.filter((a) => a.source === 'imported').length, nativeArticles: articles.filter((a) => a.source === 'native').length, }; setMetrics(m); // ── Author Stats ───────────────────────────────────────────────────── const authorMap: Record = {}; articles.forEach((a) => { const name = a.author_name || 'Unknown'; if (!authorMap[name]) { authorMap[name] = { author_name: name, total: 0, published: 0, draft: 0, pending: 0 }; } authorMap[name].total++; if (a.status === 'published') authorMap[name].published++; if (a.status === 'draft') authorMap[name].draft++; if (a.status === 'pending') authorMap[name].pending++; }); const sortedAuthors = Object.values(authorMap) .sort((a, b) => b.total - a.total) .slice(0, 10); setAuthorStats(sortedAuthors); // ── Category Stats ─────────────────────────────────────────────────── const catMap: Record = {}; articles.forEach((a) => { const cat = a.category || 'Uncategorized'; if (!catMap[cat]) catMap[cat] = { category: cat, count: 0, published: 0 }; catMap[cat].count++; if (a.status === 'published') catMap[cat].published++; }); const sortedCats = Object.values(catMap) .sort((a, b) => b.count - a.count) .slice(0, 8); setCategoryStats(sortedCats); // ── Import Stats ───────────────────────────────────────────────────── const imported = articles.filter((a) => a.source === 'imported'); const native = articles.filter((a) => a.source === 'native'); const importedPublished = imported.filter((a) => a.status === 'published').length; const nativePublished = native.filter((a) => a.status === 'published').length; setImportStats([ { label: 'WP Imported', total: imported.length, success: importedPublished, failed: imported.filter((a) => a.status === 'archived').length, rate: imported.length > 0 ? Math.round((importedPublished / imported.length) * 100) : 0, }, { label: 'Native', total: native.length, success: nativePublished, failed: native.filter((a) => a.status === 'archived').length, rate: native.length > 0 ? Math.round((nativePublished / native.length) * 100) : 0, }, ]); // ── Monthly Trends (last 6 months from article dates) ──────────────── const now = new Date(); const months: MonthlyTrend[] = []; for (let i = 5; i >= 0; i--) { const d = new Date(now.getFullYear(), now.getMonth() - i, 1); const key = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}`; const label = d.toLocaleString('default', { month: 'short', year: '2-digit' }); const monthArticles = articles.filter((a) => { const date = a.date ? new Date(a.date) : null; if (!date) return false; return ( date.getFullYear() === d.getFullYear() && date.getMonth() === d.getMonth() ); }); months.push({ month: label, published: monthArticles.filter((a) => a.status === 'published').length, imported: monthArticles.filter((a) => a.source === 'imported').length, native: monthArticles.filter((a) => a.source === 'native').length, }); } setMonthlyTrends(months); } catch (err: any) { setError(err.message ?? 'Unknown error'); } finally { setLoadingData(false); } }, []); useEffect(() => { if (user) fetchAnalytics(); }, [user, fetchAnalytics]); if (loading || loadingData) { return (

Loading analytics…

); } const publishRate = metrics && metrics.totalArticles > 0 ? Math.round((metrics.publishedArticles / metrics.totalArticles) * 100) : 0; return (
{/* ── Page Header ─────────────────────────────────────────────────── */}

Analytics

Article performance, author contributions & import health

Articles · Users · WP Import
{error && (
{error}
)} {/* ── Overview Stats ───────────────────────────────────────────── */}
} accent="#3b82f6" /> } accent="#10b981" /> } accent="#f59e0b" /> } accent="#8b5cf6" /> } accent="#06b6d4" /> } accent="#f97316" /> } accent="#555" /> } accent="#3b82f6" />
{/* ── Monthly Trend ────────────────────────────────────────────── */}
{monthlyTrends.length > 0 ? ( } /> ) : (

No trend data available

)}
{/* ── Author Contributions + Category Performance ──────────────── */}
{/* Author Contributions */}
{authorStats.length > 0 ? ( v.length > 14 ? v.slice(0, 13) + '…' : v} /> } /> ) : (

No author data available

)}
{/* Top Categories */}
{categoryStats.length > 0 ? ( <> {categoryStats.map((_, i) => ( ))} } />
{categoryStats.map((cat, i) => (
{cat.category}
{cat.published} pub {cat.count}
))}
) : (

No category data available

)}
{/* ── Import Success Rates ─────────────────────────────────────── */}
{importStats.map((stat) => (

{stat.label}

= 70 ? '#10b981' : stat.rate >= 40 ? '#f59e0b' : '#ef4444', background: stat.rate >= 70 ? '#10b98118' : stat.rate >= 40 ? '#f59e0b18' : '#ef444418', }} > {stat.rate}% success
{/* Progress bar */}
= 70 ? '#10b981' : stat.rate >= 40 ? '#f59e0b' : '#ef4444', }} />

Total

{stat.total}

Published

{stat.success}

Archived

{stat.failed}

))}
{/* ── Author Detail Table ──────────────────────────────────────── */}
{authorStats.length === 0 ? ( ) : ( authorStats.map((a, i) => { const rate = a.total > 0 ? Math.round((a.published / a.total) * 100) : 0; return ( ); }) )}
Author Total Published Draft Pending Pub Rate
No author data available
{a.author_name} {a.total} {a.published} {a.draft} {a.pending} = 70 ? '#10b981' : rate >= 40 ? '#f59e0b' : '#ef4444', background: rate >= 70 ? '#10b98118' : rate >= 40 ? '#f59e0b18' : '#ef444418', }} > {rate}%
); }