This commit is contained in:
2026-05-10 15:08:40 +02:00
parent 7278a422af
commit 8d75a95e69
14 changed files with 1181 additions and 706 deletions

View File

@@ -50,8 +50,9 @@ Szczegóły w `wp-content/plugins/yacht-booking-system/PROJECT-STATUS.md`.
- [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
- [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
- [ ] 09-07: Kolory per jacht w kalendarzu zbiorczym (color picker w settings + dopasowanie po nazwie w tytule globalnych eventów GCal)
- [ ] 09-08: Security audit i poprawki
- [ ] 09-09: Testy + tłumaczenia + dokumentacja
---
*Roadmap created: 2026-05-05*

View File

@@ -10,21 +10,21 @@ See: .paul/PROJECT.md (updated 2026-05-05)
## Current Position
Milestone: v1.0 Production Release (v1.0.0)
Phase: 9 of 9 (Finalizacja) — In progress
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)
Phase: 9 of 9 (Finalizacja) — Planning
Plan: 09-07 created, awaiting approval (Kolory per jacht w kalendarzu zbiorczym)
Status: PLAN created, ready for APPLY
Last activity: 2026-05-10 — Created .paul/phases/09-finalizacja/09-07-PLAN.md
Progress:
- Milestone: [█████████░] 97%
- Phase 9: [████████░░] 75% (6 of 8 plans complete)
- Phase 9: [██████░░░░] 67% (6 of 9 plans complete — security i docs przesunięte na 09-08/09-09)
## Loop Position
Current loop state:
```
PLAN ──▶ APPLY ──▶ UNIFY
[Loop 09-06 complete, ready for 09-07]
[Plan 09-07 created, awaiting approval]
```
## Accumulated Context
@@ -44,9 +44,9 @@ PLAN ──▶ APPLY ──▶ UNIFY
| Issue | Origin | Effort | Revisit |
|-------|--------|--------|---------|
| 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) |
| Tłumaczenia PL | Phase 1-8 | M | Phase 9 (plan 09-09) |
| Security audit | Phase 1-8 + privacy revert 09-05 + bounds 09-06 | M | Phase 9 (plan 09-08) |
| Dokumentacja PHP Doc | Phase 1-8 | L | Phase 9 (plan 09-09) |
### Blockers/Concerns
@@ -54,14 +54,14 @@ None.
## Session Continuity
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
Last session: 2026-05-10
Stopped at: Plan 09-07 utworzony — kolory per jacht w kalendarzu zbiorczym
Next action: Review and approve plan, then run /paul:apply .paul/phases/09-finalizacja/09-07-PLAN.md
Resume file: .paul/phases/09-finalizacja/09-07-PLAN.md
Resume context:
- 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)
- Plan 09-07 wstawiony przed security audit (per żądaniu klienta — kolory w `/rezerwacja/`)
- Roadmap przesunięty: 09-07 kolory, 09-08 security, 09-09 docs
- 1 checkpoint human-verify na końcu (autonomous: false)
- Brak open issues, brak blockers
---

View File

