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 ? (
| {t('usage_stats.credential_name')} |
{t('usage_stats.requests_count')} |
{t('usage_stats.success_rate')} |
{rows.map((row) => (
|
{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}
))}
{topRows.map((row) => (
{row.displayName}
))}
) : (
{t('usage_stats.no_data')}
)}
);
}