import React, { useState, useEffect, useMemo, Suspense } from 'react'; import { Card, CardContent, Typography, Box, Grid, Chip, Tooltip, CircularProgress, Tabs, Tab, Alert, } from '@mui/material'; import { motion } from 'framer-motion'; import { Clock, Activity, TrendingUp, BarChart3, Zap, Calendar } from 'lucide-react'; import { LazyBarChart, LazyPieChart, Bar, Pie, Cell, XAxis, YAxis, CartesianGrid, Tooltip as RechartsTooltip, ResponsiveContainer, Legend, ChartLoadingFallback } from '../../utils/lazyRecharts'; // Types import { UsageLog } from '../../types/billing'; // Services import { billingService, formatCurrency, formatNumber } from '../../services/billingService'; // Components import DailyCostHeatmap from './DailyCostHeatmap'; interface AdvancedCostAnalyticsProps { userId?: string; terminalTheme?: boolean; } interface TimeOfDayData { hour: number; label: string; cost: number; calls: number; avgCostPerCall: number; } interface UserActionData { endpoint: string; action: string; cost: number; calls: number; avgCostPerCall: number; avgResponseTime: number; } interface EfficiencyMetric { label: string; value: number; unit: string; trend: 'up' | 'down' | 'stable'; description: string; } const AdvancedCostAnalytics: React.FC = ({ userId, terminalTheme = false }) => { const [usageLogs, setUsageLogs] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [activeTab, setActiveTab] = useState(0); useEffect(() => { const fetchUsageLogs = async () => { try { setLoading(true); setError(null); const currentDate = new Date(); const billingPeriod = `${currentDate.getFullYear()}-${String(currentDate.getMonth() + 1).padStart(2, '0')}`; console.log('[AdvancedCostAnalytics] Fetching usage logs for period:', billingPeriod); // Try with billing period first, then without if no results let response = await billingService.getUsageLogs(2000, 0, undefined, undefined, billingPeriod); let logs = response.logs || []; // If no logs for current period, try without billing period filter to get recent logs if (logs.length === 0) { console.log('[AdvancedCostAnalytics] No logs for current period, fetching all recent logs...'); response = await billingService.getUsageLogs(2000, 0); logs = response.logs || []; } console.log('[AdvancedCostAnalytics] Received logs:', { total: logs.length, sample: logs.slice(0, 3), totalCost: logs.reduce((sum, log) => sum + (log.cost_total || 0), 0), totalCalls: logs.length, logsWithCost: logs.filter(log => (log.cost_total || 0) > 0).length, logsWithTokens: logs.filter(log => (log.tokens_total || 0) > 0).length, sampleLogStructure: logs[0] ? { id: logs[0].id, cost_total: logs[0].cost_total, tokens_total: logs[0].tokens_total, status: logs[0].status, status_code: logs[0].status_code, response_time: logs[0].response_time } : null }); setUsageLogs(logs); } catch (err) { console.error('[AdvancedCostAnalytics] Error fetching usage logs:', err); setError(err instanceof Error ? err.message : 'Failed to fetch usage logs'); // Set empty array on error to prevent showing stale data setUsageLogs([]); } finally { setLoading(false); } }; fetchUsageLogs(); }, [userId]); // Time of Day Analysis const timeOfDayData = useMemo(() => { const hourlyData: Record = {}; usageLogs.forEach(log => { // Include all logs, not just those with cost > 0, for accurate call counts const cost = log.cost_total || 0; try { const date = new Date(log.timestamp); if (isNaN(date.getTime())) { console.warn('[AdvancedCostAnalytics] Invalid timestamp:', log.timestamp); return; } const hour = date.getHours(); if (!hourlyData[hour]) { hourlyData[hour] = { cost: 0, calls: 0 }; } hourlyData[hour].cost += cost; hourlyData[hour].calls += 1; } catch (err) { console.warn('[AdvancedCostAnalytics] Error processing log timestamp:', err, log); } }); return Array.from({ length: 24 }, (_, hour) => { const data = hourlyData[hour] || { cost: 0, calls: 0 }; return { hour, label: `${hour.toString().padStart(2, '0')}:00`, cost: data.cost, calls: data.calls, avgCostPerCall: data.calls > 0 ? data.cost / data.calls : 0 } as TimeOfDayData; }); }, [usageLogs]); // User Action Breakdown const userActionData = useMemo(() => { const actionMap: Record = {}; usageLogs.forEach(log => { // Include all logs for accurate call counts const cost = log.cost_total || 0; // Extract action from endpoint (e.g., /api/blog-writer/generate -> blog-writer) const endpoint = log.endpoint || ''; const endpointParts = endpoint.split('/'); const action = endpointParts.length > 2 ? endpointParts[2] : 'other'; if (!actionMap[action]) { actionMap[action] = { cost: 0, calls: 0, responseTime: 0 }; } actionMap[action].cost += cost; actionMap[action].calls += 1; actionMap[action].responseTime += log.response_time || 0; }); return Object.entries(actionMap) .map(([endpoint, data]) => ({ endpoint, action: endpoint.replace(/-/g, ' ').replace(/\b\w/g, l => l.toUpperCase()), cost: data.cost, calls: data.calls, avgCostPerCall: data.calls > 0 ? data.cost / data.calls : 0, avgResponseTime: data.calls > 0 ? data.responseTime / data.calls : 0 } as UserActionData)) .sort((a, b) => b.cost - a.cost) .slice(0, 10); // Top 10 actions }, [usageLogs]); // Cost Efficiency Metrics const efficiencyMetrics = useMemo(() => { const totalCost = usageLogs.reduce((sum, log) => sum + (log.cost_total || 0), 0); const totalCalls = usageLogs.length; const totalTokens = usageLogs.reduce((sum, log) => sum + (log.tokens_total || 0), 0); const totalResponseTime = usageLogs.reduce((sum, log) => sum + (log.response_time || 0), 0); const successfulCalls = usageLogs.filter(log => log.status === 'success' || (log.status_code && log.status_code < 400)).length; const failedCalls = usageLogs.filter(log => log.status === 'failed' || (log.status_code && log.status_code >= 400)).length; // Debug logging if (usageLogs.length > 0) { console.log('[AdvancedCostAnalytics] Efficiency metrics calculation:', { totalLogs: usageLogs.length, totalCost, totalTokens, totalCalls, successfulCalls, failedCalls, sampleLog: usageLogs[0] }); } const avgCostPerCall = totalCalls > 0 ? totalCost / totalCalls : 0; const avgCostPerToken = totalTokens > 0 ? totalCost / totalTokens : 0; const avgResponseTime = totalCalls > 0 ? totalResponseTime / totalCalls : 0; const successRate = totalCalls > 0 ? (successfulCalls / totalCalls) * 100 : 0; // Tokens per dollar - handle division by zero const costEfficiency = totalCost > 0 && totalTokens > 0 ? totalTokens / totalCost : 0; // Calculate trends (simplified - compare first half vs second half) const midPoint = Math.floor(usageLogs.length / 2); const firstHalf = usageLogs.slice(0, midPoint); const secondHalf = usageLogs.slice(midPoint); const firstHalfAvgCost = firstHalf.length > 0 ? firstHalf.reduce((sum, log) => sum + (log.cost_total || 0), 0) / firstHalf.length : 0; const secondHalfAvgCost = secondHalf.length > 0 ? secondHalf.reduce((sum, log) => sum + (log.cost_total || 0), 0) / secondHalf.length : 0; const costTrend = secondHalfAvgCost > firstHalfAvgCost * 1.05 ? 'up' : secondHalfAvgCost < firstHalfAvgCost * 0.95 ? 'down' : 'stable'; return [ { label: 'Avg Cost per Call', value: avgCostPerCall, unit: '$', trend: costTrend, description: 'Average cost per API call' }, { label: 'Cost per 1K Tokens', value: avgCostPerToken * 1000, unit: '$', trend: costTrend, description: 'Cost efficiency for token usage' }, { label: 'Tokens per Dollar', value: costEfficiency, unit: '', trend: costTrend === 'down' ? 'up' : costTrend === 'up' ? 'down' : 'stable', description: 'How many tokens you get per dollar spent' }, { label: 'Avg Response Time', value: avgResponseTime, unit: 's', trend: 'stable', description: 'Average API response time' }, { label: 'Success Rate', value: successRate, unit: '%', trend: successRate > 95 ? 'up' : successRate < 90 ? 'down' : 'stable', description: 'Percentage of successful API calls' }, { label: 'Failed Calls', value: failedCalls, unit: '', trend: failedCalls > 0 ? 'down' : 'stable', description: 'Number of failed API calls' } ] as EfficiencyMetric[]; }, [usageLogs]); const COLORS = ['#667eea', '#764ba2', '#f093fb', '#4facfe', '#00f2fe', '#43e97b', '#fa709a', '#fee140', '#30cfd0', '#a8edea']; if (loading) { return ( Loading analytics... ); } if (error) { return ( {error} Unable to load usage analytics. Please try refreshing the page. ); } // Show message if no logs available if (usageLogs.length === 0) { return ( No Usage Data Available Usage analytics will appear here once you start making API calls. ); } return ( Advanced Cost Analytics setActiveTab(newValue)} sx={{ borderBottom: '1px solid rgba(255,255,255,0.1)', mb: 2, '& .MuiTab-root': { color: 'rgba(255,255,255,0.7)', '&.Mui-selected': { color: '#667eea', fontWeight: 'bold' } }, '& .MuiTabs-indicator': { backgroundColor: '#667eea' } }} > } label="Time of Day" iconPosition="start" /> } label="User Actions" iconPosition="start" /> } label="Efficiency Metrics" iconPosition="start" /> } label="Daily Heatmap" iconPosition="start" /> {/* Time of Day Tab */} {activeTab === 0 && ( Cost Distribution by Hour of Day }> `$${value.toFixed(2)}`} /> [formatCurrency(Number(value) || 0), 'Cost']} /> {/* Peak Hours Summary */} {timeOfDayData .sort((a, b) => b.cost - a.cost) .slice(0, 3) .map((data, idx) => ( Peak Hour {idx + 1} {data.label} {formatCurrency(data.cost)} ))} )} {/* User Actions Tab */} {activeTab === 1 && ( Cost Breakdown by User Action }> { const data = userActionData[entry.index]; return data ? `${data.action}: ${(entry.percent * 100).toFixed(0)}%` : ''; }} > {userActionData.map((_, index) => ( ))} formatCurrency(Number(value) || 0)} contentStyle={{ backgroundColor: 'rgba(0, 0, 0, 0.8)', border: '1px solid rgba(255,255,255,0.1)', borderRadius: 8 }} /> {/* Top Actions Table */} {userActionData.map((action, idx) => ( {action.action} {action.calls} calls • {formatCurrency(action.avgCostPerCall)} avg {formatCurrency(action.cost)} ))} )} {/* Efficiency Metrics Tab */} {activeTab === 2 && ( Cost Efficiency Metrics {efficiencyMetrics.map((metric, idx) => ( {metric.label} {metric.trend === 'up' && } {metric.trend === 'down' && } {metric.unit === '$' ? formatCurrency(metric.value) : metric.unit === '%' ? `${metric.value.toFixed(1)}%` : metric.unit === 's' ? `${metric.value.toFixed(2)}s` : formatNumber(metric.value)} ))} )} {/* Daily Heatmap Tab */} {activeTab === 3 && ( )} ); }; export default AdvancedCostAnalytics;