Skip to main content

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​

  1. W Kalendarzu kliknij wolny slot na korcie i godzinie, które chcesz zablokować.
  2. W oknie, które się otworzy, wybierz zakładkę Blokada.
  3. 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.
  4. 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)​

AkcjaOpis
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​

PlikRola
app/(dashboard)/dashboard/schedule/components/CourtBlockForm.tsxFormularz zakładki „Blokada" (react-hook-form + zod).
app/(dashboard)/dashboard/schedule/components/GameFormContainer.tsxTrzecia zakładka obok „Rezerwacja" i „Aktywność", widoczna dla ADMIN/BACKOFFICE.
lib/utils/court-blocks.tsCzysta logika: expandBlockDates, isTileBlock, courtBlockKey, buildCourtBlockOccurrences, isCourtBlockResource.
app/(dashboard)/dashboard/schedule/components/BookingClient.tsxDokłada kafelki blokad do zdarzeń kalendarza i obsługuje ich usuwanie.
app/(dashboard)/dashboard/schedule/components/ScheduleCalendarComponents.tsxRender 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.