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

160 lines
9.1 KiB
Markdown

---
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*