Files
2026-05-08 00:12:37 +02:00

17 KiB

phase, plan, type, wave, depends_on, files_modified, autonomous, delegation
phase plan type wave depends_on files_modified autonomous delegation
09-finalizacja 05 execute 1
09-04
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/frontend/assets/css/calendar-all.css
wp-content/plugins/yacht-booking-system/yacht-booking-system.php
false off
## Goal Pokazać tytuł rezerwacji (z Google Calendar bez prefiksu "GCal:" / "GCal (wspólny):") na paskach kalendarza zbiorczego `[yacht_calendar_all]` na stronie `/rezerwacja/` oraz rozbić ciągłą belkę wielodniową na osobne segmenty per dzień z widocznym odstępem.

Purpose

Klient zmienił zdanie po wdrożeniu 09-04 — chce identyfikować rezerwacje wzrokowo bez wchodzenia w szczegóły, w formacie zgodnym z tym co wpisuje w Google Calendar (np. "Maja - Kowalscy 5 osób"). Dodatkowo prosi o czytelniejszy podział wizualny gdy jedna rezerwacja zajmuje kilka dni — żeby na pierwszy rzut oka było widać że to oddzielne noce, a nie jedna ciągła sesja.

Output

  • REST /availability/all zwraca w title raw SUMMARY (z _booking_notes dla iCal) lub customer_name (dla rezerwacji frontowych) — bez prefiksu "GCal".
  • Frontend renderuje tytuł w pasku eventu (eventContent zwraca tekst zamiast pustego HTML).
  • Każdy event z REST jest rozbity na N eventów per dzień (1 event = 1 dzień), więc FullCalendar renderuje N osobnych pasków zamiast jednej belki.
  • CSS dodaje pionowy gap (margin/padding) między dziennymi paskami.
  • Pierwszy/ostatni dzień rezerwacji zachowuje half-day gradient (yacht odbierany/zwracany w południe).
- **Co pokazać** — Co wyświetlić jako tytuł rezerwacji w pasku? → Odpowiedź: Tytuł zaimportowany z Google Calendar bez prefiksu "GCal:" (czyli raw SUMMARY z `_booking_notes`). - **Widoczność** — Dla kogo widoczne mają być te dane? → Odpowiedź: Publicznie (dla wszystkich odwiedzających). - **Global eventy** — Co z eventami z trybu global iCal (yacht_id=0)? → Odpowiedź: Pokazać tytuł z iCal (raw SUMMARY z `_booking_notes`) — to co właściciel wpisuje w GCal. - **Styl belki** — Jak rozdzielić dni rezerwacji wizualnie? → Odpowiedź: Osobny segment per dzień z gap (każdy dzień = osobny pasek, między nimi odstęp).

Project Context

@.paul/PROJECT.md @.paul/STATE.md @.paul/codebase/architecture.md

Prior Work

@.paul/phases/09-finalizacja/09-04-SUMMARY.md (wprowadziło privacy hardening — w tym planie świadomie cofamy ukrycie tytułu, ALE bez ujawniania customer_name dla rezerwacji frontowych… patrz boundaries)

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/frontend/assets/css/calendar-all.css @wp-content/plugins/yacht-booking-system/integrations/ical/class-ical-import.php @wp-content/plugins/yacht-booking-system/includes/class-booking.php

<acceptance_criteria>

AC-1: Tytuł rezerwacji z iCal w REST

Given booking utworzony przez import iCal (`_booking_source` = `ical_global_calendar` lub `ical_import_global`) z `_booking_notes` = "Maja - Kowalscy 5 osób"
When frontend wywołuje GET /wp-json/yacht-booking/v1/availability/all
Then odpowiedź zawiera event z `title` = "Maja - Kowalscy 5 osób" (bez prefiksu "GCal:" / "GCal (wspólny):")

AC-2: Tytuł rezerwacji frontowej

Given booking utworzony przez frontend POST /bookings (`_booking_source` puste, `_booking_customer_name` = "Jan Kowalski")
When frontend wywołuje GET /availability/all
Then event ma `title` = "Jan Kowalski" (customer_name)

AC-3: Tytuł renderowany w pasku eventu

Given strona /rezerwacja/ z widgetem [yacht_calendar_all] i co najmniej jednym eventem w bieżącym miesiącu
When kalendarz się wyrenderuje
Then pasek eventu zawiera tekst tytułu (nie pusty); tekst nie jest obcinany na wąskich segmentach (ellipsis OK)

AC-4: Per-day segmenty z gap

