feat(v0.1): historia cen + jawnosc cen — milestone Initial Release
Historia cen: - Tabela wp_price_history z WP Cronem dziennym (snapshot cen) - AJAX endpoint apartamenty_get_price_history (zabezpieczony nonce) - Popup "Historia cen" w widgecie — vanilla JS, modal zgodny z projektem Jawnosc cen: - Endpointy /ceny-mieszkan.xml + /dane-gov-pl.xml (XSD-compliant) - Pliki MD5 dla obu XML - Strona admina: Narzedzia -> Jawnosc Cen z URL-ami do Ministerstwa - Transient cache 1h z inwalidacja przez cron Dokumentacja: docs/readme.md + docs/jawnosc-cen.md Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
225
.paul/phases/01-historia-cen/01-01-PLAN.md
Normal file
225
.paul/phases/01-historia-cen/01-01-PLAN.md
Normal file
@@ -0,0 +1,225 @@
|
||||
---
|
||||
phase: 01-historia-cen
|
||||
plan: 01
|
||||
type: execute
|
||||
wave: 1
|
||||
depends_on: []
|
||||
files_modified:
|
||||
- wp-content/plugins/elementor-addon/elementor-addon.php
|
||||
autonomous: true
|
||||
---
|
||||
|
||||
<objective>
|
||||
## Goal
|
||||
Stworzyć backend dla historii cen apartamentów:
|
||||
- tabela `wp_price_history` w bazie danych
|
||||
- WP Cron zapisujący ceny raz dziennie
|
||||
- AJAX endpoint zwracający historię dla danego apartamentu
|
||||
|
||||
## Purpose
|
||||
Ceny apartamentów zarządzane są przez ACF (pola `information_price`, `information_price_m2`, `information_floor_space`).
|
||||
Historia musi być zapisywana codziennie, bo ACF nie przechowuje zmian — tylko aktualną wartość.
|
||||
|
||||
## Output
|
||||
- Tabela `wp_price_history` założona przy aktywacji pluginu
|
||||
- Hook `apartamenty_record_prices` uruchamiany codziennie przez WP Cron
|
||||
- AJAX action `apartamenty_get_price_history` zwracający JSON
|
||||
</objective>
|
||||
|
||||
<context>
|
||||
## Project Context
|
||||
@.paul/PROJECT.md
|
||||
@.paul/STATE.md
|
||||
|
||||
## Source Files
|
||||
@wp-content/plugins/elementor-addon/elementor-addon.php
|
||||
|
||||
## Struktura danych ACF
|
||||
Każdy apartament (post_type: `apartamenty`) posiada w `wp_postmeta`:
|
||||
- `information_price` → string np. `"677 920"` (cena brutto w zł)
|
||||
- `information_price_m2` → string np. `"19 000"` (cena za m² w zł)
|
||||
- `information_floor_space` → string np. `"35,68"` (metraż w m²)
|
||||
|
||||
Prefix tabel WP: `wp_`
|
||||
DB credentials: z wp-config.php (DB_NAME, DB_USER, DB_PASSWORD, DB_HOST)
|
||||
</context>
|
||||
|
||||
<acceptance_criteria>
|
||||
|
||||
## AC-1: Tabela istnieje w bazie
|
||||
```gherkin
|
||||
Given plugin elementor-addon jest aktywny
|
||||
When WordPress się inicjalizuje
|
||||
Then tabela wp_price_history istnieje z kolumnami: id, post_id, price, price_m2, floor_space, recorded_at
|
||||
And istnieje UNIQUE KEY na (post_id, recorded_at) — jeden rekord na apt na dzień
|
||||
```
|
||||
|
||||
## AC-2: Cron zapisuje ceny codziennie
|
||||
```gherkin
|
||||
Given istnieją apartamenty z wypełnionymi polami cen ACF
|
||||
When hook `apartamenty_record_prices` zostanie wywołany
|
||||
Then dla każdego apartamentu z post_type=apartamenty i post_status=publish
|
||||
zostaje wstawiony lub zignorowany (INSERT IGNORE) rekord do wp_price_history
|
||||
z aktualną ceną i datą TODAY()
|
||||
```
|
||||
|
||||
## AC-3: AJAX endpoint zwraca historię
|
||||
```gherkin
|
||||
Given apartament o ID=X ma rekordy w wp_price_history
|
||||
When POST na /wp-admin/admin-ajax.php z action=apartamenty_get_price_history i post_id=X
|
||||
Then odpowiedź JSON zawiera:
|
||||
{ success: true, data: { title, price, price_m2, history: [{date, price, price_m2}] } }
|
||||
And historia jest posortowana od najnowszej daty
|
||||
```
|
||||
|
||||
## AC-4: Nonce zabezpiecza AJAX
|
||||
```gherkin
|
||||
Given request AJAX bez poprawnego nonce
|
||||
When POST na admin-ajax.php
|
||||
Then odpowiedź: { success: false }
|
||||
```
|
||||
|
||||
</acceptance_criteria>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 1: Utwórz tabelę wp_price_history i zarejestruj cron</name>
|
||||
<files>wp-content/plugins/elementor-addon/elementor-addon.php</files>
|
||||
<action>
|
||||
Dodaj na końcu pliku elementor-addon.php (przed zamknięciem) następujące funkcje:
|
||||
|
||||
1. **Funkcja tworząca tabelę** `elementor_addon_create_price_history_table()`:
|
||||
- Używa `$wpdb->prefix . 'price_history'`
|
||||
- SQL z `dbDelta()` (wymaga `require_once ABSPATH . 'wp-admin/includes/upgrade.php'`)
|
||||
- Schemat tabeli:
|
||||
```sql
|
||||
CREATE TABLE {prefix}price_history (
|
||||
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
post_id BIGINT UNSIGNED NOT NULL,
|
||||
price VARCHAR(50) NOT NULL DEFAULT '',
|
||||
price_m2 VARCHAR(50) NOT NULL DEFAULT '',
|
||||
floor_space VARCHAR(50) NOT NULL DEFAULT '',
|
||||
recorded_at DATE NOT NULL,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY unique_daily (post_id, recorded_at),
|
||||
KEY idx_post_id (post_id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
```
|
||||
- Podpięty do hooków: `register_activation_hook(__FILE__, ...)` ORAZ `init` (z check przez `get_option('elementor_addon_db_version')` — jeśli wersja != '1.0', wywołaj i ustaw opcję)
|
||||
|
||||
2. **Funkcja zapisująca ceny** `elementor_addon_record_prices()`:
|
||||
- WP_Query: post_type='apartamenty', post_status='publish', posts_per_page=-1, fields='ids'
|
||||
- Dla każdego ID: pobiera meta `information_price`, `information_price_m2`, `information_floor_space`
|
||||
- Wstawia do tabeli przez `$wpdb->query($wpdb->prepare("INSERT IGNORE INTO ...", ...))`
|
||||
- `recorded_at` = `current_time('Y-m-d')`
|
||||
|
||||
3. **Rejestracja crona**:
|
||||
- Na hooku `wp` (nie `init`): jeśli `!wp_next_scheduled('apartamenty_record_prices')` → `wp_schedule_event(time(), 'daily', 'apartamenty_record_prices')`
|
||||
- `add_action('apartamenty_record_prices', 'elementor_addon_record_prices')`
|
||||
|
||||
Unikaj: global $wpdb wewnątrz funkcji — używaj `global $wpdb` na początku każdej funkcji korzystającej z $wpdb.
|
||||
Unikaj: bezpośrednich zapytań SQL bez $wpdb->prepare() tam gdzie są parametry użytkownika.
|
||||
</action>
|
||||
<verify>
|
||||
1. Aktywuj/dezaktywuj plugin lub odwiedź dowolną stronę WP
|
||||
2. Sprawdź przez phpMyAdmin lub: `SELECT * FROM wp_price_history LIMIT 5;`
|
||||
3. Sprawdź: `SELECT option_value FROM wp_options WHERE option_name = 'elementor_addon_db_version';` → powinno zwrócić '1.0'
|
||||
4. Sprawdź cron: `SELECT * FROM wp_options WHERE option_name = 'cron';` — powinien zawierać 'apartamenty_record_prices'
|
||||
</verify>
|
||||
<done>AC-1 i AC-2 spełnione: tabela istnieje, cron zarejestrowany</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2: AJAX endpoint historii cen</name>
|
||||
<files>wp-content/plugins/elementor-addon/elementor-addon.php</files>
|
||||
<action>
|
||||
Dodaj na końcu pliku `elementor-addon.php` funkcję `elementor_addon_get_price_history_ajax()`:
|
||||
|
||||
1. **Weryfikacja nonce**: `check_ajax_referer('apartamenty_price_history_nonce', 'nonce', false)` — jeśli false: `wp_send_json_error()` i `die()`
|
||||
|
||||
2. **Walidacja post_id**: `$post_id = absint($_POST['post_id'] ?? 0)` — jeśli 0: `wp_send_json_error()`
|
||||
|
||||
3. **Pobierz tytuł i aktualne ceny**:
|
||||
- `get_the_title($post_id)`
|
||||
- `get_post_meta($post_id, 'information_price', true)`
|
||||
- `get_post_meta($post_id, 'information_price_m2', true)`
|
||||
- `get_post_meta($post_id, 'information_floor_space', true)`
|
||||
|
||||
4. **Pobierz historię z DB**:
|
||||
```php
|
||||
$rows = $wpdb->get_results($wpdb->prepare(
|
||||
"SELECT recorded_at, price, price_m2, floor_space FROM {$wpdb->prefix}price_history
|
||||
WHERE post_id = %d ORDER BY recorded_at DESC LIMIT 50",
|
||||
$post_id
|
||||
));
|
||||
```
|
||||
|
||||
5. **Zwróć JSON**:
|
||||
```php
|
||||
wp_send_json_success([
|
||||
'title' => get_the_title($post_id),
|
||||
'price' => get_post_meta($post_id, 'information_price', true),
|
||||
'price_m2' => get_post_meta($post_id, 'information_price_m2', true),
|
||||
'floor_space'=> get_post_meta($post_id, 'information_floor_space', true),
|
||||
'history' => $rows,
|
||||
]);
|
||||
```
|
||||
|
||||
6. **Zarejestruj akcję** (dla zalogowanych i niezalogowanych):
|
||||
```php
|
||||
add_action('wp_ajax_apartamenty_get_price_history', 'elementor_addon_get_price_history_ajax');
|
||||
add_action('wp_ajax_nopriv_apartamenty_get_price_history', 'elementor_addon_get_price_history_ajax');
|
||||
```
|
||||
|
||||
7. **Przekaż nonce do JS** przez `wp_localize_script` na istniejący skrypt `elementor-addon-main-js`:
|
||||
Dodaj nowy hook `wp_enqueue_scripts` z `wp_localize_script('elementor-addon-main-js', 'apartamentsData', ['ajaxUrl' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('apartamenty_price_history_nonce')])`
|
||||
WAŻNE: `wp_localize_script` musi być wywołane po enqueue/register skryptu.
|
||||
</action>
|
||||
<verify>
|
||||
Wykonaj w przeglądarce lub przez curl (po uzyskaniu nonce):
|
||||
POST /wp-admin/admin-ajax.php z body: action=apartamenty_get_price_history&post_id=203&nonce=XXXX
|
||||
Oczekiwana odpowiedź: {"success":true,"data":{"title":"Apartament 15","price":"677 920",...}}
|
||||
</verify>
|
||||
<done>AC-3 i AC-4 spełnione: endpoint zwraca dane JSON, nonce weryfikowany</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<boundaries>
|
||||
|
||||
## DO NOT CHANGE
|
||||
- wp-content/plugins/elementor-addon/widgets/apartaments.php (zmiany w planie 01-02)
|
||||
- wp-content/plugins/elementor-addon/assets/css/main.css (zmiany w planie 01-02)
|
||||
- wp-content/plugins/elementor-addon/assets/js/main.js (zmiany w planie 01-02)
|
||||
- wp-config.php
|
||||
- Inne wtyczki i motyw
|
||||
|
||||
## SCOPE LIMITS
|
||||
- Ten plan nie dodaje UI ani CSS — tylko backend PHP
|
||||
- Nie modyfikuj istniejących funkcji w elementor-addon.php, tylko dodawaj nowe na końcu
|
||||
- Nie twórz osobnego pliku PHP — wszystko w elementor-addon.php
|
||||
|
||||
</boundaries>
|
||||
|
||||
<verification>
|
||||
Before declaring plan complete:
|
||||
- [ ] Tabela `wp_price_history` istnieje w bazie (sprawdź przez phpMyAdmin lub SQL)
|
||||
- [ ] Opcja `elementor_addon_db_version` = '1.0' w wp_options
|
||||
- [ ] Cron `apartamenty_record_prices` widoczny w wp_options cron
|
||||
- [ ] AJAX action `apartamenty_get_price_history` odpowiada JSON z `success: true`
|
||||
- [ ] Request bez nonce zwraca `success: false`
|
||||
- [ ] Brak PHP Fatal errors (sprawdź wp-content/debug.log lub WP_DEBUG)
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- Tabela wp_price_history utworzona z poprawnym schematem
|
||||
- Cron rejestruje się automatycznie bez manualnej aktywacji
|
||||
- AJAX endpoint działa dla publicznych użytkowników (nopriv)
|
||||
- Nonce zabezpiecza endpoint
|
||||
- Żadnych błędów PHP
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
Po ukończeniu utwórz `.paul/phases/01-historia-cen/01-01-SUMMARY.md`
|
||||
</output>
|
||||
109
.paul/phases/01-historia-cen/01-01-SUMMARY.md
Normal file
109
.paul/phases/01-historia-cen/01-01-SUMMARY.md
Normal file
@@ -0,0 +1,109 @@
|
||||
---
|
||||
phase: 01-historia-cen
|
||||
plan: 01
|
||||
subsystem: database
|
||||
tags: [wordpress, acf, cron, ajax, mysql]
|
||||
|
||||
requires: []
|
||||
provides:
|
||||
- "Tabela wp_price_history z unikalnym kluczem (post_id, recorded_at)"
|
||||
- "WP Cron dzienny zapisujący ceny apartamentów z ACF"
|
||||
- "AJAX endpoint apartamenty_get_price_history (zalogowani i goście)"
|
||||
- "wp_localize_script przekazujący ajaxUrl + nonce do JS"
|
||||
affects: ["01-02-frontend"]
|
||||
|
||||
tech-stack:
|
||||
added: []
|
||||
patterns:
|
||||
- "INSERT IGNORE dla idempotentnego zapisu dziennego"
|
||||
- "dbDelta() + get_option version check dla migracji DB"
|
||||
- "wp_localize_script na priorytecie 20 po register"
|
||||
|
||||
key-files:
|
||||
modified:
|
||||
- "wp-content/plugins/elementor-addon/elementor-addon.php"
|
||||
|
||||
key-decisions:
|
||||
- "ACF flat meta keys (information_price, information_price_m2, information_floor_space) zamiast serializowanego pola 'information'"
|
||||
- "INSERT IGNORE zamiast ON DUPLICATE KEY UPDATE — historia to snapshot, nie aktualizacja"
|
||||
- "Hook 'wp' dla crona (nie 'init') — gwarantuje kontekst frontendu"
|
||||
|
||||
patterns-established:
|
||||
- "Nonce: apartamenty_price_history_nonce — używany w JS i PHP"
|
||||
- "AJAX action: apartamenty_get_price_history"
|
||||
- "Dane JS: window.apartamentsData.ajaxUrl, .nonce"
|
||||
|
||||
duration: 15min
|
||||
started: 2026-03-12T14:00:00Z
|
||||
completed: 2026-03-12T14:15:00Z
|
||||
---
|
||||
|
||||
# Faza 1 Plan 01: Backend Historii Cen — Summary
|
||||
|
||||
**Tabela `wp_price_history` + WP Cron dzienny + AJAX endpoint zabezpieczony nonce — cały backend historii cen gotowy.**
|
||||
|
||||
## Performance
|
||||
|
||||
| Metryka | Wartość |
|
||||
|---------|---------|
|
||||
| Czas wykonania | ~15 min |
|
||||
| Zadania | 2 ukończone |
|
||||
| Pliki zmienione | 1 |
|
||||
|
||||
## Acceptance Criteria Results
|
||||
|
||||
| Kryterium | Status | Uwagi |
|
||||
|-----------|--------|-------|
|
||||
| AC-1: Tabela istnieje w bazie | Pass | Zweryfikowano przez bezpośrednie połączenie DB — tabela `wp_price_history` założona |
|
||||
| AC-2: Cron zapisuje ceny codziennie | Pass | Hook `apartamenty_record_prices` zarejestrowany na `daily`, INSERT IGNORE zweryfikowany |
|
||||
| AC-3: AJAX endpoint zwraca historię | Pass | Akcje `wp_ajax_*` zarejestrowane, struktura JSON zgodna z planem |
|
||||
| AC-4: Nonce zabezpiecza AJAX | Pass | `check_ajax_referer` przed jakimkolwiek dostępem do danych |
|
||||
|
||||
## Accomplishments
|
||||
|
||||
- Tabela `wp_price_history` założona w bazie przez `dbDelta()` — samonaprawiająca się migracja
|
||||
- WP Cron `apartamenty_record_prices` dzienny — zapis snapshot cen ze wszystkich apartamentów
|
||||
- AJAX endpoint publiczny (nopriv) zwracający tytuł, aktualne ceny i historię jako JSON
|
||||
- Nonce `apartamenty_price_history_nonce` przekazany do JS przez `wp_localize_script`
|
||||
|
||||
## Files Created/Modified
|
||||
|
||||
| Plik | Zmiana | Co dodano |
|
||||
|------|--------|-----------|
|
||||
| `wp-content/plugins/elementor-addon/elementor-addon.php` | Zmodyfikowany | +~140 linii: tabela DB, cron, AJAX endpoint, wp_localize_script |
|
||||
|
||||
## Decisions Made
|
||||
|
||||
| Decyzja | Uzasadnienie | Wpływ |
|
||||
|---------|--------------|-------|
|
||||
| Flat meta keys zamiast `get_field('information')` | ACF zapisuje dane jako płaskie klucze — `information_price` etc. istnieją i są puste dla `information` | Cron pobiera dane bezpośrednio przez `get_post_meta` |
|
||||
| INSERT IGNORE zamiast UPDATE | Historia to snapshot — nie nadpisujemy dawnych wpisów | Jeden rekord na apartament na dzień, bezpieczny dla wielu wywołań |
|
||||
| Hook `wp` dla crona | Gwarantuje pełny kontekst WP przy rejestracji | Cron rejestruje się tylko na stronach frontendowych/adminowych |
|
||||
|
||||
## Deviations from Plan
|
||||
|
||||
Brak odchyleń — plan wykonany dokładnie jak zaplanowano.
|
||||
|
||||
## Issues Encountered
|
||||
|
||||
| Problem | Rozwiązanie |
|
||||
|---------|-------------|
|
||||
| Brak klienta mysql CLI na lokalnym środowisku | Weryfikacja przez PHP mysqli (tymczasowy skrypt `.paul/verify_task1.php`, usunięty po weryfikacji) |
|
||||
|
||||
## Next Phase Readiness
|
||||
|
||||
**Gotowe dla 01-02 (Frontend):**
|
||||
- `window.apartamentsData.ajaxUrl` — URL do admin-ajax.php
|
||||
- `window.apartamentsData.nonce` — nonce do requestu
|
||||
- AJAX action: `apartamenty_get_price_history` z `post_id` w POST body
|
||||
- Odpowiedź JSON: `{ success, data: { title, price, price_m2, floor_space, history: [{recorded_at, price, price_m2}] } }`
|
||||
|
||||
**Uwagi:**
|
||||
- Tabela ma 1 testowy rekord dla apt 203 (2026-03-12) — wstawiony podczas weryfikacji
|
||||
- Cron uruchomi się automatycznie przy pierwszym odwiedzeniu strony przez WP
|
||||
|
||||
**Blokady:** Brak
|
||||
|
||||
---
|
||||
*Phase: 01-historia-cen, Plan: 01*
|
||||
*Completed: 2026-03-12*
|
||||
598
.paul/phases/01-historia-cen/01-02-PLAN.md
Normal file
598
.paul/phases/01-historia-cen/01-02-PLAN.md
Normal file
@@ -0,0 +1,598 @@
|
||||
---
|
||||
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
|
||||
---
|
||||
|
||||
<objective>
|
||||
## 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
|
||||
</objective>
|
||||
|
||||
<context>
|
||||
## 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
|
||||
</context>
|
||||
|
||||
<acceptance_criteria>
|
||||
|
||||
## 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"
|
||||
```
|
||||
|
||||
</acceptance_criteria>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 1: Dodaj data-post-id do przycisku i popup HTML do widgetu</name>
|
||||
<files>wp-content/plugins/elementor-addon/widgets/apartaments.php</files>
|
||||
<action>
|
||||
**Zmiana 1: Wiersz "HISTORIA CEN"**
|
||||
|
||||
Znajdź wiersz z klasą `apartament-card__price-history` (linia ~157).
|
||||
Zmień `<td class="apartament-card__info_table-value">` na:
|
||||
```php
|
||||
<td class="apartament-card__info_table-value btn-historia-cen"
|
||||
data-post-id="<?php echo esc_attr( get_the_ID() ); ?>">
|
||||
```
|
||||
Reszta wiersza (tekst "HISTORIA CEN" + SVG) pozostaje bez zmian.
|
||||
|
||||
**Zmiana 2: Globalny popup HTML**
|
||||
|
||||
Dodaj PRZED `<?php wp_reset_postdata(); ?>` (czyli po zamknięciu pętli while)
|
||||
następujący HTML popupa (jeden egzemplarz dla całej strony):
|
||||
|
||||
```php
|
||||
<?php // Popup historia cen — jeden globalny, wypełniany przez JS ?>
|
||||
<div class="price-history-overlay" id="price-history-overlay" aria-hidden="true">
|
||||
<div class="price-history-modal" role="dialog" aria-modal="true">
|
||||
<button class="price-history-modal__close" id="price-history-close" aria-label="Zamknij">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 14 14" fill="none">
|
||||
<path d="M13 1L1 13M1 1L13 13" stroke="#192C44" stroke-width="2" stroke-linecap="round"/>
|
||||
</svg>
|
||||
</button>
|
||||
<h3 class="price-history-modal__title" id="price-history-title"></h3>
|
||||
<div class="price-history-modal__current">
|
||||
<div class="price-history-modal__row price-history-modal__row--bold">
|
||||
<span>Cena brutto:</span>
|
||||
<span class="price-history-modal__val" id="price-history-price"></span>
|
||||
</div>
|
||||
<div class="price-history-modal__row">
|
||||
<span>Cena m<sup>2</sup>:</span>
|
||||
<span class="price-history-modal__val" id="price-history-price-m2"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="price-history-modal__table-wrap">
|
||||
<table class="price-history-modal__table">
|
||||
<tbody id="price-history-tbody"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
Unikaj: dodawania popup wewnątrz pętli while — tylko jeden egzemplarz na stronę.
|
||||
</action>
|
||||
<verify>
|
||||
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.
|
||||
</verify>
|
||||
<done>AC-1 spełnione: przycisk ma data-post-id; popup HTML istnieje w DOM</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2: CSS popup — main.scss i main.css</name>
|
||||
<files>
|
||||
wp-content/plugins/elementor-addon/assets/css/main.scss,
|
||||
wp-content/plugins/elementor-addon/assets/css/main.css
|
||||
</files>
|
||||
<action>
|
||||
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.
|
||||
</action>
|
||||
<verify>
|
||||
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).
|
||||
</verify>
|
||||
<done>AC-2, AC-3 (style): modal wygląda zgodnie z projektem</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 3: JS — obsługa kliknięcia, AJAX, render popupa</name>
|
||||
<files>wp-content/plugins/elementor-addon/assets/js/main.js</files>
|
||||
<action>
|
||||
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 = '<tr><td colspan="3">Brak historii cen</td></tr>';
|
||||
return;
|
||||
}
|
||||
|
||||
tbody.innerHTML = d.history.map(function (row) {
|
||||
return '<tr>' +
|
||||
'<td>' + (row.recorded_at || '') + '</td>' +
|
||||
'<td>' + (row.price_m2 ? row.price_m2 + ' zł/m²' : '—') + '</td>' +
|
||||
'<td>' + (row.price ? row.price + ' zł' : '—') + '</td>' +
|
||||
'</tr>';
|
||||
}).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.
|
||||
</action>
|
||||
<verify>
|
||||
`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...".
|
||||
</verify>
|
||||
<done>AC-2, AC-3, AC-4, AC-5 spełnione: popup otwiera się z danymi AJAX, zamyka się poprawnie</done>
|
||||
</task>
|
||||
|
||||
<task type="checkpoint:human-verify" gate="blocking">
|
||||
<what-built>
|
||||
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.
|
||||
</what-built>
|
||||
<how-to-verify>
|
||||
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
|
||||
</how-to-verify>
|
||||
<resume-signal>Wpisz "zatwierdzone" lub opisz problemy do poprawienia</resume-signal>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<boundaries>
|
||||
|
||||
## 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
|
||||
|
||||
</boundaries>
|
||||
|
||||
<verification>
|
||||
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
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- 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)
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
Po ukończeniu utwórz `.paul/phases/01-historia-cen/01-02-SUMMARY.md`
|
||||
</output>
|
||||
118
.paul/phases/01-historia-cen/01-02-SUMMARY.md
Normal file
118
.paul/phases/01-historia-cen/01-02-SUMMARY.md
Normal file
@@ -0,0 +1,118 @@
|
||||
---
|
||||
phase: 01-historia-cen
|
||||
plan: 02
|
||||
subsystem: ui
|
||||
tags: [popup, modal, ajax, vanilla-js, scss, css, widget]
|
||||
|
||||
requires:
|
||||
- phase: 01-historia-cen
|
||||
plan: 01
|
||||
provides: AJAX endpoint apartamenty_get_price_history + wp_localize_script (apartamentsData)
|
||||
|
||||
provides:
|
||||
- popup „Historia cen" z danymi AJAX na stronie /apartamenty/
|
||||
- przycisk .btn-historia-cen z data-post-id w widgecie
|
||||
- style .price-history-modal / .price-history-overlay w main.scss + main.css
|
||||
- JS handler: fetch AJAX → render modal → open/close
|
||||
|
||||
affects: []
|
||||
|
||||
tech-stack:
|
||||
added: []
|
||||
patterns:
|
||||
- Globalny popup (jeden egzemplarz poza pętlą while) wypełniany przez JS
|
||||
- Vanilla JS fetch + FormData zamiast jQuery AJAX
|
||||
- CSS class toggle (is-open) zamiast display inline
|
||||
|
||||
key-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
|
||||
|
||||
key-decisions:
|
||||
- "ID price-history-sqm zamiast price-history-price-m2 — spójna zmiana w HTML i JS"
|
||||
- "max-height + overflow-y:auto na .price-history-modal__table-wrap — scroll gdy dużo historii"
|
||||
|
||||
patterns-established:
|
||||
- "Popup globalny poza pętlą — jeden egzemplarz na stronę, wypełniany przez JS"
|
||||
- "Vanilla JS fetch z FormData dla AJAX WordPress (bez jQuery)"
|
||||
|
||||
duration: ~30min
|
||||
started: 2026-03-12T00:00:00Z
|
||||
completed: 2026-03-12T00:30:00Z
|
||||
---
|
||||
|
||||
# Faza 01 Plan 02: Popup Historia Cen — Summary
|
||||
|
||||
**Klikalny modal „Historia cen" na kartach apartamentów — AJAX, vanilla JS, CSS zgodny z projektem.**
|
||||
|
||||
## Performance
|
||||
|
||||
| Metric | Value |
|
||||
|--------|-------|
|
||||
| Duration | ~30 min |
|
||||
| Started | 2026-03-12 |
|
||||
| Completed | 2026-03-12 |
|
||||
| Tasks | 3 auto + 1 checkpoint |
|
||||
| Files modified | 4 |
|
||||
|
||||
## Acceptance Criteria Results
|
||||
|
||||
| Criterion | Status | Notes |
|
||||
|-----------|--------|-------|
|
||||
| AC-1: Przycisk klikalny z data-post-id | Pass | `.btn-historia-cen[data-post-id]` w każdym wierszu |
|
||||
| AC-2: Popup otwiera się z danymi | Pass | Tytuł, cena brutto, cena m², tabela historii z AJAX |
|
||||
| AC-3: Popup zamyka się | Pass | X, klik overlay, Escape — wszystkie działają |
|
||||
| AC-4: Stan ładowania i błędu | Pass | „Ładowanie..." przy otwarciu, „Brak danych" / „Błąd ładowania" przy problemach |
|
||||
| AC-5: Pusta historia | Pass | `<td colspan="3">Brak historii cen</td>` gdy historia pusta |
|
||||
|
||||
## Accomplishments
|
||||
|
||||
- Modal popup bez bibliotek zewnętrznych — vanilla JS + CSS
|
||||
- Jeden egzemplarz popupa na stronę (poza pętlą `while`) — poprawna architektura
|
||||
- Scroll w tabeli historii (`max-height: 40vh; overflow-y: auto`) gdy dużo wpisów
|
||||
- Stan ładowania i pełna obsługa błędów AJAX
|
||||
|
||||
## Files Created/Modified
|
||||
|
||||
| File | Change | Purpose |
|
||||
|------|--------|---------|
|
||||
| `widgets/apartaments.php` | Modified | Dodano `.btn-historia-cen` + `data-post-id` + popup HTML |
|
||||
| `assets/css/main.scss` | Modified | Dodano style `.price-history-overlay` i `.price-history-modal` |
|
||||
| `assets/css/main.css` | Modified | Skompilowana wersja CSS (append) |
|
||||
| `assets/js/main.js` | Modified | Dodano obsługę AJAX + open/close popupa |
|
||||
|
||||
## Decisions Made
|
||||
|
||||
| Decision | Rationale | Impact |
|
||||
|----------|-----------|--------|
|
||||
| ID `price-history-sqm` zamiast `price-history-price-m2` | Krótsza nazwa, spójna w HTML i JS | Brak wpływu na działanie |
|
||||
| `max-height` + `overflow-y:auto` na tabeli | Ochrona przed bardzo długą historią cen | Lepsze UX przy wielu wpisach |
|
||||
|
||||
## Deviations from Plan
|
||||
|
||||
| Type | Opis | Impact |
|
||||
|------|------|--------|
|
||||
| Auto-fixed | ID `price-history-price-m2` → `price-history-sqm` | Kosmetyczna, HTML i JS spójne |
|
||||
|
||||
## Issues Encountered
|
||||
|
||||
None.
|
||||
|
||||
## Next Phase Readiness
|
||||
|
||||
**Ready:**
|
||||
- Faza 01 kompletna — oba plany (01-01 i 01-02) ukończone
|
||||
- Milestone v0.1 ma ukończone obie fazy (01 i 02)
|
||||
|
||||
**Concerns:**
|
||||
- None
|
||||
|
||||
**Blockers:**
|
||||
- None
|
||||
|
||||
---
|
||||
*Phase: 01-historia-cen, Plan: 02*
|
||||
*Completed: 2026-03-12*
|
||||
Reference in New Issue
Block a user