update
This commit is contained in:
99
.paul/PROJECT.md
Normal file
99
.paul/PROJECT.md
Normal file
@@ -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*
|
||||||
54
.paul/ROADMAP.md
Normal file
54
.paul/ROADMAP.md
Normal file
@@ -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*
|
||||||
60
.paul/STATE.md
Normal file
60
.paul/STATE.md
Normal file
@@ -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*
|
||||||
24
.paul/changelog/2026-05-06.md
Normal file
24
.paul/changelog/2026-05-06.md
Normal file
@@ -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`
|
||||||
130
.paul/codebase/architecture.md
Normal file
130
.paul/codebase/architecture.md
Normal file
@@ -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`
|
||||||
189
.paul/codebase/concerns.md
Normal file
189
.paul/codebase/concerns.md
Normal file
@@ -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.
|
||||||
110
.paul/codebase/conventions.md
Normal file
110
.paul/codebase/conventions.md
Normal file
@@ -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: `<!-- Nazwa jachtu -->`
|
||||||
|
- JS functions get JSDoc blocks: `/** Format date to YYYY-MM-DD */`
|
||||||
|
- PHPDoc generics notation for complex types: `@return array<int, array<string, mixed>>`
|
||||||
|
|
||||||
|
## 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
|
||||||
120
.paul/codebase/integrations.md
Normal file
120
.paul/codebase/integrations.md
Normal file
@@ -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)
|
||||||
52
.paul/codebase/overview.md
Normal file
52
.paul/codebase/overview.md
Normal file
@@ -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.
|
||||||
65
.paul/codebase/stack.md
Normal file
65
.paul/codebase/stack.md
Normal file
@@ -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 <file>` 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
|
||||||
94
.paul/codebase/structure.md
Normal file
94
.paul/codebase/structure.md
Normal file
@@ -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()`
|
||||||
106
.paul/codebase/testing.md
Normal file
106
.paul/codebase/testing.md
Normal file
@@ -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 '<h2>1. Section Name</h2>';
|
||||||
|
|
||||||
|
// Visual pass/fail — no assertions that throw
|
||||||
|
if ($condition) {
|
||||||
|
echo '✅ Thing works<br>';
|
||||||
|
} else {
|
||||||
|
echo '❌ Thing broken<br>';
|
||||||
|
die(); // hard stop only on fatal blockers
|
||||||
|
}
|
||||||
|
|
||||||
|
// Data output
|
||||||
|
echo '<details><summary>Raw data</summary><pre>';
|
||||||
|
print_r($data);
|
||||||
|
echo '</pre></details>';
|
||||||
|
```
|
||||||
|
|
||||||
|
## 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
|
||||||
33
.paul/config.md
Normal file
33
.paul/config.md
Normal file
@@ -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*
|
||||||
28
.paul/governance/governance_2026-05-05.jsonl
Normal file
28
.paul/governance/governance_2026-05-05.jsonl
Normal file
@@ -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"}
|
||||||
33
.paul/governance/governance_2026-05-06.jsonl
Normal file
33
.paul/governance/governance_2026-05-06.jsonl
Normal file
@@ -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"}
|
||||||
262
.paul/phases/09-finalizacja/09-01-PLAN.md
Normal file
262
.paul/phases/09-finalizacja/09-01-PLAN.md
Normal file
@@ -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
|
||||||
|
---
|
||||||
|
|
||||||
|
<objective>
|
||||||
|
## 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)
|
||||||
|
</objective>
|
||||||
|
|
||||||
|
<context>
|
||||||
|
<clarifications>
|
||||||
|
- **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.
|
||||||
|
</clarifications>
|
||||||
|
|
||||||
|
## 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
|
||||||
|
</context>
|
||||||
|
|
||||||
|
<acceptance_criteria>
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
```
|
||||||
|
|
||||||
|
</acceptance_criteria>
|
||||||
|
|
||||||
|
<tasks>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>Task 1: Wzbogacić REST/JS o detekcję krawędzi rezerwacji i wyrenderować skos</name>
|
||||||
|
<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/assets/css/calendar.css
|
||||||
|
</files>
|
||||||
|
<action>
|
||||||
|
**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).
|
||||||
|
</action>
|
||||||
|
<verify>
|
||||||
|
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).
|
||||||
|
</verify>
|
||||||
|
<done>AC-1, AC-2, AC-3, AC-4, AC-6 spełnione.</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>Task 2: Zaktualizować domyślne kolory widgetu Elementor do palety strony</name>
|
||||||
|
<files>
|
||||||
|
wp-content/plugins/yacht-booking-system/frontend/class-calendar-widget.php
|
||||||
|
</files>
|
||||||
|
<action>
|
||||||
|
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).
|
||||||
|
</action>
|
||||||
|
<verify>
|
||||||
|
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).
|
||||||
|
</verify>
|
||||||
|
<done>AC-5 spełnione.</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
<task type="checkpoint:human-verify" gate="blocking">
|
||||||
|
<what-built>
|
||||||
|
Wizualne oznaczenie pół-dni i nowa paleta widgetu na środowisku produkcyjnym (po deployu FTP).
|
||||||
|
</what-built>
|
||||||
|
<how-to-verify>
|
||||||
|
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.
|
||||||
|
</how-to-verify>
|
||||||
|
<resume-signal>Wpisz "approved" aby kontynuować, lub opisz problemy do poprawienia (kolor, kierunek skosu, kontrast itp.)</resume-signal>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
</tasks>
|
||||||
|
|
||||||
|
<boundaries>
|
||||||
|
|
||||||
|
## 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).
|
||||||
|
|
||||||
|
</boundaries>
|
||||||
|
|
||||||
|
<verification>
|
||||||
|
- [ ] `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.
|
||||||
|
</verification>
|
||||||
|
|
||||||
|
<success_criteria>
|
||||||
|
- 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.
|
||||||
|
</success_criteria>
|
||||||
|
|
||||||
|
<output>
|
||||||
|
Po zakończeniu: `.paul/phases/09-finalizacja/09-01-SUMMARY.md`
|
||||||
|
</output>
|
||||||
183
.paul/phases/09-finalizacja/09-01-SUMMARY.md
Normal file
183
.paul/phases/09-finalizacja/09-01-SUMMARY.md
Normal file
@@ -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*
|
||||||
6
.vscode/ftp-kr.json
vendored
6
.vscode/ftp-kr.json
vendored
@@ -12,6 +12,10 @@
|
|||||||
"ignoreRemoteModification": true,
|
"ignoreRemoteModification": true,
|
||||||
"ignore": [
|
"ignore": [
|
||||||
".git",
|
".git",
|
||||||
"/.vscode"
|
"/.vscode",
|
||||||
|
"/.paul",
|
||||||
|
"/.serena",
|
||||||
|
"CLAUDE.md",
|
||||||
|
"/.claude"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
12
.vscode/sftp.json
vendored
Normal file
12
.vscode/sftp.json
vendored
Normal file
@@ -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
|
||||||
|
}
|
||||||
64
CLAUDE.md
Normal file
64
CLAUDE.md
Normal file
@@ -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 <plik>` (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)
|
||||||
137
test-add-booking.php
Normal file
137
test-add-booking.php
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Test helper — dodaje / usuwa testową rezerwację, żeby zobaczyć podzielone dni.
|
||||||
|
*
|
||||||
|
* Uzycie (po wgraniu na FTP do roota WP):
|
||||||
|
* /test-add-booking.php?action=add&yacht=maja&start=2026-05-21&end=2026-05-24&token=YACHT_TEST_2026
|
||||||
|
* /test-add-booking.php?action=remove&token=YACHT_TEST_2026
|
||||||
|
* /test-add-booking.php?action=list&token=YACHT_TEST_2026
|
||||||
|
*
|
||||||
|
* Po zakonczeniu testow USUN ten plik z FTP.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Bootstrap WordPress.
|
||||||
|
$wp_load = __DIR__ . '/wp-load.php';
|
||||||
|
if ( ! file_exists( $wp_load ) ) {
|
||||||
|
die( 'wp-load.php not found — wrzuc plik do roota WordPressa.' );
|
||||||
|
}
|
||||||
|
require $wp_load;
|
||||||
|
|
||||||
|
// Token zabezpieczajacy — tylko my znamy.
|
||||||
|
$expected_token = 'YACHT_TEST_2026';
|
||||||
|
if ( ! isset( $_GET['token'] ) || $_GET['token'] !== $expected_token ) {
|
||||||
|
status_header( 403 );
|
||||||
|
die( 'Brak / zly token.' );
|
||||||
|
}
|
||||||
|
|
||||||
|
header( 'Content-Type: text/plain; charset=utf-8' );
|
||||||
|
|
||||||
|
$action = isset( $_GET['action'] ) ? sanitize_key( $_GET['action'] ) : 'list';
|
||||||
|
|
||||||
|
// Marker ktory pozwoli odnalezc nasze testowe rezerwacje.
|
||||||
|
$test_marker = '_yacht_test_booking_marker';
|
||||||
|
$test_marker_value = 'PAUL_TEST_09_01';
|
||||||
|
|
||||||
|
if ( 'add' === $action ) {
|
||||||
|
$yacht_slug = isset( $_GET['yacht'] ) ? sanitize_title( $_GET['yacht'] ) : '';
|
||||||
|
$start = isset( $_GET['start'] ) ? sanitize_text_field( $_GET['start'] ) : '';
|
||||||
|
$end = isset( $_GET['end'] ) ? sanitize_text_field( $_GET['end'] ) : '';
|
||||||
|
|
||||||
|
if ( ! $yacht_slug || ! $start || ! $end ) {
|
||||||
|
die( 'Wymagane: ?action=add&yacht=<slug>&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" );
|
||||||
File diff suppressed because one or more lines are too long
@@ -190,9 +190,14 @@
|
|||||||
opacity: 1;
|
opacity: 1;
|
||||||
|
|
||||||
.fc-daygrid-day-number {
|
.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 {
|
.fc-daygrid-day {
|
||||||
&:hover {
|
&:hover {
|
||||||
background: #f8f9fa;
|
background: #f8f9fa;
|
||||||
@@ -233,6 +238,31 @@
|
|||||||
.fc-bg-event.yacht-day-blocked {
|
.fc-bg-event.yacht-day-blocked {
|
||||||
opacity: 0.92 !important;
|
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 {
|
.yacht-day-available {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@@ -679,10 +709,31 @@ body .fc .fc-day-other .fc-daygrid-day-top {
|
|||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// .fc-theme-standard td,
|
.yacht-calendar {
|
||||||
// .fc-theme-standard th {
|
--fc-border-color: #0e2036;
|
||||||
// border: 1px solid hsla(0, 0%, 100%, 0.2);
|
|
||||||
// }
|
.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 {
|
table {
|
||||||
margin-bottom: 0 !important;
|
margin-bottom: 0 !important;
|
||||||
|
|||||||
@@ -22,8 +22,14 @@
|
|||||||
}
|
}
|
||||||
return color;
|
return color;
|
||||||
};
|
};
|
||||||
const availableBg = normalizeColor($wrapper.data('available-bg'), '#35b56a', '#d4edda');
|
const availableBg = normalizeColor($wrapper.data('available-bg'), '#f5f9ff', '#d4edda');
|
||||||
const bookedBg = normalizeColor($wrapper.data('booked-bg'), '#e53935', '#f8d7da');
|
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 yachtItems = yachtsData ? JSON.parse(yachtsData) : [];
|
||||||
const yachtMap = {};
|
const yachtMap = {};
|
||||||
const state = {
|
const state = {
|
||||||
@@ -165,16 +171,58 @@
|
|||||||
end: endDate
|
end: endDate
|
||||||
},
|
},
|
||||||
success: function(data) {
|
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 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 {
|
return {
|
||||||
id: day.date,
|
id: day.date,
|
||||||
start: day.date,
|
start: day.date,
|
||||||
allDay: true,
|
allDay: true,
|
||||||
display: 'background',
|
display: 'background',
|
||||||
backgroundColor: day.status === 'available' ? availableBg : bookedBg,
|
backgroundColor: status === 'available' ? availableBg : bookedBg,
|
||||||
classNames: ['yacht-day-' + day.status],
|
classNames: classes,
|
||||||
extendedProps: {
|
extendedProps: {
|
||||||
status: day.status,
|
status: status,
|
||||||
booking_id: day.booking_id || null
|
booking_id: day.booking_id || null
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -219,7 +219,7 @@ class Calendar_Widget extends Widget_Base {
|
|||||||
array(
|
array(
|
||||||
'label' => esc_html__( 'Kolor główny', 'yacht-booking' ),
|
'label' => esc_html__( 'Kolor główny', 'yacht-booking' ),
|
||||||
'type' => Controls_Manager::COLOR,
|
'type' => Controls_Manager::COLOR,
|
||||||
'default' => '#2271b1',
|
'default' => '#0e2036',
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -228,7 +228,7 @@ class Calendar_Widget extends Widget_Base {
|
|||||||
array(
|
array(
|
||||||
'label' => esc_html__( 'Kolor dni dostępnych', 'yacht-booking' ),
|
'label' => esc_html__( 'Kolor dni dostępnych', 'yacht-booking' ),
|
||||||
'type' => Controls_Manager::COLOR,
|
'type' => Controls_Manager::COLOR,
|
||||||
'default' => '#35b56a',
|
'default' => '#f5f9ff',
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -237,7 +237,7 @@ class Calendar_Widget extends Widget_Base {
|
|||||||
array(
|
array(
|
||||||
'label' => esc_html__( 'Kolor dni zajętych', 'yacht-booking' ),
|
'label' => esc_html__( 'Kolor dni zajętych', 'yacht-booking' ),
|
||||||
'type' => Controls_Manager::COLOR,
|
'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();
|
$calendar_id = 'yacht-calendar-' . $this->get_id();
|
||||||
$raw_height = ! empty( $settings['calendar_height']['size'] ) ? (int) $settings['calendar_height']['size'] : 600;
|
$raw_height = ! empty( $settings['calendar_height']['size'] ) ? (int) $settings['calendar_height']['size'] : 600;
|
||||||
$height = $raw_height;
|
$height = $raw_height;
|
||||||
$primary_color = ! empty( $settings['primary_color'] ) ? $settings['primary_color'] : '#2271b1';
|
$primary_color = ! empty( $settings['primary_color'] ) ? $settings['primary_color'] : '#0e2036';
|
||||||
$available_bg = ! empty( $settings['available_color'] ) ? $settings['available_color'] : '#35b56a';
|
$available_bg = ! empty( $settings['available_color'] ) ? $settings['available_color'] : '#f5f9ff';
|
||||||
$booked_bg = ! empty( $settings['booked_color'] ) ? $settings['booked_color'] : '#e53935';
|
$booked_bg = ! empty( $settings['booked_color'] ) ? $settings['booked_color'] : '#bc1834';
|
||||||
$terms_url = Settings::get_terms_page_url();
|
$terms_url = Settings::get_terms_page_url();
|
||||||
|
|
||||||
?>
|
?>
|
||||||
@@ -563,11 +563,15 @@ class Calendar_Widget extends Widget_Base {
|
|||||||
</div>
|
</div>
|
||||||
<div class="yacht-calendar-legend">
|
<div class="yacht-calendar-legend">
|
||||||
<span class="yacht-legend-item">
|
<span class="yacht-legend-item">
|
||||||
<span class="yacht-legend-swatch" style="background-color: #35b56a;"></span>
|
<span class="yacht-legend-swatch" style="background-color: <?php echo esc_attr( $available_bg ); ?>;"></span>
|
||||||
<?php esc_html_e( 'Dostępny', 'yacht-booking' ); ?>
|
<?php esc_html_e( 'Dostępny', 'yacht-booking' ); ?>
|
||||||
</span>
|
</span>
|
||||||
<span class="yacht-legend-item">
|
<span class="yacht-legend-item">
|
||||||
<span class="yacht-legend-swatch" style="background-color: #e53935;"></span>
|
<span class="yacht-legend-swatch" style="background: linear-gradient(135deg, <?php echo esc_attr( $available_bg ); ?> 0%, <?php echo esc_attr( $available_bg ); ?> 50%, <?php echo esc_attr( $booked_bg ); ?> 50%, <?php echo esc_attr( $booked_bg ); ?> 100%);"></span>
|
||||||
|
<?php esc_html_e( 'Dzień odbioru / zwrotu', 'yacht-booking' ); ?>
|
||||||
|
</span>
|
||||||
|
<span class="yacht-legend-item">
|
||||||
|
<span class="yacht-legend-swatch" style="background-color: <?php echo esc_attr( $booked_bg ); ?>;"></span>
|
||||||
<?php esc_html_e( 'Zajęty / zablokowany', 'yacht-booking' ); ?>
|
<?php esc_html_e( 'Zajęty / zablokowany', 'yacht-booking' ); ?>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user