Given rezerwacja trwająca 4 dni (np. 2026-06-012026-06-05)
When kalendarz wyrenderuje ten miesiąc
Then w siatce widoczne są 4 osobne paski (po jednym w każdej z 4 komórek dni — bez ostatniego dnia checkout 5go), z widocznym pionowym odstępem między dolną krawędzią paska dnia N a górną krawędzią paska dnia N+1 w sąsiadujących wierszach (lub wewnątrz wiersza — komórki separowane gridem)
And FullCalendar nie renderuje jednej ciągłej belki spanning 4 dni

AC-5: Half-day visual zachowany

Given rezerwacja 2026-06-012026-06-05 (yacht odbierany 1.06 w południe, zwracany 5.06 w południe)
When kalendarz się wyrenderuje
Then segment 2026-06-01 ma lewą połowę transparentną (gradient), segment 2026-06-04 ma prawą połowę transparentną; środkowe dni (2/06, 3/06) pełny kolor

AC-6: Privacy — brak ujawnienia customer_email/customer_phone

Given dowolny booking
When REST zwraca eventy
Then odpowiedź NIE zawiera `customer_email` ani `customer_phone` w żadnym polu

</acceptance_criteria>

Task 1: REST — tytuł z `_booking_notes` lub `customer_name` + split per-day wp-content/plugins/yacht-booking-system/api/class-rest-controller.php W `Rest_Controller::get_all_availability()` (api/class-rest-controller.php:355):
1. Zastąp logikę budowania `$title` (linie ~424-436) nową:
   - Pobierz `$source = get_post_meta($booking_id, '_booking_source', true)`.
   - Pobierz `$notes = get_post_meta($booking_id, '_booking_notes', true)`.
   - Jeśli `$source` jest jednym z `ICal_Import::GLOBAL_CALENDAR_SOURCE` lub `ICal_Import::GLOBAL_IMPORT_SOURCE` → `$title = $notes` jeśli niepuste, fallback `__('Rezerwacja', 'yacht-booking')`.
   - W przeciwnym razie (booking frontowy) → `$title = Booking::get_customer_name($booking_id)`, fallback `__('Rezerwacja', 'yacht-booking')`.
   - Sanitize: `$title = sanitize_text_field($title)`.
   - Usuń obsługę `$is_global_mode` zmieniającą tytuł na "Rezerwacja" — nawet w trybie global pokazujemy raw SUMMARY (per decyzji klienta).

2. Zastąp pojedynczy event `events[] = [...]` z `start = $start_date.'T12:00:00'` / `end = $end_date.'T12:00:00'` na pętlę emitującą JEDEN event per noc rezerwacji:
   - `$cursor = new DateTimeImmutable($start_date)` ; `$end_dt = new DateTimeImmutable($end_date)`.
   - Dopóki `$cursor < $end_dt` (bo end_date to dzień checkout — yacht zwracany w południe, pełna noc kończy się dzień wcześniej):
     - `$day = $cursor->format('Y-m-d')`.
     - `$is_first = ($day === $start_date)`.
     - `$next = $cursor->modify('+1 day')->format('Y-m-d')`.
     - `$is_last_night = ($next === $end_date)`.
     - Wyemituj event:
       ```php
       $events[] = [
         'id'       => $booking_id . '-' . $day,
         'title'    => $title,
         'start'    => $day . 'T12:00:00',
         'end'      => $next . 'T12:00:00',
         'color'    => $color,
         'yacht_id' => $y_id,
         'extendedProps' => [
           'is_first'       => $is_first,
           'is_last_night'  => $is_last_night,
           'booking_id'     => $booking_id,
         ],
       ];
       ```
     - `$cursor = $cursor->modify('+1 day')`.
   - Każdy emitowany event obejmuje DOKŁADNIE jedną dobę (12:00 → następny dzień 12:00). FC dayGrid wyrenderuje go jako blok TYLKO w komórce dnia rozpoczęcia (bo timed event z start.day < end.day i krótszy niż 24h+1 — w praktyce 24h — renderuje się w day cell startu).

3. Boundary: NIE dodawaj `customer_email`, `customer_phone`, `customer_name` do payloadu poza `title` jeśli source = frontend (i to świadomie — klient zaakceptował publiczną widoczność imienia/nazwiska składającego rezerwację).

