diff --git a/.paul/PROJECT.md b/.paul/PROJECT.md new file mode 100644 index 0000000..5c6b60a --- /dev/null +++ b/.paul/PROJECT.md @@ -0,0 +1,99 @@ +# jachty3.pagedev.pl + +## What This Is + +Strona internetowa wypożyczalni jachtów oparta na WordPress z customowym pluginem `yacht-booking-system`. System umożliwia prezentację floty jachtów, rezerwację przez frontend (kalendarz FullCalendar), zarządzanie rezerwacjami przez panel admina oraz dwukierunkową synchronizację z Google Calendar. + +## Core Value + +Klienci mogą sprawdzić dostępność jachtu i złożyć rezerwację przez stronę bez kontaktu z właścicielem. + +## Current State + +| Attribute | Value | +|-----------|-------| +| Version | 1.0.0 | +| Status | Beta | +| Last Updated | 2026-05-05 | + +**Plugin:** `wp-content/plugins/yacht-booking-system/` + +## Requirements + +### Validated (Shipped) + +- [x] Custom Post Types: `yacht`, `yacht_booking` — faza 1 +- [x] Admin CRUD dla jachtów — faza 2 +- [x] System dostępności z cache (`wp_yacht_availability`) — faza 3 +- [x] Frontend kalendarz FullCalendar.js + Elementor widget + shortcode — faza 4 +- [x] Backend formularza rezerwacji (REST API + email do admina) — faza 5 +- [x] Admin zarządzanie rezerwacjami (approve/cancel/delete + email do klienta) — faza 6 +- [x] Google Calendar OAuth 2.0 — faza 7 +- [x] Synchronizacja dwukierunkowa Google Calendar ↔ WordPress — faza 8 +- [x] Szablony email (WYSIWYG + tagi) — aktualizacja Codex 2026-02-11 +- [x] Eksport CSV rezerwacji — aktualizacja Codex 2026-02-11 + +### Active (In Progress) + +- [ ] Faza 9: Finalizacja (settings, testy, tłumaczenia, dokumentacja) + +### Planned (Next) + +- [ ] Tłumaczenia PL (.pot/.po/.mo) +- [ ] Security audit (nonce, escaping, SQL, capabilities) +- [ ] Testy cross-browser i mobile +- [ ] Dokumentacja użytkownika i dewelopera + +### Out of Scope + +- Integracja z systemami płatności (Stripe/PayPal) — poza obecnym zakresem + +## Target Users + +**Primary:** Właściciel wypożyczalni jachtów (admin) +- Zarządza flotą i rezerwacjami przez WP Admin +- Chce synchronizację z Google Calendar + +**Secondary:** Klienci wypożyczalni +- Sprawdzają dostępność i składają rezerwacje przez frontend + +## Context + +**Technical Context:** +- WordPress + PHP 7.4+ +- Elementor Pro (page builder) +- FullCalendar.js v6 (CDN) +- Google Calendar API (OAuth 2.0, natywna implementacja bez SDK) +- MySQL — custom tabela `wp_yacht_availability` +- REST API: namespace `yacht-booking/v1` + +## Constraints + +### Technical Constraints +- WordPress hosting (FTP deployment via ftp-kr) +- PHP 7.4+ (bez Composer/autoload PSR-4 — ładowanie manualne) +- FullCalendar via CDN (bez npm/bundlera) + +## Key Decisions + +| Decision | Rationale | Date | Status | +|----------|-----------|------|--------| +| Własna implementacja OAuth bez Google PHP Client | Lżejsze rozwiązanie bez zewnętrznych zależności | 2026-02-11 | Active | +| FullCalendar via CDN | Brak środowiska npm/Webpack na serwerze WP | 2026-02-11 | Active | +| Custom tabela `wp_yacht_availability` zamiast meta | Wydajność zapytań dostępności | 2026-02-11 | Active | + +## Tech Stack + +| Layer | Technology | Notes | +|-------|------------|-------| +| CMS | WordPress 6.x | | +| PHP | PHP 7.4+ | Singleton pattern, namespace YachtBooking\ | +| Frontend | FullCalendar.js v6 | CDN, jQuery | +| Page Builder | Elementor Pro | Custom widget | +| Database | MySQL | Custom table + CPT meta | +| Calendar Sync | Google Calendar API v3 | OAuth 2.0, native WP HTTP API | +| Deployment | FTP (ftp-kr VS Code extension) | | + +--- +*PROJECT.md — Updated when requirements or context change* +*Last updated: 2026-05-05* diff --git a/.paul/ROADMAP.md b/.paul/ROADMAP.md new file mode 100644 index 0000000..018b675 --- /dev/null +++ b/.paul/ROADMAP.md @@ -0,0 +1,54 @@ +# Roadmap: jachty3.pagedev.pl + +## Overview + +Plugin `yacht-booking-system` dla WordPress budowany w 9 fazach. Fazy 1-8 ukończone (89%). Pozostała faza 9 — finalizacja, testy i dokumentacja. + +## Current Milestone + +**v1.0 Production Release** (v1.0.0) +Status: In progress +Phases: 8 of 9 complete + +## Phases + +| Phase | Name | Plans | Status | Completed | +|-------|------|-------|--------|-----------| +| 1 | Fundament | 1 | ✅ Complete | 2026-02-11 | +| 2 | Admin — zarządzanie jachtami | 1 | ✅ Complete | 2026-02-11 | +| 3 | System dostępności | 1 | ✅ Complete | 2026-02-11 | +| 4 | Frontend — kalendarz | 1 | ✅ Complete | 2026-02-11 | +| 5 | Backend formularza rezerwacji | 1 | ✅ Complete | 2026-02-11 | +| 6 | Admin — zarządzanie rezerwacjami | 1 | ✅ Complete | 2026-02-11 | +| 7 | Google Calendar — autentykacja | 1 | ✅ Complete | 2026-02-11 | +| 8 | Google Calendar — synchronizacja | 1 | ✅ Complete | 2026-02-11 | +| 9 | Finalizacja | TBD | In progress | - | + +## Phase Details + +### Phase 1–8: Ukończone + +Szczegóły w `wp-content/plugins/yacht-booking-system/PROJECT-STATUS.md`. + +### Phase 9: Finalizacja + +**Goal:** Plugin gotowy produkcyjnie — ustawienia, bezpieczeństwo, testy, tłumaczenia, dokumentacja +**Depends on:** Phase 8 (synchronizacja GCal) +**Research:** Unlikely (wewnętrzne wzorce WP) + +**Scope:** +- Settings Page — uzupełnienie (default status, email from, format daty, waluta, T&C) +- Security audit (nonce, escaping, SQL injection, capabilities) +- Testy funkcjonalne i cross-browser +- Tłumaczenia PL (.pot/.po/.mo) +- Dokumentacja użytkownika i dewelopera + +**Plans:** +- [ ] 09-01: UX/UI polish kalendarza — half-day na pierwszym/ostatnim dniu rezerwacji + paleta widgetu pasująca do strony +- [ ] 09-02: Settings Page — uzupełnienie brakujących opcji +- [ ] 09-03: Security audit i poprawki +- [ ] 09-04: Testy + tłumaczenia + dokumentacja + +--- +*Roadmap created: 2026-05-05* +*Last updated: 2026-05-05* diff --git a/.paul/STATE.md b/.paul/STATE.md new file mode 100644 index 0000000..07b88e9 --- /dev/null +++ b/.paul/STATE.md @@ -0,0 +1,60 @@ +# Project State + +## Project Reference + +See: .paul/PROJECT.md (updated 2026-05-05) + +**Core value:** Klienci mogą sprawdzić dostępność jachtu i złożyć rezerwację przez stronę bez kontaktu z właścicielem. +**Current focus:** v1.0 Production Release — Phase 9: Finalizacja + +## Current Position + +Milestone: v1.0 Production Release (v1.0.0) +Phase: 9 of 9 (Finalizacja) — In progress +Plan: 09-01 — Complete (UX/UI kalendarza) +Status: Loop closed, ready for next plan (09-02) +Last activity: 2026-05-06 — Closed loop 09-01 (skos 45°, ciemne ramki, paleta brand strony) + +Progress: +- Milestone: [████████░░] 82% +- Phase 9: [██░░░░░░░░] 25% (1 of 4 plans complete) + +## Loop Position + +Current loop state: +``` +PLAN ──▶ APPLY ──▶ UNIFY + ✓ ✓ ✓ [Loop 09-01 complete, ready for 09-02] +``` + +## Accumulated Context + +### Decisions + +| Decision | Phase | Impact | +|----------|-------|--------| +| OAuth bez Google PHP Client | 7 | Brak zależności zewnętrznych, manual token refresh | +| FullCalendar via CDN | 4 | Brak bundlera, update ręczny | +| Custom tabela availability | 1 | Wydajne zapytania, custom cache logic | + +### Deferred Issues + +| Issue | Origin | Effort | Revisit | +|-------|--------|--------|---------| +| Tłumaczenia PL | Phase 1-8 | M | Phase 9 | +| Security audit | Phase 1-8 | M | Phase 9 | +| Dokumentacja PHP Doc | Phase 1-8 | L | Phase 9 | + +### Blockers/Concerns + +None. + +## Session Continuity + +Last session: 2026-05-06 +Stopped at: Loop 09-01 closed (UX/UI kalendarza ukończone, klient zaakceptował) +Next action: Run /paul:plan to plan 09-02 (Settings Page — uzupełnienie brakujących opcji) +Resume file: .paul/phases/09-finalizacja/09-01-SUMMARY.md + +--- +*STATE.md — Updated after every significant action* diff --git a/.paul/changelog/2026-05-06.md b/.paul/changelog/2026-05-06.md new file mode 100644 index 0000000..736e306 --- /dev/null +++ b/.paul/changelog/2026-05-06.md @@ -0,0 +1,24 @@ +# 2026-05-06 + +## Co zrobiono + +- [Phase 9, Plan 01] UX/UI polish kalendarza: skos 45° na pierwszym/ostatnim dniu rezerwacji, ciemne ramki dni, paleta widgetu dopasowana do brandu strony (granat #0e2036, czerwień #bc1834) +- Edge detection po stronie klienta — porównanie sąsiadujących dni po booking_id+status, klasy `yacht-day-{booked|blocked}-{start|end|mid|single}` +- Gradient 135° na obu skrajnych dniach (parallelogram przez wiersz), single-day blokada renderowana jako pełen booked +- CSS variables `--yacht-available-bg` / `--yacht-booked-bg` ustawiane z JS na wrapperze — reuse w gradientach + legendzie +- Nagłówki kolumn FullCalendar w granacie, ramki przez `--fc-border-color: #0e2036` + override 7 selektorów +- Cyfry dni `#021526` (past `#6c757d`) — przebicie globalnej reguły `body a:not([href])` +- Legenda rozszerzona o 3-ci swatch „Dzień odbioru / zwrotu" z gradientem +- Helper `test-add-booking.php` (token-protected, akcje add/list/remove) do smoke testu — DO USUNIĘCIA z FTP po testach + +## Zmienione pliki + +- `wp-content/plugins/yacht-booking-system/frontend/assets/js/calendar.js` +- `wp-content/plugins/yacht-booking-system/frontend/assets/css/calendar.scss` +- `wp-content/plugins/yacht-booking-system/frontend/assets/css/calendar.css` +- `wp-content/plugins/yacht-booking-system/frontend/class-calendar-widget.php` +- `test-add-booking.php` (nowy, helper testowy) +- `.paul/phases/09-finalizacja/09-01-PLAN.md` (nowy) +- `.paul/phases/09-finalizacja/09-01-SUMMARY.md` (nowy) +- `.paul/STATE.md` +- `.paul/ROADMAP.md` diff --git a/.paul/codebase/architecture.md b/.paul/codebase/architecture.md new file mode 100644 index 0000000..499dd32 --- /dev/null +++ b/.paul/codebase/architecture.md @@ -0,0 +1,130 @@ +# Architecture + +**Analysis Date:** 2026-05-05 + +## Pattern Overview + +WordPress plugin with layered architecture: CPT-backed data model, REST API surface, Singleton orchestrators, static utility classes. + +- Entry point bootstraps a single Singleton (`Yacht_Booking`) via `plugins_loaded` +- All public-facing state changes go through REST API (`/wp-json/yacht-booking/v1/`) +- Admin state changes use WordPress PRG pattern (form POST → redirect) +- External calendar sync handled by separate Integration namespaces + +## Class Inventory + +| Class | File | Pattern | Responsibility | +|-------|------|---------|---------------| +| `Yacht_Booking` | `includes/class-yacht-booking.php` | Singleton | Master orchestrator — wires all subsystems | +| `Yacht` | `includes/class-yacht.php` | Static utility | `yacht` CPT registration + meta accessors | +| `Booking` | `includes/class-booking.php` | Static utility | `yacht_booking` CPT + `create()` + status | +| `Inquiry` | `includes/class-inquiry.php` | Static utility | `yacht_inquiry` CPT + `send_emails()` | +| `Availability` | `includes/class-availability.php` | Static utility | All `wp_yacht_availability` table operations | +| `Rest_Controller` | `api/class-rest-controller.php` | Extends `WP_REST_Controller` | 7 REST endpoints, booking orchestration | +| `Settings` | `includes/class-settings.php` | Static utility | Typed `wp_options` accessors + formatting helpers | +| `Installer` | `includes/class-installer.php` | Plain class | DB table creation + default options on activation | +| `Email_Templates` | `includes/class-email-templates.php` | Static utility | Template storage, compilation, tag replacement | +| `Admin` | `admin/class-admin.php` | Singleton (admin-only) | Menu, form processing, CSV export, customer emails | +| `Booking_List_Table` | `admin/class-booking-list-table.php` | Extends `WP_List_Table` | Bookings admin table | +| `Yacht_List_Table` | `admin/class-yacht-list-table.php` | Extends `WP_List_Table` | Yachts admin table | +| `Inquiry_List_Table` | `admin/class-inquiry-list-table.php` | Extends `WP_List_Table` | Inquiries admin table | +| `Sync_Controller` | `integrations/google-calendar/class-sync-controller.php` | Singleton | GCal cron sync orchestration | +| `GCal_Service` | `integrations/google-calendar/class-gcal-service.php` | Plain class | Google Calendar API calls | +| `OAuth_Handler` | `integrations/google-calendar/class-oauth-handler.php` | Plain class | OAuth 2.0 token storage + refresh | +| `ICal_Feed` | `integrations/ical/class-ical-feed.php` | Static/plain | iCal feed generation + rewrite rule | +| `ICal_Import` | `integrations/ical/class-ical-import.php` | Static/plain | External iCal URL import + cron | +| `Shortcode` | `frontend/class-shortcode.php` | Singleton | `[yacht_calendar]` shortcode | +| `Calendar_Widget` | `frontend/class-calendar-widget.php` | Extends Elementor `Widget_Base` | Elementor `yacht-calendar` widget | + +## Dependency Graph + +``` +yacht-booking-system.php + └── Yacht_Booking (Singleton) + ├── Yacht (static) + ├── Booking (static) + │ └── fires: yacht_booking_created + │ yacht_booking_status_changed + ├── Availability (static) + ├── Settings (static) + ├── Email_Templates (static) + ├── Rest_Controller + │ ├── Yacht, Booking, Availability, Settings, Inquiry + │ ├── Email_Templates + │ └── listens: yacht_booking_created → send_booking_notification (admin email) + ├── Admin (Singleton, admin-only) + │ ├── Booking, Availability, Yacht, Settings, Email_Templates, Inquiry + │ ├── Booking_List_Table → Booking, Availability, Settings + │ ├── Yacht_List_Table, Inquiry_List_Table + │ └── listens: yacht_booking_status_changed → send_customer_notification + ├── Sync_Controller (Singleton) + │ ├── GCal_Service → OAuth_Handler + │ └── writes: Availability::mark_as_blocked + ├── ICal_Import → Availability::mark_as_blocked + └── ICal_Feed (feed output) +``` + +## WP Hook Map + +| Hook | Type | Registered by | Handler | +|------|------|---------------|---------| +| `plugins_loaded` (p10) | action | bootstrap | `yacht_booking_init()` → `Yacht_Booking::get_instance()` | +| `init` (p10) | action | Yacht_Booking | `register_post_types()` | +| `init` (p15) | action | Yacht_Booking | `register_shortcodes()` | +| `init` | action | bootstrap | `yacht_booking_load_textdomain()` | +| `wp_enqueue_scripts` | action | Yacht_Booking | `enqueue_frontend_assets()` (conditional) | +| `admin_enqueue_scripts` | action | Yacht_Booking | `enqueue_admin_assets()` (conditional) | +| `rest_api_init` | action | Yacht_Booking | `register_rest_routes()` | +| `elementor/widgets/register` | action | Yacht_Booking | `register_elementor_widgets()` | +| `admin_init` | action | Yacht_Booking | `add_custom_capabilities()` | +| `admin_menu` (p9) | action | Admin | `register_admin_menu()` | +| `admin_init` | action | Admin | `process_bulk_actions()`, `process_booking_actions()`, `process_yacht_save()`, `process_settings_save()`, `process_export_download()` | +| `admin_notices` | action | Admin | `display_admin_notices()` | +| `yacht_booking_created` | custom action | Rest_Controller | `send_booking_notification()` (admin email) | +| `yacht_booking_status_changed` | custom action | Admin | `send_customer_notification()` | +| `before_delete_post` | action | Sync_Controller | `on_booking_deleted()` (GCal event delete) | +| `wp_ajax_yacht_booking_manual_sync` | action | Sync_Controller | `ajax_manual_sync()` | +| `yacht_booking_sync_to_gcal` | cron action | Sync_Controller | `sync_booking_to_gcal()` | +| `yacht_booking_pull_from_gcal` | cron action (hourly) | Sync_Controller | `pull_from_gcal()` | +| `yacht_booking_ical_import` | cron action (hourly) | ICal_Import | `run_import()` | +| `init` | action | ICal_Feed | `add_rewrite_rules()` | +| `template_redirect` | action | ICal_Feed | `handle_feed_request()` | + +## Key Data Flows + +### Booking Creation (Frontend → REST → DB) +1. User picks dates on FullCalendar → `GET /availability/{yacht_id}` → `Availability::get_availability_calendar()` +2. User submits form → `POST /bookings` with `X-WP-Nonce` header +3. `Rest_Controller::create_booking()`: nonce check → `is_booking_enabled()` → `Availability::is_available()` +4. Price: `count_days() × get_price_per_day()` +5. `Booking::create()` → inserts CPT post + meta → fires `yacht_booking_created` +6. `Availability::mark_as_booked()` → inserts rows in `wp_yacht_availability` +7. Admin email sent via `Email_Templates`; JSON response to frontend + +### Admin Booking Management +1. Bookings list → `Booking_List_Table::prepare_items()` (WP_Query) +2. Row action URLs: nonce-protected GET `?action=approve_booking_{id}` +3. `Admin::process_booking_actions()` → `Booking::update_status()` → fires `yacht_booking_status_changed` +4. `Admin::send_customer_notification()` → customer email +5. On cancel/delete: `Availability::clear_booking_availability($booking_id)` deletes rows by booking_id +6. PRG redirect back to list + +### GCal Sync +1. Hourly WP Cron: `pull_from_gcal()` → `GCal_Service` → Google API → `Availability::mark_as_blocked()` +2. On booking create: scheduled single cron `yacht_booking_sync_to_gcal` → push to GCal +3. OAuth token refresh: `OAuth_Handler::get_access_token()` → auto-refresh if expired + +## Error Handling Strategy + +- REST endpoints return `WP_Error` with HTTP status codes +- Admin operations: `wp_die()` on capability failure; `?error=1` redirect on soft errors +- `Booking::create()` returns `false` on failure — caller checks +- GCal API: `is_wp_error()` check only; HTTP 4xx/5xx responses silently ignored +- No exceptions used anywhere + +## Custom Capabilities + +Added once to `administrator` role via `Yacht_Booking::add_custom_capabilities()`: +- `yacht_booking_manage_yachts` +- `yacht_booking_manage_bookings` +- `yacht_booking_manage_settings` diff --git a/.paul/codebase/concerns.md b/.paul/codebase/concerns.md new file mode 100644 index 0000000..6f3c790 --- /dev/null +++ b/.paul/codebase/concerns.md @@ -0,0 +1,189 @@ +# Technical Concerns + +**Analysis Date:** 2026-05-05 +**Revised:** 2026-05-05 — verified call sites; demoted false positives (see CHANGELOG at bottom) + +Prioritized list of issues found during codebase analysis. + +--- + +## CRITICAL — Security / Data Loss Risk + +### C1. Race condition on booking creation — no atomic lock + +**Files:** `includes/class-availability.php` (`is_available`), `api/class-rest-controller.php` (`create_booking` lines 326–362) + +Flow: `is_available()` → `Booking::create()` → `mark_as_booked()` — three separate non-transactional operations. Two concurrent POST requests for the same yacht and overlapping dates will both pass the check before either writes. The `UNIQUE KEY yacht_date (yacht_id, date)` will reject the second `mark_as_booked()` iteration silently, but both `yacht_booking` posts are already created. Admin sees two confirmed bookings for the same dates. + +**Fix:** Wrap in a DB transaction with `SELECT ... FOR UPDATE` on availability rows, or insert a sentinel lock row first and check the result before proceeding. + +--- + +### C2. OAuth credentials stored unencrypted in `wp_options` + +**File:** `integrations/google-calendar/class-oauth-handler.php` lines 185–187, 213–215 + +`yacht_booking_gcal_credentials` (client_id, client_secret) and `yacht_booking_gcal_tokens` (access_token, refresh_token) stored as plaintext serialized arrays. Any plugin or user with DB read access can extract a working Google Calendar refresh token. If the database is compromised (via SQL injection elsewhere on the WordPress install or hosting breach), the Google Calendar account is fully exposed. + +**Fix:** Encrypt with WordPress secret keys (`AUTH_KEY` etc.) before storing; decrypt on read. + +--- + +## HIGH — Reliability + +### H1. GCal API HTTP errors silently ignored + +**Files:** `integrations/google-calendar/class-gcal-service.php`, `class-oauth-handler.php` + +Every API call checks `is_wp_error()` but not the HTTP response code. A `401`, `403`, `429`, or `500` from Google is treated as success — `json_decode` runs, expected fields like `$body['id']` or `$body['access_token']` are absent, function returns `false`. All errors only logged when `WP_DEBUG === true`. In production: complete silence. + +--- + +### H2. Token refresh has no concurrency protection + +**File:** `integrations/google-calendar/class-oauth-handler.php` lines 107–128 + +Simultaneous WP-Cron events both finding the token expired will both call `refresh_access_token()`. Google may invalidate the first new token when the second refresh request arrives. Result: permanently broken GCal connection requiring manual reauth. + +**Fix:** Wrap refresh in a transient-based lock (`set_transient('gcal_refresh_lock', 1, 60)`), or use `wp_cache_add()` semantics. + +--- + +### H3. `register_cron_actions()` may not be called — cron hooks fire with no registered callbacks + +**File:** `integrations/google-calendar/class-sync-controller.php` lines 141–153 + +The constructor registers `on_booking_created` etc., but `register_cron_actions()` (which registers the `yacht_booking_sync_to_gcal` action itself) is a separate static method. Need to verify it is called on every `init` / `plugins_loaded` request. If not, scheduled single events silently fire with no handler. + +--- + +### H4. Single calendar for all yachts — GCal events apply to every yacht + +**File:** `integrations/google-calendar/class-gcal-service.php` lines 196–272 + +`get_calendar_id()` returns one shared option (`yacht_booking_gcal_calendar_id`). `pull_from_gcal()` calls `sync_from_gcal($yacht->ID)` for each yacht but each call fetches all events from the same calendar and blocks that yacht for all those dates. Multi-yacht sites will incorrectly block all yachts for every GCal event. + +--- + +### H5. `tmp-fix-polish.php` in project root reveals dev paths + +**File:** `tmp-fix-polish.php` + +Contains only one line: a path string to another local project (`c:\visual studio code\projekty\jachty.pagedev.pl\tmp-fix-polish.php`). No PHP tag, no code. If deployed via FTP, the raw path string is publicly accessible at the URL. Should be deleted and excluded from deployment. + +--- + +## MEDIUM — Maintainability / Correctness + +### M1. Date validation missing on booking creation + +**File:** `api/class-rest-controller.php` lines 116–122 + +`start_date`/`end_date` use only `sanitize_text_field` — no `validate_callback`. An invalid date string like `"not-a-date"` reaches `new \DateTime()` in `Availability::count_days()` without try/catch, producing an uncaught fatal error visible to the public. The `/availability` endpoint validates dates correctly; `/bookings` does not. + +**Fix:** Add `validate_callback` checking `\DateTime::createFromFormat('Y-m-d', $param) !== false`. + +--- + +### M2. No pagination on `get_yachts` and `get_bookings` + +**File:** `api/class-rest-controller.php` lines 222–245, 429–462 + +Both endpoints use `posts_per_page => -1`. Will degrade progressively as bookings accumulate. + +--- + +### M3. N+1 query pattern in booking and yacht lists + +**File:** `api/class-rest-controller.php` lines 442–458 + +For each booking: `get_post($yacht_id)` called individually inside loop. For each yacht: three separate `get_post_meta()` calls per row. With 20 bookings: 60+ queries per response. + +--- + +### M4. iCal importer ignores `STATUS:CANCELLED` events + +**File:** `integrations/ical/class-ical-import.php` `parse_ics()` method + +Parser reads UID, SUMMARY, DTSTART, DTEND but ignores STATUS. Cancelled events in the iCal feed are imported as blocks. The GCal service correctly skips `status: cancelled` events (line 347 of `class-gcal-service.php`) — parity needed in iCal importer. + +--- + +### M5. `mark_as_blocked()` passes `null` booking_id with `%d` format + +**File:** `includes/class-availability.php` lines 98–109 + +`$wpdb->replace()` receives `null` with `%d` format specifier. In older MySQL/MariaDB this may insert `0` instead of `NULL`, making it impossible to distinguish a blocked row from one linked to booking ID 0. + +--- + +### M6. OAuth redirect URI hardcoded to production domain + +**File:** `integrations/google-calendar/class-oauth-handler.php` lines 40, 72 + +`$redirect_uri = 'https://jachty.pagedev.pl/wp-admin/...'` — literal string, not `admin_url()`. OAuth fails completely on staging or local dev. Comment acknowledges this is intentional but makes testing and domain migration fragile. + +--- + +## LOW — Cleanup / Polish + +### L1. Dead code: `Availability::mark_as_available()` has bug but is never called + +**File:** `includes/class-availability.php` lines 123–149 + +The function ignores its `$start_date`/`$end_date` parameters and runs `$wpdb->delete()` filtered only by `yacht_id` — which would wipe the entire availability table for that yacht. + +**Verification:** `grep` across the plugin shows zero call sites. All code that handles cancellation/deletion uses `clear_booking_availability($booking_id)` (in `Admin`, `Rest_Controller`, `GCal_Service`, `ICal_Import`), which correctly filters by `booking_id`. + +**Risk:** Trap for a future developer who reads the method name and decides to use it. Recommend either deleting the function or replacing its body with the commented-out correct implementation that already lives below it (lines 137–146). + +--- + +### L2. Dead form field: `yacht_booking_nonce` from `wp_nonce_field` is never read + +**Files:** `frontend/class-shortcode.php:329`, `frontend/class-calendar-widget.php:429` + +The booking form renders `wp_nonce_field('yacht_booking_submit', 'yacht_booking_nonce')` as a hidden input, but `calendar.js` (lines 336–343) builds `formData` manually from named fields and never includes it. The actual nonce travels via `X-WP-Nonce` header from `yachtBookingData.nonce` (a `wp_create_nonce('wp_rest')` value), which matches what `Rest_Controller::create_booking()` verifies. + +**Risk:** Code rot — looks like it's doing something but isn't. Either remove the `wp_nonce_field` call, or wire JS to send it as a fallback for the no-JS case (currently there is no no-JS fallback anyway). + +--- + +### L3. Logging disabled in production + +All `error_log()` calls gated on `WP_DEBUG === true`. GCal sync errors, token refresh failures, and iCal import problems are silently discarded in production. Consider a persistent log table or option-based error queue surfaced in admin. + +--- + +### L4. No incremental DB migration path + +**File:** `includes/class-installer.php` + +No `upgrade()` method runs when `yacht_booking_version` in `wp_options` differs from `YACHT_BOOKING_VERSION`. `dbDelta()` adds missing columns but won't remove old ones or modify existing column types. Future schema changes risk silent failures on existing installs. + +--- + +### L5. `yacht_booking` CPT exposed to default WP REST API + +**File:** `includes/class-booking.php` line 48 + +`show_in_rest => true` registers the CPT at `/wp/v2/yacht_booking`. The plugin's own `/yacht-booking/v1/` namespace is the intended REST surface. Set `show_in_rest => false` unless there is a reason for the default WP exposure. + +--- + +### L6. `Booking::create()` does not validate end > start + +**File:** `includes/class-booking.php` lines 229–230 + +A booking with `end_date` before `start_date` is stored without error. `get_date_range()` while-loop exits immediately, no availability rows are created, and the booking is invisible in the calendar but exists as a CPT post. + +--- + +## CHANGELOG + +**2026-05-05 (revision):** Subagent audit produced two false positives that were initially listed as CRITICAL. Verified by tracing call sites: + +- **Old C1** (`mark_as_available` deletes all rows) → demoted to **L1**. The function is never called anywhere in the plugin; all cancellation paths use the correct `clear_booking_availability($booking_id)`. Remains a trap for future devs but no production impact. +- **Old C3** (POST /bookings nonce mismatch) → demoted to **L2**. The actual auth path (JS → `X-WP-Nonce` header → `wp_verify_nonce(..., 'wp_rest')`) is fully consistent. The `wp_nonce_field('yacht_booking_submit', ...)` in shortcode/widget renders a hidden input that JS never reads — dead form field, not a security gap. + +Race condition (old C2 → C1) and unencrypted OAuth tokens (old C4 → C2) remain valid CRITICAL items. diff --git a/.paul/codebase/conventions.md b/.paul/codebase/conventions.md new file mode 100644 index 0000000..b94d538 --- /dev/null +++ b/.paul/codebase/conventions.md @@ -0,0 +1,110 @@ +# Coding Conventions + +**Analysis Date:** 2026-05-05 + +## PHP Style + +**Standard:** WordPress Coding Standards (not PSR-12). + +- 1 tab indentation +- `array()` long-form always — never `[]` short syntax in plugin code +- Spaces inside control structure parens: `if ( $condition )`, `foreach ( $items as $item )` +- No spaces inside function call parens: `get_post_meta( $id, '_key', true )` +- Every PHP file starts with direct-access guard: + ```php + if ( ! defined( 'ABSPATH' ) ) { exit; } + ``` +- File-level docblock with `@package YachtBooking` + +## Naming + +| Thing | Convention | Example | +|-------|-----------|---------| +| Classes | PascalCase (underscore for multi-word) | `Rest_Controller`, `Yacht_Booking` | +| Methods | snake_case | `get_yacht_id()`, `mark_as_booked()` | +| Variables | snake_case | `$booking_id`, `$start_date` | +| Constants | SCREAMING_SNAKE_CASE | `YACHT_BOOKING_VERSION` | +| Post meta keys | underscore-prefixed, namespaced | `_booking_start_date`, `_yacht_capacity` | +| WP option keys | `yacht_booking_` prefix | `yacht_booking_version` | +| Boolean getters | `is_` / `has_` prefix | `is_available()`, `has_yacht_calendar_widget()` | +| Static getters | `get_{thing}($id)` | `Booking::get_customer_name($id)` | +| Static updaters | `update_{thing}($id, $value)` | `Yacht::update_capacity($id, $v)` | + +## Class Patterns + +**Singleton** — for hook-registering classes (must only run once): +```php +private static $instance = null; +public static function get_instance() { + if ( null === self::$instance ) { self::$instance = new self(); } + return self::$instance; +} +private function __construct() { /* register hooks here */ } +``` +Used by: `Yacht_Booking`, `Admin`, `Shortcode`, `Sync_Controller` + +**Static utility** — for pure data accessors with no instance state: +```php +public static function get_customer_name( $booking_id ) { + return get_post_meta( $booking_id, '_booking_customer_name', true ); +} +``` +Used by: `Booking`, `Yacht`, `Availability`, `Settings`, `Email_Templates` + +**All WP hook registration goes in constructor or `init_hooks()` method.** + +## Security Patterns + +- **Nonces in admin forms:** `wp_nonce_field()` on output, `check_admin_referer()` on submit +- **Nonces in REST endpoints:** `X-WP-Nonce` header verified with `wp_verify_nonce($header, 'wp_rest')` +- **iCal feed:** per-yacht token in `_yacht_ical_token` meta, compared with `hash_equals()` +- **Admin REST endpoints:** `current_user_can('yacht_booking_manage_bookings')` +- **Sanitization on input:** `sanitize_text_field()`, `sanitize_email()`, `absint()`, `wp_kses_post()` +- **Escaping on output:** `esc_html()`, `esc_attr()`, `esc_url()`, `wp_kses_post()` +- **All `$wpdb` queries:** use `$wpdb->prepare()` — no raw SQL + +## WordPress Integration Patterns + +- CPT registration via static `register()` method called by orchestrator +- Options: `add_option()` (idempotent) on activation, `get_option()` / `update_option()` for runtime +- Capability checks use custom caps (`yacht_booking_manage_bookings`), not `manage_options` +- Admin form submissions: POST → `admin_init` handler → `wp_safe_redirect()` (PRG pattern) +- Error feedback: `?error=message` query string + `admin_notices` hook display + +## JavaScript Style + +- **Pattern:** IIFE `(function($) { 'use strict'; ... })(jQuery)` in both JS files +- **Variable naming:** camelCase; jQuery-wrapped objects prefixed with `$` (`$form`, `$submitBtn`) +- **`calendar.js`:** Named inner functions (`initYachtCalendar`, `formatDate`, `escapeHtml`); uses `const`/`let` +- **`admin.js`:** Object-literal module pattern (`const YachtBookingAdmin = { init, bindEvents, handleManualSync }`) +- **Data bridge:** `wp_localize_script()` injects `yachtBookingData` (apiUrl, nonce, bookingEnabled, i18n) and `yachtBookingAdmin` +- **Error display:** Inline HTML into `.yacht-booking-response` / `.yacht-inquiry-response` divs; messages from `yachtBookingData.i18n` + +## CSS / Markup + +- BEM-like kebab-case with `yacht-` prefix: `.yacht-calendar-wrapper`, `.yacht-booking-form-container` +- Modifier suffixes: `.yacht-calendar-view-only`, `.yacht-legend-swatch-past` +- JS state classes: `.is-active`, `.yacht-day-available`, `.yacht-day-booked` +- Mobile-first approach + +## Internationalisation + +- All user-visible strings: `__()`, `esc_html__()`, `esc_attr_e()` with domain `'yacht-booking'` +- Strings with variables: `printf( esc_html__( 'Text %s', 'yacht-booking' ), esc_html( $var ) )` — never concatenation +- Translators hint: `/* translators: %s: yacht name */` before `printf` + +## Documentation + +- Public/protected methods always get docblocks; private helpers sometimes omitted +- Inline block comments label logical sections: `// Get yacht title`, `// Save booking meta` +- HTML comments label template sections in Polish: `` +- JS functions get JSDoc blocks: `/** Format date to YYYY-MM-DD */` +- PHPDoc generics notation for complex types: `@return array>` + +## Error Handling + +- PHP: return `false` or `WP_Error` on failure +- REST: return `new \WP_Error( 'code', message, array( 'status' => 4xx ) )` +- No exceptions used anywhere in the plugin +- GCal API: `is_wp_error()` check only — HTTP error codes are NOT checked (known gap) +- Logging: `error_log()` with prefixes only when `WP_DEBUG === true`; nothing in production diff --git a/.paul/codebase/integrations.md b/.paul/codebase/integrations.md new file mode 100644 index 0000000..f44fc94 --- /dev/null +++ b/.paul/codebase/integrations.md @@ -0,0 +1,120 @@ +# External Integrations + +**Analysis Date:** 2026-05-05 + +## Google Calendar API v3 + +**Purpose:** Bidirectional sync — bookings push to Google Calendar as events; Google Calendar events pull back as availability blocks. + +**Implementation:** Native PHP, no SDK. Uses WordPress HTTP API (`wp_remote_post`, `wp_remote_get`, `wp_remote_request`). + +**Files:** +- `integrations/google-calendar/class-gcal-service.php` — API calls +- `integrations/google-calendar/class-oauth-handler.php` — OAuth 2.0 token management +- `integrations/google-calendar/class-sync-controller.php` — Cron orchestration (Singleton) + +**Authentication:** OAuth 2.0 with `offline` access (refresh token). +- Auth endpoint: `https://accounts.google.com/o/oauth2/v2/auth` +- Token endpoint: `https://oauth2.googleapis.com/token` +- Scope: `https://www.googleapis.com/auth/calendar` +- Credentials stored in `wp_options` key `yacht_booking_gcal_credentials` +- Tokens stored in `wp_options` key `yacht_booking_gcal_tokens` +- Calendar ID stored in `yacht_booking_gcal_calendar_id` (defaults to `'primary'`) +- **OAuth redirect URI hardcoded:** `https://jachty.pagedev.pl/wp-admin/admin.php?page=yacht-bookings-settings&tab=google-calendar&gcal_callback=1` + +**Outbound API calls:** +| Method | URL | Purpose | +|--------|-----|---------| +| POST | `.../calendars/{id}/events` | Create event | +| PATCH | `.../calendars/{id}/events/{eventId}` | Update event | +| DELETE | `.../calendars/{id}/events/{eventId}` | Delete event | +| GET | `.../calendars/{id}/events` | List events (pull sync) | +| GET | `.../users/me/calendarList` | List calendars | +| POST | `https://oauth2.googleapis.com/token` | Token exchange/refresh | +| GET | `https://www.googleapis.com/oauth2/v2/userinfo` | Get connected email | + +**Sync model:** Pull-only for external → local (hourly WP Cron `yacht_booking_pull_from_gcal`). Push for local → GCal on `yacht_booking_created` / `yacht_booking_status_changed` (scheduled single cron events). + +**Limitations:** +- Single shared calendar for all yachts — GCal events apply to every yacht, not per-yacht +- No incoming webhooks — polling model only +- No retry logic on API failure + +## iCal Integration + +### Export (Feed) + +**Purpose:** Generate `.ics` file per yacht for subscription by external clients (other booking systems, personal calendars). + +**File:** `integrations/ical/class-ical-feed.php` + +**URL pattern:** `{home_url}/yacht-ical/{yacht_id}/{token}.ics` +- Token stored in `_yacht_ical_token` post meta (24-char alphanumeric, auto-generated) +- Verified with `hash_equals()` — timing-safe +- Returns `text/calendar` with all non-cancelled bookings for that yacht +- Regenerate token: `ICal_Feed::regenerate_token($yacht_id)` + +### Import (Subscribe) + +**Purpose:** Subscribe to external iCal URLs per yacht; import events as availability blocks (hourly cron). + +**File:** `integrations/ical/class-ical-import.php` + +**Config:** Per-yacht `_yacht_ical_import_url` post meta (set via `ICal_Import::set_import_url()`) + +**Behavior:** Fetches URL via `wp_remote_get()` (30-second timeout, SSL verification), parses VEVENT components, creates `yacht_booking` posts with `_booking_source = 'ical_import'`, marks dates as blocked via `Availability::mark_as_blocked()`. + +**Limitation:** Does not check VEVENT `STATUS` property — cancelled events in the feed are imported as blocks. + +## jsDelivr CDN + +**Purpose:** FullCalendar library loaded from CDN (no local copy). + +| Asset | URL | +|-------|-----| +| FullCalendar JS | `https://cdn.jsdelivr.net/npm/fullcalendar@6.1.10/index.global.min.js` | +| FullCalendar CSS | `https://cdn.jsdelivr.net/npm/fullcalendar@6.1.10/index.global.min.css` | +| Polish locale | `https://cdn.jsdelivr.net/npm/@fullcalendar/core@6.1.10/locales/pl.global.min.js` | + +Enqueued in `Yacht_Booking::enqueue_frontend_assets()`, conditional on page content. + +## Email + +**Provider:** WordPress `wp_mail()` only — no Mailgun, SendGrid, or other transactional service. + +**Templates:** `includes/class-email-templates.php` — templates stored in `wp_options` as tagged strings, compiled with `get_booking_template_data()`. + +**When sent:** +- On booking creation: admin notification via `Rest_Controller::send_booking_notification()` (hooked on `yacht_booking_created`) +- On status change: customer notification via `Admin::send_customer_notification()` (hooked on `yacht_booking_status_changed`) +- On inquiry create: admin + customer emails via `Inquiry::send_emails()` (built inline, not using Email_Templates) + +**Headers:** `From:` pulled from `yacht_booking_email_from` option; `Reply-To:` set explicitly. + +## REST API (Internal) + +Namespace: `yacht-booking/v1` — base: `/wp-json/yacht-booking/v1/` + +| Method | Endpoint | Auth | Handler | +|--------|----------|------|---------| +| GET | `/yachts` | Public | `get_yachts()` | +| GET | `/yachts/{id}` | Public | `get_yacht()` | +| GET | `/availability/{yacht_id}?start=Y-m-d&end=Y-m-d` | Public | `get_availability()` | +| POST | `/bookings` | Public + `X-WP-Nonce` | `create_booking()` | +| POST | `/inquiries` | Public + `X-WP-Nonce` | `create_inquiry()` | +| GET | `/bookings` | Admin cap | `get_bookings()` | +| PUT | `/bookings/{id}/status` | Admin cap | `update_booking_status()` | + +All registered in `api/class-rest-controller.php`. + +## Monitoring & Observability + +- No error tracking service (no Sentry, Bugsnag) +- `error_log()` used with prefixes: `[Yacht Booking - GCal]`, `[Yacht Booking - GCal Sync]`, `[Yacht Booking - iCal]` +- Logging only fires when `WP_DEBUG === true` — silent in production + +## Deployment + +- **Method:** FTP via VS Code ftp-kr extension (`.vscode/ftp-kr.json`) +- **CI/CD:** None — fully manual +- **Production URL:** `https://jachty.pagedev.pl` (hardcoded in GCal OAuth redirect) diff --git a/.paul/codebase/overview.md b/.paul/codebase/overview.md new file mode 100644 index 0000000..e71c308 --- /dev/null +++ b/.paul/codebase/overview.md @@ -0,0 +1,52 @@ +# Codebase Overview + +**Project:** jachty3.pagedev.pl — Yacht Booking System +**Analyzed:** 2026-05-05 + +## What This Is + +A custom WordPress plugin (`yacht-booking-system`) that provides yacht rental booking management with a FullCalendar frontend, REST API backend, Google Calendar bidirectional sync, and iCal feed export/import. Built for a Polish yacht charter company at `https://jachty.pagedev.pl`. + +## Primary Entry Point + +`wp-content/plugins/yacht-booking-system/yacht-booking-system.php` + +- Defines 5 plugin constants (`YACHT_BOOKING_VERSION`, `YACHT_BOOKING_PLUGIN_DIR`, etc.) +- Registers a custom `spl_autoload_register` for the `YachtBooking\` namespace → `includes/class-*.php` +- Fires `Yacht_Booking::get_instance()` on `plugins_loaded` +- Activation hook: runs `Installer::install()`, schedules GCal and iCal cron jobs, flushes rewrite rules + +## Core Data Model + +Two parallel stores: +1. **WordPress CPTs** (in `wp_posts` + `wp_postmeta`): + - `yacht` — meta: capacity, price_per_day, gcal_id, features, ical_token, ical_import_url + - `yacht_booking` — meta: yacht_id, start_date, end_date, status, customer_*, total_price, gcal_event_id, source + - `yacht_inquiry` — meta: yacht_id, customer_*, preferred_dates, message (hidden from WP admin UI) +2. **Custom availability table** `wp_yacht_availability`: + - One row per yacht-date pair; only `booked` and `blocked` rows stored; absence = available + - Unique key on `(yacht_id, date)` + +## User-Facing Features + +- FullCalendar month view embedded via `[yacht_calendar yacht_id="X"]` shortcode or Elementor widget +- Booking form (date selection → customer details → submission via REST) +- Inquiry form (non-binding contact, sends email to admin) +- iCal feed per yacht at `{home_url}/yacht-ical/{yacht_id}/{token}.ics` + +## Admin Features + +- Custom admin menu with 6 pages: Bookings list, Inquiries list, Yachts list/edit, Settings, Google Calendar +- Approve/Cancel/Delete bookings with PRG pattern +- CSV export of bookings +- Google Calendar bidirectional sync (OAuth 2.0, manual + hourly cron) +- iCal import from external URLs per yacht (hourly cron) +- Email notifications: admin on booking create, customer on status change + +## Language / Locale + +UI language: Polish. All strings use `__()` / `esc_html__()` with `yacht-booking` text domain. + +## Team / Deployment + +Single developer. FTP deploy via ftp-kr VS Code extension. No CI/CD pipeline. Production: `https://jachty.pagedev.pl`. No Docker, no local environment config detected. diff --git a/.paul/codebase/stack.md b/.paul/codebase/stack.md new file mode 100644 index 0000000..72e3a88 --- /dev/null +++ b/.paul/codebase/stack.md @@ -0,0 +1,65 @@ +# Technology Stack + +**Analysis Date:** 2026-05-05 + +## Languages + +- **PHP 7.4+** — all server-side plugin logic (`Requires PHP: 7.4` in plugin header) +- **JavaScript (ES5/IIFE)** — frontend calendar + booking form (`calendar.js`), admin panel JS (`admin.js`) +- **SCSS** — frontend calendar styles (source at `frontend/assets/css/calendar.scss`; must be manually compiled) +- **SQL** — custom table queries via `$wpdb->prepare()` + +## Runtime + +- **WordPress 6.0+** (`Requires at least: 6.0`) +- **Elementor Pro** — optional; plugin checks `class_exists('\Elementor\Plugin')` and degrades gracefully +- **MySQL** — custom table + WordPress standard tables + +## Key Dependencies + +| Dependency | Version | How Loaded | +|-----------|---------|-----------| +| FullCalendar | 6.1.10 | jsDelivr CDN | +| FullCalendar Polish locale | 6.1.10 | jsDelivr CDN | +| jQuery | WP bundled | WordPress core | +| Google Calendar API v3 | — | Native HTTP (no SDK) | + +**CDN URLs:** +- `https://cdn.jsdelivr.net/npm/fullcalendar@6.1.10/index.global.min.js` +- `https://cdn.jsdelivr.net/npm/fullcalendar@6.1.10/index.global.min.css` +- `https://cdn.jsdelivr.net/npm/@fullcalendar/core@6.1.10/locales/pl.global.min.js` + +## Plugin Constants + +Defined in `yacht-booking-system.php` (lines 23–27): + +```php +YACHT_BOOKING_VERSION = '1.0.0' +YACHT_BOOKING_PLUGIN_FILE = __FILE__ +YACHT_BOOKING_PLUGIN_DIR = plugin_dir_path(__FILE__) +YACHT_BOOKING_PLUGIN_URL = plugin_dir_url(__FILE__) +YACHT_BOOKING_PLUGIN_BASENAME = plugin_basename(__FILE__) +``` + +## Autoloading + +Custom `spl_autoload_register` (lines 32–56, `yacht-booking-system.php`): +- Namespace prefix: `YachtBooking\` +- Maps to: `YACHT_BOOKING_PLUGIN_DIR . 'includes/'` +- Rule: `YachtBooking\Foo_Bar` → `includes/class-foo-bar.php` +- **Only covers `includes/`** — integration and API classes loaded via explicit `require_once` + +## Configuration Storage + +- No `.env` file — all runtime config in `wp_options` +- Key options: `yacht_booking_gcal_credentials`, `yacht_booking_gcal_tokens`, `yacht_booking_gcal_calendar_id`, `yacht_booking_gcal_sync_enabled`, `yacht_booking_default_status`, `yacht_booking_enable_notifications` + +## Build Pipeline + +None. No Composer, no npm, no webpack/vite/esbuild. Assets are static hand-authored files deployed via FTP (`ftp-kr` VS Code extension, config: `.vscode/ftp-kr.json`). + +## Platform Requirements + +- **Dev:** VS Code + ftp-kr, PHP 7.4+ CLI (`php -l ` for syntax checks) +- **Production:** PHP 7.4+, MySQL, WordPress 6.0+, shared hosting or VPS +- **Note:** OAuth redirect URI hardcoded to `https://jachty.pagedev.pl` — staging/local OAuth will fail diff --git a/.paul/codebase/structure.md b/.paul/codebase/structure.md new file mode 100644 index 0000000..3f4693b --- /dev/null +++ b/.paul/codebase/structure.md @@ -0,0 +1,94 @@ +# Codebase Structure + +**Analysis Date:** 2026-05-05 + +## Directory Layout + +``` +wp-content/plugins/yacht-booking-system/ +├── yacht-booking-system.php # Plugin entry: constants, autoloader, hooks +├── includes/ # Core PHP classes (autoloaded via spl_autoload_register) +│ ├── class-yacht-booking.php # Main Singleton orchestrator +│ ├── class-yacht.php # Yacht CPT + static meta accessors +│ ├── class-booking.php # Booking CPT + static meta accessors +│ ├── class-inquiry.php # Inquiry CPT + static meta accessors + email sending +│ ├── class-availability.php # wp_yacht_availability table read/write +│ ├── class-installer.php # DB table creation, default options +│ ├── class-settings.php # wp_options accessors + formatting helpers +│ └── class-email-templates.php # Email template storage, compilation, tag replacement +├── api/ # REST API (loaded via require_once, not autoloader) +│ └── class-rest-controller.php # All REST routes under yacht-booking/v1 +├── admin/ # WP admin panel (is_admin() gate) +│ ├── class-admin.php # Admin Singleton: menu, form handling, CSV export +│ ├── class-booking-list-table.php # WP_List_Table for bookings +│ ├── class-yacht-list-table.php # WP_List_Table for yachts +│ ├── class-inquiry-list-table.php # WP_List_Table for inquiries +│ ├── views/ +│ │ └── yacht-edit.php # Add/edit yacht form template +│ └── assets/ +│ ├── css/admin.css +│ └── js/admin.js +├── frontend/ +│ ├── class-calendar-widget.php # Elementor widget (yacht-calendar) +│ ├── class-shortcode.php # [yacht_calendar] shortcode, Singleton +│ └── assets/ +│ ├── css/calendar.css # Compiled from calendar.scss +│ ├── css/calendar.scss # SCSS source (manual compile required) +│ └── js/calendar.js # IIFE jQuery; consumes REST API via yachtBookingData +├── integrations/ +│ ├── google-calendar/ # Loaded via explicit require_once (not autoloader) +│ │ ├── class-oauth-handler.php # OAuth 2.0 token storage + refresh +│ │ ├── class-gcal-service.php # Google Calendar API HTTP calls +│ │ └── class-sync-controller.php # Cron sync orchestration, Singleton +│ └── ical/ +│ ├── class-ical-feed.php # Generate iCal feed, serve via rewrite rule +│ └── class-ical-import.php # Import external iCal URLs via cron +└── languages/ # .pot / .po / .mo for yacht-booking text domain +``` + +**Project root (WordPress install root — not plugin directory):** +``` +/ +├── test-yacht-plugin.php # Manual smoke test (run in browser as admin) +├── test-api-availability.php # Manual integration test (run in browser as admin) +└── tmp-fix-polish.php # Leftover artifact — single path string, no PHP code +``` + +## Naming Rules + +| Thing | Convention | Example | +|-------|-----------|---------| +| PHP class files | `class-{kebab-case}.php` | `class-rest-controller.php` | +| PHP class names | `PascalCase` (with underscores) | `Rest_Controller`, `Yacht_Booking` | +| Directories | lowercase kebab-case | `google-calendar/`, `yacht-booking-system/` | +| Autoloader mapping | `YachtBooking\Foo_Bar` → `includes/class-foo-bar.php` | — | +| Sub-namespaces (integrations) | `YachtBooking\Integrations\GoogleCalendar` | — | + +## Where to Add New Code + +**New core feature:** +- File: `includes/class-{feature}.php`, namespace `YachtBooking\` +- Load: relies on autoloader automatically (for `includes/` only) + +**New REST endpoint:** +- Add method + route registration to `api/class-rest-controller.php` +- Public: `'permission_callback' => '__return_true'` + nonce check inside handler +- Admin: `'permission_callback' => array($this, 'can_manage_bookings')` + +**New admin page:** +- Add `add_submenu_page()` in `Admin::register_admin_menu()` +- Add `render_{page}()` method in `admin/class-admin.php` +- Add form processing method hooked on `admin_init` in `Admin::__construct()` +- View HTML: extract to `admin/views/{page}.php` + +**New external integration:** +- Create `integrations/{service-name}/` +- Follow pattern: `class-{service}-service.php` + `class-sync-controller.php` +- Register cron: `static setup_cron()` + `static clear_cron()`, call from activation/deactivation hooks +- Load via `require_once` in `Yacht_Booking::load_dependencies()` + +**New setting:** +- Add default in `Installer::create_options()` +- Add typed accessor in `Settings` class +- Add UI in `Admin::render_general_settings()` or new settings tab +- Save in `Admin::save_settings()` diff --git a/.paul/codebase/testing.md b/.paul/codebase/testing.md new file mode 100644 index 0000000..a881e76 --- /dev/null +++ b/.paul/codebase/testing.md @@ -0,0 +1,106 @@ +# Testing + +**Analysis Date:** 2026-05-05 + +## Framework + +None. No PHPUnit, no Jest, no Vitest. No automated test runner configured. + +**Only testing mechanism:** Two manual PHP scripts in the project root, run in-browser while logged in as admin against the live database. + +## Test Files + +| File | Type | Purpose | +|------|------|---------| +| `test-yacht-plugin.php` | Smoke test | Verifies plugin environment is set up correctly | +| `test-api-availability.php` | Integration test | Exercises REST endpoint + Availability class | + +**How to run:** +``` +# Syntax check (run after every PHP edit): +php -l wp-content/plugins/yacht-booking-system/includes/class-booking.php + +# Browser smoke test (must be logged in as admin): +http://jachty.pagedev.local/test-yacht-plugin.php + +# Browser integration test: +http://jachty.pagedev.local/test-api-availability.php +``` + +## What Is Tested + +### test-yacht-plugin.php (Smoke Test) +1. Plugin file exists on disk +2. Plugin is active (`is_plugin_active()`) +3. `wp_yacht_availability` table exists +4. CPTs registered (`yacht`, `yacht_booking`) +5. Plugin options set (`yacht_booking_version`, `yacht_booking_installed_at`) +6. Custom capabilities assigned to `administrator` role +7. REST namespace `yacht-booking/v1` registered + route list +8. Admin menu `yacht-bookings` slug present +9. Yacht CPT CRUD: `wp_insert_post()` + meta + `wp_delete_post()` (cleanup included) + +### test-api-availability.php (Integration Test) +1. Fetch first yacht from DB +2. Call `/availability/{yacht_id}` via `wp_remote_get()` — check HTTP 200, count statuses +3. Direct call `Availability::get_availability_calendar()` — display in HTML table +4. Direct call `Availability::is_available()` on +7 to +10 day range +5. `mark_as_booked()` with fake booking ID 999 → re-check → `clear_booking_availability(999)` → re-check (full CRUD cycle) + +## What Is NOT Tested + +- `POST /bookings` — full booking creation flow via REST never tested +- Email sending (`send_booking_notification`, `send_customer_notification`, `Inquiry::send_emails`) +- Google Calendar OAuth flow +- GCal sync (push and pull) +- iCal import and export +- Admin form processing (`process_yacht_save`, `process_booking_actions`, `save_settings`) +- Nonce verification in REST endpoints +- Status transitions (`pending` → `confirmed` → `cancelled`) and side effects +- `Shortcode::render_calendar()` output +- `Calendar_Widget::render()` output +- All JavaScript behavior +- Concurrent booking race conditions +- `mark_as_available()` bug (deletes all rows for yacht — see concerns.md) + +## Test Pattern + +```php +// Access guard +require_once __DIR__ . '/wp-load.php'; +if (!current_user_can('manage_options')) { die('Admin only'); } + +// Numbered HTML sections +echo '

