160 lines
9.1 KiB
Markdown
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*
|