diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..9d8fa1e Binary files /dev/null and b/.DS_Store differ diff --git a/.paul/PROJECT.md b/.paul/PROJECT.md index 4b7ef46..7906521 100644 --- a/.paul/PROJECT.md +++ b/.paul/PROJECT.md @@ -44,7 +44,6 @@ Plugin Elementor do rezerwacji samochodu na stronie carei.pagedev.pl, zintegrowa ## Out of Scope (backlog) - Ubezpieczenie (pakiet Soft/Premium) — czeka na API Softra -- Wyjazd zagraniczny (lista krajów + ceny) — czeka na API Softra - Eksport CSV/PDF rezerwacji - Email notyfikacje diff --git a/.paul/ROADMAP.md b/.paul/ROADMAP.md index 18aa4d5..32d17bd 100644 --- a/.paul/ROADMAP.md +++ b/.paul/ROADMAP.md @@ -19,6 +19,18 @@ Edge cases (token retry, timeout, network errors), animacje przejść form↔sum ### Phase 5: Admin Panel — Historia Formularzy ✅ Complete CPT `carei_reservation` z automatycznym zapisem po booking, lista z kolumnami i filtrem statusu, meta box szczegółów, system statusów nowe/przeczytane/zrealizowane, auto-mark-read. +--- + +## Milestone v0.2: Wyjazd Zagraniczny + +**Goal:** Wydzielenie pozycji "WYJAZD ZA GRANICĘ" z opcji dodatkowych do dedykowanej sekcji z wyszukiwarką krajów (zgodnie z Figmą). + +**Status:** In progress + +### Phase 6: Wyjazd zagraniczny — sekcja + wyszukiwarka krajów 🔄 Planning +Sekcja "Wyjazd zagraniczny" z checkboxem toggle, wyszukiwarką krajów z flagami/cenami, dodawanie/usuwanie krajów, integracja z API submit. Design z Figmy (node 32-397, 122:1054, 122:1091, 123:1195). + +--- + ### Backlog (do realizacji gdy API będzie gotowe) -- **Wyjazd zagraniczny:** Checkbox "Planuję trasę poza granicę Polski" + wyszukiwarka krajów z cenami. Wymaga endpointu listy krajów i cennika per kraj w API Softra. - **Ubezpieczenie:** Sekcja "Pakiet ochrony Soft/Premium" jak na Figmie. Wymaga dedykowanych pozycji ubezpieczeniowych w API pricelist. diff --git a/.paul/STATE.md b/.paul/STATE.md index 4d65271..3a84a1a 100644 --- a/.paul/STATE.md +++ b/.paul/STATE.md @@ -2,35 +2,33 @@ ## Current Position -Milestone: v0.1 Formularz Rezerwacji MVP — COMPLETE -Phase: 5 of 5 (Admin Panel — Historia Formularzy) — Complete -Plan: 05-01 — Complete -Status: Milestone v0.1 complete — all 5 phases finished -Last activity: 2026-03-25 — Phase 5 unified, milestone complete +Milestone: v0.2 Wyjazd Zagraniczny +Phase: 6 of 6 (Wyjazd zagraniczny — sekcja + wyszukiwarka krajów) — Planning +Plan: 06-01 created, awaiting approval +Status: PLAN created, ready for APPLY +Last activity: 2026-03-30 — Created .paul/phases/06-wyjazd-zagraniczny/06-01-PLAN.md Progress: -- Milestone: [██████████] 100% ✅ -- Phase 1: [██████████] 100% ✅ -- Phase 2: [██████████] 100% ✅ -- Phase 3: [██████████] 100% ✅ -- Phase 4: [██████████] 100% ✅ -- Phase 5: [██████████] 100% ✅ +- Milestone v0.1: [██████████] 100% ✅ +- Milestone v0.2: [░░░░░░░░░░] 0% +- Phase 6: [░░░░░░░░░░] 0% ## Loop Position Current loop state: ``` PLAN ──▶ APPLY ──▶ UNIFY - ✓ ✓ ✓ [Loop complete — milestone v0.1 finished] + ✓ ○ ○ [Plan created, awaiting approval] ``` ## Session Continuity -Last session: 2026-03-25 -Stopped at: Milestone v0.1 complete — all phases unified -Next action: /paul:complete-milestone or start next milestone -Resume file: .paul/ROADMAP.md +Last session: 2026-03-30 +Stopped at: Plan 06-01 created +Next action: Review and approve plan, then run /paul:apply .paul/phases/06-wyjazd-zagraniczny/06-01-PLAN.md +Resume file: .paul/phases/06-wyjazd-zagraniczny/06-01-PLAN.md Resume context: -- All 5 phases complete: skeleton, form UI, booking flow, polish, admin panel -- Plugin fully functional: formularz → API Softra → admin panel -- Backlog: ubezpieczenie + wyjazd zagraniczny (czeka na API) +- v0.1 complete (5 phases) +- v0.2 Phase 6: wyjazd zagraniczny — wydzielenie WYJAZD ZA GRANICĘ z extras do dedykowanej sekcji z wyszukiwarką krajów +- Dane krajów z istniejącego pricelist API (additionalItems z nazwą WYJAZD ZA GRANIC...) +- Design z Figmy: checkbox toggle + wyszukiwarka z flagami/cenami + karty krajów diff --git a/.paul/phases/06-wyjazd-zagraniczny/06-01-PLAN.md b/.paul/phases/06-wyjazd-zagraniczny/06-01-PLAN.md new file mode 100644 index 0000000..bf05ca7 --- /dev/null +++ b/.paul/phases/06-wyjazd-zagraniczny/06-01-PLAN.md @@ -0,0 +1,282 @@ +--- +phase: 06-wyjazd-zagraniczny +plan: 01 +type: execute +wave: 1 +depends_on: [] +files_modified: + - 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 +autonomous: false +--- + + +## Goal +Wydzielić pozycje "WYJAZD ZA GRANICĘ ..." z sekcji "Opcje dodatkowe" do osobnej sekcji "Wyjazd zagraniczny" z checkboxem "Planuję trasę poza granicę Polski" i interaktywną wyszukiwarką krajów zgodną z Figmą. + +## Purpose +Użytkownik chcący zarezerwować samochód z wyjazdem za granicę powinien mieć dedykowaną, intuicyjną sekcję zamiast listy checkboxów zagubionych wśród opcji dodatkowych. Design Figmy przewiduje wyszukiwarkę krajów z flagami, cenami i możliwością dodawania/usuwania — lepsze UX niż surowe checkboxy. + +## Output +- Sekcja "Wyjazd zagraniczny" z checkboxem toggle +- Wyszukiwarka krajów z 3 stanami (default, active, z wybranymi) +- Karty krajów z flagą, nazwą, ceną, przyciskiem +/× +- Wybrane kraje trafiają do API jako extras przy submit + + + +## Project Context +@.paul/PROJECT.md +@.paul/ROADMAP.md +@.paul/STATE.md + +## Prior Work +@.paul/phases/02-form-ui/02-01-SUMMARY.md — struktura formularza (opcje dodatkowe, loadExtras) + +## Source Files +@wp-content/plugins/carei-reservation/includes/class-elementor-widget.php — HTML formularza (linia 126-143: extras wrapper) +@wp-content/plugins/carei-reservation/assets/js/carei-reservation.js — loadExtras() linia 315-346, buildExtraCard() linia 348-364, getSelectedExtrasForApi() linia 588-604 +@wp-content/plugins/carei-reservation/assets/css/carei-reservation.css — style extra-card (linia 390-429) + +## Design Reference +@docs/figma-formularz/README.md — sekcja "Wyjazd zagraniczny" (linia 113-117) + wyszukiwarka krajów (linia 146-177) +@docs/figma-formularz/input-wyszukiwarka-krajow.jsx — stany wyszukiwarki, karty krajów, kolory +@docs/figma-formularz/screenshot-popup-desktop.png +@docs/figma-formularz/screenshot-input-kraje-stany.png + + + +No SPECIAL-FLOWS.md — skills section omitted. + + + + +## AC-1: Pozycje WYJAZD ZA GRANICĘ nie pojawiają się w Opcjach dodatkowych +```gherkin +Given formularz z załadowanym cennikiem z API +When pricelist zawiera pozycje zaczynające się od "WYJAZD ZA GRANIC" +Then te pozycje NIE wyświetlają się w sekcji "Opcje dodatkowe" +And wyświetlają się TYLKO w sekcji "Wyjazd zagraniczny" +``` + +## AC-2: Sekcja "Wyjazd zagraniczny" z checkboxem toggle +```gherkin +Given formularz z załadowanymi extras +When użytkownik widzi sekcję "Wyjazd zagraniczny" +Then widoczny jest checkbox "Planuję trasę poza granicę Polski" +And wyszukiwarka krajów jest UKRYTA dopóki checkbox nie jest zaznaczony +When użytkownik zaznacza checkbox +Then wyszukiwarka krajów pojawia się z animacją +When użytkownik odznacza checkbox +Then wyszukiwarka znika i wybrane kraje się czyszczą +``` + +## AC-3: Wyszukiwarka krajów — wyszukiwanie i dodawanie +```gherkin +Given checkbox "Planuję trasę..." jest zaznaczony +When użytkownik widzi domyślny stan wyszukiwarki +Then widzi ikonę "+" i tekst "Wyszukaj i dodaj kraj na trasie" +When użytkownik klika pole i wpisuje tekst (np. "Nie") +Then pod polem pojawiają się karty krajów pasujących do frazy +And każda karta ma: flagę (emoji/obraz), nazwę kraju, cenę "od X do Y zł", przycisk "+" +When użytkownik klika "+" na karcie kraju +Then kraj zostaje dodany do listy wybranych (karta zmienia styl na selected, "+" → "×") +``` + +## AC-4: Wybrane kraje trafiają do API przy submit +```gherkin +Given użytkownik wybrał kraje w wyszukiwarce (np. Niemcy, Czechy) +When formularz jest submitowany +Then getSelectedExtrasForApi() zawiera pozycje z pricelist odpowiadające wybranym krajom +And pricing summary i booking zawierają te pozycje +``` + +## AC-5: Design zgodny z Figmą +```gherkin +Given sekcja "Wyjazd zagraniczny" jest wyświetlona +Then separator "Wyjazd zagraniczny" ma taki sam styl jak inne separatory +And karty krajów mają: border-radius 8px, nazwę w #2F2482 Albert Sans SemiBold 15px, cenę w #505050 +And wybrany kraj ma tło rgba(47,36,130,0.05), border rgba(47,36,130,0.2) +And pole wyszukiwarki ma 48px wysokości, border-radius 8px +``` + + + + + + + Task 1: HTML sekcji "Wyjazd zagraniczny" + filtrowanie w JS + wp-content/plugins/carei-reservation/includes/class-elementor-widget.php, wp-content/plugins/carei-reservation/assets/js/carei-reservation.js + + **PHP (class-elementor-widget.php):** + Dodać nową sekcję HTML PRZED sekcją "Opcje dodatkowe" (między linią 135 "Ubezpieczenie" a linią 136 "Opcje dodatkowe"): + + ``` +
Wyjazd zagraniczny
+
+ + +
+ ``` + + **JS (carei-reservation.js) — modyfikacja loadExtras():** + W pętli `items.forEach()` (linia 329-341) dodać trzecią kategorię: + - Jeśli `name` zaczyna się od "wyjazd za granic" → `abroadItems.push(item)` (NIE do extraItems) + - Parsować nazwę kraju z item.name (np. "WYJAZD ZA GRANICĘ - NIEMCY" → "Niemcy") + - Przechowywać `abroadItems` w zmiennej modułowej (dostępnej dla wyszukiwarki) + + Po załadowaniu — NIE renderować abroadItems jako checkboxów. Zamiast tego, zachować dane w pamięci JS do użycia przez wyszukiwarkę. + + Avoid: Nie usuwać istniejącej logiki insurance/extras — tylko dodać nową kategorię w `forEach`. +
+ + 1. Otworzyć formularz, wybrać segment i oddział + 2. Pozycje "WYJAZD ZA GRANICĘ ..." NIE pojawiają się w "Opcje dodatkowe" + 3. Widoczna sekcja "Wyjazd zagraniczny" z checkboxem + + AC-1 satisfied: pozycje WYJAZD nie lądują w Opcjach dodatkowych. AC-2 partially: sekcja HTML istnieje. +
+ + + Task 2: Wyszukiwarka krajów — interakcje JS + style CSS + wp-content/plugins/carei-reservation/assets/js/carei-reservation.js, wp-content/plugins/carei-reservation/assets/css/carei-reservation.css + + **JS — nowy blok "Abroad Country Search":** + + 1. **Toggle checkbox:** Listener na `#carei-abroad-toggle`: + - checked → `#carei-abroad-search` display:block (slide down) + - unchecked → hide + wyczyść selected countries + + 2. **Mapowanie krajów na flagi:** Obiekt `COUNTRY_FLAGS` z emoji flagami dla typowych krajów (Niemcy 🇩🇪, Czechy 🇨🇿, Norwegia 🇳🇴, etc.). Fallback: flaga 🏳️. + + 3. **Wyszukiwanie:** Input event na `#carei-abroad-input`: + - Filtruj `abroadItems` po nazwie kraju (case-insensitive, includes) + - Renderuj karty wyników w `#carei-abroad-results` (2 kolumny CSS grid) + - Karta: flaga (emoji 24px) + nazwa kraju + cena "od X do Y zł" + przycisk "+" + - Już wybrane kraje w wynikach → styl selected (bg/border Figma) + przycisk "×" + - Puste pole = nie pokazuj wyników (stan default) + + 4. **Dodawanie kraju ("+"):** + - Dodaj do `selectedCountries` Map (id → item) + - Przerenderuj wyniki (kraj zmienia się na selected) + - Przerenderuj `#carei-abroad-selected` — lista wybranych pod wyszukiwarką + + 5. **Usuwanie kraju ("×"):** + - Usuń z `selectedCountries` + - Przerenderuj wyniki i selected + + 6. **Integracja z getSelectedExtrasForApi():** + Zmodyfikować `getSelectedExtrasForApi()` (linia 588-604) — oprócz `extras[]` checkboxów, dołączyć kraje z `selectedCountries` Map w tym samym formacie: + ```js + selectedCountries.forEach(function(item) { + items.push({ id: item.id || item.code, name: item.name, ... }); + }); + ``` + + **CSS — nowe reguły `.carei-abroad`:** + + Zgodne z Figmą (docs/figma-formularz): + - `.carei-abroad__input-wrap`: height 48px, border-radius 8px, border 1px #D0D0D0, flex, gap 8px, padding 0 16px, align-items center + - `.carei-abroad__input`: no border, flex 1, Albert Sans SemiBold 15px, color #2F2482, placeholder color #C7C7C7 + - `.carei-abroad__plus-icon`: 16px, color #2F2482 + - `.carei-abroad__results`: display grid, grid-template-columns 1fr 1fr, gap 8px, margin-top 8px + - `.carei-abroad__card`: height 56px, border-radius 8px, border 1px rgba(0,0,0,0.1), flex, align-items center, padding 0 12px, gap 8px + - `.carei-abroad__card--selected`: background rgba(47,36,130,0.05), border-color rgba(47,36,130,0.2) + - `.carei-abroad__flag`: width 24px, height 24px, border-radius 50%, font-size 20px, line-height 24px, text-align center + - `.carei-abroad__name`: Albert Sans SemiBold 15px, color #2F2482, flex 1 + - `.carei-abroad__price`: text-align right, color #505050 (format: "od" 10px + wartość 14px + "do" 10px + wartość 14px + "zł") + - `.carei-abroad__action`: 16px button, cursor pointer, no bg/border + - `.carei-abroad__selected`: margin-top 8px, flex column, gap 8px + + Mobile (max-width 600px): + - `.carei-abroad__results`: grid-template-columns 1fr (1 kolumna) + + Avoid: Nie modyfikować istniejących stylów `.carei-form__extra-card`. Nowe klasy `.carei-abroad__*`. + + + 1. Zaznacz checkbox "Planuję trasę..." → wyszukiwarka się pojawia + 2. Wpisz "Niem" → pojawia się karta Niemcy z flagą, ceną, "+" + 3. Kliknij "+" → Niemcy zmienia styl na selected, pojawia się w liście wybranych z "×" + 4. Kliknij "×" → kraj usunięty + 5. Odznacz checkbox → wyszukiwarka znika, wybrane kraje się czyszczą + 6. Submit z wybranym krajem → pricing summary zawiera pozycję WYJAZD + + AC-2, AC-3, AC-4, AC-5 satisfied: pełna interakcja wyszukiwarki, integracja z API, design Figma + + + + Sekcja "Wyjazd zagraniczny" z wyszukiwarką krajów w popup rezerwacji + + 1. Otwórz stronę z formularzem rezerwacji + 2. Wybierz segment i oddział (żeby załadować extras) + 3. Sprawdź: pozycje "WYJAZD ZA GRANICĘ ..." NIE widać w "Opcje dodatkowe" + 4. Sprawdź: widoczna sekcja "Wyjazd zagraniczny" z checkboxem + 5. Zaznacz "Planuję trasę poza granicę Polski" → wyszukiwarka się pojawia + 6. Wpisz fragment nazwy kraju → karty z flagami i cenami + 7. Dodaj kraj (+) → karta się podświetla, pojawia się w liście wybranych + 8. Usuń kraj (×) → znika z wybranych + 9. Odznacz checkbox → wszystko się czyści + 10. Zaznacz, dodaj kraje, submit → w summary widać pozycje WYJAZD + 11. Sprawdź responsywność na mobile (1 kolumna kart) + 12. Porównaj wygląd z Figmą (kolory, fonty, spacing) + + Type "approved" to continue, or describe issues to fix + + +
+ + + +## DO NOT CHANGE +- wp-content/plugins/carei-reservation/includes/class-softra-api.php (API client — nie wymaga zmian) +- wp-content/plugins/carei-reservation/includes/class-rest-proxy.php (REST proxy — nie wymaga zmian) +- wp-content/plugins/carei-reservation/includes/class-admin-panel.php (Admin panel — osobna funkcjonalność) +- Istniejąca logika ubezpieczeń (filtrowanie ubezp/ochrony w loadExtras) +- Istniejąca logika buildExtraCard() — nie modyfikować, nowe karty mają osobny builder +- Istniejące style .carei-form__extra-card — nie modyfikować + +## SCOPE LIMITS +- Nie dodawać prawdziwych obrazów flag (emoji wystarczą na MVP) +- Nie tworzyć osobnego API endpoint do krajów — dane z istniejącego pricelist +- Nie modyfikować logiki booking/confirm — tylko getSelectedExtrasForApi() +- Nie dodawać walidacji "minimum 1 kraj wybrany" — checkbox sam w sobie jest opcjonalny +- Summary overlay — wystarczy że kraje pojawią się jako standardowe pozycje extras + + + + +Before declaring plan complete: +- [ ] Pozycje "WYJAZD ZA GRANIC..." nie pojawiają się w "Opcje dodatkowe" +- [ ] Sekcja "Wyjazd zagraniczny" wyświetla się z separatorem i checkboxem +- [ ] Toggle checkbox pokazuje/ukrywa wyszukiwarkę +- [ ] Wyszukiwanie filtruje kraje w real-time +- [ ] Dodawanie/usuwanie krajów działa poprawnie +- [ ] Wybrane kraje trafiają do getSelectedExtrasForApi() +- [ ] Pricing summary zawiera pozycje WYJAZD dla wybranych krajów +- [ ] Mobile: karty w 1 kolumnie +- [ ] Brak błędów w konsoli JS + + + +- All tasks completed +- All verification checks pass +- No errors or warnings introduced +- Design wizualnie zgodny z Figmą (kolory, spacing, typografia) +- Istniejąca funkcjonalność (ubezpieczenie, opcje dodatkowe, booking flow) nienaruszona + + + +After completion, create `.paul/phases/06-wyjazd-zagraniczny/06-01-SUMMARY.md` + diff --git a/.vscode/ftp-kr.sync.cache.json b/.vscode/ftp-kr.sync.cache.json index 1817d6f..9ba4c58 100644 --- a/.vscode/ftp-kr.sync.cache.json +++ b/.vscode/ftp-kr.sync.cache.json @@ -123,16 +123,22 @@ }, "handoffs": { "archive": { + "HANDOFF-2026-03-25.md": { + "type": "-", + "size": 3911, + "lmtime": 1774394933000, + "modified": false + }, "HANDOFF-2026-03-25-phase3.md": { "type": "-", "size": 3672, "lmtime": 1774440974369, "modified": false }, - "HANDOFF-2026-03-25.md": { + "HANDOFF-2026-03-25-phase5.md": { "type": "-", - "size": 3911, - "lmtime": 1774394933000, + "size": 4436, + "lmtime": 1774453504000, "modified": false } } @@ -181,25 +187,31 @@ "size": 11298, "lmtime": 1774447717369, "modified": false + }, + "05-01-SUMMARY.md": { + "type": "-", + "size": 4697, + "lmtime": 1774456664870, + "modified": false } } }, "PROJECT.md": { "type": "-", - "size": 2025, - "lmtime": 0, + "size": 3246, + "lmtime": 1774456701930, "modified": false }, "ROADMAP.md": { "type": "-", - "size": 1906, - "lmtime": 1774447405669, + "size": 1862, + "lmtime": 1774456710745, "modified": false }, "STATE.md": { "type": "-", - "size": 1376, - "lmtime": 1774453515616, + "size": 1212, + "lmtime": 1774456724909, "modified": false } }, @@ -301,8 +313,8 @@ "includes": { "class-admin-panel.php": { "type": "-", - "size": 18994, - "lmtime": 1774448051188, + "size": 19203, + "lmtime": 1774456454972, "modified": false }, "class-elementor-widget.php": { diff --git a/.vscode/sftp.json b/.vscode/sftp.json new file mode 100644 index 0000000..3f86b62 --- /dev/null +++ b/.vscode/sftp.json @@ -0,0 +1,11 @@ +{ + "name": "My Server", + "host": "localhost", + "protocol": "sftp", + "port": 22, + "username": "username", + "remotePath": "/", + "uploadOnSave": false, + "useTempFile": false, + "openSsh": false +} diff --git a/wp-admin/.DS_Store b/wp-admin/.DS_Store new file mode 100644 index 0000000..676ccd7 Binary files /dev/null and b/wp-admin/.DS_Store differ diff --git a/wp-content/.DS_Store b/wp-content/.DS_Store new file mode 100644 index 0000000..611bffa Binary files /dev/null and b/wp-content/.DS_Store differ diff --git a/wp-content/languages/.DS_Store b/wp-content/languages/.DS_Store new file mode 100644 index 0000000..8f047d7 Binary files /dev/null and b/wp-content/languages/.DS_Store differ diff --git a/wp-content/plugins/.DS_Store b/wp-content/plugins/.DS_Store new file mode 100644 index 0000000..b9b4f1c Binary files /dev/null and b/wp-content/plugins/.DS_Store differ diff --git a/wp-content/plugins/carei-reservation/assets/css/carei-reservation.css b/wp-content/plugins/carei-reservation/assets/css/carei-reservation.css index cad203c..ed4fac7 100644 --- a/wp-content/plugins/carei-reservation/assets/css/carei-reservation.css +++ b/wp-content/plugins/carei-reservation/assets/css/carei-reservation.css @@ -77,6 +77,7 @@ width: 100%; max-height: 90vh; overflow-y: auto; + overflow-x: hidden; padding: 40px 48px; position: relative; font-family: var(--carei-font); @@ -93,13 +94,13 @@ background: none; border: none; cursor: pointer; - color: var(--carei-gray); + color: #B0B0B0; line-height: 1; padding: 4px; transition: color 0.2s; } .carei-modal-close:hover { - color: var(--carei-blue); + color: #808080; } .carei-modal-title { font-family: var(--carei-font); @@ -140,7 +141,7 @@ } .carei-form__row { display: grid; - grid-template-columns: 1fr 1fr; + grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--carei-gap-inner); } .carei-form__row--top { @@ -217,6 +218,137 @@ color: var(--carei-gray); } +/* Floating label for text inputs */ +.carei-form__float-wrap { + position: relative; + height: var(--carei-input-h); + background: var(--carei-white); + border: 1px solid transparent; + border-radius: var(--carei-radius); + transition: border-color 0.2s; +} +.carei-form__float-wrap:focus-within { + border-color: var(--carei-blue); +} +.carei-form__float-label { + position: absolute; + left: 16px; + top: 50%; + transform: translateY(-50%); + font-family: var(--carei-font); + font-weight: 400; + font-size: 15px; + color: var(--carei-gray); + pointer-events: none; + transition: top 0.15s, font-size 0.15s, transform 0.15s; + z-index: 1; +} +.carei-form__float-label--static { + position: static; + transform: none; + font-size: 11px; + padding: 6px 16px 0; + display: block; +} +.carei-form__input--float { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + border: none !important; + background: transparent !important; + padding: 0 16px !important; + z-index: 2; +} +.carei-form__input--float:not(:placeholder-shown) ~ .carei-form__float-label, +.carei-form__input--float:focus ~ .carei-form__float-label { + top: 8px; + transform: none; + font-size: 10px; +} +.carei-form__input--float:not(:placeholder-shown), +.carei-form__input--float:focus { + padding-top: 16px !important; + padding-bottom: 0 !important; +} + +/* Date input with floating label */ +.carei-form__date-wrap { + position: relative; + height: var(--carei-input-h); + background: var(--carei-white); + border: 1px solid transparent; + border-radius: var(--carei-radius); + transition: border-color 0.2s; + cursor: pointer; +} +.carei-form__date-wrap:focus-within { + border-color: var(--carei-blue); +} +.carei-form__date-icon { + position: absolute; + left: 12px; + top: 50%; + transform: translateY(-50%); + color: var(--carei-blue); + pointer-events: none; + z-index: 3; +} +.carei-form__date-label { + position: absolute; + left: 36px; + top: 50%; + transform: translateY(-50%); + font-family: var(--carei-font); + font-weight: 400; + font-size: 15px; + color: var(--carei-gray); + pointer-events: none; + transition: top 0.15s, font-size 0.15s, transform 0.15s; + z-index: 3; +} +.carei-form__date-wrap.has-value .carei-form__date-label { + top: 7px; + transform: none; + font-size: 10px; +} +.carei-form__input--date { + border: none !important; + background: transparent !important; + height: 100% !important; + width: 100%; + padding: 0 12px 0 36px !important; + font-size: 14px; + position: relative; + z-index: 2; +} +/* Pusty: ukryj natywny tekst i zablokuj interakcję z polami wewnętrznymi */ +.carei-form__input--date.is-empty { + color: transparent !important; + z-index: 1; +} +.carei-form__input--date.is-empty::-webkit-datetime-edit { + color: transparent; + -webkit-user-select: none; + user-select: none; +} +/* Wypełniony: przesuń wartość w dół pod label */ +.carei-form__date-wrap.has-value .carei-form__input--date { + padding-top: 14px !important; + padding-bottom: 0 !important; +} +/* Ukryj natywną ikonę kalendarza przeglądarki */ +.carei-form__input--date::-webkit-calendar-picker-indicator { + opacity: 0; + position: absolute; + left: 0; + top: 0; + width: 100%; + height: 100%; + cursor: pointer; +} + /* Select wrapper */ .carei-form__select-wrap { position: relative; @@ -263,16 +395,30 @@ ═══════════════════════════════════════════ */ .carei-form__phone-wrap { display: flex; - align-items: center; + flex-direction: column; background: var(--carei-white); border-radius: var(--carei-radius); border: 1px solid transparent; transition: border-color 0.2s; height: var(--carei-input-h); + padding: 4px 0 0; } .carei-form__phone-wrap:focus-within { border-color: var(--carei-blue); } +.carei-form__phone-label { + font-family: var(--carei-font); + font-weight: 400; + font-size: 10px; + color: var(--carei-gray); + padding: 0 16px; + line-height: 1; +} +.carei-form__phone-row { + display: flex; + align-items: center; + flex: 1; +} .carei-form__phone-prefix { display: flex; align-items: center; @@ -295,8 +441,10 @@ color: var(--carei-gray); } .carei-form__input--phone { - border: none; - background: transparent; + border: none !important; + background: transparent !important; + box-shadow: none !important; + outline: none !important; height: 100%; padding-left: 12px; } @@ -305,7 +453,7 @@ letter-spacing: 2px; } .carei-form__input--phone:focus { - border: none; + border: none !important; } /* ═══════════════════════════════════════════ @@ -397,35 +545,205 @@ padding: 16px; background: var(--carei-white); transition: border-color 0.2s; + min-width: 0; + overflow: hidden; } .carei-form__extra-card:has(input:checked) { border-color: var(--carei-blue); } .carei-form__checkbox-label--card { - align-items: flex-start; + align-items: center; } .carei-form__extra-content { display: flex; - flex-direction: column; - gap: 4px; + align-items: center; + gap: 8px; flex: 1; + min-width: 0; + overflow: hidden; } .carei-form__extra-content strong { - font-weight: 700; - font-size: 14px; + font-weight: 600; + font-size: 15px; color: var(--carei-blue); + word-break: break-word; + min-width: 0; } .carei-form__extra-desc { - font-size: 12px; - color: var(--carei-gray); - font-weight: 400; - line-height: 1.4; + display: none; } .carei-form__extra-price { - font-weight: 700; + font-weight: 400; + font-size: 14px; + color: var(--carei-gray); + margin-left: auto; + white-space: nowrap; + flex-shrink: 0; +} + +.carei-form__checkbox-label--abroad { + align-items: center; + font-weight: 600; font-size: 15px; - color: var(--carei-red); - margin-top: 4px; +} + +/* ═══════════════════════════════════════════ + Abroad Country Search + ═══════════════════════════════════════════ */ +.carei-abroad { + margin-top: 16px; +} +.carei-abroad__input-wrap { + display: flex; + align-items: center; + gap: 8px; + height: 48px; + padding: 0 16px; + border: 1px solid var(--carei-border); + border-radius: var(--carei-radius); + background: var(--carei-white); + transition: border-color 0.2s; +} +.carei-abroad__input-wrap:focus-within { + border-color: var(--carei-blue); +} +.carei-abroad__plus-icon { + flex-shrink: 0; + color: var(--carei-blue); +} +.carei-abroad__input { + flex: 1; + border: none !important; + outline: none !important; + box-shadow: none !important; + background: transparent !important; + font-family: 'Albert Sans', sans-serif; + font-weight: 600; + font-size: 15px; + color: var(--carei-blue); + line-height: 48px; + padding: 0 !important; + margin: 0; + min-width: 0; +} +.carei-abroad__input::placeholder { + font-weight: 400; + color: #C7C7C7; +} +.carei-abroad__clear { + display: none; + align-items: center; + justify-content: center; + flex-shrink: 0; + width: 20px; + height: 20px; + cursor: pointer; + color: #B0B0B0; + border-radius: 50%; + transition: color 0.15s; +} +.carei-abroad__clear:hover { + color: var(--carei-gray); +} +.carei-abroad__input-wrap.has-text .carei-abroad__clear { + display: flex; +} +.carei-abroad__results { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 8px; + margin-top: 8px; +} +.carei-abroad__results:empty { + display: none; +} +.carei-abroad__selected { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 8px; + margin-top: 8px; +} +.carei-abroad__selected:empty { + display: none; +} +.carei-abroad__card { + display: flex; + align-items: center; + gap: 8px; + height: 56px; + padding: 0 12px; + border: 1px solid rgba(0, 0, 0, 0.1); + border-radius: var(--carei-radius); + background: var(--carei-white); + transition: background-color 0.2s, border-color 0.2s; + min-width: 0; + overflow: hidden; +} +.carei-abroad__card:hover { + background: #2F24820D; +} +.carei-abroad__card--selected, +.carei-abroad__card--selected:hover { + background: rgba(47, 36, 130, 0.05); + border-color: rgba(47, 36, 130, 0.2); +} +.carei-abroad__flag { + flex-shrink: 0; + width: 24px; + height: 24px; + font-size: 20px; + line-height: 24px; + text-align: center; +} +.carei-abroad__name { + flex: 1; + font-family: 'Albert Sans', sans-serif; + font-weight: 600; + font-size: 15px; + color: var(--carei-blue); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +.carei-abroad__price { + text-align: right; + color: #505050; + white-space: nowrap; + flex-shrink: 0; +} +.carei-abroad__price-small { + font-size: 10px; +} +.carei-abroad__price-val { + font-size: 14px; +} +.carei-abroad__action { + flex-shrink: 0; + width: 24px; + height: 24px; + display: flex; + align-items: center; + justify-content: center; + border: none !important; + background: transparent !important; + background-color: transparent !important; + cursor: pointer; + font-size: 18px; + font-weight: 700; + color: var(--carei-blue); + padding: 0; + line-height: 1; + border-radius: 50%; + -webkit-appearance: none; + appearance: none; + box-shadow: none; +} + +@media (max-width: 600px) { + .carei-abroad__results, + .carei-abroad__selected { + grid-template-columns: 1fr; + } } /* ═══════════════════════════════════════════ diff --git a/wp-content/plugins/carei-reservation/assets/js/carei-reservation.js b/wp-content/plugins/carei-reservation/assets/js/carei-reservation.js index 42e20c9..385ee83 100644 --- a/wp-content/plugins/carei-reservation/assets/js/carei-reservation.js +++ b/wp-content/plugins/carei-reservation/assets/js/carei-reservation.js @@ -79,7 +79,7 @@ var overlay, form, segmentSelect, dateFrom, dateTo, daysCount; var pickupSelect, returnSelect, returnWrap, sameReturnCheck; - var extrasWrapper, extrasContainer, insuranceContainer, errorSummary; + var extrasWrapper, extrasContainer, insuranceContainer, abroadSection, abroadToggle, abroadSearch, abroadInput, abroadResults, abroadSelected, errorSummary; var summaryOverlay, summaryDetails, summaryTable, summaryTotal, summaryError; var summaryBack, summaryConfirm; var successView, successNumber, successClose; @@ -98,6 +98,12 @@ extrasWrapper = document.getElementById('carei-extras-wrapper'); extrasContainer = document.getElementById('carei-extras-container'); insuranceContainer = document.getElementById('carei-insurance-container'); + abroadSection = document.getElementById('carei-abroad-section'); + abroadToggle = document.getElementById('carei-abroad-toggle'); + abroadSearch = document.getElementById('carei-abroad-search'); + abroadInput = document.getElementById('carei-abroad-input'); + abroadResults = document.getElementById('carei-abroad-results'); + abroadSelected = document.getElementById('carei-abroad-selected'); errorSummary = document.getElementById('carei-error-summary'); // Summary overlay summaryOverlay = document.getElementById('carei-summary-overlay'); @@ -122,6 +128,8 @@ var currentReservationId = null; var agreementDefs = []; var lastFocusedElement = null; + var abroadItems = []; + var selectedCountries = {}; // ─── Modal Open/Close ───────────────────────────────────────── @@ -155,7 +163,7 @@ document.body.style.overflow = 'hidden'; if (!dataLoaded) { loadInitialData(); - setDefaultDates(); + initDateLabels(); dataLoaded = true; } setTimeout(function () { @@ -276,30 +284,47 @@ function maybeShowExtras() { var segment = segmentSelect ? segmentSelect.value : ''; var pickup = pickupSelect ? pickupSelect.value : ''; - if (segment && pickup) { showExtras(); loadExtras(); } else { hideExtras(); } + var from = dateFrom ? dateFrom.value : ''; + var to = dateTo ? dateTo.value : ''; + if (segment && pickup && from && to) { showExtras(); loadExtras(); } else { hideExtras(); } } function showExtras() { if (extrasWrapper) extrasWrapper.style.display = ''; } function hideExtras() { if (extrasWrapper) extrasWrapper.style.display = 'none'; } - // ─── Default Dates ──────────────────────────────────────────── + // ─── Date Labels ────────────────────────────────────────────── - function setDefaultDates() { - if (!dateFrom || !dateTo) return; - var tomorrow = new Date(); tomorrow.setDate(tomorrow.getDate() + 1); tomorrow.setHours(10, 0, 0, 0); - var dayAfter = new Date(); dayAfter.setDate(dayAfter.getDate() + 2); dayAfter.setHours(10, 0, 0, 0); - dateFrom.value = fmtDT(tomorrow); - dateTo.value = fmtDT(dayAfter); - updateDaysCount(); + function initDateLabels() { + [dateFrom, dateTo].forEach(function (input) { + if (!input) return; + updateDateEmpty(input); + input.addEventListener('change', function () { updateDateEmpty(input); updateDaysCount(); }); + input.addEventListener('input', function () { updateDateEmpty(input); }); + }); } - function fmtDT(d) { - return d.getFullYear() + '-' + pad(d.getMonth() + 1) + '-' + pad(d.getDate()) + 'T' + pad(d.getHours()) + ':' + pad(d.getMinutes()); + function updateDateEmpty(input) { + var wrap = input.closest('.carei-form__date-wrap'); + if (input.value) { + input.classList.remove('is-empty'); + if (wrap) wrap.classList.add('has-value'); + } else { + input.classList.add('is-empty'); + if (wrap) wrap.classList.remove('has-value'); + } } + function pad(n) { return String(n).padStart(2, '0'); } // ─── Days Count ─────────────────────────────────────────────── + function getRentalDays() { + if (!dateFrom || !dateTo) return 1; + var from = new Date(dateFrom.value), to = new Date(dateTo.value); + if (isNaN(from.getTime()) || isNaN(to.getTime()) || to <= from) return 1; + return Math.ceil((to - from) / 86400000); + } + function updateDaysCount() { if (!dateFrom || !dateTo || !daysCount) return; var from = new Date(dateFrom.value), to = new Date(dateTo.value); @@ -325,6 +350,8 @@ currentPriceListId = pricelist.id; var items = pricelist.additionalItems; var insuranceItems = [], extraItems = []; + abroadItems = []; + selectedCountries = {}; if (Array.isArray(items)) { items.forEach(function (item) { var name = (item.name || '').toLowerCase(); @@ -332,7 +359,11 @@ var code = (item.code || '').toUpperCase(); if (code.indexOf('BRAK') === 0 || code.indexOf('BRUD') === 0 || code.indexOf('KARA') === 0 || code.indexOf('MYCIE USŁU') === 0 || code === 'MYJ WEW') return; - if (name.indexOf('ubezp') !== -1 || name.indexOf('ochrony') !== -1 || + if (name.indexOf('wyjazd za granic') !== -1) { + item._countryName = parseCountryName(item.name); + item._countryFlag = getCountryFlag(item._countryName); + abroadItems.push(item); + } else if (name.indexOf('ubezp') !== -1 || name.indexOf('ochrony') !== -1 || name.indexOf('zniesienie') !== -1 || name.indexOf('insurance') !== -1) { insuranceItems.push(item); } else { @@ -342,6 +373,8 @@ } if (insuranceContainer) { insuranceContainer.innerHTML = ''; insuranceItems.forEach(function (item) { insuranceContainer.appendChild(buildExtraCard(item)); }); } if (extrasContainer) { extrasContainer.innerHTML = ''; extraItems.forEach(function (item) { extrasContainer.appendChild(buildExtraCard(item)); }); } + if (abroadSection) { abroadSection.style.display = abroadItems.length > 0 ? '' : 'none'; } + renderAbroadSelected(); }).catch(function (err) { console.error('Failed to load pricelist:', err); }); } @@ -363,6 +396,147 @@ return card; } + // ─── Abroad Country Search ───────────────────────────────────── + + var COUNTRY_FLAGS = { + 'niemcy': '\u{1F1E9}\u{1F1EA}', 'czechy': '\u{1F1E8}\u{1F1FF}', 'słowacja': '\u{1F1F8}\u{1F1F0}', + 'austria': '\u{1F1E6}\u{1F1F9}', 'francja': '\u{1F1EB}\u{1F1F7}', 'włochy': '\u{1F1EE}\u{1F1F9}', + 'hiszpania': '\u{1F1EA}\u{1F1F8}', 'holandia': '\u{1F1F3}\u{1F1F1}', 'belgia': '\u{1F1E7}\u{1F1EA}', + 'dania': '\u{1F1E9}\u{1F1F0}', 'szwecja': '\u{1F1F8}\u{1F1EA}', 'norwegia': '\u{1F1F3}\u{1F1F4}', + 'finlandia': '\u{1F1EB}\u{1F1EE}', 'szwajcaria': '\u{1F1E8}\u{1F1ED}', 'węgry': '\u{1F1ED}\u{1F1FA}', + 'chorwacja': '\u{1F1ED}\u{1F1F7}', 'słowenia': '\u{1F1F8}\u{1F1EE}', 'litwa': '\u{1F1F1}\u{1F1F9}', + 'łotwa': '\u{1F1F1}\u{1F1FB}', 'estonia': '\u{1F1EA}\u{1F1EA}', 'rumunia': '\u{1F1F7}\u{1F1F4}', + 'bułgaria': '\u{1F1E7}\u{1F1EC}', 'portugalia': '\u{1F1F5}\u{1F1F9}', 'grecja': '\u{1F1EC}\u{1F1F7}', + 'wielka brytania': '\u{1F1EC}\u{1F1E7}', 'irlandia': '\u{1F1EE}\u{1F1EA}', 'luksemburg': '\u{1F1F1}\u{1F1FA}', + 'serbia': '\u{1F1F7}\u{1F1F8}', 'czarnogóra': '\u{1F1F2}\u{1F1EA}', 'albania': '\u{1F1E6}\u{1F1F1}', + 'turcja': '\u{1F1F9}\u{1F1F7}', 'ukraina': '\u{1F1FA}\u{1F1E6}', 'mołdawia': '\u{1F1F2}\u{1F1E9}' + }; + + function parseCountryName(name) { + var raw = (name || '').replace(/wyjazd za granic[eę]\s*[-–—]?\s*/i, '').trim(); + if (!raw) return name || ''; + return raw.split(/\s+/).map(function (w) { + return w.charAt(0).toUpperCase() + w.slice(1).toLowerCase(); + }).join(' '); + } + + function getCountryFlag(countryName) { + var key = (countryName || '').toLowerCase(); + return COUNTRY_FLAGS[key] || '\u{1F3F3}\u{FE0F}'; + } + + function initAbroad() { + if (!abroadToggle || !abroadSearch) return; + abroadToggle.addEventListener('change', function () { + if (abroadToggle.checked) { + abroadSearch.style.display = ''; + } else { + abroadSearch.style.display = 'none'; + selectedCountries = {}; + if (abroadInput) abroadInput.value = ''; + renderAbroadResults([]); + renderAbroadSelected(); + } + }); + var abroadClear = document.getElementById('carei-abroad-clear'); + if (abroadInput) { + abroadInput.addEventListener('input', function () { + updateAbroadClear(); + var query = abroadInput.value.trim().toLowerCase(); + if (!query) { renderAbroadResults([]); return; } + var filtered = abroadItems.filter(function (item) { + return item._countryName.toLowerCase().indexOf(query) !== -1; + }); + renderAbroadResults(filtered); + }); + abroadInput.addEventListener('focus', function () { + var query = abroadInput.value.trim().toLowerCase(); + if (query) { + var filtered = abroadItems.filter(function (item) { + return item._countryName.toLowerCase().indexOf(query) !== -1; + }); + renderAbroadResults(filtered); + } + }); + } + if (abroadClear) { + abroadClear.addEventListener('click', function () { + if (abroadInput) { abroadInput.value = ''; abroadInput.focus(); } + renderAbroadResults([]); + updateAbroadClear(); + }); + } + + function updateAbroadClear() { + var wrap = abroadInput ? abroadInput.closest('.carei-abroad__input-wrap') : null; + if (wrap) { + if (abroadInput.value.trim()) { wrap.classList.add('has-text'); } + else { wrap.classList.remove('has-text'); } + } + } + } + + function renderAbroadResults(items) { + if (!abroadResults) return; + abroadResults.innerHTML = ''; + if (!items || items.length === 0) return; + items.forEach(function (item) { + var id = item.id || item.code; + var isSelected = !!selectedCountries[id]; + abroadResults.appendChild(buildCountryCard(item, isSelected)); + }); + } + + function renderAbroadSelected() { + if (!abroadSelected) return; + abroadSelected.innerHTML = ''; + var keys = Object.keys(selectedCountries); + if (keys.length === 0) return; + keys.forEach(function (id) { + var item = selectedCountries[id]; + abroadSelected.appendChild(buildCountryCard(item, true)); + }); + } + + function buildCountryCard(item, isSelected) { + var id = item.id || item.code; + var price = parseFloat(item.price || item.minPrice || 0); + var priceHtml = '' + (price > 0 ? price.toFixed(0) + ' zł' : 'Gratis') + ''; + + var card = document.createElement('div'); + card.className = 'carei-abroad__card' + (isSelected ? ' carei-abroad__card--selected' : ''); + card.innerHTML = + '' + escHtml(item._countryFlag) + '' + + '' + escHtml(item._countryName) + '' + + '' + priceHtml + '' + + '' + + (isSelected + ? '' + : '' + ) + ''; + + card.querySelector('.carei-abroad__action').addEventListener('click', function (e) { + e.preventDefault(); + if (selectedCountries[id]) { + delete selectedCountries[id]; + } else { + selectedCountries[id] = item; + } + var query = abroadInput ? abroadInput.value.trim().toLowerCase() : ''; + if (query) { + var filtered = abroadItems.filter(function (it) { + return it._countryName.toLowerCase().indexOf(query) !== -1; + }); + renderAbroadResults(filtered); + } else { + renderAbroadResults([]); + } + renderAbroadSelected(); + }); + + return card; + } + // ─── Select Helpers ─────────────────────────────────────────── function populateSelect(select, options, placeholder) { @@ -588,13 +762,31 @@ function getSelectedExtrasForApi() { var items = []; if (!form) return items; + var days = getRentalDays(); form.querySelectorAll('input[name="extras[]"]:checked').forEach(function (cb) { var price = parseFloat(cb.getAttribute('data-price') || 0); + var unit = cb.getAttribute('data-unit') || 'szt.'; + var amount = unit === 'doba' ? days : 1; items.push({ id: cb.value, name: cb.getAttribute('data-name') || '', - unit: cb.getAttribute('data-unit') || 'szt.', - amount: 1, + unit: unit, + amount: amount, + priceBeforeDiscount: price, + discount: 0, + priceAfterDiscount: price + }); + }); + Object.keys(selectedCountries).forEach(function (id) { + var item = selectedCountries[id]; + var price = parseFloat(item.price || item.minPrice || 0); + var unit = item.unit || 'szt.'; + var amount = unit === 'doba' ? days : 1; + items.push({ + id: item.id || item.code, + name: item.name, + unit: unit, + amount: amount, priceBeforeDiscount: price, discount: 0, priceAfterDiscount: price @@ -898,6 +1090,7 @@ initSameReturn(); initDynamicLoading(); initClearErrors(); + initAbroad(); initSubmit(); } diff --git a/wp-content/plugins/carei-reservation/includes/class-elementor-widget.php b/wp-content/plugins/carei-reservation/includes/class-elementor-widget.php index 0c825ab..b39e8be 100644 --- a/wp-content/plugins/carei-reservation/includes/class-elementor-widget.php +++ b/wp-content/plugins/carei-reservation/includes/class-elementor-widget.php @@ -58,11 +58,11 @@ class Carei_Reservation_Widget extends \Elementor\Widget_Base { -
- - +
+
+ + + +
-
- - +
+
+ + + +
@@ -126,6 +129,26 @@ class Carei_Reservation_Widget extends \Elementor\Widget_Base {