расписание

Расписание теннисного клуба онлайн — без двойных броней

letscourt /admin/schedule — главный экран администратора. Сетка дня по кортам, цветовая кодировка покрытий (хард / грунт / трава / падл), click-to-create бронь за ≤15 секунд, hover-popover с быстрым взглядом, drawer с inline-редактированием. Двойные брони на одном корте физически невозможны — гарантия на уровне Postgres.

Подать заявку →

Сетка дня — главный экран

На одном экране 6-10 кортов × 14 рабочих часов. Цветовое кодирование — глядя на сетку видишь занятость без чтения текста.

Хард — синий

Стандартное хард-покрытие. Indoor или outdoor — отдельные значки.

Грунт — терракотовый

Глиняный корт. Часто требует обработки между занятиями — летscourt поддерживает блок «технический перерыв».

Трава — зелёный

Реже встречается, в основном на открытых клубах. Свой ценник.

Блок — серый

Технический блок (уборка, ремонт, тренировка детей). Не считается в выручке.

Click-to-create — бронь за ≤15 секунд

Клиент звонит, говорит «хочу корт на 19:00 на час». Время от клика админа до отправленного клиенту подтверждения — меньше 15 секунд.

  1. Click в свободную клетку «19:00 на корте 2»
  2. Открывается popover с поиском клиента — начинаешь набирать «леб…», подсказка «Лебедева Мария +7…»
  3. Click в подсказку — клиент выбран
  4. (Опционально) Выбор тренера и длительности 1-4 часа
  5. Enter / Click «Создать»
  6. Бронь создана, клиенту автоматически уходит SMS/Telegram с подтверждением

Если клиента нет в базе — кнопка «+ Создать клиента» прямо в попапе с pre-fill введённого имени. Не нужно открывать отдельную форму.

Hover на броне — быстрый взгляд

Через 250 мс после наведения курсора появляется компактный popover с ключевой информацией:

  • ·Клиент: ФИО + телефон
  • ·Тип: игра / тренировка / турнир / блок
  • ·Тренер: имя если назначен
  • ·Стоимость и статус оплаты
  • ·Длительность

Удобно когда нужно глянуть «что у меня в 18:00» не открывая полноценный drawer. Снижает количество кликов в админке.

Click на броне — drawer редактирования

Полная информация и возможность изменить любое поле без открытия отдельной страницы.

Финансы

Стоимость, способ оплаты (онлайн/наличные/перевод), кнопка «Отметить оплачено» если клиент пришёл с наличкой

Тренер

Inline-замена через select. Назначить, заменить, убрать.

Клиент

Ссылка на карточку клиента. Клик → открывается профиль с историей всех его броней.

История

Создание (когда, кем) → оплата → если отменена то когда и кем. Полный audit-trail.

Действия

Редактировать (открыть форму) / Отменить (с auto-refund если была оплачена)

Защита от двойных броней — гарантия на БД

Postgres EXCLUDE constraint физически не даёт создать две пересекающиеся брони на одном корте. Это не «постараемся не пересечь» в коде — это constraint в базе данных. Если в один момент времени два администратора создают бронь на одно время одного корта — второй получит ошибку.

CONSTRAINT bookings_no_overlap
EXCLUDE USING gist (
  court_id WITH =,
  tstzrange(start_at, end_at) WITH &&
) WHERE (status != 'cancelled');

В переводе: «не позволять две записи где court_id одинаковый И диапазоны времени пересекаются (исключая отменённые брони)». btree-gist индекс делает это быстро даже на миллионах записей.

Hold с таймером 10 минут

Клиент через виджет/бот начал бронировать но не оплатил — слот блокируется на 10 минут. Если за это время не пришла оплата — бронь автоматически снимается, слот возвращается в продажу. Воркер expire-holds проверяет каждые 30 секунд.

В админ-инбоксе видны все hold-брони с обратным таймером (обновляется каждую секунду): <2 мин = красная полоска, 2-5 мин = жёлтая, >5 мин = зелёная. Админ видит «вот клиент уже 7 минут оплачивает» и может позвонить.

Запустить расписание клуба

5 минут на регистрацию и добавление кортов. Бронь принимаете в тот же день. Импорт существующих броней из Excel — в один клик.

Подать заявку →