import type { Race } from "../api"; const MS_IN_DAY = 24 * 60 * 60 * 1000; /** API date: YYYY-MM-DD или ISO-строка от сериализации (не склеивать с «T00:00:00» повторно). */ export function parseRaceDate(date: string): Date { const ymd = date.slice(0, 10); if (/^\d{4}-\d{2}-\d{2}$/.test(ymd)) { return new Date(`${ymd}T00:00:00`); } const parsed = new Date(date); return parsed; } /** Дата старта (календарный день) строго раньше сегодняшней полуночи по локали. */ export function isRaceDateInPast(raceDate: string, now: Date = new Date()): boolean { const today = new Date(now); today.setHours(0, 0, 0, 0); return parseRaceDate(raceDate).getTime() < today.getTime(); } export function parseFinishTimeToSeconds(value: string | null): number | null { if (!value) { return null; } const parts = value.split(":").map((part) => Number(part)); if (parts.some((part) => Number.isNaN(part) || part < 0)) { return null; } if (parts.length === 2) { const [minutes, seconds] = parts; return minutes * 60 + seconds; } if (parts.length === 3) { const [hours, minutes, seconds] = parts; return hours * 3600 + minutes * 60 + seconds; } return null; } export function formatDistance(distanceKm: number): string { return `${distanceKm.toLocaleString("ru-RU", { maximumFractionDigits: 1 })} км`; } export function formatRaceDate(date: string): string { return parseRaceDate(date).toLocaleDateString("ru-RU", { day: "2-digit", month: "long", year: "numeric", }); } export function sortByDateAsc(races: Race[]): Race[] { return [...races].sort((left, right) => parseRaceDate(left.date).getTime() - parseRaceDate(right.date).getTime()); } export function sortByDateDesc(races: Race[]): Race[] { return [...races].sort((left, right) => parseRaceDate(right.date).getTime() - parseRaceDate(left.date).getTime()); } export function splitRacesByDate(races: Race[], now: Date = new Date()): { upcoming: Race[]; past: Race[] } { const today = new Date(now); today.setHours(0, 0, 0, 0); const upcoming: Race[] = []; const past: Race[] = []; for (const race of races) { if (parseRaceDate(race.date).getTime() >= today.getTime()) { upcoming.push(race); } else { past.push(race); } } return { upcoming: sortByDateAsc(upcoming), past: sortByDateDesc(past), }; } function pluralizeDays(n: number): string { const mod10 = n % 10; const mod100 = n % 100; if (mod100 >= 11 && mod100 <= 19) { return "дней"; } if (mod10 === 1) { return "день"; } if (mod10 >= 2 && mod10 <= 4) { return "дня"; } return "дней"; } export function getRaceCountdownLabel(date: string, now: Date = new Date()): string { const today = new Date(now); today.setHours(0, 0, 0, 0); const target = parseRaceDate(date); const days = Math.ceil((target.getTime() - today.getTime()) / MS_IN_DAY); if (days <= 0) { return "сегодня"; } return `через ${days} ${pluralizeDays(days)}`; } export function isCloseDistance(left: number, right: number): boolean { return Math.abs(left - right) < 0.05; } export function getPaceLabel(finishTime: string | null, distanceKm: number): string | null { const totalSeconds = parseFinishTimeToSeconds(finishTime); if (!totalSeconds || distanceKm <= 0) { return null; } const paceSeconds = Math.round(totalSeconds / distanceKm); const paceMinutes = Math.floor(paceSeconds / 60); const paceRemainder = paceSeconds % 60; return `${String(paceMinutes).padStart(2, "0")}:${String(paceRemainder).padStart(2, "0")} /км`; } function isPastDateNeedingResult(status: Race["status"], raceDate: string): boolean { if (status !== "planned" && status !== "registered") { return false; } const today = new Date(); today.setHours(0, 0, 0, 0); return parseRaceDate(raceDate).getTime() < today.getTime(); } export function raceNeedsResultEntry(race: Race): boolean { return isPastDateNeedingResult(race.status, race.date); } export function getRaceStatusClassName(status: Race["status"], raceDate?: string): string { const base = "race-card__status"; let tier = `${base}--planned`; if (status === "completed") { tier = `${base}--completed`; } else if (status === "registered") { tier = `${base}--registered`; } const needs = raceDate && isPastDateNeedingResult(status, raceDate) ? ` ${base}--needs-result` : ""; return `${base} ${tier}${needs}`; } export function getRaceStatusLabel(status: Race["status"], raceDate?: string): string { if (raceDate && isPastDateNeedingResult(status, raceDate)) { return "внесите результат"; } if (status === "completed") { return "пробежал"; } if (status === "registered") { return "зарегистрирован"; } return "планирую"; }