/** * Analytics page — team usage analytics dashboard. * * Shows active users, token usage, top tools, capability gaps, * and performance trends. Admin-only. */ import React, { useEffect, useState } from 'react'; import { api, getErrorMessage, type AnalyticsResponse } from '../api.js'; interface AnalyticsProps { token: string; teamSlug: string; } /* ─── Shared styles ─── */ const cardStyle: React.CSSProperties = { padding: 20, background: '#12141a', border: '1px solid #2a2d36', borderRadius: 8, boxShadow: '0 1px 3px rgba(0,0,0,0.4), 0 1px 2px rgba(0,0,0,0.3)', }; const sectionTitle: React.CSSProperties = { fontSize: 14, fontWeight: 600, color: '#9ca3af', textTransform: 'uppercase', letterSpacing: '0.05em', margin: '0 0 12px', }; const thStyle: React.CSSProperties = { textAlign: 'left', padding: '10px 12px', fontSize: 13, color: '#9ca3af', fontWeight: 600, borderBottom: '1px solid #2a2d36', }; const tdStyle: React.CSSProperties = { padding: '10px 12px', fontSize: 14, color: '#cbd5e1', borderBottom: '1px solid #1a1d25', }; const tableStyle: React.CSSProperties = { width: '100%', borderCollapse: 'collapse', background: '#12141a', border: '1px solid #2a2d36', borderRadius: 8, overflow: 'hidden', boxShadow: '0 1px 3px rgba(0,0,0,0.4), 0 1px 2px rgba(0,0,0,0.3)', }; /* ─── Sub-components ─── */ function isRecord(value: unknown): value is Record { return typeof value === 'object' && value !== null; } function isAnalyticsResponse(value: unknown): value is AnalyticsResponse { if (!isRecord(value)) return false; const activeUsers = value.activeUsers; const tokenUsage = value.tokenUsage; const performanceTrends = value.performanceTrends; return ( isRecord(activeUsers) && typeof activeUsers.daily === 'number' && typeof activeUsers.weekly === 'number' && typeof activeUsers.monthly === 'number' && isRecord(tokenUsage) && typeof tokenUsage.total === 'number' && Array.isArray(tokenUsage.byUser) && Array.isArray(value.topTools) && Array.isArray(value.topCommands) && Array.isArray(value.capabilityGaps) && isRecord(performanceTrends) && typeof performanceTrends.correctionRate === 'number' && typeof performanceTrends.correctionTrend === 'number' && typeof performanceTrends.avgResponseTime === 'number' ); } function ActiveUsersCard({ data }: { data: AnalyticsResponse['activeUsers'] }) { return (

Active Users

{data.daily}
Last 24h
{data.weekly}
Last 7d
{data.monthly}
Last 30d
); } function TokenUsageCard({ data }: { data: AnalyticsResponse['tokenUsage'] }) { const formattedTotal = data.total >= 1000000 ? `${(data.total / 1000000).toFixed(1)}M` : data.total >= 1000 ? `${(data.total / 1000).toFixed(1)}K` : String(data.total); return (

Token Usage

{formattedTotal} tokens
{data.byUser.length === 0 ? (

No usage data yet.

) : (
{data.byUser.map((u) => ( ))}
User Tokens Cost
{u.name} {u.tokens.toLocaleString()} ${u.cost.toFixed(2)}
)}
); } function TopToolsCard({ data }: { data: AnalyticsResponse['topTools'] }) { const maxInvocations = data.length > 0 ? Math.max(...data.map((t) => t.invocations)) : 1; return (

Top Tools

{data.length === 0 ? (

No tool usage data yet.

) : (
{data.map((tool) => { const widthPercent = Math.max((tool.invocations / maxInvocations) * 100, 2); return (
{tool.name} {tool.invocations}
); })}
)}
); } function TopCommandsCard({ data }: { data: AnalyticsResponse['topCommands'] }) { const maxCount = data.length > 0 ? Math.max(...data.map((c) => c.count)) : 1; return (

Top Commands

{data.length === 0 ? (

No command usage data yet.

) : (
{data.map((cmd) => { const widthPercent = Math.max((cmd.count / maxCount) * 100, 2); return (
{cmd.name} {cmd.count}
); })}
)}
); } function CapabilityGapsCard({ data }: { data: AnalyticsResponse['capabilityGaps'] }) { return (

Capability Gaps

{data.length === 0 ? (

No capability gaps detected.

) : (
{data.map((gap) => ( ))}
Tool Requests Suggestion
{gap.tool} {gap.requestCount} {gap.suggestion}
)}
); } function PerformanceTrendsCard({ data }: { data: AnalyticsResponse['performanceTrends'] }) { const trendColor = data.correctionTrend < 0 ? '#10b981' : data.correctionTrend > 0 ? '#ef4444' : '#6b7280'; const trendArrow = data.correctionTrend < 0 ? 'v' : data.correctionTrend > 0 ? '^' : '-'; const trendLabel = data.correctionTrend < 0 ? 'improving' : data.correctionTrend > 0 ? 'worsening' : 'stable'; return (

Performance Trends

Correction Rate
{(data.correctionRate * 100).toFixed(1)}%
{trendArrow} {Math.abs(data.correctionTrend * 100).toFixed(1)}% ({trendLabel})
Avg Response Time
{data.avgResponseTime.toFixed(1)}s
per completed job
); } /* ─── Main page ─── */ export function Analytics({ token, teamSlug }: AnalyticsProps) { const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { if (!token || !teamSlug) return; let cancelled = false; (async () => { try { setLoading(true); setError(null); setData(null); const result = await api.getAnalytics(token, teamSlug); if (!isAnalyticsResponse(result)) { throw new Error('Analytics data is incomplete. Refresh or check the team server version.'); } if (!cancelled) setData(result); } catch (err) { if (!cancelled) { setData(null); setError(getErrorMessage(err, 'Failed to load analytics')); } } finally { if (!cancelled) setLoading(false); } })(); return () => { cancelled = true; }; }, [token, teamSlug]); if (loading) { return (

Analytics

Loading analytics...

); } return (

Usage Analytics

{error && (
{error}
)} {data && ( <> {/* Row 1: Active Users + Performance Trends */}
{/* Row 2: Token Usage */}
{/* Row 3: Top Tools + Top Commands */}
{/* Row 4: Capability Gaps */}
)}
); }