211 lines
14 KiB
Markdown
211 lines
14 KiB
Markdown
---
|
|
phase: 09-finalizacja
|
|
plan: 04
|
|
subsystem: api, ui, integrations
|
|
tags: [ical, fullcalendar, elementor, rest, privacy]
|
|
|
|
requires:
|
|
- phase: 09-finalizacja
|
|
provides: globalna sync iCal (09-02), cleanup OAuth (09-03)
|
|
provides:
|
|
- tryb sync iCal "wspólny kalendarz" (storage bez per-yacht matchingu)
|
|
- REST endpoint /availability/all (agregacja + privacy)
|
|
- widget Elementor + shortcode [yacht_calendar_all]
|
|
- paleta kolorów per-jacht (Rest_Controller::YACHT_COLOR_PALETTE)
|
|
- privacy: tytuły eventów nie zawierają nazwisk klientów (REST + JS)
|
|
affects: [security audit, dokumentacja]
|
|
|
|
tech-stack:
|
|
added: []
|
|
patterns:
|
|
- View helper pattern (Calendar_All_View) — render wspólny dla widgetu i shortcode
|
|
- Eager-load REST controller (constants/static methods used in frontend)
|
|
- Privacy-by-default: REST endpoint nie wystawia nazwisk klientów
|
|
|
|
key-files:
|
|
created:
|
|
- wp-content/plugins/yacht-booking-system/frontend/class-calendar-widget-all.php
|
|
- wp-content/plugins/yacht-booking-system/frontend/assets/css/calendar-all.css
|
|
- wp-content/plugins/yacht-booking-system/frontend/assets/js/calendar-all.js
|
|
modified:
|
|
- wp-content/plugins/yacht-booking-system/api/class-rest-controller.php
|
|
- wp-content/plugins/yacht-booking-system/integrations/ical/class-ical-import.php
|
|
- wp-content/plugins/yacht-booking-system/includes/class-settings.php
|
|
- wp-content/plugins/yacht-booking-system/includes/class-installer.php
|
|
- wp-content/plugins/yacht-booking-system/includes/class-yacht-booking.php
|
|
- wp-content/plugins/yacht-booking-system/admin/class-admin.php
|
|
- wp-content/plugins/yacht-booking-system/frontend/class-shortcode.php
|
|
- wp-content/plugins/yacht-booking-system/yacht-booking-system.php
|
|
|
|
key-decisions:
|
|
- "Tryb sync `global` = osobna ścieżka cleanup, dwie stałe source (per-yacht / global)"
|
|
- "Privacy: title eventów na REST = nazwa jachtu (per_yacht) lub generyczne 'Rezerwacja' (global), bez customer_name"
|
|
- "View helper `Calendar_All_View::render()` — wspólny markup widget + shortcode"
|
|
- "Eager-load Rest_Controller w load_dependencies() — używany przez frontend View, nie tylko REST"
|
|
- "Half-day visual: dynamiczny gradient JS na bazie szerokości komórki dnia (FC nie ma natywnej obsługi half-day w dayGrid)"
|
|
- "Kolor wspólnych eventów: jasnoniebieski #7fb3d5 (po iteracjach: #7f8c8d → #bc1834 → #7fb3d5)"
|
|
|
|
patterns-established:
|
|
- "Privacy-by-default w REST: tytuły eventów bez customer_name nawet w trybie per_yacht"
|
|
- "Settings z whitelistą wartości (`per_yacht`/`global`) + fallback do bezpiecznego defaultu"
|
|
- "Stała paleta kolorów współdzielona REST ↔ frontend (Rest_Controller::YACHT_COLOR_PALETTE)"
|
|
|
|
duration: ~120min
|
|
started: 2026-05-07T11:30:00Z
|
|
completed: 2026-05-07T13:30:00Z
|
|
---
|
|
|
|
# Phase 09 Plan 04: Globalna sync iCal + widget wszystkich jachtów
|
|
|
|
**Plugin obsługuje teraz dwa tryby synchronizacji iCal (per-jacht / wspólny) i ma drugi widget kalendarza pokazujący zajętość wszystkich jachtów na jednej siatce z paletą kolorów per-jacht, half-day gradientem i ciemnym formularzem zapytania po prawej stronie.**
|
|
|
|
## Performance
|
|
|
|
| Metric | Value |
|
|
|--------|-------|
|
|
| Duration | ~120min (z iteracjami UX po checkpoincie) |
|
|
| Started | 2026-05-07T11:30:00Z |
|
|
| Completed | 2026-05-07T13:30:00Z |
|
|
| Tasks | 4 auto + 1 checkpoint + 5 iteracji UX po checkpoincie |
|
|
| Files modified | 8 zmodyfikowanych + 3 nowe + 1 testowy (test-overlap-bookings.php) |
|
|
|
|
## Acceptance Criteria Results
|
|
|
|
| Criterion | Status | Notes |
|
|
|-----------|--------|-------|
|
|
| AC-1: Settings — przełącznik trybu sync iCal | Pass | Pole select w sekcji "Synchronizacja", domyślnie `per_yacht` |
|
|
| AC-2: Import iCal w trybie globalnym (wszystkie eventy) | Pass | Wszystkie eventy z basic.ics importowane, brak wpisów do `wp_yacht_availability` |
|
|
| AC-3: Import iCal w trybie per_yacht (bez regresji) | Pass | Stara ścieżka `run_per_yacht_mode()` działa identycznie, własna stała cleanup |
|
|
| AC-4: REST endpoint `/availability/all` | Pass | Zwraca FullCalendar events z timed 12:00→12:00 i kolorami |
|
|
| AC-5: Widget + shortcode | Pass | Po iteracjach: ciemne tło, formularz inquiry z select jachtów, bez tytułów eventów |
|
|
| AC-6: Auto paleta kolorów per jacht | Pass | `YACHT_COLOR_PALETTE` (8 kolorów) deterministyczna po yacht_id |
|
|
|
|
## Accomplishments
|
|
|
|
- **Tryb global iCal** — klient prowadzi jeden wspólny Google Calendar, wszystkie eventy (32 z basic.ics) są importowane bez filtrowania prefiksem; nie blokują dostępności jachtów (`yacht_id=0`).
|
|
- **Wspólny widget z formularzem inquiry** — `[yacht_calendar_all]` + Elementor "Kalendarz Jachtów (wszystkie)" pokazuje siatkę miesięczną z paskami rezerwacji w kolorach palety per-jacht (lub jednolity jasnoniebieski w trybie global), formularz "Zapytaj o rezerwację" po prawej w ciemnym stylu identycznym jak `/rezerwacja-maja/`.
|
|
- **Privacy-by-default** — REST endpoint `/availability/all` nie wystawia `customer_name` w `title` eventów; w trybie global tytuł = generyczne "Rezerwacja", w per_yacht = sama nazwa jachtu. Defense-in-depth: JS `eventContent` zwraca pusty html (nawet gdyby title trafił do REST, nie wyrenderuje się w DOM).
|
|
- **Half-day visual** — dynamiczny gradient JS na pierwszym/ostatnim dniu rezerwacji (yacht zwracany/odbierany w południe), liczony na bazie szerokości komórki dnia (FullCalendar dayGrid nie ma natywnej obsługi).
|
|
|
|
## Files Created/Modified
|
|
|
|
| File | Change | Purpose |
|
|
|------|--------|---------|
|
|
| `wp-content/plugins/yacht-booking-system/yacht-booking-system.php` | Modified | Bump wersji 1.0.0 → 1.1.0 (uruchamia Installer::install + cache busting) |
|
|
| `includes/class-settings.php` | Modified | `get_ical_sync_mode()` z whitelistą wartości |
|
|
| `includes/class-installer.php` | Modified | Default option `yacht_booking_ical_sync_mode = per_yacht` |
|
|
| `includes/class-yacht-booking.php` | Modified | Eager-load Rest_Controller, rejestracja widgetu, conditional enqueue, detekcja shortcode/widget |
|
|
| `admin/class-admin.php` | Modified | Pole select trybu sync + obsługa zapisu w `process_settings_save()` |
|
|
| `integrations/ical/class-ical-import.php` | Modified | Rozgałęzienie `run_per_yacht_mode()` / `run_global_calendar_mode()`, nowa stała `GLOBAL_CALENDAR_SOURCE`, metody `upsert_global_calendar_event()`, `get_existing_global_calendar_map()` |
|
|
| `api/class-rest-controller.php` | Modified | Stałe `YACHT_COLOR_PALETTE` + `GLOBAL_EVENT_COLOR`, route + metoda `get_all_availability()`, `get_yacht_color_palette()`, privacy w title |
|
|
| `frontend/class-shortcode.php` | Modified | Shortcode `[yacht_calendar_all]` |
|
|
| `frontend/class-calendar-widget-all.php` | Created | Widget Elementor `Calendar_Widget_All` + helper `Calendar_All_View` (server-side legenda + formularz inquiry z select jachtów) |
|
|
| `frontend/assets/css/calendar-all.css` | Created | Tła komórek emulujące paletę single-yacht (semi-transparent na ciemnym tle), styl select, ukryte tytuły eventów |
|
|
| `frontend/assets/js/calendar-all.js` | Created | FullCalendar init, dynamiczny gradient half-day, submit handler dla formularza inquiry z dropdownem jachtów |
|
|
| `test-overlap-bookings.php` | Created (root) | Test helper dodający 2 nakładające się eventy (do usunięcia po teście) |
|
|
|
|
## Decisions Made
|
|
|
|
| Decision | Rationale | Impact |
|
|
|----------|-----------|--------|
|
|
| Dwie stałe source (`ical_import_global` per-yacht, `ical_global_calendar` global) | Pozwala niezależny cleanup w obu trybach bez krzyżowych usunięć | Przełączanie trybów nie usuwa eventów z drugiego trybu |
|
|
| Privacy: brak `customer_name` w REST title | Wcześniej `title` zawierało "Maja — Jan Kowalski" — nazwiska klientów na publicznym REST | Nawet po inspekcji DOM/API nazwisk nie ma |
|
|
| View helper `Calendar_All_View::render()` zamiast duplikacji widget+shortcode | DRY | Zmiana markupu w jednym miejscu |
|
|
| Eager-load Rest_Controller w `load_dependencies()` | Frontend View używa stałych palety + statyk metod | Bez tego: `Class 'Rest_Controller' not found` przy renderze |
|
|
| Half-day przez dynamiczny gradient JS (a nie czyste CSS) | FullCalendar dayGrid nie ma natywnej obsługi half-day; CSS nie zna szerokości segmentu | Wymaga `requestAnimationFrame` w `eventDidMount` |
|
|
| Klasa wrappera `yacht-calendar-all-wrapper` (bez `yacht-calendar-wrapper`) | Calendar.js inicjalizuje wszystko z `.yacht-calendar-wrapper` → kolizja | Drugi widget niezależny od starego JS |
|
|
| Kolor wspólnych eventów: `#7fb3d5` (jasnoniebieski) | Iteracje z klientem: szary `#7f8c8d` → czerwony `#bc1834` (zbyt agresywny) → jasnoniebieski | Spójny z legendą + komfort wizualny |
|
|
| Tła komórek `rgba(245,249,255, 0.4)` na `#0e2036` | Emulacja efektu `yacht-day-available` bg-event nad ciemnym parent containerem w `/rezerwacja-maja/` | Spójny styl z istniejącym widgetem per-jacht |
|
|
|
|
## Deviations from Plan
|
|
|
|
### Summary
|
|
|
|
| Type | Count | Impact |
|
|
|------|-------|--------|
|
|
| Auto-fixed | 6 | UX poprawki po checkpoincie + privacy hardening |
|
|
| Scope additions | 1 | Privacy w REST (poza pierwotnym scope) |
|
|
| Deferred | 0 | Wszystko ukończone |
|
|
|
|
**Total impact:** Solidne UX iteracje + bonus privacy hardening (chroni przed wyciekiem nazwisk klientów).
|
|
|
|
### Auto-fixed Issues
|
|
|
|
**1. Path discrepancies — assets w `frontend/assets/` zamiast root `assets/`**
|
|
- **Found during:** Task 4 (widget assets)
|
|
- **Issue:** Plan referował `assets/js/`, `assets/css/`, `admin/views/settings-page.php` — w projekcie tych ścieżek nie ma
|
|
- **Fix:** Użyłem faktycznej struktury (`frontend/assets/{css,js}/`, formularz settings inline w `class-admin.php`)
|
|
- **Verification:** Pliki utworzone w prawidłowych miejscach, conditional enqueue działa
|
|
- **Commit:** część zmiany w `class-yacht-booking.php`
|
|
|
|
**2. `Class 'Rest_Controller' not found` przy frontend renderze**
|
|
- **Found during:** Po pierwszym deploy (raportowane przez klienta)
|
|
- **Issue:** Rest_Controller ładowany lazy w `register_rest_routes()`, View używa stałych palety przy frontend render
|
|
- **Fix:** Dodano `require_once api/class-rest-controller.php` w `Yacht_Booking::load_dependencies()` (eager)
|
|
- **Verification:** Brak fatal errors po reload
|
|
- **Commit:** `includes/class-yacht-booking.php`
|
|
|
|
**3. Brakujący formularz inquiry + brakująca legenda + brakująca instrukcja**
|
|
- **Found during:** Po checkpoincie human-verify
|
|
- **Issue:** Pierwsza wersja widgetu była tylko kalendarzem; klient oczekiwał layoutu jak na `/rezerwacja-maja/` (calendar + form right)
|
|
- **Fix:** Dodano `Calendar_All_View` render z `.yacht-inquiry-layout`, server-side legenda (działa nawet przed JS), instrukcję, formularz inquiry z `<select name="yacht_id">` dla wyboru jachtu, własny submit handler w JS
|
|
- **Verification:** Playwright snapshot — wszystko widoczne
|
|
- **Commit:** `class-calendar-widget-all.php`, `calendar-all.css`, `calendar-all.js`
|
|
|
|
**4. Kalendarz nie renderował się (tylko nagłówki dni)**
|
|
- **Found during:** Po dodaniu layoutu 2-kolumnowego
|
|
- **Issue:** FullCalendar `height: '100%'` w gridzie się nie liczyło → komórki collapsed
|
|
- **Fix:** Wysokość przekazywana przez `data-height` na wrapperze, JS odczytuje i podaje FC jako liczbę pikseli (`heightPx`)
|
|
- **Verification:** Playwright eval — fcDayGridRect ma poprawną wysokość
|
|
- **Commit:** `calendar-all.js` + `class-calendar-widget-all.php`
|
|
|
|
**5. Select formularza w jasnym natywnym stylu**
|
|
- **Found during:** Po dodaniu formularza inquiry
|
|
- **Issue:** calendar.css ma reguły dla `input[type=text|email|tel]` ale BRAK dla `<select>` → biały tekst, czarne tło, natywna strzałka
|
|
- **Fix:** Dodano w calendar-all.css regułę `.yacht-inquiry-form select` z analogicznym stylem (semi-transparent biały bg, custom SVG strzałka, dark `<option>`)
|
|
- **Verification:** Playwright eval — bg/color/border zgodne z innymi inputami
|
|
- **Commit:** `calendar-all.css`
|
|
|
|
**6. Privacy gap — nazwiska klientów w REST title**
|
|
- **Found during:** Iteracja "ukryj nazwy na zajętych dniach"
|
|
- **Issue:** REST `title = "Maja — Jan Kowalski"` — wrażliwe dane na publicznym endpointcie
|
|
- **Fix:** REST title to teraz: w trybie global `"Rezerwacja"`, w per_yacht sama nazwa jachtu (bez customer_name). JS `eventContent` zwraca pusty html (defense-in-depth)
|
|
- **Verification:** Playwright eval `/availability/all` — title bez nazwisk
|
|
- **Commit:** `class-rest-controller.php`, `calendar-all.js`, `calendar-all.css`
|
|
|
|
### Deferred Items
|
|
|
|
None.
|
|
|
|
## Issues Encountered
|
|
|
|
| Issue | Resolution |
|
|
|-------|------------|
|
|
| Brak `php` w PATH środowiska deweloperskiego (PowerShell + WSL) | Manualne `php -l` przez klienta podczas checkpointu human-verify |
|
|
| FullCalendar `height: '100%'` w grid container collapsuje | Pixel value przekazywany przez data-attribute |
|
|
| Tło kalendarza wyglądało "jasno" mimo poprawnej palety komórek | Klient oczekiwał ciemnego efektu jak `/rezerwacja-maja/`, gdzie tło to bg-eventy semi-transparent nad ciemnym parentem; emulacja przez `background: #0e2036` na container + `rgba(245,249,255, 0.4)` na komórkach |
|
|
| Klasa `yacht-calendar-wrapper` powodowała kolizję z `calendar.js` (per-jacht) | Usunięta z roota nowego widgetu |
|
|
|
|
## Skill Audit
|
|
|
|
`.paul/SPECIAL-FLOWS.md` nie istnieje — skill audit pominięty.
|
|
|
|
## Next Phase Readiness
|
|
|
|
**Ready:**
|
|
- Plugin produkcyjnie obsługuje 2 tryby sync iCal (ostatnia funkcjonalna brakująca cecha)
|
|
- Privacy-hardening REST endpointów (precedens na security audit w 09-05)
|
|
- Widget zbiorczy gotowy do publicznego użycia (read-only z formularzem inquiry)
|
|
|
|
**Concerns:**
|
|
- `test-overlap-bookings.php` w root projektu — do skasowania z FTP po teście (nie commitować)
|
|
- Brak automatycznych testów regresji (zgodnie z CLAUDE.md — testy manualne)
|
|
- Privacy-hardening REST tylko w nowym endpointcie `/availability/all`. Stary `/availability/{yacht_id}` zwraca tylko status dnia — bez customer_name — OK.
|
|
|
|
**Blockers:**
|
|
- None.
|
|
|
|
---
|
|
*Phase: 09-finalizacja, Plan: 04*
|
|
*Completed: 2026-05-07*
|