---
phase: 08-map-and-cities-widgets
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
  - wp-content/plugins/carei-reservation/includes/class-map-widget.php
  - wp-content/plugins/carei-reservation/includes/class-cities-widget.php
  - wp-content/plugins/carei-reservation/includes/class-softra-api.php
  - wp-content/plugins/carei-reservation/carei-reservation.php
  - wp-content/plugins/carei-reservation/assets/css/carei-reservation.css
  - wp-content/plugins/carei-reservation/assets/js/carei-reservation.js
autonomous: false
---

<objective>
## Goal
Dwa nowe widgety Elementor: (1) interaktywna mapa Polski z dynamicznymi pinami oddziałów i tooltipami adresowymi, (2) siatka miast oddziałowych. Plus ujednolicone cachowanie odpowiedzi API Softra (60 min).

## Purpose
Wizualna prezentacja sieci oddziałów Carei na stronie — mapa z pinami i lista miast budują zaufanie klienta i ułatwiają wybór lokalizacji.

## Output
- `class-map-widget.php` — widget Elementor z mapą SVG Polski + dynamiczne piny
- `class-cities-widget.php` — widget Elementor z siatką miast
- Rozszerzony cache w `class-softra-api.php` — transient 60 min na `/branch/list`
- Zarejestrowane widgety w `carei-reservation.php`
- Style i JS w istniejących plikach assets
</objective>

<context>
## Project Context
@.paul/PROJECT.md
@.paul/ROADMAP.md
@.paul/STATE.md

## Source Files
@wp-content/plugins/carei-reservation/carei-reservation.php
@wp-content/plugins/carei-reservation/includes/class-softra-api.php
@wp-content/plugins/carei-reservation/includes/class-search-widget.php (wzorzec widgetu)
@wp-content/plugins/carei-reservation/assets/css/carei-reservation.css
@wp-content/plugins/carei-reservation/assets/js/carei-reservation.js
</context>

<skills>
No specialized flows configured.
</skills>

<acceptance_criteria>

## AC-1: Mapa Polski z dynamicznymi pinami oddziałów
```gherkin
Given strona z widgetem Carei Map załadowana
When dane oddziałów pobrane z API (lub cache)
Then na mapie SVG Polski wyświetlają się czerwone piny w lokalizacjach odpowiadających miastom oddziałów
And po najechaniu na pin pojawia się tooltip z adresem oddziału (ulica, kod, miasto)
And po kliknięciu pina tooltip pozostaje widoczny (toggle)
```

## AC-2: Siatka miast oddziałowych
```gherkin
Given strona z widgetem Carei Cities załadowana
When dane oddziałów pobrane z API (lub cache)
Then wyświetla się siatka z nazwami miast oddziałów rozdzielonymi separatorem "|"
And miasta są unikalne (bez duplikatów nawet jeśli oddział ma wiele branchy w jednym mieście)
And układ jest responsywny (grid na desktop, kolumna na mobile)
```

## AC-3: Cachowanie odpowiedzi API z TTL 60 min
```gherkin
Given plugin Carei Reservation aktywny
When frontend lub widget pobiera dane oddziałów z /branch/list
Then wynik jest cachowany jako WP transient z TTL 60 minut
And kolejne żądania w ciągu 60 min nie generują zapytań do Softra API
And po upływie TTL dane są odświeżane z API
```

</acceptance_criteria>

<tasks>

