story writer backend migration complete, Blog writer SEO and story writer backend migration complete, Blog writer SEO and story writer frontend migration complete

This commit is contained in:
ajaysi
2025-11-13 16:14:26 +05:30
parent 7191c7e7f0
commit 3b9356e2c8
124 changed files with 20055 additions and 1208 deletions

View File

@@ -28,15 +28,33 @@ import {
calculateUsagePercentage
} from '../../services/billingService';
// Terminal Theme
import {
TerminalCard,
TerminalCardContent,
TerminalTypography,
TerminalChip,
TerminalChipSuccess,
TerminalChipError,
TerminalChipWarning,
terminalColors
} from '../SchedulerDashboard/terminalTheme';
interface BillingOverviewProps {
usageStats: UsageStats;
onRefresh: () => void;
terminalTheme?: boolean;
}
const BillingOverview: React.FC<BillingOverviewProps> = ({
usageStats,
onRefresh
onRefresh,
terminalTheme = false
}) => {
// Conditional component selection based on terminal theme
const CardComponent = terminalTheme ? TerminalCard : Card;
const CardContentComponent = terminalTheme ? TerminalCardContent : CardContent;
const TypographyComponent = terminalTheme ? TerminalTypography : Typography;
// Debug logs removed to reduce console noise
const costUsagePercentage = calculateUsagePercentage(
@@ -47,9 +65,53 @@ const BillingOverview: React.FC<BillingOverviewProps> = ({
// Debug logs removed to reduce console noise
const getStatusChip = () => {
const status = usageStats.usage_status;
const status: string = usageStats.usage_status;
const icon = getUsageStatusIcon(status);
// Helper function to format status label
const formatStatusLabel = (statusStr: string): string => {
return statusStr.charAt(0).toUpperCase() + statusStr.slice(1).replace('_', ' ');
};
if (terminalTheme) {
if (status === 'active') {
return (
<TerminalChipSuccess
icon={<span>{icon}</span>}
label={formatStatusLabel(status)}
size="small"
sx={{ fontWeight: 'bold' }}
/>
);
} else if (status === 'warning') {
return (
<TerminalChipWarning
icon={<span>{icon}</span>}
label={formatStatusLabel(status)}
size="small"
sx={{ fontWeight: 'bold' }}
/>
);
} else if (status === 'limit_reached') {
return (
<TerminalChipError
icon={<span>{icon}</span>}
label={formatStatusLabel(status)}
size="small"
sx={{ fontWeight: 'bold' }}
/>
);
}
return (
<TerminalChip
icon={<span>{icon}</span>}
label={formatStatusLabel(status)}
size="small"
sx={{ fontWeight: 'bold' }}
/>
);
}
let chipColor: 'default' | 'primary' | 'secondary' | 'error' | 'info' | 'success' | 'warning' = 'default';
if (status === 'active') chipColor = 'success';
else if (status === 'warning') chipColor = 'warning';
@@ -58,7 +120,7 @@ const BillingOverview: React.FC<BillingOverviewProps> = ({
return (
<Chip
icon={<span>{icon}</span>}
label={status.charAt(0).toUpperCase() + status.slice(1).replace('_', ' ')}
label={formatStatusLabel(status)}
color={chipColor}
size="small"
sx={{ fontWeight: 'bold' }}
@@ -66,6 +128,25 @@ const BillingOverview: React.FC<BillingOverviewProps> = ({
);
};
const cardStyles = terminalTheme
? {
height: '100%',
backgroundColor: terminalColors.background,
border: `1px solid ${terminalColors.border}`,
borderRadius: 3,
position: 'relative' as const,
overflow: 'hidden' as const
}
: {
height: '100%',
background: 'linear-gradient(135deg, rgba(255,255,255,0.1) 0%, rgba(255,255,255,0.05) 100%)',
backdropFilter: 'blur(10px)',
border: '1px solid rgba(255,255,255,0.1)',
borderRadius: 3,
position: 'relative' as const,
overflow: 'hidden' as const
};
return (
<motion.div
initial={{ opacity: 0, y: 20 }}
@@ -73,34 +154,24 @@ const BillingOverview: React.FC<BillingOverviewProps> = ({
transition={{ duration: 0.4 }}
whileHover={{ scale: 1.02 }}
>
<Card
sx={{
height: '100%',
background: 'linear-gradient(135deg, rgba(255,255,255,0.1) 0%, rgba(255,255,255,0.05) 100%)',
backdropFilter: 'blur(10px)',
border: '1px solid rgba(255,255,255,0.1)',
borderRadius: 3,
position: 'relative',
overflow: 'hidden'
}}
>
<CardComponent sx={cardStyles}>
{/* Header */}
<CardContent sx={{ pb: 1 }}>
<CardContentComponent sx={{ pb: 1 }}>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 2 }}>
<Typography variant="h6" sx={{ display: 'flex', alignItems: 'center', gap: 1, fontWeight: 'bold' }}>
<DollarSign size={20} />
<TypographyComponent variant="h6" sx={{ display: 'flex', alignItems: 'center', gap: 1, fontWeight: 'bold' }}>
<DollarSign size={20} color={terminalTheme ? terminalColors.text : undefined} />
Billing Overview
</Typography>
</TypographyComponent>
<Tooltip title="View your current billing status, usage metrics, and subscription plan details">
<Info size={16} color="rgba(255,255,255,0.7)" />
<Info size={16} color={terminalTheme ? terminalColors.textSecondary : "rgba(255,255,255,0.7)"} />
</Tooltip>
<Tooltip title="Refresh data">
<IconButton
size="small"
onClick={onRefresh}
sx={{
color: 'text.secondary',
'&:hover': { color: 'primary.main' }
color: terminalTheme ? terminalColors.text : 'text.secondary',
'&:hover': { color: terminalTheme ? terminalColors.secondary : 'primary.main' }
}}
>
<RefreshCw size={16} />
@@ -112,9 +183,9 @@ const BillingOverview: React.FC<BillingOverviewProps> = ({
<Box sx={{ mb: 3 }}>
{getStatusChip()}
</Box>
</CardContent>
</CardContentComponent>
<CardContent sx={{ pt: 0 }}>
<CardContentComponent sx={{ pt: 0 }}>
{/* Current Cost */}
<Box sx={{ mb: 3, textAlign: 'center' }}>
<motion.div
@@ -122,20 +193,20 @@ const BillingOverview: React.FC<BillingOverviewProps> = ({
animate={{ scale: 1 }}
transition={{ duration: 0.5, delay: 0.2 }}
>
<Typography
<TypographyComponent
variant="h3"
sx={{
fontWeight: 'bold',
color: '#ffffff',
textShadow: '0 2px 4px rgba(0,0,0,0.3)',
color: terminalTheme ? terminalColors.text : '#ffffff',
textShadow: terminalTheme ? 'none' : '0 2px 4px rgba(0,0,0,0.3)',
mb: 1
}}
>
{formatCurrency(usageStats.total_cost)}
</Typography>
<Typography variant="body2" sx={{ color: 'rgba(255,255,255,0.8)' }}>
</TypographyComponent>
<TypographyComponent variant="body2" sx={{ color: terminalTheme ? terminalColors.textSecondary : 'rgba(255,255,255,0.8)' }}>
Total Cost This Month
</Typography>
</TypographyComponent>
</motion.div>
</Box>
@@ -143,34 +214,34 @@ const BillingOverview: React.FC<BillingOverviewProps> = ({
<Box sx={{ mb: 3 }}>
<Tooltip title="Total number of API requests made this billing period">
<Box sx={{ display: 'flex', justifyContent: 'space-between', mb: 1 }}>
<Typography variant="body2" sx={{ color: 'rgba(255,255,255,0.7)' }}>
<TypographyComponent variant="body2" sx={{ color: terminalTheme ? terminalColors.textSecondary : 'rgba(255,255,255,0.7)' }}>
API Calls
</Typography>
<Typography variant="body2" sx={{ fontWeight: 'bold', color: '#ffffff' }}>
</TypographyComponent>
<TypographyComponent variant="body2" sx={{ fontWeight: 'bold', color: terminalTheme ? terminalColors.text : '#ffffff' }}>
{formatNumber(usageStats.total_calls)}
</Typography>
</TypographyComponent>
</Box>
</Tooltip>
<Tooltip title="Total tokens processed across all API providers (input + output tokens)">
<Box sx={{ display: 'flex', justifyContent: 'space-between', mb: 1 }}>
<Typography variant="body2" sx={{ color: 'rgba(255,255,255,0.7)' }}>
<TypographyComponent variant="body2" sx={{ color: terminalTheme ? terminalColors.textSecondary : 'rgba(255,255,255,0.7)' }}>
Tokens Used
</Typography>
<Typography variant="body2" sx={{ fontWeight: 'bold', color: '#ffffff' }}>
</TypographyComponent>
<TypographyComponent variant="body2" sx={{ fontWeight: 'bold', color: terminalTheme ? terminalColors.text : '#ffffff' }}>
{formatNumber(usageStats.total_tokens)}
</Typography>
</TypographyComponent>
</Box>
</Tooltip>
<Tooltip title="Average response time for API requests in the last 24 hours">
<Box sx={{ display: 'flex', justifyContent: 'space-between', mb: 2 }}>
<Typography variant="body2" sx={{ color: 'rgba(255,255,255,0.7)' }}>
<TypographyComponent variant="body2" sx={{ color: terminalTheme ? terminalColors.textSecondary : 'rgba(255,255,255,0.7)' }}>
Avg Response Time
</Typography>
<Typography variant="body2" sx={{ fontWeight: 'bold', color: '#ffffff' }}>
</TypographyComponent>
<TypographyComponent variant="body2" sx={{ fontWeight: 'bold', color: terminalTheme ? terminalColors.text : '#ffffff' }}>
{usageStats.avg_response_time.toFixed(0)}ms
</Typography>
</TypographyComponent>
</Box>
</Tooltip>
</Box>
@@ -179,12 +250,12 @@ const BillingOverview: React.FC<BillingOverviewProps> = ({
{usageStats.limits.limits.monthly_cost > 0 && (
<Box sx={{ mb: 3 }}>
<Box sx={{ display: 'flex', justifyContent: 'space-between', mb: 1 }}>
<Typography variant="body2" color="text.secondary">
<TypographyComponent variant="body2" sx={{ color: terminalTheme ? terminalColors.textSecondary : 'rgba(255,255,255,0.7)' }}>
Monthly Cost Limit
</Typography>
<Typography variant="body2" fontWeight="bold">
</TypographyComponent>
<TypographyComponent variant="body2" sx={{ fontWeight: 'bold', color: terminalTheme ? terminalColors.text : '#ffffff' }}>
{formatPercentage(costUsagePercentage)}
</Typography>
</TypographyComponent>
</Box>
<LinearProgress
variant="determinate"
@@ -192,17 +263,20 @@ const BillingOverview: React.FC<BillingOverviewProps> = ({
sx={{
height: 8,
borderRadius: 4,
backgroundColor: 'rgba(255,255,255,0.1)',
backgroundColor: terminalTheme ? terminalColors.backgroundLight : 'rgba(255,255,255,0.1)',
'& .MuiLinearProgress-bar': {
backgroundColor: costUsagePercentage > 80 ? '#ef4444' :
costUsagePercentage > 60 ? '#f59e0b' : '#22c55e',
backgroundColor: terminalTheme
? (costUsagePercentage > 80 ? terminalColors.error :
costUsagePercentage > 60 ? terminalColors.warning : terminalColors.success)
: (costUsagePercentage > 80 ? '#ef4444' :
costUsagePercentage > 60 ? '#f59e0b' : '#22c55e'),
borderRadius: 4,
}
}}
/>
<Typography variant="caption" color="text.secondary" sx={{ mt: 0.5, display: 'block' }}>
<TypographyComponent variant="caption" sx={{ mt: 0.5, display: 'block', color: terminalTheme ? terminalColors.textSecondary : 'rgba(255,255,255,0.7)' }}>
{formatCurrency(usageStats.total_cost)} of {formatCurrency(usageStats.limits.limits.monthly_cost)} limit
</Typography>
</TypographyComponent>
</Box>
)}
@@ -210,69 +284,73 @@ const BillingOverview: React.FC<BillingOverviewProps> = ({
<Box
sx={{
p: 2,
backgroundColor: 'rgba(255,255,255,0.05)',
backgroundColor: terminalTheme ? terminalColors.backgroundLight : 'rgba(255,255,255,0.05)',
borderRadius: 2,
border: '1px solid rgba(255,255,255,0.1)'
border: terminalTheme ? `1px solid ${terminalColors.border}` : '1px solid rgba(255,255,255,0.1)'
}}
>
<Typography variant="body2" sx={{ mb: 1, color: 'rgba(255,255,255,0.8)' }}>
<TypographyComponent variant="body2" sx={{ mb: 1, color: terminalTheme ? terminalColors.textSecondary : 'rgba(255,255,255,0.8)' }}>
Current Plan
</Typography>
<Typography variant="h6" sx={{ fontWeight: 'bold', mb: 1, color: '#ffffff' }}>
</TypographyComponent>
<TypographyComponent variant="h6" sx={{ fontWeight: 'bold', mb: 1, color: terminalTheme ? terminalColors.text : '#ffffff' }}>
{usageStats.limits.plan_name}
</Typography>
<Typography variant="caption" sx={{ color: 'rgba(255,255,255,0.7)' }}>
</TypographyComponent>
<TypographyComponent variant="caption" sx={{ color: terminalTheme ? terminalColors.textSecondary : 'rgba(255,255,255,0.7)' }}>
{usageStats.limits.tier.charAt(0).toUpperCase() + usageStats.limits.tier.slice(1)} Tier
</Typography>
</TypographyComponent>
</Box>
{/* Quick Stats */}
<Box sx={{ mt: 2, display: 'flex', justifyContent: 'space-around' }}>
<Box sx={{ textAlign: 'center' }}>
<Typography variant="h6" sx={{ fontWeight: 'bold', color: 'primary.main' }}>
<TypographyComponent variant="h6" sx={{ fontWeight: 'bold', color: terminalTheme ? terminalColors.text : 'primary.main' }}>
{usageStats.usage_percentages.gemini_calls.toFixed(0)}%
</Typography>
<Typography variant="caption" color="text.secondary">
</TypographyComponent>
<TypographyComponent variant="caption" sx={{ color: terminalTheme ? terminalColors.textSecondary : 'rgba(255,255,255,0.7)' }}>
Gemini Usage
</Typography>
</TypographyComponent>
</Box>
<Box sx={{ textAlign: 'center' }}>
<Typography variant="h6" sx={{ fontWeight: 'bold', color: 'secondary.main' }}>
<TypographyComponent variant="h6" sx={{ fontWeight: 'bold', color: terminalTheme ? terminalColors.text : 'secondary.main' }}>
{usageStats.error_rate.toFixed(1)}%
</Typography>
<Typography variant="caption" color="text.secondary">
</TypographyComponent>
<TypographyComponent variant="caption" sx={{ color: terminalTheme ? terminalColors.textSecondary : 'rgba(255,255,255,0.7)' }}>
Error Rate
</Typography>
</TypographyComponent>
</Box>
</Box>
</CardContent>
</CardContentComponent>
{/* Decorative Elements */}
<Box
sx={{
position: 'absolute',
top: -50,
right: -50,
width: 100,
height: 100,
background: 'radial-gradient(circle, rgba(102, 126, 234, 0.1) 0%, transparent 70%)',
borderRadius: '50%',
pointerEvents: 'none'
}}
/>
<Box
sx={{
position: 'absolute',
bottom: -30,
left: -30,
width: 60,
height: 60,
background: 'radial-gradient(circle, rgba(118, 75, 162, 0.1) 0%, transparent 70%)',
borderRadius: '50%',
pointerEvents: 'none'
}}
/>
</Card>
{/* Decorative Elements - only show in non-terminal theme */}
{!terminalTheme && (
<>
<Box
sx={{
position: 'absolute',
top: -50,
right: -50,
width: 100,
height: 100,
background: 'radial-gradient(circle, rgba(102, 126, 234, 0.1) 0%, transparent 70%)',
borderRadius: '50%',
pointerEvents: 'none'
}}
/>
<Box
sx={{
position: 'absolute',
bottom: -30,
left: -30,
width: 60,
height: 60,
background: 'radial-gradient(circle, rgba(118, 75, 162, 0.1) 0%, transparent 70%)',
borderRadius: '50%',
pointerEvents: 'none'
}}
/>
</>
)}
</CardComponent>
</motion.div>
);
};

View File

@@ -25,18 +25,36 @@ import { SystemHealth } from '../../types/monitoring';
import { billingService } from '../../services/billingService';
import { monitoringService } from '../../services/monitoringService';
import { onApiEvent } from '../../utils/apiEvents';
import { showToastNotification } from '../../utils/toastNotifications';
// Terminal Theme
import {
TerminalCard,
TerminalCardContent,
TerminalTypography,
TerminalChip,
TerminalChipError,
TerminalChipWarning,
terminalColors
} from '../SchedulerDashboard/terminalTheme';
interface CompactBillingDashboardProps {
userId?: string;
terminalTheme?: boolean;
}
const CompactBillingDashboard: React.FC<CompactBillingDashboardProps> = ({ userId }) => {
const CompactBillingDashboard: React.FC<CompactBillingDashboardProps> = ({ userId, terminalTheme = false }) => {
// Conditional component selection based on terminal theme
const CardComponent = terminalTheme ? TerminalCard : Card;
const CardContentComponent = terminalTheme ? TerminalCardContent : CardContent;
const TypographyComponent = terminalTheme ? TerminalTypography : Typography;
const ChipComponent = terminalTheme ? TerminalChip : Chip;
const [dashboardData, setDashboardData] = useState<DashboardData | null>(null);
const [systemHealth, setSystemHealth] = useState<SystemHealth | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const fetchData = async () => {
const fetchData = async (showSuccessToast: boolean = false) => {
try {
setLoading(true);
setError(null);
@@ -48,8 +66,21 @@ const CompactBillingDashboard: React.FC<CompactBillingDashboardProps> = ({ userI
setDashboardData(billingData);
setSystemHealth(healthData);
// Show success toast only if explicitly requested (user-initiated refresh)
if (showSuccessToast && billingData && healthData) {
showToastNotification(
'Billing data refreshed successfully',
'success',
{ duration: 3000 }
);
}
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to fetch data');
const errorMessage = err instanceof Error ? err.message : 'Failed to fetch data';
setError(errorMessage);
// Always show error toast for failures
showToastNotification(errorMessage, 'error', { duration: 5000 });
} finally {
setLoading(false);
}
@@ -80,35 +111,55 @@ const CompactBillingDashboard: React.FC<CompactBillingDashboardProps> = ({ userI
const formatNumber = (num: number) => num.toLocaleString();
if (loading && !dashboardData) {
const loadingCardStyles = terminalTheme
? {
backgroundColor: terminalColors.background,
border: `1px solid ${terminalColors.border}`,
borderRadius: 3
}
: {
background: 'linear-gradient(135deg, rgba(255,255,255,0.1) 0%, rgba(255,255,255,0.05) 100%)',
backdropFilter: 'blur(10px)',
border: '1px solid rgba(255,255,255,0.1)',
borderRadius: 3
};
return (
<Card sx={{
background: 'linear-gradient(135deg, rgba(255,255,255,0.1) 0%, rgba(255,255,255,0.05) 100%)',
backdropFilter: 'blur(10px)',
border: '1px solid rgba(255,255,255,0.1)',
borderRadius: 3
}}>
<CardContent sx={{ textAlign: 'center', py: 4 }}>
<Typography sx={{ color: 'rgba(255,255,255,0.8)' }}>Loading billing data...</Typography>
</CardContent>
</Card>
<CardComponent sx={loadingCardStyles}>
<CardContentComponent sx={{ textAlign: 'center', py: 4 }}>
<TypographyComponent sx={{ color: terminalTheme ? terminalColors.text : 'rgba(255,255,255,0.8)' }}>
Loading billing data...
</TypographyComponent>
</CardContentComponent>
</CardComponent>
);
}
if (error) {
const errorCardStyles = terminalTheme
? {
backgroundColor: terminalColors.background,
border: `1px solid ${terminalColors.error}`,
borderRadius: 3
}
: {
background: 'linear-gradient(135deg, rgba(255,255,255,0.1) 0%, rgba(255,255,255,0.05) 100%)',
backdropFilter: 'blur(10px)',
border: '1px solid rgba(255,255,255,0.1)',
borderRadius: 3
};
return (
<Card sx={{
background: 'linear-gradient(135deg, rgba(255,255,255,0.1) 0%, rgba(255,255,255,0.05) 100%)',
backdropFilter: 'blur(10px)',
border: '1px solid rgba(255,255,255,0.1)',
borderRadius: 3
}}>
<CardContent sx={{ textAlign: 'center', py: 4 }}>
<Typography sx={{ color: '#ff6b6b' }}>Error: {error}</Typography>
<IconButton onClick={fetchData} sx={{ mt: 1 }}>
<CardComponent sx={errorCardStyles}>
<CardContentComponent sx={{ textAlign: 'center', py: 4 }}>
<TypographyComponent sx={{ color: terminalTheme ? terminalColors.error : '#ff6b6b' }}>
Error: {error}
</TypographyComponent>
<IconButton onClick={() => fetchData(true)} sx={{ mt: 1, color: terminalTheme ? terminalColors.text : 'inherit' }}>
<RefreshCw size={16} />
</IconButton>
</CardContent>
</Card>
</CardContentComponent>
</CardComponent>
);
}
@@ -116,36 +167,55 @@ const CompactBillingDashboard: React.FC<CompactBillingDashboardProps> = ({ userI
const { current_usage, limits, alerts } = dashboardData;
const mainCardStyles = terminalTheme
? {
backgroundColor: terminalColors.background,
border: `1px solid ${terminalColors.border}`,
borderRadius: 4,
position: 'relative' as const,
overflow: 'hidden' as const,
boxShadow: '0 0 15px rgba(0, 255, 0, 0.2)',
'&::before': {
content: '""',
position: 'absolute',
top: 0,
left: 0,
right: 0,
height: '1px',
background: `linear-gradient(90deg, transparent, ${terminalColors.border}, transparent)`,
zIndex: 1
}
}
: {
background: 'linear-gradient(135deg, rgba(255,255,255,0.12) 0%, rgba(255,255,255,0.08) 100%)',
backdropFilter: 'blur(15px)',
border: '1px solid rgba(255,255,255,0.15)',
borderRadius: 4,
position: 'relative' as const,
overflow: 'hidden' as const,
boxShadow: '0 8px 32px rgba(0,0,0,0.2), 0 0 0 1px rgba(255,255,255,0.1)',
'&::before': {
content: '""',
position: 'absolute',
top: 0,
left: 0,
right: 0,
height: '1px',
background: 'linear-gradient(90deg, transparent, rgba(255,255,255,0.3), transparent)',
zIndex: 1
}
};
return (
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.4 }}
>
<Card
sx={{
background: 'linear-gradient(135deg, rgba(255,255,255,0.12) 0%, rgba(255,255,255,0.08) 100%)',
backdropFilter: 'blur(15px)',
border: '1px solid rgba(255,255,255,0.15)',
borderRadius: 4,
position: 'relative',
overflow: 'hidden',
boxShadow: '0 8px 32px rgba(0,0,0,0.2), 0 0 0 1px rgba(255,255,255,0.1)',
'&::before': {
content: '""',
position: 'absolute',
top: 0,
left: 0,
right: 0,
height: '1px',
background: 'linear-gradient(90deg, transparent, rgba(255,255,255,0.3), transparent)',
zIndex: 1
}
}}
>
<CardComponent sx={mainCardStyles}>
{/* Header - Removed to save space */}
<CardContent sx={{ pt: 2 }}>
<CardContentComponent sx={{ pt: 2 }}>
{/* Compact Overview */}
<Grid container spacing={2} sx={{ mb: 2 }}>
{/* Total Cost */}
@@ -170,46 +240,71 @@ const CompactBillingDashboard: React.FC<CompactBillingDashboardProps> = ({ userI
<Box sx={{
textAlign: 'center',
p: 2.5,
background: 'linear-gradient(135deg, rgba(74, 222, 128, 0.12) 0%, rgba(34, 197, 94, 0.08) 100%)',
borderRadius: 3,
border: '1px solid rgba(74, 222, 128, 0.25)',
position: 'relative',
overflow: 'hidden',
cursor: 'help',
transition: 'all 0.3s ease',
'&:hover': {
transform: 'translateY(-2px)',
boxShadow: '0 8px 25px rgba(74, 222, 128, 0.2)',
border: '1px solid rgba(74, 222, 128, 0.4)'
},
'&::before': {
content: '""',
position: 'absolute',
top: 0,
left: 0,
right: 0,
height: '3px',
background: 'linear-gradient(90deg, #4ade80, #22c55e)',
zIndex: 1
}
...(terminalTheme ? {
backgroundColor: terminalColors.backgroundLight,
borderRadius: 3,
border: `1px solid ${terminalColors.border}`,
position: 'relative',
overflow: 'hidden',
cursor: 'help',
transition: 'all 0.3s ease',
'&:hover': {
transform: 'translateY(-2px)',
boxShadow: `0 0 15px ${terminalColors.border}40`,
borderColor: terminalColors.secondary
},
'&::before': {
content: '""',
position: 'absolute',
top: 0,
left: 0,
right: 0,
height: '3px',
background: terminalColors.border,
zIndex: 1
}
} : {
background: 'linear-gradient(135deg, rgba(74, 222, 128, 0.12) 0%, rgba(34, 197, 94, 0.08) 100%)',
borderRadius: 3,
border: '1px solid rgba(74, 222, 128, 0.25)',
position: 'relative',
overflow: 'hidden',
cursor: 'help',
transition: 'all 0.3s ease',
'&:hover': {
transform: 'translateY(-2px)',
boxShadow: '0 8px 25px rgba(74, 222, 128, 0.2)',
border: '1px solid rgba(74, 222, 128, 0.4)'
},
'&::before': {
content: '""',
position: 'absolute',
top: 0,
left: 0,
right: 0,
height: '3px',
background: 'linear-gradient(90deg, #4ade80, #22c55e)',
zIndex: 1
}
})
}}>
<Typography variant="h5" sx={{
<TypographyComponent variant="h5" sx={{
fontWeight: 800,
color: '#ffffff',
textShadow: '0 2px 8px rgba(0,0,0,0.4)',
color: terminalTheme ? terminalColors.text : '#ffffff',
textShadow: terminalTheme ? 'none' : '0 2px 8px rgba(0,0,0,0.4)',
mb: 0.5
}}>
{formatCurrency(current_usage.total_cost)}
</Typography>
<Typography variant="body2" sx={{
color: 'rgba(255,255,255,0.9)',
</TypographyComponent>
<TypographyComponent variant="body2" sx={{
color: terminalTheme ? terminalColors.textSecondary : 'rgba(255,255,255,0.9)',
fontWeight: 500,
textTransform: 'uppercase',
letterSpacing: '0.5px',
fontSize: '0.75rem'
}}>
Total Cost
</Typography>
</TypographyComponent>
</Box>
</Tooltip>
</Grid>
@@ -236,46 +331,71 @@ const CompactBillingDashboard: React.FC<CompactBillingDashboardProps> = ({ userI
<Box sx={{
textAlign: 'center',
p: 2.5,
background: 'linear-gradient(135deg, rgba(59, 130, 246, 0.12) 0%, rgba(37, 99, 235, 0.08) 100%)',
borderRadius: 3,
border: '1px solid rgba(59, 130, 246, 0.25)',
position: 'relative',
overflow: 'hidden',
cursor: 'help',
transition: 'all 0.3s ease',
'&:hover': {
transform: 'translateY(-2px)',
boxShadow: '0 8px 25px rgba(59, 130, 246, 0.2)',
border: '1px solid rgba(59, 130, 246, 0.4)'
},
'&::before': {
content: '""',
position: 'absolute',
top: 0,
left: 0,
right: 0,
height: '3px',
background: 'linear-gradient(90deg, #3b82f6, #2563eb)',
zIndex: 1
}
...(terminalTheme ? {
backgroundColor: terminalColors.backgroundLight,
borderRadius: 3,
border: `1px solid ${terminalColors.border}`,
position: 'relative',
overflow: 'hidden',
cursor: 'help',
transition: 'all 0.3s ease',
'&:hover': {
transform: 'translateY(-2px)',
boxShadow: `0 0 15px ${terminalColors.border}40`,
borderColor: terminalColors.secondary
},
'&::before': {
content: '""',
position: 'absolute',
top: 0,
left: 0,
right: 0,
height: '3px',
background: terminalColors.border,
zIndex: 1
}
} : {
background: 'linear-gradient(135deg, rgba(59, 130, 246, 0.12) 0%, rgba(37, 99, 235, 0.08) 100%)',
borderRadius: 3,
border: '1px solid rgba(59, 130, 246, 0.25)',
position: 'relative',
overflow: 'hidden',
cursor: 'help',
transition: 'all 0.3s ease',
'&:hover': {
transform: 'translateY(-2px)',
boxShadow: '0 8px 25px rgba(59, 130, 246, 0.2)',
border: '1px solid rgba(59, 130, 246, 0.4)'
},
'&::before': {
content: '""',
position: 'absolute',
top: 0,
left: 0,
right: 0,
height: '3px',
background: 'linear-gradient(90deg, #3b82f6, #2563eb)',
zIndex: 1
}
})
}}>
<Typography variant="h5" sx={{
<TypographyComponent variant="h5" sx={{
fontWeight: 800,
color: '#ffffff',
textShadow: '0 2px 8px rgba(0,0,0,0.4)',
color: terminalTheme ? terminalColors.text : '#ffffff',
textShadow: terminalTheme ? 'none' : '0 2px 8px rgba(0,0,0,0.4)',
mb: 0.5
}}>
{formatNumber(current_usage.total_calls)}
</Typography>
<Typography variant="body2" sx={{
color: 'rgba(255,255,255,0.9)',
</TypographyComponent>
<TypographyComponent variant="body2" sx={{
color: terminalTheme ? terminalColors.textSecondary : 'rgba(255,255,255,0.9)',
fontWeight: 500,
textTransform: 'uppercase',
letterSpacing: '0.5px',
fontSize: '0.75rem'
}}>
API Calls
</Typography>
</TypographyComponent>
</Box>
</Tooltip>
</Grid>
@@ -302,46 +422,71 @@ const CompactBillingDashboard: React.FC<CompactBillingDashboardProps> = ({ userI
<Box sx={{
textAlign: 'center',
p: 2.5,
background: 'linear-gradient(135deg, rgba(168, 85, 247, 0.12) 0%, rgba(147, 51, 234, 0.08) 100%)',
borderRadius: 3,
border: '1px solid rgba(168, 85, 247, 0.25)',
position: 'relative',
overflow: 'hidden',
cursor: 'help',
transition: 'all 0.3s ease',
'&:hover': {
transform: 'translateY(-2px)',
boxShadow: '0 8px 25px rgba(168, 85, 247, 0.2)',
border: '1px solid rgba(168, 85, 247, 0.4)'
},
'&::before': {
content: '""',
position: 'absolute',
top: 0,
left: 0,
right: 0,
height: '3px',
background: 'linear-gradient(90deg, #a855f7, #9333ea)',
zIndex: 1
}
...(terminalTheme ? {
backgroundColor: terminalColors.backgroundLight,
borderRadius: 3,
border: `1px solid ${terminalColors.border}`,
position: 'relative',
overflow: 'hidden',
cursor: 'help',
transition: 'all 0.3s ease',
'&:hover': {
transform: 'translateY(-2px)',
boxShadow: `0 0 15px ${terminalColors.border}40`,
borderColor: terminalColors.secondary
},
'&::before': {
content: '""',
position: 'absolute',
top: 0,
left: 0,
right: 0,
height: '3px',
background: terminalColors.border,
zIndex: 1
}
} : {
background: 'linear-gradient(135deg, rgba(168, 85, 247, 0.12) 0%, rgba(147, 51, 234, 0.08) 100%)',
borderRadius: 3,
border: '1px solid rgba(168, 85, 247, 0.25)',
position: 'relative',
overflow: 'hidden',
cursor: 'help',
transition: 'all 0.3s ease',
'&:hover': {
transform: 'translateY(-2px)',
boxShadow: '0 8px 25px rgba(168, 85, 247, 0.2)',
border: '1px solid rgba(168, 85, 247, 0.4)'
},
'&::before': {
content: '""',
position: 'absolute',
top: 0,
left: 0,
right: 0,
height: '3px',
background: 'linear-gradient(90deg, #a855f7, #9333ea)',
zIndex: 1
}
})
}}>
<Typography variant="h5" sx={{
<TypographyComponent variant="h5" sx={{
fontWeight: 800,
color: '#ffffff',
textShadow: '0 2px 8px rgba(0,0,0,0.4)',
color: terminalTheme ? terminalColors.text : '#ffffff',
textShadow: terminalTheme ? 'none' : '0 2px 8px rgba(0,0,0,0.4)',
mb: 0.5
}}>
{(current_usage.total_tokens / 1000).toFixed(1)}k
</Typography>
<Typography variant="body2" sx={{
color: 'rgba(255,255,255,0.9)',
</TypographyComponent>
<TypographyComponent variant="body2" sx={{
color: terminalTheme ? terminalColors.textSecondary : 'rgba(255,255,255,0.9)',
fontWeight: 500,
textTransform: 'uppercase',
letterSpacing: '0.5px',
fontSize: '0.75rem'
}}>
Tokens
</Typography>
</TypographyComponent>
</Box>
</Tooltip>
</Grid>
@@ -371,59 +516,89 @@ const CompactBillingDashboard: React.FC<CompactBillingDashboardProps> = ({ userI
<Box sx={{
textAlign: 'center',
p: 2.5,
background: systemHealth?.status === 'healthy'
? 'linear-gradient(135deg, rgba(34, 197, 94, 0.12) 0%, rgba(22, 163, 74, 0.08) 100%)'
: 'linear-gradient(135deg, rgba(239, 68, 68, 0.12) 0%, rgba(220, 38, 38, 0.08) 100%)',
borderRadius: 3,
border: systemHealth?.status === 'healthy'
? '1px solid rgba(34, 197, 94, 0.25)'
: '1px solid rgba(239, 68, 68, 0.25)',
position: 'relative',
overflow: 'hidden',
cursor: 'help',
transition: 'all 0.3s ease',
'&:hover': {
transform: 'translateY(-2px)',
boxShadow: systemHealth?.status === 'healthy'
? '0 8px 25px rgba(34, 197, 94, 0.2)'
: '0 8px 25px rgba(239, 68, 68, 0.2)',
border: systemHealth?.status === 'healthy'
? '1px solid rgba(34, 197, 94, 0.4)'
: '1px solid rgba(239, 68, 68, 0.4)'
},
'&::before': {
content: '""',
position: 'absolute',
top: 0,
left: 0,
right: 0,
height: '3px',
...(terminalTheme ? {
backgroundColor: terminalColors.backgroundLight,
borderRadius: 3,
border: `1px solid ${systemHealth?.status === 'healthy' ? terminalColors.success : terminalColors.error}`,
position: 'relative',
overflow: 'hidden',
cursor: 'help',
transition: 'all 0.3s ease',
'&:hover': {
transform: 'translateY(-2px)',
boxShadow: `0 0 15px ${systemHealth?.status === 'healthy' ? terminalColors.success : terminalColors.error}40`,
borderColor: systemHealth?.status === 'healthy' ? terminalColors.secondary : terminalColors.error
},
'&::before': {
content: '""',
position: 'absolute',
top: 0,
left: 0,
right: 0,
height: '3px',
background: systemHealth?.status === 'healthy' ? terminalColors.success : terminalColors.error,
zIndex: 1
}
} : {
background: systemHealth?.status === 'healthy'
? 'linear-gradient(90deg, #22c55e, #16a34a)'
: 'linear-gradient(90deg, #ef4444, #dc2626)',
zIndex: 1
}
? 'linear-gradient(135deg, rgba(34, 197, 94, 0.12) 0%, rgba(22, 163, 74, 0.08) 100%)'
: 'linear-gradient(135deg, rgba(239, 68, 68, 0.12) 0%, rgba(220, 38, 38, 0.08) 100%)',
borderRadius: 3,
border: systemHealth?.status === 'healthy'
? '1px solid rgba(34, 197, 94, 0.25)'
: '1px solid rgba(239, 68, 68, 0.25)',
position: 'relative',
overflow: 'hidden',
cursor: 'help',
transition: 'all 0.3s ease',
'&:hover': {
transform: 'translateY(-2px)',
boxShadow: systemHealth?.status === 'healthy'
? '0 8px 25px rgba(34, 197, 94, 0.2)'
: '0 8px 25px rgba(239, 68, 68, 0.2)',
border: systemHealth?.status === 'healthy'
? '1px solid rgba(34, 197, 94, 0.4)'
: '1px solid rgba(239, 68, 68, 0.4)'
},
'&::before': {
content: '""',
position: 'absolute',
top: 0,
left: 0,
right: 0,
height: '3px',
background: systemHealth?.status === 'healthy'
? 'linear-gradient(90deg, #22c55e, #16a34a)'
: 'linear-gradient(90deg, #ef4444, #dc2626)',
zIndex: 1
}
})
}}>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 0.5, mb: 0.5 }}>
<CheckCircle size={18} color={systemHealth?.status === 'healthy' ? '#4ade80' : '#ff6b6b'} />
<Typography variant="body1" sx={{
color: systemHealth?.status === 'healthy' ? '#4ade80' : '#ff6b6b',
<CheckCircle size={18} color={terminalTheme
? (systemHealth?.status === 'healthy' ? terminalColors.success : terminalColors.error)
: (systemHealth?.status === 'healthy' ? '#4ade80' : '#ff6b6b')
} />
<TypographyComponent variant="body1" sx={{
color: terminalTheme
? (systemHealth?.status === 'healthy' ? terminalColors.success : terminalColors.error)
: (systemHealth?.status === 'healthy' ? '#4ade80' : '#ff6b6b'),
fontWeight: 700,
textTransform: 'capitalize',
textShadow: '0 2px 4px rgba(0,0,0,0.3)'
textShadow: terminalTheme ? 'none' : '0 2px 4px rgba(0,0,0,0.3)'
}}>
{systemHealth?.status || 'Unknown'}
</Typography>
</TypographyComponent>
</Box>
<Typography variant="body2" sx={{
color: 'rgba(255,255,255,0.9)',
<TypographyComponent variant="body2" sx={{
color: terminalTheme ? terminalColors.textSecondary : 'rgba(255,255,255,0.9)',
fontWeight: 500,
textTransform: 'uppercase',
letterSpacing: '0.5px',
fontSize: '0.75rem'
}}>
System Health
</Typography>
</TypographyComponent>
</Box>
</Tooltip>
</Grid>
@@ -435,40 +610,46 @@ const CompactBillingDashboard: React.FC<CompactBillingDashboardProps> = ({ userI
<Box sx={{
mb: 3,
p: 2.5,
background: 'linear-gradient(135deg, rgba(255,255,255,0.08) 0%, rgba(255,255,255,0.04) 100%)',
borderRadius: 3,
border: '1px solid rgba(255,255,255,0.1)'
...(terminalTheme ? {
backgroundColor: terminalColors.backgroundLight,
borderRadius: 3,
border: `1px solid ${terminalColors.border}`
} : {
background: 'linear-gradient(135deg, rgba(255,255,255,0.08) 0%, rgba(255,255,255,0.04) 100%)',
borderRadius: 3,
border: '1px solid rgba(255,255,255,0.1)'
})
}}>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 2 }}>
<Box>
<Typography variant="subtitle2" sx={{
color: '#ffffff',
<TypographyComponent variant="subtitle2" sx={{
color: terminalTheme ? terminalColors.text : '#ffffff',
fontWeight: 600,
mb: 0.5
}}>
Monthly Budget Usage
</Typography>
<Typography variant="caption" sx={{
color: 'rgba(255,255,255,0.7)',
</TypographyComponent>
<TypographyComponent variant="caption" sx={{
color: terminalTheme ? terminalColors.textSecondary : 'rgba(255,255,255,0.7)',
display: 'block'
}}>
Track your AI spending against monthly limits
</Typography>
</TypographyComponent>
</Box>
<Box sx={{ textAlign: 'right' }}>
<Typography variant="h6" sx={{
color: '#ffffff',
<TypographyComponent variant="h6" sx={{
color: terminalTheme ? terminalColors.text : '#ffffff',
fontWeight: 'bold',
textShadow: '0 2px 4px rgba(0,0,0,0.3)'
textShadow: terminalTheme ? 'none' : '0 2px 4px rgba(0,0,0,0.3)'
}}>
{formatCurrency(current_usage.total_cost)}
</Typography>
<Typography variant="caption" sx={{
color: 'rgba(255,255,255,0.7)',
</TypographyComponent>
<TypographyComponent variant="caption" sx={{
color: terminalTheme ? terminalColors.textSecondary : 'rgba(255,255,255,0.7)',
display: 'block'
}}>
of {formatCurrency(limits.limits.monthly_cost)}
</Typography>
</TypographyComponent>
</Box>
</Box>
<LinearProgress
@@ -477,21 +658,29 @@ const CompactBillingDashboard: React.FC<CompactBillingDashboardProps> = ({ userI
sx={{
height: 8,
borderRadius: 4,
backgroundColor: 'rgba(255,255,255,0.1)',
backgroundColor: terminalTheme ? terminalColors.backgroundLight : 'rgba(255,255,255,0.1)',
'& .MuiLinearProgress-bar': {
background: current_usage.total_cost / limits.limits.monthly_cost > 0.8
? 'linear-gradient(90deg, #ff6b6b, #ff5252)'
: current_usage.total_cost / limits.limits.monthly_cost > 0.6
? 'linear-gradient(90deg, #ffa726, #ff9800)'
: 'linear-gradient(90deg, #4ade80, #22c55e)',
background: terminalTheme
? (current_usage.total_cost / limits.limits.monthly_cost > 0.8
? terminalColors.error
: current_usage.total_cost / limits.limits.monthly_cost > 0.6
? terminalColors.warning
: terminalColors.success)
: (current_usage.total_cost / limits.limits.monthly_cost > 0.8
? 'linear-gradient(90deg, #ff6b6b, #ff5252)'
: current_usage.total_cost / limits.limits.monthly_cost > 0.6
? 'linear-gradient(90deg, #ffa726, #ff9800)'
: 'linear-gradient(90deg, #4ade80, #22c55e)'),
borderRadius: 4,
boxShadow: '0 2px 8px rgba(0,0,0,0.2)'
boxShadow: terminalTheme ? 'none' : '0 2px 8px rgba(0,0,0,0.2)'
}
}}
/>
<Box sx={{ display: 'flex', justifyContent: 'space-between', mt: 1 }}>
<Typography variant="caption" sx={{
color: current_usage.total_cost / limits.limits.monthly_cost > 0.8 ? '#ff6b6b' : 'rgba(255,255,255,0.7)',
<TypographyComponent variant="caption" sx={{
color: terminalTheme
? (current_usage.total_cost / limits.limits.monthly_cost > 0.8 ? terminalColors.error : terminalColors.textSecondary)
: (current_usage.total_cost / limits.limits.monthly_cost > 0.8 ? '#ff6b6b' : 'rgba(255,255,255,0.7)'),
fontWeight: current_usage.total_cost / limits.limits.monthly_cost > 0.8 ? 600 : 400
}}>
{current_usage.total_cost / limits.limits.monthly_cost > 0.8
@@ -500,13 +689,13 @@ const CompactBillingDashboard: React.FC<CompactBillingDashboardProps> = ({ userI
? '⚡ Moderate usage'
: '✅ Within budget'
}
</Typography>
<Typography variant="caption" sx={{
color: 'rgba(255,255,255,0.7)',
</TypographyComponent>
<TypographyComponent variant="caption" sx={{
color: terminalTheme ? terminalColors.textSecondary : 'rgba(255,255,255,0.7)',
fontWeight: 500
}}>
{((current_usage.total_cost / limits.limits.monthly_cost) * 100).toFixed(1)}% used
</Typography>
</TypographyComponent>
</Box>
</Box>
)}
@@ -516,38 +705,55 @@ const CompactBillingDashboard: React.FC<CompactBillingDashboardProps> = ({ userI
<Box sx={{
mb: 3,
p: 2.5,
background: 'linear-gradient(135deg, rgba(255, 107, 107, 0.1) 0%, rgba(239, 68, 68, 0.05) 100%)',
borderRadius: 3,
border: '1px solid rgba(255, 107, 107, 0.2)',
position: 'relative',
'&::before': {
content: '""',
position: 'absolute',
top: 0,
left: 0,
right: 0,
height: '3px',
background: 'linear-gradient(90deg, #ff6b6b, #ef4444)',
borderRadius: '3px 3px 0 0'
}
...(terminalTheme ? {
backgroundColor: terminalColors.backgroundLight,
borderRadius: 3,
border: `1px solid ${terminalColors.error}`,
position: 'relative',
'&::before': {
content: '""',
position: 'absolute',
top: 0,
left: 0,
right: 0,
height: '3px',
background: terminalColors.error,
borderRadius: '3px 3px 0 0'
}
} : {
background: 'linear-gradient(135deg, rgba(255, 107, 107, 0.1) 0%, rgba(239, 68, 68, 0.05) 100%)',
borderRadius: 3,
border: '1px solid rgba(255, 107, 107, 0.2)',
position: 'relative',
'&::before': {
content: '""',
position: 'absolute',
top: 0,
left: 0,
right: 0,
height: '3px',
background: 'linear-gradient(90deg, #ff6b6b, #ef4444)',
borderRadius: '3px 3px 0 0'
}
})
}}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 2 }}>
<AlertTriangle size={18} color="#ff6b6b" />
<Typography variant="subtitle2" sx={{
<AlertTriangle size={18} color={terminalTheme ? terminalColors.error : "#ff6b6b"} />
<TypographyComponent variant="subtitle2" sx={{
fontWeight: 700,
color: '#ff6b6b',
textShadow: '0 1px 2px rgba(0,0,0,0.3)'
color: terminalTheme ? terminalColors.error : '#ff6b6b',
textShadow: terminalTheme ? 'none' : '0 1px 2px rgba(0,0,0,0.3)'
}}>
System Alerts ({alerts.length})
</Typography>
</TypographyComponent>
</Box>
<Typography variant="caption" sx={{
color: 'rgba(255,255,255,0.8)',
<TypographyComponent variant="caption" sx={{
color: terminalTheme ? terminalColors.textSecondary : 'rgba(255,255,255,0.8)',
display: 'block',
mb: 2
}}>
Important notifications requiring your attention
</Typography>
</TypographyComponent>
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 1 }}>
{alerts.slice(0, 3).map((alert) => (
<Tooltip
@@ -565,42 +771,65 @@ const CompactBillingDashboard: React.FC<CompactBillingDashboardProps> = ({ userI
arrow
placement="top"
>
<Chip
label={alert.title}
size="small"
icon={<AlertTriangle size={14} />}
sx={{
backgroundColor: 'rgba(255, 107, 107, 0.2)',
color: '#ff6b6b',
border: '1px solid rgba(255, 107, 107, 0.3)',
fontWeight: 500,
'&:hover': {
backgroundColor: 'rgba(255, 107, 107, 0.3)',
transform: 'translateY(-1px)'
},
transition: 'all 0.2s ease'
}}
/>
{terminalTheme ? (
<TerminalChipError
label={alert.title}
size="small"
icon={<AlertTriangle size={14} />}
sx={{
fontWeight: 500,
'&:hover': {
transform: 'translateY(-1px)'
},
transition: 'all 0.2s ease'
}}
/>
) : (
<Chip
label={alert.title}
size="small"
icon={<AlertTriangle size={14} />}
sx={{
backgroundColor: 'rgba(255, 107, 107, 0.2)',
color: '#ff6b6b',
border: '1px solid rgba(255, 107, 107, 0.3)',
fontWeight: 500,
'&:hover': {
backgroundColor: 'rgba(255, 107, 107, 0.3)',
transform: 'translateY(-1px)'
},
transition: 'all 0.2s ease'
}}
/>
)}
</Tooltip>
))}
{alerts.length > 3 && (
<Chip
label={`+${alerts.length - 3} more`}
size="small"
sx={{
backgroundColor: 'rgba(255,255,255,0.1)',
color: 'rgba(255,255,255,0.8)',
border: '1px solid rgba(255,255,255,0.2)',
fontWeight: 500
}}
/>
terminalTheme ? (
<TerminalChip
label={`+${alerts.length - 3} more`}
size="small"
sx={{ fontWeight: 500 }}
/>
) : (
<Chip
label={`+${alerts.length - 3} more`}
size="small"
sx={{
backgroundColor: 'rgba(255,255,255,0.1)',
color: 'rgba(255,255,255,0.8)',
border: '1px solid rgba(255,255,255,0.2)',
fontWeight: 500
}}
/>
)
)}
</Box>
</Box>
)}
</CardContent>
</Card>
</CardContentComponent>
</CardComponent>
</motion.div>
);
};

View File

@@ -24,6 +24,7 @@ import {
import { billingService } from '../../services/billingService';
import { monitoringService } from '../../services/monitoringService';
import { onApiEvent } from '../../utils/apiEvents';
import { showToastNotification } from '../../utils/toastNotifications';
// Types
import { DashboardData } from '../../types/billing';
@@ -38,20 +39,31 @@ import UsageTrends from './UsageTrends';
import UsageAlerts from './UsageAlerts';
import ComprehensiveAPIBreakdown from './ComprehensiveAPIBreakdown';
// Terminal Theme
import {
TerminalTypography,
TerminalAlert,
terminalColors
} from '../SchedulerDashboard/terminalTheme';
interface EnhancedBillingDashboardProps {
userId?: string;
terminalTheme?: boolean;
}
type ViewMode = 'compact' | 'detailed';
const EnhancedBillingDashboard: React.FC<EnhancedBillingDashboardProps> = ({ userId }) => {
const EnhancedBillingDashboard: React.FC<EnhancedBillingDashboardProps> = ({ userId, terminalTheme = false }) => {
// Conditional component selection based on terminal theme
const TypographyComponent = terminalTheme ? TerminalTypography : Typography;
const AlertComponent = terminalTheme ? TerminalAlert : Alert;
const [dashboardData, setDashboardData] = useState<DashboardData | null>(null);
const [systemHealth, setSystemHealth] = useState<SystemHealth | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [viewMode, setViewMode] = useState<ViewMode>('compact');
const fetchDashboardData = async () => {
const fetchDashboardData = async (showSuccessToast: boolean = false) => {
try {
const [billingData, healthData] = await Promise.all([
billingService.getDashboardData(),
@@ -59,8 +71,21 @@ const EnhancedBillingDashboard: React.FC<EnhancedBillingDashboardProps> = ({ use
]);
setDashboardData(billingData);
setSystemHealth(healthData);
// Show success toast only if explicitly requested (user-initiated refresh)
if (showSuccessToast && billingData && healthData) {
showToastNotification(
'Billing data refreshed successfully',
'success',
{ duration: 3000 }
);
}
} catch (error) {
setError(error instanceof Error ? error.message : 'Failed to fetch dashboard data');
const errorMessage = error instanceof Error ? error.message : 'Failed to fetch dashboard data';
setError(errorMessage);
// Always show error toast for failures
showToastNotification(errorMessage, 'error', { duration: 5000 });
} finally {
setLoading(false);
}
@@ -95,6 +120,29 @@ const EnhancedBillingDashboard: React.FC<EnhancedBillingDashboardProps> = ({ use
return () => document.removeEventListener('visibilitychange', onVisible);
}, []);
// Listen for billing refresh requests (e.g., when subscription limits are exceeded)
useEffect(() => {
const handleBillingRefresh = () => {
console.log('EnhancedBillingDashboard: Billing refresh requested, refreshing data...');
// Use a fresh call to fetchDashboardData to ensure we get latest data
Promise.all([billingService.getDashboardData(), monitoringService.getSystemHealth()])
.then(([billingData, healthData]) => {
setDashboardData(billingData);
setSystemHealth(healthData);
})
.catch((error) => {
const errorMessage = error instanceof Error ? error.message : 'Failed to refresh billing data';
setError(errorMessage);
console.error('Error refreshing billing data:', error);
});
};
window.addEventListener('billing-refresh-requested', handleBillingRefresh);
return () => {
window.removeEventListener('billing-refresh-requested', handleBillingRefresh);
};
}, []); // Empty deps - handler doesn't depend on component state
const handleViewModeChange = (
event: React.MouseEvent<HTMLElement>,
newViewMode: ViewMode | null,
@@ -117,9 +165,9 @@ const EnhancedBillingDashboard: React.FC<EnhancedBillingDashboardProps> = ({ use
if (error) {
return (
<Container maxWidth="xl" sx={{ py: 4 }}>
<Alert severity="error" sx={{ mb: 3 }}>
<AlertComponent severity="error" sx={{ mb: 3 }}>
{error}
</Alert>
</AlertComponent>
</Container>
);
}
@@ -127,9 +175,9 @@ const EnhancedBillingDashboard: React.FC<EnhancedBillingDashboardProps> = ({ use
if (!dashboardData) {
return (
<Container maxWidth="xl" sx={{ py: 4 }}>
<Alert severity="warning">
<AlertComponent severity="warning">
No billing data available. Please check your subscription status.
</Alert>
</AlertComponent>
</Container>
);
}
@@ -146,17 +194,17 @@ const EnhancedBillingDashboard: React.FC<EnhancedBillingDashboardProps> = ({ use
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 2 }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2, flex: 1 }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<Typography
<TypographyComponent
variant="h4"
sx={{
fontWeight: 800,
mb: 1.5,
fontSize: '1.1rem',
color: 'rgba(255,255,255,0.95)',
color: terminalTheme ? terminalColors.text : 'rgba(255,255,255,0.95)',
}}
>
Billing & Usage Dashboard
</Typography>
</TypographyComponent>
<Tooltip
title={
<Box>
@@ -231,7 +279,7 @@ const EnhancedBillingDashboard: React.FC<EnhancedBillingDashboardProps> = ({ use
<Tooltip title="Refresh billing data">
<IconButton
size="small"
onClick={fetchDashboardData}
onClick={() => fetchDashboardData(true)}
disabled={loading}
sx={{
color: 'rgba(255,255,255,0.7)',
@@ -305,7 +353,7 @@ const EnhancedBillingDashboard: React.FC<EnhancedBillingDashboardProps> = ({ use
exit={{ opacity: 0, x: 20 }}
transition={{ duration: 0.3 }}
>
<CompactBillingDashboard userId={userId} />
<CompactBillingDashboard userId={userId} terminalTheme={terminalTheme} />
</motion.div>
) : (
<motion.div
@@ -321,6 +369,7 @@ const EnhancedBillingDashboard: React.FC<EnhancedBillingDashboardProps> = ({ use
<BillingOverview
usageStats={dashboardData.current_usage}
onRefresh={fetchDashboardData}
terminalTheme={terminalTheme}
/>
</Grid>

View File

@@ -0,0 +1,467 @@
/**
* Subscription Renewal History Component
* Displays historical subscription renewals with details about plan changes, renewals, and usage.
*/
import React, { useState, useEffect } from 'react';
import {
Box,
Table,
TableBody,
TableContainer,
TableHead,
TableRow,
TablePagination,
IconButton,
Tooltip,
CircularProgress,
Chip,
Typography,
} from '@mui/material';
import {
Refresh as RefreshIcon,
TrendingUp as TrendingUpIcon,
TrendingDown as TrendingDownIcon,
Add as AddIcon,
} from '@mui/icons-material';
import { RefreshCw } from 'lucide-react'; // Use lucide-react for header icon
import { billingService } from '../../services/billingService';
import { SubscriptionRenewal, RenewalHistoryResponse } from '../../types/billing';
import {
TerminalPaper,
TerminalTypography,
TerminalTableCell,
TerminalTableRow,
TerminalAlert,
TerminalChip,
TerminalChipSuccess,
TerminalChipWarning,
TerminalChipError,
terminalColors
} from '../SchedulerDashboard/terminalTheme';
import { showToastNotification } from '../../utils/toastNotifications';
interface SubscriptionRenewalHistoryProps {
userId?: string;
terminalTheme?: boolean;
initialLimit?: number;
}
const SubscriptionRenewalHistory: React.FC<SubscriptionRenewalHistoryProps> = ({
userId,
terminalTheme = false,
initialLimit = 20
}) => {
const [renewals, setRenewals] = useState<SubscriptionRenewal[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [page, setPage] = useState(0);
const [rowsPerPage, setRowsPerPage] = useState(initialLimit);
const [totalCount, setTotalCount] = useState(0);
const fetchRenewals = async () => {
try {
setLoading(true);
setError(null);
const response: RenewalHistoryResponse = await billingService.getRenewalHistory(
userId,
rowsPerPage,
page * rowsPerPage
);
setRenewals(response.renewals || []);
setTotalCount(response.total_count || 0);
// Don't show success toast on automatic refresh - only show errors
} catch (err: any) {
const errorMessage = err.message || 'Failed to fetch renewal history';
setError(errorMessage);
console.error('Error fetching renewal history:', err);
// Show error toast
showToastNotification(errorMessage, 'error');
} finally {
setLoading(false);
}
};
useEffect(() => {
fetchRenewals();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [page, rowsPerPage, userId]);
const handleChangePage = (_event: unknown, newPage: number) => {
setPage(newPage);
};
const handleChangeRowsPerPage = (event: React.ChangeEvent<HTMLInputElement>) => {
setRowsPerPage(parseInt(event.target.value, 10));
setPage(0);
};
const formatDate = (dateString: string | null) => {
if (!dateString) return 'N/A';
try {
return new Date(dateString).toLocaleDateString('en-US', {
year: 'numeric',
month: 'short',
day: 'numeric',
hour: '2-digit',
minute: '2-digit'
});
} catch {
return 'Invalid Date';
}
};
const formatCurrency = (amount: number) => {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
minimumFractionDigits: 2,
maximumFractionDigits: 2,
}).format(amount);
};
const getRenewalTypeIcon = (type: string): React.ReactElement | undefined => {
switch (type) {
case 'upgrade':
return <TrendingUpIcon fontSize="small" sx={{ color: terminalTheme ? terminalColors.success : '#4ade80' }} />;
case 'downgrade':
return <TrendingDownIcon fontSize="small" sx={{ color: terminalTheme ? terminalColors.warning : '#f59e0b' }} />;
case 'renewal':
return <RefreshIcon fontSize="small" sx={{ color: terminalTheme ? terminalColors.primary : '#3b82f6' }} />;
case 'new':
return <AddIcon fontSize="small" sx={{ color: terminalTheme ? terminalColors.primary : '#3b82f6' }} />;
default:
return undefined;
}
};
const getRenewalTypeChip = (type: string) => {
const ChipComponent = terminalTheme ? TerminalChip : Chip;
const chipStyles = {
upgrade: { backgroundColor: 'rgba(34, 197, 94, 0.2)', color: '#22c55e' },
downgrade: { backgroundColor: 'rgba(245, 158, 11, 0.2)', color: '#f59e0b' },
renewal: { backgroundColor: 'rgba(59, 130, 246, 0.2)', color: '#3b82f6' },
new: { backgroundColor: 'rgba(59, 130, 246, 0.2)', color: '#3b82f6' },
};
const chipStyle = chipStyles[type as keyof typeof chipStyles] || chipStyles.renewal;
const label = type.charAt(0).toUpperCase() + type.slice(1);
const icon = getRenewalTypeIcon(type);
if (terminalTheme) {
const TerminalChipComponent = type === 'upgrade' ? TerminalChipSuccess :
type === 'downgrade' ? TerminalChipWarning :
TerminalChip;
return (
<TerminalChipComponent
label={label}
size="small"
icon={icon}
sx={{ fontWeight: 500 }}
/>
);
}
return (
<Chip
label={label}
size="small"
icon={icon}
sx={{
...chipStyle,
fontWeight: 500,
border: `1px solid ${chipStyle.color}40`
}}
/>
);
};
// Conditional component selection based on terminal theme
const PaperComponent = terminalTheme ? TerminalPaper : Box;
// Alert component wrapper for terminal theme
const AlertWrapper: React.FC<{ children: React.ReactNode; severity?: 'error' | 'info' | 'warning'; sx?: any }> = ({ children, severity = 'info', sx }) => {
if (terminalTheme) {
return (
<TerminalAlert severity={severity} sx={sx}>
{children}
</TerminalAlert>
);
}
return (
<Box sx={{
p: 2,
backgroundColor: severity === 'error' ? 'rgba(239, 68, 68, 0.1)' : 'rgba(59, 130, 246, 0.1)',
borderRadius: 2,
color: severity === 'error' ? '#ef4444' : '#3b82f6',
...sx
}}>
{children}
</Box>
);
};
return (
<PaperComponent sx={{ p: 3, mt: 4 }}>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 3 }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
{terminalTheme ? (
<RefreshCw size={20} color={terminalColors.primary} />
) : (
<RefreshCw size={20} />
)}
{terminalTheme ? (
<TerminalTypography variant="h6" sx={{ fontWeight: 'bold' }}>
Subscription Renewal History
</TerminalTypography>
) : (
<Typography variant="h6" sx={{ fontWeight: 'bold' }}>
Subscription Renewal History
</Typography>
)}
</Box>
<Tooltip title="Refresh Renewal History">
<IconButton
onClick={fetchRenewals}
disabled={loading}
sx={{ color: terminalTheme ? terminalColors.primary : 'inherit' }}
>
<RefreshIcon />
</IconButton>
</Tooltip>
</Box>
{loading && renewals.length === 0 ? (
<Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center', minHeight: 200 }}>
<CircularProgress sx={{ color: terminalTheme ? terminalColors.primary : 'inherit' }} />
</Box>
) : error ? (
<AlertWrapper severity="error">
{terminalTheme ? (
<TerminalTypography>{error}</TerminalTypography>
) : (
<Typography>{error}</Typography>
)}
</AlertWrapper>
) : renewals.length === 0 ? (
<AlertWrapper severity="info">
{terminalTheme ? (
<TerminalTypography>No renewal history found. Your subscription renewals will appear here.</TerminalTypography>
) : (
<Typography>No renewal history found. Your subscription renewals will appear here.</Typography>
)}
</AlertWrapper>
) : (
<>
<TableContainer component={Box} sx={{ maxHeight: 600, overflow: 'auto' }}>
<Table stickyHeader size="small">
<TableHead>
<TableRow>
<TerminalTableCell sx={{ width: '8%' }}>#</TerminalTableCell>
<TerminalTableCell sx={{ width: '12%' }}>Type</TerminalTableCell>
<TerminalTableCell sx={{ width: '15%' }}>Plan</TerminalTableCell>
<TerminalTableCell sx={{ width: '12%' }}>Previous Plan</TerminalTableCell>
<TerminalTableCell sx={{ width: '10%' }}>Billing Cycle</TerminalTableCell>
<TerminalTableCell sx={{ width: '12%' }}>Period Start</TerminalTableCell>
<TerminalTableCell sx={{ width: '12%' }}>Period End</TerminalTableCell>
<TerminalTableCell sx={{ width: '10%' }}>Amount</TerminalTableCell>
<TerminalTableCell sx={{ width: '9%' }}>Status</TerminalTableCell>
</TableRow>
</TableHead>
<TableBody>
{renewals.map((renewal) => (
<TerminalTableRow key={renewal.id}>
<TerminalTableCell>
{terminalTheme ? (
<TerminalTypography variant="body2" sx={{ fontSize: '0.75rem' }}>
#{renewal.renewal_count}
</TerminalTypography>
) : (
<Typography variant="body2" sx={{ fontSize: '0.75rem' }}>
#{renewal.renewal_count}
</Typography>
)}
</TerminalTableCell>
<TerminalTableCell>
{getRenewalTypeChip(renewal.renewal_type)}
</TerminalTableCell>
<TerminalTableCell>
{terminalTheme ? (
<>
<TerminalTypography variant="body2" sx={{ fontSize: '0.75rem', fontWeight: 600 }}>
{renewal.plan_name}
</TerminalTypography>
<TerminalTypography variant="caption" sx={{ fontSize: '0.7rem', display: 'block', opacity: 0.7 }}>
{renewal.plan_tier}
</TerminalTypography>
</>
) : (
<>
<Typography variant="body2" sx={{ fontSize: '0.75rem', fontWeight: 600 }}>
{renewal.plan_name}
</Typography>
<Typography variant="caption" sx={{ fontSize: '0.7rem', display: 'block', opacity: 0.7 }}>
{renewal.plan_tier}
</Typography>
</>
)}
</TerminalTableCell>
<TerminalTableCell>
{renewal.previous_plan_name ? (
terminalTheme ? (
<>
<TerminalTypography variant="body2" sx={{ fontSize: '0.75rem' }}>
{renewal.previous_plan_name}
</TerminalTypography>
<TerminalTypography variant="caption" sx={{ fontSize: '0.7rem', display: 'block', opacity: 0.7 }}>
{renewal.previous_plan_tier}
</TerminalTypography>
</>
) : (
<>
<Typography variant="body2" sx={{ fontSize: '0.75rem' }}>
{renewal.previous_plan_name}
</Typography>
<Typography variant="caption" sx={{ fontSize: '0.7rem', display: 'block', opacity: 0.7 }}>
{renewal.previous_plan_tier}
</Typography>
</>
)
) : (
terminalTheme ? (
<TerminalTypography variant="body2" sx={{ fontSize: '0.75rem', fontStyle: 'italic', opacity: 0.5 }}>
N/A
</TerminalTypography>
) : (
<Typography variant="body2" sx={{ fontSize: '0.75rem', fontStyle: 'italic', opacity: 0.5 }}>
N/A
</Typography>
)
)}
</TerminalTableCell>
<TerminalTableCell>
{terminalTheme ? (
<TerminalTypography variant="body2" sx={{ fontSize: '0.75rem', textTransform: 'capitalize' }}>
{renewal.billing_cycle}
</TerminalTypography>
) : (
<Typography variant="body2" sx={{ fontSize: '0.75rem', textTransform: 'capitalize' }}>
{renewal.billing_cycle}
</Typography>
)}
</TerminalTableCell>
<TerminalTableCell>
{terminalTheme ? (
<TerminalTypography variant="body2" sx={{ fontSize: '0.75rem' }}>
{formatDate(renewal.new_period_start)}
</TerminalTypography>
) : (
<Typography variant="body2" sx={{ fontSize: '0.75rem' }}>
{formatDate(renewal.new_period_start)}
</Typography>
)}
</TerminalTableCell>
<TerminalTableCell>
{terminalTheme ? (
<TerminalTypography variant="body2" sx={{ fontSize: '0.75rem' }}>
{formatDate(renewal.new_period_end)}
</TerminalTypography>
) : (
<Typography variant="body2" sx={{ fontSize: '0.75rem' }}>
{formatDate(renewal.new_period_end)}
</Typography>
)}
</TerminalTableCell>
<TerminalTableCell>
{terminalTheme ? (
<TerminalTypography variant="body2" sx={{ fontSize: '0.75rem', fontWeight: 600 }}>
{formatCurrency(renewal.payment_amount)}
</TerminalTypography>
) : (
<Typography variant="body2" sx={{ fontSize: '0.75rem', fontWeight: 600 }}>
{formatCurrency(renewal.payment_amount)}
</Typography>
)}
</TerminalTableCell>
<TerminalTableCell>
{renewal.payment_status === 'paid' ? (
terminalTheme ? (
<TerminalChipSuccess label="Paid" size="small" />
) : (
<Chip
label="Paid"
size="small"
sx={{
backgroundColor: 'rgba(34, 197, 94, 0.2)',
color: '#22c55e',
fontWeight: 500
}}
/>
)
) : renewal.payment_status === 'pending' ? (
terminalTheme ? (
<TerminalChipWarning label="Pending" size="small" />
) : (
<Chip
label="Pending"
size="small"
sx={{
backgroundColor: 'rgba(245, 158, 11, 0.2)',
color: '#f59e0b',
fontWeight: 500
}}
/>
)
) : (
terminalTheme ? (
<TerminalChipError label="Failed" size="small" />
) : (
<Chip
label="Failed"
size="small"
sx={{
backgroundColor: 'rgba(239, 68, 68, 0.2)',
color: '#ef4444',
fontWeight: 500
}}
/>
)
)}
</TerminalTableCell>
</TerminalTableRow>
))}
</TableBody>
</Table>
</TableContainer>
<TablePagination
component="div"
count={totalCount}
page={page}
onPageChange={handleChangePage}
rowsPerPage={rowsPerPage}
onRowsPerPageChange={handleChangeRowsPerPage}
rowsPerPageOptions={[10, 20, 50, 100]}
sx={{
color: terminalTheme ? terminalColors.text : 'inherit',
'& .MuiTablePagination-selectLabel, & .MuiTablePagination-displayedRows': {
color: terminalTheme ? terminalColors.text : 'inherit',
fontFamily: terminalTheme ? 'monospace' : 'inherit'
},
'& .MuiIconButton-root': {
color: terminalTheme ? terminalColors.primary : 'inherit'
}
}}
/>
</>
)}
</PaperComponent>
);
};
export default SubscriptionRenewalHistory;

View File

@@ -0,0 +1,426 @@
/**
* Usage Logs Table Component
* Displays API usage logs in a table with pagination and filtering.
* Terminal-themed UI matching scheduler dashboard style.
*/
import React, { useState, useEffect } from 'react';
import {
Box,
Table,
TableBody,
TableContainer,
TableHead,
TableRow,
TablePagination,
IconButton,
Tooltip,
Select,
MenuItem,
FormControl,
InputLabel,
CircularProgress
} from '@mui/material';
import {
CheckCircle as CheckCircleIcon,
Error as ErrorIcon,
Refresh as RefreshIcon,
Receipt as ReceiptIcon
} from '@mui/icons-material';
import { billingService } from '../../services/billingService';
import { UsageLog, UsageLogsResponse } from '../../types/billing';
import {
TerminalPaper,
TerminalTypography,
TerminalChipSuccess,
TerminalChipError,
TerminalTableCell,
TerminalTableRow,
TerminalAlert,
terminalColors
} from '../SchedulerDashboard/terminalTheme';
import { formatCurrency } from '../../services/billingService';
interface UsageLogsTableProps {
initialLimit?: number;
}
const UsageLogsTable: React.FC<UsageLogsTableProps> = ({ initialLimit = 50 }) => {
const [logs, setLogs] = useState<UsageLog[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [page, setPage] = useState(0);
const [rowsPerPage, setRowsPerPage] = useState(initialLimit);
const [totalCount, setTotalCount] = useState(0);
const [statusFilter, setStatusFilter] = useState<'success' | 'failed' | 'all'>('all');
const [providerFilter, setProviderFilter] = useState<string>('all');
const fetchLogs = async () => {
try {
setLoading(true);
setError(null);
const statusCode = statusFilter === 'all' ? undefined : (statusFilter === 'success' ? 200 : 400);
const provider = providerFilter === 'all' ? undefined : providerFilter;
const response: UsageLogsResponse = await billingService.getUsageLogs(
rowsPerPage,
page * rowsPerPage,
provider,
statusCode
);
setLogs(response.logs || []);
setTotalCount(response.total_count || 0);
} catch (err: any) {
setError(err.message || 'Failed to fetch usage logs');
console.error('Error fetching usage logs:', err);
} finally {
setLoading(false);
}
};
useEffect(() => {
fetchLogs();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [page, rowsPerPage, statusFilter, providerFilter]);
const handleChangePage = (_event: unknown, newPage: number) => {
setPage(newPage);
};
const handleChangeRowsPerPage = (event: React.ChangeEvent<HTMLInputElement>) => {
setRowsPerPage(parseInt(event.target.value, 10));
setPage(0);
};
const getStatusIcon = (status: string): React.ReactElement | undefined => {
switch (status) {
case 'success':
return <CheckCircleIcon fontSize="small" sx={{ color: terminalColors.success }} />;
case 'failed':
return <ErrorIcon fontSize="small" sx={{ color: terminalColors.error }} />;
default:
return undefined;
}
};
const formatDate = (dateString: string) => {
try {
const date = new Date(dateString);
return date.toLocaleString();
} catch {
return dateString;
}
};
const formatResponseTime = (seconds: number) => {
if (!seconds) return 'N/A';
const ms = seconds * 1000;
if (ms < 1000) return `${ms.toFixed(0)}ms`;
return `${seconds.toFixed(2)}s`;
};
const formatTokens = (tokens: number) => {
return new Intl.NumberFormat('en-US').format(tokens);
};
return (
<TerminalPaper sx={{ p: 3 }}>
<Box display="flex" alignItems="center" justifyContent="space-between" mb={2}>
<Box display="flex" alignItems="center" gap={1}>
<ReceiptIcon sx={{ color: terminalColors.primary }} />
<TerminalTypography variant="h6" component="h2" sx={{ fontSize: '1.25rem', fontWeight: 'bold' }}>
API Usage Logs
</TerminalTypography>
</Box>
<Box display="flex" alignItems="center" gap={2}>
<FormControl
size="small"
sx={{
minWidth: 120,
'& .MuiOutlinedInput-root': {
color: terminalColors.primary,
'& fieldset': {
borderColor: terminalColors.primary,
},
'&:hover fieldset': {
borderColor: terminalColors.secondary,
},
},
'& .MuiInputLabel-root': {
color: terminalColors.textSecondary,
},
'& .MuiSelect-icon': {
color: terminalColors.primary,
}
}}
>
<InputLabel>Provider</InputLabel>
<Select
value={providerFilter}
label="Provider"
onChange={(e) => {
setProviderFilter(e.target.value);
setPage(0);
}}
MenuProps={{
PaperProps: {
sx: {
backgroundColor: terminalColors.backgroundLight,
border: `1px solid ${terminalColors.primary}`,
'& .MuiMenuItem-root': {
color: terminalColors.primary,
fontFamily: 'monospace',
'&:hover': {
backgroundColor: 'rgba(0, 255, 0, 0.1)',
},
'&.Mui-selected': {
backgroundColor: 'rgba(0, 255, 0, 0.15)',
}
}
}
}
}}
>
<MenuItem value="all">All</MenuItem>
<MenuItem value="gemini">Gemini</MenuItem>
<MenuItem value="huggingface">HuggingFace</MenuItem>
</Select>
</FormControl>
<FormControl
size="small"
sx={{
minWidth: 120,
'& .MuiOutlinedInput-root': {
color: terminalColors.primary,
'& fieldset': {
borderColor: terminalColors.primary,
},
'&:hover fieldset': {
borderColor: terminalColors.secondary,
},
},
'& .MuiInputLabel-root': {
color: terminalColors.textSecondary,
},
'& .MuiSelect-icon': {
color: terminalColors.primary,
}
}}
>
<InputLabel>Status</InputLabel>
<Select
value={statusFilter}
label="Status"
onChange={(e) => {
setStatusFilter(e.target.value as any);
setPage(0);
}}
MenuProps={{
PaperProps: {
sx: {
backgroundColor: terminalColors.backgroundLight,
border: `1px solid ${terminalColors.primary}`,
'& .MuiMenuItem-root': {
color: terminalColors.primary,
fontFamily: 'monospace',
'&:hover': {
backgroundColor: 'rgba(0, 255, 0, 0.1)',
},
'&.Mui-selected': {
backgroundColor: 'rgba(0, 255, 0, 0.15)',
}
}
}
}
}}
>
<MenuItem value="all">All</MenuItem>
<MenuItem value="success">Success</MenuItem>
<MenuItem value="failed">Failed</MenuItem>
</Select>
</FormControl>
<Tooltip title="Refresh logs">
<IconButton
onClick={fetchLogs}
size="small"
sx={{
color: terminalColors.primary,
border: `1px solid ${terminalColors.primary}`,
'&:hover': {
backgroundColor: 'rgba(0, 255, 0, 0.1)',
}
}}
>
<RefreshIcon />
</IconButton>
</Tooltip>
</Box>
</Box>
{error && (
<TerminalAlert severity="error" sx={{ mb: 2 }}>
{error}
</TerminalAlert>
)}
{loading ? (
<Box display="flex" justifyContent="center" p={3}>
<CircularProgress sx={{ color: terminalColors.primary }} />
</Box>
) : (
<>
<TableContainer
sx={{
backgroundColor: terminalColors.background,
maxHeight: '600px',
overflow: 'auto'
}}
>
<Table size="small" sx={{ minWidth: 800 }}>
<TableHead>
<TerminalTableRow>
<TerminalTableCell sx={{ fontWeight: 'bold', color: terminalColors.primary }}>Timestamp</TerminalTableCell>
<TerminalTableCell sx={{ fontWeight: 'bold', color: terminalColors.primary }}>Provider</TerminalTableCell>
<TerminalTableCell sx={{ fontWeight: 'bold', color: terminalColors.primary }}>Model</TerminalTableCell>
<TerminalTableCell sx={{ fontWeight: 'bold', color: terminalColors.primary }}>Tokens</TerminalTableCell>
<TerminalTableCell sx={{ fontWeight: 'bold', color: terminalColors.primary }}>Cost</TerminalTableCell>
<TerminalTableCell sx={{ fontWeight: 'bold', color: terminalColors.primary }}>Status</TerminalTableCell>
<TerminalTableCell sx={{ fontWeight: 'bold', color: terminalColors.primary }}>Response Time</TerminalTableCell>
<TerminalTableCell sx={{ fontWeight: 'bold', color: terminalColors.primary }}>Endpoint</TerminalTableCell>
</TerminalTableRow>
</TableHead>
<TableBody>
{logs.length === 0 ? (
<TerminalTableRow>
<TerminalTableCell colSpan={8} align="center">
<Box sx={{ py: 4, textAlign: 'center' }}>
<ReceiptIcon sx={{ color: terminalColors.textSecondary, fontSize: 48, mb: 2, opacity: 0.5 }} />
<TerminalTypography variant="body2" sx={{ color: terminalColors.primary, mb: 1, fontWeight: 'bold' }}>
No Usage Logs Yet
</TerminalTypography>
<TerminalTypography variant="body2" sx={{ color: terminalColors.textSecondary }}>
API usage logs will appear here once you start making API calls.
</TerminalTypography>
</Box>
</TerminalTableCell>
</TerminalTableRow>
) : (
logs.map((log) => (
<TerminalTableRow
key={log.id}
sx={{
backgroundColor: terminalColors.background,
color: terminalColors.primary,
'&:hover': {
backgroundColor: 'rgba(0, 255, 0, 0.1)',
}
}}
>
<TerminalTableCell>
<TerminalTypography variant="body2" sx={{ fontSize: '0.75rem' }}>
{formatDate(log.timestamp)}
</TerminalTypography>
</TerminalTableCell>
<TerminalTableCell>
<TerminalTypography variant="body2" sx={{ textTransform: 'capitalize' }}>
{log.provider}
</TerminalTypography>
</TerminalTableCell>
<TerminalTableCell>
<TerminalTypography variant="body2" sx={{ fontSize: '0.75rem' }}>
{log.model_used || 'N/A'}
</TerminalTypography>
</TerminalTableCell>
<TerminalTableCell>
<TerminalTypography variant="body2">
{formatTokens(log.tokens_total)}
</TerminalTypography>
</TerminalTableCell>
<TerminalTableCell>
<TerminalTypography variant="body2">
{formatCurrency(log.cost_total)}
</TerminalTypography>
</TerminalTableCell>
<TerminalTableCell>
{log.status === 'success' ? (
<TerminalChipSuccess
icon={getStatusIcon(log.status) || undefined}
label={`${log.status_code}`}
size="small"
/>
) : (
<TerminalChipError
icon={getStatusIcon(log.status) || undefined}
label={`${log.status_code}`}
size="small"
/>
)}
</TerminalTableCell>
<TerminalTableCell>
<TerminalTypography variant="body2" sx={{ fontSize: '0.75rem' }}>
{formatResponseTime(log.response_time)}
</TerminalTypography>
</TerminalTableCell>
<TerminalTableCell>
<Tooltip title={log.endpoint || ''}>
<TerminalTypography variant="body2" sx={{ fontSize: '0.75rem', maxWidth: '200px', overflow: 'hidden', textOverflow: 'ellipsis' }}>
{log.is_aggregated ? (
<span style={{ color: terminalColors.warning, fontStyle: 'italic' }}>
[AGGREGATED] {log.error_message || 'Historical data'}
</span>
) : (
`${log.method} ${log.endpoint?.substring(0, 30)}...`
)}
</TerminalTypography>
</Tooltip>
</TerminalTableCell>
</TerminalTableRow>
))
)}
</TableBody>
</Table>
</TableContainer>
<TablePagination
component="div"
count={totalCount}
page={page}
onPageChange={handleChangePage}
rowsPerPage={rowsPerPage}
onRowsPerPageChange={handleChangeRowsPerPage}
rowsPerPageOptions={[10, 25, 50, 100]}
sx={{
color: terminalColors.primary,
borderTop: `1px solid ${terminalColors.primary}`,
'& .MuiTablePagination-toolbar': {
color: terminalColors.primary,
},
'& .MuiTablePagination-selectLabel, & .MuiTablePagination-displayedRows': {
color: terminalColors.primary,
fontFamily: 'monospace',
},
'& .MuiIconButton-root': {
color: terminalColors.primary,
'&:hover': {
backgroundColor: 'rgba(0, 255, 0, 0.1)',
},
'&.Mui-disabled': {
color: terminalColors.textSecondary,
}
},
'& .MuiSelect-root': {
color: terminalColors.primary,
borderColor: terminalColors.primary,
}
}}
/>
</>
)}
</TerminalPaper>
);
};
export default UsageLogsTable;