Avoid: usuwania starego endpointu `/availability/{yacht_id}` (out of scope), modyfikacji `Rest_Controller::YACHT_COLOR_PALETTE`, generowania title po stronie JS (single source of truth = REST).
1. `php -l wp-content/plugins/yacht-booking-system/api/class-rest-controller.php` → no syntax errors. 2. Po deploy: `curl 'https://jachty3.pagedev.pl/wp-json/yacht-booking/v1/availability/all?start=2026-05-01&end=2026-09-01' | jq` → - Każdy booking imported z iCal ma N eventów (N = liczba nocy), każdy z title = SUMMARY bez "GCal:". - Brak pól `customer_email`, `customer_phone`. - extendedProps.is_first true tylko na pierwszym evencie z danego booking_id. AC-1, AC-2, AC-6 spełnione; każdy booking emituje N eventów po jednej nocy każdy. Task 2: Frontend JS — render tytułu + half-day per single-day event wp-content/plugins/yacht-booking-system/frontend/assets/js/calendar-all.js W `calendar-all.js`:
1. Zastąp `eventContent` (linie 73-76) implementacją renderującą tytuł:
   ```js
   eventContent: function (arg) {
     var title = arg.event.title || '';
     var $el = $('<div class="yc-event-title"></div>').text(title);
     return { domNodes: [ $el.get(0) ] };
   }
   ```
   — `$().text()` zapewnia escaping (XSS safe).

2. Zaktualizuj `applyHalfDayGradient(info)` (linia 147+):
   - Zamiast `info.isStart` / `info.isEnd` (które dla single-day eventów ZAWSZE są oba true → wpadałoby w "single segment containing both start and end") → odczytuj z `extendedProps`:
     ```js
     var isFirstDay = info.event.extendedProps && info.event.extendedProps.is_first;
     var isLastNight = info.event.extendedProps && info.event.extendedProps.is_last_night;
     ```
   - `startTrans = isFirstDay`, `endTrans = isLastNight`.
   - Jeśli `isFirstDay && isLastNight && (booking 1-nocny)` → tak jak było (gradient przezroczysty po obu stronach).
   - Pozostała logika gradient stops bez zmian.
   - Reszta (środkowa noc) → pełny kolor.

3. Boundary: NIE zmieniaj logiki `events:` fetch (REST URL bez zmian — endpoint ten sam, tylko payload zmieniony).
1. W przeglądarce na `/rezerwacja/`: każdy pasek pokazuje tytuł z `_booking_notes`. 2. DevTools: dla rezerwacji wielonocnej pierwszy pasek ma `background-image: linear-gradient` z transparent po lewej, ostatni z transparent po prawej, środkowe pełny kolor. AC-3, AC-5 spełnione. Task 3: CSS — gap między dziennymi paskami + styl tytułu w pasku wp-content/plugins/yacht-booking-system/frontend/assets/css/calendar-all.css W `calendar-all.css`:
1. Usuń lub przekomentuj regułę ukrywającą tytuł (linie 89-93):
   ```css
   .yacht-calendar-all .fc-event-title,
   .yacht-calendar-all .fc-daygrid-event-dot,
   .yacht-calendar-all .fc-event-time {
     display: none !important;
   }
   ```
   Zostaw `display: none` dla `.fc-daygrid-event-dot` i `.fc-event-time`, zostaw widoczne `.fc-event-title` (lub po prostu nie ruszaj — eventContent zwraca custom DOM, a fc-event-title FC nie wstawi).

2. Dodaj styl dla custom kontenera tytułu:
   ```css
   .yacht-calendar-all .yc-event-title {
     padding: 1px 6px;
     font-size: 11px;
     font-weight: 600;
     color: #fff;
     text-shadow: 0 1px 1px rgba(0,0,0,0.35);
     white-space: nowrap;
     overflow: hidden;
     text-overflow: ellipsis;
     line-height: 1.3;
   }
   ```

3. Dodaj gap pionowy między dziennymi paskami (per-day segmenty leżą obok siebie w sąsiednich komórkach — gap zapewnia margin):
   ```css
   .yacht-calendar-all .fc-daygrid-event {
     margin: 1px 2px !important;
     border-radius: 2px;
   }
   ```
   Komórki dnia w FC są separowane border-collapse — `margin: 1px 2px` na evencie tworzy widoczną przerwę po lewej i prawej krawędzi paska, co wizualnie segmentuje rezerwację wielodniową.

