From 9506327343a594816b5023caeddb11eef276fb82 Mon Sep 17 00:00:00 2001 From: kaitranntt Date: Tue, 9 Dec 2025 22:46:10 -0500 Subject: [PATCH] refactor(analytics): inline sessions table component --- .../components/analytics/sessions-table.tsx | 269 ------------------ ui/src/hooks/use-usage.ts | 16 -- ui/src/pages/analytics.tsx | 251 ++++++---------- 3 files changed, 90 insertions(+), 446 deletions(-) delete mode 100644 ui/src/components/analytics/sessions-table.tsx diff --git a/ui/src/components/analytics/sessions-table.tsx b/ui/src/components/analytics/sessions-table.tsx deleted file mode 100644 index 956ac4a6..00000000 --- a/ui/src/components/analytics/sessions-table.tsx +++ /dev/null @@ -1,269 +0,0 @@ -/** - * Sessions Table Component - * - * Displays session history with pagination and filtering. - * Shows session duration, tokens, cost, and metadata. - */ - -import { useState, useMemo } from 'react'; -import { formatDistanceToNow } from 'date-fns'; -import { - Table, - TableBody, - TableCell, - TableHead, - TableHeader, - TableRow, -} from '@/components/ui/table'; -import { Button } from '@/components/ui/button'; -import { Input } from '@/components/ui/input'; -import { Badge } from '@/components/ui/badge'; -import { Skeleton } from '@/components/ui/skeleton'; -import { ChevronLeft, ChevronRight, Search, Clock, Zap, DollarSign } from 'lucide-react'; -import { cn } from '@/lib/utils'; -import type { PaginatedSessions } from '@/hooks/use-usage'; - -interface SessionsTableProps { - data?: PaginatedSessions; - isLoading?: boolean; -} - -export function SessionsTable({ data, isLoading }: SessionsTableProps) { - const [searchTerm, setSearchTerm] = useState(''); - const [currentPage, setCurrentPage] = useState(0); - - // Get sessions array (stable reference for memoization) - const sessions = useMemo(() => data?.sessions ?? [], [data?.sessions]); - - // Filter sessions based on search term - const filteredSessions = useMemo(() => { - if (!searchTerm) return sessions; - - const term = searchTerm.toLowerCase(); - return sessions.filter( - (session) => - session.profile.toLowerCase().includes(term) || - session.model.toLowerCase().includes(term) || - session.id.toLowerCase().includes(term) - ); - }, [sessions, searchTerm]); - - // Pagination for filtered data - const pageSize = 10; - const paginatedSessions = useMemo(() => { - if (!filteredSessions) return []; - const start = currentPage * pageSize; - return filteredSessions.slice(start, start + pageSize); - }, [filteredSessions, currentPage]); - - const totalPages = Math.ceil((filteredSessions?.length || 0) / pageSize); - - if (isLoading) { - return ; - } - - if (!data || data.sessions.length === 0) { - return ( -
- -

No sessions found

-

Start using Claude Code to see session history

-
- ); - } - - return ( -
- {/* Search Bar */} -
-
- - { - setSearchTerm(e.target.value); - setCurrentPage(0); - }} - className="pl-8" - /> -
-
- - {/* Table */} -
- - - - Session ID - Profile - Model - Duration - Tokens - Cost - Requests - Last Used - - - - {paginatedSessions.map((session) => ( - - {session.id.slice(0, 8)}... - - {session.profile} - - {session.model} - {session.duration ? formatDuration(session.duration) : '-'} - -
- - {formatNumber(session.tokens)} -
-
- -
- $ - {session.cost.toFixed(4)} -
-
- {session.requests} - - {formatDistanceToNow(new Date(session.startTime), { addSuffix: true })} - -
- ))} -
-
-
- - {/* Pagination */} - {totalPages > 1 && ( -
-

- Showing {currentPage * pageSize + 1} to{' '} - {Math.min((currentPage + 1) * pageSize, filteredSessions?.length || 0)} of{' '} - {filteredSessions?.length} sessions -

-
- -
- {Array.from({ length: Math.min(5, totalPages) }, (_, i) => { - const page = i; - return ( - - ); - })} -
- -
-
- )} -
- ); -} - -// Helper functions -function formatDuration(ms: number): string { - 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`; -} - -function formatNumber(num: number): string { - if (num >= 1000000) { - return `${(num / 1000000).toFixed(1)}M`; - } - if (num >= 1000) { - return `${(num / 1000).toFixed(1)}K`; - } - return num.toLocaleString(); -} - -// Skeleton loading state -function SessionsTableSkeleton() { - return ( -
-
- -
-
- - - - Session ID - Profile - Model - Duration - Tokens - Cost - Requests - Last Used - - - - {[1, 2, 3, 4, 5].map((i) => ( - - - - - - - - - - - - - - - - - - - - - - - - - - - ))} - -
-
-
- ); -} diff --git a/ui/src/hooks/use-usage.ts b/ui/src/hooks/use-usage.ts index 2a19800a..69dfe88e 100644 --- a/ui/src/hooks/use-usage.ts +++ b/ui/src/hooks/use-usage.ts @@ -175,22 +175,6 @@ export function useModelUsage(options?: UsageQueryOptions) { }); } -export function useSessions(options?: UsageQueryOptions) { - return useQuery({ - queryKey: ['usage', 'sessions', options], - queryFn: () => usageApi.sessions(options), - staleTime: 60 * 1000, // 1 minute - }); -} - -export function useMonthlyUsage(months?: number, profile?: string) { - return useQuery({ - queryKey: ['usage', 'monthly', months, profile], - queryFn: () => usageApi.monthly(months, profile), - staleTime: 5 * 60 * 1000, // 5 minutes - }); -} - /** * Hook to refresh all usage data * Clears server-side cache and invalidates React Query cache diff --git a/ui/src/pages/analytics.tsx b/ui/src/pages/analytics.tsx index d000c405..6f102607 100644 --- a/ui/src/pages/analytics.tsx +++ b/ui/src/pages/analytics.tsx @@ -1,14 +1,13 @@ /** * Analytics Page * - * Displays Claude Code usage analytics with charts and tables. - * Features daily/monthly views, trend charts, model breakdown, and session history. + * Displays Claude Code usage analytics with charts. + * Features trend charts, model breakdown, and cost analysis. */ import { useState, useMemo } from 'react'; import type { DateRange } from 'react-day-picker'; import { startOfMonth, subDays, formatDistanceToNow } from 'date-fns'; -import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; import { Skeleton } from '@/components/ui/skeleton'; @@ -16,27 +15,22 @@ import { DateRangeFilter } from '@/components/analytics/date-range-filter'; import { UsageSummaryCards } from '@/components/analytics/usage-summary-cards'; import { UsageTrendChart } from '@/components/analytics/usage-trend-chart'; import { ModelBreakdownChart } from '@/components/analytics/model-breakdown-chart'; -import { SessionsTable } from '@/components/analytics/sessions-table'; -import { TrendingUp, PieChart, Clock, Calendar, RefreshCw } from 'lucide-react'; +import { TrendingUp, PieChart, RefreshCw } from 'lucide-react'; import { useUsageSummary, useUsageTrends, useModelUsage, - useSessions, useRefreshUsage, useUsageStatus, } from '@/hooks/use-usage'; import { getModelColor } from '@/lib/utils'; -type ViewMode = 'daily' | 'monthly' | 'sessions'; - export function AnalyticsPage() { // Default to last 30 days const [dateRange, setDateRange] = useState({ from: subDays(new Date(), 30), to: new Date(), }); - const [viewMode, setViewMode] = useState('daily'); const [isRefreshing, setIsRefreshing] = useState(false); // Refresh hook @@ -61,10 +55,6 @@ export function AnalyticsPage() { const { data: summary, isLoading: isSummaryLoading } = useUsageSummary(apiOptions); const { data: trends, isLoading: isTrendsLoading } = useUsageTrends(apiOptions); const { data: models, isLoading: isModelsLoading } = useModelUsage(apiOptions); - const { data: sessions, isLoading: isSessionsLoading } = useSessions({ - ...apiOptions, - limit: 50, - }); const { data: status } = useUsageStatus(); // Format "Last updated" text @@ -90,6 +80,7 @@ export function AnalyticsPage() { { label: '7D', range: { from: subDays(new Date(), 7), to: new Date() } }, { label: '30D', range: { from: subDays(new Date(), 30), to: new Date() } }, { label: 'Month', range: { from: startOfMonth(new Date()), to: new Date() } }, + { label: 'All Time', range: { from: undefined, to: new Date() } }, ]} /> {lastUpdatedText && ( @@ -112,163 +103,101 @@ export function AnalyticsPage() { {/* Summary Cards */} - {/* Main Content Tabs */} - setViewMode(v as ViewMode)} - className="flex-1 flex flex-col min-h-0" - > - - - Daily - - - Monthly - - - Sessions - - + {/* Main Content */} +
+ {/* Usage Trend Chart - Full Width */} + + + + + Usage Trends + + + + + + -
- {/* Daily View */} - - {/* Usage Trend Chart - Full Width */} - - - - - Usage Trends - - - - - - - - {/* Bottom Row - Model Usage & Cost */} -
- {/* Model Distribution */} - - - - - Model Usage - - - -
-
- + {/* Model Distribution */} + + + + + Model Usage + + + +
+
+ +
+
+ {models?.slice(0, 8).map((model) => ( +
+
+
+
+ + {model.model} + + + {model.percentage.toFixed(1)}% + +
+
-
- {models?.slice(0, 8).map((model) => ( -
+ ))} +
+
+ + + + {/* Cost Breakdown */} + + + Cost by Model + + + {isModelsLoading ? ( + + ) : ( +
+ {[...(models || [])] + .sort((a, b) => b.cost - a.cost) + .map((model) => ( +
+
-
-
- - {model.model} - - - {model.percentage.toFixed(1)}% - -
-
+ + {model.model} +
- ))} -
-
- - - - {/* Cost Breakdown */} - - - Cost by Model - - - {isModelsLoading ? ( - - ) : ( -
- {[...(models || [])] - .sort((a, b) => b.cost - a.cost) - .map((model) => ( -
-
-
- - {model.model} - -
- - ${model.cost.toFixed(4)} - -
- ))} -
- )} - - -
- - - {/* Monthly View */} - - - - - - Monthly Overview - - - - - - - - - {/* Sessions View */} - - - - - - Session History - - - -
- + + ${model.cost.toFixed(4)} + +
+ ))}
-
-
- + )} + +
- +
);