update
This commit is contained in:
@@ -1,112 +0,0 @@
|
||||
# PAUL Handoff
|
||||
|
||||
**Date:** 2026-03-25
|
||||
**Status:** paused — checkpoint human-verify pending (awaiting deploy + test)
|
||||
|
||||
---
|
||||
|
||||
## READ THIS FIRST
|
||||
|
||||
You have no prior context. This document tells you everything.
|
||||
|
||||
**Project:** Carei — Formularz rezerwacji samochodu jako plugin Elementor, zintegrowany z Softra Rent API
|
||||
**Core value:** Klient klika "Złóż zapytanie o rezerwację" → modal z formularzem → dane z API Softra → rezerwacja
|
||||
|
||||
---
|
||||
|
||||
## Current State
|
||||
|
||||
**Milestone:** v0.1 Formularz Rezerwacji MVP
|
||||
**Phase:** 2 of 4 — Form UI Krok 1 (Formularz)
|
||||
**Plan:** 02-01 — APPLY in progress (Tasks 1+2 done, Task 3 checkpoint pending)
|
||||
|
||||
**Loop Position:**
|
||||
```
|
||||
PLAN ──▶ APPLY ──▶ UNIFY
|
||||
✓ ◐ ○ [Apply in progress — checkpoint:human-verify waiting]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## What Was Done
|
||||
|
||||
**Phase 1 (complete):**
|
||||
- Plugin `carei-reservation` z natywnym cURL proxy do Softra Rent API
|
||||
- 10 WP REST endpoints w namespace `carei/v1`
|
||||
- Widget Elementor z przyciskiem CTA + modal overlay
|
||||
- Fix: widget require_once przeniesiony do hooka elementor/widgets/register
|
||||
- Fix: zamiana wp_remote_post na natywny cURL (matching softra-test.php)
|
||||
|
||||
**Phase 2 (in progress — Tasks 1+2 done):**
|
||||
- HTML formularza z wszystkimi sekcjami z Figmy (widget render)
|
||||
- CSS 541 linii (responsive, custom checkboxy, karty opcji, walidacja)
|
||||
- JS 580+ linii (API integration, walidacja, interakcje)
|
||||
- Nowy endpoint GET /car-classes-all (listAll) — segmenty ładują się od razu bez wymagania oddziału
|
||||
- Opcje dodatkowe ładowane dynamicznie z cennika API
|
||||
|
||||
---
|
||||
|
||||
## What's In Progress
|
||||
|
||||
- **Task 3 checkpoint:human-verify** — użytkownik musi wgrać pliki na serwer i przetestować wizualnie
|
||||
- Pliki do wgrania:
|
||||
- `includes/class-softra-api.php` (natywny cURL + get_all_car_classes)
|
||||
- `includes/class-rest-proxy.php` (nowy endpoint car-classes-all)
|
||||
- `includes/class-elementor-widget.php` (pełny HTML formularza)
|
||||
- `assets/css/carei-reservation.css` (kompletne style)
|
||||
- `assets/js/carei-reservation.js` (logika + API)
|
||||
|
||||
---
|
||||
|
||||
## What's Next
|
||||
|
||||
**Immediate:** Wgrać pliki na serwer → przetestować formularz → "approved" lub opisać problemy
|
||||
|
||||
**After that:**
|
||||
- Jeśli approved → /paul:unify → Phase 3 (Overlay/podsumowanie + submit rezerwacji do API)
|
||||
- Jeśli issues → fix → re-verify
|
||||
|
||||
**Remaining phases:**
|
||||
- Phase 3: Form UI Krok 2 (Overlay z podsumowaniem, tworzenie klienta, booking)
|
||||
- Phase 4: Polish & integration testing
|
||||
|
||||
---
|
||||
|
||||
## Key Decisions Made
|
||||
|
||||
| Decision | Rationale |
|
||||
|----------|-----------|
|
||||
| Natywny cURL zamiast wp_remote_post | softra-test.php działa z cURL, WP HTTP API timeout |
|
||||
| GET /car/class/listAll dla segmentów | Segment jest pierwszym polem w Figmie, nie powinien wymagać oddziału |
|
||||
| Osobny plugin carei-reservation | Czystsza separacja od elementor-addon |
|
||||
| sslverify nie ustawiane (domyślne cURL) | Tak jak w softra-test.php który działa na produkcji |
|
||||
|
||||
---
|
||||
|
||||
## Key Files
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `.paul/STATE.md` | Live project state |
|
||||
| `.paul/ROADMAP.md` | Phase overview (Phase 1 ✅, Phase 2-4 ⬜) |
|
||||
| `.paul/phases/02-form-ui-step1/02-01-PLAN.md` | Current plan |
|
||||
| `.paul/phases/01-reservation-form-plugin/01-01-SUMMARY.md` | Phase 1 summary |
|
||||
| `wp-content/plugins/carei-reservation/` | Plugin directory (all code) |
|
||||
| `docs/figma-formularz/README.md` | Figma design spec |
|
||||
| `docs/figma-formularz/screenshot-desktop.png` | Desktop reference |
|
||||
| `docs/figma-formularz/screenshot-mobile.png` | Mobile reference |
|
||||
| `softra-test.php` | Working Softra API test (reference for cURL config) |
|
||||
| `.env` | API credentials (url, username, password) |
|
||||
|
||||
---
|
||||
|
||||
## Resume Instructions
|
||||
|
||||
1. Read `.paul/STATE.md` for latest position
|
||||
2. User needs to deploy files to server and test
|
||||
3. After test: "approved" → run `/paul:unify` then `/paul:plan` for Phase 3
|
||||
4. Or run `/paul:resume` or `/paul:progress`
|
||||
|
||||
---
|
||||
|
||||
*Handoff created: 2026-03-25*
|
||||
131
.paul/phases/02-form-ui-step1/02-01-SUMMARY.md
Normal file
131
.paul/phases/02-form-ui-step1/02-01-SUMMARY.md
Normal file
@@ -0,0 +1,131 @@
|
||||
---
|
||||
phase: 02-form-ui-step1
|
||||
plan: 01
|
||||
subsystem: ui
|
||||
tags: [elementor, modal, form, softra-api, css, javascript]
|
||||
|
||||
requires:
|
||||
- phase: 01-reservation-form-plugin
|
||||
provides: Plugin skeleton, REST proxy, Elementor widget mount point
|
||||
provides:
|
||||
- Complete reservation form UI (Step 1) in Elementor modal
|
||||
- Dynamic segment/branch loading from Softra API
|
||||
- Segment-to-branch filtering with cached mapping
|
||||
- Form validation and data collection
|
||||
affects: [03-form-ui-step2, 05-admin-panel]
|
||||
|
||||
tech-stack:
|
||||
added: []
|
||||
patterns: [BEM CSS naming, IIFE JS module, cached API mapping via WP transients]
|
||||
|
||||
key-files:
|
||||
created: []
|
||||
modified:
|
||||
- wp-content/plugins/carei-reservation/includes/class-elementor-widget.php
|
||||
- wp-content/plugins/carei-reservation/assets/css/carei-reservation.css
|
||||
- wp-content/plugins/carei-reservation/assets/js/carei-reservation.js
|
||||
- wp-content/plugins/carei-reservation/includes/class-softra-api.php
|
||||
- wp-content/plugins/carei-reservation/includes/class-rest-proxy.php
|
||||
|
||||
key-decisions:
|
||||
- "Segment shows ALL classes from ALL branches (car-classes-all endpoint)"
|
||||
- "Pickup location filters by selected segment via cached backend mapping"
|
||||
- "Extras hidden until segment AND pickup selected"
|
||||
- "Insurance section skipped — API has no dedicated insurance items"
|
||||
- "Foreign travel section skipped — API has no country/travel endpoints"
|
||||
- "Backend segment-branches map cached in WP transient for 6 hours"
|
||||
|
||||
patterns-established:
|
||||
- "Form field visibility driven by selection state, not page load"
|
||||
- "Backend mapping endpoint with transient caching for cross-entity relationships"
|
||||
|
||||
duration: ~3h
|
||||
started: 2025-03-25T08:00:00Z
|
||||
completed: 2025-03-25T11:00:00Z
|
||||
---
|
||||
|
||||
# Phase 2 Plan 01: Form UI — Krok 1 Summary
|
||||
|
||||
**Kompletny formularz rezerwacji w modalu Elementor z dynamicznym ładowaniem segmentów i oddziałów z API Softra, filtrowanie lokalizacji po segmencie, walidacja i zbieranie danych.**
|
||||
|
||||
## Performance
|
||||
|
||||
| Metric | Value |
|
||||
|--------|-------|
|
||||
| Duration | ~3h (across sessions) |
|
||||
| Tasks | 3 completed |
|
||||
| Files modified | 5 |
|
||||
|
||||
## Acceptance Criteria Results
|
||||
|
||||
| Criterion | Status | Notes |
|
||||
|-----------|--------|-------|
|
||||
| AC-1: Formularz renderuje się w modalu zgodnie z Figmą | Pass | Layout, kolory, typografia zgodne. Pominięto sekcje ubezpieczenia i wyjazdu zagranicznego (brak w API) |
|
||||
| AC-2: Dynamiczne dane z API | Pass | Segmenty z car-classes-all, oddziały filtrowane po segmencie, opcje dodatkowe z pricelist. Zmieniono flow vs plan: segment ładuje się pierwszy (ze wszystkich lokalizacji), nie po wyborze oddziału |
|
||||
| AC-3: Interakcje formularza działają poprawnie | Pass | Checkbox zwrot, walidacja, auto-kalkulacja dni, dynamiczne extras — wszystko działa |
|
||||
|
||||
## Accomplishments
|
||||
|
||||
- Formularz rezerwacji renderuje się w modalu z pełnym stylingiem Figma (desktop + mobile)
|
||||
- Dynamiczne ładowanie: segmenty (wszystkie), oddziały (filtrowane po segmencie), opcje dodatkowe (z cennika API)
|
||||
- Nowy endpoint `/segments-branches-map` z cachem 6h — mapuje segmenty na oddziały
|
||||
- Walidacja formularza z komunikatami, zbieranie danych do console.log (gotowe na Phase 3)
|
||||
|
||||
## Files Modified
|
||||
|
||||
| File | Change | Purpose |
|
||||
|------|--------|---------|
|
||||
| `includes/class-elementor-widget.php` | Modified | Pełny HTML formularza: segment+daty w jednej linii, pickup+checkbox w jednej linii, ukrywalny wrapper na extras |
|
||||
| `assets/css/carei-reservation.css` | Modified | 550+ linii — modal, form layout, inputs, checkboxes, cards, responsive, validation, disabled states |
|
||||
| `assets/js/carei-reservation.js` | Modified | 450+ linii — API helpers, segment/branch loading, filtering, extras hide/show, validation, form collection |
|
||||
| `includes/class-softra-api.php` | Modified | Nowa metoda `get_segments_branches_map()` — cached mapping segment→branches |
|
||||
| `includes/class-rest-proxy.php` | Modified | Nowy endpoint `GET /segments-branches-map` |
|
||||
|
||||
## Decisions Made
|
||||
|
||||
| Decision | Rationale | Impact |
|
||||
|----------|-----------|--------|
|
||||
| Segment ładuje WSZYSTKIE klasy, nie filtrowane po oddziale | Wymaganie klienta — użytkownik widzi pełną ofertę | Odwrócony flow vs plan: segment pierwszy, oddział filtrowany |
|
||||
| Oddziały filtrowane po segmencie | Klient najpierw wybiera segment, potem widzi tylko pasujące oddziały | Nowy backend endpoint z cachem |
|
||||
| Pominięto sekcję ubezpieczenia | API nie ma dedykowanych pozycji ubezpieczeniowych (Soft/Premium) | Do backlogu — wrócimy gdy API będzie gotowe |
|
||||
| Pominięto sekcję wyjazdu zagranicznego | API nie ma endpointów country/travel | Do backlogu |
|
||||
| Extras ukryte do wybrania segmentu + oddziału | Wymaganie klienta — mniej informacji na start | Lepszy UX, mniej przeładowania |
|
||||
|
||||
## Deviations from Plan
|
||||
|
||||
### Summary
|
||||
|
||||
| Type | Count | Impact |
|
||||
|------|-------|--------|
|
||||
| Scope changes (user-directed) | 3 | Layout i flow zmienione per feedback klienta |
|
||||
| Deferred features | 2 | Ubezpieczenie + wyjazd zagraniczny — API limitations |
|
||||
|
||||
**Total impact:** Formularz jest prostszy niż w Figmie (brak 2 sekcji), ale flow jest lepszy (segment-first).
|
||||
|
||||
### Scope Changes
|
||||
|
||||
1. **Segment+daty w jednej linii** — klient poprosił o kompaktniejszy layout
|
||||
2. **Pickup+checkbox w jednej linii** — klient poprosił
|
||||
3. **Odwrócony flow** — segment pierwszy (z wszystkich lokalizacji), oddziały filtrowane po segmencie
|
||||
|
||||
### Deferred Items
|
||||
|
||||
- Sekcja ubezpieczenia (Pakiet Soft/Premium) — API nie ma tych pozycji, w backlogu
|
||||
- Sekcja wyjazdu zagranicznego (checkbox + wyszukiwarka krajów) — API nie ma endpointów, w backlogu
|
||||
|
||||
## Next Phase Readiness
|
||||
|
||||
**Ready:**
|
||||
- Formularz zbiera wszystkie dane i loguje do console
|
||||
- API proxy obsługuje customer/add, makebooking, confirm
|
||||
- Cennik (pricelist) ładuje się dynamicznie
|
||||
- Phase 5 dodana do roadmapy: Admin Panel historia formularzy
|
||||
|
||||
**Concerns:**
|
||||
- Opcje dodatkowe z API zawierają pozycje karno-zwrotowe (BRAK, BRUD, KARA) — Phase 3 powinien je filtrować przy wysyłce
|
||||
|
||||
**Blockers:** None
|
||||
|
||||
---
|
||||
*Phase: 02-form-ui-step1, Plan: 01*
|
||||
*Completed: 2026-03-25*
|
||||
395
.paul/phases/03-form-submit-booking/03-01-PLAN.md
Normal file
395
.paul/phases/03-form-submit-booking/03-01-PLAN.md
Normal file
@@ -0,0 +1,395 @@
|
||||
---
|
||||
phase: 03-form-submit-booking
|
||||
plan: 01
|
||||
type: execute
|
||||
wave: 1
|
||||
depends_on: ["02-01"]
|
||||
files_modified:
|
||||
- wp-content/plugins/carei-reservation/includes/class-elementor-widget.php
|
||||
- wp-content/plugins/carei-reservation/assets/css/carei-reservation.css
|
||||
- wp-content/plugins/carei-reservation/assets/js/carei-reservation.js
|
||||
- wp-content/plugins/carei-reservation/includes/class-softra-api.php
|
||||
- wp-content/plugins/carei-reservation/includes/class-rest-proxy.php
|
||||
autonomous: false
|
||||
---
|
||||
|
||||
<objective>
|
||||
## Goal
|
||||
Zbudować kompletny flow wysyłki formularza: tworzenie klienta w API Softra → podsumowanie kosztów w overlay → złożenie rezerwacji → potwierdzenie. Formularz staje się w pełni funkcjonalny.
|
||||
|
||||
## Purpose
|
||||
Bez tego flow formularz tylko zbiera dane do console.log. To jest core value — klient składa rezerwację która trafia do systemu Softra Rent.
|
||||
|
||||
## Output
|
||||
Działający submit: formularz → API customer/add → pricing summary overlay → makebooking → confirm → komunikat sukcesu.
|
||||
</objective>
|
||||
|
||||
<context>
|
||||
## Project Context
|
||||
@.paul/PROJECT.md
|
||||
@.paul/ROADMAP.md
|
||||
|
||||
## Prior Work
|
||||
@.paul/phases/02-form-ui-step1/02-01-SUMMARY.md
|
||||
- Formularz Krok 1 w modalu z dynamicznym ładowaniem segmentów/oddziałów/extras
|
||||
- Walidacja + zbieranie danych (console.log)
|
||||
- Pricelist ID dostępny z odpowiedzi /pricelist endpoint
|
||||
|
||||
## API Documentation
|
||||
@docs/rent-api-02-klienci-i-konta.md — customer/add wymaga: name, address, paymentMethod, isCompany, account|skipAccountCreate, pesel|passportNo|idCard (dla osób fizycznych)
|
||||
@docs/rent-api-03-rezerwacje-i-platnosci.md — makebooking wymaga: dateFrom, dateTo, customerId, pickUpLocation, returnLocation, carParameters, priceListId, validTime
|
||||
|
||||
## Source Files
|
||||
@wp-content/plugins/carei-reservation/includes/class-elementor-widget.php
|
||||
@wp-content/plugins/carei-reservation/assets/js/carei-reservation.js
|
||||
@wp-content/plugins/carei-reservation/assets/css/carei-reservation.css
|
||||
@wp-content/plugins/carei-reservation/includes/class-softra-api.php
|
||||
@wp-content/plugins/carei-reservation/includes/class-rest-proxy.php
|
||||
</context>
|
||||
|
||||
<acceptance_criteria>
|
||||
|
||||
## AC-1: Formularz zbiera dodatkowe dane wymagane przez API
|
||||
```gherkin
|
||||
Given formularz rezerwacji jest otwarty
|
||||
When użytkownik przewija do sekcji "Dane najemcy"
|
||||
Then widoczne są dodatkowe pola: Miejscowość, Kod pocztowy, Ulica, PESEL
|
||||
And pola adresowe i PESEL są wymagane przy walidacji
|
||||
```
|
||||
|
||||
## AC-2: Submit formularza tworzy klienta i pokazuje podsumowanie kosztów
|
||||
```gherkin
|
||||
Given użytkownik wypełnił wszystkie wymagane pola formularza
|
||||
When klika "Wyślij"
|
||||
Then przycisk zmienia się na "Przetwarzanie..." (disabled)
|
||||
And w tle: tworzony jest klient via /customer/add
|
||||
And w tle: pobierane jest podsumowanie kosztów via /rent/princingSummary
|
||||
And wyświetla się overlay z podsumowaniem: lista opłat, wartość netto/brutto
|
||||
And overlay ma przycisk "Potwierdź rezerwację" i "Wróć do formularza"
|
||||
```
|
||||
|
||||
## AC-3: Potwierdzenie rezerwacji kończy flow
|
||||
```gherkin
|
||||
Given overlay z podsumowaniem jest wyświetlony
|
||||
When użytkownik klika "Potwierdź rezerwację"
|
||||
Then w tle: tworzona jest rezerwacja via /rent/makebooking
|
||||
And w tle: rezerwacja jest potwierdzana via /rent/confirm
|
||||
And overlay zamienia się na komunikat sukcesu z numerem rezerwacji
|
||||
And jest przycisk "Zamknij" który zamyka modal
|
||||
|
||||
When rezerwacja się nie powiedzie (API error)
|
||||
Then wyświetla się komunikat błędu z przyczyną (rejectReason)
|
||||
And użytkownik może wrócić do formularza i spróbować ponownie
|
||||
```
|
||||
|
||||
</acceptance_criteria>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 1: Dodatkowe pola formularza + overlay podsumowania (HTML/CSS)</name>
|
||||
<files>wp-content/plugins/carei-reservation/includes/class-elementor-widget.php, wp-content/plugins/carei-reservation/assets/css/carei-reservation.css</files>
|
||||
<action>
|
||||
**class-elementor-widget.php — dodaj do sekcji "Dane najemcy":**
|
||||
|
||||
Po wierszu Imię/Nazwisko, przed wierszem Email/Telefon, dodaj wiersz adresowy:
|
||||
```
|
||||
.carei-form__row (3 kolumny: Miejscowość | Kod pocztowy | Ulica)
|
||||
├─ input#carei-city (Miejscowość, required)
|
||||
├─ input#carei-zipcode (Kod pocztowy, placeholder "00-000", required)
|
||||
└─ input#carei-street (Ulica i nr domu, required)
|
||||
```
|
||||
|
||||
Po wierszu Email/Telefon dodaj wiersz PESEL:
|
||||
```
|
||||
.carei-form__field.carei-form__field--half
|
||||
└─ input#carei-pesel (PESEL, type="text", maxlength="11", required)
|
||||
```
|
||||
|
||||
**Overlay podsumowania — dodaj PO form, ale wewnątrz .carei-modal:**
|
||||
|
||||
```html
|
||||
<div id="carei-summary-overlay" class="carei-summary" style="display:none;">
|
||||
<h3 class="carei-summary__title">Podsumowanie rezerwacji</h3>
|
||||
<div class="carei-summary__details" id="carei-summary-details">
|
||||
<!-- JS wypełni: segment, daty, oddział -->
|
||||
</div>
|
||||
<div class="carei-summary__table" id="carei-summary-table">
|
||||
<!-- JS wypełni: tabela opłat z princingSummary -->
|
||||
</div>
|
||||
<div class="carei-summary__total" id="carei-summary-total">
|
||||
<!-- JS wypełni: netto, VAT, brutto -->
|
||||
</div>
|
||||
<div class="carei-summary__actions">
|
||||
<button type="button" class="carei-summary__btn carei-summary__btn--back" id="carei-summary-back">
|
||||
Wróć do formularza
|
||||
</button>
|
||||
<button type="button" class="carei-summary__btn carei-summary__btn--confirm" id="carei-summary-confirm">
|
||||
Potwierdź rezerwację
|
||||
</button>
|
||||
</div>
|
||||
<div class="carei-summary__error" id="carei-summary-error" style="display:none;"></div>
|
||||
</div>
|
||||
```
|
||||
|
||||
**Widok sukcesu — dodaj PO overlay podsumowania:**
|
||||
|
||||
```html
|
||||
<div id="carei-success-view" class="carei-success" style="display:none;">
|
||||
<div class="carei-success__icon">✓</div>
|
||||
<h3 class="carei-success__title">Rezerwacja złożona!</h3>
|
||||
<p class="carei-success__number" id="carei-success-number"></p>
|
||||
<p class="carei-success__message">Potwierdzenie zostało wysłane na podany adres e-mail.</p>
|
||||
<button type="button" class="carei-success__close" id="carei-success-close">Zamknij</button>
|
||||
</div>
|
||||
```
|
||||
|
||||
**carei-reservation.css — dodaj style:**
|
||||
|
||||
- `.carei-form__row--address`: grid 3 kolumny (1fr auto 1fr), mobile: 1 kolumna
|
||||
- `.carei-form__field--half`: max-width 50%, mobile: 100%
|
||||
- `.carei-summary`: pełna wysokość modala, flex column, gap 24px, padding jak modal
|
||||
- `.carei-summary__title`: jak .carei-modal-title
|
||||
- `.carei-summary__table`: tabela z opłatami — wiersz: nazwa | ilość | cena | wartość
|
||||
- `.carei-summary__table table`: width 100%, border-collapse, alternating rows
|
||||
- `.carei-summary__total`: pogrubiony, wyrównany do prawej, netto/VAT/brutto
|
||||
- `.carei-summary__actions`: flex, gap 16px, justify-content space-between
|
||||
- `.carei-summary__btn--back`: border 1px solid carei-blue, bg transparent, color carei-blue
|
||||
- `.carei-summary__btn--confirm`: bg carei-red, color white (jak submit)
|
||||
- `.carei-summary__error`: bg rgba(255,0,0,0.05), color red, padding 12px, border-radius
|
||||
- `.carei-success`: flex column, align-items center, text-align center, gap 16px
|
||||
- `.carei-success__icon`: 64px circle, bg green, color white, font-size 32px
|
||||
- `.carei-success__close`: jak .carei-form__submit
|
||||
</action>
|
||||
<verify>
|
||||
`php -l wp-content/plugins/carei-reservation/includes/class-elementor-widget.php` — brak syntax errors.
|
||||
CSS plik zawiera style dla `.carei-summary` i `.carei-success`.
|
||||
</verify>
|
||||
<done>AC-1 satisfied: dodatkowe pola adresowe i PESEL widoczne, overlay HTML gotowy</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2: JS — submit flow (customer → pricing → booking → confirm)</name>
|
||||
<files>wp-content/plugins/carei-reservation/assets/js/carei-reservation.js, wp-content/plugins/carei-reservation/includes/class-softra-api.php, wp-content/plugins/carei-reservation/includes/class-rest-proxy.php</files>
|
||||
<action>
|
||||
**JS — przepisz initSubmit() i dodaj nowe funkcje:**
|
||||
|
||||
**Stan globalny — dodaj zmienne:**
|
||||
```
|
||||
var currentCustomerId = null;
|
||||
var currentPriceListId = null; // z odpowiedzi pricelist (już ładowany w loadExtras)
|
||||
var currentPricingSummary = null;
|
||||
var currentReservationId = null;
|
||||
var summaryOverlay, successView, summaryBack, summaryConfirm;
|
||||
```
|
||||
|
||||
**initRefs() — dodaj referencje:**
|
||||
- summaryOverlay = getElementById('carei-summary-overlay')
|
||||
- successView = getElementById('carei-success-view')
|
||||
- summaryBack = getElementById('carei-summary-back')
|
||||
- summaryConfirm = getElementById('carei-summary-confirm')
|
||||
- summaryError = getElementById('carei-summary-error')
|
||||
|
||||
**loadExtras() — zapisz priceListId:**
|
||||
- Przy ładowaniu pricelist, zapisz `currentPriceListId = pricelist.id` z pierwszego elementu odpowiedzi
|
||||
|
||||
**Nowy flow submit:**
|
||||
|
||||
1. `handleSubmit()`:
|
||||
- Walidacja (jak teraz)
|
||||
- Disable submit btn → "Przetwarzanie..."
|
||||
- Wywołaj `createCustomerAndShowSummary()`
|
||||
|
||||
2. `createCustomerAndShowSummary()`:
|
||||
- Zbierz dane formularza
|
||||
- POST /customer z danymi:
|
||||
```json
|
||||
{
|
||||
"firstName": formData.firstName,
|
||||
"lastName": formData.lastName,
|
||||
"name": firstName + " " + lastName,
|
||||
"isCompany": "N",
|
||||
"address": {
|
||||
"city": formData.city,
|
||||
"zipCode": formData.zipCode,
|
||||
"street": formData.street,
|
||||
"homeNo": "-"
|
||||
},
|
||||
"pesel": formData.pesel,
|
||||
"email": formData.email,
|
||||
"phoneMobile": formData.phone,
|
||||
"paymentMethod": "GOTÓWKA",
|
||||
"skipAccountCreate": "T",
|
||||
"emailVerified": true
|
||||
}
|
||||
```
|
||||
- On success: `currentCustomerId = response.customerId`
|
||||
- Wywołaj `loadPricingSummary()`
|
||||
- On error: pokaż błąd, re-enable submit btn
|
||||
|
||||
3. `loadPricingSummary()`:
|
||||
- POST /pricing-summary z danymi:
|
||||
```json
|
||||
{
|
||||
"dateFrom": formData.dateFrom + ":00",
|
||||
"dateTo": formData.dateTo + ":00",
|
||||
"customerId": currentCustomerId,
|
||||
"pickUpLocation": { "branchName": formData.pickupBranch, "outOfBranch": "N" },
|
||||
"returnLocation": { "branchName": returnBranch, "outOfBranch": "N" },
|
||||
"carParameters": { "categoryName": formData.segment },
|
||||
"priceListId": currentPriceListId,
|
||||
"priceItems": selectedExtrasAsBookingPriceItems()
|
||||
}
|
||||
```
|
||||
- On success: `currentPricingSummary = response`
|
||||
- Wywołaj `showSummaryOverlay(response)`
|
||||
- On error: pokaż błąd, re-enable submit btn
|
||||
|
||||
4. `selectedExtrasAsBookingPriceItems()`:
|
||||
- Dla każdego zaznaczonego checkbox extras[]:
|
||||
```json
|
||||
{
|
||||
"id": checkbox.value,
|
||||
"name": checkbox label text,
|
||||
"unit": "szt.",
|
||||
"amount": 1,
|
||||
"priceBeforeDiscount": checkbox.dataset.price,
|
||||
"discount": 0,
|
||||
"priceAfterDiscount": checkbox.dataset.price
|
||||
}
|
||||
```
|
||||
|
||||
5. `showSummaryOverlay(summary)`:
|
||||
- Ukryj form, pokaż summaryOverlay
|
||||
- Wypełnij #carei-summary-details: segment, daty, oddział
|
||||
- Wypełnij #carei-summary-table: tabela z summary.pricelist items
|
||||
- Kolumny: Nazwa | Ilość | Cena netto | Wartość brutto
|
||||
- Oznacz wiersze addedBySystem=true (np. kursywą + "doliczone automatycznie")
|
||||
- Wypełnij #carei-summary-total: totalNetValue, totalVatValue, totalGrossValue
|
||||
|
||||
6. `handleSummaryBack()`:
|
||||
- Ukryj summaryOverlay, pokaż form
|
||||
- Re-enable submit btn
|
||||
|
||||
7. `handleSummaryConfirm()`:
|
||||
- Disable confirm btn → "Rezerwuję..."
|
||||
- POST /booking z pełnymi danymi makebooking:
|
||||
```json
|
||||
{
|
||||
"dateFrom": ..., "dateTo": ...,
|
||||
"customerId": currentCustomerId,
|
||||
"pickUpLocation": { "branchName": ..., "outOfBranch": "N" },
|
||||
"returnLocation": { "branchName": ..., "outOfBranch": "N" },
|
||||
"carParameters": { "categoryName": segment },
|
||||
"priceListId": currentPriceListId,
|
||||
"validTime": 30,
|
||||
"priceItems": selectedExtrasAsBookingPriceItems(),
|
||||
"agreementItems": agreementItemsFromForm(),
|
||||
"comments": formData.message
|
||||
}
|
||||
```
|
||||
- On success (response.success=true):
|
||||
- `currentReservationId = response.reservationId`
|
||||
- POST /booking/confirm z { reservationId }
|
||||
- Pokaż success view z response.reservationNo
|
||||
- On error: pokaż błąd w #carei-summary-error, re-enable btn
|
||||
|
||||
8. `agreementItemsFromForm()`:
|
||||
- Privacy checkbox → return [{ id: agreementId, value: true }]
|
||||
- AgreementId: załaduj raz z GET /agreements na modal open, zapisz w zmiennej
|
||||
|
||||
9. `showSuccessView(reservationNo)`:
|
||||
- Ukryj summaryOverlay, pokaż successView
|
||||
- Wypełnij #carei-success-number: "Nr rezerwacji: " + reservationNo
|
||||
|
||||
10. `handleSuccessClose()`:
|
||||
- Zamknij modal (closeModal())
|
||||
- Reset formularza
|
||||
|
||||
**Walidacja — dodaj nowe wymagane pola do requiredFields:**
|
||||
- { id: 'carei-city', type: 'input', msg: 'Podaj miejscowość' }
|
||||
- { id: 'carei-zipcode', type: 'input', msg: 'Podaj kod pocztowy' }
|
||||
- { id: 'carei-street', type: 'input', msg: 'Podaj ulicę' }
|
||||
- { id: 'carei-pesel', type: 'pesel', msg: 'Podaj poprawny PESEL (11 cyfr)' }
|
||||
- Walidacja PESEL: dokładnie 11 cyfr
|
||||
|
||||
**REST proxy — dodaj brakujący endpoint /cancel:**
|
||||
W class-rest-proxy.php dodaj route POST /booking/cancel.
|
||||
W class-softra-api.php metoda cancel_booking już może istnieć, jeśli nie — dodaj.
|
||||
|
||||
**WAŻNE — obsługa błędów:**
|
||||
- Każdy krok API (customer, pricing, booking, confirm) może failować
|
||||
- Przy każdym błędzie: wyświetl czytelny komunikat, pozwól wrócić do formularza
|
||||
- Timeout: jeśli API nie odpowie w 30s → "Serwer nie odpowiada, spróbuj ponownie"
|
||||
- rejectReason z API: wyświetl użytkownikowi (np. "CAR_NOT_FOUND" → "Brak dostępnego pojazdu w wybranym terminie")
|
||||
</action>
|
||||
<verify>
|
||||
JS plik nie zawiera syntax errors.
|
||||
PHP pliki: `php -l class-softra-api.php`, `php -l class-rest-proxy.php` — brak błędów.
|
||||
Flow: form submit → customer → pricing summary → display overlay.
|
||||
</verify>
|
||||
<done>AC-2 (submit tworzy klienta i pokazuje podsumowanie), AC-3 (potwierdzenie kończy flow)</done>
|
||||
</task>
|
||||
|
||||
<task type="checkpoint:human-verify" gate="blocking">
|
||||
<what-built>Kompletny flow rezerwacji: formularz → tworzenie klienta → podsumowanie kosztów → rezerwacja → potwierdzenie</what-built>
|
||||
<how-to-verify>
|
||||
1. Wgraj pliki na serwer (carei.pagedev.pl)
|
||||
2. Otwórz formularz, wypełnij wszystkie pola (w tym adres, PESEL)
|
||||
3. Kliknij "Wyślij" — sprawdź:
|
||||
- Przycisk zmienia się na "Przetwarzanie..."
|
||||
- Po chwili pojawia się overlay z podsumowaniem kosztów
|
||||
- Tabela pokazuje opłaty (wynajem + ewentualne dodatki)
|
||||
- Widoczne wartości netto/VAT/brutto
|
||||
4. Kliknij "Wróć do formularza" — formularz wraca
|
||||
5. Kliknij "Wyślij" ponownie → overlay → "Potwierdź rezerwację"
|
||||
- Sprawdź czy pojawia się komunikat sukcesu z numerem rezerwacji
|
||||
6. Test błędów:
|
||||
- Wyślij z niepoprawnym PESEL → walidacja łapie
|
||||
- Wyślij z datami w przeszłości → sprawdź reakcję API
|
||||
7. Na mobile: overlay i success view powinny być responsywne
|
||||
</how-to-verify>
|
||||
<resume-signal>Type "approved" to continue, or describe issues to fix</resume-signal>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<boundaries>
|
||||
|
||||
## DO NOT CHANGE
|
||||
- wp-content/plugins/carei-reservation/carei-reservation.php (main plugin file)
|
||||
- wp-content/plugins/elementor-addon/* (istniejący plugin)
|
||||
- wp-content/themes/hello-elementor/* (theme)
|
||||
- .env, docs/*
|
||||
|
||||
## SCOPE LIMITS
|
||||
- Ten plan implementuje flow: customer → pricing summary → booking → confirm
|
||||
- NIE implementuje płatności online (preautoryzacja karty itp.)
|
||||
- NIE implementuje konta klienta (skipAccountCreate='T')
|
||||
- NIE buduje panelu admina (Phase 5)
|
||||
- Wyjazd zagraniczny i ubezpieczenie pominięte (backlog)
|
||||
- Anulowanie rezerwacji: endpoint dodany ale UI nie (future)
|
||||
|
||||
</boundaries>
|
||||
|
||||
<verification>
|
||||
Before declaring plan complete:
|
||||
- [ ] `php -l` na wszystkich zmienionych plikach PHP — brak błędów
|
||||
- [ ] Formularz zawiera pola: adres (city, zip, street), PESEL
|
||||
- [ ] Submit flow: customer/add → princingSummary → overlay z tabelą kosztów
|
||||
- [ ] Confirm flow: makebooking → confirm → success view z numerem rezerwacji
|
||||
- [ ] Error handling: API errors wyświetlane użytkownikowi
|
||||
- [ ] Responsive: overlay i success view działają na mobile
|
||||
- [ ] Human verify: pełny test flow na żywym serwerze
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- Formularz składa rezerwację w systemie Softra Rent
|
||||
- Użytkownik widzi podsumowanie kosztów przed potwierdzeniem
|
||||
- Użytkownik dostaje numer rezerwacji po potwierdzeniu
|
||||
- Błędy API wyświetlane czytelnie
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
After completion, create `.paul/phases/03-form-submit-booking/03-01-SUMMARY.md`
|
||||
</output>
|
||||
130
.paul/phases/03-form-submit-booking/03-01-SUMMARY.md
Normal file
130
.paul/phases/03-form-submit-booking/03-01-SUMMARY.md
Normal file
@@ -0,0 +1,130 @@
|
||||
---
|
||||
phase: 03-form-submit-booking
|
||||
plan: 01
|
||||
subsystem: ui, api
|
||||
tags: [softra-api, booking, customer, pricing-summary, form, modal]
|
||||
|
||||
requires:
|
||||
- phase: 02-form-ui-step1
|
||||
provides: Form UI with segment/branch filtering, validation, data collection
|
||||
provides:
|
||||
- Complete booking flow: customer creation → pricing summary → reservation → confirmation
|
||||
- Address and PESEL fields for API compliance
|
||||
- Pricing summary overlay with cost breakdown
|
||||
- Success view with reservation number
|
||||
affects: [04-polish-testing, 05-admin-panel]
|
||||
|
||||
tech-stack:
|
||||
added: []
|
||||
patterns: [multi-step API flow with error recovery, boolean API params for Java backend]
|
||||
|
||||
key-files:
|
||||
modified:
|
||||
- wp-content/plugins/carei-reservation/includes/class-elementor-widget.php
|
||||
- wp-content/plugins/carei-reservation/assets/css/carei-reservation.css
|
||||
- wp-content/plugins/carei-reservation/assets/js/carei-reservation.js
|
||||
- wp-content/plugins/carei-reservation/includes/class-softra-api.php
|
||||
- wp-content/plugins/carei-reservation/includes/class-rest-proxy.php
|
||||
|
||||
key-decisions:
|
||||
- "Boolean values for Java API (true/false not 'T'/'N') despite documentation saying otherwise"
|
||||
- "Simplified segments-branches-map: 2 API calls instead of ~40 — all branches shown for all segments"
|
||||
- "drivers[] required in makebooking — populated from form data (same person as customer)"
|
||||
- "skipAccountCreate: true — no customer account, MVP approach"
|
||||
- "Penalty items (BRAK, BRUD, KARA) filtered out from extras display"
|
||||
|
||||
patterns-established:
|
||||
- "Softra API uses Java boolean deserialization — always send true/false, never 'T'/'N'"
|
||||
- "Multi-step API flow with error recovery at each step"
|
||||
|
||||
duration: ~2h
|
||||
started: 2026-03-25T11:00:00Z
|
||||
completed: 2026-03-25T13:00:00Z
|
||||
---
|
||||
|
||||
# Phase 3 Plan 01: Submit + Booking Flow Summary
|
||||
|
||||
**Pełny flow rezerwacji: formularz → tworzenie klienta → podsumowanie kosztów w overlay → złożenie rezerwacji → potwierdzenie z numerem.**
|
||||
|
||||
## Performance
|
||||
|
||||
| Metric | Value |
|
||||
|--------|-------|
|
||||
| Duration | ~2h |
|
||||
| Tasks | 3 completed |
|
||||
| Files modified | 5 |
|
||||
|
||||
## Acceptance Criteria Results
|
||||
|
||||
| Criterion | Status | Notes |
|
||||
|-----------|--------|-------|
|
||||
| AC-1: Dodatkowe pola (adres, PESEL) | Pass | Miejscowość, kod pocztowy, ulica, PESEL — wymagane, walidowane |
|
||||
| AC-2: Submit tworzy klienta i pokazuje podsumowanie | Pass | customer/add → princingSummary → overlay z tabelą kosztów i wszystkimi wybranymi opcjami |
|
||||
| AC-3: Potwierdzenie rezerwacji kończy flow | Pass | makebooking → confirm → success view z numerem rezerwacji |
|
||||
|
||||
## Accomplishments
|
||||
|
||||
- Pełny booking flow działa end-to-end na produkcji
|
||||
- Overlay podsumowania pokazuje: dane wynajmu, najemcę, wybrane opcje, tabelę kosztów (netto/VAT/brutto)
|
||||
- Error handling na każdym kroku z czytelnym komunikatem
|
||||
- Endpoint /booking/cancel dodany (UI do wykorzystania w przyszłości)
|
||||
|
||||
## Files Modified
|
||||
|
||||
| File | Change | Purpose |
|
||||
|------|--------|---------|
|
||||
| `includes/class-elementor-widget.php` | Modified | Pola adresowe + PESEL, overlay podsumowania, success view |
|
||||
| `assets/css/carei-reservation.css` | Modified | 800+ linii — style summary, success, address row |
|
||||
| `assets/js/carei-reservation.js` | Modified | 730+ linii — pełny booking flow z error handling |
|
||||
| `includes/class-softra-api.php` | Modified | cancel_booking(), uproszczone segments-branches-map (2 calls) |
|
||||
| `includes/class-rest-proxy.php` | Modified | Endpoint POST /booking/cancel |
|
||||
|
||||
## Decisions Made
|
||||
|
||||
| Decision | Rationale | Impact |
|
||||
|----------|-----------|--------|
|
||||
| Boolean zamiast string w API | Java deserializacja nie akceptuje 'T'/'N', wymaga true/false | Wszystkie pola boolean muszą być native boolean |
|
||||
| Uproszczone mapowanie segment→branch | 39 oddziałów × 30s timeout = niemożliwe w jednym request | Wszystkie oddziały dostępne dla każdego segmentu, API weryfikuje przy rezerwacji |
|
||||
| drivers[] z danymi najemcy | API wymaga (NotNull), dokumentacja nie zaznacza jako required | Kierowca = ten sam co najemca formularza |
|
||||
|
||||
## Deviations from Plan
|
||||
|
||||
### Summary
|
||||
|
||||
| Type | Count | Impact |
|
||||
|------|-------|--------|
|
||||
| Auto-fixed | 3 | API compatibility fixes discovered during testing |
|
||||
|
||||
### Auto-fixed Issues
|
||||
|
||||
**1. Boolean serialization**
|
||||
- Issue: isCompany='N', skipAccountCreate='T' → HTTP 400
|
||||
- Fix: Changed to isCompany: false, skipAccountCreate: true
|
||||
- Verification: customer/add returns 200 with customerId
|
||||
|
||||
**2. Segments-branches-map timeout**
|
||||
- Issue: ~40 sequential API calls → cURL timeout after 30s
|
||||
- Fix: Simplified to 2 API calls (branches + car-classes-all), all branches shown for all segments
|
||||
- Verification: Endpoint returns in <2s
|
||||
|
||||
**3. Missing drivers field**
|
||||
- Issue: makebooking requires drivers[] (NotNull), not documented as required
|
||||
- Fix: Added drivers array with form data (firstName, lastName, address, pesel, phone, email)
|
||||
- Verification: makebooking returns success with reservationId
|
||||
|
||||
## Next Phase Readiness
|
||||
|
||||
**Ready:**
|
||||
- Rezerwacja działa end-to-end
|
||||
- Wszystkie endpointy API przetestowane na produkcji
|
||||
- Error handling na każdym kroku
|
||||
|
||||
**Concerns:**
|
||||
- Penalty items z pricelist filtrowane po kodzie (BRAK/BRUD/KARA) — może nie pokryć wszystkich
|
||||
- Brak email notification po rezerwacji (zależy od konfiguracji Softra)
|
||||
|
||||
**Blockers:** None
|
||||
|
||||
---
|
||||
*Phase: 03-form-submit-booking, Plan: 01*
|
||||
*Completed: 2026-03-25*
|
||||
273
.paul/phases/04-polish-testing/04-01-PLAN.md
Normal file
273
.paul/phases/04-polish-testing/04-01-PLAN.md
Normal file
@@ -0,0 +1,273 @@
|
||||
---
|
||||
phase: 04-polish-testing
|
||||
plan: 01
|
||||
type: execute
|
||||
wave: 1
|
||||
depends_on: []
|
||||
files_modified:
|
||||
- wp-content/plugins/carei-reservation/assets/js/carei-reservation.js
|
||||
- wp-content/plugins/carei-reservation/assets/css/carei-reservation.css
|
||||
- wp-content/plugins/carei-reservation/includes/class-elementor-widget.php
|
||||
autonomous: false
|
||||
---
|
||||
|
||||
<objective>
|
||||
## Goal
|
||||
Polish formularza rezerwacji: obsługa edge cases (wygasły token, brak dostępności, timeouty), animacje przejść form↔summary↔success, poprawki a11y (ARIA, focus management, keyboard nav), naprawa bugów CSS.
|
||||
|
||||
## Purpose
|
||||
Formularz działa end-to-end (Phase 3), ale brak obsługi błędów brzegowych, brak animacji, a11y nie jest wdrożone. Phase 4 doprowadza formularz do jakości produkcyjnej.
|
||||
|
||||
## Output
|
||||
Zmodyfikowane: JS (error handling + animacje + a11y), CSS (transitions + fix bugs), PHP widget (ARIA attrs).
|
||||
</objective>
|
||||
|
||||
<context>
|
||||
## Project Context
|
||||
@.paul/PROJECT.md
|
||||
@.paul/ROADMAP.md
|
||||
@.paul/STATE.md
|
||||
|
||||
## Prior Work
|
||||
@.paul/phases/03-form-submit-booking/03-01-SUMMARY.md
|
||||
- Phase 3: pełny booking flow działa (customer → pricing → makebooking → confirm → success)
|
||||
- API discoveries: boolean true/false, drivers[] required, penalty items filtered
|
||||
|
||||
## Source Files
|
||||
@wp-content/plugins/carei-reservation/assets/js/carei-reservation.js
|
||||
@wp-content/plugins/carei-reservation/assets/css/carei-reservation.css
|
||||
@wp-content/plugins/carei-reservation/includes/class-elementor-widget.php
|
||||
</context>
|
||||
|
||||
<acceptance_criteria>
|
||||
|
||||
## AC-1: Obsługa wygasłego tokenu JWT
|
||||
```gherkin
|
||||
Given formularz jest otwarty i token JWT serwera wygasł (>1h)
|
||||
When użytkownik wysyła formularz lub zmienia segment/extras
|
||||
Then system automatycznie odświeża token i ponawia request (retry 1x)
|
||||
And użytkownik nie widzi błędu tokenu — flow kontynuuje normalnie
|
||||
```
|
||||
|
||||
## AC-2: Obsługa braku dostępności pojazdu
|
||||
```gherkin
|
||||
Given użytkownik wypełnił formularz i jest na ekranie podsumowania
|
||||
When API makebooking zwraca rejectReason CAR_NOT_FOUND
|
||||
Then wyświetla się czytelny komunikat "Brak dostępnego pojazdu w wybranym terminie. Zmień daty lub segment."
|
||||
And przycisk "Wróć do formularza" jest aktywny
|
||||
And użytkownik może wrócić i zmienić dane
|
||||
```
|
||||
|
||||
## AC-3: Obsługa timeoutów i błędów sieciowych
|
||||
```gherkin
|
||||
Given dowolny request API trwa >15s lub sieć jest niedostępna
|
||||
When fetch rzuca timeout lub network error
|
||||
Then wyświetla się komunikat "Wystąpił problem z połączeniem. Spróbuj ponownie."
|
||||
And przycisk submit/confirm wraca do stanu aktywnego
|
||||
And formularz nie jest zablokowany
|
||||
```
|
||||
|
||||
## AC-4: Animacje przejść między krokami
|
||||
```gherkin
|
||||
Given użytkownik jest na formularzu
|
||||
When przechodzi do podsumowania (submit) lub do success (confirm)
|
||||
Then przejście jest animowane (fade out → fade in, ~300ms)
|
||||
And przy powrocie z podsumowania do formularza również jest animacja
|
||||
```
|
||||
|
||||
## AC-5: Accessibility — ARIA i focus management
|
||||
```gherkin
|
||||
Given użytkownik otwiera modal
|
||||
When modal się otwiera
|
||||
Then focus przenosi się na pierwszy interaktywny element modalu
|
||||
And modal ma role="dialog" i aria-modal="true"
|
||||
And focus jest trapowany w modalu (Tab nie wychodzi poza modal)
|
||||
And po zamknięciu modalu focus wraca na przycisk trigger
|
||||
And przejście form→summary→success przenosi focus na odpowiedni heading
|
||||
```
|
||||
|
||||
## AC-6: Fix CSS — orphaned styles w media query
|
||||
```gherkin
|
||||
Given plik CSS ma orphaned styles poza media query (linie ~560-570)
|
||||
When CSS jest naprawiony
|
||||
Then style .carei-form__row--address i .carei-summary__actions są wewnątrz @media (max-width: 768px)
|
||||
And nie ma podwójnego zamknięcia } na końcu media query
|
||||
```
|
||||
|
||||
</acceptance_criteria>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 1: Edge cases — token retry, timeout, lepsze error messages</name>
|
||||
<files>wp-content/plugins/carei-reservation/assets/js/carei-reservation.js</files>
|
||||
<action>
|
||||
1. **Token retry:** Wrap apiGet/apiPost — jeśli response HTTP 401 lub 403, wywołaj retry 1x (token odświeża się automatycznie po stronie PHP proxy). Dodaj flagę `isRetry` żeby nie zapętlić.
|
||||
|
||||
2. **Timeout:** Dodaj AbortController z timeout 15s do fetch w apiGet/apiPost. Na timeout rzuć Error z komunikatem "Przekroczono czas oczekiwania. Spróbuj ponownie."
|
||||
|
||||
3. **Network error:** W handleResponse/catch, rozróżnij TypeError (network) od Error (API). Na TypeError pokaż: "Brak połączenia z serwerem. Sprawdź internet i spróbuj ponownie."
|
||||
|
||||
4. **Rozszerz translateRejectReason:** Dodaj więcej kodów z API:
|
||||
- CUSTOMER_ALREADY_EXISTS → "Klient o tych danych już istnieje w systemie"
|
||||
- INVALID_PESEL → "Nieprawidłowy numer PESEL"
|
||||
- PRICE_LIST_EXPIRED → "Cennik wygasł. Odśwież formularz."
|
||||
|
||||
5. **Submit button recovery:** Upewnij się, że KAŻDY catch w createCustomerAndShowSummary i handleSummaryConfirm przywraca przyciski do stanu aktywnego.
|
||||
|
||||
Avoid: Nie zmieniaj endpointów API ani logiki proxy PHP. Retry tylko na 401/403, nie na inne kody.
|
||||
</action>
|
||||
<verify>
|
||||
- Symuluj offline: w DevTools Network → Offline → submit → czytelny komunikat, przycisk aktywny
|
||||
- Symuluj slow: Network → Slow 3G → submit → po 15s timeout message
|
||||
- Sprawdź że po błędzie można ponownie wysłać formularz
|
||||
</verify>
|
||||
<done>AC-1, AC-2, AC-3 satisfied: token retry, timeout handling, network errors, rozszerzone komunikaty</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2: Animacje przejść + fix CSS bug</name>
|
||||
<files>wp-content/plugins/carei-reservation/assets/css/carei-reservation.css, wp-content/plugins/carei-reservation/assets/js/carei-reservation.js</files>
|
||||
<action>
|
||||
1. **CSS transitions:** Dodaj klasy animacji:
|
||||
```css
|
||||
.carei-step-enter { opacity: 0; transform: translateY(10px); }
|
||||
.carei-step-active { opacity: 1; transform: translateY(0); transition: opacity 0.3s ease, transform 0.3s ease; }
|
||||
.carei-step-exit { opacity: 0; transform: translateY(-10px); transition: opacity 0.2s ease, transform 0.2s ease; }
|
||||
```
|
||||
|
||||
2. **JS transitions:** W showSummaryOverlay(), handleSummaryBack(), showSuccessView():
|
||||
- Dodaj klasę `carei-step-exit` do wychodzącego elementu
|
||||
- Po 200ms (transitionend lub setTimeout): hide wychodzący, show wchodzący z `carei-step-enter`
|
||||
- Po 1 frame (requestAnimationFrame): zamień `carei-step-enter` na `carei-step-active`
|
||||
|
||||
3. **Modal open animation:** Dodaj fade-in na overlay (.carei-modal-overlay) i scale na .carei-modal:
|
||||
```css
|
||||
.carei-modal-overlay { opacity: 0; transition: opacity 0.3s ease; }
|
||||
.carei-modal-overlay.is-open { opacity: 1; }
|
||||
.carei-modal { transform: scale(0.95); transition: transform 0.3s ease; }
|
||||
.carei-modal-overlay.is-open .carei-modal { transform: scale(1); }
|
||||
```
|
||||
Zmień display:none/flex na visibility+opacity pattern (display:flex zawsze, visibility:hidden gdy nie is-open).
|
||||
|
||||
4. **Fix CSS bug:** Linie ~558-570 — orphaned styles po zamknięciu media query 768px. Przenieś `.carei-form__row--address`, `.carei-summary__actions`, `.carei-summary__btn` do wnętrza `@media (max-width: 768px)` i usuń dodatkowy `}`.
|
||||
|
||||
Avoid: Nie dodawaj żadnych bibliotek animacji. Czyste CSS transitions + JS class toggle.
|
||||
</action>
|
||||
<verify>
|
||||
- Otwórz modal: smooth fade-in + scale
|
||||
- Submit formularz: form fade out → summary fade in
|
||||
- "Wróć": summary fade out → form fade in
|
||||
- "Potwierdź": summary fade out → success fade in
|
||||
- Sprawdź w DevTools że nie ma CSS parse errors
|
||||
</verify>
|
||||
<done>AC-4 satisfied: animacje przejść. AC-6 satisfied: CSS bug naprawiony.</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 3: Accessibility — ARIA, focus trap, focus management</name>
|
||||
<files>wp-content/plugins/carei-reservation/includes/class-elementor-widget.php, wp-content/plugins/carei-reservation/assets/js/carei-reservation.js</files>
|
||||
<action>
|
||||
1. **PHP widget — ARIA attrs:**
|
||||
- Modal overlay div: dodaj `role="dialog"` `aria-modal="true"` `aria-labelledby="carei-modal-title"`
|
||||
- Close button: dodaj `aria-label="Zamknij formularz"`
|
||||
- Form sections: dodaj `role="group"` z `aria-label` na sekcjach (dane rezerwacji, dane osobowe, opcje)
|
||||
- Submit button: dodaj `aria-busy="false"` (JS zmieni na true podczas loading)
|
||||
- Summary confirm: dodaj `aria-busy="false"`
|
||||
|
||||
2. **JS — Focus management:**
|
||||
- openModal(): po otwarciu, focus na pierwszy select (segmentSelect) lub na modal-title
|
||||
- closeModal(): zapisz `lastFocusedElement` przed open, przywróć focus po close
|
||||
- showSummaryOverlay(): focus na .carei-summary__title
|
||||
- showSuccessView(): focus na .carei-success__title
|
||||
- handleSummaryBack(): focus na segmentSelect
|
||||
|
||||
3. **JS — Focus trap:**
|
||||
- W openModal(), dodaj keydown listener na modal:
|
||||
- Zbierz wszystkie focusable elements w modalu
|
||||
- Na Tab z ostatniego → focus na pierwszy
|
||||
- Na Shift+Tab z pierwszego → focus na ostatni
|
||||
- Usuń trap w closeModal()
|
||||
|
||||
4. **JS — aria-busy:**
|
||||
- setSubmitState('loading'): dodaj aria-busy="true" na submit button
|
||||
- setSubmitState('ready'): dodaj aria-busy="false"
|
||||
- Analogicznie dla summaryConfirm
|
||||
|
||||
5. **Announcements:** Dodaj aria-live="polite" region (sr-only) do modalu. Announce:
|
||||
- "Ładowanie podsumowania..." przy submit
|
||||
- "Rezerwacja potwierdzona" przy success
|
||||
- Błędy API
|
||||
|
||||
Avoid: Nie dodawaj aria-label do elementów które mają visible text. Nie rób z formularza role="form" (natywny <form> wystarczy).
|
||||
</action>
|
||||
<verify>
|
||||
- Tab przez formularz: focus nie wychodzi poza modal
|
||||
- Submit → focus na heading podsumowania
|
||||
- Confirm → focus na heading success
|
||||
- Close modal → focus wraca na trigger button
|
||||
- Screen reader: ogłasza "Ładowanie...", "Rezerwacja potwierdzona", błędy
|
||||
</verify>
|
||||
<done>AC-5 satisfied: ARIA attrs, focus trap, focus management, aria-live announcements</done>
|
||||
</task>
|
||||
|
||||
<task type="checkpoint:human-verify" gate="blocking">
|
||||
<what-built>Edge cases, animacje przejść, accessibility — pełny polish formularza</what-built>
|
||||
<how-to-verify>
|
||||
1. Otwórz https://carei.pagedev.pl
|
||||
2. Kliknij "Złóż zapytanie o rezerwację" — modal fade-in smooth
|
||||
3. Tab przez formularz — focus nie wychodzi poza modal
|
||||
4. Wypełnij formularz i wyślij — form animuje się do summary
|
||||
5. Kliknij "Wróć do formularza" — animacja powrotu
|
||||
6. Wyślij ponownie → Potwierdź → success z animacją
|
||||
7. Zamknij modal → focus wraca na przycisk trigger
|
||||
8. Test offline: DevTools → Network → Offline → submit → komunikat błędu
|
||||
9. Test mobile: responsive 375px — form działa, animacje smooth
|
||||
</how-to-verify>
|
||||
<resume-signal>Type "approved" to continue, or describe issues to fix</resume-signal>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<boundaries>
|
||||
|
||||
## DO NOT CHANGE
|
||||
- wp-content/plugins/carei-reservation/includes/class-softra-api.php (API proxy stable)
|
||||
- wp-content/plugins/carei-reservation/includes/class-rest-proxy.php (REST endpoints stable)
|
||||
- .env (credentials)
|
||||
- Logika API endpoints (segments-branches-map, pricelist, customer, booking)
|
||||
|
||||
## SCOPE LIMITS
|
||||
- Nie dodawaj nowych endpointów API
|
||||
- Nie zmieniaj flow rezerwacji (customer → pricing → booking → confirm)
|
||||
- Nie dodawaj bibliotek zewnętrznych
|
||||
- Nie implementuj e2e test framework — weryfikacja manualna na produkcji
|
||||
- Nie dodawaj ubezpieczenia ani wyjazdu zagranicznego (backlog)
|
||||
|
||||
</boundaries>
|
||||
|
||||
<verification>
|
||||
Before declaring plan complete:
|
||||
- [ ] Formularz działa end-to-end bez regresji
|
||||
- [ ] Token retry: 401 → retry → success
|
||||
- [ ] Timeout 15s: komunikat, przycisk aktywny
|
||||
- [ ] Network error: komunikat, przycisk aktywny
|
||||
- [ ] CAR_NOT_FOUND: czytelny komunikat PL
|
||||
- [ ] Animacje: modal open, form→summary, summary→form, summary→success
|
||||
- [ ] Focus: modal open → focus w modalu, close → focus na trigger
|
||||
- [ ] Focus trap: Tab nie wychodzi poza modal
|
||||
- [ ] ARIA: role=dialog, aria-modal, aria-busy, aria-live
|
||||
- [ ] CSS: brak orphaned styles, media queries poprawne
|
||||
- [ ] Mobile 375px: responsive OK
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- All 6 acceptance criteria met
|
||||
- All verification checks pass
|
||||
- No regressions in booking flow
|
||||
- Human verification approved on carei.pagedev.pl
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
After completion, create `.paul/phases/04-polish-testing/04-01-SUMMARY.md`
|
||||
</output>
|
||||
126
.paul/phases/04-polish-testing/04-01-SUMMARY.md
Normal file
126
.paul/phases/04-polish-testing/04-01-SUMMARY.md
Normal file
@@ -0,0 +1,126 @@
|
||||
---
|
||||
phase: 04-polish-testing
|
||||
plan: 01
|
||||
subsystem: ui, api
|
||||
tags: [a11y, animations, error-handling, focus-trap, aria, css-transitions]
|
||||
|
||||
requires:
|
||||
- phase: 03-form-submit-booking
|
||||
provides: Complete booking flow end-to-end (customer → pricing → booking → confirm → success)
|
||||
provides:
|
||||
- Token retry on 401/403 with auto-recovery
|
||||
- AbortController timeout 15s with Polish error messages
|
||||
- Network error detection and user-friendly messaging
|
||||
- Extended reject reason translations (6 codes)
|
||||
- Animated step transitions (form ↔ summary ↔ success)
|
||||
- Modal open/close animations (fade + scale)
|
||||
- ARIA dialog with focus trap and focus management
|
||||
- aria-live announcements for screen readers
|
||||
- CSS bug fix (orphaned media query styles)
|
||||
affects: [05-admin-panel]
|
||||
|
||||
tech-stack:
|
||||
added: []
|
||||
patterns: [AbortController timeout, CSS class-based step transitions, focus trap pattern, aria-live announcements]
|
||||
|
||||
key-files:
|
||||
modified:
|
||||
- wp-content/plugins/carei-reservation/assets/js/carei-reservation.js
|
||||
- wp-content/plugins/carei-reservation/assets/css/carei-reservation.css
|
||||
- wp-content/plugins/carei-reservation/includes/class-elementor-widget.php
|
||||
|
||||
key-decisions:
|
||||
- "Inline style.display for step visibility instead of CSS-only classes — HTML inline display:none overrides classes"
|
||||
- "Focus trap via keydown listener on overlay, filtering visible focusable elements"
|
||||
- "aria-live region appended to modal overlay for scoped announcements"
|
||||
|
||||
patterns-established:
|
||||
- "transitionStep(outEl, inEl, callback) pattern for animated view switching"
|
||||
- "hideStep/showStep helpers using style.display for reliable visibility control"
|
||||
- "announce() via aria-live polite region with 100ms delay for screen reader pickup"
|
||||
|
||||
duration: ~45min
|
||||
started: 2026-03-25T14:00:00Z
|
||||
completed: 2026-03-25T14:45:00Z
|
||||
---
|
||||
|
||||
# Phase 4 Plan 01: Polish & Integration Testing Summary
|
||||
|
||||
**Edge cases (token retry, timeout, network errors), animated step transitions, ARIA dialog with focus trap — formularz gotowy produkcyjnie.**
|
||||
|
||||
## Performance
|
||||
|
||||
| Metric | Value |
|
||||
|--------|-------|
|
||||
| Duration | ~45min |
|
||||
| Tasks | 3 auto + 1 checkpoint |
|
||||
| Files modified | 3 |
|
||||
| Deviations | 1 (auto-fixed) |
|
||||
|
||||
## Acceptance Criteria Results
|
||||
|
||||
| Criterion | Status | Notes |
|
||||
|-----------|--------|-------|
|
||||
| AC-1: Wygasły token JWT | Pass | 401/403 → auto-retry 1x, user nie widzi błędu |
|
||||
| AC-2: Brak dostępności pojazdu | Pass | CAR_NOT_FOUND → czytelny komunikat PL z sugestią |
|
||||
| AC-3: Timeouty i błędy sieciowe | Pass | AbortController 15s, TypeError → retry → komunikat PL |
|
||||
| AC-4: Animacje przejść | Pass | fade+translate form↔summary↔success, modal scale |
|
||||
| AC-5: Accessibility | Pass | role=dialog, aria-modal, focus trap, focus management, aria-live |
|
||||
| AC-6: Fix CSS orphaned styles | Pass | Styles przeniesione do @media (max-width: 768px) |
|
||||
|
||||
## Accomplishments
|
||||
|
||||
- Pełny error recovery: token retry, timeout 15s, network errors — formularz nigdy nie blokuje się na stałe
|
||||
- Animowane przejścia między krokami: smooth fade+translate (250ms) z callback focus management
|
||||
- Focus trap w modalu: Tab nie wychodzi poza modal, Escape zamyka, focus wraca na trigger
|
||||
- aria-live announcements: "Ładowanie podsumowania...", "Rezerwacja potwierdzona", błędy API
|
||||
|
||||
## Files Modified
|
||||
|
||||
| File | Change | Purpose |
|
||||
|------|--------|---------|
|
||||
| `assets/js/carei-reservation.js` | Modified | Token retry, timeout, network errors, animacje, focus trap, aria-live |
|
||||
| `assets/css/carei-reservation.css` | Modified | Modal animations, step transitions, sr-only class, CSS bug fix |
|
||||
| `includes/class-elementor-widget.php` | Modified | ARIA: role=dialog, aria-modal, aria-labelledby, aria-busy, tabindex=-1 |
|
||||
|
||||
## Decisions Made
|
||||
|
||||
| Decision | Rationale | Impact |
|
||||
|----------|-----------|--------|
|
||||
| style.display zamiast CSS class dla visibility | HTML inline display:none nadpisuje klasy CSS | hideStep/showStep helpers operują na style.display |
|
||||
| Focus na segment select po open | Pierwszy interaktywny element, najlogiczniejszy start | 350ms delay po animacji otwarcia |
|
||||
| aria-live region w overlay | Scoped do modalu, nie zanieczyszcza strony | announce() z 100ms delay dla pickup przez screen readery |
|
||||
|
||||
## Deviations from Plan
|
||||
|
||||
### Summary
|
||||
|
||||
| Type | Count | Impact |
|
||||
|------|-------|--------|
|
||||
| Auto-fixed | 1 | Konieczna zmiana podejścia do visibility |
|
||||
|
||||
**Total impact:** Essential fix, no scope creep
|
||||
|
||||
### Auto-fixed Issues
|
||||
|
||||
**1. Inline display:none vs CSS class conflict**
|
||||
- **Found during:** Checkpoint (user reported form title instead of summary)
|
||||
- **Issue:** HTML elements miały `style="display:none;"` inline, a transitionStep używał klas CSS (`carei-step--hidden`) — inline style nadpisywał klasę
|
||||
- **Fix:** Zmieniono na hideStep()/showStep() operujące na `el.style.display`
|
||||
- **Verification:** User potwierdził poprawne działanie po fix
|
||||
|
||||
## Next Phase Readiness
|
||||
|
||||
**Ready:**
|
||||
- Formularz produkcyjnie gotowy: error handling, animacje, a11y
|
||||
- Booking flow end-to-end przetestowany na carei.pagedev.pl
|
||||
|
||||
**Concerns:**
|
||||
- Penalty items filtrowane po kodzie (BRAK/BRUD/KARA) — może nie pokryć przyszłych kodów
|
||||
- Brak email notification po rezerwacji (zależy od konfiguracji Softra)
|
||||
|
||||
**Blockers:** None
|
||||
|
||||
---
|
||||
*Phase: 04-polish-testing, Plan: 01*
|
||||
*Completed: 2026-03-25*
|
||||
159
.vscode/ftp-kr.sync.cache.json
vendored
159
.vscode/ftp-kr.sync.cache.json
vendored
@@ -102,6 +102,108 @@
|
||||
"lmtime": 0,
|
||||
"modified": false
|
||||
},
|
||||
".paul": {
|
||||
"HANDOFF-2026-03-25.md": {
|
||||
"type": "-",
|
||||
"size": 3911,
|
||||
"lmtime": 0,
|
||||
"modified": false
|
||||
},
|
||||
"HANDOFF-2026-03-25-phase3.md": {
|
||||
"type": "-",
|
||||
"size": 3672,
|
||||
"lmtime": 1774440974369,
|
||||
"modified": false
|
||||
},
|
||||
"HANDOFF-2026-03-25-phase5.md": {
|
||||
"type": "-",
|
||||
"size": 4436,
|
||||
"lmtime": 1774453504251,
|
||||
"modified": false
|
||||
},
|
||||
"handoffs": {
|
||||
"archive": {
|
||||
"HANDOFF-2026-03-25-phase3.md": {
|
||||
"type": "-",
|
||||
"size": 3672,
|
||||
"lmtime": 1774440974369,
|
||||
"modified": false
|
||||
},
|
||||
"HANDOFF-2026-03-25.md": {
|
||||
"type": "-",
|
||||
"size": 3911,
|
||||
"lmtime": 1774394933000,
|
||||
"modified": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"phases": {
|
||||
"01-reservation-form-plugin": {},
|
||||
"02-form-ui-step1": {
|
||||
"02-01-SUMMARY.md": {
|
||||
"type": "-",
|
||||
"size": 6067,
|
||||
"lmtime": 1774426464638,
|
||||
"modified": false
|
||||
}
|
||||
},
|
||||
"03-form-submit-booking": {
|
||||
"03-01-PLAN.md": {
|
||||
"type": "-",
|
||||
"size": 17170,
|
||||
"lmtime": 1774429395933,
|
||||
"modified": false
|
||||
},
|
||||
"03-01-SUMMARY.md": {
|
||||
"type": "-",
|
||||
"size": 5371,
|
||||
"lmtime": 1774440223363,
|
||||
"modified": false
|
||||
}
|
||||
},
|
||||
"04-polish-testing": {
|
||||
"04-01-PLAN.md": {
|
||||
"type": "-",
|
||||
"size": 12463,
|
||||
"lmtime": 1774444454151,
|
||||
"modified": false
|
||||
},
|
||||
"04-01-SUMMARY.md": {
|
||||
"type": "-",
|
||||
"size": 5317,
|
||||
"lmtime": 1774447383859,
|
||||
"modified": false
|
||||
}
|
||||
},
|
||||
"05-admin-panel": {
|
||||
"05-01-PLAN.md": {
|
||||
"type": "-",
|
||||
"size": 11298,
|
||||
"lmtime": 1774447717369,
|
||||
"modified": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"PROJECT.md": {
|
||||
"type": "-",
|
||||
"size": 2025,
|
||||
"lmtime": 0,
|
||||
"modified": false
|
||||
},
|
||||
"ROADMAP.md": {
|
||||
"type": "-",
|
||||
"size": 1906,
|
||||
"lmtime": 1774447405669,
|
||||
"modified": false
|
||||
},
|
||||
"STATE.md": {
|
||||
"type": "-",
|
||||
"size": 1376,
|
||||
"lmtime": 1774453515616,
|
||||
"modified": false
|
||||
}
|
||||
},
|
||||
".playwright-mcp": {},
|
||||
"readme.html": {
|
||||
"type": "-",
|
||||
"size": 7425,
|
||||
@@ -169,7 +271,62 @@
|
||||
"lmtime": 0,
|
||||
"modified": false
|
||||
},
|
||||
"wp-content": {},
|
||||
"wp-content": {
|
||||
"plugins": {
|
||||
"carei-reservation": {
|
||||
"assets": {
|
||||
"js": {
|
||||
"carei-reservation.js": {
|
||||
"type": "-",
|
||||
"size": 42643,
|
||||
"lmtime": 1774446710808,
|
||||
"modified": false
|
||||
}
|
||||
},
|
||||
"css": {
|
||||
"carei-reservation.css": {
|
||||
"type": "-",
|
||||
"size": 22586,
|
||||
"lmtime": 1774445557903,
|
||||
"modified": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"carei-reservation.php": {
|
||||
"type": "-",
|
||||
"size": 3199,
|
||||
"lmtime": 1774448068413,
|
||||
"modified": false
|
||||
},
|
||||
"includes": {
|
||||
"class-admin-panel.php": {
|
||||
"type": "-",
|
||||
"size": 18994,
|
||||
"lmtime": 1774448051188,
|
||||
"modified": false
|
||||
},
|
||||
"class-elementor-widget.php": {
|
||||
"type": "-",
|
||||
"size": 16135,
|
||||
"lmtime": 1774445732326,
|
||||
"modified": false
|
||||
},
|
||||
"class-rest-proxy.php": {
|
||||
"type": "-",
|
||||
"size": 10811,
|
||||
"lmtime": 1774448090860,
|
||||
"modified": false
|
||||
},
|
||||
"class-softra-api.php": {
|
||||
"type": "-",
|
||||
"size": 8662,
|
||||
"lmtime": 1774439108636,
|
||||
"modified": false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"wp-cron.php": {
|
||||
"type": "-",
|
||||
"size": 5617,
|
||||
|
||||
@@ -54,7 +54,7 @@
|
||||
Modal Overlay & Container
|
||||
═══════════════════════════════════════════ */
|
||||
.carei-modal-overlay {
|
||||
display: none;
|
||||
display: flex;
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 100000;
|
||||
@@ -62,9 +62,13 @@
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 20px;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: opacity 0.3s ease, visibility 0.3s ease;
|
||||
}
|
||||
.carei-modal-overlay.is-open {
|
||||
display: flex;
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
.carei-modal {
|
||||
background: var(--carei-bg);
|
||||
@@ -76,6 +80,11 @@
|
||||
padding: 40px 48px;
|
||||
position: relative;
|
||||
font-family: var(--carei-font);
|
||||
transform: scale(0.95) translateY(10px);
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
.carei-modal-overlay.is-open .carei-modal {
|
||||
transform: scale(1) translateY(0);
|
||||
}
|
||||
.carei-modal-close {
|
||||
position: absolute;
|
||||
@@ -134,8 +143,21 @@
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: var(--carei-gap-inner);
|
||||
}
|
||||
.carei-form__row--dates {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
.carei-form__row--top {
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
}
|
||||
.carei-form__row--pickup {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--carei-gap-inner);
|
||||
}
|
||||
.carei-form__row--pickup > .carei-form__field {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
.carei-form__checkbox-label--inline {
|
||||
white-space: nowrap;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.carei-form__field {
|
||||
display: flex;
|
||||
@@ -483,6 +505,20 @@
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* Disabled select */
|
||||
.carei-form__select-wrap select:disabled {
|
||||
color: var(--carei-placeholder);
|
||||
cursor: not-allowed;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
/* Extras wrapper — hidden until segment + pickup selected */
|
||||
.carei-form__extras-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--carei-gap-section);
|
||||
}
|
||||
|
||||
/* Return branch slide */
|
||||
.carei-form__return-wrap {
|
||||
overflow: hidden;
|
||||
@@ -496,6 +532,37 @@
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════
|
||||
Step Transitions (form ↔ summary ↔ success)
|
||||
═══════════════════════════════════════════ */
|
||||
.carei-step {
|
||||
transition: opacity 0.25s ease, transform 0.25s ease;
|
||||
}
|
||||
.carei-step--hidden {
|
||||
display: none !important;
|
||||
}
|
||||
.carei-step--exiting {
|
||||
opacity: 0;
|
||||
transform: translateY(-8px);
|
||||
}
|
||||
.carei-step--entering {
|
||||
opacity: 0;
|
||||
transform: translateY(8px);
|
||||
}
|
||||
|
||||
/* Screen reader only */
|
||||
.carei-sr-only {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
white-space: nowrap;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════
|
||||
Mobile Responsive (<768px)
|
||||
═══════════════════════════════════════════ */
|
||||
@@ -516,7 +583,7 @@
|
||||
.carei-form__row {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
.carei-form__row--dates {
|
||||
.carei-form__row--top {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
.carei-form__footer {
|
||||
@@ -528,14 +595,246 @@
|
||||
justify-content: center;
|
||||
padding: 16px;
|
||||
}
|
||||
.carei-form__row--address {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
.carei-summary__actions {
|
||||
flex-direction: column;
|
||||
}
|
||||
.carei-summary__btn {
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
/* Very small screens */
|
||||
@media (max-width: 480px) {
|
||||
.carei-form__row--dates {
|
||||
.carei-form__row--top {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
.carei-modal {
|
||||
padding: 24px 16px;
|
||||
}
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════
|
||||
Address Row
|
||||
═══════════════════════════════════════════ */
|
||||
.carei-form__row--address {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto 1fr;
|
||||
gap: var(--carei-gap-inner);
|
||||
}
|
||||
.carei-form__field--zipcode {
|
||||
width: 120px;
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════
|
||||
Summary Overlay
|
||||
═══════════════════════════════════════════ */
|
||||
.carei-summary {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--carei-gap-section);
|
||||
}
|
||||
.carei-summary__title {
|
||||
font-family: var(--carei-font);
|
||||
font-weight: 700;
|
||||
font-size: 20px;
|
||||
color: var(--carei-blue);
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
}
|
||||
.carei-summary__title span {
|
||||
color: var(--carei-red);
|
||||
}
|
||||
.carei-summary__details {
|
||||
font-family: var(--carei-font);
|
||||
font-size: 14px;
|
||||
color: var(--carei-gray);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
background: var(--carei-white);
|
||||
padding: 16px;
|
||||
border-radius: var(--carei-radius);
|
||||
}
|
||||
.carei-summary__details strong {
|
||||
color: var(--carei-blue);
|
||||
}
|
||||
.carei-summary__table table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-family: var(--carei-font);
|
||||
font-size: 13px;
|
||||
}
|
||||
.carei-summary__table th {
|
||||
text-align: left;
|
||||
font-weight: 600;
|
||||
color: var(--carei-blue);
|
||||
padding: 8px 12px;
|
||||
border-bottom: 2px solid var(--carei-border);
|
||||
font-size: 12px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.carei-summary__table td {
|
||||
padding: 8px 12px;
|
||||
border-bottom: 1px solid var(--carei-border);
|
||||
color: var(--carei-gray);
|
||||
}
|
||||
.carei-summary__table tr:nth-child(even) td {
|
||||
background: rgba(237, 237, 243, 0.5);
|
||||
}
|
||||
.carei-summary__table .carei-summary__auto-item td {
|
||||
font-style: italic;
|
||||
color: var(--carei-placeholder);
|
||||
}
|
||||
.carei-summary__table td:last-child {
|
||||
text-align: right;
|
||||
font-weight: 600;
|
||||
color: var(--carei-blue);
|
||||
}
|
||||
.carei-summary__total {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
gap: 4px;
|
||||
font-family: var(--carei-font);
|
||||
font-size: 14px;
|
||||
color: var(--carei-gray);
|
||||
padding: 0 12px;
|
||||
}
|
||||
.carei-summary__total-row {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
}
|
||||
.carei-summary__total-row--gross {
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
color: var(--carei-blue);
|
||||
}
|
||||
.carei-summary__total-label {
|
||||
min-width: 120px;
|
||||
text-align: right;
|
||||
}
|
||||
.carei-summary__total-value {
|
||||
min-width: 100px;
|
||||
text-align: right;
|
||||
}
|
||||
.carei-summary__actions {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: var(--carei-gap-inner);
|
||||
padding-top: 8px;
|
||||
}
|
||||
.carei-summary__btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 14px 28px;
|
||||
font-family: var(--carei-font);
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
border-radius: var(--carei-radius);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
line-height: 1;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.carei-summary__btn svg {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
.carei-summary__btn--back {
|
||||
background: transparent;
|
||||
border: 1px solid var(--carei-blue);
|
||||
color: var(--carei-blue);
|
||||
}
|
||||
.carei-summary__btn--back:hover {
|
||||
background: rgba(47, 36, 130, 0.05);
|
||||
}
|
||||
.carei-summary__btn--confirm {
|
||||
background: var(--carei-red);
|
||||
border: 1px solid var(--carei-red);
|
||||
color: var(--carei-white);
|
||||
}
|
||||
.carei-summary__btn--confirm:hover {
|
||||
background: var(--carei-red-hover);
|
||||
border-color: var(--carei-red-hover);
|
||||
}
|
||||
.carei-summary__btn:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.carei-summary__error {
|
||||
background: rgba(255, 0, 0, 0.05);
|
||||
color: var(--carei-red);
|
||||
padding: 12px 16px;
|
||||
border-radius: var(--carei-radius);
|
||||
font-family: var(--carei-font);
|
||||
font-size: 13px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════
|
||||
Success View
|
||||
═══════════════════════════════════════════ */
|
||||
.carei-success {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
gap: 16px;
|
||||
padding: 40px 0;
|
||||
}
|
||||
.carei-success__icon {
|
||||
width: 72px;
|
||||
height: 72px;
|
||||
border-radius: 50%;
|
||||
background: #22c55e;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.carei-success__title {
|
||||
font-family: var(--carei-font);
|
||||
font-weight: 700;
|
||||
font-size: 22px;
|
||||
color: var(--carei-blue);
|
||||
margin: 0;
|
||||
}
|
||||
.carei-success__number {
|
||||
font-family: var(--carei-font);
|
||||
font-weight: 700;
|
||||
font-size: 16px;
|
||||
color: var(--carei-blue);
|
||||
background: var(--carei-white);
|
||||
padding: 12px 24px;
|
||||
border-radius: var(--carei-radius);
|
||||
margin: 0;
|
||||
}
|
||||
.carei-success__message {
|
||||
font-family: var(--carei-font);
|
||||
font-size: 14px;
|
||||
color: var(--carei-gray);
|
||||
margin: 0;
|
||||
}
|
||||
.carei-success__close {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 14px 32px;
|
||||
background: var(--carei-red);
|
||||
color: var(--carei-white);
|
||||
font-family: var(--carei-font);
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
border: none;
|
||||
border-radius: var(--carei-radius);
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s;
|
||||
margin-top: 8px;
|
||||
}
|
||||
.carei-success__close:hover {
|
||||
background: var(--carei-red-hover);
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -56,20 +56,21 @@ class Carei_Reservation_Widget extends \Elementor\Widget_Base {
|
||||
<?php echo $button_text; ?>
|
||||
</button>
|
||||
|
||||
<div class="carei-modal-overlay" data-carei-modal>
|
||||
<div class="carei-modal-overlay" data-carei-modal role="dialog" aria-modal="true" aria-labelledby="carei-modal-title">
|
||||
<div class="carei-modal">
|
||||
<button type="button" class="carei-modal-close" data-carei-close-modal>
|
||||
<button type="button" class="carei-modal-close" data-carei-close-modal aria-label="Zamknij formularz">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M18 6L6 18M6 6l12 12" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
|
||||
</svg>
|
||||
</button>
|
||||
<h2 class="carei-modal-title">Wypełnij formularz rezerwacji<span>.</span></h2>
|
||||
<h2 class="carei-modal-title" id="carei-modal-title">Wypełnij formularz rezerwacji<span>.</span></h2>
|
||||
|
||||
<form class="carei-form" id="carei-reservation-form" novalidate>
|
||||
|
||||
<!-- Dane wynajmu -->
|
||||
<div class="carei-form__section">
|
||||
<div class="carei-form__field carei-form__field--full">
|
||||
<div class="carei-form__row carei-form__row--top">
|
||||
<div class="carei-form__field">
|
||||
<div class="carei-form__select-wrap">
|
||||
<select id="carei-segment" name="segment" required>
|
||||
<option value="" disabled selected>Wybierz segment pojazdu</option>
|
||||
@@ -77,8 +78,6 @@ class Carei_Reservation_Widget extends \Elementor\Widget_Base {
|
||||
<svg class="carei-form__select-arrow" width="16" height="16" viewBox="0 0 16 16"><path d="M4 6l4 4 4-4" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round"/></svg>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="carei-form__row carei-form__row--dates">
|
||||
<div class="carei-form__field">
|
||||
<label class="carei-form__label-small" for="carei-date-from">Od kiedy?</label>
|
||||
<input type="datetime-local" id="carei-date-from" name="dateFrom" class="carei-form__input" required>
|
||||
@@ -94,7 +93,8 @@ class Carei_Reservation_Widget extends \Elementor\Widget_Base {
|
||||
|
||||
<div class="carei-form__days-count" id="carei-days-count">Wybrano: <strong>0 dni</strong></div>
|
||||
|
||||
<div class="carei-form__field carei-form__field--full">
|
||||
<div class="carei-form__row carei-form__row--pickup">
|
||||
<div class="carei-form__field">
|
||||
<div class="carei-form__select-wrap carei-form__select-wrap--icon">
|
||||
<svg class="carei-form__icon-pin" width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M8 1C5.24 1 3 3.24 3 6c0 3.75 5 9 5 9s5-5.25 5-9c0-2.76-2.24-5-5-5zm0 7a2 2 0 110-4 2 2 0 010 4z" fill="currentColor"/></svg>
|
||||
<select id="carei-pickup-branch" name="pickupBranch" required>
|
||||
@@ -103,14 +103,14 @@ class Carei_Reservation_Widget extends \Elementor\Widget_Base {
|
||||
<svg class="carei-form__select-arrow" width="16" height="16" viewBox="0 0 16 16"><path d="M4 6l4 4 4-4" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round"/></svg>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<label class="carei-form__checkbox-label">
|
||||
<label class="carei-form__checkbox-label carei-form__checkbox-label--inline">
|
||||
<input type="checkbox" id="carei-same-return" name="sameReturn" checked>
|
||||
<span class="carei-form__checkbox-box">
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M2 7l3.5 3.5L12 4" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||
</span>
|
||||
<span class="carei-form__checkbox-text">Zwrot w tej samej lokalizacji</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="carei-form__field carei-form__field--full carei-form__return-wrap" id="carei-return-wrap" style="display:none;">
|
||||
<div class="carei-form__select-wrap carei-form__select-wrap--icon">
|
||||
@@ -123,38 +123,23 @@ class Carei_Reservation_Widget extends \Elementor\Widget_Base {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Opcje dodatkowe -->
|
||||
<div class="carei-form__divider"><span>Opcje dodatkowe</span></div>
|
||||
<!-- Ubezpieczenie + Opcje dodatkowe (ukryte do wybrania segmentu i miejsca odbioru) -->
|
||||
<div id="carei-extras-wrapper" class="carei-form__extras-wrapper" style="display:none;">
|
||||
|
||||
<div class="carei-form__divider"><span>Ubezpieczenie</span></div>
|
||||
<div class="carei-form__section">
|
||||
<div class="carei-form__row" id="carei-insurance-container">
|
||||
<!-- Dynamicznie z API pricelist -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="carei-form__divider"><span>Opcje dodatkowe</span></div>
|
||||
<div class="carei-form__section">
|
||||
<div class="carei-form__row" id="carei-extras-container">
|
||||
<div class="carei-form__extra-card">
|
||||
<label class="carei-form__checkbox-label carei-form__checkbox-label--card">
|
||||
<input type="checkbox" name="extras[]" value="insurance">
|
||||
<span class="carei-form__checkbox-box">
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M2 7l3.5 3.5L12 4" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||
</span>
|
||||
<span class="carei-form__extra-content">
|
||||
<strong>Rozszerzone ubezpieczenie</strong>
|
||||
<span class="carei-form__extra-desc">Obejmuje brak odpowiedzialności najemcy za wszelki szkody poniesione na aucie.</span>
|
||||
<span class="carei-form__extra-price">300 zł</span>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="carei-form__extra-card">
|
||||
<label class="carei-form__checkbox-label carei-form__checkbox-label--card">
|
||||
<input type="checkbox" name="extras[]" value="child-seat">
|
||||
<span class="carei-form__checkbox-box">
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M2 7l3.5 3.5L12 4" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||
</span>
|
||||
<span class="carei-form__extra-content">
|
||||
<strong>Fotelik dla dziecka</strong>
|
||||
<span class="carei-form__extra-desc">Prosimy zawrzeć wiek dziecka w wiadomości.</span>
|
||||
<span class="carei-form__extra-price">50 zł</span>
|
||||
</span>
|
||||
</label>
|
||||
<!-- Dynamicznie z API pricelist -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Dane najemcy -->
|
||||
@@ -172,6 +157,21 @@ class Carei_Reservation_Widget extends \Elementor\Widget_Base {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="carei-form__row carei-form__row--address">
|
||||
<div class="carei-form__field">
|
||||
<label class="carei-form__label-small" for="carei-city">Miejscowość</label>
|
||||
<input type="text" id="carei-city" name="city" class="carei-form__input" required>
|
||||
</div>
|
||||
<div class="carei-form__field carei-form__field--zipcode">
|
||||
<label class="carei-form__label-small" for="carei-zipcode">Kod pocztowy</label>
|
||||
<input type="text" id="carei-zipcode" name="zipCode" class="carei-form__input" placeholder="00-000" maxlength="6" required>
|
||||
</div>
|
||||
<div class="carei-form__field">
|
||||
<label class="carei-form__label-small" for="carei-street">Ulica i nr domu</label>
|
||||
<input type="text" id="carei-street" name="street" class="carei-form__input" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="carei-form__row">
|
||||
<div class="carei-form__field">
|
||||
<label class="carei-form__label-small" for="carei-email">Adres e-mail</label>
|
||||
@@ -189,6 +189,14 @@ class Carei_Reservation_Widget extends \Elementor\Widget_Base {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="carei-form__row">
|
||||
<div class="carei-form__field">
|
||||
<label class="carei-form__label-small" for="carei-pesel">PESEL</label>
|
||||
<input type="text" id="carei-pesel" name="pesel" class="carei-form__input" maxlength="11" placeholder="00000000000" required>
|
||||
</div>
|
||||
<div class="carei-form__field"></div>
|
||||
</div>
|
||||
|
||||
<div class="carei-form__field carei-form__field--full">
|
||||
<textarea id="carei-message" name="message" class="carei-form__textarea" placeholder="Twoja wiadomość dotycząca rezerwacji" rows="4"></textarea>
|
||||
</div>
|
||||
@@ -203,7 +211,7 @@ class Carei_Reservation_Widget extends \Elementor\Widget_Base {
|
||||
</span>
|
||||
<span class="carei-form__checkbox-text">Zgadzam się na <a href="/polityka-prywatnosci/" target="_blank">Politykę Prywatności</a></span>
|
||||
</label>
|
||||
<button type="submit" class="carei-form__submit">
|
||||
<button type="submit" class="carei-form__submit" aria-busy="false">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z"/></svg>
|
||||
Wyślij
|
||||
</button>
|
||||
@@ -214,6 +222,37 @@ class Carei_Reservation_Widget extends \Elementor\Widget_Base {
|
||||
</div>
|
||||
|
||||
</form>
|
||||
|
||||
<!-- Podsumowanie kosztów (po submit) -->
|
||||
<div id="carei-summary-overlay" class="carei-summary" style="display:none;">
|
||||
<h3 class="carei-summary__title" tabindex="-1">Podsumowanie rezerwacji<span>.</span></h3>
|
||||
<div class="carei-summary__details" id="carei-summary-details"></div>
|
||||
<div class="carei-summary__table" id="carei-summary-table"></div>
|
||||
<div class="carei-summary__total" id="carei-summary-total"></div>
|
||||
<div class="carei-summary__error" id="carei-summary-error" style="display:none;"></div>
|
||||
<div class="carei-summary__actions">
|
||||
<button type="button" class="carei-summary__btn carei-summary__btn--back" id="carei-summary-back">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none"><path d="M19 12H5M12 19l-7-7 7-7" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||
Wróć do formularza
|
||||
</button>
|
||||
<button type="button" class="carei-summary__btn carei-summary__btn--confirm" id="carei-summary-confirm" aria-busy="false">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z"/></svg>
|
||||
Potwierdź rezerwację
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Sukces -->
|
||||
<div id="carei-success-view" class="carei-success" style="display:none;">
|
||||
<div class="carei-success__icon">
|
||||
<svg width="40" height="40" viewBox="0 0 24 24" fill="none"><path d="M20 6L9 17l-5-5" stroke="#fff" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||
</div>
|
||||
<h3 class="carei-success__title" tabindex="-1">Rezerwacja złożona!</h3>
|
||||
<p class="carei-success__number" id="carei-success-number"></p>
|
||||
<p class="carei-success__message">Potwierdzenie zostanie wysłane na podany adres e-mail.</p>
|
||||
<button type="button" class="carei-success__close" id="carei-success-close">Zamknij</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
|
||||
@@ -157,6 +157,60 @@ class Carei_Softra_API {
|
||||
return $this->request( 'GET', '/car/class/listAll' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get branches list with all segment names.
|
||||
* Returns branches + all segments. Frontend shows all branches for any segment
|
||||
* (actual availability verified at booking time by the API).
|
||||
* Cached as WP transient for 6 hours.
|
||||
*/
|
||||
public function get_segments_branches_map() {
|
||||
$cache_key = 'carei_segments_branches_map';
|
||||
$cached = get_transient( $cache_key );
|
||||
if ( false !== $cached ) {
|
||||
return $cached;
|
||||
}
|
||||
|
||||
$branches = $this->get_branches();
|
||||
if ( is_wp_error( $branches ) || ! is_array( $branches ) ) {
|
||||
return is_wp_error( $branches ) ? $branches : array();
|
||||
}
|
||||
|
||||
$all_classes = $this->get_all_car_classes();
|
||||
$segments = array();
|
||||
if ( ! is_wp_error( $all_classes ) && is_array( $all_classes ) ) {
|
||||
foreach ( $all_classes as $cls ) {
|
||||
$seg = is_string( $cls ) ? $cls : ( isset( $cls['name'] ) ? $cls['name'] : '' );
|
||||
if ( '' !== $seg ) {
|
||||
$segments[] = $seg;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// All branches potentially have all segments.
|
||||
// Actual availability is checked at booking via /car/class/list.
|
||||
$branch_names = array();
|
||||
foreach ( $branches as $b ) {
|
||||
if ( ! empty( $b['name'] ) ) {
|
||||
$branch_names[] = $b['name'];
|
||||
}
|
||||
}
|
||||
|
||||
$map = array();
|
||||
foreach ( $segments as $seg ) {
|
||||
$map[ $seg ] = $branch_names;
|
||||
}
|
||||
|
||||
$result = array(
|
||||
'segmentToBranches' => $map,
|
||||
'branchToSegments' => array(),
|
||||
'branches' => $branches,
|
||||
);
|
||||
|
||||
set_transient( $cache_key, $result, 6 * HOUR_IN_SECONDS );
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function get_car_classes( $date_from, $date_to, $branch_name ) {
|
||||
return $this->request( 'POST', '/car/class/list', array(
|
||||
'dateFrom' => $date_from,
|
||||
@@ -203,6 +257,13 @@ class Carei_Softra_API {
|
||||
) );
|
||||
}
|
||||
|
||||
public function cancel_booking( $reservation_id, $reason ) {
|
||||
return $this->request( 'POST', '/rent/cancel', array(
|
||||
'reservationId' => $reservation_id,
|
||||
'reason' => $reason,
|
||||
) );
|
||||
}
|
||||
|
||||
public function get_agreements() {
|
||||
return $this->request( 'GET', '/agreement/def/list' );
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user