--- phase: 09-finalizacja plan: 05 subsystem: ui, api tags: [fullcalendar, rest, privacy-revert, ux] requires: - phase: 09-finalizacja provides: widget zbiorczy [yacht_calendar_all] + REST /availability/all (09-04) provides: - tytuły rezerwacji (raw SUMMARY z `_booking_notes` lub `customer_name`) widoczne na paskach kalendarza zbiorczego - per-day allDay events (każda doba rezerwacji = osobny pasek w swojej komórce) - half-day visual przeniesiony na flagi `extendedProps.is_first` / `is_last_night` - native tooltip na hover (title + data dnia) affects: [security audit (09-06) — privacy hardening z 09-04 świadomie cofnięty] tech-stack: added: [] patterns: - allDay events per occupied day (start INCLUSIVE → end INCLUSIVE) zamiast jednej timed belki cross-midnight - extendedProps jako single source of truth dla half-day flag (server decyduje, JS renderuje) - native HTML title attribute jako tooltip (zero zależności, accessibility-friendly) key-files: created: [] modified: - 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 key-decisions: - "Per-day allDay events zamiast 1 timed event 12:00→12:00 (poprzednia próba z timed eventami crossowała północ → FC renderował schodki)" - "Iteracja od start_date do end_date INCLUSIVE — pierwszy i ostatni dzień mają half-day visual" - "Cofnięcie privacy hardening z 09-04 — title z `_booking_notes` (iCal raw SUMMARY) lub `customer_name` widoczny publicznie per żądanie klienta" - "Tooltip = native `title` attribute (bez tippy.js / qtip — zero JS, działa offline)" patterns-established: - "extendedProps jako kontrakt server↔JS dla flag visual (kiedy is_start/is_end FC nie wystarcza)" - "Privacy w REST jest decyzją biznesową, nie techniczną — security audit musi to weryfikować osobno" duration: ~75min started: 2026-05-07T22:30:00Z completed: 2026-05-07T23:50:00Z --- # Phase 09 Plan 05: UX rezerwacja — tytuły GCal + per-day segmenty **Kalendarz zbiorczy `[yacht_calendar_all]` na `/rezerwacja/` pokazuje tytuły rezerwacji (raw SUMMARY z Google Calendar bez prefiksu "GCal:") na osobnych paskach per dzień, z half-day visual na pierwszym i ostatnim dniu rezerwacji oraz native tooltip na hover.** ## Performance | Metric | Value | |--------|-------| | Duration | ~75min (z 1 iteracją UX po pierwszym deploy) | | Started | 2026-05-07T22:30:00Z | | Completed | 2026-05-07T23:50:00Z | | Tasks | 4 auto + 1 checkpoint + 1 iteracja UX (allDay fix) + 1 mini-fix (tooltip) | | Files modified | 4 | ## Acceptance Criteria Results | Criterion | Status | Notes | |-----------|--------|-------| | AC-1: Tytuł rezerwacji z iCal w REST | Pass | `_booking_notes` zwracane jako `title`, prefiks "GCal:" zniknął | | AC-2: Tytuł rezerwacji frontowej | Pass | Booking::get_customer_name() jako fallback | | AC-3: Tytuł renderowany w pasku eventu | Pass | `.yc-event-title` z ellipsis; weryfikacja Playwright (events: "Kubuś - Julia R") | | AC-4: Per-day segmenty z gap | Pass | Po fix do allDay — każda doba w osobnej komórce, margin 2px daje gap | | AC-5: Half-day visual zachowany | Pass | extendedProps.is_first / is_last_night sterują gradientem | | AC-6: Privacy — brak email/phone | Pass | Payload REST nie zawiera customer_email ani customer_phone | ## Accomplishments - **Tytuły rezerwacji widoczne** — klient może wzrokowo identyfikować rezerwacje na publicznym kalendarzu (np. "Kubuś - Julia R", "Maja - Kowalscy 5 osób") bez prefiksu "GCal:". - **Per-day allDay events** — każda doba rezerwacji to osobny pasek w pojedynczej komórce dnia (1.06, 2.06, 3.06...) z 2px gapem między nimi. Rezerwacja 5-dniowa = 5 osobnych prostokątów zamiast jednej ciągłej belki. - **Half-day visual zachowany** — pierwszy dzień rezerwacji (yacht odbierany w południe) ma lewą połowę transparentną, ostatni dzień (zwrot w południe) prawą połowę transparentną. Środkowe dni — pełny kolor. - **Native tooltip na hover** — `title` attribute z formatem "{tytuł} ({YYYY-MM-DD})" — zero zależności, działa accessibility-friendly. ## Files Created/Modified | File | Change | Purpose | |------|--------|---------| | `api/class-rest-controller.php` | Modified | `get_all_availability()`: title z `_booking_notes`/`customer_name`, split per-day allDay events z extendedProps (is_first / is_last_night) | | `frontend/assets/js/calendar-all.js` | Modified | `eventContent` renderuje `.yc-event-title` (escaped przez `.text()`); `eventDidMount` ustawia native `title` tooltip; `applyHalfDayGradient` czyta flagi z extendedProps | | `frontend/assets/css/calendar-all.css` | Modified | Usunięte `display:none` z `.fc-event-title`; nowy `.yc-event-title` (ellipsis); `margin: 1px 2px` na `.fc-daygrid-event` (gap między paskami); mobile font-size | | `yacht-booking-system.php` | Modified | Bump wersji 1.1.0 → 1.2.0 (cache busting) | ## Decisions Made | Decision | Rationale | Impact | |----------|-----------|--------| | Per-day allDay events zamiast timed 12:00→12:00 | Pierwsza próba z timed eventami crossowała północ → FC dayGrid renderował każdą dobę jako pasek na granicy dwóch komórek (efekt "schodków") | Każda doba zamknięta w jednej komórce, czysty layout | | Iteracja `start_date <= end_date` (INCLUSIVE) | Yacht obecny od noon dnia start do noon dnia end → wszystkie dni tego zakresu są "occupied" | Rezerwacja 1.06 → 5.06 emituje 5 eventów (1, 2, 3, 4, 5) zamiast 4 nocy | | Cofnięcie privacy z 09-04 | Klient zmienił zdanie — chce wzrokowo identyfikować rezerwacje | Tytuły widoczne publicznie. `customer_email`/`customer_phone` nadal NIE wychodzą przez REST. Security audit (09-06) musi to uwzględnić | | Tooltip = native `title` attribute | Klient prosił o "jakiś label na hover" — najprostsze, bez nowych zależności | Hover po ~1s pokazuje "Kubuś - Julia R (2026-06-04)" | ## Deviations from Plan ### Summary | Type | Count | Impact | |------|-------|--------| | Auto-fixed | 1 | Krytyczny: pierwsza wersja (timed events 12:00→12:00) renderowała "schodki" — zmiana na allDay events naprawiła layout | | Scope additions | 1 | Native tooltip na hover (poza pierwotnym PLAN.md, prośba klienta po weryfikacji) | | Deferred | 0 | — | **Total impact:** Pierwsza implementacja per-PLAN była technicznie poprawna (timed events 12:00→12:00 = 1 doba), ale FC dayGrid renderuje takie eventy jako rozciągnięte przez granicę dwóch komórek (każda doba pojawiała się na pograniczu dnia N i N+1). Fix do allDay events rozwiązał to zachowując semantykę (start/end INCLUSIVE). ### Auto-fixed Issues **1. Timed events 12:00→12:00 renderowały "schodki" w FC dayGrid** - **Found during:** Checkpoint human-verify (klient zgłosił "paski jakoś dziwnie wyszły") - **Issue:** Każdy event był 24h cross-midnight (start dzień N 12:00 → dzień N+1 12:00). FC dayGrid pokazywał każdą dobę jako fragment na granicy dwóch komórek, eventy nakładały się wizualnie tworząc efekt schodków - **Fix:** Zmiana na `allDay: true` z `start = day` (bez end). Iteracja od start_date do end_date INCLUSIVE. Każdy event w jednej komórce - **Files:** `api/class-rest-controller.php` (pętla while) - **Verification:** Playwright snapshot po deploy — paski wyrównane do komórek dni, brak nakładek - **Commit:** część zmiany w Task 1 (przed checkpointem) + redeploy po fix ### Scope Additions **1. Native tooltip na hover** - **Origin:** Po zatwierdzeniu allDay fix klient poprosił o "jakiś label na hover" - **Implementation:** `info.el.setAttribute('title', info.event.title + ' (' + dayStr + ')')` w `eventDidMount` - **Verification:** klient potwierdził OK - **Files:** `frontend/assets/js/calendar-all.js` ### Deferred Items None. ## Issues Encountered | Issue | Resolution | |-------|------------| | Timed events crossowały północ → "schodki" w grid | Zmiana modelu na allDay events INCLUSIVE start↔end | ## Skill Audit `.paul/SPECIAL-FLOWS.md` nie istnieje — skill audit pominięty. ## Next Phase Readiness **Ready:** - Widget zbiorczy `[yacht_calendar_all]` w pełni funkcjonalny — UX zatwierdzony przez klienta - Plugin v1.2.0 deployed - Privacy w REST świadomie zarządzona — security audit (09-06) ma jasny precedens do oceny **Concerns:** - **Privacy regression vs 09-04** — `_booking_notes` (raw iCal SUMMARY) i `customer_name` są teraz publicznie wystawiane na `/availability/all`. Klient zaakceptował biznesowo. Security audit (09-06) powinien: 1. Potwierdzić że `customer_email`/`customer_phone` nadal NIE wyciekają (są zablokowane na poziomie REST shape). 2. Rozważyć czy tytuły rezerwacji (zawierające imiona/nazwiska klientów) wymagają dodatkowych zabezpieczeń (np. throttling, robots noindex na endpoincie REST). - Brak automatycznych testów regresji — przy kolejnych zmianach w `get_all_availability()` regresja może umknąć **Blockers:** - None --- *Phase: 09-finalizacja, Plan: 05* *Completed: 2026-05-07*