Files
ridebus-status/src/components/Statistics.tsx
2026-10-13 02:24:39 +03:00

24 lines
4.1 KiB
TypeScript

import { useState } from 'react';
import type { Locale, Service } from '../types';
import { formatDuration, formatPercent, formatTime, translations } from '../lib/i18n';
import { ArrowRight, Statistics as StatisticsIcon } from './Icons';
import { ResponseChart } from './Charts';
export function ReliabilityMetrics({ service, locale }: { service: Service; locale: Locale }) {
const t = translations[locale], stats = service.reliability;
const duration = (value: number | null) => `${stats.downtimeIncomplete && value !== null ? '≥ ' : ''}${formatDuration(value, locale)}`;
return <><dl className="reliabilityGrid"><div><dt>{t.measuredDowntime}</dt><dd>{duration(stats.downtimeMs)}</dd><small>{t.confirmedIncidents}: {stats.incidentCount}</small></div><div><dt>{t.longestOutage}</dt><dd>{duration(stats.longestDowntimeMs)}</dd></div><div><dt>{t.lastRecovery}</dt><dd className="recoveryTime">{stats.lastRecoveryAt === null ? t.noRecovery : formatTime(stats.lastRecoveryAt, locale, true)}</dd>{stats.lastRecoveryScope && <small>{stats.lastRecoveryScope === 'scenario' ? t.scenarioTitle : t.basicCheck}</small>}</div></dl><p className="statisticsCoverage">{t.timeCoverage}: {stats.observedMs > 0 && stats.coverage < .1 ? `<${formatPercent(.1, locale)}` : formatPercent(stats.coverage, locale)}{stats.downtimeIncomplete && <> · {t.incompleteDowntime}</>}</p></>;
}
export function StatisticsPanel({ services, locale, range, onDetails }: { services: Service[]; locale: Locale; range: number; onDetails: (id: string) => void }) {
const t = translations[locale];
const [selected, setSelected] = useState(services[0]?.id || 'api');
const [period, setPeriod] = useState<'24h' | 'range'>('24h');
const service = services.find(item => item.id === selected) || services[0];
if (!service) return null;
const latency = period === '24h' ? service.latency24h : service.latency;
const points = period === '24h' ? service.responseHistory : service.latencyHistory;
const change = latency.changePercent;
return <section className="statisticsSection" aria-labelledby="statistics-title"><div className="sectionHeading"><div><h2 id="statistics-title">{t.statisticsTitle}</h2><p>{t.statisticsDescription}</p></div><span className="statisticsRange">{range} {t.days}</span></div><div className="statisticsCard"><div className="servicePicker" role="group" aria-label={t.serviceSelection}>{services.map(item => <button key={item.id} aria-pressed={item.id === service.id} className={item.id === service.id ? 'selected' : ''} onClick={() => setSelected(item.id)}>{item.name[locale]}</button>)}</div><ReliabilityMetrics service={service} locale={locale} /><p className="statisticsMethod">{t.statisticsMethod}</p><div className="latencyHeading"><h3><StatisticsIcon size={19} />{t.latencyTrend}</h3><div className="incidentFilters" role="group" aria-label={t.latencyTrend}><button aria-pressed={period === '24h'} className={period === '24h' ? 'selected' : ''} onClick={() => setPeriod('24h')}>{t.last24hours}</button><button aria-pressed={period === 'range'} className={period === 'range' ? 'selected' : ''} onClick={() => setPeriod('range')}>{range} {t.days}</button></div></div><div className="latencySummary"><div><span>{t.periodAverage}</span><strong>{latency.averageMs === null ? '—' : `${latency.averageMs} ${t.ms}`}</strong></div><div title={t.latencyPercentileHint}><span>{t.latencyPercentile}</span><strong>{latency.p95Ms === null ? '—' : `${latency.p95Ms} ${t.ms}`}</strong></div><div className={`latencyChange ${change === null || change === 0 ? '' : change > 0 ? 'slower' : 'faster'}`}><span>{t.latencyComparison}</span><strong>{change === null ? '—' : `${change > 0 ? '+' : ''}${change.toLocaleString(locale)}%`}</strong>{change === null && <small>{t.latencyNoComparison}</small>}</div></div><ResponseChart key={`${service.id}:${period}:${range}`} points={points} locale={locale} bucketMs={period === '24h' ? 3600000 : 86400000} interactive /><div className="statisticsFoot"><span>{latency.samples.toLocaleString(locale)} {t.measurements}</span><button className="textButton" onClick={() => onDetails(service.id)}>{t.details}<ArrowRight size={17} /></button></div></div></section>;
}