13 KiB
План: календарь забегов (SPA + API + PostgreSQL)
Консолидированный план реализации с актуальными решениями. После инициализации git — работать в отдельной ветке (например feature/race-calendar-app).
1. Цели продукта
- Расписание по месяцу — выбор месяца/года, список забегов.
- Расписание на год — календарная сетка года, отметки на датах со стартами; по клику на дату — модалка/панель со списком забегов и переход на карточку.
- Добавлять забеги запланированные и уже прошедшие.
- Для прошедших — ввод/редактирование результата и стартового номера; поля можно дописать позже.
- Личные рекорды на главной по дистанциям: 1 км, 5 км, 10 км, 15 км, 21.1 км, 42.2 км — обновляются, если в забеге указан более быстрый результат на «подходящей» дистанции.
- Старты (организатор): дата и время старта, расписание кластеров, выдача номеров — ручной ввод; обязательна возможность указать официальную ссылку на страницу организатора (автопарсинг сайтов не входит в объём).
- Авторизация не требуется.
2. Исходные данные в репозитории
- Файл
import/races_2026_calendar.csv— колонкиdate,month,day,event,distance_km. - Назначение CSV: один раз — как вход для seed-скрипта, который пишет строки в PostgreSQL.
- В рантайме ни фронт, ни API этот CSV не читают. После успешного seed файл может оставаться в репо только как архив/референс.
3. Стек и структура репозитория
- Монорепозиторий:
frontend/(Vite + React 18 + TypeScript + react-router-dom) иbackend/(Node.js + Fastify или Express). - БД: PostgreSQL; схема — SQL-миграции в
backend/(или согласованный каталог миграций). - Локально:
docker-compose.ymlс сервисом Postgres. - Стили: CSS + BEM + CSS variables (минимальная дизайн-система: цвета, отступы, типографика).
- Даты/время:
date-fnsилиIntl, русская локаль там, где нужно.
3.1 Переменные окружения
Только на сервере API (никогда не во фронт-бандле):
DB_HOST=
DB_PORT=
DB_NAME=
DB_USER=
DB_PASSWORD=
Дополнительно по необходимости: PORT или API_PORT, NODE_ENV, CORS_ORIGIN.
Фронтенд (Vite): только публичный адрес API, например:
VITE_API_BASE_URL=http://localhost:3001
В коде: import.meta.env.VITE_API_BASE_URL.
Шаблон без секретов — корневой .env.example; описание — в docs/frontend.md и docs/backend.md.
4. Источник правды и поток данных
4.1 Рантайм
Единственный источник правды для календаря и карточек в работающем приложении — PostgreSQL. SPA общается только с HTTP API.
localStorage не используется для хранения забегов или «дельт».
flowchart LR
User[User]
SPA[React SPA]
API[Node API]
DB[(PostgreSQL)]
User --> SPA
SPA -->|"fetch VITE_API_BASE_URL"| API
API -->|"DB_* connection"| DB
4.2 Вне рантайма (разово)
Seed-скрипт (запуск вручную при развёртывании/обновлении стартового набора):
- Читает
import/*.csvи/или опциональноpublic/data/races.json. - Выполняет
INSERT/ upsert в таблицуraces. - Не вызывается из SPA и не выполняется на каждом HTTP-запросе.
Промежуточная ступень CSV → races.json для работы SPA не обязательна: seed может писать в БД напрямую из CSV и/или из JSON.
4.3 Разовый перенос CSV → БД
- Парсинг: заголовок; кавычки в
event;distance_km— число;date—YYYY-MM-DD. - После переноса приложение в обычном режиме не использует
import/races_2026_calendar.csv. Новые старты — через UI/API (или отдельная админ-фича импорта, если появится позже).
5. Модель данных (БД и API)
Один набор полей — в таблице, в JSON тел запросов/ответов API и в опциональном файле для seed. Согласовать camelCase в API и snake_case в SQL в docs/backend.md.
| Поле | Обяз. | Примечание |
|---|---|---|
id |
да | Стабильный ключ, напр. 2026-05-03-kazan-marathon |
date |
да | День старта YYYY-MM-DD |
title |
да | Название |
distanceKm |
да | Число км |
status |
нет | planned / completed; иначе можно вывести из даты |
officialUrl |
нет | Ссылка на организатора |
startTime |
нет | Напр. 09:30 |
clusterSchedule |
нет | Многострочный текст |
bibPickup |
нет | Выдача номеров |
bibNumber |
нет | Стартовый номер |
finishTime |
нет | Время H:MM:SS / HH:MM:SS; для PR — перевод в секунды |
notes |
нет | Заметки |
Опционально в миграциях: created_at, updated_at.
Операции: GET список/фильтры по году-месяцу при необходимости, GET :id, POST, PATCH :id, при необходимости DELETE — зафиксировать в docs/backend.md.
6. Поведение SPA
6.1 Форма «Добавить забег»
- Обязательно: дата, название, дистанция.
- Если дата строго до сегодня (локальная дата пользователя) — показать результат и стартовый номер (необязательны при первом сохранении).
- Если дата в будущем — поля результата скрыты.
- Опционально: чекбокс «Уже прошёл» для случая сегодняшней даты, чтобы открыть поля результата.
- Сохранение:
**POST** на API → запись в БД.
6.2 Страница забега
- Все поля модели; для прошедших — акцент на результат и номер.
- Редактирование результата и номера в любой момент:
**PATCH** на API, затем обновление состояния на клиенте (refetch / инвалидация кэша).
6.3 Личные рекорды
- Дистанции: 1, 5, 10, 15, 21.1, 42.2 км.
- Учитывать забеги с заполненным
finishTimeи дистанцией в пределах допуска к целевой (напр. 21.0975 → 21.1, 42.195 → 42.2). - «Чужие» дистанции (2 км, 6 км, 30 км…) в таблицу PR по умолчанию не включаются.
- Расчёт на клиенте по данным, полученным с API.
6.4 Экраны
- Главная — PR, навигация «Месяц» / «Год», ближайшие старты.
- Месяц — селектор, список.
- Год — сетка, маркеры, клик по дате → модалка со списком → карточка.
- Карточка забега, форма добавления.
- Доступность модалки: фокус, Esc, контраст.
7. Документация
Создать каталог **docs/**:
| Файл | Назначение |
|---|---|
docs/backend.md |
Docker, DB_*, миграции, seed, REST API, CORS |
docs/frontend.md |
Структура frontend/, VITE_*, сборка, контракт с API |
docs/ux-spa.md |
Сценарии, экраны, BEM, кратко про a11y |
Корневой **README.md**: описание проекта, быстрый старт, ссылки на три файла выше.
8. Риски и ограничения
**DB_* и пароли** — только в окружении сервера API; в git — только.env.exampleбез реальных секретов.- Актуальные данные после работы в UI — в БД; файлы в git не синхронизируются с правками автоматически. Надёжный бэкап — резервное копирование PostgreSQL.
- Деплой: Postgres + процесс API с env + статическая раздача фронта (или иная схема хостинга — описать в README).
- Данные организаторов (расписание стартов) — вручную + ссылка; без парсинга чужих сайтов в объёме первой версии.
9. Ключевые пути после реализации
docker-compose.yml,.env.examplebackend/— сервер, миграции, роутыscripts/илиbackend/scripts/— разовый seed из CSV/JSONimport/races_2026_calendar.csv— только вход seed (не рантайм)public/data/races.json— опциональный вход seedfrontend/src/api/— клиент APIfrontend/src/pages/,frontend/src/components/— UI (BEM)frontend/src/lib/distances.ts— PRdocs/*.md,README.md
10. Чеклист задач (implementation todos)
- Монорепо:
frontend/+backend/, BEM, токены, роутер. - Postgres в docker-compose, миграции таблицы
races, бэкенд читаетDB_*. - REST CRUD + разовый seed (CSV и/или JSON) → БД.
- Клиент API на фронте, типы, загрузка данных для экранов и PR.
- Экраны месяц и год, модалка по дате.
- Форма добавления с условными полями; карточка с PATCH результата/номера.
- Поля организатора на карточке.
- A11y модалки, мобильная вёрстка, смоук-сценарии.
docs/+ README +.env.example.
11. Идеи на будущее (тематика бега)
- Недельный объём / простой план подготовки.
- Импорт/экспорт JSON для бэкапа через API.
- Фильтры (город, дистанция), погода по дню старта.
- График динамики PR.
- Загрузка GPX / тренировки.
- Браузерные напоминания за N дней до старта.
- Сравнение результатов одной дистанции по годам.
- PWA для офлайн-просмотра (read-only кэш не заменяет БД без продуманной синхронизации).
Документ создан как единый актуальный план в корне репозитория. При расхождениях с черновиками в IDE приоритет у этого файла.