--- phase: 01-historia-cen plan: 02 type: execute wave: 1 depends_on: ["01-01"] files_modified: - wp-content/plugins/elementor-addon/widgets/apartaments.php - wp-content/plugins/elementor-addon/assets/css/main.scss - wp-content/plugins/elementor-addon/assets/css/main.css - wp-content/plugins/elementor-addon/assets/js/main.js autonomous: false --- ## Goal Zbudować popup „Historia cen" — klikalny przycisk na karcie apartamentu otwiera modal z aktualną ceną i tabelą historii zmian, zasilany przez AJAX endpoint z planu 01-01. ## Purpose Użytkownik może zobaczyć historię zmian cen apartamentu bez opuszczania strony. Popup pokazuje nazwę apartamentu, aktualną cenę brutto i za m², oraz tabelę dat ze zmianami. ## Output - Przycisk „HISTORIA CEN" z `data-post-id` w widgecie - Globalny popup HTML (overlay + modal) w widgecie - CSS modal pasujący do projektu (font Barlow, kolor #192c44, border 4px solid) - JS: fetch AJAX → wypełnienie popupa → pokaż/ukryj ## Project Context @.paul/PROJECT.md @.paul/STATE.md ## Prior Work @.paul/phases/01-historia-cen/01-01-SUMMARY.md ## Source Files @wp-content/plugins/elementor-addon/widgets/apartaments.php @wp-content/plugins/elementor-addon/assets/css/main.scss @wp-content/plugins/elementor-addon/assets/js/main.js ## API Contract (z planu 01-01) POST admin-ajax.php body: action=apartamenty_get_price_history&post_id=ID&nonce=NONCE response: { success: true, data: { title: "Apartament X", price: "677 920", // cena brutto price_m2: "19 000", // cena za m² floor_space: "35,68", // metraż history: [ { recorded_at: "2026-01-16", price: "677 920", price_m2: "19 000" } ] } } ## JS globals dostępne na stronie window.apartamentsData.ajaxUrl — URL do admin-ajax.php window.apartamentsData.nonce — nonce 'apartamenty_price_history_nonce' ## Design (z mockupu Group 68.png) - Białe tło, border matching kart (#192c44) - Tytuł (bold, duży) + X w prawym górnym rogu - "Cena brutto:" (bold) → wartość bold po prawej z " zł" - "Cena m²:" (normal) → wartość normal po prawej z " zł" - Separator - Tabela: data | cena_m2 (format "X zł/m²") | cena brutto (format "X,00 zł") - Overlay ciemne tło półprzezroczyste ## Istniejące style CSS - Font: 'Barlow', sans-serif - Kolor główny: #192c44 - Border kart: 4px solid #192c44 - Wiersz .apartament-card__price-history już ma cursor: pointer ## AC-1: Przycisk "Historia cen" jest klikalny ```gherkin Given apartament ma dane cen w ACF When użytkownik widzi kartę apartamentu Then wiersz "HISTORIA CEN" jest klikalny i posiada atrybut data-post-id z ID apartamentu ``` ## AC-2: Popup otwiera się z danymi ```gherkin Given użytkownik klika "HISTORIA CEN" przy apartamencie When request AJAX zakończy się sukcesem Then pojawia się modal z: - tytułem apartamentu - aktualną ceną brutto (bold) - aktualną ceną m² (normal) - tabelą historii (data | cena m² | cena brutto) ``` ## AC-3: Popup zamyka się ```gherkin Given popup jest otwarty When użytkownik klika przycisk X lub klika poza modalem (na overlay) Then popup znika ``` ## AC-4: Stan ładowania i błędu ```gherkin Given użytkownik kliknął "Historia cen" When AJAX jest w trakcie Then popup pokazuje "Ładowanie..." And jeśli AJAX zwróci błąd, popup pokazuje "Brak danych" ``` ## AC-5: Historia jest pusta ```gherkin Given apartament nie ma jeszcze rekordów w tabeli historii When użytkownik otworzy popup Then widoczna jest sekcja z aktualną ceną, a tabela historii jest pusta lub pokazuje komunikat "Brak historii cen" ``` Task 1: Dodaj data-post-id do przycisku i popup HTML do widgetu wp-content/plugins/elementor-addon/widgets/apartaments.php **Zmiana 1: Wiersz "HISTORIA CEN"** Znajdź wiersz z klasą `apartament-card__price-history` (linia ~157). Zmień `` na: ```php ``` Reszta wiersza (tekst "HISTORIA CEN" + SVG) pozostaje bez zmian. **Zmiana 2: Globalny popup HTML** Dodaj PRZED `` (czyli po zamknięciu pętli while) następujący HTML popupa (jeden egzemplarz dla całej strony): ```php ``` Unikaj: dodawania popup wewnątrz pętli while — tylko jeden egzemplarz na stronę. Sprawdź PHP syntax: `php -l wp-content/plugins/elementor-addon/widgets/apartaments.php` Sprawdź czy data-post-id jest w HTML widgetu przez DevTools lub view-source. AC-1 spełnione: przycisk ma data-post-id; popup HTML istnieje w DOM Task 2: CSS popup — main.scss i main.css wp-content/plugins/elementor-addon/assets/css/main.scss, wp-content/plugins/elementor-addon/assets/css/main.css Dodaj na końcu pliku `main.scss` (po zamknięciu `.apartaments { }`) poniższe style. Następnie dodaj **te same skompilowane style** (bez SCSS zagnieżdżeń, rozwinięte) na końcu pliku `main.css`. **Style do dodania w main.scss:** ```scss // Historia cen — popup overlay .price-history-overlay { display: none; position: fixed; inset: 0; z-index: 99999; background: rgba(25, 44, 68, 0.55); align-items: center; justify-content: center; padding: 20px; &.is-open { display: flex; } } .price-history-modal { position: relative; background: #fff; border: 4px solid #192c44; padding: 32px 36px 28px; max-width: 560px; width: 100%; max-height: 80vh; overflow-y: auto; font-family: 'Barlow', sans-serif; color: #192c44; @media (max-width: 600px) { padding: 24px 20px 20px; } &__close { position: absolute; top: 14px; right: 14px; background: none; border: 2px solid #192c44; width: 30px; height: 30px; display: flex; align-items: center; justify-content: center; cursor: pointer; padding: 0; line-height: 1; } &__title { font-size: 22px; font-weight: 700; margin: 0 0 18px; padding-right: 40px; color: #192c44; } &__current { margin-bottom: 16px; } &__row { display: flex; justify-content: space-between; font-size: 18px; line-height: 1.5; color: #192c44; &--bold { font-weight: 700; } } &__val { text-align: right; } &__table-wrap { border-top: 1px solid #192c44; padding-top: 12px; margin-top: 4px; } &__table { width: 100%; border-collapse: collapse; tr { border: none; background: transparent; td { padding: 4px 0; font-size: 15px; color: #192c44; font-family: 'Barlow', sans-serif; font-weight: 400; border: none; background: transparent; &:last-child { text-align: right; } &:nth-child(2) { text-align: center; } } } } } ``` **Skompilowana wersja CSS do dodania na końcu main.css** (rozwinięte selektory, bez &): ```css /* Historia cen — popup */ .price-history-overlay { display: none; position: fixed; inset: 0; z-index: 99999; background: rgba(25, 44, 68, 0.55); align-items: center; justify-content: center; padding: 20px; } .price-history-overlay.is-open { display: flex; } .price-history-modal { position: relative; background: #fff; border: 4px solid #192c44; padding: 32px 36px 28px; max-width: 560px; width: 100%; max-height: 80vh; overflow-y: auto; font-family: 'Barlow', sans-serif; color: #192c44; } @media (max-width: 600px) { .price-history-modal { padding: 24px 20px 20px; } } .price-history-modal__close { position: absolute; top: 14px; right: 14px; background: none; border: 2px solid #192c44; width: 30px; height: 30px; display: flex; align-items: center; justify-content: center; cursor: pointer; padding: 0; line-height: 1; } .price-history-modal__title { font-size: 22px; font-weight: 700; margin: 0 0 18px; padding-right: 40px; color: #192c44; } .price-history-modal__current { margin-bottom: 16px; } .price-history-modal__row { display: flex; justify-content: space-between; font-size: 18px; line-height: 1.5; color: #192c44; } .price-history-modal__row--bold { font-weight: 700; } .price-history-modal__val { text-align: right; } .price-history-modal__table-wrap { border-top: 1px solid #192c44; padding-top: 12px; margin-top: 4px; } .price-history-modal__table { width: 100%; border-collapse: collapse; } .price-history-modal__table tr { border: none; background: transparent; } .price-history-modal__table td { padding: 4px 0; font-size: 15px; color: #192c44; font-family: 'Barlow', sans-serif; font-weight: 400; border: none; background: transparent; } .price-history-modal__table td:last-child { text-align: right; } .price-history-modal__table td:nth-child(2) { text-align: center; } ``` Unikaj: modyfikowania istniejących reguł CSS — tylko append na końcu. Unikaj: zmian w mapie .css.map — pozostaw jak jest. Sprawdź że klasy `.price-history-overlay` i `.price-history-modal` są obecne w main.css. Sprawdź że nie ma błędów składni SCSS (ręczna inspekcja nawiasów). AC-2, AC-3 (style): modal wygląda zgodnie z projektem Task 3: JS — obsługa kliknięcia, AJAX, render popupa wp-content/plugins/elementor-addon/assets/js/main.js Dodaj na końcu pliku `main.js` (po zamknięciu istniejącego DOMContentLoaded) nowy blok obsługi historii cen. **Kod do dodania:** ```javascript // Historia cen document.addEventListener('DOMContentLoaded', function () { var overlay = document.getElementById('price-history-overlay'); var closeBtn = document.getElementById('price-history-close'); if (!overlay) return; // popup nie istnieje na tej stronie function openPopup() { overlay.setAttribute('aria-hidden', 'false'); overlay.classList.add('is-open'); document.body.style.overflow = 'hidden'; } function closePopup() { overlay.setAttribute('aria-hidden', 'true'); overlay.classList.remove('is-open'); document.body.style.overflow = ''; } // Zamknij przyciskiem X closeBtn.addEventListener('click', closePopup); // Zamknij klikając na overlay (poza modalem) overlay.addEventListener('click', function (e) { if (e.target === overlay) closePopup(); }); // Zamknij klawiszem Escape document.addEventListener('keydown', function (e) { if (e.key === 'Escape' && overlay.classList.contains('is-open')) closePopup(); }); // Kliknięcie w przycisk Historia cen document.querySelectorAll('.btn-historia-cen').forEach(function (btn) { btn.addEventListener('click', function () { var postId = this.dataset.postId; if (!postId) return; // Reset i pokaż "Ładowanie..." document.getElementById('price-history-title').textContent = 'Ładowanie...'; document.getElementById('price-history-price').textContent = ''; document.getElementById('price-history-price-m2').textContent = ''; document.getElementById('price-history-tbody').innerHTML = ''; openPopup(); // Sprawdź dostępność danych globalnych (wp_localize_script) if (typeof apartamentsData === 'undefined') { document.getElementById('price-history-title').textContent = 'Błąd konfiguracji'; return; } // Buduj FormData var formData = new FormData(); formData.append('action', 'apartamenty_get_price_history'); formData.append('post_id', postId); formData.append('nonce', apartamentsData.nonce); fetch(apartamentsData.ajaxUrl, { method: 'POST', body: formData, credentials: 'same-origin', }) .then(function (res) { return res.json(); }) .then(function (json) { if (!json.success) { document.getElementById('price-history-title').textContent = 'Brak danych'; return; } var d = json.data; document.getElementById('price-history-title').textContent = d.title || ''; document.getElementById('price-history-price').textContent = d.price ? d.price + ' zł' : '—'; document.getElementById('price-history-price-m2').textContent = d.price_m2 ? d.price_m2 + ' zł' : '—'; var tbody = document.getElementById('price-history-tbody'); if (!d.history || d.history.length === 0) { tbody.innerHTML = 'Brak historii cen'; return; } tbody.innerHTML = d.history.map(function (row) { return '' + '' + (row.recorded_at || '') + '' + '' + (row.price_m2 ? row.price_m2 + ' zł/m²' : '—') + '' + '' + (row.price ? row.price + ' zł' : '—') + '' + ''; }).join(''); }) .catch(function () { document.getElementById('price-history-title').textContent = 'Błąd ładowania'; }); }); }); }); ``` Unikaj: modyfikowania istniejącego bloku DOMContentLoaded (Swiper/Fancybox) — tylko append. Unikaj: jQuery — używaj vanilla JS zgodnie z istniejącym stylem kodu. `php -l` nie jest tu potrzebny — sprawdź składnię JS ręcznie (nawiasy, cudzysłowy). Po wgraniu: otwórz stronę /apartamenty/, otwórz DevTools Console, kliknij "HISTORIA CEN" → popup powinien się pojawić z danymi lub "Ładowanie...". AC-2, AC-3, AC-4, AC-5 spełnione: popup otwiera się z danymi AJAX, zamyka się poprawnie Popup „Historia cen" — kliknięcie przycisku otwiera modal z danymi z AJAX. Przycisk ma data-post-id, popup zamyka się przez X / klik overlay / Escape. 1. Odwiedź: https://wyszynskiego12.pagedev.pl/apartamenty/ 2. Odszukaj wiersz „HISTORIA CEN" przy dowolnym apartamencie i kliknij go 3. Sprawdź czy pojawia się modal z: - Tytułem apartamentu (np. „Apartament 15") - Ceną brutto (bold) - Ceną m² (normal) - Tabelą historii (lub „Brak historii cen" jeśli tabela pusta) 4. Kliknij X → popup zamknięty 5. Kliknij poza modalem → popup zamknięty 6. Naciśnij Escape → popup zamknięty 7. Otwórz DevTools → Console → brak błędów JavaScript Wpisz "zatwierdzone" lub opisz problemy do poprawienia ## DO NOT CHANGE - wp-content/plugins/elementor-addon/elementor-addon.php (ukończony w planie 01-01) - wp-content/plugins/elementor-addon/plugins/ (biblioteki zewnętrzne) - wp-config.php ## SCOPE LIMITS - Nie dodawaj animacji CSS poza prostym display:flex/none - Nie dodawaj nowych zależności JS (bez jQuery, bez bibliotek) - Popup ma jeden egzemplarz na stronę — nie w pętli - Nie modyfikuj istniejących reguł CSS — tylko append Before declaring plan complete: - [ ] PHP syntax OK: `php -l widgets/apartaments.php` - [ ] Klasa `.price-history-overlay` istnieje w main.css - [ ] Klasa `.btn-historia-cen` jest na wierszu HISTORIA CEN w HTML - [ ] `data-post-id` jest ustawiony poprawnie dla każdego apartamentu - [ ] JS nie wyrzuca błędów w konsoli - [ ] Checkpoint human-verify zatwierdzony - Kliknięcie "HISTORIA CEN" otwiera popup z danymi apartamentu - Popup zamyka się przez X, klik overlay i Escape - Stan ładowania i błędu jest obsługiwany - Brak błędów JS w konsoli - Wygląd zgodny z projektem (font Barlow, kolor #192c44, border 4px) Po ukończeniu utwórz `.paul/phases/01-historia-cen/01-02-SUMMARY.md`