import { useEffect, useState, useRef } from 'react'; import { useTranslation } from 'react-i18next'; import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, PieChart, Pie, Cell } from 'recharts'; import { TrendingUp, Users, MessageSquare, BrainCircuit, ChevronRight, Award, Clock, Search, Loader2, Terminal } from 'lucide-react'; import { useAuth } from '../lib/auth'; import { useTenant } from '../lib/tenant'; import { api } from '../lib/api'; import { logError } from '../lib/logger'; const COLORS = ['#6366f1', '#10b981', '#f59e0b', '#ef4444']; export default function AnalyticsPage() { const { token } = useAuth(); const { t } = useTranslation(); const { selectedOrgId } = useTenant(); const [usage, setUsage] = useState(null); const [pedagogy, setPedagogy] = useState(null); const [loading, setLoading] = useState(true); const [sqlQuestion, setSqlQuestion] = useState(''); const [sqlLoading, setSqlLoading] = useState(false); const [sqlResult, setSqlResult] = useState<{ rows: Record[]; sql: string; count: number } | null>(null); const [sqlError, setSqlError] = useState(null); const sqlInputRef = useRef(null); useEffect(() => { if (!selectedOrgId || !token) return; const fetchData = async () => { setLoading(true); try { const [usageData, pedagogyData] = await Promise.all([ api.get('/v1/analytics/usage', token, selectedOrgId), api.get('/v1/analytics/pedagogy', token, selectedOrgId) ]); setUsage(usageData); setPedagogy(pedagogyData); } catch (err) { logError('Analytics fetch error:', err); } finally { setLoading(false); } }; fetchData(); }, [selectedOrgId, token]); if (!selectedOrgId) { return (
{t('dashboard.select_org_hint')}
); } if (loading) return
{t('common.loading')}
; const handleSqlQuery = async (e: React.FormEvent) => { e.preventDefault(); if (!sqlQuestion.trim() || !selectedOrgId || !token) return; setSqlLoading(true); setSqlResult(null); setSqlError(null); try { const data = await api.post('/v1/analytics/query', { question: sqlQuestion, language: 'FR' }, token, selectedOrgId); setSqlResult(data); } catch (err: any) { setSqlError(err?.message ?? t('analytics.sql_error')); } finally { setSqlLoading(false); } }; const EXAMPLE_QUESTIONS = [ t('analytics.sql_example_1'), t('analytics.sql_example_2'), t('analytics.sql_example_3'), t('analytics.sql_example_4'), ]; const messageData = [ { name: t('analytics.messages.inbound'), value: usage?.messages?.inbound || 0 }, { name: t('analytics.messages.outbound'), value: usage?.messages?.outbound || 0 }, ]; const completionData = [ { name: t('analytics.completion.completed'), value: pedagogy?.completion?.completed || 0 }, { name: t('analytics.completion.in_progress'), value: pedagogy?.completion?.active || 0 }, ]; return (

{t('dashboard.title')}

{t('dashboard.subtitle')}

Live System
{/* KPI Cards */}
} trend={null} color="text-indigo-600" bg="bg-indigo-50" /> } trend={null} color="text-emerald-600" bg="bg-emerald-50" /> } trend={null} color="text-amber-600" bg="bg-amber-50" /> } trend={`${((usage?.costs?.totalTokens ?? 0) / 1000).toFixed(1)}k tokens`} color="text-purple-600" bg="bg-purple-50" />
{/* Messages Chart */}

{t('analytics.messages.title')}

{/* Completion Pie */}

{t('analytics.completion.title')}

{completionData.map((_, index) => ( ))}
{completionData.map((d, i) => (
{d.name}: {d.value}
))}
{/* Detailed Pedagogy */}
{t('analytics.performance.title')}
{pedagogy?.performance?.averageScore?.toFixed(1) || 0}
{t('analytics.performance.avg_score')}
{t('analytics.engagement.title')}
{pedagogy?.performance?.averageProgressDays?.toFixed(1) || 0}
{t('analytics.engagement.avg_days')}
{/* ── AI Cost Breakdown ─────────────────────────────────────────── */} {usage?.costs?.byFeature?.length > 0 && (

{t('analytics.ai_cost_title')}

{t('analytics.ai_cost_subtitle')}

{(usage.costs.byFeature as Array<{ feature: string; calls: number; tokensIn: number; tokensOut: number; costUsd: number }>) .sort((a, b) => b.costUsd - a.costUsd) .map((row) => ( ))}
{t('analytics.col_feature')} {t('analytics.col_calls')} {t('analytics.col_tokens_in')} {t('analytics.col_tokens_out')} {t('analytics.col_cost')}
{row.feature} {row.calls.toLocaleString()} {row.tokensIn.toLocaleString()} {row.tokensOut.toLocaleString()} ${row.costUsd.toFixed(4)}
{t('analytics.total')} ${(usage.costs.totalUsd as number).toFixed(4)}
)} {/* ── Text-to-SQL Search ─────────────────────────────────────────── */}

{t('analytics.nl_search_title')}

{t('analytics.nl_search_subtitle')}

{/* Example questions */}
{EXAMPLE_QUESTIONS.map(q => ( ))}
setSqlQuestion(e.target.value)} placeholder={t('analytics.nl_search_placeholder')} className="flex-1 px-4 py-3 rounded-xl border border-slate-200 text-sm focus:outline-none focus:ring-2 focus:ring-violet-300 bg-slate-50" />
{/* Error */} {sqlError && (
{sqlError}
)} {/* Results */} {sqlResult && (
{sqlResult.count} résultat{sqlResult.count > 1 ? 's' : ''}
{(sqlResult as any)._showSql && (
                {sqlResult.sql}
              
)} {sqlResult.rows.length > 0 ? (
{Object.keys(sqlResult.rows[0]).map(col => ( ))} {sqlResult.rows.map((row, i) => ( {Object.values(row).map((val, j) => ( ))} ))}
{col}
{val === null ? : String(val)}
) : (
{t('analytics.no_results')}
)}
)}
); } function StatCard({ title, value, icon, trend, color, bg }: any) { return (
{icon}
{trend != null && (
{trend}
)}
{title}
{value}
); } function Building2(props: any) { return ( ); }