@@ -0,0 +1,25 @@
{"ts":"2026-05-10T11:56:04Z","tool":"Bash","cmd":"mkdir -p \".paul/phases/09-finalizacja\"\",\"description\":\"Ensure phase dir exists\"},\"tool_response\":{\"stdout\":\"\",\"stderr\":\"\",\"interrupted\":false,\"isImage\":false,\"noOutputExpected\":","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
{"ts":"2026-05-10T11:57:43Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\.paul\\\\phases\\\\09-finalizacja\\\\09-07-PLAN.md","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
{"ts":"2026-05-10T11:57:51Z","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-10T11:57:58Z","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-10T11:58:02Z","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-10T11:58:10Z","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-10T12:02:43Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\wp-content\\\\plugins\\\\yacht-booking-system\\\\includes\\\\class-yacht.php","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
{"ts":"2026-05-10T12:02:53Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\wp-content\\\\plugins\\\\yacht-booking-system\\\\admin\\\\views\\\\yacht-edit.php","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
{"ts":"2026-05-10T12:02:57Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\wp-content\\\\plugins\\\\yacht-booking-system\\\\admin\\\\views\\\\yacht-edit.php","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
{"ts":"2026-05-10T12:03:12Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\wp-content\\\\plugins\\\\yacht-booking-system\\\\admin\\\\class-admin.php","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
{"ts":"2026-05-10T12:03:18Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\wp-content\\\\plugins\\\\yacht-booking-system\\\\includes\\\\class-yacht-booking.php","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
{"ts":"2026-05-10T12:03:25Z","tool":"Bash","cmd":"php -l \"wp-content/plugins/yacht-booking-system/includes/class-yacht.php\" && php -l \"wp-content/plugins/yacht-booking-system/admin/class-admin.php\" && php -l \"wp-content/plugins/yacht-booking-sys","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
{"ts":"2026-05-10T12:03:52Z","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-10T12:04:15Z","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-10T12:04:22Z","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-10T12:04:31Z","tool":"Bash","cmd":"php -l \"wp-content/plugins/yacht-booking-system/api/class-rest-controller.php\"\",\"description\":\"PHP lint Task 2\"},\"tool_response\":{\"stdout\":\"No syntax errors detected in wp-content/plugins/","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
{"ts":"2026-05-10T12:04:48Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\wp-content\\\\plugins\\\\yacht-booking-system\\\\frontend\\\\class-calendar-widget-all.php","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
{"ts":"2026-05-10T12:04:54Z","tool":"Bash","cmd":"php -l \"wp-content/plugins/yacht-booking-system/frontend/class-calendar-widget-all.php\"\",\"description\":\"PHP lint Task 3\"},\"tool_response\":{\"stdout\":\"No syntax errors detected in wp-content","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
{"ts":"2026-05-10T12:08:06Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\wp-content\\\\plugins\\\\yacht-booking-system\\\\frontend\\\\assets\\\\css\\\\calendar.scss","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
{"ts":"2026-05-10T12:08:16Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\wp-content\\\\plugins\\\\yacht-booking-system\\\\frontend\\\\assets\\\\css\\\\calendar.css","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
{"ts":"2026-05-10T12:52:44Z","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-10T12:52:45Z","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-10T12:54:48Z","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-10T13:01:31Z","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-10T13:01:34Z","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"}

View File

@@ -0,0 +1,225 @@
---
phase: 09-finalizacja
plan: 07
type: execute
wave: 1
depends_on: []
files_modified:
- wp-content/plugins/yacht-booking-system/includes/class-yacht.php
- wp-content/plugins/yacht-booking-system/admin/class-admin.php
- wp-content/plugins/yacht-booking-system/admin/views/yacht-edit.php
- wp-content/plugins/yacht-booking-system/api/class-rest-controller.php
- wp-content/plugins/yacht-booking-system/frontend/class-calendar-widget-all.php
- wp-content/plugins/yacht-booking-system/yacht-booking-system.php
- .paul/ROADMAP.md
autonomous: false
delegation: off
---
<objective>
## Goal
Dodać administracyjny wybór koloru per jacht (`_yacht_color`) i zastosować go w widgecie zbiorczym `/rezerwacja/`. Dla rezerwacji bez przypisanego `yacht_id` (eventy z globalnego Google Calendar, source=`ical_global_calendar`) dopasować jacht po występowaniu jego nazwy/aliasu w tytule rezerwacji (case-insensitive, w dowolnym miejscu) i pokolorować event kolorem tego jachtu.
## Purpose
Klient chce rozróżniać jachty wzrokiem w jednym wspólnym kalendarzu — obecnie kolory są przydzielane automatycznie z palety i nie ma kontroli nad wyborem; dodatkowo eventy z globalnego GCal lecą jednym kolorem `GLOBAL_EVENT_COLOR`, mimo że tytuł zawiera nazwę jachtu.
## Output
- Pole color picker w formularzu jachtu (`yacht-edit.php`) zapisywane do meta `_yacht_color`
- Backend `Rest_Controller::get_yacht_color_palette()` honoruje meta `_yacht_color` z fallbackiem na obecną paletę (deterministyczna paleta po `yacht_id` zachowana dla braków)
- `get_all_availability()` dla eventów `is_global_event === true` próbuje dopasować jacht po nazwie/aliasie w `post_title` rezerwacji i ustawia `color` + dodaje `yacht_id` do eventu (tylko do kolorystyki frontendu, bez zmiany danych w DB)
- Legenda widgetu zbiorczego pokazuje wybrane kolory
- ROADMAP zaktualizowany: 09-07 = ten plan, 09-08 = security audit, 09-09 = testy/tłumaczenia/dokumentacja
</objective>
<context>
<clarifications>
- **Numer planu** — Gdzie ten plan wpasować w roadmapę fazy 9?
→ Odpowiedź: 09-07 (przed security audit). Security przesuwamy na 09-08, dokumentacja na 09-09.
- **Fallback** — Jak ma działać brak ustawionego koloru?
→ Odpowiedź: Auto z palety (jak teraz, deterministycznie po `yacht_id`).
- **Zakres** — Czy kolor stosować również w widgecie pojedynczego jachtu?
→ Odpowiedź: Tylko zbiorczy (`/rezerwacja/`). Pojedynczy widget bez zmian.
- **Global iCal** — Czy eventy z yacht_id=0 mają korzystać z koloru jachtu?
→ Odpowiedź: Tak — nazwa jachtu może wystąpić w dowolnym miejscu w tytule rezerwacji (case-insensitive, substring). Najdłuższe trafienie wygrywa, brak trafienia → `GLOBAL_EVENT_COLOR`.
</clarifications>
## Project Context
@.paul/PROJECT.md
@.paul/ROADMAP.md
@.paul/STATE.md
## Source Files
@wp-content/plugins/yacht-booking-system/api/class-rest-controller.php
@wp-content/plugins/yacht-booking-system/frontend/class-calendar-widget-all.php
@wp-content/plugins/yacht-booking-system/admin/views/yacht-edit.php
@wp-content/plugins/yacht-booking-system/admin/class-admin.php
@wp-content/plugins/yacht-booking-system/includes/class-yacht.php
@wp-content/plugins/yacht-booking-system/integrations/ical/class-ical-import.php
</context>
<acceptance_criteria>
## AC-1: Admin może wybrać kolor jachtu
```gherkin
Given administrator otwiera formularz edycji jachtu (yacht-edit.php)
When wybierze kolor w polu "Kolor jachtu w kalendarzu zbiorczym" i zapisze formularz
Then meta `_yacht_color` jachtu zostaje zapisana jako sanitowany hex (`#rrggbb`)
And po przeładowaniu formularza widzi zapisany kolor w polu
```
## AC-2: Kolor admina ma priorytet w widgecie zbiorczym
```gherkin
Given jacht ma ustawione `_yacht_color = #ff6600`
When frontend pobiera `GET /availability/all`
Then eventy tego jachtu (yacht_id > 0) mają `backgroundColor = #ff6600`
And legenda widgetu pokazuje kolor #ff6600 obok jego nazwy
```
## AC-3: Fallback z palety dla jachtów bez ustawionego koloru
```gherkin
Given jacht nie ma `_yacht_color` (brak meta lub pusta wartość)
When frontend pobiera `GET /availability/all`
Then eventy tego jachtu otrzymują kolor z `YACHT_COLOR_PALETTE` (deterministycznie po posortowanym yacht_id, jak obecnie)
```
## AC-4: Globalne eventy GCal kolorowane po nazwie jachtu w tytule
```gherkin
Given event z globalnego kalendarza (yacht_id=0, source=`ical_global_calendar`) o tytule "Rezerwacja - Maja - Kowalski"
And istnieje jacht o `post_title = "Maja"` z kolorem `#3498db` (lub fallback z palety)
When frontend pobiera `GET /availability/all`
Then ten event ma `backgroundColor = #3498db`
And jeśli żadna nazwa/alias jachtu nie pojawia się w tytule `backgroundColor = GLOBAL_EVENT_COLOR`
And gdy w tytule pasuje wiele nazw wybierany jest najdłuższy (np. "Maja Bis" wygrywa z "Maja")
```
</acceptance_criteria>
<tasks>
<task type="auto">
<name>Task 1: Pole koloru w formularzu jachtu + meta `_yacht_color`</name>
<files>
wp-content/plugins/yacht-booking-system/admin/views/yacht-edit.php,
wp-content/plugins/yacht-booking-system/admin/class-admin.php,
wp-content/plugins/yacht-booking-system/includes/class-yacht.php,
wp-content/plugins/yacht-booking-system/yacht-booking-system.php
</files>
<action>
1. `class-yacht.php`: dodaj statyczne metody `get_color( $yacht_id ): string` (zwraca '' gdy brak meta) i `update_color( $yacht_id, $color )` z sanityzacją do `#rrggbb` (regex `/^#[0-9a-f]{6}$/i`, lowercase). Klucz meta: `_yacht_color`.
2. `admin/views/yacht-edit.php`: w sekcji ustawień jachtu dodaj pole `<input type="text" class="yacht-color-picker" name="yacht_color" value="<?php echo esc_attr( Yacht::get_color( $yacht_id ) ); ?>">` z labelem "Kolor jachtu (kalendarz zbiorczy)". Pod inputem krótki tekst pomocniczy: "Pozostaw puste, aby użyć automatycznego koloru z palety."
3. `class-admin.php``save_yacht()`: po istniejących wpisach meta wywołaj `Yacht::update_color( $saved_id, $_POST['yacht_color'] ?? '' )`. Pusty string usuwa meta (delete_post_meta).
4. `class-admin.php``enqueue_admin_assets()` lub punkt rejestrujący style admin: na stronie `yacht-bookings-add-yacht` enqueue `wp-color-picker` (style + script) oraz inline init: `jQuery('.yacht-color-picker').wpColorPicker();`. Inline JS wpięty przez `wp_add_inline_script( 'wp-color-picker', ... )`.
Avoid: zapisywanie surowej wartości z $_POST bez sanityzacji; nadpisywanie istniejącej meta gdy POST nie zawiera `yacht_color` (ale tutaj formularz zawsze wysyła to pole).
</action>
<verify>
Edytuj jacht w panelu, ustaw kolor `#ff6600`, zapisz. Sprawdź `wp_postmeta`: `meta_key = _yacht_color`, `meta_value = #ff6600`. Wyczyść pole, zapisz → meta usunięta. `php -l` na każdym zmienionym pliku.
</verify>
<done>AC-1 satisfied.</done>
</task>
<task type="auto">
<name>Task 2: Backend — `_yacht_color` w palecie + matching globalnych eventów po nazwie</name>
<files>
wp-content/plugins/yacht-booking-system/api/class-rest-controller.php
</files>
<action>
1. `get_yacht_color_palette( $yacht_ids )`: dla każdego `yacht_id` najpierw spróbuj `Yacht::get_color( $yacht_id )`; jeśli niepuste — użyj. Inaczej fallback na obecny algorytm `palette[i % count]` (i = pozycja w posortowanej tablicy ID, jak teraz). Wynik nadal `array<int,string>`.
2. `get_all_availability()`: zbuduj mapę dopasowań nazwa→yacht_id raz przed pętlą eventów:
- Pobierz wszystkie publish jachty z polami `ID, post_title`.
- Dla każdego: dodaj wpis `[strtolower(post_title) => yacht_id]` oraz, jeśli `Yacht::get_gcal_alias($id)` niepusty, `[strtolower($alias) => yacht_id]`.
- Posortuj klucze malejąco po długości (najdłuższy match wygrywa).
3. W pętli eventów, gdy `$is_global_event === true` (yacht_id=0 lub source=GLOBAL_CALENDAR_SOURCE):
- Weź `post_title` rezerwacji (`$booking->post_title`), zlowercase'uj.
- Iteruj po posortowanej liście kluczy; pierwszy `mb_strpos($title_lower, $key) !== false` wygrywa → `$matched_yacht_id = mapa[key]`.
- Jeśli match → `$color = $color_map[$matched_yacht_id] ?? GLOBAL_EVENT_COLOR`. Brak → `$color = GLOBAL_EVENT_COLOR` (jak teraz).
- Nie modyfikuj `yacht_id` w odpowiedzi (zostaje 0); kolor wystarczy do wizualizacji.
4. Zachowaj obecne zachowanie dla eventów per-yacht (`yacht_id > 0`) bez zmian.
Avoid: case-sensitive porównań; budowania mapy w pętli (perf); modyfikowania danych eventu poza polem `color`.
</action>
<verify>
`php -l class-rest-controller.php`. Ręcznie: `curl '/wp-json/yacht-booking/v1/availability/all?start=2026-05-01&end=2026-06-01' | jq '.[] | {title, color}'` — eventy globalne z nazwą jachtu w tytule mają kolor jachtu, bez nazwy → `#7fb3d5`. Per-yacht jachty z ustawionym `_yacht_color` mają ten kolor.
</verify>
<done>AC-2, AC-3, AC-4 satisfied.</done>
</task>
<task type="auto">
<name>Task 3: Legenda widgetu zbiorczego + ROADMAP update</name>
<files>
wp-content/plugins/yacht-booking-system/frontend/class-calendar-widget-all.php,
.paul/ROADMAP.md
</files>
<action>
1. `class-calendar-widget-all.php`: linia ~149-179 — `get_yacht_color_palette()` po Task 2 już honoruje `_yacht_color`, więc legenda dostanie poprawne kolory bez zmian logiki. Zweryfikuj że `$color_map` przekazywana do legendy jest taką samą referencją (jeśli nie — ujednolicić). Jeśli logika legendy oczekuje `palette[i % count]`, zaktualizować by używała tej samej funkcji co backend.
2. `.paul/ROADMAP.md`: w sekcji "Plans" Phase 9:
- Dodaj `- [ ] 09-07: Kolory per jacht w kalendarzu zbiorczym (color picker + dopasowanie po nazwie)`
- Przesuń istniejące: `09-07: Security audit``09-08`, `09-08: Testy + tłumaczenia + dokumentacja``09-09`.
- Zaktualizuj nagłówek "Phases: 8 of 9 complete" oraz tabelę faz (Phase 9 plans count) jeśli to potrzebne.
Avoid: dotykania STATE.md tutaj — to robi krok update_state w workflow.
</action>
<verify>
Otwórz `/rezerwacja/`, sprawdź wzrokowo: legenda u góry pokazuje kolory jachtów = wybranym w panelu (lub fallback z palety dla braków). `.paul/ROADMAP.md` zawiera 09-07 jako kolory, 09-08 jako security, 09-09 jako docs.
</verify>
<done>AC-2 (legenda) satisfied; roadmap zsynchronizowany z planem.</done>
</task>
<task type="checkpoint:human-verify" gate="blocking">
<what-built>
Pole koloru w formularzu jachtu + zastosowanie kolorów w widgecie zbiorczym (`/rezerwacja/`) z dopasowaniem nazwy jachtu w tytule eventów globalnych.
</what-built>
<how-to-verify>
1. Wejdź do panelu admina → Jachty → wybierz dwa jachty, ustaw różne kolory (np. `#ff6600`, `#1abc9c`). Trzeci zostaw bez koloru.
2. Odśwież `https://jachty3.pagedev.pl/rezerwacja/`.
3. Sprawdź:
- Rezerwacje pierwszych dwóch jachtów mają wybrane kolory.
- Rezerwacje trzeciego jachtu mają kolor z palety (auto).
- Eventy z globalnego GCal, których tytuł zawiera nazwę jednego z dwóch pierwszych jachtów, też dostają jego kolor.
- Legenda u góry kalendarza zbiorczego pokazuje te same kolory.
4. Wyczyść kolor jednego jachtu w panelu, zapisz, odśwież `/rezerwacja/` → ten jacht wraca do koloru z palety.
</how-to-verify>
<resume-signal>Wpisz "approved" aby zamknąć plan przez `/paul:unify`, lub opisz zaobserwowane problemy.</resume-signal>
</task>
</tasks>
<boundaries>
## DO NOT CHANGE
- `wp-content/plugins/yacht-booking-system/integrations/ical/class-ical-import.php` (per-yacht prefix matching pozostaje bez zmian — zmieniamy tylko prezentację w `/availability/all`)
- `wp_yacht_availability` (schemat)
- Pojedynczy widget jachtu (`class-calendar-widget.php`) — kolory tam bez zmian
- Endpoint `/availability/{yacht_id}` — bez zmian
- `GLOBAL_EVENT_COLOR` jako fallback (nadal `#7fb3d5`)
- `YACHT_COLOR_PALETTE` jako fallback (nadal 8 hex)
## SCOPE LIMITS
- Bez migracji DB (meta dodawana on-demand przez update_post_meta)
- Bez zmian w warstwie GCal sync (push/pull do Google) — kolory są tylko widoczne w naszym widgecie zbiorczym
- Bez tłumaczeń pól (PL hardcoded; tłumaczenia objęte planem 09-09)
- Bez zmian w endpointach REST poza `get_all_availability()`
- Brak wsparcia dla CSS gradient/secondary color — jeden hex per jacht
</boundaries>
<verification>
- [ ] `php -l` przechodzi na wszystkich zmienionych plikach PHP
- [ ] Po zapisie formularza jachtu z kolorem `_yacht_color` jest w `wp_postmeta`
- [ ] Po wyczyszczeniu pola meta jest usuwana
- [ ] `GET /wp-json/yacht-booking/v1/availability/all` zwraca `color` zgodny z meta lub paletą fallback
- [ ] Globalne eventy GCal z nazwą jachtu w tytule kolorowane kolorem tego jachtu
- [ ] Najdłuższe dopasowanie wygrywa (np. "Maja Bis" > "Maja")
- [ ] Brak match → `GLOBAL_EVENT_COLOR`
- [ ] Legenda widgetu zbiorczego spójna z kolorami eventów
- [ ] ROADMAP.md odzwierciedla nową kolejność (07 kolory, 08 security, 09 docs)
- [ ] Wszystkie kryteria akceptacji spełnione przy weryfikacji manualnej
</verification>
<success_criteria>
- Wszystkie zadania ukończone i zweryfikowane
- Brak regresji w widgecie pojedynczego jachtu
- Brak regresji w existing per-yacht iCal import (matching prefix nadal działa)
- Klient akceptuje kolory na `/rezerwacja/`
</success_criteria>
<output>
Po ukończeniu utwórz `.paul/phases/09-finalizacja/09-07-SUMMARY.md`.
</output>

View File

@@ -412,6 +412,10 @@ class Admin {
$gcal_alias = isset( $data['yacht_gcal_alias'] ) ? sanitize_text_field( wp_unslash( $data['yacht_gcal_alias'] ) ) : '';
Yacht::update_gcal_alias( $saved_id, $gcal_alias );
// Save admin-selected color for aggregated calendar.
$yacht_color = isset( $data['yacht_color'] ) ? sanitize_text_field( wp_unslash( $data['yacht_color'] ) ) : '';
Yacht::update_color( $saved_id, $yacht_color );
return $saved_id;
}

View File

@@ -13,7 +13,8 @@ if ( ! defined( 'ABSPATH' ) ) {
// Get yacht data
$title = $yacht ? $yacht->post_title : '';
$content = $yacht ? $yacht->post_content : '';
$gcal_alias = $yacht ? \YachtBooking\Yacht::get_gcal_alias( $yacht->ID ) : '';
$gcal_alias = $yacht ? \YachtBooking\Yacht::get_gcal_alias( $yacht->ID ) : '';
$yacht_color = $yacht ? \YachtBooking\Yacht::get_color( $yacht->ID ) : '';
$page_title = $yacht ? __( 'Edytuj Jacht', 'yacht-booking' ) : __( 'Dodaj Jacht', 'yacht-booking' );
?>
@@ -86,6 +87,28 @@ $page_title = $yacht ? __( 'Edytuj Jacht', 'yacht-booking' ) : __( 'Dodaj Jacht'
</td>
</tr>
<!-- Kolor jachtu w kalendarzu zbiorczym -->
<tr>
<th scope="row">
<label for="yacht_color">
<?php esc_html_e( 'Kolor w kalendarzu zbiorczym', 'yacht-booking' ); ?>
</label>
</th>
<td>
<input
type="text"
name="yacht_color"
id="yacht_color"
class="yacht-color-picker"
value="<?php echo esc_attr( $yacht_color ); ?>"
data-default-color=""
/>
<p class="description">
<?php esc_html_e( 'Kolor pasków rezerwacji tego jachtu w widgecie zbiorczym (/rezerwacja/). Pozostaw puste, aby użyć automatycznego koloru z palety.', 'yacht-booking' ); ?>
</p>
</td>
</tr>
<!-- Alias dla globalnej synchronizacji iCal -->
<tr>
<th scope="row">

View File

@@ -376,18 +376,40 @@ class Rest_Controller extends \WP_REST_Controller {
$is_global_mode = ( 'global' === Settings::get_ical_sync_mode() );
// Build yacht_id → color map (deterministic by ascending yacht_id).
$yacht_posts = get_posts(
// Build yacht_id → color map (admin-selected `_yacht_color` lub fallback z palety po ID).
$yacht_posts_full = get_posts(
array(
'post_type' => 'yacht',
'post_status' => 'publish',
'posts_per_page' => -1,
'orderby' => 'ID',
'order' => 'ASC',
'fields' => 'ids',
)
);
$color_map = self::get_yacht_color_palette( $yacht_posts );
$yacht_ids = array();
foreach ( $yacht_posts_full as $yp ) {
$yacht_ids[] = (int) $yp->ID;
}
$color_map = self::get_yacht_color_palette( $yacht_ids );
// Build name/alias → yacht_id map (lowercase keys, sorted by length DESC for longest match).
$name_map = array();
foreach ( $yacht_posts_full as $yp ) {
$title = mb_strtolower( trim( (string) $yp->post_title ) );
if ( '' !== $title ) {
$name_map[ $title ] = (int) $yp->ID;
}
$alias = mb_strtolower( trim( (string) \YachtBooking\Yacht::get_gcal_alias( $yp->ID ) ) );
if ( '' !== $alias ) {
$name_map[ $alias ] = (int) $yp->ID;
}
}
uksort(
$name_map,
function( $a, $b ) {
return mb_strlen( $b ) - mb_strlen( $a );
}
);
// Query bookings overlapping [start, end] with status confirmed or pending.
$bookings = get_posts(
@@ -437,15 +459,6 @@ class Rest_Controller extends \WP_REST_Controller {
$source = (string) get_post_meta( $booking_id, '_booking_source', true );
$is_global_event = ( 0 === $yacht_id || \YachtBooking\Integrations\ICal\ICal_Import::GLOBAL_CALENDAR_SOURCE === $source );
// Color: zachowane z poprzedniej logiki (per-yacht paleta lub kolor global).
if ( $is_global_mode || $is_global_event ) {
$color = self::GLOBAL_EVENT_COLOR;
$y_id = 0;
} else {
$color = isset( $color_map[ $yacht_id ] ) ? $color_map[ $yacht_id ] : self::GLOBAL_EVENT_COLOR;
$y_id = $yacht_id;
}
// Title: raw SUMMARY z _booking_notes (iCal) lub customer_name (frontend).
// Klient świadomie cofa privacy z 09-04 — tytuły rezerwacji widoczne publicznie.
if ( in_array( $source, $ical_sources, true ) ) {
@@ -457,6 +470,26 @@ class Rest_Controller extends \WP_REST_Controller {
}
$title = sanitize_text_field( $title );
// Color resolution:
// - per-yacht event (yacht_id > 0): admin color or palette fallback
// - global event (yacht_id = 0): match yacht name/alias anywhere in title (longest wins)
if ( ! $is_global_event ) {
$color = isset( $color_map[ $yacht_id ] ) ? $color_map[ $yacht_id ] : self::GLOBAL_EVENT_COLOR;
$y_id = $yacht_id;
} else {
$color = self::GLOBAL_EVENT_COLOR;
$y_id = 0;
$title_lower = mb_strtolower( $title );
foreach ( $name_map as $needle => $matched_id ) {
if ( '' !== $needle && false !== mb_strpos( $title_lower, $needle ) ) {
if ( isset( $color_map[ $matched_id ] ) ) {
$color = $color_map[ $matched_id ];
}
break;
}
}
}
// Split na N eventów per dzień (allDay = każdy event mieści się w jednej komórce).
// Iteracja od start_date do end_date INCLUSIVE — pierwszy i ostatni dzień
// mają half-day visual (yacht odbierany / zwracany w południe).
@@ -562,7 +595,8 @@ class Rest_Controller extends \WP_REST_Controller {
$map = array();
foreach ( $ids as $i => $yacht_id ) {
$map[ $yacht_id ] = $palette[ $i % $count ];
$admin_color = \YachtBooking\Yacht::get_color( $yacht_id );
$map[ $yacht_id ] = '' !== $admin_color ? $admin_color : $palette[ $i % $count ];
}
return $map;

View File

@@ -79,11 +79,20 @@
/* Event styling — pełne wypełnienie kafelka kolorem jachtu, bez kropek/czasu */
.yacht-calendar-all .fc-event {
border: none !important;
padding: 2px 4px;
padding: 0 1px;
font-size: 12px;
font-weight: 500;
color: #fff;
cursor: default;
display: flex;
align-items: center;
}
.yacht-calendar-all .fc-event-main {
display: flex;
align-items: center;
width: 100%;
height: 100%;
}
.yacht-calendar-all .fc-daygrid-event-dot,
@@ -93,7 +102,7 @@
/* Custom kontener tytułu (renderowany przez eventContent w JS). */
.yacht-calendar-all .yc-event-title {
padding: 1px 6px;
padding: 0 2px;
font-size: 11px;
font-weight: 600;
color: #fff;
@@ -102,13 +111,15 @@
overflow: hidden;
text-overflow: ellipsis;
line-height: 1.3;
text-align: center;
width: 100%;
}
/* Pasek eventu wyższy + gap między dziennymi segmentami (rezerwacja wielonocna
= N osobnych pasków zamiast jednej belki — patrz REST split per-day). */
.yacht-calendar-all .fc-daygrid-event {
min-height: 18px;
margin: 1px 2px !important;
margin: 1px 0 !important;
border-radius: 2px;
}
@@ -123,7 +134,7 @@
.yacht-calendar-all .yc-event-title {
font-size: 10px;
padding: 1px 4px;
padding: 0 1px;
}
.yacht-calendar-all .fc-toolbar.fc-header-toolbar {

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -167,19 +167,18 @@ class Calendar_All_View {
<?php if ( $show_legend ) : ?>
<div class="yacht-calendar-legend yacht-calendar-all-legend" aria-label="<?php esc_attr_e( 'Legenda kalendarza', 'yacht-booking' ); ?>">
<?php foreach ( $yacht_posts as $yacht ) : ?>
<?php $color = isset( $color_map[ $yacht->ID ] ) ? $color_map[ $yacht->ID ] : $global_color; ?>
<span class="yacht-legend-item">
<span class="yacht-legend-swatch" style="background-color: <?php echo esc_attr( $color ); ?>;"></span>
<?php echo esc_html( $yacht->post_title ); ?>
</span>
<?php endforeach; ?>
<?php if ( 'global' === $sync_mode ) : ?>
<span class="yacht-legend-item">
<span class="yacht-legend-swatch" style="background-color: <?php echo esc_attr( $global_color ); ?>;"></span>
<?php esc_html_e( 'Rezerwacja', 'yacht-booking' ); ?>
<?php esc_html_e( 'Inne', 'yacht-booking' ); ?>
</span>
<?php else : ?>
<?php foreach ( $yacht_posts as $yacht ) : ?>
<?php $color = isset( $color_map[ $yacht->ID ] ) ? $color_map[ $yacht->ID ] : $global_color; ?>
<span class="yacht-legend-item">
<span class="yacht-legend-swatch" style="background-color: <?php echo esc_attr( $color ); ?>;"></span>
<?php echo esc_html( $yacht->post_title ); ?>
</span>
<?php endforeach; ?>
<?php endif; ?>
</div>
<?php endif; ?>

View File

@@ -218,6 +218,16 @@ class Yacht_Booking {
YACHT_BOOKING_VERSION
);
// WP color picker on yacht edit form.
if ( isset( $_GET['page'] ) && 'yacht-bookings-add-yacht' === $_GET['page'] ) {
wp_enqueue_style( 'wp-color-picker' );
wp_enqueue_script( 'wp-color-picker' );
wp_add_inline_script(
'wp-color-picker',
'jQuery(function($){ $(".yacht-color-picker").wpColorPicker(); });'
);
}
wp_enqueue_script(
'yacht-booking-admin',
YACHT_BOOKING_PLUGIN_URL . 'admin/assets/js/admin.js',

View File

@@ -152,4 +152,37 @@ class Yacht {
public static function update_features( $yacht_id, $features ) {
update_post_meta( $yacht_id, '_yacht_features', is_array( $features ) ? $features : array() );
}
/**
* Get admin-selected yacht color for the aggregated calendar.
*
* Returns sanitized hex (#rrggbb, lowercase) or '' when not set.
*
* @param int $yacht_id Yacht post ID.
* @return string
*/
public static function get_color( $yacht_id ) {
$value = (string) get_post_meta( $yacht_id, '_yacht_color', true );
if ( '' === $value ) {
return '';
}
return preg_match( '/^#[0-9a-f]{6}$/i', $value ) ? strtolower( $value ) : '';
}
/**
* Update yacht color. Empty value removes the meta (fallback to palette).
*
* @param int $yacht_id Yacht post ID.
* @param string $color Hex color (#rrggbb) or empty string.
*/
public static function update_color( $yacht_id, $color ) {
$color = trim( (string) $color );
if ( '' === $color ) {
delete_post_meta( $yacht_id, '_yacht_color' );
return;
}
if ( preg_match( '/^#[0-9a-f]{6}$/i', $color ) ) {
update_post_meta( $yacht_id, '_yacht_color', strtolower( $color ) );
}
}
}