This commit is contained in:
2026-05-07 14:57:59 +02:00
parent c4a485e530
commit 811069a25c
35 changed files with 2980 additions and 30 deletions

View File

@@ -47,8 +47,9 @@ Szczegóły w `wp-content/plugins/yacht-booking-system/PROJECT-STATUS.md`.
- [ ] 09-01: UX/UI polish kalendarza — half-day na pierwszym/ostatnim dniu rezerwacji + paleta widgetu pasująca do strony
- [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
- [ ] 09-04: Security audit i poprawki
- [ ] 09-05: Testy + tłumaczenia + dokumentacja
- [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
---
*Roadmap created: 2026-05-05*

View File

@@ -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-03 — Complete (Cleanup OAuth + per-yacht iCal)
Status: Loop closed, ready for next plan (09-04)
Last activity: 2026-05-06 — Closed loop 09-03 (3 pliki OAuth usunięte, per-yacht iCal wycofany, migration po cichu)
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)
Progress:
- Milestone: [█████████░] 92%
- Phase 9: [██████░░░░] 60% (3 of 5 plans complete)
- Milestone: [█████████░] 95%
- Phase 9: [██████░░░░] 67% (4 of 6 plans complete)
## Loop Position
Current loop state:
```
PLAN ──▶ APPLY ──▶ UNIFY
✓ ✓ ✓ [Loop 09-03 complete, ready for 09-04]
✓ ✓ ✓ [Loop 09-04 complete, ready for 09-05]
```
## Accumulated Context
@@ -51,15 +51,16 @@ None.
## Session Continuity
Last session: 2026-05-06 (paused — end of productive day, 2 pełne pętle zamknięte)
Stopped at: Loop 09-03 zamknięty + handoff utworzony
Next action: Run /paul:plan to plan 09-04 (Security audit). Klient wykonuje deploy FTP zmienionych plików i aktywację pluginu (cleanup migration uruchomi się automatycznie).
Resume file: .paul/HANDOFF-2026-05-06.md
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
Resume context:
- Faza 9: 3/5 planów ukończonych (60%), milestone v1.0 92%
- Plugin sprowadzony do jednego mechanizmu sync (globalny iCal) — kontekst w 09-02 i 09-03 SUMMARY
- 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
- Sesja zaczęła z 09-01 zamknięte, kończy z 09-03 zamknięte (2 pełne pętle PLAN→APPLY→UNIFY w jednej sesji)
---
*STATE.md — Updated after every significant action*

View File

@@ -0,0 +1,29 @@
# 2026-05-07
## Co zrobiono
- [Faza 9, Plan 04] Globalna sync iCal (tryb wspólny kalendarz) + nowy widget "wszystkie jachty"
- Settings: nowe pole "Tryb synchronizacji iCal" (per_yacht / global) z whitelistą i bezpiecznym defaultem
- ICal_Import: rozgałęzienie `run_per_yacht_mode()` / `run_global_calendar_mode()`, nowa stała `GLOBAL_CALENDAR_SOURCE`, niezależny cleanup obu trybów
- REST: nowy endpoint `GET /availability/all` (timed 12:00→12:00, kolory z palety per-jacht, privacy w title)
- Frontend: widget Elementor `Calendar_Widget_All` + shortcode `[yacht_calendar_all]` z layoutem 2-kolumnowym (kalendarz + formularz inquiry z wyborem jachtu)
- 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
## Zmienione pliki
- `wp-content/plugins/yacht-booking-system/yacht-booking-system.php`
- `wp-content/plugins/yacht-booking-system/includes/class-settings.php`
- `wp-content/plugins/yacht-booking-system/includes/class-installer.php`
- `wp-content/plugins/yacht-booking-system/includes/class-yacht-booking.php`
- `wp-content/plugins/yacht-booking-system/admin/class-admin.php`
- `wp-content/plugins/yacht-booking-system/integrations/ical/class-ical-import.php`
- `wp-content/plugins/yacht-booking-system/api/class-rest-controller.php`
- `wp-content/plugins/yacht-booking-system/frontend/class-shortcode.php`
- `wp-content/plugins/yacht-booking-system/frontend/class-calendar-widget-all.php` (nowy)
- `wp-content/plugins/yacht-booking-system/frontend/assets/css/calendar-all.css` (nowy)
- `wp-content/plugins/yacht-booking-system/frontend/assets/js/calendar-all.js` (nowy)
- `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`

View File

@@ -0,0 +1,60 @@
{"ts":"2026-05-07T11:38:05Z","tool":"Bash","cmd":"mkdir -p \".paul/phases/09-finalizacja\" 2>&1; ls \".paul/phases/09-finalizacja/\" 2>&1\",\"description\":\"Ensure phase dir exists\"},\"tool_response\":{\"stdout\":\"09-01-PLAN.md\\n09-01-SUMMARY.md\","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
{"ts":"2026-05-07T11:40:34Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\.paul\\\\phases\\\\09-finalizacja\\\\09-04-PLAN.md","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
{"ts":"2026-05-07T11:40: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-07T11:41: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-07T11:41:17Z","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-07T11:43:11Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\wp-content\\\\plugins\\\\yacht-booking-system\\\\includes\\\\class-settings.php","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
{"ts":"2026-05-07T11:43:16Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\wp-content\\\\plugins\\\\yacht-booking-system\\\\includes\\\\class-installer.php","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
{"ts":"2026-05-07T11:43:27Z","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-07T11:43:34Z","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-07T11:43:48Z","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-07T11:44:13Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\wp-content\\\\plugins\\\\yacht-booking-system\\\\integrations\\\\ical\\\\class-ical-import.php","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
{"ts":"2026-05-07T11:44:23Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\wp-content\\\\plugins\\\\yacht-booking-system\\\\integrations\\\\ical\\\\class-ical-import.php","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
{"ts":"2026-05-07T11:44:51Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\wp-content\\\\plugins\\\\yacht-booking-system\\\\integrations\\\\ical\\\\class-ical-import.php","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
{"ts":"2026-05-07T11:45:10Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\wp-content\\\\plugins\\\\yacht-booking-system\\\\integrations\\\\ical\\\\class-ical-import.php","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
{"ts":"2026-05-07T11:45:18Z","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-07T11:45:28Z","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-07T11:45:51Z","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-07T11:46:21Z","tool":"Write","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-07T11:46:31Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\wp-content\\\\plugins\\\\yacht-booking-system\\\\frontend\\\\class-shortcode.php","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
{"ts":"2026-05-07T11:46:46Z","tool":"Write","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-07T11:47:10Z","tool":"Write","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-07T11:47: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-07T11:47:54Z","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-07T11:48:32Z","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-07T11:48: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-07T11:48:59Z","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-07T11:49:07Z","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-07T11:49:25Z","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-07T11:49:33Z","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-07T11:49:52Z","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-07T11:49:57Z","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-07T12:14:15Z","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-07T12:14:36Z","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-07T12:14: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-07T12:14:59Z","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-07T12:15:13Z","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-07T12:15:45Z","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-07T12:19:10Z","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-07T12:22:31Z","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-07T12:22:40Z","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-07T12:23:25Z","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-07T12:23:42Z","tool":"Write","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-07T12:27:00Z","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-07T12:31: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-07T12:31:55Z","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-07T12:32:16Z","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-07T12:32:33Z","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-07T12:32:45Z","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-07T12:32:55Z","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-07T12:37: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-07T12:38:01Z","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-07T12:39:24Z","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-07T12:40:50Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\test-overlap-bookings.php","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
{"ts":"2026-05-07T12:45:42Z","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-07T12:48:49Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\.paul\\\\phases\\\\09-finalizacja\\\\09-04-SUMMARY.md","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
{"ts":"2026-05-07T12:49:17Z","tool":"Write","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-07T12:49:31Z","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: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"}

View File

@@ -0,0 +1,329 @@
---
phase: 09-finalizacja
plan: 04
type: execute
wave: 1
depends_on: []
files_modified:
- wp-content/plugins/yacht-booking-system/includes/class-settings.php
- wp-content/plugins/yacht-booking-system/includes/class-installer.php
- wp-content/plugins/yacht-booking-system/admin/class-admin.php
- wp-content/plugins/yacht-booking-system/admin/views/settings-page.php
- wp-content/plugins/yacht-booking-system/integrations/ical/class-ical-import.php
- wp-content/plugins/yacht-booking-system/api/class-rest-controller.php
- wp-content/plugins/yacht-booking-system/includes/class-yacht-booking.php
- wp-content/plugins/yacht-booking-system/frontend/class-calendar-widget-all.php (NEW)
- wp-content/plugins/yacht-booking-system/frontend/class-shortcode.php
- wp-content/plugins/yacht-booking-system/assets/js/calendar-all.js (NEW)
- wp-content/plugins/yacht-booking-system/assets/css/calendar-all.css (NEW)
autonomous: false
delegation: off
---
<objective>
## Goal
Wprowadzić tryb "globalnej" synchronizacji iCal (wszystkie eventy z jednego feedu trafiają do wspólnego storage bez per-yacht matchingu) oraz nowy publiczny widget kalendarza pokazujący zajętość WSZYSTKICH publikowanych jachtów na jednej siatce, z kolorami per-jacht i wizualnym efektem "half-day" na pierwszym/ostatnim dniu rezerwacji.
## Purpose
- Klient prowadzi jeden wspólny Google Calendar do wszystkich jachtów. Obecny tryb wymaga prefiksu w SUMMARY ("Maja - Klient") i wyrzuca nierozpoznane eventy. Nowy tryb pozwala importować je wszystkie bez utraty (np. "Pierwszy dzień szkoły") jako wydarzenia informacyjne na wspólnym widoku.
- Frontend ma jedno miejsce ("kalendarz floty") gdzie potencjalny klient widzi wszystkie zajętości naraz, bez rozpraszającego efektu ukośników z trybu per-jacht. Half-day daje wizualny sygnał, że ktoś może wynająć od/do południa.
## Output
- Settings: nowy select `ical_sync_mode` (`per_yacht` | `global`)
- iCal Import: gałąź `global` zapisuje WSZYSTKIE eventy do wspólnego storage (`_booking_source = 'ical_global_calendar'`, brak yacht_id), bez wpisów do `wp_yacht_availability`
- REST: `GET /availability/all` — zwraca eventy FullCalendar dla wszystkich jachtów + globalne wydarzenia, z kolorami auto-paleta i czasami timed (12:00 → 12:00)
- Nowy widget Elementor `Yacht_Calendar_All_Widget` + shortcode `[yacht_calendar_all]` renderujący FullCalendar timed-grid z legendą jachtów
</objective>
<context>
<clarifications>
- **Unmatched** — Co zrobić z eventami iCal bez prefiksu lub z nieznaną nazwą jachtu?
→ Odpowiedź: Dodatkowa opcja w ustawieniach przełączająca tryb sync. Tryb `per_yacht` (obecny — match po prefiksie) lub `global` (jeden wspólny kalendarz, wszystkie eventy zapisywane bez przypisywania do jachtów; w danym dniu może być kilka wydarzeń).
- **Widget zakres** — Nowy widget pokazuje które jachty?
→ Odpowiedź: Wszystkie publikowane jachty (auto, `post_status = publish`).
- **Rozróżnienie** — Jak odróżnić rezerwacje różnych jachtów?
→ Odpowiedź: Kolor per jacht z auto palety (predefiniowana tablica kolorów indeksowana wg kolejności yacht_id, deterministycznie).
- **Half-day** — Jak pokazać start/koniec w połowie dnia?
→ Odpowiedź: FullCalendar timed events. Dane all-day konwertowane na timed (start = data 12:00, end = data+1 12:00) na poziomie REST endpointu.
</clarifications>
## Project Context
@.paul/PROJECT.md
@.paul/ROADMAP.md
@.paul/STATE.md
@.paul/codebase/architecture.md
@.paul/codebase/db_schema.md
## Prior Work
@.paul/phases/09-finalizacja/09-02-SUMMARY.md
@.paul/phases/09-finalizacja/09-03-SUMMARY.md
## Source Files
@wp-content/plugins/yacht-booking-system/integrations/ical/class-ical-import.php
@wp-content/plugins/yacht-booking-system/includes/class-settings.php
@wp-content/plugins/yacht-booking-system/admin/class-admin.php
@wp-content/plugins/yacht-booking-system/admin/views/settings-page.php
@wp-content/plugins/yacht-booking-system/api/class-rest-controller.php
@wp-content/plugins/yacht-booking-system/frontend/class-calendar-widget.php
@wp-content/plugins/yacht-booking-system/frontend/class-shortcode.php
@wp-content/plugins/yacht-booking-system/includes/class-yacht-booking.php
@wp-content/plugins/yacht-booking-system/includes/class-installer.php
</context>
<acceptance_criteria>
## AC-1: Settings — przełącznik trybu synchronizacji iCal
```gherkin
Given admin otwiera "Yacht Booking Settings Synchronizacja"
When zobaczy nowe pole "Tryb synchronizacji iCal" z opcjami "Per jacht" i "Wspólny kalendarz"
And wybiera "Wspólny kalendarz" i zapisuje
Then opcja `yacht_booking_ical_sync_mode` w wp_options = "global"
And formularz pokazuje wybraną wartość po reload
And opis pomocniczy wyjaśnia różnicę między trybami (per_yacht: matching po prefiksie / global: wszystkie eventy bez filtrowania)
```
## AC-2: Import iCal w trybie globalnym — wszystkie eventy bez filtrowania
```gherkin
Given `yacht_booking_ical_sync_mode = "global"` i feed URL ustawiony
And feed zawiera 32 eventy (z prefiksami "Kubuś -", "Maja -", bez prefiksu "Pierwszy dzień szkoły", bez SUMMARY itp.)
When uruchamia się hook `yacht_booking_ical_global_import`
Then powstaje booking dla KAŻDEGO eventu z UID i `start`/`end` (poza past events)
And `_booking_source = "ical_global_calendar"`, `_booking_yacht_id = 0`, `_booking_status = "confirmed"`
And eventy NIE są wpisywane do `wp_yacht_availability` (nie blokują dostępności żadnego jachtu)
And eventy bez SUMMARY otrzymują domyślny tytuł (np. "Wydarzenie kalendarza")
And ponowne uruchomienie cron-u (idempotencja) bookingi z tymi samymi UID są aktualizowane, nie duplikowane
And eventy obecne w DB ale brakujące w nowym feedzie są usuwane (stale cleanup)
```
## AC-3: Import iCal w trybie per-jacht — bez regresji
```gherkin
Given `yacht_booking_ical_sync_mode = "per_yacht"` (domyślne dla istniejących instalacji)
When uruchamia się hook globalnego importu
Then zachowanie jest IDENTYCZNE jak przed tą zmianą (matching po prefiksie SUMMARY, wpisy do `wp_yacht_availability`, stale cleanup ograniczony do `_booking_source = "ical_import_global"`)
And istniejące bookingi importowane wcześniej w trybie per-jacht działają dalej i są nadal aktualizowane
```
## AC-4: REST endpoint `GET /availability/all`
```gherkin
Given istnieje min. 2 publikowane jachty (np. "Kubuś", "Maja") z rezerwacjami
And w trybie globalnym istnieją wydarzenia z `_booking_source = "ical_global_calendar"`
When klient niezalogowany wywołuje `GET /wp-json/yacht-booking/v1/availability/all?start=2026-05-01&end=2026-12-31`
Then odpowiedź to JSON tablica obiektów FullCalendar event:
{ id, title, start (ISO datetime z "T12:00:00"), end (ISO datetime z "T12:00:00"), color, yacht_id }
And bookingi per-jacht mają `color` z deterministycznej palety (tablica 8 kolorów indeksowana wg kolejności yacht_id)
And globalne wydarzenia mają osobny kolor (np. szary `#7f8c8d`) i `yacht_id = 0`
And tylko bookingi `confirmed`/`pending` są zwracane (nie `cancelled`/`rejected`)
And `start <= end` zachowane
```
## AC-5: Nowy widget i shortcode
```gherkin
Given strona z `[yacht_calendar_all]` lub widgetem Elementor "Yacht Calendar (wszystkie jachty)"
When klient otwiera stronę
Then widoczny FullCalendar w widoku miesięcznym (dayGridMonth)
And eventy z `GET /availability/all` są renderowane jako timed events 12:00 12:00 (efekt half-day na pierwszym/ostatnim dniu rezerwacji)
And nad/pod kalendarzem jest legenda kolorów (kropka + nazwa jachtu) auto-generowana z palety
And widget jest read-only (brak formularza rezerwacji, brak klikalności dnia)
And brak ukośników (cell background pełny / pusty)
```
## AC-6: Auto paleta kolorów per jacht
```gherkin
Given lista jachtów posortowana po ID rosnąco
When system mapuje yacht_id kolor
Then używana jest stała tablica min. 8 kolorów (np. #3498db, #e74c3c, #2ecc71, #f39c12, #9b59b6, #1abc9c, #34495e, #d35400)
And mapowanie jest deterministyczne (yacht_id index modulo długość palety) ten sam yacht zawsze ten sam kolor
And paleta jest wspólna dla REST endpointu i frontendowej legendy
```
</acceptance_criteria>
<tasks>
<task type="auto">
<name>Task 1: Settings — przełącznik trybu sync iCal + zachowanie per_yacht jako default</name>
<files>
wp-content/plugins/yacht-booking-system/includes/class-settings.php,
wp-content/plugins/yacht-booking-system/includes/class-installer.php,
wp-content/plugins/yacht-booking-system/admin/class-admin.php,
wp-content/plugins/yacht-booking-system/admin/views/settings-page.php
</files>
<action>
1. `Settings`: dodaj typowany getter `get_ical_sync_mode()` zwracający `'per_yacht'` lub `'global'` (default: `'per_yacht'`). Klucz opcji: `yacht_booking_ical_sync_mode`. Dodaj setter w obecnym wzorcu klasy.
2. `Installer`: dopisz default opcji `yacht_booking_ical_sync_mode = 'per_yacht'` do tablicy defaultów (nie nadpisuj istniejącej wartości jeśli jest).
3. `Admin::process_settings_save()`: obsłuż nowe pole formularza (sanitize_text_field + whitelista wartości; nieznane → fallback `per_yacht`).
4. `settings-page.php` (sekcja "Synchronizacja"): dodaj `<select name="ical_sync_mode">` z dwoma opcjami i opisem pomocniczym po polsku wyjaśniającym różnicę. Selected = bieżąca wartość. Zachowaj wzorzec nonce + i18n istniejący w pliku.
Avoid: dodawanie nowej tabeli/migration — wystarczy `wp_options`. Avoid: zmiana default na `global` (regresja dla istniejących klientów).
</action>
<verify>
- W WP Admin → Settings widzę nowe pole, mogę zapisać "Wspólny kalendarz" i po reload jest zaznaczone.
- `wp option get yacht_booking_ical_sync_mode` zwraca `global`.
- `php -l` na każdym zmienionym pliku.
</verify>
<done>AC-1 satisfied.</done>
</task>
<task type="auto">
<name>Task 2: ICal_Import — gałąź global (storage bez per-yacht matchingu, idempotencja, stale cleanup)</name>
<files>
wp-content/plugins/yacht-booking-system/integrations/ical/class-ical-import.php
</files>
<action>
1. W `run_global_import()` po pobraniu i sparsowaniu eventów odczytaj `Settings::get_ical_sync_mode()`.
2. Jeśli `per_yacht` — kod bez zmian (obecna ścieżka match po prefiksie + wpisy do availability).
3. Jeśli `global` — pomiń `build_yacht_lookup_map()` / `match_yacht_by_prefix()`. Dla KAŻDEGO eventu (z UID + start + end, pomijając past events) wywołaj nową metodę `upsert_global_calendar_event($event, $existing_id)`:
- Wstawia/aktualizuje CPT `yacht_booking` z metami: `_booking_source = 'ical_global_calendar'`, `_booking_yacht_id = 0`, `_booking_status = 'confirmed'`, `_ical_event_uid = uid`, `_booking_start_date`, `_booking_end_date`, `_booking_customer_name = 'Google Calendar (kalendarz wspólny)'`, `_booking_total_price = 0`, `_booking_notes = summary`.
- Tytuł postu: `summary` lub fallback `__( 'Wydarzenie kalendarza', 'yacht-booking' )` gdy puste.
- NIE wywołuj `Availability::mark_as_booked()` (wydarzenia globalne nie blokują dostępności jachtów).
4. Stale cleanup: w trybie global pobierz mapę istniejących bookingów po `_booking_source = 'ical_global_calendar'` (nowa stała `GLOBAL_CALENDAR_SOURCE`) i usuń te których UID brakuje w nowym feedzie (`wp_delete_post`, force).
5. Stale cleanup w trybie per_yacht musi pozostać ograniczony do `_booking_source = 'ical_import_global'` (obecna stała) — nie tykać bookingów `ical_global_calendar` w drugą stronę i odwrotnie. Dwie stałe, dwie ścieżki cleanup.
6. Logowanie: w trybie global loguj liczbę zaimportowanych / zaktualizowanych / usuniętych eventów.
Avoid: dotykanie `wp_yacht_availability` w trybie global. Avoid: zmiana sygnatury `run_global_import()` (cron callback).
</action>
<verify>
- Test ręczny: ustaw URL na lokalną kopię `D:\temp\basic.ics` (zhostowaną w WP uploads lub serve), włącz tryb `global`, uruchom `wp cron event run yacht_booking_ical_global_import`.
- W WP Admin → Bookings widzę ~30 nowych pozycji z prefiksem "GCal:" lub tytułem eventu, brak wpływu na dostępność `yacht`.
- Drugie uruchomienie cron-u nie tworzy duplikatów (count się zgadza).
- `wp db query "SELECT COUNT(*) FROM wp_yacht_availability WHERE booking_id IN (SELECT post_id FROM wp_postmeta WHERE meta_key='_booking_source' AND meta_value='ical_global_calendar')"` zwraca 0.
- Przełącz na `per_yacht` i uruchom — nowa logika nie ma efektu, stara działa jak wcześniej.
- `php -l`.
</verify>
<done>AC-2 + AC-3 satisfied.</done>
</task>
<task type="auto">
<name>Task 3: REST endpoint /availability/all — agregacja wszystkich jachtów + globalnych eventów z kolorami i timed</name>
<files>
wp-content/plugins/yacht-booking-system/api/class-rest-controller.php,
wp-content/plugins/yacht-booking-system/includes/class-yacht-booking.php
</files>
<action>
1. W `Rest_Controller`: dodaj nową metodę `get_all_availability( $request )` zarejestrowaną jako `GET /yacht-booking/v1/availability/all` (callback w `register_rest_routes()`, `permission_callback => '__return_true'`).
2. Walidacja parametrów `start`, `end` (Y-m-d, opcjonalne — default: pierwszy dzień bieżącego miesiąca / + 12 mies.).
3. Pobierz publikowane jachty (`get_posts` post_type=yacht). Zbuduj mapę `yacht_id => color` przez nową statyczną metodę `Rest_Controller::get_yacht_color_palette( array $yacht_ids )`:
- Stała tablica 8 kolorów hex.
- Mapuj `yacht_id` (sortowane rosnąco) na `palette[ index % count(palette) ]`.
- Stała kolor globalnego eventu: `#7f8c8d`.
4. Query bookings: `post_type = yacht_booking`, status `confirmed` lub `pending`, daty przecinające `start``end`. Per booking zwróć:
```
{
id: booking_id,
title: '<nazwa_jachtu> — <imię_klienta>' lub tytuł postu dla global,
start: '<start_date>T12:00:00',
end: '<end_date>T12:00:00',
color: <z palety lub szary dla global>,
yacht_id: <id lub 0>
}
```
5. Globalne eventy (`_booking_source = ical_global_calendar`) traktowane jednolicie z yacht_id=0 i kolorem szarym; tytuł = `_booking_notes` lub post_title.
6. Pomiń `cancelled`/`rejected`.
7. W `Yacht_Booking::register_rest_routes()` upewnij się, że nowa trasa się rejestruje (jeśli rejestracja jest delegowana do `Rest_Controller::register_routes()` — bez zmiany).
Avoid: cache na poziomie endpointu (małe wolumeny, prosto). Avoid: dodawanie nowych zależności / SDK.
</action>
<verify>
- `curl 'https://<host>/wp-json/yacht-booking/v1/availability/all?start=2026-05-01&end=2026-12-31'` zwraca tablicę JSON z polami { id, title, start, end, color, yacht_id }.
- Każdy yacht_id ma stały kolor między requestami.
- Bookingi per-yacht mają yacht_id != 0; globalne mają yacht_id = 0 i kolor `#7f8c8d`.
- Daty zawierają `T12:00:00`.
- `php -l`.
</verify>
<done>AC-4 + AC-6 satisfied.</done>
</task>
<task type="auto">
<name>Task 4: Widget Elementor + shortcode "yacht_calendar_all" + JS/CSS</name>
<files>
wp-content/plugins/yacht-booking-system/frontend/class-calendar-widget-all.php (NEW),
wp-content/plugins/yacht-booking-system/frontend/class-shortcode.php,
wp-content/plugins/yacht-booking-system/assets/js/calendar-all.js (NEW),
wp-content/plugins/yacht-booking-system/assets/css/calendar-all.css (NEW),
wp-content/plugins/yacht-booking-system/includes/class-yacht-booking.php
</files>
<action>
1. **Nowy widget** `Yacht_Calendar_All_Widget` (extends `\Elementor\Widget_Base`) — wzorowany na `Calendar_Widget`, name=`yacht-calendar-all`, title=`Yacht Calendar (wszystkie jachty)`, ikona kalendarza. `render()` wypisuje `<div class="yacht-calendar-all" data-rest="<URL>"></div>`.
2. **Shortcode** `[yacht_calendar_all]` w `Shortcode` (lub nowa metoda) — bez atrybutów. Wypisuje ten sam markup co widget.
3. **`Yacht_Booking`**: rejestracja widgetu w `register_elementor_widgets()` (dopisz obok istniejącego). Conditional enqueue assets `calendar-all.js`/`calendar-all.css` gdy w treści jest shortcode lub na stronie z widgetem (rozszerz obecny mechanizm).
4. **`calendar-all.js`** (IIFE jQuery): inicjalizacja FullCalendar v6 (CDN), `initialView: 'dayGridMonth'`, `events: <URL z data-rest>`, `displayEventTime: false`, `eventDisplay: 'block'`. Po `eventDidMount` dodaj klasę `.is-half-day-start` / `.is-half-day-end` do pierwszej/ostatniej komórki eventu (na podstawie hours = 12). Renderuj legendę (lista jachtów + kolorów) pobraną z dodatkowego endpointu `GET /availability/all/legend` LUB wyciągniętą z eventów po pierwszym fetch (wybierz prostsze — preferuj wyciąganie z eventów na froncie).
5. **`calendar-all.css`** — minimalistyczny styl: ukryj kropkę przy timed event, pełne wypełnienie kafelka kolorem eventu, half-day cell renderowany przez gradient `linear-gradient(to right, transparent 50%, var(--color) 50%)` na pierwszym dniu i odwrotnie na ostatnim. Brak ukośników. Mobile-first.
6. Brak modal-a / formularza rezerwacji — read-only.
Avoid: npm/bundler — FullCalendar via CDN jak istniejący widget. Avoid: dotykanie istniejącego widgetu/CSS.
</action>
<verify>
- `php -l` na nowych/zmienionych plikach PHP.
- Strona z `[yacht_calendar_all]` renderuje kalendarz z eventami pobranymi z REST.
- Eventy z różnych jachtów mają różne kolory (paleta).
- Pierwszy/ostatni dzień rezerwacji wizualnie wypełniony do połowy (gradient/half cell).
- Brak konsoli błędów JS.
- Widget pojawia się w panelu Elementor pod kategorią pluginu.
</verify>
<done>AC-5 satisfied (półdniowe rendery + legenda + read-only). AC-6 deterministyczna paleta wspólna z REST.</done>
</task>
<task type="checkpoint:human-verify" gate="blocking">
<what-built>
- Settings: nowa opcja "Tryb synchronizacji iCal" (per_yacht / global)
- iCal Import: tryb global importuje wszystkie eventy bez filtrowania, nie blokuje dostępności jachtów
- REST: `GET /availability/all` zwraca wszystkie rezerwacje + globalne eventy z kolorami i timed (12:00)
- Frontend: shortcode `[yacht_calendar_all]` + widget Elementor "Yacht Calendar (wszystkie jachty)" z efektem half-day i legendą kolorów
</what-built>
<how-to-verify>
1. Deploy plików przez FTP (ftp-kr) i ewentualnie aktywuj plugin (jeśli wymagane przez Installer dla nowej opcji).
2. Wejdź w WP Admin → Yacht Booking → Settings → sekcja Synchronizacja: ustaw "Wspólny kalendarz", podaj URL feedu (lub tymczasowo `D:\temp\basic.ics` wystawiony przez serwer testowy), zapisz.
3. Uruchom ręcznie `yacht_booking_ical_global_import` (np. WP-CLI: `wp cron event run yacht_booking_ical_global_import` lub przycisk "Synchronizuj teraz" jeśli istnieje).
4. Sprawdź WP Admin → Bookings: powinny pojawić się ~30 wydarzeń bez yacht_id.
5. Zweryfikuj że dostępność jachtów (kalendarz pojedynczego jachtu) się NIE zmieniła.
6. Stwórz testową stronę z `[yacht_calendar_all]`. Otwórz w przeglądarce.
7. Sprawdź: a) różne kolory dla "Maja" i "Kubuś", b) szare dla globalnych, c) pierwszy/ostatni dzień rezerwacji wypełniony do połowy, d) brak ukośników, e) legenda widoczna.
8. Przełącz tryb na "Per jacht", uruchom cron — sprawdź że stary mechanizm nadal działa (matching po prefiksie, wpisy w availability).
</how-to-verify>
<resume-signal>Type "approved" to continue, or describe issues to fix</resume-signal>
</task>
</tasks>
<boundaries>
## DO NOT CHANGE
- `frontend/class-calendar-widget.php` (istniejący widget per-jacht — kopiujemy NOWY plik, nie dotykamy starego)
- `assets/js/calendar.js`, `assets/css/calendar.css` (assety istniejącego widgetu)
- Schema tabeli `wp_yacht_availability` — nie dodajemy kolumn
- `Booking::create()` flow (REST POST /bookings) — flow rezerwacji frontendowej bez zmian
- Settings: nie zmieniamy domyślnej wartości `ical_sync_mode` na `global` (regresja). Default = `per_yacht`.
- Stała `ICal_Import::GLOBAL_IMPORT_SOURCE = 'ical_import_global'` (per-jacht stale cleanup) — dodajemy DRUGĄ stałą `GLOBAL_CALENDAR_SOURCE = 'ical_global_calendar'`, nie zmieniamy istniejącej
## SCOPE LIMITS
- Brak konfigurowalnej palety kolorów (color picker per jacht) — auto paleta z hardcoded tablicy. Color picker w meta jachtu = osobny plan jeśli klient zechce.
- Brak osobnego widoku "tylko globalne wydarzenia" — globalne pokazują się tylko na nowym wspólnym widgecie.
- Brak konfiguracji shortcode (atrybuty `yachts=`, `view=`) — wszystkie publikowane, dayGridMonth.
- Brak klikalności dnia / formularza rezerwacji na nowym widgecie — read-only podgląd.
- Brak migracji istniejących bookingów `_booking_source = ical_import_global` na nowy storage. Klient musi ręcznie usunąć stare jeśli przełączy się trwale na global.
- Bez zmian dla widgetu Elementor istniejącego (per-jacht) — dwa widgety obok siebie.
</boundaries>
<verification>
Before declaring plan complete:
- [ ] `php -l` na każdym zmienionym/nowym pliku PHP — bez błędów
- [ ] `wp option get yacht_booking_ical_sync_mode` zwraca poprawną wartość po zapisie
- [ ] W trybie `global` po cron-imporcie liczba wydarzeń w `Bookings` ≈ liczba VEVENT z feedu (poza past)
- [ ] W trybie `global` brak wpisów do `wp_yacht_availability` dla `_booking_source = ical_global_calendar`
- [ ] W trybie `per_yacht` zachowanie identyczne jak przed planem (regression check)
- [ ] `GET /availability/all` zwraca poprawny JSON dla niezalogowanego klienta
- [ ] Strona z `[yacht_calendar_all]` renderuje się bez błędów JS w konsoli
- [ ] Half-day visual widoczny na pierwszym i ostatnim dniu każdej rezerwacji
- [ ] Legenda kolorów spójna z kolorami eventów
- [ ] Wszystkie acceptance criteria spełnione
</verification>
<success_criteria>
- Klient może przełączyć tryb sync w settings i zobaczyć efekt po cron-imporcie
- Wszystkie eventy z basic.ics (32) są zaimportowane w trybie global (poza past events)
- Nowy widget pokazuje wszystkie jachty + globalne eventy z kolorami i half-day
- Brak regresji w trybie per-jacht
- Brak regresji w istniejącym widgecie per-jacht
- Brak nowych ostrzeżeń PHP / błędów konsoli
</success_criteria>
<output>
After completion, create `.paul/phases/09-finalizacja/09-04-SUMMARY.md`
</output>

View File

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

View File

@@ -0,0 +1 @@
[ 720ms] [LOG] JQMIGRATE: Migrate is installed, version 3.4.1 @ https://jachty3.pagedev.pl/wp-includes/js/jquery/jquery-migrate.min.js?ver=3.4.1:1

View File

@@ -0,0 +1 @@
[ 600ms] [LOG] JQMIGRATE: Migrate is installed, version 3.4.1 @ https://jachty3.pagedev.pl/wp-includes/js/jquery/jquery-migrate.min.js?ver=3.4.1:1

View File

@@ -0,0 +1 @@
[ 572ms] [LOG] JQMIGRATE: Migrate is installed, version 3.4.1 @ https://jachty3.pagedev.pl/wp-includes/js/jquery/jquery-migrate.min.js?ver=3.4.1:1

View File

@@ -0,0 +1 @@
[ 608ms] [LOG] JQMIGRATE: Migrate is installed, version 3.4.1 @ https://jachty3.pagedev.pl/wp-includes/js/jquery/jquery-migrate.min.js?ver=3.4.1:1

View File

@@ -0,0 +1 @@
[ 626ms] [LOG] JQMIGRATE: Migrate is installed, version 3.4.1 @ https://jachty3.pagedev.pl/wp-includes/js/jquery/jquery-migrate.min.js?ver=3.4.1:1

View File

@@ -0,0 +1 @@
[ 365ms] [ERROR] Failed to load resource: the server responded with a status of 404 () @ https://jachty3.pagedev.pl/favicon.ico:0

View File

@@ -0,0 +1,239 @@
- generic [active] [ref=e1]:
- link "Przejdź do treści" [ref=e2] [cursor=pointer]:
- /url: "#content"
- banner [ref=e3]:
- generic [ref=e5]:
- link [ref=e7] [cursor=pointer]:
- /url: https://jachty3.pagedev.pl
- navigation "Menu" [ref=e9]:
- list [ref=e10]:
- listitem [ref=e11]:
- link "Start" [ref=e12] [cursor=pointer]:
- /url: https://jachty3.pagedev.pl/
- listitem [ref=e13]:
- link "Maja" [ref=e14] [cursor=pointer]:
- /url: https://jachty3.pagedev.pl/maja/
- listitem [ref=e15]:
- link "Kubuś" [ref=e16] [cursor=pointer]:
- /url: https://jachty3.pagedev.pl/kubus/
- listitem [ref=e17]:
- link "O Nas" [ref=e18] [cursor=pointer]:
- /url: https://jachty3.pagedev.pl/o-nas/
- listitem [ref=e19]:
- link "Cennik" [ref=e20] [cursor=pointer]:
- /url: https://jachty3.pagedev.pl/cennik/
- listitem [ref=e21]:
- link "Blog" [ref=e22] [cursor=pointer]:
- /url: https://jachty3.pagedev.pl/blog/
- listitem [ref=e23]:
- link "Kontakt" [ref=e24] [cursor=pointer]:
- /url: https://jachty3.pagedev.pl/kontakt/
- listitem [ref=e25]:
- link "Zarezerwuj →" [ref=e26] [cursor=pointer]:
- /url: /rezerwacja-maja/
- main [ref=e27]:
- generic [ref=e30]:
- heading "Rezerwacja" [level=1] [ref=e34]
- generic [ref=e42]:
- paragraph [ref=e44]: Aby zarezerwować termin, wypełnij formularz po prawej stronie albo skontaktuj się z nami telefonicznie lub mailowo.
- generic "Legenda jachtów" [ref=e45]:
- generic [ref=e46]: Maja
- generic [ref=e48]: Kubuś
- generic [ref=e50]: Wspólne wydarzenia
- generic [ref=e52]:
- generic [ref=e54]:
- generic [ref=e55]:
- generic [ref=e56]:
- generic [ref=e57]:
- button "Poprzedni" [ref=e58] [cursor=pointer]:
- img [ref=e59]:
- button "Następny" [ref=e60] [cursor=pointer]:
- img [ref=e61]:
- button "Dziś" [disabled] [ref=e62]
- heading "maj 2026" [level=2] [ref=e64]
- generic "maj 2026" [ref=e65]:
- grid [ref=e67]:
- rowgroup [ref=e68]:
- row "poniedziałek wtorek środa czwartek piątek sobota niedziela" [ref=e72]:
- columnheader "poniedziałek" [ref=e73]:
- generic "poniedziałek" [ref=e75]: pon.
- columnheader "wtorek" [ref=e76]:
- generic "wtorek" [ref=e78]: wt.
- columnheader "środa" [ref=e79]:
- generic "środa" [ref=e81]: śr.
- columnheader "czwartek" [ref=e82]:
- generic "czwartek" [ref=e84]: czw.
- columnheader "piątek" [ref=e85]:
- generic "piątek" [ref=e87]: pt.
- columnheader "sobota" [ref=e88]:
- generic "sobota" [ref=e90]: sob.
- columnheader "niedziela" [ref=e91]:
- generic "niedziela" [ref=e93]: niedz.
- rowgroup [ref=e94]:
- generic [ref=e97]:
- row "27 kwietnia 2026 28 kwietnia 2026 29 kwietnia 2026 30 kwietnia 2026 1 maja 2026 2 maja 2026 3 maja 2026" [ref=e99]:
- gridcell "27 kwietnia 2026" [ref=e100]:
- generic "27 kwietnia 2026" [ref=e103]: "27"
- gridcell "28 kwietnia 2026" [ref=e105]:
- generic "28 kwietnia 2026" [ref=e108]: "28"
- gridcell "29 kwietnia 2026" [ref=e110]:
- generic "29 kwietnia 2026" [ref=e113]: "29"
- gridcell "30 kwietnia 2026" [ref=e115]:
- generic "30 kwietnia 2026" [ref=e118]: "30"
- gridcell "1 maja 2026" [ref=e120]:
- generic "1 maja 2026" [ref=e123]: "1"
- gridcell "2 maja 2026" [ref=e125]:
- generic "2 maja 2026" [ref=e128]: "2"
- gridcell "3 maja 2026" [ref=e130]:
- generic "3 maja 2026" [ref=e133]: "3"
- row "4 maja 2026 5 maja 2026 6 maja 2026 7 maja 2026 8 maja 2026 9 maja 2026 10 maja 2026" [ref=e135]:
- gridcell "4 maja 2026" [ref=e136]:
- generic "4 maja 2026" [ref=e139]: "4"
- gridcell "5 maja 2026" [ref=e141]:
- generic "5 maja 2026" [ref=e144]: "5"
- gridcell "6 maja 2026" [ref=e146]:
- generic "6 maja 2026" [ref=e149]: "6"
- gridcell "7 maja 2026" [ref=e151]:
- generic "7 maja 2026" [ref=e154]: "7"
- gridcell "8 maja 2026" [ref=e156]:
- generic "8 maja 2026" [ref=e159]: "8"
- gridcell "9 maja 2026" [ref=e161]:
- generic "9 maja 2026" [ref=e164]: "9"
- gridcell "10 maja 2026" [ref=e166]:
- generic "10 maja 2026" [ref=e169]: "10"
- row "11 maja 2026 12 maja 2026 13 maja 2026 14 maja 2026 15 maja 2026 16 maja 2026 17 maja 2026" [ref=e171]:
- gridcell "11 maja 2026" [ref=e172]:
- generic "11 maja 2026" [ref=e175]: "11"
- gridcell "12 maja 2026" [ref=e177]:
- generic "12 maja 2026" [ref=e180]: "12"
- gridcell "13 maja 2026" [ref=e182]:
- generic "13 maja 2026" [ref=e185]: "13"
- gridcell "14 maja 2026" [ref=e187]:
- generic "14 maja 2026" [ref=e190]: "14"
- gridcell "15 maja 2026" [ref=e192]:
- generic "15 maja 2026" [ref=e195]: "15"
- gridcell "16 maja 2026" [ref=e197]:
- generic "16 maja 2026" [ref=e200]: "16"
- gridcell "17 maja 2026" [ref=e202]:
- generic "17 maja 2026" [ref=e205]: "17"
- row "18 maja 2026 19 maja 2026 20 maja 2026 21 maja 2026 22 maja 2026 23 maja 2026 24 maja 2026" [ref=e207]:
- gridcell "18 maja 2026" [ref=e208]:
- generic "18 maja 2026" [ref=e211]: "18"
- gridcell "19 maja 2026" [ref=e213]:
- generic "19 maja 2026" [ref=e216]: "19"
- gridcell "20 maja 2026" [ref=e218]:
- generic "20 maja 2026" [ref=e221]: "20"
- gridcell "21 maja 2026" [ref=e223]:
- generic "21 maja 2026" [ref=e226]: "21"
- gridcell "22 maja 2026" [ref=e228]:
- generic "22 maja 2026" [ref=e231]: "22"
- gridcell "23 maja 2026" [ref=e233]:
- generic "23 maja 2026" [ref=e236]: "23"
- gridcell "24 maja 2026" [ref=e238]:
- generic "24 maja 2026" [ref=e241]: "24"
- row "25 maja 2026 26 maja 2026 27 maja 2026 28 maja 2026 29 maja 2026 30 maja 2026 31 maja 2026" [ref=e243]:
- gridcell "25 maja 2026" [ref=e244]:
- generic "25 maja 2026" [ref=e247]: "25"
- gridcell "26 maja 2026" [ref=e249]:
- generic "26 maja 2026" [ref=e252]: "26"
- gridcell "27 maja 2026" [ref=e254]:
- generic "27 maja 2026" [ref=e257]: "27"
- gridcell "28 maja 2026" [ref=e259]:
- generic "28 maja 2026" [ref=e262]: "28"
- gridcell "29 maja 2026" [ref=e264]:
- generic "29 maja 2026" [ref=e267]: "29"
- gridcell "30 maja 2026" [ref=e269]:
- generic "30 maja 2026" [ref=e272]: "30"
- gridcell "31 maja 2026" [ref=e274]:
- generic "31 maja 2026" [ref=e277]: "31"
- row "1 czerwca 2026 2 czerwca 2026 3 czerwca 2026 4 czerwca 2026 5 czerwca 2026 6 czerwca 2026 7 czerwca 2026" [ref=e279]:
- gridcell "1 czerwca 2026" [ref=e280]:
- generic "1 czerwca 2026" [ref=e283]: "1"
- gridcell "2 czerwca 2026" [ref=e285]:
- generic "2 czerwca 2026" [ref=e288]: "2"
- gridcell "3 czerwca 2026" [ref=e290]:
- generic "3 czerwca 2026" [ref=e293]: "3"
- gridcell "4 czerwca 2026" [ref=e295]:
- generic "4 czerwca 2026" [ref=e298]: "4"
- gridcell "5 czerwca 2026" [ref=e300]:
- generic "5 czerwca 2026" [ref=e303]: "5"
- gridcell "6 czerwca 2026" [ref=e305]:
- generic "6 czerwca 2026" [ref=e308]: "6"
- gridcell "7 czerwca 2026" [ref=e310]:
- generic "7 czerwca 2026" [ref=e313]: "7"
- generic [ref=e316]:
- heading "Zapytaj o rezerwację" [level=4] [ref=e317]
- paragraph [ref=e318]: Wybierz jacht i wypełnij formularz — odezwiemy się w sprawie dostępności i cen.
- generic [ref=e319]:
- generic [ref=e320]:
- generic [ref=e321]: Jacht *
- combobox "Jacht *" [ref=e322]:
- option "— wybierz jacht —" [selected]
- option "Maja"
- option "Kubuś"
- generic [ref=e323]:
- generic [ref=e324]: Imię i nazwisko *
- textbox "Imię i nazwisko *" [ref=e325]
- generic [ref=e326]:
- generic [ref=e327]: Email *
- textbox "Email *" [ref=e328]
- generic [ref=e329]:
- generic [ref=e330]: Telefon *
- textbox "Telefon *" [ref=e331]
- generic [ref=e332]:
- generic [ref=e333]: Preferowane terminy
- textbox "Preferowane terminy" [ref=e334]:
- /placeholder: np. 15-22 lipca
- generic [ref=e335]:
- generic [ref=e336]: Wiadomość
- textbox "Wiadomość" [ref=e337]:
- /placeholder: Dodatkowe pytania lub uwagi...
- button "Wyślij zapytanie" [ref=e339] [cursor=pointer]
- contentinfo [ref=e340]:
- generic [ref=e347]:
- generic [ref=e348]:
- heading "Wypłyń z nami." [level=3] [ref=e350]:
- text: Wypłyń
- strong [ref=e351]: z nami
- text: .
- paragraph [ref=e353]: Przestronne kabiny, ciepły fornir, biała tapicerka i wszystko, czego potrzeba do długiego rejsu. Pokład zaprojektowany dla wygody całej załogi.
- generic [ref=e354]:
- paragraph [ref=e356]: Nawigacja
- list [ref=e358]:
- listitem [ref=e359]:
- link "Start" [ref=e360] [cursor=pointer]:
- /url: /
- generic [ref=e361]: Start
- listitem [ref=e362]:
- link "Sedna 26 „Maja\"" [ref=e363] [cursor=pointer]:
- /url: /maja/
- generic [ref=e364]: Sedna 26 „Maja"
- listitem [ref=e365]:
- link "Sedna 26 „Kubuś\"" [ref=e366] [cursor=pointer]:
- /url: /kubus/
- generic [ref=e367]: Sedna 26 „Kubuś"
- listitem [ref=e368]:
- link "O nas" [ref=e369] [cursor=pointer]:
- /url: /o-nas/
- generic [ref=e370]: O nas
- listitem [ref=e371]:
- link "Cennik" [ref=e372] [cursor=pointer]:
- /url: /cennik/
- generic [ref=e373]: Cennik
- listitem [ref=e374]:
- link "Kontakt" [ref=e375] [cursor=pointer]:
- /url: /kontakt/
- generic [ref=e376]: Kontakt
- generic [ref=e377]:
- paragraph [ref=e379]: Marina
- list [ref=e381]:
- listitem [ref=e382]:
- generic [ref=e383]: Keja Dedal
- listitem [ref=e384]:
- generic [ref=e385]: Polańczyk, Bieszczady
- listitem [ref=e386]:
- generic [ref=e387]: Jezioro Solińskie
- generic [ref=e400]:
- paragraph [ref=e402]: © 2026 PKMP Jachty. Wszystkie prawa zastrzeżone.
- paragraph [ref=e404]: Solina · Bieszczady · PL
- generic [ref=e410]: laptop

View File

@@ -0,0 +1,239 @@
- generic [active] [ref=e1]:
- link "Przejdź do treści" [ref=e2] [cursor=pointer]:
- /url: "#content"
- banner [ref=e3]:
- generic [ref=e5]:
- link [ref=e7] [cursor=pointer]:
- /url: https://jachty3.pagedev.pl
- navigation "Menu" [ref=e9]:
- list [ref=e10]:
- listitem [ref=e11]:
- link "Start" [ref=e12] [cursor=pointer]:
- /url: https://jachty3.pagedev.pl/
- listitem [ref=e13]:
- link "Maja" [ref=e14] [cursor=pointer]:
- /url: https://jachty3.pagedev.pl/maja/
- listitem [ref=e15]:
- link "Kubuś" [ref=e16] [cursor=pointer]:
- /url: https://jachty3.pagedev.pl/kubus/
- listitem [ref=e17]:
- link "O Nas" [ref=e18] [cursor=pointer]:
- /url: https://jachty3.pagedev.pl/o-nas/
- listitem [ref=e19]:
- link "Cennik" [ref=e20] [cursor=pointer]:
- /url: https://jachty3.pagedev.pl/cennik/
- listitem [ref=e21]:
- link "Blog" [ref=e22] [cursor=pointer]:
- /url: https://jachty3.pagedev.pl/blog/
- listitem [ref=e23]:
- link "Kontakt" [ref=e24] [cursor=pointer]:
- /url: https://jachty3.pagedev.pl/kontakt/
- listitem [ref=e25]:
- link "Zarezerwuj →" [ref=e26] [cursor=pointer]:
- /url: /rezerwacja-maja/
- main [ref=e27]:
- generic [ref=e30]:
- heading "Rezerwacja" [level=1] [ref=e34]
- generic [ref=e42]:
- paragraph [ref=e44]: Aby zarezerwować termin, wypełnij formularz po prawej stronie albo skontaktuj się z nami telefonicznie lub mailowo.
- generic "Legenda jachtów" [ref=e45]:
- generic [ref=e46]: Maja
- generic [ref=e48]: Kubuś
- generic [ref=e50]: Wspólne wydarzenia
- generic [ref=e52]:
- generic [ref=e54]:
- generic [ref=e55]:
- generic [ref=e56]:
- generic [ref=e57]:
- button "Poprzedni" [ref=e58] [cursor=pointer]:
- img [ref=e59]:
- button "Następny" [ref=e60] [cursor=pointer]:
- img [ref=e61]:
- button "Dziś" [disabled] [ref=e62]
- heading "maj 2026" [level=2] [ref=e64]
- generic "maj 2026" [ref=e65]:
- grid [ref=e67]:
- rowgroup [ref=e68]:
- row "poniedziałek wtorek środa czwartek piątek sobota niedziela" [ref=e72]:
- columnheader "poniedziałek" [ref=e73]:
- generic "poniedziałek" [ref=e75]: pon.
- columnheader "wtorek" [ref=e76]:
- generic "wtorek" [ref=e78]: wt.
- columnheader "środa" [ref=e79]:
- generic "środa" [ref=e81]: śr.
- columnheader "czwartek" [ref=e82]:
- generic "czwartek" [ref=e84]: czw.
- columnheader "piątek" [ref=e85]:
- generic "piątek" [ref=e87]: pt.
- columnheader "sobota" [ref=e88]:
- generic "sobota" [ref=e90]: sob.
- columnheader "niedziela" [ref=e91]:
- generic "niedziela" [ref=e93]: niedz.
- rowgroup [ref=e94]:
- generic [ref=e97]:
- row "27 kwietnia 2026 28 kwietnia 2026 29 kwietnia 2026 30 kwietnia 2026 1 maja 2026 2 maja 2026 3 maja 2026" [ref=e99]:
- gridcell "27 kwietnia 2026" [ref=e100]:
- generic "27 kwietnia 2026" [ref=e103]: "27"
- gridcell "28 kwietnia 2026" [ref=e105]:
- generic "28 kwietnia 2026" [ref=e108]: "28"
- gridcell "29 kwietnia 2026" [ref=e110]:
- generic "29 kwietnia 2026" [ref=e113]: "29"
- gridcell "30 kwietnia 2026" [ref=e115]:
- generic "30 kwietnia 2026" [ref=e118]: "30"
- gridcell "1 maja 2026" [ref=e120]:
- generic "1 maja 2026" [ref=e123]: "1"
- gridcell "2 maja 2026" [ref=e125]:
- generic "2 maja 2026" [ref=e128]: "2"
- gridcell "3 maja 2026" [ref=e130]:
- generic "3 maja 2026" [ref=e133]: "3"
- row "4 maja 2026 5 maja 2026 6 maja 2026 7 maja 2026 8 maja 2026 9 maja 2026 10 maja 2026" [ref=e135]:
- gridcell "4 maja 2026" [ref=e136]:
- generic "4 maja 2026" [ref=e139]: "4"
- gridcell "5 maja 2026" [ref=e141]:
- generic "5 maja 2026" [ref=e144]: "5"
- gridcell "6 maja 2026" [ref=e146]:
- generic "6 maja 2026" [ref=e149]: "6"
- gridcell "7 maja 2026" [ref=e151]:
- generic "7 maja 2026" [ref=e154]: "7"
- gridcell "8 maja 2026" [ref=e156]:
- generic "8 maja 2026" [ref=e159]: "8"
- gridcell "9 maja 2026" [ref=e161]:
- generic "9 maja 2026" [ref=e164]: "9"
- gridcell "10 maja 2026" [ref=e166]:
- generic "10 maja 2026" [ref=e169]: "10"
- row "11 maja 2026 12 maja 2026 13 maja 2026 14 maja 2026 15 maja 2026 16 maja 2026 17 maja 2026" [ref=e171]:
- gridcell "11 maja 2026" [ref=e172]:
- generic "11 maja 2026" [ref=e175]: "11"
- gridcell "12 maja 2026" [ref=e177]:
- generic "12 maja 2026" [ref=e180]: "12"
- gridcell "13 maja 2026" [ref=e182]:
- generic "13 maja 2026" [ref=e185]: "13"
- gridcell "14 maja 2026" [ref=e187]:
- generic "14 maja 2026" [ref=e190]: "14"
- gridcell "15 maja 2026" [ref=e192]:
- generic "15 maja 2026" [ref=e195]: "15"
- gridcell "16 maja 2026" [ref=e197]:
- generic "16 maja 2026" [ref=e200]: "16"
- gridcell "17 maja 2026" [ref=e202]:
- generic "17 maja 2026" [ref=e205]: "17"
- row "18 maja 2026 19 maja 2026 20 maja 2026 21 maja 2026 22 maja 2026 23 maja 2026 24 maja 2026" [ref=e207]:
- gridcell "18 maja 2026" [ref=e208]:
- generic "18 maja 2026" [ref=e211]: "18"
- gridcell "19 maja 2026" [ref=e213]:
- generic "19 maja 2026" [ref=e216]: "19"
- gridcell "20 maja 2026" [ref=e218]:
- generic "20 maja 2026" [ref=e221]: "20"
- gridcell "21 maja 2026" [ref=e223]:
- generic "21 maja 2026" [ref=e226]: "21"
- gridcell "22 maja 2026" [ref=e228]:
- generic "22 maja 2026" [ref=e231]: "22"
- gridcell "23 maja 2026" [ref=e233]:
- generic "23 maja 2026" [ref=e236]: "23"
- gridcell "24 maja 2026" [ref=e238]:
- generic "24 maja 2026" [ref=e241]: "24"
- row "25 maja 2026 26 maja 2026 27 maja 2026 28 maja 2026 29 maja 2026 30 maja 2026 31 maja 2026" [ref=e243]:
- gridcell "25 maja 2026" [ref=e244]:
- generic "25 maja 2026" [ref=e247]: "25"
- gridcell "26 maja 2026" [ref=e249]:
- generic "26 maja 2026" [ref=e252]: "26"
- gridcell "27 maja 2026" [ref=e254]:
- generic "27 maja 2026" [ref=e257]: "27"
- gridcell "28 maja 2026" [ref=e259]:
- generic "28 maja 2026" [ref=e262]: "28"
- gridcell "29 maja 2026" [ref=e264]:
- generic "29 maja 2026" [ref=e267]: "29"
- gridcell "30 maja 2026" [ref=e269]:
- generic "30 maja 2026" [ref=e272]: "30"
- gridcell "31 maja 2026" [ref=e274]:
- generic "31 maja 2026" [ref=e277]: "31"
- row "1 czerwca 2026 2 czerwca 2026 3 czerwca 2026 4 czerwca 2026 5 czerwca 2026 6 czerwca 2026 7 czerwca 2026" [ref=e279]:
- gridcell "1 czerwca 2026" [ref=e280]:
- generic "1 czerwca 2026" [ref=e283]: "1"
- gridcell "2 czerwca 2026" [ref=e285]:
- generic "2 czerwca 2026" [ref=e288]: "2"
- gridcell "3 czerwca 2026" [ref=e290]:
- generic "3 czerwca 2026" [ref=e293]: "3"
- gridcell "4 czerwca 2026" [ref=e295]:
- generic "4 czerwca 2026" [ref=e298]: "4"
- gridcell "5 czerwca 2026" [ref=e300]:
- generic "5 czerwca 2026" [ref=e303]: "5"
- gridcell "6 czerwca 2026" [ref=e305]:
- generic "6 czerwca 2026" [ref=e308]: "6"
- gridcell "7 czerwca 2026" [ref=e310]:
- generic "7 czerwca 2026" [ref=e313]: "7"
- generic [ref=e316]:
- heading "Zapytaj o rezerwację" [level=4] [ref=e317]
- paragraph [ref=e318]: Wybierz jacht i wypełnij formularz — odezwiemy się w sprawie dostępności i cen.
- generic [ref=e319]:
- generic [ref=e320]:
- generic [ref=e321]: Jacht *
- combobox "Jacht *" [ref=e322] [cursor=pointer]:
- option "— wybierz jacht —" [selected]
- option "Maja"
- option "Kubuś"
- generic [ref=e323]:
- generic [ref=e324]: Imię i nazwisko *
- textbox "Imię i nazwisko *" [ref=e325]
- generic [ref=e326]:
- generic [ref=e327]: Email *
- textbox "Email *" [ref=e328]
- generic [ref=e329]:
- generic [ref=e330]: Telefon *
- textbox "Telefon *" [ref=e331]
- generic [ref=e332]:
- generic [ref=e333]: Preferowane terminy
- textbox "Preferowane terminy" [ref=e334]:
- /placeholder: np. 15-22 lipca
- generic [ref=e335]:
- generic [ref=e336]: Wiadomość
- textbox "Wiadomość" [ref=e337]:
- /placeholder: Dodatkowe pytania lub uwagi...
- button "Wyślij zapytanie" [ref=e339] [cursor=pointer]
- contentinfo [ref=e340]:
- generic [ref=e347]:
- generic [ref=e348]:
- heading "Wypłyń z nami." [level=3] [ref=e350]:
- text: Wypłyń
- strong [ref=e351]: z nami
- text: .
- paragraph [ref=e353]: Przestronne kabiny, ciepły fornir, biała tapicerka i wszystko, czego potrzeba do długiego rejsu. Pokład zaprojektowany dla wygody całej załogi.
- generic [ref=e354]:
- paragraph [ref=e356]: Nawigacja
- list [ref=e358]:
- listitem [ref=e359]:
- link "Start" [ref=e360] [cursor=pointer]:
- /url: /
- generic [ref=e361]: Start
- listitem [ref=e362]:
- link "Sedna 26 „Maja\"" [ref=e363] [cursor=pointer]:
- /url: /maja/
- generic [ref=e364]: Sedna 26 „Maja"
- listitem [ref=e365]:
- link "Sedna 26 „Kubuś\"" [ref=e366] [cursor=pointer]:
- /url: /kubus/
- generic [ref=e367]: Sedna 26 „Kubuś"
- listitem [ref=e368]:
- link "O nas" [ref=e369] [cursor=pointer]:
- /url: /o-nas/
- generic [ref=e370]: O nas
- listitem [ref=e371]:
- link "Cennik" [ref=e372] [cursor=pointer]:
- /url: /cennik/
- generic [ref=e373]: Cennik
- listitem [ref=e374]:
- link "Kontakt" [ref=e375] [cursor=pointer]:
- /url: /kontakt/
- generic [ref=e376]: Kontakt
- generic [ref=e377]:
- paragraph [ref=e379]: Marina
- list [ref=e381]:
- listitem [ref=e382]:
- generic [ref=e383]: Keja Dedal
- listitem [ref=e384]:
- generic [ref=e385]: Polańczyk, Bieszczady
- listitem [ref=e386]:
- generic [ref=e387]: Jezioro Solińskie
- generic [ref=e400]:
- paragraph [ref=e402]: © 2026 PKMP Jachty. Wszystkie prawa zastrzeżone.
- paragraph [ref=e404]: Solina · Bieszczady · PL
- generic [ref=e410]: laptop

View File

@@ -0,0 +1,240 @@
- generic [active] [ref=e1]:
- link "Przejdź do treści" [ref=e2] [cursor=pointer]:
- /url: "#content"
- banner [ref=e3]:
- generic [ref=e5]:
- link [ref=e7] [cursor=pointer]:
- /url: https://jachty3.pagedev.pl
- navigation "Menu" [ref=e9]:
- list [ref=e10]:
- listitem [ref=e11]:
- link "Start" [ref=e12] [cursor=pointer]:
- /url: https://jachty3.pagedev.pl/
- listitem [ref=e13]:
- link "Maja" [ref=e14] [cursor=pointer]:
- /url: https://jachty3.pagedev.pl/maja/
- listitem [ref=e15]:
- link "Kubuś" [ref=e16] [cursor=pointer]:
- /url: https://jachty3.pagedev.pl/kubus/
- listitem [ref=e17]:
- link "O Nas" [ref=e18] [cursor=pointer]:
- /url: https://jachty3.pagedev.pl/o-nas/
- listitem [ref=e19]:
- link "Cennik" [ref=e20] [cursor=pointer]:
- /url: https://jachty3.pagedev.pl/cennik/
- listitem [ref=e21]:
- link "Blog" [ref=e22] [cursor=pointer]:
- /url: https://jachty3.pagedev.pl/blog/
- listitem [ref=e23]:
- link "Kontakt" [ref=e24] [cursor=pointer]:
- /url: https://jachty3.pagedev.pl/kontakt/
- listitem [ref=e25]:
- link "Zarezerwuj →" [ref=e26] [cursor=pointer]:
- /url: /rezerwacja-maja/
- main [ref=e27]:
- generic [ref=e30]:
- generic [ref=e32]:
- paragraph [ref=e34]: Sedna 26
- heading "Maja" [level=1] [ref=e36]
- generic [ref=e44]:
- 'heading "Dostepnosc: Maja" [level=3] [ref=e46]'
- paragraph [ref=e48]: Zielone dni są dostępne, czerwone są zajęte lub zablokowane. Aby zarezerwować jacht, skontaktuj się z nami telefonicznie lub mailowo.
- generic "Legenda kalendarza rezerwacji" [ref=e49]:
- generic [ref=e50]: Dostępny
- generic [ref=e52]: Zajęty / zablokowany
- generic [ref=e54]: Data przeszła
- generic [ref=e56]:
- generic [ref=e58]:
- generic [ref=e59]:
- generic [ref=e60]:
- generic [ref=e61]:
- button "Poprzedni" [ref=e62] [cursor=pointer]:
- img [ref=e63]:
- button "Następny" [ref=e64] [cursor=pointer]:
- img [ref=e65]:
- button "Dzisiaj" [disabled] [ref=e66]
- heading "maj 2026" [level=2] [ref=e68]
- button "Miesiąc" [pressed] [ref=e70] [cursor=pointer]
- generic "maj 2026" [ref=e71]:
- grid [ref=e73]:
- rowgroup [ref=e74]:
- row "poniedziałek wtorek środa czwartek piątek sobota niedziela" [ref=e78]:
- columnheader "poniedziałek" [ref=e79]:
- generic "poniedziałek" [ref=e81]: pon.
- columnheader "wtorek" [ref=e82]:
- generic "wtorek" [ref=e84]: wt.
- columnheader "środa" [ref=e85]:
- generic "środa" [ref=e87]: śr.
- columnheader "czwartek" [ref=e88]:
- generic "czwartek" [ref=e90]: czw.
- columnheader "piątek" [ref=e91]:
- generic "piątek" [ref=e93]: pt.
- columnheader "sobota" [ref=e94]:
- generic "sobota" [ref=e96]: sob.
- columnheader "niedziela" [ref=e97]:
- generic "niedziela" [ref=e99]: niedz.
- rowgroup [ref=e100]:
- generic [ref=e103]:
- row "27 kwietnia 2026 28 kwietnia 2026 29 kwietnia 2026 30 kwietnia 2026 1 maja 2026 2 maja 2026 3 maja 2026" [ref=e105]:
- gridcell "27 kwietnia 2026" [ref=e106]:
- generic "27 kwietnia 2026" [ref=e109]: "27"
- gridcell "28 kwietnia 2026" [ref=e111]:
- generic "28 kwietnia 2026" [ref=e114]: "28"
- gridcell "29 kwietnia 2026" [ref=e116]:
- generic "29 kwietnia 2026" [ref=e119]: "29"
- gridcell "30 kwietnia 2026" [ref=e121]:
- generic "30 kwietnia 2026" [ref=e124]: "30"
- gridcell "1 maja 2026" [ref=e126]:
- generic "1 maja 2026" [ref=e129]: "1"
- gridcell "2 maja 2026" [ref=e131]:
- generic "2 maja 2026" [ref=e134]: "2"
- gridcell "3 maja 2026" [ref=e136]:
- generic "3 maja 2026" [ref=e139]: "3"
- row "4 maja 2026 5 maja 2026 6 maja 2026 7 maja 2026 8 maja 2026 9 maja 2026 10 maja 2026" [ref=e141]:
- gridcell "4 maja 2026" [ref=e142]:
- generic "4 maja 2026" [ref=e145]: "4"
- gridcell "5 maja 2026" [ref=e147]:
- generic "5 maja 2026" [ref=e150]: "5"
- gridcell "6 maja 2026" [ref=e152]:
- generic "6 maja 2026" [ref=e155]: "6"
- gridcell "7 maja 2026" [ref=e157]:
- generic "7 maja 2026" [ref=e160]: "7"
- gridcell "8 maja 2026" [ref=e162]:
- generic "8 maja 2026" [ref=e165]: "8"
- gridcell "9 maja 2026" [ref=e167]:
- generic "9 maja 2026" [ref=e170]: "9"
- gridcell "10 maja 2026" [ref=e172]:
- generic "10 maja 2026" [ref=e175]: "10"
- row "11 maja 2026 12 maja 2026 13 maja 2026 14 maja 2026 15 maja 2026 16 maja 2026 17 maja 2026" [ref=e177]:
- gridcell "11 maja 2026" [ref=e178]:
- generic "11 maja 2026" [ref=e181]: "11"
- gridcell "12 maja 2026" [ref=e183]:
- generic "12 maja 2026" [ref=e186]: "12"
- gridcell "13 maja 2026" [ref=e188]:
- generic "13 maja 2026" [ref=e191]: "13"
- gridcell "14 maja 2026" [ref=e193]:
- generic "14 maja 2026" [ref=e196]: "14"
- gridcell "15 maja 2026" [ref=e198]:
- generic "15 maja 2026" [ref=e201]: "15"
- gridcell "16 maja 2026" [ref=e203]:
- generic "16 maja 2026" [ref=e206]: "16"
- gridcell "17 maja 2026" [ref=e208]:
- generic "17 maja 2026" [ref=e211]: "17"
- row "18 maja 2026 19 maja 2026 20 maja 2026 21 maja 2026 22 maja 2026 23 maja 2026 24 maja 2026" [ref=e213]:
- gridcell "18 maja 2026" [ref=e214]:
- generic "18 maja 2026" [ref=e217]: "18"
- gridcell "19 maja 2026" [ref=e219]:
- generic "19 maja 2026" [ref=e222]: "19"
- gridcell "20 maja 2026" [ref=e224]:
- generic "20 maja 2026" [ref=e227]: "20"
- gridcell "21 maja 2026" [ref=e229]:
- generic "21 maja 2026" [ref=e232]: "21"
- gridcell "22 maja 2026" [ref=e234]:
- generic "22 maja 2026" [ref=e237]: "22"
- gridcell "23 maja 2026" [ref=e239]:
- generic "23 maja 2026" [ref=e242]: "23"
- gridcell "24 maja 2026" [ref=e244]:
- generic "24 maja 2026" [ref=e247]: "24"
- row "25 maja 2026 26 maja 2026 27 maja 2026 28 maja 2026 29 maja 2026 30 maja 2026 31 maja 2026" [ref=e249]:
- gridcell "25 maja 2026" [ref=e250]:
- generic "25 maja 2026" [ref=e253]: "25"
- gridcell "26 maja 2026" [ref=e255]:
- generic "26 maja 2026" [ref=e258]: "26"
- gridcell "27 maja 2026" [ref=e260]:
- generic "27 maja 2026" [ref=e263]: "27"
- gridcell "28 maja 2026" [ref=e265]:
- generic "28 maja 2026" [ref=e268]: "28"
- gridcell "29 maja 2026" [ref=e270]:
- generic "29 maja 2026" [ref=e273]: "29"
- gridcell "30 maja 2026" [ref=e275]:
- generic "30 maja 2026" [ref=e278]: "30"
- gridcell "31 maja 2026" [ref=e280]:
- generic "31 maja 2026" [ref=e283]: "31"
- row "1 czerwca 2026 2 czerwca 2026 3 czerwca 2026 4 czerwca 2026 5 czerwca 2026 6 czerwca 2026 7 czerwca 2026" [ref=e285]:
- gridcell "1 czerwca 2026" [ref=e286]:
- generic "1 czerwca 2026" [ref=e289]: "1"
- gridcell "2 czerwca 2026" [ref=e291]:
- generic "2 czerwca 2026" [ref=e294]: "2"
- gridcell "3 czerwca 2026" [ref=e296]:
- generic "3 czerwca 2026" [ref=e299]: "3"
- gridcell "4 czerwca 2026" [ref=e301]:
- generic "4 czerwca 2026" [ref=e304]: "4"
- gridcell "5 czerwca 2026" [ref=e306]:
- generic "5 czerwca 2026" [ref=e309]: "5"
- gridcell "6 czerwca 2026" [ref=e311]:
- generic "6 czerwca 2026" [ref=e314]: "6"
- gridcell "7 czerwca 2026" [ref=e316]:
- generic "7 czerwca 2026" [ref=e319]: "7"
- generic [ref=e322]:
- heading "Zapytaj o rezerwację" [level=4] [ref=e323]
- paragraph [ref=e324]: Wypełnij formularz, a skontaktujemy się z Tobą w sprawie rezerwacji.
- generic [ref=e325]:
- generic [ref=e326]:
- generic [ref=e327]: Imię i nazwisko *
- textbox "Imię i nazwisko *" [ref=e328]
- generic [ref=e329]:
- generic [ref=e330]: Email *
- textbox "Email *" [ref=e331]
- generic [ref=e332]:
- generic [ref=e333]: Telefon *
- textbox "Telefon *" [ref=e334]
- generic [ref=e335]:
- generic [ref=e336]: Preferowane terminy
- textbox "Preferowane terminy" [ref=e337]:
- /placeholder: np. 15-22 lipca
- generic [ref=e338]:
- generic [ref=e339]: Wiadomość
- textbox "Wiadomość" [ref=e340]:
- /placeholder: Dodatkowe pytania lub uwagi...
- button "Wyślij zapytanie" [ref=e342] [cursor=pointer]
- generic "Szybki wybór jachtu" [ref=e343]:
- generic [ref=e344]: "Zobacz też:"
- button "Kubuś" [ref=e346] [cursor=pointer]
- contentinfo [ref=e347]:
- generic [ref=e354]:
- generic [ref=e355]:
- heading "Wypłyń z nami." [level=3] [ref=e357]:
- text: Wypłyń
- strong [ref=e358]: z nami
- text: .
- paragraph [ref=e360]: Przestronne kabiny, ciepły fornir, biała tapicerka i wszystko, czego potrzeba do długiego rejsu. Pokład zaprojektowany dla wygody całej załogi.
- generic [ref=e361]:
- paragraph [ref=e363]: Nawigacja
- list [ref=e365]:
- listitem [ref=e366]:
- link "Start" [ref=e367] [cursor=pointer]:
- /url: /
- generic [ref=e368]: Start
- listitem [ref=e369]:
- link "Sedna 26 „Maja\"" [ref=e370] [cursor=pointer]:
- /url: /maja/
- generic [ref=e371]: Sedna 26 „Maja"
- listitem [ref=e372]:
- link "Sedna 26 „Kubuś\"" [ref=e373] [cursor=pointer]:
- /url: /kubus/
- generic [ref=e374]: Sedna 26 „Kubuś"
- listitem [ref=e375]:
- link "O nas" [ref=e376] [cursor=pointer]:
- /url: /o-nas/
- generic [ref=e377]: O nas
- listitem [ref=e378]:
- link "Cennik" [ref=e379] [cursor=pointer]:
- /url: /cennik/
- generic [ref=e380]: Cennik
- listitem [ref=e381]:
- link "Kontakt" [ref=e382] [cursor=pointer]:
- /url: /kontakt/
- generic [ref=e383]: Kontakt
- generic [ref=e384]:
- paragraph [ref=e386]: Marina
- list [ref=e388]:
- listitem [ref=e389]:
- generic [ref=e390]: Keja Dedal
- listitem [ref=e391]:
- generic [ref=e392]: Polańczyk, Bieszczady
- listitem [ref=e393]:
- generic [ref=e394]: Jezioro Solińskie
- generic [ref=e407]:
- paragraph [ref=e409]: © 2026 PKMP Jachty. Wszystkie prawa zastrzeżone.
- paragraph [ref=e411]: Solina · Bieszczady · PL
- generic [ref=e417]: laptop

View File

@@ -0,0 +1,239 @@
- generic [active] [ref=e1]:
- link "Przejdź do treści" [ref=e2] [cursor=pointer]:
- /url: "#content"
- banner [ref=e3]:
- generic [ref=e5]:
- link [ref=e7] [cursor=pointer]:
- /url: https://jachty3.pagedev.pl
- navigation "Menu" [ref=e9]:
- list [ref=e10]:
- listitem [ref=e11]:
- link "Start" [ref=e12] [cursor=pointer]:
- /url: https://jachty3.pagedev.pl/
- listitem [ref=e13]:
- link "Maja" [ref=e14] [cursor=pointer]:
- /url: https://jachty3.pagedev.pl/maja/
- listitem [ref=e15]:
- link "Kubuś" [ref=e16] [cursor=pointer]:
- /url: https://jachty3.pagedev.pl/kubus/
- listitem [ref=e17]:
- link "O Nas" [ref=e18] [cursor=pointer]:
- /url: https://jachty3.pagedev.pl/o-nas/
- listitem [ref=e19]:
- link "Cennik" [ref=e20] [cursor=pointer]:
- /url: https://jachty3.pagedev.pl/cennik/
- listitem [ref=e21]:
- link "Blog" [ref=e22] [cursor=pointer]:
- /url: https://jachty3.pagedev.pl/blog/
- listitem [ref=e23]:
- link "Kontakt" [ref=e24] [cursor=pointer]:
- /url: https://jachty3.pagedev.pl/kontakt/
- listitem [ref=e25]:
- link "Zarezerwuj →" [ref=e26] [cursor=pointer]:
- /url: /rezerwacja-maja/
- main [ref=e27]:
- generic [ref=e30]:
- heading "Rezerwacja" [level=1] [ref=e34]
- generic [ref=e42]:
- paragraph [ref=e44]: Aby zarezerwować termin, wypełnij formularz po prawej stronie albo skontaktuj się z nami telefonicznie lub mailowo.
- generic "Legenda jachtów" [ref=e45]:
- generic [ref=e46]: Maja
- generic [ref=e48]: Kubuś
- generic [ref=e50]: Wspólne wydarzenia
- generic [ref=e52]:
- generic [ref=e54]:
- generic [ref=e55]:
- generic [ref=e56]:
- generic [ref=e57]:
- button "Poprzedni" [ref=e58] [cursor=pointer]:
- img [ref=e59]:
- button "Następny" [ref=e60] [cursor=pointer]:
- img [ref=e61]:
- button "Dziś" [disabled] [ref=e62]
- heading "maj 2026" [level=2] [ref=e64]
- generic "maj 2026" [ref=e65]:
- grid [ref=e67]:
- rowgroup [ref=e68]:
- row "poniedziałek wtorek środa czwartek piątek sobota niedziela" [ref=e72]:
- columnheader "poniedziałek" [ref=e73]:
- generic "poniedziałek" [ref=e75]: pon.
- columnheader "wtorek" [ref=e76]:
- generic "wtorek" [ref=e78]: wt.
- columnheader "środa" [ref=e79]:
- generic "środa" [ref=e81]: śr.
- columnheader "czwartek" [ref=e82]:
- generic "czwartek" [ref=e84]: czw.
- columnheader "piątek" [ref=e85]:
- generic "piątek" [ref=e87]: pt.
- columnheader "sobota" [ref=e88]:
- generic "sobota" [ref=e90]: sob.
- columnheader "niedziela" [ref=e91]:
- generic "niedziela" [ref=e93]: niedz.
- rowgroup [ref=e94]:
- generic [ref=e97]:
- row "27 kwietnia 2026 28 kwietnia 2026 29 kwietnia 2026 30 kwietnia 2026 1 maja 2026 2 maja 2026 3 maja 2026" [ref=e99]:
- gridcell "27 kwietnia 2026" [ref=e100]:
- generic "27 kwietnia 2026" [ref=e103]: "27"
- gridcell "28 kwietnia 2026" [ref=e105]:
- generic "28 kwietnia 2026" [ref=e108]: "28"
- gridcell "29 kwietnia 2026" [ref=e110]:
- generic "29 kwietnia 2026" [ref=e113]: "29"
- gridcell "30 kwietnia 2026" [ref=e115]:
- generic "30 kwietnia 2026" [ref=e118]: "30"
- gridcell "1 maja 2026" [ref=e120]:
- generic "1 maja 2026" [ref=e123]: "1"
- gridcell "2 maja 2026" [ref=e125]:
- generic "2 maja 2026" [ref=e128]: "2"
- gridcell "3 maja 2026" [ref=e130]:
- generic "3 maja 2026" [ref=e133]: "3"
- row "4 maja 2026 5 maja 2026 6 maja 2026 7 maja 2026 8 maja 2026 9 maja 2026 10 maja 2026" [ref=e135]:
- gridcell "4 maja 2026" [ref=e136]:
- generic "4 maja 2026" [ref=e139]: "4"
- gridcell "5 maja 2026" [ref=e141]:
- generic "5 maja 2026" [ref=e144]: "5"
- gridcell "6 maja 2026" [ref=e146]:
- generic "6 maja 2026" [ref=e149]: "6"
- gridcell "7 maja 2026" [ref=e151]:
- generic "7 maja 2026" [ref=e154]: "7"
- gridcell "8 maja 2026" [ref=e156]:
- generic "8 maja 2026" [ref=e159]: "8"
- gridcell "9 maja 2026" [ref=e161]:
- generic "9 maja 2026" [ref=e164]: "9"
- gridcell "10 maja 2026" [ref=e166]:
- generic "10 maja 2026" [ref=e169]: "10"
- row "11 maja 2026 12 maja 2026 13 maja 2026 14 maja 2026 15 maja 2026 16 maja 2026 17 maja 2026" [ref=e171]:
- gridcell "11 maja 2026" [ref=e172]:
- generic "11 maja 2026" [ref=e175]: "11"
- gridcell "12 maja 2026" [ref=e177]:
- generic "12 maja 2026" [ref=e180]: "12"
- gridcell "13 maja 2026" [ref=e182]:
- generic "13 maja 2026" [ref=e185]: "13"
- gridcell "14 maja 2026" [ref=e187]:
- generic "14 maja 2026" [ref=e190]: "14"
- gridcell "15 maja 2026" [ref=e192]:
- generic "15 maja 2026" [ref=e195]: "15"
- gridcell "16 maja 2026" [ref=e197]:
- generic "16 maja 2026" [ref=e200]: "16"
- gridcell "17 maja 2026" [ref=e202]:
- generic "17 maja 2026" [ref=e205]: "17"
- row "18 maja 2026 19 maja 2026 20 maja 2026 21 maja 2026 22 maja 2026 23 maja 2026 24 maja 2026" [ref=e207]:
- gridcell "18 maja 2026" [ref=e208]:
- generic "18 maja 2026" [ref=e211]: "18"
- gridcell "19 maja 2026" [ref=e213]:
- generic "19 maja 2026" [ref=e216]: "19"
- gridcell "20 maja 2026" [ref=e218]:
- generic "20 maja 2026" [ref=e221]: "20"
- gridcell "21 maja 2026" [ref=e223]:
- generic "21 maja 2026" [ref=e226]: "21"
- gridcell "22 maja 2026" [ref=e228]:
- generic "22 maja 2026" [ref=e231]: "22"
- gridcell "23 maja 2026" [ref=e233]:
- generic "23 maja 2026" [ref=e236]: "23"
- gridcell "24 maja 2026" [ref=e238]:
- generic "24 maja 2026" [ref=e241]: "24"
- row "25 maja 2026 26 maja 2026 27 maja 2026 28 maja 2026 29 maja 2026 30 maja 2026 31 maja 2026" [ref=e243]:
- gridcell "25 maja 2026" [ref=e244]:
- generic "25 maja 2026" [ref=e247]: "25"
- gridcell "26 maja 2026" [ref=e249]:
- generic "26 maja 2026" [ref=e252]: "26"
- gridcell "27 maja 2026" [ref=e254]:
- generic "27 maja 2026" [ref=e257]: "27"
- gridcell "28 maja 2026" [ref=e259]:
- generic "28 maja 2026" [ref=e262]: "28"
- gridcell "29 maja 2026" [ref=e264]:
- generic "29 maja 2026" [ref=e267]: "29"
- gridcell "30 maja 2026" [ref=e269]:
- generic "30 maja 2026" [ref=e272]: "30"
- gridcell "31 maja 2026" [ref=e274]:
- generic "31 maja 2026" [ref=e277]: "31"
- row "1 czerwca 2026 2 czerwca 2026 3 czerwca 2026 4 czerwca 2026 5 czerwca 2026 6 czerwca 2026 7 czerwca 2026" [ref=e279]:
- gridcell "1 czerwca 2026" [ref=e280]:
- generic "1 czerwca 2026" [ref=e283]: "1"
- gridcell "2 czerwca 2026" [ref=e285]:
- generic "2 czerwca 2026" [ref=e288]: "2"
- gridcell "3 czerwca 2026" [ref=e290]:
- generic "3 czerwca 2026" [ref=e293]: "3"
- gridcell "4 czerwca 2026" [ref=e295]:
- generic "4 czerwca 2026" [ref=e298]: "4"
- gridcell "5 czerwca 2026" [ref=e300]:
- generic "5 czerwca 2026" [ref=e303]: "5"
- gridcell "6 czerwca 2026" [ref=e305]:
- generic "6 czerwca 2026" [ref=e308]: "6"
- gridcell "7 czerwca 2026" [ref=e310]:
- generic "7 czerwca 2026" [ref=e313]: "7"
- generic [ref=e316]:
- heading "Zapytaj o rezerwację" [level=4] [ref=e317]
- paragraph [ref=e318]: Wybierz jacht i wypełnij formularz — odezwiemy się w sprawie dostępności i cen.
- generic [ref=e319]:
- generic [ref=e320]:
- generic [ref=e321]: Jacht *
- combobox "Jacht *" [ref=e322] [cursor=pointer]:
- option "— wybierz jacht —" [selected]
- option "Maja"
- option "Kubuś"
- generic [ref=e323]:
- generic [ref=e324]: Imię i nazwisko *
- textbox "Imię i nazwisko *" [ref=e325]
- generic [ref=e326]:
- generic [ref=e327]: Email *
- textbox "Email *" [ref=e328]
- generic [ref=e329]:
- generic [ref=e330]: Telefon *
- textbox "Telefon *" [ref=e331]
- generic [ref=e332]:
- generic [ref=e333]: Preferowane terminy
- textbox "Preferowane terminy" [ref=e334]:
- /placeholder: np. 15-22 lipca
- generic [ref=e335]:
- generic [ref=e336]: Wiadomość
- textbox "Wiadomość" [ref=e337]:
- /placeholder: Dodatkowe pytania lub uwagi...
- button "Wyślij zapytanie" [ref=e339] [cursor=pointer]
- contentinfo [ref=e340]:
- generic [ref=e347]:
- generic [ref=e348]:
- heading "Wypłyń z nami." [level=3] [ref=e350]:
- text: Wypłyń
- strong [ref=e351]: z nami
- text: .
- paragraph [ref=e353]: Przestronne kabiny, ciepły fornir, biała tapicerka i wszystko, czego potrzeba do długiego rejsu. Pokład zaprojektowany dla wygody całej załogi.
- generic [ref=e354]:
- paragraph [ref=e356]: Nawigacja
- list [ref=e358]:
- listitem [ref=e359]:
- link "Start" [ref=e360] [cursor=pointer]:
- /url: /
- generic [ref=e361]: Start
- listitem [ref=e362]:
- link "Sedna 26 „Maja\"" [ref=e363] [cursor=pointer]:
- /url: /maja/
- generic [ref=e364]: Sedna 26 „Maja"
- listitem [ref=e365]:
- link "Sedna 26 „Kubuś\"" [ref=e366] [cursor=pointer]:
- /url: /kubus/
- generic [ref=e367]: Sedna 26 „Kubuś"
- listitem [ref=e368]:
- link "O nas" [ref=e369] [cursor=pointer]:
- /url: /o-nas/
- generic [ref=e370]: O nas
- listitem [ref=e371]:
- link "Cennik" [ref=e372] [cursor=pointer]:
- /url: /cennik/
- generic [ref=e373]: Cennik
- listitem [ref=e374]:
- link "Kontakt" [ref=e375] [cursor=pointer]:
- /url: /kontakt/
- generic [ref=e376]: Kontakt
- generic [ref=e377]:
- paragraph [ref=e379]: Marina
- list [ref=e381]:
- listitem [ref=e382]:
- generic [ref=e383]: Keja Dedal
- listitem [ref=e384]:
- generic [ref=e385]: Polańczyk, Bieszczady
- listitem [ref=e386]:
- generic [ref=e387]: Jezioro Solińskie
- generic [ref=e400]:
- paragraph [ref=e402]: © 2026 PKMP Jachty. Wszystkie prawa zastrzeżone.
- paragraph [ref=e404]: Solina · Bieszczady · PL
- generic [ref=e410]: laptop

View File

@@ -0,0 +1,240 @@
- generic [active] [ref=e1]:
- link "Przejdź do treści" [ref=e2] [cursor=pointer]:
- /url: "#content"
- banner [ref=e3]:
- generic [ref=e5]:
- link [ref=e7] [cursor=pointer]:
- /url: https://jachty3.pagedev.pl
- navigation "Menu" [ref=e9]:
- list [ref=e10]:
- listitem [ref=e11]:
- link "Start" [ref=e12] [cursor=pointer]:
- /url: https://jachty3.pagedev.pl/
- listitem [ref=e13]:
- link "Maja" [ref=e14] [cursor=pointer]:
- /url: https://jachty3.pagedev.pl/maja/
- listitem [ref=e15]:
- link "Kubuś" [ref=e16] [cursor=pointer]:
- /url: https://jachty3.pagedev.pl/kubus/
- listitem [ref=e17]:
- link "O Nas" [ref=e18] [cursor=pointer]:
- /url: https://jachty3.pagedev.pl/o-nas/
- listitem [ref=e19]:
- link "Cennik" [ref=e20] [cursor=pointer]:
- /url: https://jachty3.pagedev.pl/cennik/
- listitem [ref=e21]:
- link "Blog" [ref=e22] [cursor=pointer]:
- /url: https://jachty3.pagedev.pl/blog/
- listitem [ref=e23]:
- link "Kontakt" [ref=e24] [cursor=pointer]:
- /url: https://jachty3.pagedev.pl/kontakt/
- listitem [ref=e25]:
- link "Zarezerwuj →" [ref=e26] [cursor=pointer]:
- /url: /rezerwacja-maja/
- main [ref=e27]:
- generic [ref=e30]:
- generic [ref=e32]:
- paragraph [ref=e34]: Sedna 26
- heading "Maja" [level=1] [ref=e36]
- generic [ref=e44]:
- 'heading "Dostepnosc: Maja" [level=3] [ref=e46]'
- paragraph [ref=e48]: Zielone dni są dostępne, czerwone są zajęte lub zablokowane. Aby zarezerwować jacht, skontaktuj się z nami telefonicznie lub mailowo.
- generic "Legenda kalendarza rezerwacji" [ref=e49]:
- generic [ref=e50]: Dostępny
- generic [ref=e52]: Zajęty / zablokowany
- generic [ref=e54]: Data przeszła
- generic [ref=e56]:
- generic [ref=e58]:
- generic [ref=e59]:
- generic [ref=e60]:
- generic [ref=e61]:
- button "Poprzedni" [ref=e62] [cursor=pointer]:
- img [ref=e63]:
- button "Następny" [ref=e64] [cursor=pointer]:
- img [ref=e65]:
- button "Dzisiaj" [disabled] [ref=e66]
- heading "maj 2026" [level=2] [ref=e68]
- button "Miesiąc" [pressed] [ref=e70] [cursor=pointer]
- generic "maj 2026" [ref=e71]:
- grid [ref=e73]:
- rowgroup [ref=e74]:
- row "poniedziałek wtorek środa czwartek piątek sobota niedziela" [ref=e78]:
- columnheader "poniedziałek" [ref=e79]:
- generic "poniedziałek" [ref=e81]: pon.
- columnheader "wtorek" [ref=e82]:
- generic "wtorek" [ref=e84]: wt.
- columnheader "środa" [ref=e85]:
- generic "środa" [ref=e87]: śr.
- columnheader "czwartek" [ref=e88]:
- generic "czwartek" [ref=e90]: czw.
- columnheader "piątek" [ref=e91]:
- generic "piątek" [ref=e93]: pt.
- columnheader "sobota" [ref=e94]:
- generic "sobota" [ref=e96]: sob.
- columnheader "niedziela" [ref=e97]:
- generic "niedziela" [ref=e99]: niedz.
- rowgroup [ref=e100]:
- generic [ref=e103]:
- row "27 kwietnia 2026 28 kwietnia 2026 29 kwietnia 2026 30 kwietnia 2026 1 maja 2026 2 maja 2026 3 maja 2026" [ref=e105]:
- gridcell "27 kwietnia 2026" [ref=e106]:
- generic "27 kwietnia 2026" [ref=e109]: "27"
- gridcell "28 kwietnia 2026" [ref=e111]:
- generic "28 kwietnia 2026" [ref=e114]: "28"
- gridcell "29 kwietnia 2026" [ref=e116]:
- generic "29 kwietnia 2026" [ref=e119]: "29"
- gridcell "30 kwietnia 2026" [ref=e121]:
- generic "30 kwietnia 2026" [ref=e124]: "30"
- gridcell "1 maja 2026" [ref=e126]:
- generic "1 maja 2026" [ref=e129]: "1"
- gridcell "2 maja 2026" [ref=e131]:
- generic "2 maja 2026" [ref=e134]: "2"
- gridcell "3 maja 2026" [ref=e136]:
- generic "3 maja 2026" [ref=e139]: "3"
- row "4 maja 2026 5 maja 2026 6 maja 2026 7 maja 2026 8 maja 2026 9 maja 2026 10 maja 2026" [ref=e141]:
- gridcell "4 maja 2026" [ref=e142]:
- generic "4 maja 2026" [ref=e145]: "4"
- gridcell "5 maja 2026" [ref=e147]:
- generic "5 maja 2026" [ref=e150]: "5"
- gridcell "6 maja 2026" [ref=e152]:
- generic "6 maja 2026" [ref=e155]: "6"
- gridcell "7 maja 2026" [ref=e157]:
- generic "7 maja 2026" [ref=e160]: "7"
- gridcell "8 maja 2026" [ref=e162]:
- generic "8 maja 2026" [ref=e165]: "8"
- gridcell "9 maja 2026" [ref=e167]:
- generic "9 maja 2026" [ref=e170]: "9"
- gridcell "10 maja 2026" [ref=e172]:
- generic "10 maja 2026" [ref=e175]: "10"
- row "11 maja 2026 12 maja 2026 13 maja 2026 14 maja 2026 15 maja 2026 16 maja 2026 17 maja 2026" [ref=e177]:
- gridcell "11 maja 2026" [ref=e178]:
- generic "11 maja 2026" [ref=e181]: "11"
- gridcell "12 maja 2026" [ref=e183]:
- generic "12 maja 2026" [ref=e186]: "12"
- gridcell "13 maja 2026" [ref=e188]:
- generic "13 maja 2026" [ref=e191]: "13"
- gridcell "14 maja 2026" [ref=e193]:
- generic "14 maja 2026" [ref=e196]: "14"
- gridcell "15 maja 2026" [ref=e198]:
- generic "15 maja 2026" [ref=e201]: "15"
- gridcell "16 maja 2026" [ref=e203]:
- generic "16 maja 2026" [ref=e206]: "16"
- gridcell "17 maja 2026" [ref=e208]:
- generic "17 maja 2026" [ref=e211]: "17"
- row "18 maja 2026 19 maja 2026 20 maja 2026 21 maja 2026 22 maja 2026 23 maja 2026 24 maja 2026" [ref=e213]:
- gridcell "18 maja 2026" [ref=e214]:
- generic "18 maja 2026" [ref=e217]: "18"
- gridcell "19 maja 2026" [ref=e219]:
- generic "19 maja 2026" [ref=e222]: "19"
- gridcell "20 maja 2026" [ref=e224]:
- generic "20 maja 2026" [ref=e227]: "20"
- gridcell "21 maja 2026" [ref=e229]:
- generic "21 maja 2026" [ref=e232]: "21"
- gridcell "22 maja 2026" [ref=e234]:
- generic "22 maja 2026" [ref=e237]: "22"
- gridcell "23 maja 2026" [ref=e239]:
- generic "23 maja 2026" [ref=e242]: "23"
- gridcell "24 maja 2026" [ref=e244]:
- generic "24 maja 2026" [ref=e247]: "24"
- row "25 maja 2026 26 maja 2026 27 maja 2026 28 maja 2026 29 maja 2026 30 maja 2026 31 maja 2026" [ref=e249]:
- gridcell "25 maja 2026" [ref=e250]:
- generic "25 maja 2026" [ref=e253]: "25"
- gridcell "26 maja 2026" [ref=e255]:
- generic "26 maja 2026" [ref=e258]: "26"
- gridcell "27 maja 2026" [ref=e260]:
- generic "27 maja 2026" [ref=e263]: "27"
- gridcell "28 maja 2026" [ref=e265]:
- generic "28 maja 2026" [ref=e268]: "28"
- gridcell "29 maja 2026" [ref=e270]:
- generic "29 maja 2026" [ref=e273]: "29"
- gridcell "30 maja 2026" [ref=e275]:
- generic "30 maja 2026" [ref=e278]: "30"
- gridcell "31 maja 2026" [ref=e280]:
- generic "31 maja 2026" [ref=e283]: "31"
- row "1 czerwca 2026 2 czerwca 2026 3 czerwca 2026 4 czerwca 2026 5 czerwca 2026 6 czerwca 2026 7 czerwca 2026" [ref=e285]:
- gridcell "1 czerwca 2026" [ref=e286]:
- generic "1 czerwca 2026" [ref=e289]: "1"
- gridcell "2 czerwca 2026" [ref=e291]:
- generic "2 czerwca 2026" [ref=e294]: "2"
- gridcell "3 czerwca 2026" [ref=e296]:
- generic "3 czerwca 2026" [ref=e299]: "3"
- gridcell "4 czerwca 2026" [ref=e301]:
- generic "4 czerwca 2026" [ref=e304]: "4"
- gridcell "5 czerwca 2026" [ref=e306]:
- generic "5 czerwca 2026" [ref=e309]: "5"
- gridcell "6 czerwca 2026" [ref=e311]:
- generic "6 czerwca 2026" [ref=e314]: "6"
- gridcell "7 czerwca 2026" [ref=e316]:
- generic "7 czerwca 2026" [ref=e319]: "7"
- generic [ref=e322]:
- heading "Zapytaj o rezerwację" [level=4] [ref=e323]
- paragraph [ref=e324]: Wypełnij formularz, a skontaktujemy się z Tobą w sprawie rezerwacji.
- generic [ref=e325]:
- generic [ref=e326]:
- generic [ref=e327]: Imię i nazwisko *
- textbox "Imię i nazwisko *" [ref=e328]
- generic [ref=e329]:
- generic [ref=e330]: Email *
- textbox "Email *" [ref=e331]
- generic [ref=e332]:
- generic [ref=e333]: Telefon *
- textbox "Telefon *" [ref=e334]
- generic [ref=e335]:
- generic [ref=e336]: Preferowane terminy
- textbox "Preferowane terminy" [ref=e337]:
- /placeholder: np. 15-22 lipca
- generic [ref=e338]:
- generic [ref=e339]: Wiadomość
- textbox "Wiadomość" [ref=e340]:
- /placeholder: Dodatkowe pytania lub uwagi...
- button "Wyślij zapytanie" [ref=e342] [cursor=pointer]
- generic "Szybki wybór jachtu" [ref=e343]:
- generic [ref=e344]: "Zobacz też:"
- button "Kubuś" [ref=e346] [cursor=pointer]
- contentinfo [ref=e347]:
- generic [ref=e354]:
- generic [ref=e355]:
- heading "Wypłyń z nami." [level=3] [ref=e357]:
- text: Wypłyń
- strong [ref=e358]: z nami
- text: .
- paragraph [ref=e360]: Przestronne kabiny, ciepły fornir, biała tapicerka i wszystko, czego potrzeba do długiego rejsu. Pokład zaprojektowany dla wygody całej załogi.
- generic [ref=e361]:
- paragraph [ref=e363]: Nawigacja
- list [ref=e365]:
- listitem [ref=e366]:
- link "Start" [ref=e367] [cursor=pointer]:
- /url: /
- generic [ref=e368]: Start
- listitem [ref=e369]:
- link "Sedna 26 „Maja\"" [ref=e370] [cursor=pointer]:
- /url: /maja/
- generic [ref=e371]: Sedna 26 „Maja"
- listitem [ref=e372]:
- link "Sedna 26 „Kubuś\"" [ref=e373] [cursor=pointer]:
- /url: /kubus/
- generic [ref=e374]: Sedna 26 „Kubuś"
- listitem [ref=e375]:
- link "O nas" [ref=e376] [cursor=pointer]:
- /url: /o-nas/
- generic [ref=e377]: O nas
- listitem [ref=e378]:
- link "Cennik" [ref=e379] [cursor=pointer]:
- /url: /cennik/
- generic [ref=e380]: Cennik
- listitem [ref=e381]:
- link "Kontakt" [ref=e382] [cursor=pointer]:
- /url: /kontakt/
- generic [ref=e383]: Kontakt
- generic [ref=e384]:
- paragraph [ref=e386]: Marina
- list [ref=e388]:
- listitem [ref=e389]:
- generic [ref=e390]: Keja Dedal
- listitem [ref=e391]:
- generic [ref=e392]: Polańczyk, Bieszczady
- listitem [ref=e393]:
- generic [ref=e394]: Jezioro Solińskie
- generic [ref=e407]:
- paragraph [ref=e409]: © 2026 PKMP Jachty. Wszystkie prawa zastrzeżone.
- paragraph [ref=e411]: Solina · Bieszczady · PL
- generic [ref=e417]: laptop

View File

@@ -0,0 +1 @@
- generic [ref=e2]: Brak testowych rezerwacji do usuniecia.

View File

@@ -0,0 +1 @@
- generic [ref=e2]: "Testowe rezerwacje overlap (marker PAUL_OVERLAP_09_04): (brak)"

BIN
all-cal.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

BIN
layout.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

BIN
rezerwacja-full.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1018 KiB

BIN
single-cal.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
single-wrapper.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

View File

@@ -89,6 +89,13 @@ class Admin {
check_admin_referer( 'yacht_booking_save_global_ical' );
$import_url = isset( $_POST['global_ical_import_url'] ) ? esc_url_raw( wp_unslash( $_POST['global_ical_import_url'] ) ) : '';
update_option( 'yacht_booking_global_ical_import_url', $import_url );
$sync_mode = isset( $_POST['ical_sync_mode'] ) ? sanitize_text_field( wp_unslash( $_POST['ical_sync_mode'] ) ) : 'per_yacht';
if ( ! in_array( $sync_mode, array( 'per_yacht', 'global' ), true ) ) {
$sync_mode = 'per_yacht';
}
update_option( 'yacht_booking_ical_sync_mode', $sync_mode );
wp_safe_redirect( admin_url( 'admin.php?page=yacht-bookings-settings&tab=google-calendar&global_ical_saved=1' ) );
exit;
}
@@ -1242,6 +1249,7 @@ class Admin {
$global_import_url = (string) get_option( 'yacht_booking_global_ical_import_url', '' );
$global_export_url = \YachtBooking\Integrations\ICal\ICal_Feed::get_global_feed_url();
$last_global_run = (string) get_option( 'yacht_booking_global_ical_last_import', '' );
$ical_sync_mode = Settings::get_ical_sync_mode();
?>
<div class="card" style="margin-top: 30px;">
<h3><?php esc_html_e( 'Globalna synchronizacja iCal (jeden wspólny kalendarz)', 'yacht-booking' ); ?></h3>
@@ -1284,6 +1292,27 @@ class Admin {
<form method="post" action="">
<?php wp_nonce_field( 'yacht_booking_save_global_ical' ); ?>
<table class="form-table">
<tr>
<th scope="row">
<label for="ical_sync_mode"><?php esc_html_e( 'Tryb synchronizacji iCal', 'yacht-booking' ); ?></label>
</th>
<td>
<select name="ical_sync_mode" id="ical_sync_mode">
<option value="per_yacht" <?php selected( $ical_sync_mode, 'per_yacht' ); ?>>
<?php esc_html_e( 'Per jacht — dopasowanie po prefiksie SUMMARY', 'yacht-booking' ); ?>
</option>
<option value="global" <?php selected( $ical_sync_mode, 'global' ); ?>>
<?php esc_html_e( 'Wspólny kalendarz — wszystkie eventy bez filtrowania', 'yacht-booking' ); ?>
</option>
</select>
<p class="description">
<?php esc_html_e( 'Per jacht: importowane są tylko eventy z prefiksem nazwy jachtu w tytule (format: "Nazwa jachtu - opis"). Eventy bez dopasowania są ignorowane. Tworzą blokady dostępności.', 'yacht-booking' ); ?>
</p>
<p class="description">
<?php esc_html_e( 'Wspólny kalendarz: importowane są wszystkie eventy bez wyjątku, jako wspólne wydarzenia kalendarza. NIE blokują dostępności poszczególnych jachtów. Pokazywane na widgecie zbiorczym "wszystkie jachty".', 'yacht-booking' ); ?>
</p>
</td>
</tr>
<tr>
<th scope="row">
<label for="global_ical_import_url"><?php esc_html_e( 'iCal Import URL', 'yacht-booking' ); ?></label>

View File

@@ -24,6 +24,30 @@ class Rest_Controller extends \WP_REST_Controller {
*/
const NAMESPACE = 'yacht-booking/v1';
/**
* Deterministyczna paleta kolorów dla widgetu zbiorczego (wszystkie jachty).
*
* Mapowanie: yacht_id (sortowane rosnąco) → palette[index % count].
* Wspólna dla REST i frontendu (używana przez calendar-all.js do legendy).
*/
const YACHT_COLOR_PALETTE = array(
'#3498db',
'#e74c3c',
'#2ecc71',
'#f39c12',
'#9b59b6',
'#1abc9c',
'#34495e',
'#d35400',
);
/**
* Kolor dla wspólnych wydarzeń kalendarza (yacht_id=0, source=ical_global_calendar).
* Jasnoniebieski — dobry kontrast na ciemnogranatowym tle kalendarza, łagodniejszy
* dla oka niż akcent czerwony.
*/
const GLOBAL_EVENT_COLOR = '#7fb3d5';
/**
* Constructor
*/
@@ -97,6 +121,27 @@ class Rest_Controller extends \WP_REST_Controller {
)
);
// GET /yacht-booking/v1/availability/all
register_rest_route(
self::NAMESPACE,
'/availability/all',
array(
'methods' => \WP_REST_Server::READABLE,
'callback' => array( $this, 'get_all_availability' ),
'permission_callback' => '__return_true',
'args' => array(
'start' => array(
'required' => false,
'sanitize_callback' => 'sanitize_text_field',
),
'end' => array(
'required' => false,
'sanitize_callback' => 'sanitize_text_field',
),
),
)
);
// POST /yacht-booking/v1/bookings
register_rest_route(
self::NAMESPACE,
@@ -297,6 +342,136 @@ class Rest_Controller extends \WP_REST_Controller {
return rest_ensure_response( $events );
}
/**
* Get aggregated availability for all yachts + globalne wydarzenia kalendarza.
*
* Zwraca tablicę FullCalendar events (timed 12:00 → 12:00 dla efektu half-day).
* Eventy z yacht_id > 0: kolor z palety per-jacht.
* Eventy yacht_id = 0 (sync_mode=global): kolor `GLOBAL_EVENT_COLOR`.
*
* @param \WP_REST_Request $request Request object.
* @return \WP_REST_Response
*/
public function get_all_availability( $request ) {
$start = $request->get_param( 'start' );
$end = $request->get_param( 'end' );
if ( ! $start || ! preg_match( '/^\d{4}-\d{2}-\d{2}$/', $start ) ) {
$start = gmdate( 'Y-m-01' );
}
if ( ! $end || ! preg_match( '/^\d{4}-\d{2}-\d{2}$/', $end ) ) {
$end = gmdate( 'Y-m-d', strtotime( $start . ' +12 months' ) );
}
$is_global_mode = ( 'global' === Settings::get_ical_sync_mode() );
// Build yacht_id → color map (deterministic by ascending yacht_id).
$yacht_posts = 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 );
// Query bookings overlapping [start, end] with status confirmed or pending.
$bookings = get_posts(
array(
'post_type' => 'yacht_booking',
'post_status' => 'publish',
'posts_per_page' => -1,
'meta_query' => array(
'relation' => 'AND',
array(
'key' => '_booking_status',
'value' => array( 'confirmed', 'pending' ),
'compare' => 'IN',
),
array(
'key' => '_booking_end_date',
'value' => $start,
'compare' => '>=',
'type' => 'DATE',
),
array(
'key' => '_booking_start_date',
'value' => $end,
'compare' => '<=',
'type' => 'DATE',
),
),
)
);
$events = array();
foreach ( $bookings as $booking ) {
$booking_id = $booking->ID;
$start_date = Booking::get_start_date( $booking_id );
$end_date = Booking::get_end_date( $booking_id );
if ( ! $start_date || ! $end_date ) {
continue;
}
$yacht_id = Booking::get_yacht_id( $booking_id );
$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 );
// W trybie global wszystko traktujemy jak wspólne wydarzenia: szary kolor,
// brak yacht_id, generyczny tytuł "Rezerwacja" — bez wycieku danych klientów.
if ( $is_global_mode || $is_global_event ) {
$color = self::GLOBAL_EVENT_COLOR;
$title = __( 'Rezerwacja', 'yacht-booking' );
$y_id = 0;
} else {
$color = isset( $color_map[ $yacht_id ] ) ? $color_map[ $yacht_id ] : self::GLOBAL_EVENT_COLOR;
$yacht = get_post( $yacht_id );
// Tryb per_yacht: pokazujemy tylko nazwę jachtu (bez nazwiska klienta — privacy).
$title = $yacht ? $yacht->post_title : __( 'Rezerwacja', 'yacht-booking' );
$y_id = $yacht_id;
}
$events[] = array(
'id' => $booking_id,
'title' => $title,
'start' => $start_date . 'T12:00:00',
'end' => $end_date . 'T12:00:00',
'color' => $color,
'yacht_id' => $y_id,
);
}
return rest_ensure_response( $events );
}
/**
* Buduje deterministyczną mapę yacht_id → kolor z palety.
*
* Sortuje yacht_ids rosnąco i indeksuje paletę modulo długość. Zapewnia stabilny
* kolor dla danego jachtu między requestami i wspólny przypisanie z frontendem.
*
* @param array $yacht_ids Lista ID jachtów.
* @return array<int,string> yacht_id => hex color.
*/
public static function get_yacht_color_palette( $yacht_ids ) {
$ids = array_map( 'intval', (array) $yacht_ids );
sort( $ids, SORT_NUMERIC );
$palette = self::YACHT_COLOR_PALETTE;
$count = count( $palette );
$map = array();
foreach ( $ids as $i => $yacht_id ) {
$map[ $yacht_id ] = $palette[ $i % $count ];
}
return $map;
}
/**
* Create new booking
*

View File

@@ -0,0 +1,113 @@
/* Yacht Calendar (All) — wspólny widok wszystkich jachtów.
Layout, instrukcja, legenda i formularz dziedziczą z calendar.css
(klasy .yacht-inquiry-layout, .yacht-calendar-instructions, .yacht-calendar-legend,
.yacht-inquiry-form-container, .yacht-inquiry-form). Tutaj tylko nadpisania
specyficzne dla widgetu zbiorczego. */
.yacht-calendar-all-wrapper {
max-width: 1200px;
margin: 0 auto 40px;
padding: 20px;
width: 100%;
box-sizing: border-box;
}
.yacht-calendar-all {
width: 100%;
/* Ciemne granatowe tło — emuluje styl /rezerwacja-maja/ gdzie komórki bg-event
są semi-transparent (#f5f9ff @ 0.66) nad ciemnym tłem parent containera. */
background: #0e2036;
}
/* Komórki przyszłe — semi-transparent biały, daje ciemnoszary efekt nad #0e2036
(efekt identyczny jak yacht-day-available bg-event w single-yacht widget). */
.yacht-calendar-all .fc-daygrid-day {
background: rgba(245, 249, 255, 0.4);
}
/* Przeszłe — białe (jak yacht-day-available .fc-event-past w single-yacht). */
.yacht-calendar-all .fc-daygrid-day.fc-day-past {
background: #ffffff;
}
/* Dziś — lekko jaśniejszy ciemny. */
.yacht-calendar-all .fc-daygrid-day.fc-day-today {
background: rgba(255, 255, 255, 0.55);
}
/* Sąsiedni miesiąc — taki sam jak przyszłe (spójny ciemny). */
.yacht-calendar-all .fc-daygrid-day.fc-day-other {
background: rgba(245, 249, 255, 0.32);
}
/* Select w formularzu zapytania — calendar.css nie ma reguł dla <select>,
wymuszamy ten sam styl co inputy (ciemnoprzezroczysty, biały tekst, custom strzałka). */
.yacht-inquiry-form select {
width: 100%;
padding: 10px 12px;
border: 1px solid hsla(0, 0%, 100%, 0.2);
border-radius: 4px;
font-size: 14px;
transition: border-color 0.3s ease, box-shadow 0.3s ease;
background-color: hsla(0, 0%, 100%, 0.1);
color: #fff;
box-sizing: border-box;
font-family: inherit;
appearance: none;
-webkit-appearance: none;
-moz-appearance: none;
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'><path d='M1 1l5 5 5-5' stroke='white' stroke-width='2' fill='none'/></svg>");
background-repeat: no-repeat;
background-position: right 12px center;
padding-right: 32px;
cursor: pointer;
}
.yacht-inquiry-form select:focus {
outline: none;
border-color: #bc1834;
box-shadow: 0 0 0 3px rgba(188, 24, 52, 0.3);
background-color: hsla(0, 0%, 100%, 0.15);
}
.yacht-inquiry-form select option {
background: #021526;
color: #fff;
}
/* Event styling — pełne wypełnienie kafelka kolorem jachtu, bez kropek/czasu */
.yacht-calendar-all .fc-event {
border: none !important;
padding: 2px 4px;
font-size: 12px;
font-weight: 500;
color: #fff;
cursor: default;
}
.yacht-calendar-all .fc-event-title,
.yacht-calendar-all .fc-daygrid-event-dot,
.yacht-calendar-all .fc-event-time {
display: none !important;
}
/* Pasek eventu zawsze wyższy żeby był czytelny bez tekstu */
.yacht-calendar-all .fc-daygrid-event {
min-height: 18px;
}
/* Half-day visual: gradient wpisywany przez calendar-all.js (eventDidMount), który
liczy szerokość komórki dnia i ustawia background-image dla danego segmentu. */
/* Mobile */
@media (max-width: 600px) {
.yacht-calendar-all .fc-event {
font-size: 10px;
}
.yacht-calendar-all .fc-toolbar.fc-header-toolbar {
flex-direction: column;
gap: 8px;
}
}

View File

@@ -0,0 +1,229 @@
/**
* Yacht Calendar (All) — wspólny widok wszystkich jachtów.
*
* Inicjalizuje FullCalendar dayGridMonth dla każdego elementu .yacht-calendar-all-wrapper
* na stronie. Pobiera eventy z REST `/availability/all` (timed 12:00→12:00), renderuje
* legendę kolorów wyciągniętą z eventów i dodaje klasy half-day na pierwszym/ostatnim
* dniu każdej rezerwacji.
*/
(function ($) {
'use strict';
if (typeof window.FullCalendar === 'undefined') {
return;
}
function pad(n) {
return n < 10 ? '0' + n : '' + n;
}
function ymd(date) {
return date.getFullYear() + '-' + pad(date.getMonth() + 1) + '-' + pad(date.getDate());
}
function initCalendar(wrapper) {
var $wrapper = $(wrapper);
var $cal = $wrapper.find('.yacht-calendar-all');
if (!$cal.length) return;
var restUrl = $wrapper.data('rest');
if (!restUrl) return;
var heightPx = parseInt($wrapper.data('height'), 10);
if (!heightPx || heightPx < 200) heightPx = 650;
var calendar = new window.FullCalendar.Calendar($cal.get(0), {
initialView: 'dayGridMonth',
locale: 'pl',
firstDay: 1,
headerToolbar: {
left: 'prev,next today',
center: 'title',
right: ''
},
height: heightPx,
displayEventTime: false,
eventDisplay: 'block',
events: function (fetchInfo, successCallback, failureCallback) {
var url = restUrl
+ (restUrl.indexOf('?') === -1 ? '?' : '&')
+ 'start=' + ymd(fetchInfo.start)
+ '&end=' + ymd(fetchInfo.end);
$.getJSON(url)
.done(function (data) {
successCallback(data || []);
})
.fail(function () {
failureCallback(new Error('Failed to fetch /availability/all'));
});
},
eventDidMount: function (info) {
// Per-event color via CSS variable.
var color = info.event.backgroundColor || '#3498db';
info.el.style.setProperty('--yc-event-color', color);
// Half-day visual: gradient z half-cell transparent na pierwszym/ostatnim dniu.
// Liczone po renderze (wymaga znajomości szerokości komórki dnia w siatce).
// Przekładamy na requestAnimationFrame żeby DOM był ułożony.
window.requestAnimationFrame(function () {
applyHalfDayGradient(info);
});
},
eventContent: function () {
// Pasek koloru bez treści — privacy: nie pokazujemy nazwisk klientów ani nazw jachtów.
return { html: '' };
}
});
calendar.render();
// Inquiry form submission (yacht select dropdown).
var $form = $wrapper.find('.yacht-calendar-all-inquiry-form');
if ($form.length) {
$form.on('submit', function (e) {
e.preventDefault();
var $submitBtn = $form.find('.yacht-booking-submit');
var $response = $form.find('.yacht-calendar-all-inquiry-response');
var originalBtnText = $submitBtn.text();
var i18n = (window.yachtBookingData && window.yachtBookingData.i18n) || {};
var yachtId = parseInt($form.find('[name="yacht_id"]').val(), 10) || 0;
if (!yachtId) {
$response.html('<div class="booking-error" style="padding: 12px; background: #f8d7da; color: #721c24; border: 1px solid #f5c6cb; border-radius: 4px;"><strong>' +
(i18n.errorTitle || 'Błąd!') + '</strong> Wybierz jacht z listy.</div>');
return;
}
$submitBtn.prop('disabled', true).text(i18n.submitting || 'Wysyłanie...');
$response.html('');
var formData = {
yacht_id: yachtId,
customer_name: $form.find('[name="customer_name"]').val(),
customer_email: $form.find('[name="customer_email"]').val(),
customer_phone: $form.find('[name="customer_phone"]').val(),
preferred_dates: $form.find('[name="preferred_dates"]').val(),
message: $form.find('[name="message"]').val()
};
$.ajax({
url: window.yachtBookingData.apiUrl + '/inquiries',
method: 'POST',
beforeSend: function (xhr) {
xhr.setRequestHeader('X-WP-Nonce', window.yachtBookingData.nonce);
},
contentType: 'application/json',
data: JSON.stringify(formData),
success: function (resp) {
$response.html('<div class="booking-success" style="padding: 12px; background: #d4edda; color: #155724; border: 1px solid #c3e6cb; border-radius: 4px;"><strong>' +
(i18n.successTitle || 'Sukces!') + '</strong> ' +
((resp && resp.message) || i18n.inquirySuccess || 'Twoje zapytanie zostało wysłane.') +
'</div>');
$form[0].reset();
},
error: function (xhr) {
var msg = i18n.errorMessage || 'Wystąpił błąd. Spróbuj ponownie.';
if (xhr.responseJSON && xhr.responseJSON.message) {
msg = xhr.responseJSON.message;
}
$response.html('<div class="booking-error" style="padding: 12px; background: #f8d7da; color: #721c24; border: 1px solid #f5c6cb; border-radius: 4px;"><strong>' +
(i18n.errorTitle || 'Błąd!') + '</strong> ' + msg + '</div>');
},
complete: function () {
$submitBtn.prop('disabled', false).text(originalBtnText);
}
});
});
}
}
/**
* Apply half-day gradient to event bar. Pierwsze dni rezerwacji: lewa połowa pierwszej
* komórki transparentna (yacht wraca w południe). Ostatnie: prawa połowa ostatniej
* komórki transparentna (yacht wypływa w południe). Dla segmentów środkowych — pełny kolor.
*/
function applyHalfDayGradient(info) {
var el = info.el;
var rect = el.getBoundingClientRect();
if (!rect.width) return;
// Find a sibling day cell to read its width.
var dayCell = el.closest('.fc-daygrid-day') || el.closest('td');
// Fallback: scan parent for any .fc-daygrid-day sibling.
if (!dayCell) {
var grid = el.closest('.fc-daygrid');
if (grid) {
dayCell = grid.querySelector('.fc-daygrid-day');
}
}
if (!dayCell) return;
var cellRect = dayCell.getBoundingClientRect();
if (!cellRect.width) return;
var halfPct = (cellRect.width / 2) / rect.width * 100;
// Clamp to safe bounds.
if (halfPct < 1) halfPct = 1;
if (halfPct > 49) halfPct = 49;
var color = el.style.getPropertyValue('--yc-event-color') || '#3498db';
var startTrans = info.isStart;
var endTrans = info.isEnd;
// Build gradient.
var stops;
if (startTrans && endTrans) {
// Single segment containing both start and end.
stops = [
'transparent 0%',
'transparent ' + halfPct.toFixed(2) + '%',
color + ' ' + halfPct.toFixed(2) + '%',
color + ' ' + (100 - halfPct).toFixed(2) + '%',
'transparent ' + (100 - halfPct).toFixed(2) + '%',
'transparent 100%'
];
} else if (startTrans) {
stops = [
'transparent 0%',
'transparent ' + halfPct.toFixed(2) + '%',
color + ' ' + halfPct.toFixed(2) + '%',
color + ' 100%'
];
} else if (endTrans) {
stops = [
color + ' 0%',
color + ' ' + (100 - halfPct).toFixed(2) + '%',
'transparent ' + (100 - halfPct).toFixed(2) + '%',
'transparent 100%'
];
} else {
// Middle segment — pełny kolor.
el.style.backgroundColor = color;
el.style.backgroundImage = 'none';
return;
}
el.style.backgroundImage = 'linear-gradient(to right, ' + stops.join(', ') + ')';
el.style.backgroundColor = 'transparent';
}
function escapeHtml(str) {
return String(str).replace(/[&<>"']/g, function (m) {
return ({
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#39;'
})[m];
});
}
$(function () {
$('.yacht-calendar-all-wrapper').each(function () {
initCalendar(this);
});
});
})(jQuery);

View File

@@ -0,0 +1,282 @@
<?php
/**
* Yacht Calendar (All) Widget for Elementor
*
* Wspólny kalendarz pokazujący rezerwacje WSZYSTKICH publikowanych jachtów + globalne
* wydarzenia (sync_mode=global). Read-only, kolory per-jacht z auto palety, half-day
* przez timed events 12:00 → 12:00.
*
* @package YachtBooking
*/
namespace YachtBooking;
// Exit if accessed directly
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
use Elementor\Controls_Manager;
use Elementor\Widget_Base;
/**
* Yacht Calendar (All) Widget Class
*/
class Calendar_Widget_All extends Widget_Base {
/**
* Widget name (Elementor identifier).
*/
public function get_name() {
return 'yacht-calendar-all';
}
/**
* Widget title.
*/
public function get_title() {
return esc_html__( 'Kalendarz Jachtów (wszystkie)', 'yacht-booking' );
}
/**
* Widget icon.
*/
public function get_icon() {
return 'eicon-calendar';
}
/**
* Widget categories.
*/
public function get_categories() {
return array( 'basic' );
}
/**
* Widget keywords.
*/
public function get_keywords() {
return array( 'yacht', 'calendar', 'wszystkie', 'flota', 'kalendarz', 'rezerwacje' );
}
/**
* Register widget controls.
*/
protected function register_controls() {
$this->start_controls_section(
'content_section',
array(
'label' => esc_html__( 'Ustawienia Kalendarza', 'yacht-booking' ),
'tab' => Controls_Manager::TAB_CONTENT,
)
);
$this->add_control(
'calendar_height',
array(
'label' => esc_html__( 'Wysokość kalendarza', 'yacht-booking' ),
'type' => Controls_Manager::SLIDER,
'range' => array(
'px' => array(
'min' => 400,
'max' => 1000,
),
),
'default' => array(
'size' => 650,
'unit' => 'px',
),
)
);
$this->add_control(
'show_legend',
array(
'label' => esc_html__( 'Pokaż legendę kolorów', 'yacht-booking' ),
'type' => Controls_Manager::SWITCHER,
'label_on' => esc_html__( 'Tak', 'yacht-booking' ),
'label_off' => esc_html__( 'Nie', 'yacht-booking' ),
'return_value' => 'yes',
'default' => 'yes',
)
);
$this->end_controls_section();
}
/**
* Render widget output.
*/
protected function render() {
$settings = $this->get_settings_for_display();
$height = ! empty( $settings['calendar_height']['size'] ) ? (int) $settings['calendar_height']['size'] : 650;
$show_legend = ! isset( $settings['show_legend'] ) || 'yes' === $settings['show_legend'];
$dom_id = 'yacht-calendar-all-' . $this->get_id();
echo Calendar_All_View::render( $dom_id, $height, $show_legend ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
}
/**
* Pomocnicza klasa renderująca markup widgetu i shortcode.
*
* Wspólna dla widgetu Elementor i shortcode `[yacht_calendar_all]` żeby uniknąć duplikacji.
*/
class Calendar_All_View {
/**
* Render markup wspólnego kalendarza.
*
* @param string $dom_id Unikalny ID kontenera FullCalendar.
* @param int $height Wysokość kalendarza w px.
* @param bool $show_legend Czy renderować legendę.
* @return string HTML.
*/
public static function render( $dom_id, $height = 650, $show_legend = true ) {
$rest_url = esc_url_raw( rest_url( 'yacht-booking/v1/availability/all' ) );
// Yachts for legend + form select.
$yacht_posts = get_posts(
array(
'post_type' => 'yacht',
'post_status' => 'publish',
'posts_per_page' => -1,
'orderby' => 'ID',
'order' => 'ASC',
)
);
$yacht_ids = wp_list_pluck( $yacht_posts, 'ID' );
$color_map = \YachtBooking\Rest_Controller::get_yacht_color_palette( $yacht_ids );
$global_color = \YachtBooking\Rest_Controller::GLOBAL_EVENT_COLOR;
$sync_mode = \YachtBooking\Settings::get_ical_sync_mode();
$terms_url = \YachtBooking\Settings::get_terms_page_url();
$form_uid = preg_replace( '/[^a-z0-9_-]/i', '', $dom_id );
ob_start();
?>
<div class="yacht-calendar-all-wrapper"
data-rest="<?php echo esc_attr( $rest_url ); ?>"
data-show-legend="<?php echo $show_legend ? '1' : '0'; ?>"
data-height="<?php echo esc_attr( (int) $height ); ?>">
<div class="yacht-calendar-instructions">
<p>
<?php esc_html_e( 'Aby zarezerwować termin, wypełnij formularz po prawej stronie albo skontaktuj się z nami telefonicznie lub mailowo.', 'yacht-booking' ); ?>
</p>
</div>
<?php if ( $show_legend ) : ?>
<div class="yacht-calendar-legend yacht-calendar-all-legend" aria-label="<?php esc_attr_e( 'Legenda kalendarza', 'yacht-booking' ); ?>">
<?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' ); ?>
</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; ?>
<div class="yacht-inquiry-layout yacht-calendar-all-layout">
<div class="yacht-inquiry-calendar-col">
<div id="<?php echo esc_attr( $dom_id ); ?>"
class="yacht-calendar yacht-calendar-all"
style="height: <?php echo esc_attr( $height ); ?>px;"></div>
</div>
<div class="yacht-inquiry-form-col">
<div class="yacht-inquiry-form-container">
<h4><?php esc_html_e( 'Zapytaj o rezerwację', 'yacht-booking' ); ?></h4>
<p class="yacht-inquiry-desc">
<?php esc_html_e( 'Wybierz jacht i wypełnij formularz — odezwiemy się w sprawie dostępności i cen.', 'yacht-booking' ); ?>
</p>
<form class="yacht-inquiry-form yacht-calendar-all-inquiry-form">
<?php wp_nonce_field( 'yacht_inquiry_submit', 'yacht_inquiry_nonce' ); ?>
<div class="form-field">
<label for="all_inquiry_yacht_<?php echo esc_attr( $form_uid ); ?>">
<?php esc_html_e( 'Jacht', 'yacht-booking' ); ?> <span class="required">*</span>
</label>
<select id="all_inquiry_yacht_<?php echo esc_attr( $form_uid ); ?>" name="yacht_id" required>
<option value=""><?php esc_html_e( '— wybierz jacht —', 'yacht-booking' ); ?></option>
<?php foreach ( $yacht_posts as $yacht ) : ?>
<option value="<?php echo esc_attr( $yacht->ID ); ?>"><?php echo esc_html( $yacht->post_title ); ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="form-field">
<label for="all_inquiry_name_<?php echo esc_attr( $form_uid ); ?>">
<?php esc_html_e( 'Imię i nazwisko', 'yacht-booking' ); ?> <span class="required">*</span>
</label>
<input type="text" id="all_inquiry_name_<?php echo esc_attr( $form_uid ); ?>" name="customer_name" required>
</div>
<div class="form-field">
<label for="all_inquiry_email_<?php echo esc_attr( $form_uid ); ?>">
<?php esc_html_e( 'Email', 'yacht-booking' ); ?> <span class="required">*</span>
</label>
<input type="email" id="all_inquiry_email_<?php echo esc_attr( $form_uid ); ?>" name="customer_email" required>
</div>
<div class="form-field">
<label for="all_inquiry_phone_<?php echo esc_attr( $form_uid ); ?>">
<?php esc_html_e( 'Telefon', 'yacht-booking' ); ?> <span class="required">*</span>
</label>
<input type="tel" id="all_inquiry_phone_<?php echo esc_attr( $form_uid ); ?>" name="customer_phone" required>
</div>
<div class="form-field">
<label for="all_inquiry_dates_<?php echo esc_attr( $form_uid ); ?>">
<?php esc_html_e( 'Preferowane terminy', 'yacht-booking' ); ?>
</label>
<input type="text"
id="all_inquiry_dates_<?php echo esc_attr( $form_uid ); ?>"
name="preferred_dates"
placeholder="<?php esc_attr_e( 'np. 15-22 lipca', 'yacht-booking' ); ?>">
</div>
<div class="form-field">
<label for="all_inquiry_message_<?php echo esc_attr( $form_uid ); ?>">
<?php esc_html_e( 'Wiadomość', 'yacht-booking' ); ?>
</label>
<textarea id="all_inquiry_message_<?php echo esc_attr( $form_uid ); ?>"
name="message"
rows="3"
placeholder="<?php esc_attr_e( 'Dodatkowe pytania lub uwagi...', 'yacht-booking' ); ?>"></textarea>
</div>
<?php if ( $terms_url ) : ?>
<p class="booking-terms">
<?php
printf(
wp_kses_post( __( 'Wysyłając formularz akceptujesz %s.', 'yacht-booking' ) ),
'<a href="' . esc_url( $terms_url ) . '" target="_blank" rel="noopener">' . esc_html__( 'regulamin', 'yacht-booking' ) . '</a>'
);
?>
</p>
<?php endif; ?>
<div class="form-actions">
<button type="submit" class="yacht-booking-submit">
<?php esc_html_e( 'Wyślij zapytanie', 'yacht-booking' ); ?>
</button>
</div>
<div class="yacht-inquiry-response yacht-calendar-all-inquiry-response"></div>
</form>
</div>
</div>
</div>
</div>
<?php
return ob_get_clean();
}
}

View File

@@ -41,6 +41,39 @@ class Shortcode {
*/
private function __construct() {
add_shortcode( 'yacht_calendar', array( $this, 'render_calendar' ) );
add_shortcode( 'yacht_calendar_all', array( $this, 'render_calendar_all' ) );
}
/**
* Render shortcode `[yacht_calendar_all]` — wspólny kalendarz wszystkich jachtów.
*
* Atrybuty:
* - height: wysokość w px (default 650)
* - show_legend: yes|no (default yes)
*
* @param array $atts Shortcode attributes.
* @return string HTML.
*/
public function render_calendar_all( $atts ) {
$atts = shortcode_atts(
array(
'height' => 650,
'show_legend' => 'yes',
),
$atts,
'yacht_calendar_all'
);
// Lazy-load widget class for the View helper.
if ( ! class_exists( '\YachtBooking\Calendar_All_View' ) ) {
require_once YACHT_BOOKING_PLUGIN_DIR . 'frontend/class-calendar-widget-all.php';
}
$dom_id = 'yacht-calendar-all-' . wp_rand( 1000, 9999 );
$height = (int) $atts['height'];
$show_legend = 'yes' === strtolower( (string) $atts['show_legend'] );
return Calendar_All_View::render( $dom_id, $height, $show_legend );
}
/**

View File

@@ -167,6 +167,7 @@ class Installer {
'yacht_booking_global_ical_import_url' => '',
'yacht_booking_global_ical_token' => '',
'yacht_booking_global_ical_last_import' => '',
'yacht_booking_ical_sync_mode' => 'per_yacht',
);
foreach ( $options as $key => $value ) {

View File

@@ -113,6 +113,20 @@ class Settings {
return trim( number_format_i18n( (float) $amount, 2 ) . ' ' . self::get_currency_symbol() );
}
/**
* Get iCal sync mode.
*
* Tryb synchronizacji iCal:
* - per_yacht: import po prefiksie SUMMARY ("Nazwa - opis"), wpisy do availability
* - global: wszystkie eventy zapisane jako wspólne wydarzenia kalendarza, bez wpływu na availability
*
* @return string
*/
public static function get_ical_sync_mode() {
$mode = get_option( 'yacht_booking_ical_sync_mode', 'per_yacht' );
return in_array( $mode, array( 'per_yacht', 'global' ), true ) ? $mode : 'per_yacht';
}
/**
* Get terms page ID.
*

View File

@@ -77,6 +77,9 @@ class Yacht_Booking {
require_once YACHT_BOOKING_PLUGIN_DIR . 'includes/class-booking.php';
require_once YACHT_BOOKING_PLUGIN_DIR . 'includes/class-availability.php';
// REST controller — eagerly loaded because View helpers use its color palette + stałe.
require_once YACHT_BOOKING_PLUGIN_DIR . 'api/class-rest-controller.php';
// Load admin classes
if ( is_admin() ) {
require_once YACHT_BOOKING_PLUGIN_DIR . 'admin/class-admin.php';
@@ -157,6 +160,24 @@ class Yacht_Booking {
true
);
// Calendar (all yachts) — wspólny widget. Załaduj tylko gdy strona go potrzebuje.
if ( $this->should_load_calendar_all_assets() ) {
wp_enqueue_style(
'yacht-booking-calendar-all',
YACHT_BOOKING_PLUGIN_URL . 'frontend/assets/css/calendar-all.css',
array( 'fullcalendar' ),
filemtime( YACHT_BOOKING_PLUGIN_DIR . 'frontend/assets/css/calendar-all.css' )
);
wp_enqueue_script(
'yacht-booking-calendar-all',
YACHT_BOOKING_PLUGIN_URL . 'frontend/assets/js/calendar-all.js',
array( 'jquery', 'fullcalendar', 'fullcalendar-pl' ),
filemtime( YACHT_BOOKING_PLUGIN_DIR . 'frontend/assets/js/calendar-all.js' ),
true
);
}
// Localize script
wp_localize_script(
'yacht-booking-calendar',
@@ -228,8 +249,53 @@ class Yacht_Booking {
return true;
}
// Check if post contains yacht calendar shortcode or widget
if ( $post && ( has_shortcode( $post->post_content, 'yacht_calendar' ) || $this->has_yacht_calendar_widget( $post->ID ) ) ) {
// Check if post contains yacht calendar shortcode or widget (per-jacht lub wszystkie)
if ( $post ) {
if ( has_shortcode( $post->post_content, 'yacht_calendar' )
|| has_shortcode( $post->post_content, 'yacht_calendar_all' )
|| $this->has_yacht_calendar_widget( $post->ID )
|| $this->has_yacht_calendar_all_widget( $post->ID ) ) {
return true;
}
}
return false;
}
/**
* Check if post contains the wspólny widget kalendarza floty.
*
* @param int $post_id Post ID.
* @return bool
*/
private function has_yacht_calendar_all_widget( $post_id ) {
if ( ! class_exists( '\Elementor\Plugin' ) ) {
return false;
}
$document = \Elementor\Plugin::$instance->documents->get( $post_id );
if ( ! $document ) {
return false;
}
$data = $document->get_elements_data();
return $this->find_widget_recursive( $data, 'yacht-calendar-all' );
}
/**
* Check if calendar-all (wspólny) assets should be loaded — used to enqueue
* dodatkowe pliki tylko na stronach które ich potrzebują.
*
* @return bool
*/
private function should_load_calendar_all_assets() {
global $post;
if ( class_exists( '\Elementor\Plugin' ) && \Elementor\Plugin::$instance->preview->is_preview_mode() ) {
return true;
}
if ( $post && ( has_shortcode( $post->post_content, 'yacht_calendar_all' ) || $this->has_yacht_calendar_all_widget( $post->ID ) ) ) {
return true;
}
@@ -296,11 +362,13 @@ class Yacht_Booking {
* @param object $widgets_manager Elementor widgets manager.
*/
public function register_elementor_widgets( $widgets_manager ) {
// Load widget class
// Load widget classes
require_once YACHT_BOOKING_PLUGIN_DIR . 'frontend/class-calendar-widget.php';
require_once YACHT_BOOKING_PLUGIN_DIR . 'frontend/class-calendar-widget-all.php';
// Register widget
// Register widgets
$widgets_manager->register( new Calendar_Widget() );
$widgets_manager->register( new Calendar_Widget_All() );
}
/**

View File

@@ -11,6 +11,7 @@
namespace YachtBooking\Integrations\ICal;
use YachtBooking\Availability;
use YachtBooking\Settings;
use YachtBooking\Yacht;
// Exit if accessed directly.
@@ -29,6 +30,14 @@ class ICal_Import {
*/
const GLOBAL_IMPORT_SOURCE = 'ical_import_global';
/**
* Booking source identifier dla trybu "wspólny kalendarz" (sync_mode=global).
*
* Eventy z tym source nie blokują dostępności jachtów (yacht_id=0) i są pokazywane
* tylko na widgecie zbiorczym "wszystkie jachty".
*/
const GLOBAL_CALENDAR_SOURCE = 'ical_global_calendar';
/**
* Separator między prefiksem nazwy jachtu a resztą tytułu eventu.
*
@@ -95,13 +104,30 @@ class ICal_Import {
return false;
}
$events = self::parse_ics( $body );
$events = self::parse_ics( $body );
$mode = Settings::get_ical_sync_mode();
if ( 'global' === $mode ) {
self::run_global_calendar_mode( $events );
} else {
self::run_per_yacht_mode( $events );
}
update_option( 'yacht_booking_global_ical_last_import', current_time( 'mysql' ) );
return true;
}
/**
* Tryb per-jacht: dopasowanie po prefiksie SUMMARY, wpisy do availability.
*
* @param array $events Sparsowane eventy iCal.
*/
protected static function run_per_yacht_mode( $events ) {
$yacht_map = self::build_yacht_lookup_map();
if ( empty( $yacht_map ) ) {
self::log( 'Global iCal: no yachts in DB — nothing to match', 'error' );
update_option( 'yacht_booking_global_ical_last_import', current_time( 'mysql' ) );
return true;
self::log( 'Per-yacht iCal: no yachts in DB — nothing to match', 'error' );
return;
}
$existing_map = self::get_existing_global_import_map();
@@ -117,11 +143,11 @@ class ICal_Import {
continue;
}
$summary = isset( $event['summary'] ) ? (string) $event['summary'] : '';
$summary = isset( $event['summary'] ) ? (string) $event['summary'] : '';
$yacht_id = self::match_yacht_by_prefix( $summary, $yacht_map );
if ( ! $yacht_id ) {
self::log( sprintf( 'Global iCal: skip event "%s" — no yacht match for prefix', $summary ) );
self::log( sprintf( 'Per-yacht iCal: skip event "%s" — no yacht match for prefix', $summary ) );
continue;
}
@@ -131,20 +157,76 @@ class ICal_Import {
$booking_id = self::upsert_global_booking( $yacht_id, $event, $existing_id );
if ( ! $booking_id ) {
self::log( sprintf( 'Global iCal: failed to upsert event %s', $event['uid'] ), 'error' );
self::log( sprintf( 'Per-yacht iCal: failed to upsert event %s', $event['uid'] ), 'error' );
}
}
// Stale cleanup — usuń bookingi których UID nie ma już w feedzie.
// Ograniczone do GLOBAL_IMPORT_SOURCE (per-yacht) — nie tyka GLOBAL_CALENDAR_SOURCE.
foreach ( $existing_map as $uid => $booking_id ) {
if ( ! in_array( $uid, $seen_uids, true ) ) {
\YachtBooking\Availability::clear_booking_availability( $booking_id );
wp_delete_post( $booking_id, true );
}
}
}
update_option( 'yacht_booking_global_ical_last_import', current_time( 'mysql' ) );
return true;
/**
* Tryb global: wszystkie eventy (bez filtrowania) zapisane jako wspólne wydarzenia
* kalendarza. yacht_id=0, brak wpisów do wp_yacht_availability.
*
* @param array $events Sparsowane eventy iCal.
*/
protected static function run_global_calendar_mode( $events ) {
$existing_map = self::get_existing_global_calendar_map();
$seen_uids = array();
$imported = 0;
$updated = 0;
foreach ( $events as $event ) {
if ( empty( $event['uid'] ) || empty( $event['start'] ) || empty( $event['end'] ) ) {
continue;
}
// Skip past events.
if ( strtotime( $event['end'] ) < time() ) {
continue;
}
$seen_uids[] = $event['uid'];
$existing_id = isset( $existing_map[ $event['uid'] ] ) ? (int) $existing_map[ $event['uid'] ] : 0;
$booking_id = self::upsert_global_calendar_event( $event, $existing_id );
if ( ! $booking_id ) {
self::log( sprintf( 'Global calendar iCal: failed to upsert event %s', $event['uid'] ), 'error' );
continue;
}
if ( $existing_id > 0 ) {
$updated++;
} else {
$imported++;
}
}
// Stale cleanup — usuń tylko eventy z GLOBAL_CALENDAR_SOURCE których UID brakuje.
$deleted = 0;
foreach ( $existing_map as $uid => $booking_id ) {
if ( ! in_array( $uid, $seen_uids, true ) ) {
wp_delete_post( $booking_id, true );
$deleted++;
}
}
self::log(
sprintf(
'Global calendar iCal: imported=%d, updated=%d, deleted=%d',
$imported,
$updated,
$deleted
)
);
}
/**
@@ -297,6 +379,95 @@ class ICal_Import {
return (int) $booking_id;
}
/**
* Mapa istniejących eventów GLOBAL_CALENDAR_SOURCE (tryb global): uid => booking_id.
*
* @return array
*/
protected static function get_existing_global_calendar_map() {
$bookings = get_posts(
array(
'post_type' => 'yacht_booking',
'post_status' => 'publish',
'posts_per_page' => -1,
'fields' => 'ids',
'meta_query' => array(
array(
'key' => '_booking_source',
'value' => self::GLOBAL_CALENDAR_SOURCE,
),
),
)
);
$map = array();
foreach ( $bookings as $booking_id ) {
$uid = get_post_meta( $booking_id, '_ical_event_uid', true );
if ( $uid ) {
$map[ $uid ] = (int) $booking_id;
}
}
return $map;
}
/**
* Tworzy lub aktualizuje wspólne wydarzenie kalendarza (sync_mode=global).
*
* yacht_id=0, brak wpisów do availability — event tylko informacyjny na widgecie zbiorczym.
*
* @param array $event Parsed event data.
* @param int $existing_id Existing booking ID (0 dla nowego).
* @return int|false
*/
protected static function upsert_global_calendar_event( $event, $existing_id = 0 ) {
$summary = ! empty( $event['summary'] )
? sanitize_text_field( $event['summary'] )
: __( 'Wydarzenie kalendarza', 'yacht-booking' );
$post_data = array(
'post_type' => 'yacht_booking',
'post_status' => 'publish',
'post_title' => sprintf(
/* translators: %s: event summary */
__( 'GCal (wspólny): %s', 'yacht-booking' ),
$summary
),
);
if ( $existing_id > 0 ) {
$post_data['ID'] = $existing_id;
$booking_id = wp_update_post( $post_data, true );
} else {
$booking_id = wp_insert_post( $post_data, true );
}
if ( is_wp_error( $booking_id ) || ! $booking_id ) {
return false;
}
update_post_meta( $booking_id, '_booking_yacht_id', 0 );
update_post_meta( $booking_id, '_booking_start_date', $event['start'] );
update_post_meta( $booking_id, '_booking_end_date', $event['end'] );
update_post_meta( $booking_id, '_booking_status', 'confirmed' );
update_post_meta( $booking_id, '_booking_customer_name', __( 'Google Calendar (kalendarz wspólny)', 'yacht-booking' ) );
update_post_meta( $booking_id, '_booking_customer_email', '' );
update_post_meta( $booking_id, '_booking_customer_phone', '' );
update_post_meta( $booking_id, '_booking_total_price', 0 );
update_post_meta( $booking_id, '_booking_source', self::GLOBAL_CALENDAR_SOURCE );
update_post_meta( $booking_id, '_ical_event_uid', $event['uid'] );
update_post_meta( $booking_id, '_booking_notes', $summary );
// CELOWO BEZ Availability::mark_as_booked() — wspólne eventy nie blokują dostępności jachtów.
// Defensywnie: jeśli kiedyś existing_id miał wpisy availability (np. po przełączeniu trybu),
// usuń je, by nie zostawiały śmieci.
if ( $existing_id > 0 ) {
\YachtBooking\Availability::clear_booking_availability( $existing_id );
}
return (int) $booking_id;
}
/**
* Parse .ics content into array of events.
*

View File

@@ -3,7 +3,7 @@
* Plugin Name: Yacht Booking System
* Plugin URI: https://jachty.pagedev.pl
* Description: System rezerwacji jachtów z kalendarzem i integracją z Google Calendar
* Version: 1.0.0
* Version: 1.1.0
* Author: PageDev
* Author URI: https://pagedev.pl
* Text Domain: yacht-booking
@@ -20,7 +20,7 @@ if ( ! defined( 'ABSPATH' ) ) {
}
// Define plugin constants
define( 'YACHT_BOOKING_VERSION', '1.0.0' );
define( 'YACHT_BOOKING_VERSION', '1.1.0' );
define( 'YACHT_BOOKING_PLUGIN_FILE', __FILE__ );
define( 'YACHT_BOOKING_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
define( 'YACHT_BOOKING_PLUGIN_URL', plugin_dir_url( __FILE__ ) );