4. Mobile (max-width: 600px): zmniejsz font-size tytułu do 10px (już jest reguła `.fc-event { font-size: 10px }` — uzupełnij `.yc-event-title { font-size: 10px }` w tej media query).
1. W przeglądarce: rezerwacja 4-nocna wygląda jak 4 osobne paski z 4px łączną przerwą między nimi (2px lewa + 2px prawa kolejnego). 2. Tytuł widoczny na każdym pasku z ellipsis przy wąskich kolumnach. AC-4 spełnione (per-day segmenty z gap), AC-3 wzmocnione (czytelny tytuł). Task 4: Bump wersji pluginu (cache busting JS/CSS) wp-content/plugins/yacht-booking-system/yacht-booking-system.php W headerze pluginu i stałej `YACHT_BOOKING_VERSION` zmień `1.1.0` → `1.2.0`. To wymusza odświeżenie cache assets przez `wp_enqueue_scripts` (filemtime fallback nie działa wszędzie po FTP deploy). `grep -n "1.2.0" wp-content/plugins/yacht-booking-system/yacht-booking-system.php` → 2 trafienia (Version: header + define). Wersja pluginu podbita; po deploy klient widzi nowe assety bez ręcznego czyszczenia cache. - REST `/availability/all` zwraca eventy per noc z tytułem z `_booking_notes` (iCal) lub `customer_name` (frontend). - Frontend renderuje tytuł w pasku eventu. - Każda doba rezerwacji to osobny pasek z 2px gapem po bokach (efekt segmentów). - Half-day gradient zachowany na pierwszej/ostatniej dobie rezerwacji. 1. Po deploy FTP odwiedź https://jachty3.pagedev.pl/rezerwacja/ (twardy reload Ctrl+F5). 2. Zweryfikuj wzrokowo: - Każdy pasek rezerwacji pokazuje tytuł zgodny z tym co wpisałeś w Google Calendar (np. "Maja - Kowalscy") bez prefiksu "GCal:". - Rezerwacja wielodniowa wygląda jak osobne kafelki z widocznym odstępem (a nie jedna ciągła belka). - Pierwszy dzień rezerwacji ma lewą połowę "pustą" (gradient), ostatni dzień prawą — to half-day visual. 3. DevTools → Network → `/availability/all`: payload zawiera N eventów per booking, brak `customer_email`/`customer_phone`. 4. Test edge case: jednodniowa rezerwacja (1 noc) — powinna mieć obie połówki transparentne (gradient z obu stron). Wpisz "approved" żeby zamknąć plan, albo opisz co poprawić.

DO NOT CHANGE

  • wp-content/plugins/yacht-booking-system/api/class-rest-controller.php — endpoint /availability/{yacht_id} (per-yacht, używa innego frontu — [yacht_calendar yacht_id="X"]).
  • wp-content/plugins/yacht-booking-system/frontend/assets/js/calendar.js (single-yacht widget — out of scope).
  • wp-content/plugins/yacht-booking-system/integrations/ical/class-ical-import.php (_booking_notes jest już zapisywany — nie trzeba ruszać).
  • Rest_Controller::YACHT_COLOR_PALETTE i GLOBAL_EVENT_COLOR (kolory pozostają, paleta per-yacht działa nadal).
  • Wszystko związane z formularzem inquiry (right-side panel) — out of scope.

SCOPE LIMITS

  • Plan dotyczy WYŁĄCZNIE widgetu zbiorczego [yacht_calendar_all] na /rezerwacja/. Single-yacht widget pozostaje bez zmian.
  • NIE rozszerzamy payloadu REST o customer_email / customer_phone — tylko title (klient zaakceptował publiczną widoczność tytułu, ale email/telefon zostają prywatne).
  • NIE wprowadzamy admin-only fallback dla privacy — zgodnie z odpowiedzią klienta widoczność jest publiczna dla wszystkich.
  • Rewersal privacy z 09-04: świadoma decyzja klienta. Odnotować w SUMMARY że hardening tytułów został cofnięty per żądanie biznesu (security audit 09-06 powinien to uwzględnić jeśli zmieni zdanie).
Before declaring plan complete: - [ ] `php -l class-rest-controller.php` passes - [ ] Deploy via ftp-kr (4 zmodyfikowane pliki) - [ ] curl `/availability/all` zwraca N eventów per booking, title z `_booking_notes` / `customer_name`, brak email/phone - [ ] Strona `/rezerwacja/` (Ctrl+F5): tytuły widoczne, paski per-day rozdzielone gapem, half-day na first/last - [ ] Brak regresji w widgecie single-yacht (np. `/rezerwacja-maja/`) - [ ] Wszystkie acceptance criteria spełnione

<success_criteria>

  • Wszystkie 4 zadania auto + checkpoint zatwierdzone
  • Brak fatal errors / warnings PHP w logach
  • Klient zaakceptował wzrokowy efekt na produkcji
  • Plugin v1.2.0 deployed </success_criteria>
After completion, create `.paul/phases/09-finalizacja/09-05-SUMMARY.md`