fix(14-mobile-modal-fix): Modal rezerwacji działa na mobile/tablet
Sekcja Elementor zawierająca modal miała elementor-hidden-mobile/tablet, co powodowało display:none na rodzicu. Modal position:fixed wewnątrz ukrytego elementu miał zerowe wymiary. Fix: przeniesienie overlay do document.body w initRefs(). Plan Phase 13 (pakiety ochronne) utworzony, BLOCKED — czeka na klienta. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
204
.paul/phases/13-protection-packages/13-01-PLAN.md
Normal file
204
.paul/phases/13-protection-packages/13-01-PLAN.md
Normal file
@@ -0,0 +1,204 @@
|
||||
---
|
||||
phase: 13-protection-packages
|
||||
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
|
||||
delegation: off
|
||||
---
|
||||
|
||||
<objective>
|
||||
## Goal
|
||||
Wyświetlanie pakietów ochronnych SOFT i PREMIUM jako dedykowanych kafelków z ceną obliczaną dynamicznie na podstawie liczby dni wynajmu (3 progi cenowe: minimalna 1-3 dni, za dobę 4-15 dni, maksymalna 16+ dni).
|
||||
|
||||
## Purpose
|
||||
Klient chce prezentować pakiety ochronne w zrozumiały sposób — zamiast generycznego "od X do Y zł" użytkownik widzi jedną konkretną cenę dopasowaną do długości jego rezerwacji. Wybór pakietu jest opcjonalny i wzajemnie wykluczający (SOFT lub PREMIUM, nie oba).
|
||||
|
||||
## Output
|
||||
- Sekcja "Pakiety ochronne" z dwoma kafelkami (SOFT, PREMIUM) z dynamiczną ceną
|
||||
- Cena przeliczana automatycznie przy zmianie dat
|
||||
- Wybrany pakiet uwzględniany w podsumowaniu i booking submission
|
||||
</objective>
|
||||
|
||||
<context>
|
||||
## Project Context
|
||||
@.paul/PROJECT.md
|
||||
@.paul/ROADMAP.md
|
||||
@.paul/STATE.md
|
||||
|
||||
## 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>
|
||||
|
||||
<skills>
|
||||
No SPECIAL-FLOWS.md — skills section omitted.
|
||||
</skills>
|
||||
|
||||
<acceptance_criteria>
|
||||
|
||||
## AC-1: Tiered price calculation
|
||||
```gherkin
|
||||
Given insurance items from API with minPrice, price (per day), and maxPrice
|
||||
When the user has selected rental dates spanning N days
|
||||
Then each protection package shows:
|
||||
- minPrice (flat) when N <= 3
|
||||
- price × N when 4 <= N <= 15
|
||||
- maxPrice (flat) when N > 15
|
||||
```
|
||||
|
||||
## AC-2: Mutually exclusive selection
|
||||
```gherkin
|
||||
Given two protection package cards (SOFT, PREMIUM) are displayed
|
||||
When the user clicks on one package
|
||||
Then the other package is deselected (radio behavior)
|
||||
And the user can also deselect to choose no package
|
||||
```
|
||||
|
||||
## AC-3: Dynamic price update on date change
|
||||
```gherkin
|
||||
Given protection packages are displayed with calculated prices
|
||||
When the user changes pickup or return date
|
||||
Then the prices on both cards recalculate immediately
|
||||
And the correct tier (flat/per-day/flat) is applied based on new duration
|
||||
```
|
||||
|
||||
## AC-4: Package included in booking submission
|
||||
```gherkin
|
||||
Given the user has selected a protection package (SOFT or PREMIUM)
|
||||
When the booking is submitted
|
||||
Then the selected package is included in priceItems with correct calculated price
|
||||
And the package appears in the summary overlay with its name and total price
|
||||
```
|
||||
|
||||
</acceptance_criteria>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 1: Render protection packages as radio-style cards with tiered pricing</name>
|
||||
<files>wp-content/plugins/carei-reservation/assets/js/carei-reservation.js, wp-content/plugins/carei-reservation/assets/css/carei-reservation.css</files>
|
||||
<action>
|
||||
1. In the pricelist loading section (~line 432-458), after filtering insuranceItems:
|
||||
- Create a new function `buildProtectionCard(item, days)` that:
|
||||
- Calculates price based on days: `days <= 3` → item.minPrice, `days >= 4 && days <= 15` → item.price * days, `days > 15` → item.maxPrice
|
||||
- Renders a card with radio behavior (use checkbox with JS toggle for deselect capability)
|
||||
- Shows price label: "180 zł" (flat) or "60 zł/doba = 420 zł" (per-day with total)
|
||||
- Card design: prominent tile with package name, price, and a brief description if available from API
|
||||
- Replace current `insuranceContainer` rendering: instead of `buildExtraCard` for each item, use `buildProtectionCard`
|
||||
- Use `name="protection"` with type="radio" BUT wrap in a click handler that allows deselection (click selected = deselect)
|
||||
|
||||
2. Add function `updateProtectionPrices()` that:
|
||||
- Reads current days count from the date fields (reuse existing daysCount logic)
|
||||
- Recalculates prices for both cards
|
||||
- Updates the displayed price labels
|
||||
- Call this function when dates change (hook into existing date change handler)
|
||||
|
||||
3. CSS for protection cards in carei-reservation.css:
|
||||
- Two cards side by side (flex row, gap)
|
||||
- Active/selected state with border highlight (#2F2482)
|
||||
- Responsive: stack vertically on mobile
|
||||
- Style consistent with existing extra cards but visually distinct (larger, more prominent)
|
||||
|
||||
Avoid: Changing the existing `buildExtraCard` function — it still serves regular extras.
|
||||
Avoid: Hardcoding prices — all pricing comes from API item fields (minPrice, price, maxPrice).
|
||||
</action>
|
||||
<verify>
|
||||
- Open reservation form, select dates for 2 days → packages show flat minPrice
|
||||
- Change dates to 7 days → packages show per-day price × 7
|
||||
- Change dates to 20 days → packages show flat maxPrice
|
||||
- Click SOFT → SOFT selected, click PREMIUM → SOFT deselected + PREMIUM selected
|
||||
- Click selected card again → deselected (no package chosen)
|
||||
</verify>
|
||||
<done>AC-1, AC-2, AC-3 satisfied: Protection packages render with correct tiered pricing, mutually exclusive selection, and dynamic price updates</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2: Wire protection package into booking submission and summary</name>
|
||||
<files>wp-content/plugins/carei-reservation/assets/js/carei-reservation.js</files>
|
||||
<action>
|
||||
1. In the booking submission logic (where extras[] checkboxes are collected):
|
||||
- Add collection of selected protection package: query `input[name="protection"]:checked`
|
||||
- Build priceItem with: id, name, unit='szt.', amount=1, priceBeforeDiscount = calculated total price, discount=0, priceAfterDiscount = same
|
||||
- Append to the priceItems array sent to API
|
||||
|
||||
2. In the summary overlay rendering:
|
||||
- Add a row for the selected protection package showing package name and total price
|
||||
- If no package selected, don't show a row
|
||||
|
||||
3. Ensure the protection price is included in the total calculation in the summary.
|
||||
|
||||
Avoid: Changing the admin panel storage logic — protection items go through as regular priceItems, same as extras.
|
||||
Avoid: Duplicating protection item if it also appears in extras — ensure the filtering separates them cleanly.
|
||||
</action>
|
||||
<verify>
|
||||
- Select SOFT package + submit → summary shows "Pakiet ochrony Soft — XXX zł"
|
||||
- Submit without package → no protection row in summary
|
||||
- Full booking flow completes with package included in API call
|
||||
</verify>
|
||||
<done>AC-4 satisfied: Selected package included in booking submission and visible in summary</done>
|
||||
</task>
|
||||
|
||||
<task type="checkpoint:human-verify" gate="blocking">
|
||||
<what-built>Protection package cards with tiered pricing, radio selection, date-driven recalculation, and booking integration</what-built>
|
||||
<how-to-verify>
|
||||
1. Otwórz stronę carei.pagedev.pl i kliknij rezerwację
|
||||
2. Wybierz segment, lokalizację i daty na 2 dni → sprawdź cenę pakietów (powinna być cena minimalna)
|
||||
3. Zmień daty na 7 dni → cena powinna się przeliczyć (cena za dobę × 7)
|
||||
4. Zmień daty na 20 dni → cena powinna być stała (maksymalna)
|
||||
5. Kliknij SOFT → zaznaczony, kliknij PREMIUM → SOFT odznaczony, PREMIUM zaznaczony
|
||||
6. Kliknij zaznaczony ponownie → odznaczony (brak pakietu)
|
||||
7. Wybierz pakiet i przejdź do podsumowania → pakiet widoczny z ceną
|
||||
8. Sprawdź na mobile → karty jedna pod drugą
|
||||
</how-to-verify>
|
||||
<resume-signal>Type "approved" to continue, or describe issues to fix</resume-signal>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<boundaries>
|
||||
|
||||
## DO NOT CHANGE
|
||||
- class-rest-proxy.php (API proxy unchanged — data comes from existing pricelist endpoint)
|
||||
- class-admin-panel.php (admin storage — protection items stored same as extras via priceItems)
|
||||
- class-softra-api.php (Softra API client — no changes needed)
|
||||
- Abroad section logic (wyjazd zagraniczny)
|
||||
- Hero search form widget
|
||||
|
||||
## SCOPE LIMITS
|
||||
- No new API endpoints — protection packages come from existing pricelist/additionalItems
|
||||
- No backend pricing logic — all tier calculation is client-side based on API item fields
|
||||
- No new admin UI for managing packages — data managed in Softra system
|
||||
- Description/zakres usług text comes from API item.description — no hardcoded descriptions
|
||||
|
||||
</boundaries>
|
||||
|
||||
<verification>
|
||||
Before declaring plan complete:
|
||||
- [ ] Protection packages display with correct tiered pricing for all 3 duration ranges
|
||||
- [ ] Selection is mutually exclusive with deselect capability
|
||||
- [ ] Prices recalculate on date change
|
||||
- [ ] Selected package included in booking submission priceItems
|
||||
- [ ] Summary overlay shows selected package with price
|
||||
- [ ] No regressions in existing extras or abroad sections
|
||||
- [ ] Responsive layout works on mobile
|
||||
- [ ] All acceptance criteria met
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- All tasks completed
|
||||
- All verification checks pass
|
||||
- No errors or warnings introduced
|
||||
- Protection packages display correctly for all 3 pricing tiers
|
||||
- Booking flow works end-to-end with selected package
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
After completion, create `.paul/phases/13-protection-packages/13-01-SUMMARY.md`
|
||||
</output>
|
||||
95
.paul/phases/14-mobile-modal-fix/14-01-PLAN.md
Normal file
95
.paul/phases/14-mobile-modal-fix/14-01-PLAN.md
Normal file
@@ -0,0 +1,95 @@
|
||||
---
|
||||
phase: 14-mobile-modal-fix
|
||||
plan: 01
|
||||
type: execute
|
||||
wave: 1
|
||||
depends_on: []
|
||||
files_modified:
|
||||
- wp-content/plugins/carei-reservation/assets/js/carei-reservation.js
|
||||
autonomous: true
|
||||
delegation: off
|
||||
---
|
||||
|
||||
<objective>
|
||||
## Goal
|
||||
Fix: modal rezerwacji nie otwiera się na mobile/tablet — przenieść overlay do document.body aby uniezależnić od ukrytych sekcji Elementor.
|
||||
|
||||
## Purpose
|
||||
Formularz rezerwacji był niefunkcjonalny na urządzeniach mobilnych i tabletach — klienci nie mogli złożyć rezerwacji z telefonu.
|
||||
|
||||
## Output
|
||||
- Modal otwiera się poprawnie na mobile i tablet
|
||||
- Brak regresji na desktop
|
||||
</objective>
|
||||
|
||||
<context>
|
||||
## Project Context
|
||||
@.paul/PROJECT.md
|
||||
|
||||
## Source Files
|
||||
@wp-content/plugins/carei-reservation/assets/js/carei-reservation.js
|
||||
</context>
|
||||
|
||||
<acceptance_criteria>
|
||||
|
||||
## AC-1: Modal otwiera się na mobile
|
||||
```gherkin
|
||||
Given strona carei.pagedev.pl otwarta na urządzeniu mobilnym (375px)
|
||||
When użytkownik wypełni hero search form i kliknie "Złóż zapytanie o rezerwację"
|
||||
Then modal rezerwacji otwiera się na pełnym ekranie z pre-wypełnionymi danymi
|
||||
```
|
||||
|
||||
## AC-2: Brak regresji desktop
|
||||
```gherkin
|
||||
Given strona otwarta na desktopie
|
||||
When użytkownik kliknie przycisk otwarcia modala
|
||||
Then modal otwiera się jak dotychczas (centered overlay)
|
||||
```
|
||||
|
||||
</acceptance_criteria>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 1: Przenieś overlay do document.body w initRefs()</name>
|
||||
<files>wp-content/plugins/carei-reservation/assets/js/carei-reservation.js</files>
|
||||
<action>
|
||||
W funkcji initRefs(), po querySelector overlay, dodać:
|
||||
if (overlay && overlay.parentElement !== document.body) {
|
||||
document.body.appendChild(overlay);
|
||||
}
|
||||
Przyczyna: sekcja Elementor (elementor-element-a7629b1) ma klasy
|
||||
elementor-hidden-tablet elementor-hidden-mobile, co ustawia display:none
|
||||
na rodzicu. Modal position:fixed wewnątrz ukrytego rodzica ma zerowe wymiary.
|
||||
</action>
|
||||
<verify>Otworzyć stronę na mobile (375px), wypełnić formularz, kliknąć submit — modal się otwiera</verify>
|
||||
<done>AC-1, AC-2 satisfied</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<boundaries>
|
||||
## DO NOT CHANGE
|
||||
- CSS modala (carei-reservation.css)
|
||||
- Elementor widget PHP
|
||||
- Logika formularza i API
|
||||
|
||||
## SCOPE LIMITS
|
||||
- Tylko fix JS — jedna linijka appendChild
|
||||
</boundaries>
|
||||
|
||||
<verification>
|
||||
- [ ] Modal otwiera się na mobile (375px)
|
||||
- [ ] Modal otwiera się na tablet (768px)
|
||||
- [ ] Modal otwiera się na desktop (1440px)
|
||||
- [ ] Pre-fill z hero search form działa na mobile
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- Modal działa na wszystkich breakpointach
|
||||
- Brak regresji
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
After completion, create `.paul/phases/14-mobile-modal-fix/14-01-SUMMARY.md`
|
||||
</output>
|
||||
108
.paul/phases/14-mobile-modal-fix/14-01-SUMMARY.md
Normal file
108
.paul/phases/14-mobile-modal-fix/14-01-SUMMARY.md
Normal file
@@ -0,0 +1,108 @@
|
||||
---
|
||||
phase: 14-mobile-modal-fix
|
||||
plan: 01
|
||||
subsystem: ui
|
||||
tags: [mobile, elementor, modal, responsive]
|
||||
|
||||
requires:
|
||||
- phase: none
|
||||
provides: n/a
|
||||
provides:
|
||||
- Modal rezerwacji działa na mobile/tablet
|
||||
affects: []
|
||||
|
||||
tech-stack:
|
||||
added: []
|
||||
patterns: [overlay-to-body pattern for Elementor hidden sections]
|
||||
|
||||
key-files:
|
||||
created: []
|
||||
modified:
|
||||
- wp-content/plugins/carei-reservation/assets/js/carei-reservation.js
|
||||
|
||||
key-decisions:
|
||||
- "appendChild do body zamiast CSS override — niezależne od przyszłych zmian widoczności sekcji Elementor"
|
||||
|
||||
patterns-established:
|
||||
- "Modal overlay musi być dzieckiem body, nie sekcji Elementor (unika problemów z hidden responsive)"
|
||||
|
||||
duration: 15min
|
||||
started: 2026-04-09T21:50:00Z
|
||||
completed: 2026-04-09T22:05:00Z
|
||||
---
|
||||
|
||||
# Phase 14 Plan 01: Mobile Modal Fix Summary
|
||||
|
||||
**Fix: modal rezerwacji przeniesiony do document.body — działa na mobile/tablet niezależnie od widoczności sekcji Elementor.**
|
||||
|
||||
## Performance
|
||||
|
||||
| Metric | Value |
|
||||
|--------|-------|
|
||||
| Duration | ~15min |
|
||||
| Started | 2026-04-09 |
|
||||
| Completed | 2026-04-09 |
|
||||
| Tasks | 1 completed |
|
||||
| Files modified | 1 |
|
||||
|
||||
## Acceptance Criteria Results
|
||||
|
||||
| Criterion | Status | Notes |
|
||||
|-----------|--------|-------|
|
||||
| AC-1: Modal otwiera się na mobile | Pass | Zweryfikowane Playwright 375×812 |
|
||||
| AC-2: Brak regresji desktop | Pass | Desktop bez zmian — overlay i tak jest position:fixed |
|
||||
|
||||
## Accomplishments
|
||||
|
||||
- Modal rezerwacji działa na mobile i tablet — klienci mogą składać rezerwacje z telefonu
|
||||
- Pre-fill z hero search form działa poprawnie na mobile po fixie
|
||||
|
||||
## Root Cause
|
||||
|
||||
Sekcja Elementor (`elementor-element-a7629b1`, klasa `header-box`) miała ustawione `elementor-hidden-tablet elementor-hidden-mobile` w edytorze Elementor. To powodowało `display:none` na rodzicu modala. Modal `position:fixed` wewnątrz ukrytego elementu miał `width:0, height:0` — był w DOM, miał klasę `is-open`, ale był niewidoczny.
|
||||
|
||||
## Fix
|
||||
|
||||
Jedna linijka w `initRefs()` (carei-reservation.js:90):
|
||||
```js
|
||||
if (overlay && overlay.parentElement !== document.body) {
|
||||
document.body.appendChild(overlay);
|
||||
}
|
||||
```
|
||||
|
||||
Przeniesienie overlay do `<body>` gwarantuje że `position:fixed` działa względem viewport, niezależnie od struktury Elementor.
|
||||
|
||||
## Files Created/Modified
|
||||
|
||||
| File | Change | Purpose |
|
||||
|------|--------|---------|
|
||||
| `wp-content/plugins/carei-reservation/assets/js/carei-reservation.js` | Modified | appendChild overlay do body w initRefs() |
|
||||
|
||||
## Decisions Made
|
||||
|
||||
| Decision | Rationale | Impact |
|
||||
|----------|-----------|--------|
|
||||
| appendChild do body (nie CSS override) | Niezależne od przyszłych zmian responsive w Elementor | Trwały fix, zero ryzyka regresji |
|
||||
|
||||
## Deviations from Plan
|
||||
|
||||
None — plan wykonany dokładnie jak opisany.
|
||||
|
||||
## Issues Encountered
|
||||
|
||||
None.
|
||||
|
||||
## Next Phase Readiness
|
||||
|
||||
**Ready:**
|
||||
- Phase 13 (Pakiety ochronne) — BLOCKED, czeka na potwierdzenie klienta
|
||||
|
||||
**Concerns:**
|
||||
- None
|
||||
|
||||
**Blockers:**
|
||||
- Phase 13 zablokowana — potrzebna odpowiedź klienta o źródle danych cenowych
|
||||
|
||||
---
|
||||
*Phase: 14-mobile-modal-fix, Plan: 01*
|
||||
*Completed: 2026-04-09*
|
||||
Reference in New Issue
Block a user