Blokady kortu w grafiku
Blokada wyłącza kort z rezerwacji w wybranym terminie — na czas konserwacji, turnieju, imprezy zamkniętej czy złej pogody. Zakłada się ją z poziomu grafiku, tak samo jak rezerwację, a w kalendarzu pojawia się jako szary kafelek.
👤 Instrukcja dla pracownika (Recepcja / Administracja)
Dodanie blokady
- W Kalendarzu kliknij wolny slot na korcie i godzinie, które chcesz zablokować.
- W oknie, które się otworzy, wybierz zakładkę Blokada.
- Uzupełnij:
- Korty — zaznaczony jest kort z klikniętego slotu; możesz dołożyć kolejne albo użyć Zaznacz wszystkie, żeby zablokować całą halę.
- Czas blokady — godzina rozpoczęcia i długość.
- Powód (opcjonalnie) — np. „Konserwacja nawierzchni". Pojawi się na kafelku w grafiku.
- Widoczność opisu — Tylko personel (domyślnie) albo Personel i klienci. Sam termin jest niedostępny dla klientów w obu wariantach — ustawienie decyduje wyłącznie o tym, czy klient zobaczy powód.
- Powtarzaj blokadę — opcjonalnie codziennie / co tydzień / co miesiąc, przez określoną liczbę powtórzeń albo do wskazanej daty.
- Kliknij Utwórz blokadę.
Na dole formularza widać podsumowanie, ile blokad zostanie założonych (liczba kortów × liczba terminów).
Kolizja z istniejącymi zajęciami
Jeśli któryś z zaznaczonych kortów ma w tym czasie zajęcia lub rezerwację, formularz nie zapisze blokady — wypisze kolidujące terminy i zmieni przycisk na Zablokuj mimo to. Blokada nigdy nie odwołuje ani nie usuwa istniejących zajęć: jeśli mimo wszystko ją założysz, zajęcia zostają w grafiku, a kort przestaje być dostępny dla nowych rezerwacji.
Kolizji nie tworzą zajęcia odwołane i zarchiwizowane.
Usunięcie blokady
Kliknij szary kafelek w grafiku i potwierdź Usuń blokadę. Termin od razu wraca do puli dostępnych.
Co blokada faktycznie robi
Blokada to zamknięcie kortu — dokładnie ten sam mechanizm, co Zamknięcia w ustawieniach kortu. Oznacza to, że:
- termin znika z rezerwacji online klienta i z widoku dostępności,
- recepcja nie założy na nim rezerwacji ani zajęć,
- serie cykliczne pomijają zablokowane terminy i raportują je jako pominięte,
- blokadę widać także w Godzinach otwarcia → Zamknięcia, gdzie można ją edytować lub usunąć.
Odwrotnie to nie działa w drugą stronę wizualnie: zamknięcia wielodniowe i bezterminowe (np. remont na trzy miesiące) nadal wyszarzają siatkę zamiast rysować kafelek — kafelek zalałby cały grafik.
🛠️ Dokumentacja techniczna
Model danych
Blokada nie ma własnej tabeli — jest wpisem w kolumnie court.closures
(JSON, migracja 0111_add_closures_to_court.sql) o typie ClosureEntry:
interface ClosureEntry {
id?: string; // uuid nadawany przy tworzeniu z grafiku
dateFrom: string; // yyyy-MM-dd
dateTo?: string;
startTime?: string; // HH:mm (Europe/Warsaw)
endTime?: string;
reason?: string;
isIndefinite?: boolean;
visibility?: 'staff' | 'all';
}
Dzięki temu blokada od razu obowiązuje we wszystkich ścieżkach, które już
czytają zamknięcia: lib/actions/booking.ts, lib/actions/public-booking.ts,
lib/utils/recurring-occurrences.ts, widok dostępności i siatka grafiku.
Wpisy sprzed tej funkcji nie mają id — identyfikuje się je sygnaturą pól
(courtBlockKey).
Akcje serwerowe (lib/actions/court.ts)
| Akcja | Opis |
|---|---|
createCourtBlocks(input) | Waliduje rolę (ADMIN/BACKOFFICE), rozwija powtórzenia, sprawdza kolizje z game, dopisuje wpisy do closures wszystkich wskazanych kortów jednym dbBatch i unieważnia cache kortów. |
deleteCourtBlock(courtId, blockKey) | Usuwa jeden wpis (po id lub sygnaturze pól) i unieważnia cache. |
createCourtBlocks zwraca { created, conflicts }. Przy created === 0
i niepustych conflicts formularz pokazuje listę kolizji; ponowne wysłanie
leci z force: true.
Kolizje liczone są jednym zapytaniem po wszystkich kortach i całym oknie
czasowym serii (bez zapytań w pętli), a nakładanie sprawdzane jest już
w pamięci. Godziny są zapisywane jako czas ścienny Europe/Warsaw
i konwertowane do UTC przez zonedTimeToUtc; 24:00 rozwiązuje się na
północ dnia następnego, więc zmiana czasu nie przesuwa okna.
Warstwa UI
| Plik | Rola |
|---|---|
app/(dashboard)/dashboard/schedule/components/CourtBlockForm.tsx | Formularz zakładki „Blokada" (react-hook-form + zod). |
app/(dashboard)/dashboard/schedule/components/GameFormContainer.tsx | Trzecia zakładka obok „Rezerwacja" i „Aktywność", widoczna dla ADMIN/BACKOFFICE. |
lib/utils/court-blocks.ts | Czysta logika: expandBlockDates, isTileBlock, courtBlockKey, buildCourtBlockOccurrences, isCourtBlockResource. |
app/(dashboard)/dashboard/schedule/components/BookingClient.tsx | Dokłada kafelki blokad do zdarzeń kalendarza i obsługuje ich usuwanie. |
app/(dashboard)/dashboard/schedule/components/ScheduleCalendarComponents.tsx | Render kafelka (CourtBlockEventContent). |
Kafelki powstają z zamknięć jednodniowych (isTileBlock) mieszczących się
w widocznym zakresie dat i znikają, gdy w grafiku włączony jest filtr
uczestnika, rodzaju zajęć lub „tylko wolne miejsca" — wtedy siatka odpowiada na
inne pytanie i kafelki tylko przeszkadzają.
Testy jednostki logicznej: lib/utils/court-blocks.test.ts.