<task type="auto">
  <name>Task 1: Cache API + Widget mapki Polski z pinami</name>
  <files>
    wp-content/plugins/carei-reservation/includes/class-softra-api.php,
    wp-content/plugins/carei-reservation/includes/class-map-widget.php,
    wp-content/plugins/carei-reservation/carei-reservation.php,
    wp-content/plugins/carei-reservation/assets/css/carei-reservation.css,
    wp-content/plugins/carei-reservation/assets/js/carei-reservation.js
  </files>
  <action>
    **1a. Cachowanie `/branch/list` (class-softra-api.php):**
    - Dodaj metodę `get_branches_cached()` opakowującą `get_branches()` w transient `carei_branches_list` z TTL 60 min (HOUR_IN_SECONDS).
    - Zmień `get_segments_branches_map()` by korzystała z `get_branches_cached()` zamiast `get_branches()`.
    - Dodaj endpoint REST `/branches-full` w REST proxy zwracający pełne dane branchy (z description/address) — istniejący `/branches` zwraca tylko nazwy.

    **1b. Widget mapy (class-map-widget.php):**
    - Nowa klasa `Carei_Map_Widget extends \Elementor\Widget_Base`
    - `get_name()` → `'carei-map'`, `get_title()` → `'Carei Map'`
    - W `render()`:
      - Pobierz oddziały z REST API `/carei/v1/branches-full` przez JS (lub inline z PHP jeśli cache aktywny)
      - Wyrenderuj kontener `<div class="carei-map">` z inline SVG mapy Polski (kontur województw)
      - SVG mapy: użytkownik dostarczy kod SVG — na razie placeholder z prostym konturem Polski
    - Mapowanie miast na współrzędne SVG:
      - W PHP: tablica asocjacyjna `CITY_COORDINATES` mapująca nazwy miast (lowercase, bez polskich znaków) na współrzędne [x, y] w viewBox SVG
      - Miasta znane w Carei: Warszawa, Białystok, Wrocław, Kraków, Gdańsk, Poznań, Katowice, Łódź, Lublin, Szczecin, Rzeszów, Bydgoszcz, Olsztyn, Kielce, Opole, Zielona Góra, Radom itp.
      - Przekaż współrzędne do JS przez `wp_localize_script` lub data-attribute
    - W JS: dynamicznie dodaj `<circle>` (piny) na mapie SVG w pozycjach z mapowania
    - Tooltip: na hover/click nad pinem — `<div class="carei-map__tooltip">` z adresem z pola `description` brancha
    - Styl pina: czerwone kółko (#FF0000), r=6, hover r=8 z transition
    - Styl tooltipa: ciemne tło (#2F2482), biały tekst, strzałka (jak na Figmie)

    **1c. Rejestracja widgetu:**
    - W `carei-reservation.php` w hooku `elementor/widgets/register`: require + register `Carei_Map_Widget`

    **Avoid:**
    - NIE używaj zewnętrznych bibliotek mapowych (Leaflet, Google Maps) — to prosta mapa SVG
    - NIE hardkoduj danych oddziałów — zawsze z API/cache
  </action>
  <verify>
    - Widget "Carei Map" widoczny w panelu Elementor
    - Mapa SVG renderuje się z pinami w lokalizacjach oddziałów
    - Hover na pinie pokazuje tooltip z adresem
    - Drugie załadowanie strony nie generuje nowego requestu do Softra (cache aktywny)
  </verify>
  <done>AC-1 satisfied: Mapa z dynamicznymi pinami i tooltipami. AC-3 satisfied: Cache 60 min na branch/list.</done>
</task>

<task type="auto">
  <name>Task 2: Widget siatki miast</name>
  <files>
    wp-content/plugins/carei-reservation/includes/class-cities-widget.php,
    wp-content/plugins/carei-reservation/carei-reservation.php,
    wp-content/plugins/carei-reservation/assets/css/carei-reservation.css,
    wp-content/plugins/carei-reservation/assets/js/carei-reservation.js
  </files>
  <action>
    **2a. Widget miast (class-cities-widget.php):**
    - Nowa klasa `Carei_Cities_Widget extends \Elementor\Widget_Base`
    - `get_name()` → `'carei-cities'`, `get_title()` → `'Carei Cities'`
    - W `render()`:
      - Pobierz oddziały z cache/API (ten sam endpoint co mapa)
      - Wyciągnij unikalne nazwy miast z pola description branchy (parse miasto z adresu)
      - Renderuj `<div class="carei-cities">` z siatką miast
    - HTML: `<span class="carei-cities__item">Miasto</span>` rozdzielone separatorem `|`
    - Layout: CSS grid/flex, 5 kolumn na desktop (jak na Figmie), 2-3 kolumny na tablet, 1 na mobile
    - Font: Albert Sans, kolor #2F2482
    - Miasta dynamiczne — jeśli oddział dodany w Softra, automatycznie pojawia się w widgecie

    **2b. Rejestracja widgetu:**
    - W `carei-reservation.php`: require + register `Carei_Cities_Widget`

    **Avoid:**
    - NIE hardkoduj listy miast
    - NIE duplikuj requestów — użyj tego samego cache co mapa
  </action>
  <verify>
    - Widget "Carei Cities" widoczny w panelu Elementor
    - Siatka miast renderuje się z unikalnymi miastami z API
    - Responsywność: 5 kolumn desktop → mniej na mobile
    - Brak duplikatów miast
  </verify>
  <done>AC-2 satisfied: Siatka miast dynamiczna, responsywna, unikalne wartości.</done>
</task>

<task type="checkpoint:human-verify" gate="blocking">
  <what-built>Dwa widgety Elementor: mapa Polski z pinami oddziałów + siatka miast. Cachowanie API 60 min.</what-built>
  <how-to-verify>
    1. Otwórz stronę w Elementorze, dodaj widget "Carei Map" — sprawdź czy piny są na mapie
    2. Najedź na pin — tooltip z adresem powinien się pojawić
    3. Dodaj widget "Carei Cities" — sprawdź siatkę miast
    4. Sprawdź responsywność (mobile preview w Elementorze)
    5. Opcjonalnie: podaj kod SVG mapy Polski do zastąpienia placeholdera
  </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-elementor-widget.php (główny widget rezerwacji)
- wp-content/plugins/carei-reservation/includes/class-admin-panel.php (admin panel)
- Istniejące endpointy REST (nie zmieniaj zachowania `/branches`, `/car-classes` itd.)
- Logika formularza rezerwacji w JS (sekcje modal, booking flow)

## SCOPE LIMITS
- Mapa to SVG statyczny z dynamicznymi pinami — NIE interaktywna mapa z zoomem
- Brak geokodowania w runtime — współrzędne miast zmapowane statycznie w PHP
- Tooltip prosty (adres) — bez linków, bez routingu do formularza
- Cachowanie tylko na `/branch/list` — inne endpointy bez zmian w tym planie

</boundaries>

<verification>
Before declaring plan complete:
- [ ] Widget "Carei Map" renderuje mapę z pinami na prawidłowych pozycjach
- [ ] Tooltip na hover/click pokazuje adres oddziału
- [ ] Widget "Carei Cities" wyświetla unikalne miasta w siatce
- [ ] Cache transient `carei_branches_list` aktywny z TTL 60 min
- [ ] Responsywność obu widgetów (desktop + mobile)
- [ ] Brak błędów JS w konsoli
- [ ] Brak broken layouts w istniejących widgetach (rezerwacja, search form)
</verification>

<success_criteria>
- Oba widgety dostępne w Elementorze i renderują dane z API
- Piny na mapie odpowiadają miastom oddziałów z Softra
- Tooltips wyświetlają adresy
- Siatka miast dynamiczna i responsywna
- Cache 60 min działa — brak nadmiarowych requestów do API
- Istniejąca funkcjonalność nienaruszona
</success_criteria>

<output>
After completion, create `.paul/phases/08-map-and-cities-widgets/08-01-SUMMARY.md`
</output>
