import { useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { Bar } from 'react-chartjs-2'; import type { ChartData, ChartOptions, TooltipItem } from 'chart.js'; import { Card } from '@/components/ui/Card'; import { formatCompactNumber } from '@/utils/usage'; import type { UsageIdentity } from '@/lib/types'; import styles from '@/pages/UsagePage.module.scss'; export interface CredentialStatsCardProps { credentials: UsageIdentity[]; loading: boolean; } export interface CredentialRow { key: string; displayName: string; type: string; success: number; failure: number; total: number; successRate: number; } export function buildCredentialRows(credentials: UsageIdentity[]): CredentialRow[] { return credentials .map((credential) => { const displayName = String(credential.name || credential.identity || '').trim() || '-'; const sourceType = String(credential.type || credential.auth_type_name || '').trim(); const key = String(credential.id || credential.identity || '').trim() || displayName; const success = Number(credential.success_count) || 0; const failure = Number(credential.failure_count) || 0; const total = Number(credential.total_requests) || success + failure; return { key, displayName, type: sourceType, success, failure, total, successRate: total > 0 ? (success / total) * 100 : 100, }; }) .sort((a, b) => b.total - a.total); } export function getTopCredentialRows(rows: CredentialRow[], limit = 10): CredentialRow[] { return rows.filter((row) => row.total > 0).slice(0, limit); } function CredentialStatsTitle({ title, subtitle, eyebrow }: { title: string; subtitle: string; eyebrow: string }) { return (
{eyebrow}

{title}

{subtitle}

); } export function CredentialStatsCard({ credentials, loading, }: CredentialStatsCardProps) { const { t } = useTranslation(); const rows = useMemo(() => buildCredentialRows(credentials), [credentials]); return ( } className={styles.detailsFixedCard} > {loading ? (
{t('common.loading')}
) : rows.length > 0 ? (
{rows.map((row) => ( ))}
{t('usage_stats.credential_name')} {t('usage_stats.requests_count')} {t('usage_stats.success_rate')}
{row.displayName} {row.type && {row.type}} {formatCompactNumber(row.total)} ({row.success.toLocaleString()}{' '} {row.failure.toLocaleString()}) = 95 ? styles.statSuccess : row.successRate >= 80 ? styles.statNeutral : styles.statFailure } > {row.successRate.toFixed(1)}%
) : (
{t('usage_stats.no_data')}
)}
); } export function CredentialTopChartCard({ credentials, loading }: CredentialStatsCardProps) { const { t } = useTranslation(); const rows = useMemo(() => buildCredentialRows(credentials), [credentials]); const topRows = useMemo(() => getTopCredentialRows(rows), [rows]); const chartData = useMemo>(() => ({ labels: topRows.map((row) => row.displayName), datasets: [ { label: t('usage_stats.failure'), data: topRows.map((row) => row.failure), backgroundColor: 'rgba(239, 68, 68, 0.78)', hoverBackgroundColor: 'rgba(239, 68, 68, 0.88)', borderColor: 'transparent', borderWidth: 0, borderSkipped: false, borderRadius: topRows.map((row) => ({ topLeft: 0, bottomLeft: 0, topRight: row.success > 0 ? 0 : 6, bottomRight: row.success > 0 ? 0 : 6, })), stack: 'requests', }, { label: t('usage_stats.success'), data: topRows.map((row) => row.success), backgroundColor: 'rgba(34, 197, 94, 0.76)', hoverBackgroundColor: 'rgba(34, 197, 94, 0.86)', borderColor: 'transparent', borderWidth: 0, borderSkipped: false, borderRadius: topRows.map((row) => ({ topLeft: row.failure > 0 ? 0 : 6, bottomLeft: row.failure > 0 ? 0 : 6, topRight: 6, bottomRight: 6, })), stack: 'requests', }, ], }), [topRows, t]); const chartOptions = useMemo>(() => ({ indexAxis: 'y', responsive: true, maintainAspectRatio: false, animation: false, plugins: { legend: { display: false }, tooltip: { displayColors: true, callbacks: { title: (items: TooltipItem<'bar'>[]) => { const index = items[0]?.dataIndex ?? 0; return topRows[index]?.displayName ?? ''; }, afterBody: (items: TooltipItem<'bar'>[]) => { const index = items[0]?.dataIndex ?? 0; const row = topRows[index]; if (!row) return []; return [ `${t('usage_stats.total_requests')}: ${row.total.toLocaleString()}`, `${t('usage_stats.success_rate')}: ${row.successRate.toFixed(1)}%`, ]; }, }, }, }, scales: { x: { stacked: true, beginAtZero: true, grid: { color: 'rgba(148, 163, 184, 0.18)', }, ticks: { precision: 0, color: '#94a3b8', }, }, y: { stacked: true, grid: { display: false, }, ticks: { display: false, }, }, }, }), [topRows, t]); return ( } > {loading ? (
{t('common.loading')}
) : topRows.length > 0 ? (
{chartData.datasets.map((dataset) => (
{dataset.label}
))}
) : (
{t('usage_stats.no_data')}
)}
); }