import type { Race } from "../api"; import { formatRaceDate, isCloseDistance, parseFinishTimeToSeconds, parseRaceDate } from "../lib"; type PaceTrendChartProps = { races: Race[]; distanceKm: number; }; /** Линейный график: время финиша (минуты) по завершённым стартам выбранной дистанции. */ export function PaceTrendChart(props: PaceTrendChartProps): JSX.Element { const { races, distanceKm } = props; const series = races .filter( (race) => race.status === "completed" && isCloseDistance(race.distanceKm, distanceKm) && parseFinishTimeToSeconds(race.finishTime) != null, ) .sort( (a, b) => parseRaceDate(a.date).getTime() - parseRaceDate(b.date).getTime(), ) .map((race) => { const seconds = parseFinishTimeToSeconds(race.finishTime)!; return { race, minutes: seconds / 60 }; }); if (series.length < 2) { return (
Нужно минимум два завершённых старта с временем на выбранной дистанции.
); } const minutes = series.map((s) => s.minutes); const minM = Math.min(...minutes); const maxM = Math.max(...minutes); const range = maxM - minM || 1; const n = series.length; const pad = 4; const w = 100; const h = 36; const innerW = w - pad * 2; const innerH = h - pad * 2; const points = series .map((s, i) => { const x = pad + (n === 1 ? innerW / 2 : (i / (n - 1)) * innerW); const norm = (maxM - s.minutes) / range; const y = pad + (1 - norm) * innerH; return `${x},${y}`; }) .join(" "); const last = series[series.length - 1]!; const best = series.reduce((currentBest, item) => (item.minutes < currentBest.minutes ? item : currentBest), series[0]!); const dotPoints = series.map((s, i) => { const x = pad + (n === 1 ? innerW / 2 : (i / (n - 1)) * innerW); const norm = (maxM - s.minutes) / range; const y = pad + (1 - norm) * innerH; return { x, y, id: s.race.id }; }); return (Последний: {formatRaceDate(last.race.date)} · {last.race.finishTime} · {last.minutes.toFixed(1)} мин
Лучший: {formatRaceDate(best.race.date)} · {best.race.finishTime} · {best.minutes.toFixed(1)} мин