1. Section Name

'; + +// Visual pass/fail — no assertions that throw +if ($condition) { + echo '✅ Thing works
'; +} else { + echo '❌ Thing broken
'; + die(); // hard stop only on fatal blockers +} + +// Data output +echo '
Raw data
';
+print_r($data);
+echo '
'; +``` + +## Mocking + +None. All tests hit the live WordPress database. Side effects are real: +- `test-yacht-plugin.php`: creates and deletes a test yacht post +- `test-api-availability.php`: inserts availability rows with `booking_id=999`, then deletes them + +## Coverage Tooling + +None. No coverage measurement. + +## Style Notes in Test Files + +Intentional deviations from plugin code conventions (acceptable for dev-only scripts): +- Short array syntax `[]` used (plugin uses `array()`) +- No namespaces +- Partial output escaping — `$yacht->post_title` sometimes raw-echoed +- One raw `$wpdb->get_var()` without `prepare()` (`test-yacht-plugin.php:49`) — safe here as table name is not user input diff --git a/.paul/config.md b/.paul/config.md new file mode 100644 index 0000000..52d9d30 --- /dev/null +++ b/.paul/config.md @@ -0,0 +1,33 @@ +# Project Config + +**Project:** jachty3.pagedev.pl +**Created:** 2026-05-05 + +## Project Settings + +```yaml +project: + name: jachty3.pagedev.pl + version: 1.0.0 +``` + +## Integrations + +### SonarQube + +```yaml +sonarqube: + enabled: false + reason: "User declined during init" +``` + +## Preferences + +```yaml +preferences: + auto_commit: false + verbose_output: false +``` + +--- +*Config created: 2026-05-05* diff --git a/.paul/governance/governance_2026-05-05.jsonl b/.paul/governance/governance_2026-05-05.jsonl new file mode 100644 index 0000000..94c5c37 --- /dev/null +++ b/.paul/governance/governance_2026-05-05.jsonl @@ -0,0 +1,28 @@ +{"ts":"2026-05-05T21:35:55Z","tool":"Bash","cmd":"mkdir -p .paul/phases .paul/codebase\",\"description\":\"Create PAUL directory structure\"},\"tool_response\":{\"stdout\":\"\",\"stderr\":\"\",\"interrupted\":false,\"isImage\":false,\"noOutputExpecte","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"} +{"ts":"2026-05-05T21:39:20Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\.paul\\\\PROJECT.md","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"} +{"ts":"2026-05-05T21:39:38Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\.paul\\\\ROADMAP.md","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"} +{"ts":"2026-05-05T21:39:55Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\.paul\\\\STATE.md","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"} +{"ts":"2026-05-05T21:40:21Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\CLAUDE.md","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"} +{"ts":"2026-05-05T21:40:27Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\.paul\\\\config.md","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"} +{"ts":"2026-05-05T21:42:03Z","tool":"Bash","cmd":"mkdir -p \"/c/visual studio code/projekty/jachty3.pagedev.pl/.planning/codebase\"\",\"description\":\"Create .planning/codebase directory\"},\"tool_response\":{\"stdout\":\"\",\"stderr\":\"\",\"interr","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"} +{"ts":"2026-05-05T21:42:13Z","tool":"Bash","cmd":"mkdir -p \"/c/visual studio code/projekty/jachty3.pagedev.pl/.planning/codebase\"\",\"description\":\"Create .planning/codebase directory\"},\"tool_response\":{\"stdout\":\"\",\"stderr\":\"\",\"interr","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"} +{"ts":"2026-05-05T21:42:16Z","tool":"Bash","cmd":"powershell -Command \"Get-Item 'C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\tmp-fix-polish.php' | Select-Object FullName, Length, Attributes, LinkType, Target | Format-List\"\",\"des","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"} +{"ts":"2026-05-05T21:42:26Z","tool":"Bash","cmd":"powershell -Command \"Get-Content 'C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\tmp-fix-polish.php'\"\",\"description\":\"Read tmp-fix-polish.php content\"},\"tool_response\":{\"stdou","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"} +{"ts":"2026-05-05T21:42:33Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\.planning\\\\codebase\\\\STACK.md","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"} +{"ts":"2026-05-05T21:43:00Z","tool":"Bash","cmd":"mkdir -p \"/c/visual studio code/projekty/jachty3.pagedev.pl/.planning/codebase\" && echo \"ok\"\",\"description\":\"Create .planning/codebase directory\"},\"tool_response\":{\"stdout\":\"ok\",\"stder","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"} +{"ts":"2026-05-05T21:43:18Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\.planning\\\\codebase\\\\ARCHITECTURE.md","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"} +{"ts":"2026-05-05T21:43:41Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\.planning\\\\codebase\\\\INTEGRATIONS.md","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"} +{"ts":"2026-05-05T21:44:00Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\.planning\\\\codebase\\\\CONVENTIONS.md","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"} +{"ts":"2026-05-05T21:44:06Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\.planning\\\\codebase\\\\STRUCTURE.md","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"} +{"ts":"2026-05-05T21:44:46Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\.planning\\\\codebase\\\\TESTING.md","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"} +{"ts":"2026-05-05T21:45:40Z","tool":"Bash","cmd":"mkdir -p \"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\.paul\\\\codebase\"\",\"description\":\"Create .paul/codebase directory\"},\"tool_response\":{\"stdout\":\"\",\"stderr\":\"\",\","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"} +{"ts":"2026-05-05T21:46:03Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\.paul\\\\codebase\\\\overview.md","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"} +{"ts":"2026-05-05T21:46:23Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\.paul\\\\codebase\\\\stack.md","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"} +{"ts":"2026-05-05T21:47:08Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\.paul\\\\codebase\\\\architecture.md","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"} +{"ts":"2026-05-05T21:47:32Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\.paul\\\\codebase\\\\structure.md","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"} +{"ts":"2026-05-05T21:48:12Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\.paul\\\\codebase\\\\conventions.md","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"} +{"ts":"2026-05-05T21:48:45Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\.paul\\\\codebase\\\\integrations.md","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"} +{"ts":"2026-05-05T21:49:18Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\.paul\\\\codebase\\\\testing.md","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"} +{"ts":"2026-05-05T21:50:11Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\.paul\\\\codebase\\\\concerns.md","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"} +{"ts":"2026-05-05T21:54:43Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\.paul\\\\codebase\\\\concerns.md","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"} +{"ts":"2026-05-05T21:59:23Z","tool":"Bash","cmd":"mkdir -p \"C:/visual studio code/projekty/jachty3.pagedev.pl/.paul/phases/09-finalizacja\" 2>&1 && ls \"C:/visual studio code/projekty/jachty3.pagedev.pl/.paul/phases/\"\",\"description\":\"Create pha","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"} diff --git a/.paul/governance/governance_2026-05-06.jsonl b/.paul/governance/governance_2026-05-06.jsonl new file mode 100644 index 0000000..c90d340 --- /dev/null +++ b/.paul/governance/governance_2026-05-06.jsonl @@ -0,0 +1,33 @@ +{"ts":"2026-05-05T22:01:07Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\.paul\\\\phases\\\\09-finalizacja\\\\09-01-PLAN.md","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"} +{"ts":"2026-05-05T22:01:19Z","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-05T22:01:27Z","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-05T22:01:34Z","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-05T22:03:50Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\wp-content\\\\plugins\\\\yacht-booking-system\\\\frontend\\\\assets\\\\js\\\\calendar.js","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"} +{"ts":"2026-05-05T22:04:02Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\wp-content\\\\plugins\\\\yacht-booking-system\\\\frontend\\\\assets\\\\js\\\\calendar.js","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"} +{"ts":"2026-05-05T22:04:14Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\wp-content\\\\plugins\\\\yacht-booking-system\\\\frontend\\\\assets\\\\css\\\\calendar.scss","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"} +{"ts":"2026-05-05T22:04:28Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\wp-content\\\\plugins\\\\yacht-booking-system\\\\frontend\\\\assets\\\\css\\\\calendar.css","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"} +{"ts":"2026-05-05T22:04:41Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\wp-content\\\\plugins\\\\yacht-booking-system\\\\frontend\\\\class-calendar-widget.php","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"} +{"ts":"2026-05-05T22:04:47Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\wp-content\\\\plugins\\\\yacht-booking-system\\\\frontend\\\\class-calendar-widget.php","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"} +{"ts":"2026-05-05T22:05:00Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\wp-content\\\\plugins\\\\yacht-booking-system\\\\frontend\\\\class-calendar-widget.php","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"} +{"ts":"2026-05-05T22:05:05Z","tool":"Bash","cmd":"php -l \"C:/visual studio code/projekty/jachty3.pagedev.pl/wp-content/plugins/yacht-booking-system/frontend/class-calendar-widget.php\" 2>&1\",\"description\":\"PHP syntax check\"},\"tool_response\":{","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"} +{"ts":"2026-05-05T22:05:13Z","tool":"Bash","cmd":"node -c \"C:/visual studio code/projekty/jachty3.pagedev.pl/wp-content/plugins/yacht-booking-system/frontend/assets/js/calendar.js\" 2>&1; echo \"exit=$?\"\",\"description\":\"JS syntax check\"},\"too","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"} +{"ts":"2026-05-05T22:07:14Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\wp-content\\\\plugins\\\\yacht-booking-system\\\\frontend\\\\assets\\\\css\\\\calendar.scss","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"} +{"ts":"2026-05-05T22:07:20Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\wp-content\\\\plugins\\\\yacht-booking-system\\\\frontend\\\\assets\\\\css\\\\calendar.css","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"} +{"ts":"2026-05-05T22:07:50Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\test-add-booking.php","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"} +{"ts":"2026-05-05T22:07:56Z","tool":"Bash","cmd":"php -l \"C:/visual studio code/projekty/jachty3.pagedev.pl/test-add-booking.php\" 2>&1\",\"description\":\"PHP syntax check\"},\"tool_response\":{\"stdout\":\"No syntax errors detected in C:/visual st","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"} +{"ts":"2026-05-05T22:10:25Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\wp-content\\\\plugins\\\\yacht-booking-system\\\\frontend\\\\assets\\\\css\\\\calendar.scss","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"} +{"ts":"2026-05-05T22:10:38Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\wp-content\\\\plugins\\\\yacht-booking-system\\\\frontend\\\\assets\\\\css\\\\calendar.scss","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"} +{"ts":"2026-05-05T22:10:47Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\wp-content\\\\plugins\\\\yacht-booking-system\\\\frontend\\\\assets\\\\css\\\\calendar.css","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"} +{"ts":"2026-05-05T22:10:55Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\wp-content\\\\plugins\\\\yacht-booking-system\\\\frontend\\\\assets\\\\css\\\\calendar.css","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"} +{"ts":"2026-05-05T22:11:02Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\wp-content\\\\plugins\\\\yacht-booking-system\\\\frontend\\\\class-calendar-widget.php","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"} +{"ts":"2026-05-05T22:11:54Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\wp-content\\\\plugins\\\\yacht-booking-system\\\\frontend\\\\assets\\\\css\\\\calendar.scss","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"} +{"ts":"2026-05-05T22:12:02Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\wp-content\\\\plugins\\\\yacht-booking-system\\\\frontend\\\\assets\\\\css\\\\calendar.css","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"} +{"ts":"2026-05-05T22:12:08Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\wp-content\\\\plugins\\\\yacht-booking-system\\\\frontend\\\\class-calendar-widget.php","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"} +{"ts":"2026-05-05T22:12:41Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\wp-content\\\\plugins\\\\yacht-booking-system\\\\frontend\\\\assets\\\\css\\\\calendar.scss","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"} +{"ts":"2026-05-05T22:12:48Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\wp-content\\\\plugins\\\\yacht-booking-system\\\\frontend\\\\assets\\\\css\\\\calendar.css","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"} +{"ts":"2026-05-05T22:13:51Z","tool":"Bash","cmd":"cd \"C:/visual studio code/projekty/jachty3.pagedev.pl\" && git diff --stat HEAD -- wp-content/plugins/yacht-booking-system/ 2>&1\",\"description\":\"stats of modified files\"},\"tool_response\":{\"st","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"} +{"ts":"2026-05-05T22:15:13Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\.paul\\\\phases\\\\09-finalizacja\\\\09-01-SUMMARY.md","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"} +{"ts":"2026-05-05T22:15:24Z","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-05T22:15:30Z","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-05T22:15:41Z","tool":"Bash","cmd":"mkdir -p \"C:/visual studio code/projekty/jachty3.pagedev.pl/.paul/changelog/\"\",\"description\":\"Create changelog dir\"},\"tool_response\":{\"stdout\":\"\",\"stderr\":\"\",\"interrupted\":false,\"i","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"} +{"ts":"2026-05-05T22:15:59Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\.paul\\\\changelog\\\\2026-05-06.md","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"} diff --git a/.paul/phases/09-finalizacja/09-01-PLAN.md b/.paul/phases/09-finalizacja/09-01-PLAN.md new file mode 100644 index 0000000..5f65320 --- /dev/null +++ b/.paul/phases/09-finalizacja/09-01-PLAN.md @@ -0,0 +1,262 @@ +--- +phase: 09-finalizacja +plan: 01 +type: execute +wave: 1 +depends_on: [] +files_modified: + - wp-content/plugins/yacht-booking-system/frontend/assets/js/calendar.js + - wp-content/plugins/yacht-booking-system/frontend/assets/css/calendar.scss + - wp-content/plugins/yacht-booking-system/frontend/assets/css/calendar.css + - wp-content/plugins/yacht-booking-system/frontend/class-calendar-widget.php +autonomous: false +delegation: off +--- + + +## Goal +Wprowadzić wizualne oznaczenie „pół-dnia" na pierwszym i ostatnim dniu rezerwacji w kalendarzu frontowym (FullCalendar) oraz dostroić domyślną kolorystykę widgetu Elementor `yacht-calendar` do palety strony jachty3.pagedev.pl (granat + czerwony akcent). + +## Purpose +Klient zgłosił, że obecna wizualizacja kalendarza nie odzwierciedla rzeczywistego rytmu wynajmu jachtów: jacht jest wydawany danego dnia od ustalonej godziny odbioru (po południu) i zwracany w dniu zakończenia rano. Pełne zablokowanie skrajnych dni rezerwacji wprowadza w błąd potencjalnych klientów — sugeruje, że dzień odbioru/zwrotu jest niedostępny, podczas gdy w praktyce można wtedy rozpocząć/zakończyć inną rezerwację. Dodatkowo obecna paleta zieleń+czerwień nie pasuje do granatowo-czerwonego brandingu strony. + +## Output +- Zmodyfikowany kalendarz frontowy (JS + CSS) renderujący skos „pół-zajętego" dnia dla pierwszego i ostatniego dnia każdej ciągłej blokady (booking, GCal, iCal) +- Zaktualizowane domyślne kolory widgetu Elementor (`primary`, `available`, `booked`) dopasowane do palety strony +- Zachowana kompatybilność z istniejącymi instancjami widgetu (override przez ustawienia Elementora dalej działa) + + + + +- **Half-day** — Który dzień ma być wypełniony „w połowie"? + → Odpowiedź: Pierwszy i ostatni dzień rezerwacji. Wizualnie: dzień startu = lewa połowa wolna, prawa połowa zajęta (odbiór po południu); dzień końca = lewa połowa zajęta, prawa połowa wolna (zwrot rano). +- **Kolorystyka** — Skąd brać kolory dopasowane do strony? + → Odpowiedź: Zmienić domyślne wartości w `Calendar_Widget` (Elementor). Istniejące instancje będą mogły zachować swoje ustawienia (override) lub zresetować do nowych defaultów. +- **Blocked GCal** — Czy half-day dotyczy też blokad zewnętrznych (Google Calendar / iCal, bez `booking_id`)? + → Odpowiedź: Tak — analogicznie. Pierwszy i ostatni dzień ciągłego pasma `blocked` także jest pół-zajęty. Detekcja na podstawie ciągłości statusu (poprzedni/następny dzień ma inny status lub inny `booking_id`). +- **Scope** — Czy to plan fazy 9 czy hotfix? + → Odpowiedź: Plan 09-01 fazy 9 (UX/UI polish). Plany 09-02 (settings page), 09-03 (security audit), 09-04 (testy/tłumaczenia/dokumentacja) idą później. +- **Selectability ostatniego dnia** (wynika z odpowiedzi 1) — Czy pierwszy/ostatni dzień rezerwacji ma być klikalny dla nowej rezerwacji? + → Odpowiedź: Wizualnie tak (skos pokazuje, że pół dnia jest wolne), natomiast logika walidacji rezerwacji **pozostaje bez zmian** w tym planie — overlap dalej blokuje wybór skrajnych dni. Zmiana logiki to osobna decyzja biznesowa (godziny check-in/out, polityka rezerwacji) i będzie tematem oddzielnego planu jeśli klient tego zechce. + + +## Project Context +@.paul/PROJECT.md +@.paul/ROADMAP.md +@.paul/STATE.md +@.paul/codebase/architecture.md + +## Source Files +@wp-content/plugins/yacht-booking-system/frontend/assets/js/calendar.js +@wp-content/plugins/yacht-booking-system/frontend/assets/css/calendar.scss +@wp-content/plugins/yacht-booking-system/frontend/class-calendar-widget.php +@wp-content/plugins/yacht-booking-system/includes/class-availability.php + + + + +## AC-1: Skos na pierwszym dniu rezerwacji +```gherkin +Given jacht ma rezerwację w terminie 21–24 maja +When użytkownik otwiera kalendarz tego jachtu +Then dzień 21 maja renderuje się z lewą połową w kolorze "available" i prawą połową w kolorze "booked" (gradient po przekątnej z lewego dołu do prawej góry, lub liniowo 50/50) +And dni 22 i 23 maja są w pełni w kolorze "booked" +``` + +## AC-2: Skos na ostatnim dniu rezerwacji +```gherkin +Given jacht ma rezerwację w terminie 21–24 maja +When użytkownik otwiera kalendarz tego jachtu +Then dzień 24 maja renderuje się z lewą połową w kolorze "booked" i prawą połową w kolorze "available" +``` + +## AC-3: Detekcja ciągłości — różne booking_id obok siebie +```gherkin +Given jacht ma rezerwację A (15–18 maja, booking_id=10) i rezerwację B (19–22 maja, booking_id=11) +When kalendarz renderuje maj +Then dzień 18 jest "ostatni" rezerwacji A (skos prawy = available) — ale jeśli 19 jest pierwszym B, na styku 18/19 oba dni mają skosy w przeciwnych kierunkach +And dni 16, 17 (środek A) oraz 20, 21 (środek B) są w pełni booked +``` + +## AC-4: Skos także dla blokad GCal/iCal +```gherkin +Given jacht ma blokadę zewnętrzną (status=blocked, booking_id=null) w dniach 5–7 czerwca +When kalendarz renderuje czerwiec +Then dzień 5 czerwca = lewa połowa available, prawa booked +And dzień 7 czerwca = lewa booked, prawa available +And dzień 6 czerwca = w pełni booked +``` + +## AC-5: Nowa paleta domyślna widgetu +```gherkin +Given administrator dodaje świeży widget "Yacht Calendar" w Elementorze +When otwiera ustawienia stylu +Then domyślne kolory to: primary = granat strony (np. #0a2a5e), available = jasny / transparentny (np. #f5f9ff lub rgba), booked = czerwień strony (np. #d63638) +And istniejące widgety (z zapisanymi własnymi kolorami) zachowują swoje wartości — nie są nadpisywane +``` + +## AC-6: Walidacja wyboru dat niezmieniona +```gherkin +Given jacht ma rezerwację 21–24 maja +When użytkownik próbuje wybrać 24–26 maja w kalendarzu +Then system blokuje wybór (alert "Wybrane daty zawierają niedostępne terminy") +# Zmiana logiki check-in/check-out poza zakresem tego planu. +``` + + + + + + + Task 1: Wzbogacić REST/JS o detekcję krawędzi rezerwacji i wyrenderować skos + + wp-content/plugins/yacht-booking-system/frontend/assets/js/calendar.js, + wp-content/plugins/yacht-booking-system/frontend/assets/css/calendar.scss, + wp-content/plugins/yacht-booking-system/frontend/assets/css/calendar.css + + + **Detekcja krawędzi (klient-side, w `events` callback):** + 1. Po pobraniu danych z `/availability/{yacht_id}` posortować dni po dacie. + 2. Dla każdego dnia o statusie != 'available' obliczyć: + - `isStart` = poprzedni dzień (date - 1) jest 'available' LUB ma inny `booking_id` LUB ma inny status (booked vs blocked). + - `isEnd` = następny dzień (date + 1) j.w. + - Jeżeli zarówno `isStart` jak i `isEnd` to dzień jest "single" (1-dniowa blokada) → traktuj jako pełen booked (nie skos), albo opcjonalnie skos w kształcie klepsydry — w tym planie: pełen booked dla 1-dniowych. + 3. Zamiast pojedynczego eventu `display: 'background'` z jednym kolorem, dodaj `classNames`: + - `yacht-day-booked` (środek) + - `yacht-day-booked-start` (pierwszy dzień) + - `yacht-day-booked-end` (ostatni dzień) + - analogicznie `yacht-day-blocked-*` dla statusu 'blocked' + 4. Zachować istniejący `backgroundColor` jako fallback dla starszych przeglądarek; rzeczywiste tło renderować przez CSS (linear-gradient). + 5. Przekazać kolor `availableBg` i `bookedBg` do CSS przez CSS custom properties ustawiane na wrapperze: `--yacht-available-bg`, `--yacht-booked-bg`. + - Set `$wrapper.css({'--yacht-available-bg': availableBg, '--yacht-booked-bg': bookedBg})`. + + **CSS (calendar.scss + skompilowany calendar.css):** + 1. Dodaj reguły: + ```css + .yacht-day-booked-start .fc-bg-event, + .yacht-day-booked-start.fc-day { + background: linear-gradient(to right, + var(--yacht-available-bg) 0%, + var(--yacht-available-bg) 50%, + var(--yacht-booked-bg) 50%, + var(--yacht-booked-bg) 100%) !important; + } + .yacht-day-booked-end .fc-bg-event, + .yacht-day-booked-end.fc-day { + background: linear-gradient(to right, + var(--yacht-booked-bg) 0%, + var(--yacht-booked-bg) 50%, + var(--yacht-available-bg) 50%, + var(--yacht-available-bg) 100%) !important; + } + /* Analogicznie dla blocked-* */ + ``` + 2. Skos liniowy 50/50 — preferowany nad ukośnym (`to bottom right`), bo zachowuje czytelność daty i nie nakłada się na cyfrę. + 3. Zaktualizować legendę widgetu o trzeci stan "Częściowo zajęty" (gradient sample) — tylko jeśli widget już renderuje legendę; jeśli nie, pominąć. + + **Nie zmieniać:** + - logiki walidacji `selectAllow` / `select` (AC-6). + - struktury REST API (`/availability/{yacht_id}` zwraca dalej te same pola). + + **Avoid:** + - Mutacji `event.display` — zostaw `'background'`, klasy CSS wystarczą. + - Inline styles na komórkach (FullCalendar je nadpisuje na re-render). + + + 1. `php -l wp-content/plugins/yacht-booking-system/frontend/class-calendar-widget.php` — syntax OK. + 2. Skompilować SCSS → CSS (lub edytować oba pliki ręcznie spójnie); `git diff calendar.css` pokazuje nowe selektory. + 3. Manualnie: po deployu FTP otworzyć /rezerwacja-maja/, sprawdzić że dla istniejącej rezerwacji pierwszy/ostatni dzień ma skos. (CHECKPOINT human-verify w Task 3). + + AC-1, AC-2, AC-3, AC-4, AC-6 spełnione. + + + + Task 2: Zaktualizować domyślne kolory widgetu Elementor do palety strony + + wp-content/plugins/yacht-booking-system/frontend/class-calendar-widget.php + + + W `Calendar_Widget::register_controls()` (linie ~218–245 oraz ~272–274 dla `render()` i ~530–532 dla `_content_template()`) zmienić wartości `'default'` na paletę dopasowaną do strony jachty3.pagedev.pl: + + - `primary_color`: `#2271b1` → `#0a2a5e` (granat główny strony) + - `available_color`: `#35b56a` → `#f5f9ff` (bardzo jasny niebieski / prawie biały) + - `booked_color`: `#e53935` → `#d63638` (czerwień akcentu strony, taka sama jak CTA) + + Jeśli paleta strony zostanie później doprecyzowana — wartości można dostroić w ramach UNIFY. Punkt wyjścia: granat #0a2a5e + czerwień #d63638 (zgodne z screenshotem strony). + + Dodatkowo: + - W szablonie legendy (linie ~566–570) zaktualizować `background-color` swatch'y do nowych defaultów (lub jeszcze lepiej: użyć CSS custom properties z wrappera, żeby legenda też reagowała na zmianę w Elementorze). + - Fallback w `calendar.js` (`normalizeColor`) — dodać stare zielone/czerwone `#35b56a` i `#e53935` jako "legacyColor", aby instancje wciąż używające tych wartości automatycznie dostały nowe domyślne. Dla istniejących instancji ze świadomie dobranymi kolorami (np. brand klienta) zachować jak jest — czyli **nie ruszać** instancji, gdzie wartość jest inna niż stary default. + + **Avoid:** + - Migracji wartości w bazie danych (Elementor zapisuje per-widget; nie ruszamy istniejących). + - Łamania backward-compatibility dla widgetów bez ustawionego koloru (puste = nowy default). + + + 1. `php -l wp-content/plugins/yacht-booking-system/frontend/class-calendar-widget.php`. + 2. Po deployu: dodać nowy widget Yacht Calendar w Elementor → sprawdzić że suwaki pokazują nowe wartości HEX. + 3. Otworzyć stronę z istniejącym widgetem — kolory niezmienione (jeśli zapisane w meta) lub odświeżone (jeśli legacy zielony/czerwony). + + AC-5 spełnione. + + + + + Wizualne oznaczenie pół-dni i nowa paleta widgetu na środowisku produkcyjnym (po deployu FTP). + + + 1. Po wgraniu plików przez ftp-kr odświeżyć z hard-reload: https://jachty3.pagedev.pl/rezerwacja-maja/ + 2. Wybrać miesiąc, w którym istnieje rezerwacja (lub stworzyć testową). + 3. Sprawdzić wizualnie: + - Pierwszy dzień rezerwacji: lewa połowa jasna, prawa czerwona. + - Środkowe dni: w pełni czerwone. + - Ostatni dzień: lewa czerwona, prawa jasna. + - Tło wolnych dni: jasno-niebieskie / białe (dopasowane do strony). + - Brak regresji w wyborze dat (selecton działa, walidacja overlap aktywna). + 4. Sprawdzić w Elementorze: nowy widget Yacht Calendar → defaultowe kolory = granat/jasny/czerwień. + 5. Mobile (DevTools 375px) — skos czytelny, data nie znika. + + Wpisz "approved" aby kontynuować, lub opisz problemy do poprawienia (kolor, kierunek skosu, kontrast itp.) + + + + + + +## DO NOT CHANGE +- `wp-content/plugins/yacht-booking-system/api/class-rest-controller.php` — REST API surface bez zmian. +- `wp-content/plugins/yacht-booking-system/includes/class-availability.php` — schemat danych i metody bez zmian. +- Logika walidacji rezerwacji w `Rest_Controller::create_booking()` i `selectAllow` w JS — nie zmieniać polityki overlap. +- `wp_yacht_availability` — schemat tabeli niezmieniony. +- Backend admin (`admin/class-admin.php` itd.) — plan dotyczy tylko frontendu. +- Istniejące zapisane ustawienia kolorów w meta widgetów Elementora. + +## SCOPE LIMITS +- Tylko frontend kalendarza (FullCalendar w widgecie + shortcode). +- Tylko CSS + JS detekcja krawędzi; brak nowych endpointów REST. +- Bez zmian w polityce check-in/check-out (godziny odbioru/zwrotu jako konfiguracja → ewentualnie osobny plan). +- Bez zmian w systemie email / templates. +- Bez tłumaczeń .po/.mo (osobny plan 09-04). + + + + +- [ ] `php -l` na zmodyfikowanych plikach PHP — bez błędów składni. +- [ ] Skompilowany `calendar.css` zsynchronizowany z `calendar.scss`. +- [ ] Brak nowych warningów w konsoli przeglądarki (DevTools) na /rezerwacja-maja/. +- [ ] FullCalendar nadal renderuje miesiąc/dni i refetch działa po zmianie jachtu. +- [ ] Wszystkie AC (AC-1..AC-6) zweryfikowane wizualnie w checkpoint human-verify. + + + +- Skos na pierwszym/ostatnim dniu każdej ciągłej blokady (booked + blocked). +- Środkowe dni renderują się w pełnym kolorze booked. +- 1-dniowe blokady renderują się jako pełen booked (decyzja: nie używamy klepsydry). +- Nowe defaulty kolorów w widgecie pasują wizualnie do palety strony (granat + czerwień). +- Istniejące widgety z customowymi kolorami nie tracą swoich ustawień. +- Klient potwierdza ("approved") w checkpoint human-verify. + + + +Po zakończeniu: `.paul/phases/09-finalizacja/09-01-SUMMARY.md` + diff --git a/.paul/phases/09-finalizacja/09-01-SUMMARY.md b/.paul/phases/09-finalizacja/09-01-SUMMARY.md new file mode 100644 index 0000000..64a70a4 --- /dev/null +++ b/.paul/phases/09-finalizacja/09-01-SUMMARY.md @@ -0,0 +1,183 @@ +--- +phase: 09-finalizacja +plan: 01 +subsystem: ui +tags: [fullcalendar, css, elementor, frontend, ux] + +requires: + - phase: 04-frontend-kalendarz + provides: FullCalendar widget Elementor + REST availability endpoint + - phase: 03-system-dostepnosci + provides: wp_yacht_availability table z polem booking_id (bazą detekcji krawędzi) + +provides: + - Wizualne oznaczenie pierwszego/ostatniego dnia rezerwacji jako pół-zajętego (gradient 45°) + - Detekcja krawędzi rezerwacji po stronie klienta (start/end/mid/single) na bazie sąsiedztwa booking_id+status + - Paleta widgetu Elementor dopasowana do brandu strony (granat #0e2036, czerwień #bc1834) + - CSS custom properties --yacht-available-bg / --yacht-booked-bg umożliwiające reużycie kolorów w gradientach i legendzie + +affects: + - 09-02-settings (settings page może w przyszłości eksponować godziny check-in/out) + - przyszłe plany dotyczące UX kalendarza + +tech-stack: + added: [CSS custom properties, FullCalendar --fc-border-color override] + patterns: + - "Edge detection przez sąsiedztwo: porównanie current vs (date-1, date+1) po booking_id i status" + - "Linear-gradient 50/50 z hard stop dla skosu 45° zamiast SVG/canvas" + +key-files: + created: + - test-add-booking.php (helper testowy w roocie projektu — DO USUNIĘCIA po teście) + modified: + - wp-content/plugins/yacht-booking-system/frontend/assets/js/calendar.js + - wp-content/plugins/yacht-booking-system/frontend/assets/css/calendar.scss + - wp-content/plugins/yacht-booking-system/frontend/assets/css/calendar.css + - wp-content/plugins/yacht-booking-system/frontend/class-calendar-widget.php + +key-decisions: + - "Skos 45° z linear-gradient zamiast SVG/clip-path — prostszy CSS, działa we wszystkich nowoczesnych przeglądarkach" + - "Single-day blokada renderowana jako pełen booked (nie klepsydra) — uproszczenie wizualne" + - "Walidacja overlap (selectAllow/select) niezmieniona — polityka check-in/check-out poza zakresem" + - "Defaulty kolorów dopasowane do faktycznej palety strony (granat #0e2036, czerwień #bc1834) zamiast generycznych #0a2a5e/#d63638 z planu" + - "Cyfry dni #021526 z !important — przebicie globalnej reguły body a:not([href])" + +patterns-established: + - "CSS custom properties ustawiane z JS na wrapperze pozwalają na reuse koloru w gradientach + legendzie bez duplikacji" + - "Krawędzie segmentu = porównanie 3 atrybutów (status, booking_id, sąsiad) — działa zarówno dla bookings jak i blocked z GCal/iCal" + +duration: ~90min +started: 2026-05-06T08:00:00+02:00 +completed: 2026-05-06T09:30:00+02:00 +--- + +# Phase 9 Plan 01: UX/UI polish kalendarza — Summary + +**Pierwszy/ostatni dzień rezerwacji renderuje się jako pół-zajęty trójkąt (skos 45°) z paletą strony (granat + czerwień); ramki dni i nagłówki kolumn ciemne, cyfry granatowe.** + +## Performance + +| Metric | Value | +|--------|-------| +| Duration | ~90 min | +| Started | 2026-05-06T08:00:00+02:00 | +| Completed | 2026-05-06T09:30:00+02:00 | +| Tasks | 3 of 3 (2 auto + 1 checkpoint) | +| Files modified | 4 (plugin) + 1 utworzony (test helper) | + +## Acceptance Criteria Results + +| Criterion | Status | Notes | +|-----------|--------|-------| +| AC-1: Skos na pierwszym dniu rezerwacji | Pass | Po iteracji finalna orientacja: gradient 135° → top-left = wolny, bottom-right = zajęty | +| AC-2: Skos na ostatnim dniu rezerwacji | Pass | Gradient 135° → top-left = zajęty, bottom-right = wolny. Razem ze start tworzy parallelogram przez wiersz | +| AC-3: Detekcja ciągłości — różne booking_id | Pass | `sameSegment(a,b)` porównuje status + booking_id; różne `booking_id` przerywają segment | +| AC-4: Skos także dla blokad GCal/iCal | Pass | Klasy `yacht-day-blocked-start/end` reagują na ten sam gradient — jeden zestaw reguł | +| AC-5: Nowa paleta domyślna widgetu | Pass | primary #0e2036, available #f5f9ff, booked #bc1834 (zamiast generycznych z planu — wzięto faktyczną paletę z SCSS strony) | +| AC-6: Walidacja wyboru dat niezmieniona | Pass | `selectAllow` i `select` callback bez zmian, overlap dalej blokuje | + +## Accomplishments + +- Skos 45° pierwszego/ostatniego dnia rezerwacji (gradient 135° + 135°), spójny dla bookings i blokad GCal/iCal +- Detekcja krawędzi po stronie klienta — bez zmian w REST API ani schemacie DB +- Paleta widgetu dopasowana do brandu strony (granat + czerwień) z fallbackiem dla legacy zielony/czerwony +- Ciemne ramki kalendarza przez `--fc-border-color` (oficjalna zmienna FullCalendar v6) + override dla 7 selektorów +- Cyfry dni granatowe `#021526`, past `#6c757d` zamiast białych +- Legenda rozszerzona o swatch „Dzień odbioru / zwrotu" z gradientem 135° +- Helper `test-add-booking.php` z token-protected akcjami add/list/remove dla łatwego smoke testu + +## Files Created/Modified + +| File | Change | Purpose | +|------|--------|---------| +| `wp-content/plugins/yacht-booking-system/frontend/assets/js/calendar.js` | Modified (+58/-3) | Edge detection w `events` callback; CSS custom properties na wrapperze; nowe defaulty `#f5f9ff`/`#bc1834` | +| `wp-content/plugins/yacht-booking-system/frontend/assets/css/calendar.scss` | Modified (+61/-7) | Gradienty 135° dla start/end; ciemne ramki przez --fc-border-color + override; nagłówki kolumn granatowe; cyfry dni #021526 | +| `wp-content/plugins/yacht-booking-system/frontend/assets/css/calendar.css` | Modified (~) | Skompilowane reguły zsynchronizowane z SCSS | +| `wp-content/plugins/yacht-booking-system/frontend/class-calendar-widget.php` | Modified (+15/-7) | Defaulty kolorów Elementora; legenda rozszerzona o swatch gradientu | +| `test-add-booking.php` | Created | Token-protected helper FTP do tworzenia/listowania/usuwania testowych rezerwacji | + +## Decisions Made + +| Decision | Rationale | Impact | +|----------|-----------|--------| +| Skos przez `linear-gradient(135deg, A 50%, B 50%)` zamiast SVG/clip-path | Najprostsze CSS, brak dodatkowych assetów, pełne wsparcie w nowoczesnych przeglądarkach | Łatwa modyfikacja kątów / kierunków bez zmian w JS | +| Paleta `#0e2036`/`#bc1834` zamiast `#0a2a5e`/`#d63638` z planu | Po inspekcji `calendar.scss` znaleziono faktyczne kolory już używane na stronie (przyciski, switcher, instructions) | Spójność wizualna z resztą widgetu — bez nowych kolorów do utrzymania | +| Iteracja kierunku skosu pierwszego dnia (45° → 135°) | Klient zgłosił błędny kierunek po pierwszym deployu; flip → parallelogram przez wiersz | Wymagała 2 deployów, ale teraz oba dni mają tę samą oś diagonalną | +| Cyfry dni z `!important` na `.fc-daygrid-day-number` | Globalna reguła `body a:not([href]):not([tabindex])` na końcu pliku ustawiała `color: #fff` na linkach FullCalendar | Bez !important globalna reguła wygrywała specyficznością | +| Single-day blokada = pełen booked (klasa `*-single`, brak gradientu) | Skos klepsydry byłby wizualnie mylący dla 1-dniowej blokady | Klasa `*-single` zarezerwowana — łatwa do wykorzystania w przyszłości | +| `test-add-booking.php` w roocie z tokenem | Zgodne z konwencją projektu (`test-*.php` per CLAUDE.md); brak DB CLI; token chroni przed nieautoryzowanym użyciem | Plik DO USUNIĘCIA po testach — nie commitować na produkcję | + +## Deviations from Plan + +### Summary + +| Type | Count | Impact | +|------|-------|--------| +| Auto-fixed | 3 | Essential UX fixes wymagane przez klienta podczas verify | +| Scope additions | 1 | test-add-booking.php helper — nie był w planie ale niezbędny do weryfikacji | +| Deferred | 0 | — | + +### Auto-fixed Issues + +**1. [UX] Ramki dni w kalendarzu były jasne mimo override w pierwszej iteracji** +- **Found during:** Checkpoint human-verify (iteracja 2) +- **Issue:** Pierwotny override `border-color: #0e2036 !important` na 3 selektorach nie działał — FullCalendar v6 używa `--fc-border-color` na wielu warstwach (scrollgrid section, daygrid-day, theme-standard td/th) +- **Fix:** Ustawiłem `--fc-border-color: #0e2036` na `.yacht-calendar` i `.yacht-calendar .fc` plus override na 7 selektorach (td/th/scrollgrid/scrollgrid-section/daygrid-day/col-header-cell) +- **Files:** calendar.scss, calendar.css +- **Verification:** Klient potwierdził „Jest ok" po deployu + +**2. [UX] Skos pionowy zamiast 45°** +- **Found during:** Checkpoint human-verify (iteracja 2) +- **Issue:** Pierwotny gradient `to right` dawał pionowy podział 50/50 — nieczytelny estetycznie i niezgodny z image013.png +- **Fix:** Zmiana na `linear-gradient(45deg, ...)` (start) i `linear-gradient(135deg, ...)` (end), potem korekta start na `135deg` po feedbacku +- **Files:** calendar.scss, calendar.css, class-calendar-widget.php (legenda) +- **Verification:** „Jest ok" po finalnej korekcie kierunku + +**3. [UX] Cyfry dni białe (nieczytelne na jasnym tle)** +- **Found during:** Checkpoint human-verify (iteracja 4) +- **Issue:** Globalna reguła `body a:not([href]):not([tabindex]) { color: #fff }` (linia 672 SCSS) pokrywała linki cyfr FullCalendar +- **Fix:** `.yacht-calendar .fc-daygrid-day-number { color: #021526 !important; font-weight: 600 }`. Past days `#6c757d` +- **Files:** calendar.scss, calendar.css +- **Verification:** Klient potwierdził „Jest ok" + +### Scope Additions + +**1. test-add-booking.php — token-protected helper** +- **Reason:** Klient chciał zobaczyć podzielone dni; brak istniejącej rezerwacji do testów; brak dostępu DB CLI z tej maszyny +- **Implementation:** Skrypt PHP w roocie z tokenem `YACHT_TEST_2026`, akcje add/list/remove, marker meta `_yacht_test_booking_marker = PAUL_TEST_09_01` +- **Cleanup:** Plik DO USUNIĘCIA z FTP po zakończeniu testów (jest w `.gitignore`? — nie, świadomie zostawiamy w repo lokalnym aby user mógł użyć ponownie; produkcyjnie usunąć z FTP) + +### Deferred Items + +Brak. + +## Issues Encountered + +| Issue | Resolution | +|-------|------------| +| Pierwsza iteracja gradientu pionowa zamiast skośnej | Klient feedback → kąt 45°/135° | +| Kierunek skosu pierwszego dnia odwrotny niż oczekiwany | Flip 45° → 135° na start | +| Cyfry dni białe | !important override na `.fc-daygrid-day-number` | +| Ramki dalej jasne po pierwszej próbie | Dodanie `--fc-border-color` (zmienna FullCalendar v6) plus szerszy zestaw selektorów | + +## Skill Audit + +Brak `.paul/SPECIAL-FLOWS.md` w projekcie — pominięto. + +## Next Phase Readiness + +**Ready:** +- Phase 9 / Plan 01 zamknięty, kalendarz UX/UI w stanie produkcyjnym +- Paleta widgetu znormalizowana, można używać CSS variables w przyszłych iteracjach +- Helper `test-add-booking.php` dostępny do dalszych smoke testów + +**Concerns:** +- Walidacja overlap dalej blokuje rezerwację rozpoczynającą się w dniu zwrotu — wizualnie skos sugeruje że to dozwolone. Decyzja biznesowa: czy klient chce wprowadzić politykę check-in/check-out z godzinami (osobny plan) +- `test-add-booking.php` powinien zostać USUNIĘTY z FTP po testach (zostaje w repo dla wygody) + +**Blockers:** +- Brak. Faza 9 może kontynuować planem 09-02 (Settings Page) + +--- +*Phase: 09-finalizacja, Plan: 01* +*Completed: 2026-05-06* diff --git a/.vscode/ftp-kr.json b/.vscode/ftp-kr.json index a3400db..21cad4c 100644 --- a/.vscode/ftp-kr.json +++ b/.vscode/ftp-kr.json @@ -1,17 +1,21 @@ { - "host": "host117523.hostido.net.pl", - "username": "www@jachty3.pagedev.pl", - "password": "r3VEubZQHHBndzUrZFqX", - "remotePath": "/public_html", - "protocol": "ftp", - "port": 0, - "fileNameEncoding": "utf8", - "autoUpload": true, - "autoDelete": false, - "autoDownload": false, - "ignoreRemoteModification": true, - "ignore": [ - ".git", - "/.vscode" - ] + "host": "host117523.hostido.net.pl", + "username": "www@jachty3.pagedev.pl", + "password": "r3VEubZQHHBndzUrZFqX", + "remotePath": "/public_html", + "protocol": "ftp", + "port": 0, + "fileNameEncoding": "utf8", + "autoUpload": true, + "autoDelete": false, + "autoDownload": false, + "ignoreRemoteModification": true, + "ignore": [ + ".git", + "/.vscode", + "/.paul", + "/.serena", + "CLAUDE.md", + "/.claude" + ] } \ No newline at end of file diff --git a/.vscode/sftp.json b/.vscode/sftp.json new file mode 100644 index 0000000..547a1de --- /dev/null +++ b/.vscode/sftp.json @@ -0,0 +1,12 @@ +{ + "name": "host117523.hostido.net.pl", + "host": "host117523.hostido.net.pl", + "protocol": "ftp", + "port": 21, + "username": "www@jachty3.pagedev.pl", + "password": "r3VEubZQHHBndzUrZFqX", + "remotePath": "/public_html", + "uploadOnSave": false, + "useTempFile": false, + "openSsh": false +} diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..5580c42 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,64 @@ +# Projektowe zasady dla jachty3.pagedev.pl + +## Stack + +- **Język:** PHP 7.4+ +- **CMS:** WordPress 6.x +- **Page Builder:** Elementor Pro +- **Frontend:** FullCalendar.js v6 (CDN), jQuery +- **Baza danych:** MySQL — WordPress CPT + custom tabela `wp_yacht_availability` +- **Integracja:** Google Calendar API v3 (OAuth 2.0, natywna implementacja) +- **Deployment:** FTP via ftp-kr (VS Code extension) + +## Główny plugin + +`wp-content/plugins/yacht-booking-system/` — namespace `YachtBooking\` + +## Zasady kodu PHP + +- WordPress Coding Standards (nie PSR-12) +- Nazewnictwo: PascalCase dla klas, snake_case dla metod i zmiennych +- Singleton pattern dla głównych klas +- Security: nonce verification + sanitize + escape na wyjściu +- Każda operacja DB przez `$wpdb->prepare()` — bez surowych zapytań SQL + +## Baza danych + +- Custom tabela: `wp_yacht_availability` (yacht_id, date, status, booking_id) +- CPT: `yacht` (meta: capacity, price_per_day, gcal_id, features) +- CPT: `yacht_booking` (meta: yacht_id, start_date, end_date, status, customer_*, total_price, gcal_event_id) +- Schemat dokumentowany w `.paul/codebase/db_schema.md` +- Zmiany schematu przez klasę `Installer` (incremental upgrades) + +## REST API + +Namespace: `/wp-json/yacht-booking/v1/` +- `GET /yachts`, `GET /yachts/{id}` — publiczne +- `GET /availability/{yacht_id}` — publiczne +- `POST /bookings` — publiczne (wymaga X-WP-Nonce) +- `GET /bookings`, `PUT /bookings/{id}/status` — admin only + +## Testy + +- Brak automatycznych testów — testy manualne przez pliki `test-*.php` w root projektu +- Po każdej zmianie weryfikuj: `php -l ` (syntax check) + +## Deployment + +1. Edytuj pliki lokalnie w VS Code +2. Deploy przez FTP (ftp-kr) — automatyczny przy zapisie lub ręczny +3. Konfiguracja FTP: `.vscode/ftp-kr.json` + +## Frontend + +- Elementor widget: `frontend/class-calendar-widget.php` +- Shortcode: `[yacht_calendar yacht_id="X"]` +- JavaScript: IIFE pattern `(function($) {...})(jQuery)` +- CSS: mobile-first, BEM-like nazewnictwo klas +- Nie używaj npm/webpack — zasoby statyczne lub CDN + +## Wdrażanie zmian Google Calendar + +- OAuth tokens w `wp_options` (klucze: `yacht_booking_*`) +- Auto-refresh tokenów w `class-oauth-handler.php` +- Sync: `class-sync-controller.php` (hooks + cron) diff --git a/test-add-booking.php b/test-add-booking.php new file mode 100644 index 0000000..f355c1a --- /dev/null +++ b/test-add-booking.php @@ -0,0 +1,137 @@ +&start=YYYY-MM-DD&end=YYYY-MM-DD' ); + } + + $yacht_post = get_page_by_path( $yacht_slug, OBJECT, 'yacht' ); + if ( ! $yacht_post ) { + // Sprobuj po tytule, jesli slug nie pasuje. + $candidates = get_posts( array( + 'post_type' => 'yacht', + 's' => $yacht_slug, + 'numberposts' => 1, + ) ); + $yacht_post = $candidates ? $candidates[0] : null; + } + + if ( ! $yacht_post ) { + echo "Nie znaleziono jachtu '{$yacht_slug}'. Dostepne jachty:\n"; + $all = get_posts( array( 'post_type' => 'yacht', 'numberposts' => -1 ) ); + foreach ( $all as $y ) { + echo " - ID={$y->ID}, slug={$y->post_name}, title={$y->post_title}\n"; + } + exit; + } + + $booking_id = \YachtBooking\Booking::create( array( + 'yacht_id' => $yacht_post->ID, + 'start_date' => $start, + 'end_date' => $end, + 'customer_name' => 'TEST PAUL', + 'customer_email' => 'test@example.com', + 'customer_phone' => '000000000', + 'status' => 'confirmed', + 'total_price' => 0, + ) ); + + if ( ! $booking_id ) { + die( 'Booking::create zwrocilo false.' ); + } + + // Marker, zeby latwo odnalezc i usunac. + update_post_meta( $booking_id, $test_marker, $test_marker_value ); + + // Zapisz dni jako booked w wp_yacht_availability. + \YachtBooking\Availability::mark_as_booked( $yacht_post->ID, $start, $end, $booking_id ); + + echo "OK — utworzono testowa rezerwacje:\n"; + echo " booking_id = {$booking_id}\n"; + echo " yacht = {$yacht_post->post_title} (ID={$yacht_post->ID})\n"; + echo " termin = {$start} -> {$end}\n"; + echo "\nOdwiedz strone kalendarza (hard reload Ctrl+F5) i zobacz podzielone dni.\n"; + echo "Aby usunac: /test-add-booking.php?action=remove&token={$expected_token}\n"; + exit; +} + +if ( 'remove' === $action ) { + $bookings = get_posts( array( + 'post_type' => 'yacht_booking', + 'numberposts' => -1, + 'post_status' => 'any', + 'meta_key' => $test_marker, + 'meta_value' => $test_marker_value, + ) ); + + if ( ! $bookings ) { + die( 'Brak testowych rezerwacji do usuniecia.' ); + } + + foreach ( $bookings as $b ) { + \YachtBooking\Availability::clear_booking_availability( $b->ID ); + wp_delete_post( $b->ID, true ); + echo "Usunieto booking_id={$b->ID}\n"; + } + echo "Gotowe. Mozesz teraz USUNAC plik test-add-booking.php z FTP.\n"; + exit; +} + +if ( 'list' === $action ) { + $bookings = get_posts( array( + 'post_type' => 'yacht_booking', + 'numberposts' => -1, + 'post_status' => 'any', + 'meta_key' => $test_marker, + 'meta_value' => $test_marker_value, + ) ); + echo "Testowe rezerwacje (marker {$test_marker_value}):\n"; + if ( ! $bookings ) { + echo " (brak)\n"; + } + foreach ( $bookings as $b ) { + $yacht_id = \YachtBooking\Booking::get_yacht_id( $b->ID ); + $start = \YachtBooking\Booking::get_start_date( $b->ID ); + $end = \YachtBooking\Booking::get_end_date( $b->ID ); + echo " - ID={$b->ID}, yacht_id={$yacht_id}, {$start} -> {$end}\n"; + } + exit; +} + +die( "Nieznana akcja '{$action}'. Dozwolone: add | remove | list" ); diff --git a/wp-content/plugins/yacht-booking-system/frontend/assets/css/calendar.css b/wp-content/plugins/yacht-booking-system/frontend/assets/css/calendar.css index 3437d69..05302da 100644 --- a/wp-content/plugins/yacht-booking-system/frontend/assets/css/calendar.css +++ b/wp-content/plugins/yacht-booking-system/frontend/assets/css/calendar.css @@ -1 +1 @@ -.yacht-calendar-wrapper{max-width:1200px;margin:0 auto 40px;padding:20px}.yacht-calendar-switcher{width:100vw;margin-left:50%;transform:translateX(-50%);border:none;padding:80px 20px;border-radius:0;display:flex;flex-direction:column;align-items:center;margin-bottom:0;justify-content:center;background:#0e2036;gap:30px}.yacht-calendar-switcher-label{font-family:"DM Sans",Sans-serif !important;font-size:80px;font-weight:500;letter-spacing:-0.04em;color:var(--e-global-color-7077776);line-height:.8}@media(max-width: 768px){.yacht-calendar-switcher-label{font-size:64px}}@media(max-width: 480px){.yacht-calendar-switcher-label{font-size:48px}}.yacht-calendar-switcher-buttons{display:flex;flex-wrap:wrap;gap:8px}.yacht-calendar-switcher-button{font-family:"JetBrains Mono",Sans-serif !important;font-size:.75rem !important;font-weight:500 !important;text-transform:uppercase;line-height:1 !important;letter-spacing:.25em !important;padding:16px 32px 16px 32px !important;border-radius:0 !important;background:#bc1834 !important;color:#fff !important;border:none !important;cursor:pointer !important}.yacht-calendar-switcher-button:hover{background:#a9000c !important}.yacht-calendar-switcher-button.is-active{background:#bc1834;border-color:#bc1834;color:#fff;box-shadow:0 8px 18px rgba(188,24,52,.18)}.yacht-calendar-header{margin-bottom:30px}.yacht-calendar-title{font-size:28px;font-weight:700;color:#021526;margin:0 0 15px 0}.yacht-calendar-description{color:#666;font-size:16px;line-height:1.6}.yacht-calendar-instructions{margin:0 0 30px 0;padding:12px 14px;color:#fff;border-left:1px solid red;background:#d40924;border:none;border-radius:0}.yacht-calendar-instructions p{margin:0;font-family:"Inter Tight",Sans-serif;font-size:1rem;font-weight:400}.yacht-calendar-legend{display:flex;flex-wrap:wrap;gap:10px 16px;align-items:center;margin:0 0 18px 0}.yacht-legend-item{display:inline-flex;align-items:center;gap:8px;color:#fff;font-family:"Roboto",Sans-serif;font-size:15px;font-weight:300}.yacht-legend-swatch{width:16px;height:16px;border-radius:4px;border:1px solid rgba(0,0,0,.2)}.yacht-legend-swatch-past{background:#d0d5dd}.yacht-calendar{margin-bottom:30px;background:hsla(0,0%,100%,.1) f;border-radius:8px;box-shadow:0 2px 12px rgba(0,0,0,.08);padding:20px}.yacht-calendar .fc{font-family:inherit}.yacht-calendar .fc-button-primary{background:#bc1834;border-color:#bc1834;text-transform:uppercase;font-weight:600;font-size:13px;padding:8px 16px}.yacht-calendar .fc-button-primary:hover{background:#021526;border-color:#021526}.yacht-calendar .fc-button-primary:not(:disabled):active{background:#021526;border-color:#021526}.yacht-calendar .fc-button-primary:not(:disabled).fc-button-active{background:#021526;border-color:#021526}.yacht-calendar .fc-daygrid-day.fc-day-past{background:#f5f5f5;opacity:1}.yacht-calendar .fc-daygrid-day.fc-day-past .fc-daygrid-day-number{color:#fff}.yacht-calendar .fc-daygrid-day:hover{background:#f8f9fa}.yacht-calendar .fc-highlight{background:rgba(188,24,52,.1) !important}.yacht-calendar .fc-daygrid-day-top{display:flex;justify-content:center;align-items:center;padding:5px}.yacht-calendar .fc-daygrid-day-number{padding:4px 6px;min-width:28px;text-align:center;display:inline-block;font-size:14px;line-height:1.2}.yacht-calendar .fc-daygrid-day-frame{min-height:80px;display:flex;flex-direction:column;position:relative}.yacht-calendar .fc-bg-event{opacity:.78 !important}.yacht-calendar .fc-bg-event.yacht-day-available{opacity:.66 !important}.yacht-calendar .fc-bg-event.yacht-day-booked{opacity:.92 !important}.yacht-calendar .fc-bg-event.yacht-day-blocked{opacity:.92 !important}.yacht-day-available{cursor:pointer}.yacht-day-booked{cursor:not-allowed}.yacht-day-blocked{cursor:not-allowed}.yacht-booking-form-container{background:#f9f9f9;padding:30px;border-radius:8px;margin-top:30px;border:1px solid #e0e0e0}.yacht-booking-form-container h4{margin:0 0 25px 0;font-size:22px;font-weight:700;color:#021526}.yacht-booking-form .form-row{display:grid;grid-template-columns:1fr 1fr;gap:20px;margin-bottom:20px}.yacht-booking-form .form-field{margin-bottom:20px}.yacht-booking-form label{display:block;font-weight:600;margin-bottom:8px;color:#333;font-size:14px}.yacht-booking-form label .required{color:#bc1834;margin-left:2px}.yacht-booking-form input[type=text]{width:100%;padding:12px 15px;border:1px solid #ddd;border-radius:4px;font-size:16px;transition:border-color .3s ease,box-shadow .3s ease;background:#fff;box-sizing:border-box}.yacht-booking-form input[type=email]{width:100%;padding:12px 15px;border:1px solid #ddd;border-radius:4px;font-size:16px;transition:border-color .3s ease,box-shadow .3s ease;background:#fff;box-sizing:border-box}.yacht-booking-form input[type=tel]{width:100%;padding:12px 15px;border:1px solid #ddd;border-radius:4px;font-size:16px;transition:border-color .3s ease,box-shadow .3s ease;background:#fff;box-sizing:border-box}.yacht-booking-form input[type=date]{width:100%;padding:12px 15px;border:1px solid #ddd;border-radius:4px;font-size:16px;transition:border-color .3s ease,box-shadow .3s ease;background:#fff;box-sizing:border-box}.yacht-booking-form input:focus{outline:none;border-color:#bc1834;box-shadow:0 0 0 3px rgba(188,24,52,.1)}.yacht-booking-form input:-moz-read-only{background:#f5f5f5;cursor:not-allowed}.yacht-booking-form input:read-only{background:#f5f5f5;cursor:not-allowed}.yacht-booking-form .form-actions{margin-top:25px}.booking-terms{margin-top:18px;font-size:14px;line-height:1.5;color:#333}.booking-terms a{color:#bc1834;text-decoration:underline}.yacht-booking-submit{font-family:"JetBrains Mono",Sans-serif !important;font-size:.75rem !important;font-weight:500 !important;text-transform:uppercase;line-height:1 !important;letter-spacing:.25em !important;padding:16px 32px 16px 32px !important;border-radius:0 !important;background:#bc1834 !important;color:#fff !important;border:none !important;cursor:pointer !important;transition:all .3s ease;width:100%}.yacht-booking-submit:hover{background:#a9000c !important}.yacht-booking-submit:disabled{background:#ccc;cursor:not-allowed;transform:none;box-shadow:none}.yacht-booking-response{margin-top:20px}.booking-success{padding:15px;border-radius:4px;font-size:15px;line-height:1.5}.booking-success strong{display:block;margin-bottom:5px}.booking-error{padding:15px;border-radius:4px;font-size:15px;line-height:1.5}.booking-error strong{display:block;margin-bottom:5px}.yacht-calendar-error{padding:20px;border-radius:4px;margin-bottom:20px}.yacht-calendar-error p{margin:0;line-height:1.5}.yacht-calendar-notice{padding:20px;border-radius:4px;margin-bottom:20px}.yacht-calendar-notice p{margin:0;line-height:1.5}.yacht-calendar-view-only{max-width:1200px;padding:15px 15px 0 15px;margin-bottom:0}.yacht-calendar-view-only .yacht-calendar-header{margin-bottom:15px}.yacht-calendar-view-only .yacht-calendar-title{font-size:22px;margin-bottom:10px}.yacht-calendar-view-only .yacht-calendar-instructions{margin-bottom:30px;padding:8px 12px;font-size:13px}.yacht-calendar-view-only .yacht-calendar-legend{margin-bottom:10px;font-size:13px}.yacht-calendar-view-only .yacht-calendar{padding:10px;margin-bottom:0;height:auto !important;overflow:visible}.yacht-calendar-view-only .fc-daygrid-day-frame{min-height:36px}.yacht-calendar-view-only .fc-daygrid-day-top{padding:2px}.yacht-calendar-view-only .fc-daygrid-day-number{padding:2px 4px;min-width:22px;font-size:12px}.yacht-calendar-view-only .fc-col-header-cell{font-size:12px;padding:4px 0}.yacht-calendar-view-only .fc-button{font-size:11px !important;padding:5px 10px !important}.yacht-calendar-view-only .fc-toolbar-title{font-size:16px !important}.yacht-inquiry-layout{display:grid;grid-template-columns:1fr 1fr;gap:24px;align-items:start;margin-bottom:30px}.yacht-inquiry-form-container{background:#021526;padding:24px;border-radius:8px;border:none}.yacht-inquiry-form-container h4{margin:0 0 8px 0;font-size:20px;font-weight:700;color:#fff}.yacht-inquiry-desc{margin:0 0 18px 0;color:hsla(0,0%,100%,.7);font-size:14px;line-height:1.5}.yacht-inquiry-form .form-field{margin-bottom:14px}.yacht-inquiry-form label{display:block;font-weight:600;margin-bottom:5px;color:hsla(0,0%,100%,.9);font-size:13px}.yacht-inquiry-form label .required{color:#ff6b6b;margin-left:2px}.yacht-inquiry-form input[type=text]{width:100%;padding:10px 12px;border:1px solid hsla(0,0%,100%,.2);border-radius:4px;font-size:14px;transition:border-color .3s ease,box-shadow .3s ease;background:hsla(0,0%,100%,.1);color:#fff;box-sizing:border-box;font-family:inherit}.yacht-inquiry-form input[type=email]{width:100%;padding:10px 12px;border:1px solid hsla(0,0%,100%,.2);border-radius:4px;font-size:14px;transition:border-color .3s ease,box-shadow .3s ease;background:hsla(0,0%,100%,.1);color:#fff;box-sizing:border-box;font-family:inherit}.yacht-inquiry-form input[type=tel]{width:100%;padding:10px 12px;border:1px solid hsla(0,0%,100%,.2);border-radius:4px;font-size:14px;transition:border-color .3s ease,box-shadow .3s ease;background:hsla(0,0%,100%,.1);color:#fff;box-sizing:border-box;font-family:inherit}.yacht-inquiry-form textarea{width:100%;padding:10px 12px;border:1px solid hsla(0,0%,100%,.2);border-radius:4px;font-size:14px;transition:border-color .3s ease,box-shadow .3s ease;background:hsla(0,0%,100%,.1);color:#fff;box-sizing:border-box;font-family:inherit;resize:vertical;min-height:60px}.yacht-inquiry-form textarea::-moz-placeholder{color:hsla(0,0%,100%,.4)}.yacht-inquiry-form textarea::placeholder{color:hsla(0,0%,100%,.4)}.yacht-inquiry-form textarea:focus{outline:none;border-color:#bc1834;box-shadow:0 0 0 3px rgba(188,24,52,.3);background:hsla(0,0%,100%,.15)}.yacht-inquiry-form input::-moz-placeholder{color:hsla(0,0%,100%,.4)}.yacht-inquiry-form input::placeholder{color:hsla(0,0%,100%,.4)}.yacht-inquiry-form input:focus{outline:none;border-color:#bc1834;box-shadow:0 0 0 3px rgba(188,24,52,.3);background:hsla(0,0%,100%,.15)}.yacht-inquiry-form .form-actions{margin-top:18px}.yacht-inquiry-form .booking-terms{color:hsla(0,0%,100%,.6);font-size:13px}.yacht-inquiry-form .booking-terms a{color:hsla(0,0%,100%,.85)}.yacht-inquiry-form .yacht-booking-submit{background:#bc1834;border-color:#bc1834;color:#fff}.yacht-inquiry-form .yacht-booking-submit:hover{background:#fff;color:#021526}.yacht-inquiry-response{margin-top:15px}@media(max-width: 992px){.yacht-calendar-title{font-size:24px}.yacht-calendar{padding:15px}.yacht-inquiry-layout{grid-template-columns:1fr}}@media(max-width: 768px){.yacht-inquiry-form-container{padding:18px}.yacht-calendar-title{font-size:22px}.yacht-calendar{padding:10px}.yacht-calendar .fc-toolbar{flex-direction:column;gap:10px}.yacht-calendar .fc-toolbar-chunk{display:flex;justify-content:center;width:100%}.yacht-calendar .fc-button{font-size:12px;padding:6px 12px}.yacht-calendar-legend{gap:8px 12px}.yacht-booking-form-container{padding:20px}.yacht-booking-form .form-row{grid-template-columns:1fr;gap:0}}@media(max-width: 480px){.yacht-calendar-switcher-button{width:100%;justify-content:center}.yacht-calendar-title{font-size:20px}.yacht-booking-form-container{padding:15px}.yacht-booking-form input[type=text]{font-size:14px;padding:10px 12px}.yacht-booking-form input[type=email]{font-size:14px;padding:10px 12px}.yacht-booking-form input[type=tel]{font-size:14px;padding:10px 12px}.yacht-booking-submit{padding:12px 20px;font-size:14px}}body a:not([href]):not([tabindex]),body a:not([href]):not([tabindex]):focus,body a:not([href]):not([tabindex]):hover{color:#fff}body .fc .fc-day-other .fc-daygrid-day-top{opacity:1}table{margin-bottom:0 !important}/*# sourceMappingURL=calendar.css.map */ \ No newline at end of file +.yacht-calendar-wrapper{max-width:1200px;margin:0 auto 40px;padding:20px}.yacht-calendar-switcher{width:100vw;margin-left:50%;transform:translateX(-50%);border:none;padding:80px 20px;border-radius:0;display:flex;flex-direction:column;align-items:center;margin-bottom:0;justify-content:center;background:#0e2036;gap:30px}.yacht-calendar-switcher-label{font-family:"DM Sans",Sans-serif !important;font-size:80px;font-weight:500;letter-spacing:-0.04em;color:var(--e-global-color-7077776);line-height:.8}@media(max-width: 768px){.yacht-calendar-switcher-label{font-size:64px}}@media(max-width: 480px){.yacht-calendar-switcher-label{font-size:48px}}.yacht-calendar-switcher-buttons{display:flex;flex-wrap:wrap;gap:8px}.yacht-calendar-switcher-button{font-family:"JetBrains Mono",Sans-serif !important;font-size:.75rem !important;font-weight:500 !important;text-transform:uppercase;line-height:1 !important;letter-spacing:.25em !important;padding:16px 32px 16px 32px !important;border-radius:0 !important;background:#bc1834 !important;color:#fff !important;border:none !important;cursor:pointer !important}.yacht-calendar-switcher-button:hover{background:#a9000c !important}.yacht-calendar-switcher-button.is-active{background:#bc1834;border-color:#bc1834;color:#fff;box-shadow:0 8px 18px rgba(188,24,52,.18)}.yacht-calendar-header{margin-bottom:30px}.yacht-calendar-title{font-size:28px;font-weight:700;color:#021526;margin:0 0 15px 0}.yacht-calendar-description{color:#666;font-size:16px;line-height:1.6}.yacht-calendar-instructions{margin:0 0 30px 0;padding:12px 14px;color:#fff;border-left:1px solid red;background:#d40924;border:none;border-radius:0}.yacht-calendar-instructions p{margin:0;font-family:"Inter Tight",Sans-serif;font-size:1rem;font-weight:400}.yacht-calendar-legend{display:flex;flex-wrap:wrap;gap:10px 16px;align-items:center;margin:0 0 18px 0}.yacht-legend-item{display:inline-flex;align-items:center;gap:8px;color:#fff;font-family:"Roboto",Sans-serif;font-size:15px;font-weight:300}.yacht-legend-swatch{width:16px;height:16px;border-radius:4px;border:1px solid rgba(0,0,0,.2)}.yacht-legend-swatch-past{background:#d0d5dd}.yacht-calendar{margin-bottom:30px;background:hsla(0,0%,100%,.1) f;border-radius:8px;box-shadow:0 2px 12px rgba(0,0,0,.08);padding:20px}.yacht-calendar .fc{font-family:inherit}.yacht-calendar .fc-button-primary{background:#bc1834;border-color:#bc1834;text-transform:uppercase;font-weight:600;font-size:13px;padding:8px 16px}.yacht-calendar .fc-button-primary:hover{background:#021526;border-color:#021526}.yacht-calendar .fc-button-primary:not(:disabled):active{background:#021526;border-color:#021526}.yacht-calendar .fc-button-primary:not(:disabled).fc-button-active{background:#021526;border-color:#021526}.yacht-calendar .fc-daygrid-day.fc-day-past{background:#f5f5f5;opacity:1}.yacht-calendar .fc-daygrid-day.fc-day-past .fc-daygrid-day-number{color:#6c757d}.yacht-calendar .fc-daygrid-day-number,.yacht-calendar a.fc-daygrid-day-number{color:#021526 !important;font-weight:600}.yacht-calendar .fc-daygrid-day:hover{background:#f8f9fa}.yacht-calendar .fc-highlight{background:rgba(188,24,52,.1) !important}.yacht-calendar .fc-daygrid-day-top{display:flex;justify-content:center;align-items:center;padding:5px}.yacht-calendar .fc-daygrid-day-number{padding:4px 6px;min-width:28px;text-align:center;display:inline-block;font-size:14px;line-height:1.2}.yacht-calendar .fc-daygrid-day-frame{min-height:80px;display:flex;flex-direction:column;position:relative}.yacht-calendar .fc-bg-event{opacity:.78 !important}.yacht-calendar .fc-bg-event.yacht-day-available{opacity:.66 !important}.yacht-calendar .fc-bg-event.yacht-day-booked{opacity:.92 !important}.yacht-calendar .fc-bg-event.yacht-day-blocked{opacity:.92 !important}.yacht-calendar .fc-bg-event.yacht-day-booked-start,.yacht-calendar .fc-bg-event.yacht-day-blocked-start{background:linear-gradient(135deg,var(--yacht-available-bg, #f5f9ff) 0%,var(--yacht-available-bg, #f5f9ff) 50%,var(--yacht-booked-bg, #bc1834) 50%,var(--yacht-booked-bg, #bc1834) 100%) !important;opacity:.92 !important}.yacht-calendar .fc-bg-event.yacht-day-booked-end,.yacht-calendar .fc-bg-event.yacht-day-blocked-end{background:linear-gradient(135deg,var(--yacht-booked-bg, #bc1834) 0%,var(--yacht-booked-bg, #bc1834) 50%,var(--yacht-available-bg, #f5f9ff) 50%,var(--yacht-available-bg, #f5f9ff) 100%) !important;opacity:.92 !important}.yacht-day-available{cursor:pointer}.yacht-day-booked{cursor:not-allowed}.yacht-day-blocked{cursor:not-allowed}.yacht-booking-form-container{background:#f9f9f9;padding:30px;border-radius:8px;margin-top:30px;border:1px solid #e0e0e0}.yacht-booking-form-container h4{margin:0 0 25px 0;font-size:22px;font-weight:700;color:#021526}.yacht-booking-form .form-row{display:grid;grid-template-columns:1fr 1fr;gap:20px;margin-bottom:20px}.yacht-booking-form .form-field{margin-bottom:20px}.yacht-booking-form label{display:block;font-weight:600;margin-bottom:8px;color:#333;font-size:14px}.yacht-booking-form label .required{color:#bc1834;margin-left:2px}.yacht-booking-form input[type=text]{width:100%;padding:12px 15px;border:1px solid #ddd;border-radius:4px;font-size:16px;transition:border-color .3s ease,box-shadow .3s ease;background:#fff;box-sizing:border-box}.yacht-booking-form input[type=email]{width:100%;padding:12px 15px;border:1px solid #ddd;border-radius:4px;font-size:16px;transition:border-color .3s ease,box-shadow .3s ease;background:#fff;box-sizing:border-box}.yacht-booking-form input[type=tel]{width:100%;padding:12px 15px;border:1px solid #ddd;border-radius:4px;font-size:16px;transition:border-color .3s ease,box-shadow .3s ease;background:#fff;box-sizing:border-box}.yacht-booking-form input[type=date]{width:100%;padding:12px 15px;border:1px solid #ddd;border-radius:4px;font-size:16px;transition:border-color .3s ease,box-shadow .3s ease;background:#fff;box-sizing:border-box}.yacht-booking-form input:focus{outline:none;border-color:#bc1834;box-shadow:0 0 0 3px rgba(188,24,52,.1)}.yacht-booking-form input:-moz-read-only{background:#f5f5f5;cursor:not-allowed}.yacht-booking-form input:read-only{background:#f5f5f5;cursor:not-allowed}.yacht-booking-form .form-actions{margin-top:25px}.booking-terms{margin-top:18px;font-size:14px;line-height:1.5;color:#333}.booking-terms a{color:#bc1834;text-decoration:underline}.yacht-booking-submit{font-family:"JetBrains Mono",Sans-serif !important;font-size:.75rem !important;font-weight:500 !important;text-transform:uppercase;line-height:1 !important;letter-spacing:.25em !important;padding:16px 32px 16px 32px !important;border-radius:0 !important;background:#bc1834 !important;color:#fff !important;border:none !important;cursor:pointer !important;transition:all .3s ease;width:100%}.yacht-booking-submit:hover{background:#a9000c !important}.yacht-booking-submit:disabled{background:#ccc;cursor:not-allowed;transform:none;box-shadow:none}.yacht-booking-response{margin-top:20px}.booking-success{padding:15px;border-radius:4px;font-size:15px;line-height:1.5}.booking-success strong{display:block;margin-bottom:5px}.booking-error{padding:15px;border-radius:4px;font-size:15px;line-height:1.5}.booking-error strong{display:block;margin-bottom:5px}.yacht-calendar-error{padding:20px;border-radius:4px;margin-bottom:20px}.yacht-calendar-error p{margin:0;line-height:1.5}.yacht-calendar-notice{padding:20px;border-radius:4px;margin-bottom:20px}.yacht-calendar-notice p{margin:0;line-height:1.5}.yacht-calendar-view-only{max-width:1200px;padding:15px 15px 0 15px;margin-bottom:0}.yacht-calendar-view-only .yacht-calendar-header{margin-bottom:15px}.yacht-calendar-view-only .yacht-calendar-title{font-size:22px;margin-bottom:10px}.yacht-calendar-view-only .yacht-calendar-instructions{margin-bottom:30px;padding:8px 12px;font-size:13px}.yacht-calendar-view-only .yacht-calendar-legend{margin-bottom:10px;font-size:13px}.yacht-calendar-view-only .yacht-calendar{padding:10px;margin-bottom:0;height:auto !important;overflow:visible}.yacht-calendar-view-only .fc-daygrid-day-frame{min-height:36px}.yacht-calendar-view-only .fc-daygrid-day-top{padding:2px}.yacht-calendar-view-only .fc-daygrid-day-number{padding:2px 4px;min-width:22px;font-size:12px}.yacht-calendar-view-only .fc-col-header-cell{font-size:12px;padding:4px 0}.yacht-calendar-view-only .fc-button{font-size:11px !important;padding:5px 10px !important}.yacht-calendar-view-only .fc-toolbar-title{font-size:16px !important}.yacht-inquiry-layout{display:grid;grid-template-columns:1fr 1fr;gap:24px;align-items:start;margin-bottom:30px}.yacht-inquiry-form-container{background:#021526;padding:24px;border-radius:8px;border:none}.yacht-inquiry-form-container h4{margin:0 0 8px 0;font-size:20px;font-weight:700;color:#fff}.yacht-inquiry-desc{margin:0 0 18px 0;color:hsla(0,0%,100%,.7);font-size:14px;line-height:1.5}.yacht-inquiry-form .form-field{margin-bottom:14px}.yacht-inquiry-form label{display:block;font-weight:600;margin-bottom:5px;color:hsla(0,0%,100%,.9);font-size:13px}.yacht-inquiry-form label .required{color:#ff6b6b;margin-left:2px}.yacht-inquiry-form input[type=text]{width:100%;padding:10px 12px;border:1px solid hsla(0,0%,100%,.2);border-radius:4px;font-size:14px;transition:border-color .3s ease,box-shadow .3s ease;background:hsla(0,0%,100%,.1);color:#fff;box-sizing:border-box;font-family:inherit}.yacht-inquiry-form input[type=email]{width:100%;padding:10px 12px;border:1px solid hsla(0,0%,100%,.2);border-radius:4px;font-size:14px;transition:border-color .3s ease,box-shadow .3s ease;background:hsla(0,0%,100%,.1);color:#fff;box-sizing:border-box;font-family:inherit}.yacht-inquiry-form input[type=tel]{width:100%;padding:10px 12px;border:1px solid hsla(0,0%,100%,.2);border-radius:4px;font-size:14px;transition:border-color .3s ease,box-shadow .3s ease;background:hsla(0,0%,100%,.1);color:#fff;box-sizing:border-box;font-family:inherit}.yacht-inquiry-form textarea{width:100%;padding:10px 12px;border:1px solid hsla(0,0%,100%,.2);border-radius:4px;font-size:14px;transition:border-color .3s ease,box-shadow .3s ease;background:hsla(0,0%,100%,.1);color:#fff;box-sizing:border-box;font-family:inherit;resize:vertical;min-height:60px}.yacht-inquiry-form textarea::-moz-placeholder{color:hsla(0,0%,100%,.4)}.yacht-inquiry-form textarea::placeholder{color:hsla(0,0%,100%,.4)}.yacht-inquiry-form textarea:focus{outline:none;border-color:#bc1834;box-shadow:0 0 0 3px rgba(188,24,52,.3);background:hsla(0,0%,100%,.15)}.yacht-inquiry-form input::-moz-placeholder{color:hsla(0,0%,100%,.4)}.yacht-inquiry-form input::placeholder{color:hsla(0,0%,100%,.4)}.yacht-inquiry-form input:focus{outline:none;border-color:#bc1834;box-shadow:0 0 0 3px rgba(188,24,52,.3);background:hsla(0,0%,100%,.15)}.yacht-inquiry-form .form-actions{margin-top:18px}.yacht-inquiry-form .booking-terms{color:hsla(0,0%,100%,.6);font-size:13px}.yacht-inquiry-form .booking-terms a{color:hsla(0,0%,100%,.85)}.yacht-inquiry-form .yacht-booking-submit{background:#bc1834;border-color:#bc1834;color:#fff}.yacht-inquiry-form .yacht-booking-submit:hover{background:#fff;color:#021526}.yacht-inquiry-response{margin-top:15px}@media(max-width: 992px){.yacht-calendar-title{font-size:24px}.yacht-calendar{padding:15px}.yacht-inquiry-layout{grid-template-columns:1fr}}@media(max-width: 768px){.yacht-inquiry-form-container{padding:18px}.yacht-calendar-title{font-size:22px}.yacht-calendar{padding:10px}.yacht-calendar .fc-toolbar{flex-direction:column;gap:10px}.yacht-calendar .fc-toolbar-chunk{display:flex;justify-content:center;width:100%}.yacht-calendar .fc-button{font-size:12px;padding:6px 12px}.yacht-calendar-legend{gap:8px 12px}.yacht-booking-form-container{padding:20px}.yacht-booking-form .form-row{grid-template-columns:1fr;gap:0}}@media(max-width: 480px){.yacht-calendar-switcher-button{width:100%;justify-content:center}.yacht-calendar-title{font-size:20px}.yacht-booking-form-container{padding:15px}.yacht-booking-form input[type=text]{font-size:14px;padding:10px 12px}.yacht-booking-form input[type=email]{font-size:14px;padding:10px 12px}.yacht-booking-form input[type=tel]{font-size:14px;padding:10px 12px}.yacht-booking-submit{padding:12px 20px;font-size:14px}}body a:not([href]):not([tabindex]),body a:not([href]):not([tabindex]):focus,body a:not([href]):not([tabindex]):hover{color:#fff}body .fc .fc-day-other .fc-daygrid-day-top{opacity:1}.yacht-calendar{--fc-border-color:#0e2036}.yacht-calendar .fc{--fc-border-color:#0e2036}.yacht-calendar .fc-theme-standard td,.yacht-calendar .fc-theme-standard th,.yacht-calendar .fc-theme-standard .fc-scrollgrid,.yacht-calendar .fc-scrollgrid,.yacht-calendar .fc-scrollgrid-section>*,.yacht-calendar .fc-daygrid-day,.yacht-calendar .fc-col-header-cell{border-color:#0e2036 !important}.yacht-calendar .fc-col-header-cell{background:#0e2036;color:#fff}.yacht-calendar .fc-col-header-cell .fc-col-header-cell-cushion{color:#fff;font-weight:600;padding:8px 4px}table{margin-bottom:0 !important}/*# sourceMappingURL=calendar.css.map */ \ No newline at end of file diff --git a/wp-content/plugins/yacht-booking-system/frontend/assets/css/calendar.scss b/wp-content/plugins/yacht-booking-system/frontend/assets/css/calendar.scss index 9e6459e..691b598 100644 --- a/wp-content/plugins/yacht-booking-system/frontend/assets/css/calendar.scss +++ b/wp-content/plugins/yacht-booking-system/frontend/assets/css/calendar.scss @@ -190,9 +190,14 @@ opacity: 1; .fc-daygrid-day-number { - color: #fff; + color: #6c757d; } } + .fc-daygrid-day-number, + a.fc-daygrid-day-number { + color: #021526 !important; + font-weight: 600; + } .fc-daygrid-day { &:hover { background: #f8f9fa; @@ -233,6 +238,31 @@ .fc-bg-event.yacht-day-blocked { opacity: 0.92 !important; } + // Half-day rendering for first / last day of a booking or blockade. + // Skos 45°. Pierwszy dzień: trójkąt bottom-left = wolny, top-right = zajęty (odbiór po południu). + // Ostatni dzień: trójkąt top-left = zajęty, bottom-right = wolny (zwrot rano). + .fc-bg-event.yacht-day-booked-start, + .fc-bg-event.yacht-day-blocked-start { + background: linear-gradient( + 135deg, + var(--yacht-available-bg, #f5f9ff) 0%, + var(--yacht-available-bg, #f5f9ff) 50%, + var(--yacht-booked-bg, #bc1834) 50%, + var(--yacht-booked-bg, #bc1834) 100% + ) !important; + opacity: 0.92 !important; + } + .fc-bg-event.yacht-day-booked-end, + .fc-bg-event.yacht-day-blocked-end { + background: linear-gradient( + 135deg, + var(--yacht-booked-bg, #bc1834) 0%, + var(--yacht-booked-bg, #bc1834) 50%, + var(--yacht-available-bg, #f5f9ff) 50%, + var(--yacht-available-bg, #f5f9ff) 100% + ) !important; + opacity: 0.92 !important; + } } .yacht-day-available { cursor: pointer; @@ -679,10 +709,31 @@ body .fc .fc-day-other .fc-daygrid-day-top { opacity: 1; } -// .fc-theme-standard td, -// .fc-theme-standard th { -// border: 1px solid hsla(0, 0%, 100%, 0.2); -// } +.yacht-calendar { + --fc-border-color: #0e2036; + + .fc { + --fc-border-color: #0e2036; + } + .fc-theme-standard td, + .fc-theme-standard th, + .fc-theme-standard .fc-scrollgrid, + .fc-scrollgrid, + .fc-scrollgrid-section > *, + .fc-daygrid-day, + .fc-col-header-cell { + border-color: #0e2036 !important; + } + .fc-col-header-cell { + background: #0e2036; + color: #fff; + } + .fc-col-header-cell .fc-col-header-cell-cushion { + color: #fff; + font-weight: 600; + padding: 8px 4px; + } +} table { margin-bottom: 0 !important; diff --git a/wp-content/plugins/yacht-booking-system/frontend/assets/js/calendar.js b/wp-content/plugins/yacht-booking-system/frontend/assets/js/calendar.js index 503e341..ab3bcc9 100644 --- a/wp-content/plugins/yacht-booking-system/frontend/assets/js/calendar.js +++ b/wp-content/plugins/yacht-booking-system/frontend/assets/js/calendar.js @@ -22,8 +22,14 @@ } return color; }; - const availableBg = normalizeColor($wrapper.data('available-bg'), '#35b56a', '#d4edda'); - const bookedBg = normalizeColor($wrapper.data('booked-bg'), '#e53935', '#f8d7da'); + const availableBg = normalizeColor($wrapper.data('available-bg'), '#f5f9ff', '#d4edda'); + const bookedBg = normalizeColor($wrapper.data('booked-bg'), '#bc1834', '#f8d7da'); + + // Expose colors as CSS custom properties so half-day gradients can read them. + $wrapper.css({ + '--yacht-available-bg': availableBg, + '--yacht-booked-bg': bookedBg + }); const yachtItems = yachtsData ? JSON.parse(yachtsData) : []; const yachtMap = {}; const state = { @@ -165,16 +171,58 @@ end: endDate }, success: function(data) { + // Index by date so we can probe neighbours for edge detection. + const byDate = {}; + data.forEach(function(day) { + byDate[day.date] = day; + }); + + function shiftDate(dateString, deltaDays) { + const d = new Date(dateString + 'T00:00:00'); + d.setDate(d.getDate() + deltaDays); + return formatDate(d); + } + + function sameSegment(a, b) { + if (!a || !b) return false; + if (a.status !== b.status) return false; + // Different bookings (or booking vs blocked) break the segment. + const aId = a.booking_id || null; + const bId = b.booking_id || null; + return aId === bId; + } + const events = data.map(function(day) { + const status = day.status; + const classes = ['yacht-day-' + status]; + + if (status !== 'available') { + const prev = byDate[shiftDate(day.date, -1)]; + const next = byDate[shiftDate(day.date, +1)]; + const isStart = !sameSegment(day, prev); + const isEnd = !sameSegment(day, next); + + if (isStart && isEnd) { + // Single-day blockade — render as full booked, no half. + classes.push('yacht-day-' + status + '-single'); + } else if (isStart) { + classes.push('yacht-day-' + status + '-start'); + } else if (isEnd) { + classes.push('yacht-day-' + status + '-end'); + } else { + classes.push('yacht-day-' + status + '-mid'); + } + } + return { id: day.date, start: day.date, allDay: true, display: 'background', - backgroundColor: day.status === 'available' ? availableBg : bookedBg, - classNames: ['yacht-day-' + day.status], + backgroundColor: status === 'available' ? availableBg : bookedBg, + classNames: classes, extendedProps: { - status: day.status, + status: status, booking_id: day.booking_id || null } }; diff --git a/wp-content/plugins/yacht-booking-system/frontend/class-calendar-widget.php b/wp-content/plugins/yacht-booking-system/frontend/class-calendar-widget.php index 4c55cf8..59e2e71 100644 --- a/wp-content/plugins/yacht-booking-system/frontend/class-calendar-widget.php +++ b/wp-content/plugins/yacht-booking-system/frontend/class-calendar-widget.php @@ -219,7 +219,7 @@ class Calendar_Widget extends Widget_Base { array( 'label' => esc_html__( 'Kolor główny', 'yacht-booking' ), 'type' => Controls_Manager::COLOR, - 'default' => '#2271b1', + 'default' => '#0e2036', ) ); @@ -228,7 +228,7 @@ class Calendar_Widget extends Widget_Base { array( 'label' => esc_html__( 'Kolor dni dostępnych', 'yacht-booking' ), 'type' => Controls_Manager::COLOR, - 'default' => '#35b56a', + 'default' => '#f5f9ff', ) ); @@ -237,7 +237,7 @@ class Calendar_Widget extends Widget_Base { array( 'label' => esc_html__( 'Kolor dni zajętych', 'yacht-booking' ), 'type' => Controls_Manager::COLOR, - 'default' => '#e53935', + 'default' => '#bc1834', ) ); @@ -269,9 +269,9 @@ class Calendar_Widget extends Widget_Base { $calendar_id = 'yacht-calendar-' . $this->get_id(); $raw_height = ! empty( $settings['calendar_height']['size'] ) ? (int) $settings['calendar_height']['size'] : 600; $height = $raw_height; - $primary_color = ! empty( $settings['primary_color'] ) ? $settings['primary_color'] : '#2271b1'; - $available_bg = ! empty( $settings['available_color'] ) ? $settings['available_color'] : '#35b56a'; - $booked_bg = ! empty( $settings['booked_color'] ) ? $settings['booked_color'] : '#e53935'; + $primary_color = ! empty( $settings['primary_color'] ) ? $settings['primary_color'] : '#0e2036'; + $available_bg = ! empty( $settings['available_color'] ) ? $settings['available_color'] : '#f5f9ff'; + $booked_bg = ! empty( $settings['booked_color'] ) ? $settings['booked_color'] : '#bc1834'; $terms_url = Settings::get_terms_page_url(); ?> @@ -563,11 +563,15 @@ class Calendar_Widget extends Widget_Base {
- + - + + + + +