11 KiB
11 KiB
phase, plan, type, wave, depends_on, files_modified, autonomous, delegation
| phase | plan | type | wave | depends_on | files_modified | autonomous | delegation | ||||
|---|---|---|---|---|---|---|---|---|---|---|---|
| 09-finalizacja | 06 | execute | 1 |
|
|
false | off |
Purpose
Klient nie chce, żeby odwiedzający stronę /rezerwacja/ przeglądali historię rezerwacji ani puste przyszłe miesiące — kalendarz ma pokazywać tylko sensowny przedział "od dziś do ostatniej znanej rezerwacji".
Output
- Nowy REST endpoint
GET /yacht-booking/v1/availability/boundszwracający{ max_booking_date: 'YYYY-MM-DD' | null }(najpóźniejsza data zakończenia rezerwacji confirmed/pending). - JS
calendar-all.jsprzed inicjalizacją FullCalendar fetchuje bounds i ustawiavalidRangeblokujący nawigację. - Plugin bumped → 1.2.1 (cache busting).
Project Context
@.paul/PROJECT.md @.paul/STATE.md @.paul/codebase/architecture.md
Source Files
@wp-content/plugins/yacht-booking-system/api/class-rest-controller.php @wp-content/plugins/yacht-booking-system/frontend/assets/js/calendar-all.js @wp-content/plugins/yacht-booking-system/yacht-booking-system.php
<acceptance_criteria>
AC-1: REST endpoint zwraca max booking date
Given baza zawiera rezerwacje confirmed/pending z `_booking_end_date` od 2026-05-15 do 2026-08-20
When frontend wywołuje GET /wp-json/yacht-booking/v1/availability/bounds
Then odpowiedź = `{ "max_booking_date": "2026-08-20" }` (HTTP 200, public, brak nonce required)
AC-2: Brak rezerwacji → null
Given baza nie zawiera żadnych rezerwacji confirmed/pending z `_booking_end_date >= dziś`
When frontend wywołuje GET /availability/bounds
Then odpowiedź = `{ "max_booking_date": null }`
AC-3: Prev disabled w bieżącym miesiącu
Given strona /rezerwacja/ wczytana, widok = aktualny miesiąc (dziś)
When kalendarz wyrenderowany
Then przycisk "prev" w toolbar FC ma atrybut `disabled` (lub klasę `fc-button-disabled`); klik nie zmienia widoku
And przycisk "today" również zachowuje się normalnie (jest na bieżącym miesiącu — nic się nie zmienia)
AC-4: Next disabled na/po miesiącu ostatniej rezerwacji
Given max_booking_date = "2026-08-20", widok bieżący = sierpień 2026
When kalendarz wyrenderowany
Then przycisk "next" wyłączony (validRange.end = 2026-09-01)
AC-5: Next disabled gdy brak przyszłych rezerwacji
Given max_booking_date = null
When kalendarz wyrenderowany na bieżącym miesiącu
Then przyciski "prev" i "next" są wyłączone (validRange = bieżący miesiąc tylko)
AC-6: Nawigacja w obrębie zakresu działa
Given max_booking_date = "2026-08-20", aktualny miesiąc = maj 2026, widok = maj 2026
When user klika "next" 3 razy
Then widok zmienia się czerwiec → lipiec → sierpień; w sierpniu "next" jest wyłączony
</acceptance_criteria>
Task 1: REST endpoint /availability/bounds wp-content/plugins/yacht-booking-system/api/class-rest-controller.php W `Rest_Controller::register_rest_routes()` dodaj rejestrację nowego route'u (obok istniejącego `/availability/all`):```php
register_rest_route(
self::NAMESPACE,
'/availability/bounds',
array(
'methods' => 'GET',
'callback' => array( $this, 'get_availability_bounds' ),
'permission_callback' => '__return_true',
)
);
```
Dodaj nową metodę publiczną `get_availability_bounds( $request )`:
- Zapytanie WP_Query lub `get_posts` o `post_type = yacht_booking`, `post_status = publish`, status confirmed/pending (`_booking_status` IN), `posts_per_page = 1`, sortowane malejąco po `_booking_end_date` (meta_key + orderby=meta_value + meta_type=DATE).
- Pobierz `_booking_end_date` z pierwszego wyniku.
- Walidacja: jeśli niepuste i pasuje do `^\d{4}-\d{2}-\d{2}$` → zwróć `array( 'max_booking_date' => $date )`. W przeciwnym razie `array( 'max_booking_date' => null )`.
- Filtruj tylko rezerwacje z `_booking_end_date >= dziś` (gmdate('Y-m-d')) — historyczne nie powinny rozszerzać zakresu.
Avoid: dotykać innych endpointów, nie zmieniaj `get_all_availability`. Brak `permission_callback => 'admin'` — endpoint publiczny (potrzebny przy renderze widgetu dla anonimowych userów).
1. `php -l class-rest-controller.php` → no syntax errors.
2. Po deploy: `curl 'https://jachty3.pagedev.pl/wp-json/yacht-booking/v1/availability/bounds'` → JSON z polem `max_booking_date`.
3. Wartość powinna pasować do najpóźniejszej rezerwacji w bazie (confirmed/pending, end_date >= dziś).
AC-1, AC-2 spełnione.
Task 2: JS bootstrap bounds + validRange
wp-content/plugins/yacht-booking-system/frontend/assets/js/calendar-all.js
Zmień `initCalendar(wrapper)` tak, by przed `new FullCalendar.Calendar(...)` wykonał `$.getJSON` do `/availability/bounds` i ustawił `validRange` na bazie odpowiedzi:
1. Helper na początku pliku (poza initCalendar):
```js
function firstOfMonth(d) {
return new Date(d.getFullYear(), d.getMonth(), 1);
}
function nextMonthFirst(d) {
return new Date(d.getFullYear(), d.getMonth() + 1, 1);
}
```
2. W `initCalendar`: wyciągnij `restBase` z `restUrl` (zamień `/availability/all` na bazową ścieżkę REST namespace, czyli najprościej: zbuduj `boundsUrl` przez `restUrl.replace(/\/availability\/all.*$/, '/availability/bounds')`).
3. Wykonaj `$.getJSON(boundsUrl)` PRZED instancjacją FC. W `done` callback:
- `var today = new Date();`
- `var rangeStart = firstOfMonth(today);`
- `var maxDate = data && data.max_booking_date ? new Date(data.max_booking_date + 'T00:00:00') : null;`
- `var rangeEnd;`
- Jeśli `maxDate && maxDate >= today`: `rangeEnd = nextMonthFirst(maxDate);` (validRange.end jest exclusive — daje cały miesiąc maxDate)
- W przeciwnym wypadku (null lub przeszłość): `rangeEnd = nextMonthFirst(today);` (tylko bieżący miesiąc dostępny)
- Następnie wywołaj funkcję `buildCalendar(wrapper, $cal, restUrl, heightPx, rangeStart, rangeEnd)` która zawiera dotychczasową logikę inicjalizacji FC z dodanym `validRange: { start: rangeStart, end: rangeEnd }` w opcjach Calendar.
- W `fail` callback: zbuduj kalendarz bez `validRange` (graceful degradation — lepsze niż brak kalendarza).
4. Pozostała logika (events fetch, eventDidMount, eventContent, form submit) bez zmian.
Avoid: nie usuwaj istniejących handlerów (form submit, half-day gradient, tooltip). Nie zmieniaj `eventContent` ani `applyHalfDayGradient`.
1. W przeglądarce na /rezerwacja/: DevTools Network — request do `/availability/bounds` przed/równolegle do `/availability/all`.
2. Klik "prev" w bieżącym miesiącu — brak akcji; przycisk wizualnie disabled.
3. Klik "next" wielokrotny — kalendarz dochodzi do miesiąca ostatniej rezerwacji i tam się zatrzymuje.
AC-3, AC-4, AC-5, AC-6 spełnione.
Task 3: Bump wersji pluginu
wp-content/plugins/yacht-booking-system/yacht-booking-system.php
Zmień `Version: 1.2.0` → `Version: 1.2.1` i `define( 'YACHT_BOOKING_VERSION', '1.2.0' )` → `'1.2.1'` (2 wystąpienia).
`grep -n "1.2.1"` → 2 trafienia.
Wersja podbita; assets cache busted po deploy.
- REST `/availability/bounds` zwraca `max_booking_date` (data ostatniej rezerwacji confirmed/pending).
- JS przed init kalendarza pobiera bounds i ustawia `validRange` (start = pierwszy dzień bieżącego miesiąca, end = pierwszy dzień miesiąca PO miesiącu max_booking_date — exclusive).
- Prev disabled na bieżącym miesiącu, next disabled na/po miesiącu ostatniej rezerwacji.
- Plugin v1.2.1.
1. Wgraj 3 zmienione pliki przez ftp-kr.
2. Otwórz https://jachty3.pagedev.pl/rezerwacja/ z Ctrl+F5.
3. Zweryfikuj:
- Prev przycisk wyglądnięci (disabled / wyszarzony) bo widok = bieżący miesiąc.
- Klikaj "next" — kalendarz przechodzi przez miesiące tylko do miesiąca, w którym kończy się ostatnia rezerwacja.
- W ostatnim miesiącu z rezerwacjami next staje się disabled.
4. DevTools → Network → request `/availability/bounds` zwraca poprawny JSON.
5. Edge case (jeśli możliwe do przetestowania): wyłącz wszystkie przyszłe rezerwacje → reload → kalendarz pokazuje tylko bieżący miesiąc, oba przyciski disabled.
Wpisz "approved" lub opisz problemy.
DO NOT CHANGE
get_all_availability()w REST — kontrakt eventów per-day allDay zachowany.- Single-yacht widget (
frontend/class-calendar-widget.php,calendar.js,calendar.css) — out of scope. - Inquiry form i jego submit handler — out of scope.
eventContent,applyHalfDayGradient,eventDidMount— bez zmian.
SCOPE LIMITS
- Plan dotyczy tylko widgetu zbiorczego na
/rezerwacja/. - NIE dodajemy admin-only logiki (publiczny endpoint).
- NIE cache'ujemy odpowiedzi
/boundsdługoterminowo (jeden fetch per page load wystarczy; dane zmieniają się rzadko). - NIE zmieniamy nawigacji per-rok (tylko miesiąc-po-miesiącu).
<success_criteria>
- 3 zadania auto + checkpoint zatwierdzone
- Brak fatal errors / warnings PHP
- Klient zaakceptował zachowanie nawigacji
- Plugin v1.2.1 deployed </success_criteria>