import { useEffect, useState } from 'react'; import { api } from '@renderer/api'; import { formatTokensCompact } from '@shared/utils/tokenFormatting'; import { AlertCircle, BarChart3, ChevronDown, ChevronRight, FileCode, Info, Loader2, } from 'lucide-react'; import type { MemberFullStats } from '@shared/types'; interface MemberStatsTabProps { teamName: string; memberName: string; } export const MemberStatsTab = ({ teamName, memberName, }: MemberStatsTabProps): React.JSX.Element => { const [stats, setStats] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { let cancelled = false; setLoading(true); setError(null); void (async () => { try { const result = await api.teams.getMemberStats(teamName, memberName); if (!cancelled) { setStats(result); } } catch (e) { if (!cancelled) { setError(e instanceof Error ? e.message : 'Unknown error'); } } finally { if (!cancelled) { setLoading(false); } } })(); return () => { cancelled = true; }; }, [teamName, memberName]); if (loading) { return (
Computing stats...
); } if (error) { return (
{error}
); } if (!stats) { return (
No stats available
); } const totalTokens = stats.inputTokens + stats.outputTokens; const totalToolCalls = Object.values(stats.toolUsage).reduce((sum, c) => sum + c, 0); return (
); }; const StatCard = ({ label, value, sub, info, }: { label: string; value: string | number; sub?: string; info?: string; }): React.JSX.Element => (

{value}

{label}

{info && ( {info} )}
{sub &&

{sub}

}
); const SummaryCards = ({ stats, totalTokens, totalToolCalls, }: { stats: MemberFullStats; totalTokens: number; totalToolCalls: number; }): React.JSX.Element => (
0 ? `-${stats.linesRemoved}` : undefined} info="Approximate. Accurate for Edit and Write tools. Bash file writes are estimated from command patterns (heredoc, echo, sed) and may be underreported." />
); const ToolUsageBars = ({ toolUsage, }: { toolUsage: Record; }): React.JSX.Element | null => { const entries = Object.entries(toolUsage).sort(([, a], [, b]) => b - a); if (entries.length === 0) return null; const maxCount = entries[0][1]; return (

Tool Usage

{entries.map(([name, count]) => (
{name}
{count}
))}
); }; const FilesTouchedSection = ({ files }: { files: string[] }): React.JSX.Element | null => { const [expanded, setExpanded] = useState(false); if (files.length === 0) return null; const visibleFiles = expanded ? files : files.slice(0, 5); const hiddenCount = files.length - 5; return (

Files Touched ({files.length})

{visibleFiles.map((filePath) => { const basename = filePath.split('/').pop() ?? filePath; return (
{basename}
); })}
{hiddenCount > 0 && ( )}
); }; const StatsFooter = ({ stats }: { stats: MemberFullStats }): React.JSX.Element => { const computedAgo = formatRelativeTime(stats.computedAt); return (
{stats.sessionCount} session{stats.sessionCount !== 1 ? 's' : ''} ยท computed {computedAgo}
); }; function formatRelativeTime(isoString: string): string { const date = new Date(isoString); const now = Date.now(); const diffMs = now - date.getTime(); const diffMin = Math.floor(diffMs / 60_000); const diffHours = Math.floor(diffMin / 60); if (diffMin < 1) return 'just now'; if (diffMin < 60) return `${diffMin}m ago`; if (diffHours < 24) return `${diffHours}h ago`; return date.toLocaleDateString(); }