"use client"; import { Card, CardContent, CardHeader, CardTitle } from "@kandev/ui/card"; import { Button } from "@kandev/ui/button"; import { PageTopbar } from "@/components/page-topbar"; import { ToggleGroup, ToggleGroupItem } from "@kandev/ui/toggle-group"; import type { StatsResponse } from "@/lib/types/http"; import { useMemo, useState } from "react"; import { useRouter, useSearchParams } from "next/navigation"; import { IconChartBar } from "@tabler/icons-react"; import { OverviewCards, WorkloadSection, RepositoryStatsGrid, TopRepositories, RepoLeaders, } from "./stats-sections"; import { ActivityHeatmap, AgentUsageList, CompletedTasksChart, MostProductiveSummary, } from "./stats-charts"; import { PRStatsPanel } from "@/components/github/pr-stats"; interface StatsPageClientProps { stats: StatsResponse | null; error: string | null; workspaceId?: string; activeRange?: RangeKey; } const EMPTY_STATS: StatsResponse = { global: { total_tasks: 0, completed_tasks: 0, in_progress_tasks: 0, total_sessions: 0, total_turns: 0, total_messages: 0, total_user_messages: 0, total_tool_calls: 0, total_duration_ms: 0, avg_turns_per_task: 0, avg_messages_per_task: 0, avg_duration_ms_per_task: 0, }, task_stats: [], daily_activity: [], completed_activity: [], agent_usage: [], repository_stats: [], git_stats: { total_commits: 0, total_files_changed: 0, total_insertions: 0, total_deletions: 0 }, }; function formatDuration(ms: number): string { if (ms === 0) return "—"; const seconds = Math.floor(ms / 1000); const minutes = Math.floor(seconds / 60); const hours = Math.floor(minutes / 60); if (hours > 0) return `${hours}h ${minutes % 60}m`; if (minutes > 0) return `${minutes}m ${seconds % 60}s`; return `${seconds}s`; } type RangeKey = "week" | "month" | "all"; function getRangeLabel(range: RangeKey): string { switch (range) { case "week": return "Last Week"; case "month": return "Last Month"; case "all": return "All Time"; default: return "Last Month"; } } function StatsEmptyState({ message }: { message: string }) { return (
} />

{message}

); } type StatsHeaderProps = { global: StatsResponse["global"]; range: RangeKey; copied: boolean; onRangeChange: (r: RangeKey) => void; onCopy: () => void; }; function StatsHeader({ global, range, copied, onRangeChange, onCopy }: StatsHeaderProps) { return ( } subtitle={`${global.total_tasks} tasks · ${global.total_sessions} sessions · ${formatDuration(global.total_duration_ms)}`} actions={ <> { if (v) onRangeChange(v as RangeKey); }} variant="outline" className="h-7" > {(["week", "month", "all"] as RangeKey[]).map((key) => ( {getRangeLabel(key)} ))} } /> ); } type StatsContentProps = { resolvedStats: StatsResponse; rangeLabel: string; workspaceId?: string }; function SectionDivider({ id, label }: { id: string; label: string }) { return (
{label}
); } function TelemetrySection({ completedActivity, dailyActivity, agentUsage, rangeLabel, }: { completedActivity: StatsResponse["completed_activity"]; dailyActivity: StatsResponse["daily_activity"]; agentUsage: StatsResponse["agent_usage"]; rangeLabel: string; }) { return ( <>
Completed Tasks Over Time Most Productive
Activity ({rangeLabel.toLowerCase()}) Top Agents
); } function StatsContent({ resolvedStats, rangeLabel, workspaceId }: StatsContentProps) { const { global, task_stats, completed_activity, daily_activity, agent_usage, repository_stats, git_stats, } = resolvedStats; return (
Repository Activity Top Repositories Repo Leaders
); } function buildStatsSummary( resolvedStats: StatsResponse, rangeLabel: string, completedInRange: number, ): string { const { global, repository_stats, git_stats } = resolvedStats; const completion = global.total_tasks > 0 ? `${Math.round((global.completed_tasks / global.total_tasks) * 100)}%` : "—"; const topRepo = repository_stats .filter((r) => r.total_tasks > 0) .sort((a, b) => b.total_tasks - a.total_tasks)[0]; const topRepoLabel = topRepo ? `${topRepo.repository_name} (${topRepo.total_tasks} tasks)` : "—"; const hasGitStats = git_stats && (git_stats.total_commits > 0 || git_stats.total_files_changed > 0); const gitLine = hasGitStats ? `${git_stats.total_commits} commits, +${git_stats.total_insertions.toLocaleString()}/-${git_stats.total_deletions.toLocaleString()}` : "no git activity"; return [ `*KanDev Stats — ${rangeLabel}*`, `- Tasks: ${global.total_tasks} total (${global.completed_tasks} done, ${global.in_progress_tasks} in progress) · ${completion} completion`, `- Completed (${rangeLabel}): ${completedInRange}`, `- Time: ${formatDuration(global.total_duration_ms)} total · ${formatDuration(global.avg_duration_ms_per_task)} avg/task`, `- Repos: ${repository_stats.length} tracked · Top repo: ${topRepoLabel}`, `- Git: ${gitLine}`, ].join("\n"); } export function StatsPageClient({ stats, error, workspaceId, activeRange }: StatsPageClientProps) { const router = useRouter(); const searchParams = useSearchParams(); const [copied, setCopied] = useState(false); const range = (activeRange ?? "month") as RangeKey; const rangeLabel = getRangeLabel(range); const resolvedStats = stats ?? EMPTY_STATS; const completedInRange = useMemo( () => (resolvedStats.completed_activity ?? []).reduce((sum, item) => sum + item.completed_tasks, 0), [resolvedStats.completed_activity], ); const statsSummary = useMemo( () => buildStatsSummary(resolvedStats, rangeLabel, completedInRange), [resolvedStats, rangeLabel, completedInRange], ); if (!workspaceId) return ; if (error) return (
} />

Error loading stats: {error}

); if (!stats) return ; const handleCopyStats = async () => { try { await navigator.clipboard.writeText(statsSummary); setCopied(true); window.setTimeout(() => setCopied(false), 2000); } catch (err) { console.error("Failed to copy stats summary", err); } }; const handleRangeChange = (nextRange: RangeKey) => { const params = new URLSearchParams(searchParams?.toString() ?? ""); params.set("range", nextRange); router.push(`/stats?${params.toString()}`); }; return (
); }