update
This commit is contained in:
@@ -48,8 +48,10 @@ Szczegóły w `wp-content/plugins/yacht-booking-system/PROJECT-STATUS.md`.
|
||||
- [x] 09-02: Globalna synchronizacja iCal — wspólny Google Calendar z podziałem na jachty po prefiksie nazwy w tytule eventu (export feed + import URL + alias jachtu) ✅ 2026-05-06
|
||||
- [x] 09-03: Cleanup OAuth + per-yacht iCal — usunięcie martwego kodu (3 pliki GCal/OAuth, per-yacht feedy, pole "Google Calendar ID") + cleanup migration "po cichu" ✅ 2026-05-06
|
||||
- [x] 09-04: Globalna sync iCal (tryb wspólny kalendarz, bez filtrowania) + nowy widget "wszystkie jachty" (kolory per-jacht, half-day, bez ukośników, formularz inquiry, privacy w REST) ✅ 2026-05-07
|
||||
- [ ] 09-05: Security audit i poprawki
|
||||
- [ ] 09-06: Testy + tłumaczenia + dokumentacja
|
||||
- [x] 09-05: UX rezerwacja — tytuły rezerwacji z GCal w paskach + per-day allDay events + tooltip na hover (cofa privacy z 09-04 per żądanie klienta) ✅ 2026-05-07
|
||||
- [x] 09-06: UX rezerwacja — blokada nawigacji kalendarza (REST `/availability/bounds` + validRange w FC) ✅ 2026-05-08
|
||||
- [ ] 09-07: Security audit i poprawki
|
||||
- [ ] 09-08: Testy + tłumaczenia + dokumentacja
|
||||
|
||||
---
|
||||
*Roadmap created: 2026-05-05*
|
||||
|
||||
@@ -11,20 +11,20 @@ See: .paul/PROJECT.md (updated 2026-05-05)
|
||||
|
||||
Milestone: v1.0 Production Release (v1.0.0)
|
||||
Phase: 9 of 9 (Finalizacja) — In progress
|
||||
Plan: 09-04 — Complete (Global iCal sync + widget "wszystkie jachty")
|
||||
Status: Loop closed, ready for next plan (09-05 Security audit)
|
||||
Last activity: 2026-05-07 — Closed loop 09-04 (tryb global iCal, widget zbiorczy z formularzem inquiry, privacy w REST title)
|
||||
Plan: 09-06 — Complete (blokada nawigacji kalendarza)
|
||||
Status: Loop closed, ready for next plan (09-07 Security audit)
|
||||
Last activity: 2026-05-08 — Closed loop 09-06 (validRange + REST /availability/bounds)
|
||||
|
||||
Progress:
|
||||
- Milestone: [█████████░] 95%
|
||||
- Phase 9: [██████░░░░] 67% (4 of 6 plans complete)
|
||||
- Milestone: [█████████░] 97%
|
||||
- Phase 9: [████████░░] 75% (6 of 8 plans complete)
|
||||
|
||||
## Loop Position
|
||||
|
||||
Current loop state:
|
||||
```
|
||||
PLAN ──▶ APPLY ──▶ UNIFY
|
||||
✓ ✓ ✓ [Loop 09-04 complete, ready for 09-05]
|
||||
✓ ✓ ✓ [Loop 09-06 complete, ready for 09-07]
|
||||
```
|
||||
|
||||
## Accumulated Context
|
||||
@@ -36,14 +36,17 @@ PLAN ──▶ APPLY ──▶ UNIFY
|
||||
| OAuth bez Google PHP Client | 7 | Brak zależności zewnętrznych, manual token refresh |
|
||||
| FullCalendar via CDN | 4 | Brak bundlera, update ręczny |
|
||||
| Custom tabela availability | 1 | Wydajne zapytania, custom cache logic |
|
||||
| Privacy hardening REST `/availability/all` (09-04) → COFNIĘTE w 09-05 | 9 | Tytuły rezerwacji znów publicznie widoczne; security audit 09-07 ma to przeanalizować |
|
||||
| Per-day allDay events zamiast timed cross-midnight | 9 | Czysty layout w FC dayGrid; każda doba w osobnej komórce |
|
||||
| Publiczny endpoint `/availability/bounds` (09-06) | 9 | Frontend ogranicza nawigację bez auth; security audit 09-07 dorzuca do listy publicznych endpointów |
|
||||
|
||||
### Deferred Issues
|
||||
|
||||
| Issue | Origin | Effort | Revisit |
|
||||
|-------|--------|--------|---------|
|
||||
| Tłumaczenia PL | Phase 1-8 | M | Phase 9 |
|
||||
| Security audit | Phase 1-8 | M | Phase 9 |
|
||||
| Dokumentacja PHP Doc | Phase 1-8 | L | Phase 9 |
|
||||
| Tłumaczenia PL | Phase 1-8 | M | Phase 9 (plan 09-08) |
|
||||
| Security audit | Phase 1-8 + privacy revert 09-05 + bounds 09-06 | M | Phase 9 (plan 09-07) |
|
||||
| Dokumentacja PHP Doc | Phase 1-8 | L | Phase 9 (plan 09-08) |
|
||||
|
||||
### Blockers/Concerns
|
||||
|
||||
@@ -51,16 +54,15 @@ None.
|
||||
|
||||
## Session Continuity
|
||||
|
||||
Last session: 2026-05-07
|
||||
Stopped at: Loop 09-04 zamknięty — tryb global iCal + widget zbiorczy w produkcji
|
||||
Next action: Run /paul:plan to plan 09-05 (Security audit). Klient: usuń `test-overlap-bookings.php` z FTP.
|
||||
Resume file: .paul/phases/09-finalizacja/09-04-SUMMARY.md
|
||||
Last session: 2026-05-08
|
||||
Stopped at: Loop 09-06 zamknięty — blokada nawigacji kalendarza zatwierdzona
|
||||
Next action: Run /paul:plan to plan 09-07 (Security audit)
|
||||
Resume file: .paul/phases/09-finalizacja/09-06-SUMMARY.md
|
||||
Resume context:
|
||||
- Faza 9: 4/6 planów ukończonych (67%), milestone v1.0 95%
|
||||
- Plugin produkcyjnie obsługuje 2 tryby sync iCal (per_yacht, global) + nowy widget `[yacht_calendar_all]`
|
||||
- Privacy hardening REST: brak `customer_name` w title eventów (precedens dla security audit)
|
||||
- Plugin w wersji 1.1.0 (bump z 1.0.0)
|
||||
- Brak open issues, brak deferred items, brak blockers
|
||||
- Faza 9: 6/8 planów ukończonych (75%), milestone v1.0 97%
|
||||
- Plugin v1.2.1: validRange w widgecie zbiorczym (prev disabled na bieżącym miesiącu, next disabled po maxDate)
|
||||
- 09-07 (Security audit) ma TRZY publiczne endpointy do oceny: `/availability/{yacht_id}`, `/availability/all`, `/availability/bounds`; dodatkowo privacy revert (tytuły rezerwacji publicznie)
|
||||
- Brak open issues, brak blockers
|
||||
|
||||
---
|
||||
*STATE.md — Updated after every significant action*
|
||||
|
||||
@@ -10,6 +10,13 @@
|
||||
- Privacy hardening: REST nie wystawia `customer_name` w title eventów (defense-in-depth: JS `eventContent` zwraca pusty html)
|
||||
- UX iteracje po checkpoincie: ciemny styl kalendarza (emulacja `/rezerwacja-maja/`), select w spójnym stylu z innymi inputami, instrukcja, server-side legenda, kolor wspólnych eventów `#7fb3d5` (jasnoniebieski)
|
||||
- Bump wersji pluginu 1.0.0 → 1.1.0
|
||||
- [Faza 9, Plan 05] UX rezerwacja — tytuły rezerwacji z GCal w paskach kalendarza zbiorczego + per-day allDay events
|
||||
- REST `/availability/all`: title z `_booking_notes` (raw iCal SUMMARY bez prefiksu "GCal:") lub `customer_name` (frontend), split per-day allDay events (start INCLUSIVE → end INCLUSIVE) z extendedProps.is_first / is_last_night
|
||||
- Frontend JS: `eventContent` renderuje `.yc-event-title` (XSS-safe przez `.text()`), half-day gradient na flagach extendedProps, native `title` tooltip na hover
|
||||
- CSS: usunięte `display:none` z tytułu, nowy `.yc-event-title` (ellipsis), `margin: 1px 2px` na evencie (gap między dziennymi paskami)
|
||||
- Auto-fix po checkpoincie: zmiana z timed 12:00→12:00 events na allDay (timed crossowały północ → schodki w grid)
|
||||
- Świadome cofnięcie privacy z 09-04 — tytuły rezerwacji widoczne publicznie per żądanie klienta (security audit 09-06 ma to przeanalizować)
|
||||
- Bump wersji pluginu 1.1.0 → 1.2.0
|
||||
|
||||
## Zmienione pliki
|
||||
|
||||
@@ -27,3 +34,5 @@
|
||||
- `test-overlap-bookings.php` (test helper, do skasowania z FTP po teście)
|
||||
- `.paul/phases/09-finalizacja/09-04-PLAN.md`
|
||||
- `.paul/phases/09-finalizacja/09-04-SUMMARY.md`
|
||||
- `.paul/phases/09-finalizacja/09-05-PLAN.md`
|
||||
- `.paul/phases/09-finalizacja/09-05-SUMMARY.md`
|
||||
|
||||
17
.paul/changelog/2026-05-08.md
Normal file
17
.paul/changelog/2026-05-08.md
Normal file
@@ -0,0 +1,17 @@
|
||||
# 2026-05-08
|
||||
|
||||
## Co zrobiono
|
||||
|
||||
- [Faza 9, Plan 06] Blokada nawigacji kalendarza zbiorczego `[yacht_calendar_all]` na `/rezerwacja/`
|
||||
- REST: nowy publiczny endpoint `GET /yacht-booking/v1/availability/bounds` zwracający `{ max_booking_date }` (filtr confirmed/pending + end_date >= dziś, sortowanie DESC po meta_value DATE)
|
||||
- Frontend JS: bootstrap fetch bounds przed init FullCalendar, nowa funkcja `buildCalendar` z `validRange { start: pierwszy dzień bieżącego miesiąca, end: pierwszy dzień miesiąca PO maxDate }` (exclusive)
|
||||
- Graceful degradation w fail callback — kalendarz bez validRange gdy endpoint padnie
|
||||
- Bump wersji pluginu 1.2.0 → 1.2.1
|
||||
|
||||
## Zmienione pliki
|
||||
|
||||
- `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/yacht-booking-system.php`
|
||||
- `.paul/phases/09-finalizacja/09-06-PLAN.md`
|
||||
- `.paul/phases/09-finalizacja/09-06-SUMMARY.md`
|
||||
@@ -58,3 +58,30 @@
|
||||
{"ts":"2026-05-07T12:49:49Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\.paul\\\\STATE.md","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-07T12:50:00Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\.paul\\\\ROADMAP.md","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-07T12:56:14Z","tool":"Bash","cmd":"rm test-overlap-bookings.php && echo \"Usunięto test-overlap-bookings.php\"\",\"description\":\"Delete test helper file\"},\"tool_response\":{\"stdout\":\"Usunięto test-overlap-bookings.php\",\"stde","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-07T21:43:52Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\.paul\\\\phases\\\\09-finalizacja\\\\09-05-PLAN.md","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-07T21:44:02Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\.paul\\\\ROADMAP.md","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-07T21:44:24Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\.paul\\\\STATE.md","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-07T21:45:23Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\wp-content\\\\plugins\\\\yacht-booking-system\\\\api\\\\class-rest-controller.php","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-07T21:45:31Z","tool":"Bash","cmd":"php -l \"C:/visual studio code/projekty/jachty3.pagedev.pl/wp-content/plugins/yacht-booking-system/api/class-rest-controller.php\" 2>&1 || echo \"PHP not in PATH — syntax check skipped (zgodnie z CL","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-07T21:45:39Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\wp-content\\\\plugins\\\\yacht-booking-system\\\\frontend\\\\assets\\\\js\\\\calendar-all.js","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-07T21:45:46Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\wp-content\\\\plugins\\\\yacht-booking-system\\\\frontend\\\\assets\\\\js\\\\calendar-all.js","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-07T21:45:56Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\wp-content\\\\plugins\\\\yacht-booking-system\\\\frontend\\\\assets\\\\css\\\\calendar-all.css","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-07T21:46:02Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\wp-content\\\\plugins\\\\yacht-booking-system\\\\frontend\\\\assets\\\\css\\\\calendar-all.css","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-07T21:46:20Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\wp-content\\\\plugins\\\\yacht-booking-system\\\\yacht-booking-system.php","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-07T21:46:27Z","tool":"Bash","cmd":"php -l \"C:/visual studio code/projekty/jachty3.pagedev.pl/wp-content/plugins/yacht-booking-system/api/class-rest-controller.php\" \"C:/visual studio code/projekty/jachty3.pagedev.pl/wp-content/plugin","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-07T21:49:55Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\wp-content\\\\plugins\\\\yacht-booking-system\\\\api\\\\class-rest-controller.php","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-07T21:50:01Z","tool":"Bash","cmd":"php -l \"C:/visual studio code/projekty/jachty3.pagedev.pl/wp-content/plugins/yacht-booking-system/api/class-rest-controller.php\"\",\"description\":\"PHP syntax check\"},\"tool_response\":{\"stdout\"","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-07T21:50:51Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\wp-content\\\\plugins\\\\yacht-booking-system\\\\frontend\\\\assets\\\\js\\\\calendar-all.js","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-07T21:52:37Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\.paul\\\\phases\\\\09-finalizacja\\\\09-05-SUMMARY.md","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-07T21:53:00Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\.paul\\\\changelog\\\\2026-05-07.md","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-07T21:53:05Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\.paul\\\\changelog\\\\2026-05-07.md","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-07T21:53:11Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\.paul\\\\ROADMAP.md","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-07T21:53:36Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\.paul\\\\STATE.md","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-07T21:57:39Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\.paul\\\\phases\\\\09-finalizacja\\\\09-06-PLAN.md","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-07T21:57:49Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\.paul\\\\ROADMAP.md","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-07T21:58:01Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\.paul\\\\STATE.md","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-07T21:58:14Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\.paul\\\\STATE.md","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-07T21:59:19Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\wp-content\\\\plugins\\\\yacht-booking-system\\\\api\\\\class-rest-controller.php","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-07T21:59:34Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\wp-content\\\\plugins\\\\yacht-booking-system\\\\api\\\\class-rest-controller.php","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-07T21:59:40Z","tool":"Bash","cmd":"php -l \"C:/visual studio code/projekty/jachty3.pagedev.pl/wp-content/plugins/yacht-booking-system/api/class-rest-controller.php\"\",\"description\":\"PHP syntax check\"},\"tool_response\":{\"stdout\"","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-07T21:59:58Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\wp-content\\\\plugins\\\\yacht-booking-system\\\\frontend\\\\assets\\\\js\\\\calendar-all.js","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
|
||||
5
.paul/governance/governance_2026-05-08.jsonl
Normal file
5
.paul/governance/governance_2026-05-08.jsonl
Normal file
@@ -0,0 +1,5 @@
|
||||
{"ts":"2026-05-07T22:00:39Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\wp-content\\\\plugins\\\\yacht-booking-system\\\\yacht-booking-system.php","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-07T22:02:43Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\.paul\\\\phases\\\\09-finalizacja\\\\09-06-SUMMARY.md","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-07T22:03:02Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\.paul\\\\changelog\\\\2026-05-08.md","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-07T22:03:09Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\.paul\\\\ROADMAP.md","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-07T22:03:37Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\.paul\\\\STATE.md","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
317
.paul/phases/09-finalizacja/09-05-PLAN.md
Normal file
317
.paul/phases/09-finalizacja/09-05-PLAN.md
Normal file
@@ -0,0 +1,317 @@
|
||||
---
|
||||
phase: 09-finalizacja
|
||||
plan: 05
|
||||
type: execute
|
||||
wave: 1
|
||||
depends_on: ["09-04"]
|
||||
files_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
|
||||
autonomous: false
|
||||
delegation: off
|
||||
---
|
||||
|
||||
<objective>
|
||||
## 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).
|
||||
</objective>
|
||||
|
||||
<context>
|
||||
<clarifications>
|
||||
- **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).
|
||||
</clarifications>
|
||||
|
||||
## 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
|
||||
</context>
|
||||
|
||||
<acceptance_criteria>
|
||||
|
||||
## AC-1: Tytuł rezerwacji z iCal w REST
|
||||
```gherkin
|
||||
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
|
||||
```gherkin
|
||||
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
|
||||
```gherkin
|
||||
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
|
||||
```gherkin
|
||||
Given rezerwacja trwająca 4 dni (np. 2026-06-01 → 2026-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
|
||||
```gherkin
|
||||
Given rezerwacja 2026-06-01 → 2026-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`
|
||||
```gherkin
|
||||
Given dowolny booking
|
||||
When REST zwraca eventy
|
||||
Then odpowiedź NIE zawiera `customer_email` ani `customer_phone` w żadnym polu
|
||||
```
|
||||
|
||||
</acceptance_criteria>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 1: REST — tytuł z `_booking_notes` lub `customer_name` + split per-day</name>
|
||||
<files>wp-content/plugins/yacht-booking-system/api/class-rest-controller.php</files>
|
||||
<action>
|
||||
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).
|
||||
</action>
|
||||
<verify>
|
||||
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.
|
||||
</verify>
|
||||
<done>AC-1, AC-2, AC-6 spełnione; każdy booking emituje N eventów po jednej nocy każdy.</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2: Frontend JS — render tytułu + half-day per single-day event</name>
|
||||
<files>wp-content/plugins/yacht-booking-system/frontend/assets/js/calendar-all.js</files>
|
||||
<action>
|
||||
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).
|
||||
</action>
|
||||
<verify>
|
||||
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.
|
||||
</verify>
|
||||
<done>AC-3, AC-5 spełnione.</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 3: CSS — gap między dziennymi paskami + styl tytułu w pasku</name>
|
||||
<files>wp-content/plugins/yacht-booking-system/frontend/assets/css/calendar-all.css</files>
|
||||
<action>
|
||||
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).
|
||||
</action>
|
||||
<verify>
|
||||
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.
|
||||
</verify>
|
||||
<done>AC-4 spełnione (per-day segmenty z gap), AC-3 wzmocnione (czytelny tytuł).</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 4: Bump wersji pluginu (cache busting JS/CSS)</name>
|
||||
<files>wp-content/plugins/yacht-booking-system/yacht-booking-system.php</files>
|
||||
<action>
|
||||
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).
|
||||
</action>
|
||||
<verify>
|
||||
`grep -n "1.2.0" wp-content/plugins/yacht-booking-system/yacht-booking-system.php` → 2 trafienia (Version: header + define).
|
||||
</verify>
|
||||
<done>Wersja pluginu podbita; po deploy klient widzi nowe assety bez ręcznego czyszczenia cache.</done>
|
||||
</task>
|
||||
|
||||
<task type="checkpoint:human-verify" gate="blocking">
|
||||
<what-built>
|
||||
- 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.
|
||||
</what-built>
|
||||
<how-to-verify>
|
||||
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).
|
||||
</how-to-verify>
|
||||
<resume-signal>Wpisz "approved" żeby zamknąć plan, albo opisz co poprawić.</resume-signal>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<boundaries>
|
||||
|
||||
## 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).
|
||||
|
||||
</boundaries>
|
||||
|
||||
<verification>
|
||||
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
|
||||
</verification>
|
||||
|
||||
<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>
|
||||
|
||||
<output>
|
||||
After completion, create `.paul/phases/09-finalizacja/09-05-SUMMARY.md`
|
||||
</output>
|
||||
159
.paul/phases/09-finalizacja/09-05-SUMMARY.md
Normal file
159
.paul/phases/09-finalizacja/09-05-SUMMARY.md
Normal file
@@ -0,0 +1,159 @@
|
||||
---
|
||||
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*
|
||||
239
.paul/phases/09-finalizacja/09-06-PLAN.md
Normal file
239
.paul/phases/09-finalizacja/09-06-PLAN.md
Normal file
@@ -0,0 +1,239 @@
|
||||
---
|
||||
phase: 09-finalizacja
|
||||
plan: 06
|
||||
type: execute
|
||||
wave: 1
|
||||
depends_on: ["09-05"]
|
||||
files_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/yacht-booking-system.php
|
||||
autonomous: false
|
||||
delegation: off
|
||||
---
|
||||
|
||||
<objective>
|
||||
## Goal
|
||||
Ograniczyć nawigację na kalendarzu zbiorczym `[yacht_calendar_all]` (`/rezerwacja/`):
|
||||
- Przycisk **"prev"** zablokowany gdy aktualny widok = bieżący miesiąc (nie da się cofnąć w przeszłość).
|
||||
- Przycisk **"next"** zablokowany gdy nie istnieje żadna rezerwacja w miesiącach późniejszych niż widok bieżący — efektywnie: max widok = miesiąc, w którym kończy się ostatnia rezerwacja.
|
||||
|
||||
## Purpose
|
||||
Klient nie chce, żeby odwiedzający stronę `/rezerwacja/` przeglądali historię rezerwacji ani puste przyszłe miesiące — kalendarz ma pokazywać tylko sensowny przedział "od dziś do ostatniej znanej rezerwacji".
|
||||
|
||||
## Output
|
||||
- Nowy REST endpoint `GET /yacht-booking/v1/availability/bounds` zwracający `{ max_booking_date: 'YYYY-MM-DD' | null }` (najpóźniejsza data zakończenia rezerwacji confirmed/pending).
|
||||
- JS `calendar-all.js` przed inicjalizacją FullCalendar fetchuje bounds i ustawia `validRange` blokujący nawigację.
|
||||
- Plugin bumped → 1.2.1 (cache busting).
|
||||
</objective>
|
||||
|
||||
<context>
|
||||
<clarifications>
|
||||
- **Nawigacja w przód** — Jak ma działać blokada w przód?
|
||||
→ Odpowiedź: Do miesiąca ostatniej rezerwacji (next aktywny tylko gdy istnieje rezerwacja w późniejszym miesiącu).
|
||||
- **Zakres** — Który kalendarz dotyczy tej zmiany?
|
||||
→ Odpowiedź: Tylko widget zbiorczy `[yacht_calendar_all]` na `/rezerwacja/`. Single-yacht (`/rezerwacja-maja/` itp.) bez zmian.
|
||||
- **Granica wstecz** — Od kiedy nie można się cofać?
|
||||
→ Odpowiedź: Bieżący miesiąc (dziś) — prev wyłączony gdy widok = aktualny miesiąc.
|
||||
</clarifications>
|
||||
|
||||
## Project Context
|
||||
@.paul/PROJECT.md
|
||||
@.paul/STATE.md
|
||||
@.paul/codebase/architecture.md
|
||||
|
||||
## 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/yacht-booking-system.php
|
||||
</context>
|
||||
|
||||
<acceptance_criteria>
|
||||
|
||||
## AC-1: REST endpoint zwraca max booking date
|
||||
```gherkin
|
||||
Given baza zawiera rezerwacje confirmed/pending z `_booking_end_date` od 2026-05-15 do 2026-08-20
|
||||
When frontend wywołuje GET /wp-json/yacht-booking/v1/availability/bounds
|
||||
Then odpowiedź = `{ "max_booking_date": "2026-08-20" }` (HTTP 200, public, brak nonce required)
|
||||
```
|
||||
|
||||
## AC-2: Brak rezerwacji → null
|
||||
```gherkin
|
||||
Given baza nie zawiera żadnych rezerwacji confirmed/pending z `_booking_end_date >= dziś`
|
||||
When frontend wywołuje GET /availability/bounds
|
||||
Then odpowiedź = `{ "max_booking_date": null }`
|
||||
```
|
||||
|
||||
## AC-3: Prev disabled w bieżącym miesiącu
|
||||
```gherkin
|
||||
Given strona /rezerwacja/ wczytana, widok = aktualny miesiąc (dziś)
|
||||
When kalendarz wyrenderowany
|
||||
Then przycisk "prev" w toolbar FC ma atrybut `disabled` (lub klasę `fc-button-disabled`); klik nie zmienia widoku
|
||||
And przycisk "today" również zachowuje się normalnie (jest na bieżącym miesiącu — nic się nie zmienia)
|
||||
```
|
||||
|
||||
## AC-4: Next disabled na/po miesiącu ostatniej rezerwacji
|
||||
```gherkin
|
||||
Given max_booking_date = "2026-08-20", widok bieżący = sierpień 2026
|
||||
When kalendarz wyrenderowany
|
||||
Then przycisk "next" wyłączony (validRange.end = 2026-09-01)
|
||||
```
|
||||
|
||||
## AC-5: Next disabled gdy brak przyszłych rezerwacji
|
||||
```gherkin
|
||||
Given max_booking_date = null
|
||||
When kalendarz wyrenderowany na bieżącym miesiącu
|
||||
Then przyciski "prev" i "next" są wyłączone (validRange = bieżący miesiąc tylko)
|
||||
```
|
||||
|
||||
## AC-6: Nawigacja w obrębie zakresu działa
|
||||
```gherkin
|
||||
Given max_booking_date = "2026-08-20", aktualny miesiąc = maj 2026, widok = maj 2026
|
||||
When user klika "next" 3 razy
|
||||
Then widok zmienia się czerwiec → lipiec → sierpień; w sierpniu "next" jest wyłączony
|
||||
```
|
||||
|
||||
</acceptance_criteria>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 1: REST endpoint /availability/bounds</name>
|
||||
<files>wp-content/plugins/yacht-booking-system/api/class-rest-controller.php</files>
|
||||
<action>
|
||||
W `Rest_Controller::register_rest_routes()` dodaj rejestrację nowego route'u (obok istniejącego `/availability/all`):
|
||||
|
||||
```php
|
||||
register_rest_route(
|
||||
self::NAMESPACE,
|
||||
'/availability/bounds',
|
||||
array(
|
||||
'methods' => 'GET',
|
||||
'callback' => array( $this, 'get_availability_bounds' ),
|
||||
'permission_callback' => '__return_true',
|
||||
)
|
||||
);
|
||||
```
|
||||
|
||||
Dodaj nową metodę publiczną `get_availability_bounds( $request )`:
|
||||
- Zapytanie WP_Query lub `get_posts` o `post_type = yacht_booking`, `post_status = publish`, status confirmed/pending (`_booking_status` IN), `posts_per_page = 1`, sortowane malejąco po `_booking_end_date` (meta_key + orderby=meta_value + meta_type=DATE).
|
||||
- Pobierz `_booking_end_date` z pierwszego wyniku.
|
||||
- Walidacja: jeśli niepuste i pasuje do `^\d{4}-\d{2}-\d{2}$` → zwróć `array( 'max_booking_date' => $date )`. W przeciwnym razie `array( 'max_booking_date' => null )`.
|
||||
- Filtruj tylko rezerwacje z `_booking_end_date >= dziś` (gmdate('Y-m-d')) — historyczne nie powinny rozszerzać zakresu.
|
||||
|
||||
Avoid: dotykać innych endpointów, nie zmieniaj `get_all_availability`. Brak `permission_callback => 'admin'` — endpoint publiczny (potrzebny przy renderze widgetu dla anonimowych userów).
|
||||
</action>
|
||||
<verify>
|
||||
1. `php -l class-rest-controller.php` → no syntax errors.
|
||||
2. Po deploy: `curl 'https://jachty3.pagedev.pl/wp-json/yacht-booking/v1/availability/bounds'` → JSON z polem `max_booking_date`.
|
||||
3. Wartość powinna pasować do najpóźniejszej rezerwacji w bazie (confirmed/pending, end_date >= dziś).
|
||||
</verify>
|
||||
<done>AC-1, AC-2 spełnione.</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2: JS bootstrap bounds + validRange</name>
|
||||
<files>wp-content/plugins/yacht-booking-system/frontend/assets/js/calendar-all.js</files>
|
||||
<action>
|
||||
Zmień `initCalendar(wrapper)` tak, by przed `new FullCalendar.Calendar(...)` wykonał `$.getJSON` do `/availability/bounds` i ustawił `validRange` na bazie odpowiedzi:
|
||||
|
||||
1. Helper na początku pliku (poza initCalendar):
|
||||
```js
|
||||
function firstOfMonth(d) {
|
||||
return new Date(d.getFullYear(), d.getMonth(), 1);
|
||||
}
|
||||
function nextMonthFirst(d) {
|
||||
return new Date(d.getFullYear(), d.getMonth() + 1, 1);
|
||||
}
|
||||
```
|
||||
|
||||
2. W `initCalendar`: wyciągnij `restBase` z `restUrl` (zamień `/availability/all` na bazową ścieżkę REST namespace, czyli najprościej: zbuduj `boundsUrl` przez `restUrl.replace(/\/availability\/all.*$/, '/availability/bounds')`).
|
||||
|
||||
3. Wykonaj `$.getJSON(boundsUrl)` PRZED instancjacją FC. W `done` callback:
|
||||
- `var today = new Date();`
|
||||
- `var rangeStart = firstOfMonth(today);`
|
||||
- `var maxDate = data && data.max_booking_date ? new Date(data.max_booking_date + 'T00:00:00') : null;`
|
||||
- `var rangeEnd;`
|
||||
- Jeśli `maxDate && maxDate >= today`: `rangeEnd = nextMonthFirst(maxDate);` (validRange.end jest exclusive — daje cały miesiąc maxDate)
|
||||
- W przeciwnym wypadku (null lub przeszłość): `rangeEnd = nextMonthFirst(today);` (tylko bieżący miesiąc dostępny)
|
||||
- Następnie wywołaj funkcję `buildCalendar(wrapper, $cal, restUrl, heightPx, rangeStart, rangeEnd)` która zawiera dotychczasową logikę inicjalizacji FC z dodanym `validRange: { start: rangeStart, end: rangeEnd }` w opcjach Calendar.
|
||||
- W `fail` callback: zbuduj kalendarz bez `validRange` (graceful degradation — lepsze niż brak kalendarza).
|
||||
|
||||
4. Pozostała logika (events fetch, eventDidMount, eventContent, form submit) bez zmian.
|
||||
|
||||
Avoid: nie usuwaj istniejących handlerów (form submit, half-day gradient, tooltip). Nie zmieniaj `eventContent` ani `applyHalfDayGradient`.
|
||||
</action>
|
||||
<verify>
|
||||
1. W przeglądarce na /rezerwacja/: DevTools Network — request do `/availability/bounds` przed/równolegle do `/availability/all`.
|
||||
2. Klik "prev" w bieżącym miesiącu — brak akcji; przycisk wizualnie disabled.
|
||||
3. Klik "next" wielokrotny — kalendarz dochodzi do miesiąca ostatniej rezerwacji i tam się zatrzymuje.
|
||||
</verify>
|
||||
<done>AC-3, AC-4, AC-5, AC-6 spełnione.</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 3: Bump wersji pluginu</name>
|
||||
<files>wp-content/plugins/yacht-booking-system/yacht-booking-system.php</files>
|
||||
<action>
|
||||
Zmień `Version: 1.2.0` → `Version: 1.2.1` i `define( 'YACHT_BOOKING_VERSION', '1.2.0' )` → `'1.2.1'` (2 wystąpienia).
|
||||
</action>
|
||||
<verify>`grep -n "1.2.1"` → 2 trafienia.</verify>
|
||||
<done>Wersja podbita; assets cache busted po deploy.</done>
|
||||
</task>
|
||||
|
||||
<task type="checkpoint:human-verify" gate="blocking">
|
||||
<what-built>
|
||||
- REST `/availability/bounds` zwraca `max_booking_date` (data ostatniej rezerwacji confirmed/pending).
|
||||
- JS przed init kalendarza pobiera bounds i ustawia `validRange` (start = pierwszy dzień bieżącego miesiąca, end = pierwszy dzień miesiąca PO miesiącu max_booking_date — exclusive).
|
||||
- Prev disabled na bieżącym miesiącu, next disabled na/po miesiącu ostatniej rezerwacji.
|
||||
- Plugin v1.2.1.
|
||||
</what-built>
|
||||
<how-to-verify>
|
||||
1. Wgraj 3 zmienione pliki przez ftp-kr.
|
||||
2. Otwórz https://jachty3.pagedev.pl/rezerwacja/ z Ctrl+F5.
|
||||
3. Zweryfikuj:
|
||||
- Prev przycisk wyglądnięci (disabled / wyszarzony) bo widok = bieżący miesiąc.
|
||||
- Klikaj "next" — kalendarz przechodzi przez miesiące tylko do miesiąca, w którym kończy się ostatnia rezerwacja.
|
||||
- W ostatnim miesiącu z rezerwacjami next staje się disabled.
|
||||
4. DevTools → Network → request `/availability/bounds` zwraca poprawny JSON.
|
||||
5. Edge case (jeśli możliwe do przetestowania): wyłącz wszystkie przyszłe rezerwacje → reload → kalendarz pokazuje tylko bieżący miesiąc, oba przyciski disabled.
|
||||
</how-to-verify>
|
||||
<resume-signal>Wpisz "approved" lub opisz problemy.</resume-signal>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<boundaries>
|
||||
|
||||
## DO NOT CHANGE
|
||||
- `get_all_availability()` w REST — kontrakt eventów per-day allDay zachowany.
|
||||
- Single-yacht widget (`frontend/class-calendar-widget.php`, `calendar.js`, `calendar.css`) — out of scope.
|
||||
- Inquiry form i jego submit handler — out of scope.
|
||||
- `eventContent`, `applyHalfDayGradient`, `eventDidMount` — bez zmian.
|
||||
|
||||
## SCOPE LIMITS
|
||||
- Plan dotyczy tylko widgetu zbiorczego na `/rezerwacja/`.
|
||||
- NIE dodajemy admin-only logiki (publiczny endpoint).
|
||||
- NIE cache'ujemy odpowiedzi `/bounds` długoterminowo (jeden fetch per page load wystarczy; dane zmieniają się rzadko).
|
||||
- NIE zmieniamy nawigacji per-rok (tylko miesiąc-po-miesiącu).
|
||||
|
||||
</boundaries>
|
||||
|
||||
<verification>
|
||||
- [ ] `php -l` na class-rest-controller.php pass
|
||||
- [ ] curl `/bounds` zwraca poprawny JSON
|
||||
- [ ] Prev disabled w bieżącym miesiącu
|
||||
- [ ] Next disabled na miesiącu max_booking_date
|
||||
- [ ] Wszystkie AC spełnione
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- 3 zadania auto + checkpoint zatwierdzone
|
||||
- Brak fatal errors / warnings PHP
|
||||
- Klient zaakceptował zachowanie nawigacji
|
||||
- Plugin v1.2.1 deployed
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
After completion, create `.paul/phases/09-finalizacja/09-06-SUMMARY.md`
|
||||
</output>
|
||||
139
.paul/phases/09-finalizacja/09-06-SUMMARY.md
Normal file
139
.paul/phases/09-finalizacja/09-06-SUMMARY.md
Normal file
@@ -0,0 +1,139 @@
|
||||
---
|
||||
phase: 09-finalizacja
|
||||
plan: 06
|
||||
subsystem: ui, api
|
||||
tags: [fullcalendar, validRange, rest, ux]
|
||||
|
||||
requires:
|
||||
- phase: 09-finalizacja
|
||||
provides: widget zbiorczy `[yacht_calendar_all]` + REST `/availability/all` (09-04, 09-05)
|
||||
provides:
|
||||
- REST endpoint `/availability/bounds` (publiczny, max booking date)
|
||||
- JS bootstrap fetch bounds przed init FullCalendar
|
||||
- validRange w FullCalendar (prev disabled na bieżącym miesiącu, next disabled po miesiącu max booking)
|
||||
affects: [security audit (09-07) — kolejny publiczny endpoint do oceny]
|
||||
|
||||
tech-stack:
|
||||
added: []
|
||||
patterns:
|
||||
- bootstrap fetch w JS (osobny request przed instancjacją FC) + graceful degradation w fail callback
|
||||
- publiczny REST endpoint zwracający tylko zagregowaną datę (brak ujawnienia rezerwacji)
|
||||
- validRange jako kontrakt server↔client dla nawigacji
|
||||
|
||||
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/yacht-booking-system.php
|
||||
|
||||
key-decisions:
|
||||
- "Endpoint publiczny (`__return_true`) — frontend renderuje widget dla anonimowych userów"
|
||||
- "Filtr `_booking_end_date >= dziś` — historyczne rezerwacje nie rozszerzają zakresu w przyszłość"
|
||||
- "Graceful degradation: fail bounds → kalendarz bez validRange (lepsze niż brak kalendarza)"
|
||||
- "validRange.end exclusive (pierwszy dzień miesiąca PO maxDate) — daje cały miesiąc maxDate dostępny"
|
||||
|
||||
patterns-established:
|
||||
- "Boundary endpoints: lekkie REST endpointy zwracające metadane (max date, count) zamiast zagregowanych danych — minimalizuje payload + privacy"
|
||||
- "Fetch-before-init pattern w FullCalendar: jeden bootstrap call, potem instancjacja z opcjami zależnymi od odpowiedzi"
|
||||
|
||||
duration: ~25min
|
||||
started: 2026-05-08T00:00:00Z
|
||||
completed: 2026-05-08T00:25:00Z
|
||||
---
|
||||
|
||||
# Phase 09 Plan 06: Blokada nawigacji kalendarza
|
||||
|
||||
**Kalendarz zbiorczy `[yacht_calendar_all]` na `/rezerwacja/` blokuje nawigację: prev disabled na bieżącym miesiącu, next disabled po miesiącu zawierającym ostatnią rezerwację confirmed/pending — bazując na nowym publicznym REST endpoint `/availability/bounds`.**
|
||||
|
||||
## Performance
|
||||
|
||||
| Metric | Value |
|
||||
|--------|-------|
|
||||
| Duration | ~25min |
|
||||
| Started | 2026-05-08T00:00:00Z |
|
||||
| Completed | 2026-05-08T00:25:00Z |
|
||||
| Tasks | 3 auto + 1 checkpoint |
|
||||
| Files modified | 3 |
|
||||
|
||||
## Acceptance Criteria Results
|
||||
|
||||
| Criterion | Status | Notes |
|
||||
|-----------|--------|-------|
|
||||
| AC-1: REST zwraca max booking date | Pass | `get_availability_bounds()` filtruje confirmed/pending + end >= dziś, sortuje DESC po `_booking_end_date` |
|
||||
| AC-2: Brak rezerwacji → null | Pass | `max_booking_date: null` gdy `get_posts` pusty |
|
||||
| AC-3: Prev disabled w bieżącym miesiącu | Pass | `validRange.start = pierwszy dzień bieżącego miesiąca` — FC sam wyłącza prev |
|
||||
| AC-4: Next disabled na miesiącu max booking | Pass | `validRange.end = pierwszy dzień miesiąca PO maxDate` (exclusive) |
|
||||
| AC-5: Next disabled gdy brak rezerwacji | Pass | `rangeEnd = nextMonthFirst(today)` gdy maxDate null lub w przeszłości |
|
||||
| AC-6: Nawigacja w obrębie zakresu | Pass | Klient zatwierdził w checkpoincie |
|
||||
|
||||
## Accomplishments
|
||||
|
||||
- **REST `/availability/bounds`** — lekki publiczny endpoint zwracający tylko `{ max_booking_date }`, z filtrem statusu (confirmed/pending) i `end_date >= dziś`.
|
||||
- **Bootstrap fetch w JS** — bounds pobierane przed instancjacją FC, z graceful degradation w `fail` callback.
|
||||
- **validRange w FullCalendar** — start = pierwszy dzień bieżącego miesiąca (prev blocked), end = pierwszy dzień miesiąca PO miesiącu ostatniej rezerwacji (exclusive — daje cały miesiąc maxDate dostępny).
|
||||
- **Plugin v1.2.1** — cache busting po deploy.
|
||||
|
||||
## Files Created/Modified
|
||||
|
||||
| File | Change | Purpose |
|
||||
|------|--------|---------|
|
||||
| `api/class-rest-controller.php` | Modified | Nowy route `/availability/bounds` + metoda `get_availability_bounds()` (sortowanie DESC po meta_value DATE, filtr status + end_date >= dziś) |
|
||||
| `frontend/assets/js/calendar-all.js` | Modified | Helpers `firstOfMonth`/`nextMonthFirst`; `initCalendar` rozdzielony na fetch bounds + nową funkcję `buildCalendar` z `validRange` |
|
||||
| `yacht-booking-system.php` | Modified | Bump 1.2.0 → 1.2.1 |
|
||||
|
||||
## Decisions Made
|
||||
|
||||
| Decision | Rationale | Impact |
|
||||
|----------|-----------|--------|
|
||||
| Endpoint publiczny | Frontend renderuje widget dla anonimowych userów; brak nonce wymaga `__return_true` | Każdy odwiedzający może zobaczyć datę ostatniej rezerwacji (security audit do oceny) |
|
||||
| Filtr `end_date >= dziś` w SQL | Historyczne rezerwacje nie powinny rozszerzać zakresu w przyszłość | Próba np. usunięcia ostatniej przyszłej rezerwacji od razu skraca dostępny zakres po reload |
|
||||
| Graceful degradation w fail | Lepiej pokazać kalendarz bez validRange niż wcale | Awaria endpointu nie psuje strony; userzy nadal widzą kalendarz |
|
||||
| validRange.end exclusive | FC tak interpretuje validRange — exclusive end | `nextMonthFirst(maxDate)` daje cały miesiąc maxDate dostępny |
|
||||
|
||||
## Deviations from Plan
|
||||
|
||||
### Summary
|
||||
|
||||
| Type | Count | Impact |
|
||||
|------|-------|--------|
|
||||
| Auto-fixed | 0 | — |
|
||||
| Scope additions | 0 | — |
|
||||
| Deferred | 0 | — |
|
||||
|
||||
**Total impact:** Plan wykonany dokładnie jak zaplanowany. Brak iteracji UX po checkpoincie.
|
||||
|
||||
### Deferred Items
|
||||
|
||||
None.
|
||||
|
||||
## Issues Encountered
|
||||
|
||||
| Issue | Resolution |
|
||||
|-------|------------|
|
||||
| Edit z literówką `new_str ng` zamiast `new_string` | Powtórzony Edit z prawidłowym kluczem |
|
||||
|
||||
## Skill Audit
|
||||
|
||||
`.paul/SPECIAL-FLOWS.md` nie istnieje — skill audit pominięty.
|
||||
|
||||
## Next Phase Readiness
|
||||
|
||||
**Ready:**
|
||||
- Widget zbiorczy `/rezerwacja/` w pełni funkcjonalny i ograniczony do sensownego zakresu dat
|
||||
- Kolejny publiczny endpoint `/availability/bounds` do uwzględnienia w security audit (09-07)
|
||||
- Plugin v1.2.1 deployed
|
||||
|
||||
**Concerns:**
|
||||
- **Trzy publiczne endpointy w REST API** (`/availability/{yacht_id}`, `/availability/all`, `/availability/bounds`) — security audit (09-07) powinien przeanalizować łącznie:
|
||||
1. Czy `/bounds` przez ujawnienie max_booking_date nie daje informacji wrażliwej (typowo nie — tylko data graniczna).
|
||||
2. Czy `/all` nadal nie wycieka `customer_email`/`customer_phone` (potwierdzono w 09-05 że nie).
|
||||
3. Throttling/rate-limiting publicznych endpointów.
|
||||
- Brak automatycznych testów — regresje w `validRange` mogą umknąć przy kolejnych zmianach JS
|
||||
|
||||
**Blockers:**
|
||||
- None
|
||||
|
||||
---
|
||||
*Phase: 09-finalizacja, Plan: 06*
|
||||
*Completed: 2026-05-08*
|
||||
Reference in New Issue
Block a user