Дата-утилиты
Дата-утилиты отвечают за выбор периодов, вычисление диапазонов, генерацию интервалов и форматирование дат в статистике.
Ключевые файлы
| Файл | Назначение |
|---|---|
app/components/date/utils.ts | Базовые утилиты: toDuration, getStartOf, getEndOf, вычисление интервалов |
app/components/date/types.ts | Типы: Period, Range, StatDateParams, IntervalsInRangeProps |
app/components/date/statDateParams.ts | Вычисление диапазона из параметров, парсинг query-параметров |
app/components/date/useStatDate.ts | Composable: реактивное состояние дат с персистенцией в localStorage |
app/components/stat/date/useGetDateRange.ts | Человекочитаемые подписи ("Сегодня", "Последние 3 месяца", "Мар - Июн") |
app/components/stat/intervals.ts | Распределение транзакций по интервалам, средние значения |
Система периодов
Все операции с датами используют union-тип Period:
const periods = ['day', 'week', 'month', 'year'] as const
type Period = typeof periods[number]
Три базовых хелпера преобразуют Period в операции date-fns:
toDuration(period, value) // Period -> Duration ({ days: 3 }, { months: 1 })
getStartOf(date, period) // Period -> начало периода (startOfMonth, startOfWeek, ...)
getEndOf(date, period) // Period -> конец периода (endOfMonth, endOfWeek, ...)
Все три используют exhaustive switch - добавление нового варианта периода вызовет ошибку компиляции, а не молчаливый fallback.
Типобезопасный Duration
toDuration заменяет динамические ключи свойств типизированным возвратом Duration:
// Раньше: теряет типобезопасность, TS видит Record<string, number>
sub(date, { [`${period}s`]: value })
// Сейчас: возвращает типизированный Duration
sub(date, toDuration(period, value))
Вычисление диапазона дат
computeDateRange() преобразует StatDateParams в конкретный { start, end }:
Приоритет:
1. customDate (выбор в календаре) -> возвращается как есть
2. isShowMaxRange -> maxRange.start до конца текущего периода
3. isShowMaxRange + isSkipEmpty -> maxRange как есть
4. вычисленный -> от Date.now() по rangeBy/rangeDuration/rangeOffset
Диапазон - это Vue computed, использующий Date.now() как базу. Поскольку Date.now() не является реактивной зависимостью, диапазон обновляется только при изменении params или maxRange (не автоматически в полночь). Обновление страницы или любое изменение параметров запускает перевычисление.
Генерация интервалов
getIntervalsInRange() разбивает диапазон дат на подинтервалы:
range: 1 мар - 31 мар, intervalsBy: 'week'
-> [1-2 мар, 3-9 мар, 10-16 мар, 17-23 мар, 24-30 мар, 31 мар]
Крайние интервалы обрезаются по границам диапазона.
Производительность
Интервалы собираются через push() с последующим reverse() вместо unshift() в цикле. unshift сдвигает все элементы при каждом вызове (O(n^2) на весь цикл), тогда как push + reverse работает за O(n).
Распределение транзакций
bucketTrnsByIntervals() распределяет транзакции по интервалам бинарным поиском:
// O(N log M), где N = транзакции, M = интервалы
for each transaction:
бинарный поиск по интервалам
добавление в нужный бакет
Интервалы отсортированы и не пересекаются, что позволяет использовать бинарный поиск. Транзакции вне любого интервала молча пропускаются.
Форматирование дат
useGetDateRange() создает человекочитаемые подписи. Каждая функция форматирования возвращает полную строку (без разделения на "начало"/"конец" с последующей конкатенацией):
duration=1, текущий период -> "Сегодня", "Этот месяц", "Этот год"
duration=1, предыдущий -> "Вчера", "Прошлый месяц", "Прошлый год"
duration=1, этот год -> "15 марта"
duration>1, текущий период -> "Последние 3 дня", "Последние 6 месяцев"
остальные -> "Мар - Июн", "10-15 мар 2024"
Сравнение периодов использует isSamePeriod(date1, date2, period) - один хелпер вместо повторяющихся switch-блоков.
Парсинг query-параметров
parseStatDateQueryParams() мержит URL query-параметры в StatDateParams через Zod-валидацию. Все поля проверяются через !== undefined (не falsy), чтобы rangeOffset=0 и intervalSelected=0 применялись корректно.
Вычисление средних
computeAverageTotal() считает средние за день/неделю/месяц для диапазона дат. Возвращает undefined для однодневных диапазонов или нулевых сумм (ранний выход до вычисления интервалов). Среднее за день всегда присутствует при возврате результата (диапазон >= 2 дней гарантирован), поэтому проверка на пустой объект не нужна.
Далее
- Архитектура - структура проекта и паттерн хранилищ
- Тех. решения - обоснование ключевых решений