update
This commit is contained in:
@@ -12,7 +12,7 @@ Użytkownicy mogą przeglądać na stronie ofertę dewelopera.
|
||||
|
||||
| Attribute | Value |
|
||||
|-----------|-------|
|
||||
| Version | 0.1.0 |
|
||||
| Version | 0.2.0 |
|
||||
| Status | Released |
|
||||
| Last Updated | 2026-03-12 |
|
||||
|
||||
@@ -30,6 +30,13 @@ Użytkownicy mogą przeglądać na stronie ofertę dewelopera.
|
||||
|
||||
- (brak — gotowy na nowy milestone)
|
||||
|
||||
### Validated (Shipped) — v0.2
|
||||
|
||||
- [x] Widget Elementor wyświetla ceny miejsc postojowych (zwykłe i rodzinne) — v0.2
|
||||
- [x] Popup historia cen dla miejsc postojowych (reuse apartamentów) — v0.2
|
||||
- [x] Cron zapisuje ceny parkingowe codziennie do wp_parking_price_history — v0.2
|
||||
- [x] XML /ceny-mieszkan.xml zawiera sekcję <miejsca_postojowe> — v0.2
|
||||
|
||||
### Planned (Next)
|
||||
|
||||
- (do zdefiniowania podczas następnego planowania)
|
||||
@@ -72,4 +79,4 @@ Użytkownicy mogą przeglądać na stronie ofertę dewelopera.
|
||||
|
||||
---
|
||||
*Created: 2026-03-12*
|
||||
*Last updated: 2026-03-12 after v0.1 Initial Release*
|
||||
*Last updated: 2026-03-25 after v0.2 Miejsca Postojowe*
|
||||
|
||||
@@ -9,12 +9,23 @@ Strona internetowa dla dewelopera pozwalająca na okazanie oferty klientom. Proj
|
||||
Brak aktywnego milestone.
|
||||
Uruchom `/paul:discuss-milestone` lub `/paul:milestone` aby zdefiniować następny.
|
||||
|
||||
## Next Milestone
|
||||
|
||||
Uruchom `/paul:discuss-milestone` lub `/paul:milestone` aby zdefiniować.
|
||||
|
||||
## Completed Milestones
|
||||
|
||||
<details>
|
||||
<summary>v0.2 Miejsca Postojowe - 2026-03-25 (1 faza)</summary>
|
||||
|
||||
| Phase | Name | Plans | Completed |
|
||||
|-------|------|-------|-----------|
|
||||
| 3 | Miejsca postojowe | 1/1 | 2026-03-25 |
|
||||
|
||||
**Kluczowe deliverables:**
|
||||
- Widget Elementor "Miejsca Postojowe" (zwykłe + rodzinne)
|
||||
- Historia cen z popup (reuse apartamentów)
|
||||
- Cron dzienny zapis cen parkingowych (wp_parking_price_history)
|
||||
- XML export rozszerzony o sekcję <miejsca_postojowe>
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>v0.1 Initial Release - 2026-03-12 (2 fazy)</summary>
|
||||
|
||||
@@ -31,4 +42,4 @@ Uruchom `/paul:discuss-milestone` lub `/paul:milestone` aby zdefiniować.
|
||||
</details>
|
||||
|
||||
---
|
||||
*Roadmap updated: 2026-03-12 after v0.1 Initial Release*
|
||||
*Roadmap updated: 2026-03-25 after v0.2 Miejsca Postojowe*
|
||||
|
||||
@@ -5,25 +5,27 @@
|
||||
See: .paul/PROJECT.md (updated 2026-03-12)
|
||||
|
||||
**Core value:** Uzytkownik moze przegladac oferte dewelopera i sprawdzac historie cen
|
||||
**Current focus:** Milestone v0.1 ukonczone — gotowy na nowy milestone
|
||||
**Current focus:** Milestone v0.2 Miejsca Postojowe — ukonczone
|
||||
|
||||
## Current Position
|
||||
|
||||
Milestone: Awaiting next milestone
|
||||
Phase: None active
|
||||
Plan: None
|
||||
Status: Milestone v0.1 Initial Release complete — ready for next
|
||||
Last activity: 2026-03-12 — Milestone v0.1 completed
|
||||
Milestone: v0.2 Miejsca Postojowe — Complete
|
||||
Phase: [3] of [3] (Miejsca postojowe) — Complete
|
||||
Plan: 03-01 complete
|
||||
Status: Milestone v0.2 complete — ready for next
|
||||
Last activity: 2026-03-25 — UNIFY 03-01 complete
|
||||
|
||||
Progress:
|
||||
- v0.1 Initial Release: [██████████] 100% ✓
|
||||
- v0.2 Miejsca Postojowe: [██████████] 100% ✓
|
||||
- Phase 3: [██████████] 100% ✓
|
||||
|
||||
## Loop Position
|
||||
|
||||
Current loop state:
|
||||
```
|
||||
PLAN ──▶ APPLY ──▶ UNIFY
|
||||
○ ○ ○ [Milestone complete — ready for next]
|
||||
✓ ✓ ✓ [Loop complete — milestone done]
|
||||
```
|
||||
|
||||
## Accumulated Context
|
||||
@@ -35,6 +37,9 @@ PLAN ──▶ APPLY ──▶ UNIFY
|
||||
| INSERT IGNORE w cronie | Phase 1 | Jeden rekord na apt na dzien, idempotentny |
|
||||
| XML jako czysty PHP string ENT_XML1 | Phase 2 | Brak zaleznosci od ext-dom |
|
||||
| Transient 1h + inwalidacja przez cron | Phase 2 | Cache XML odswieza sie po kazdym cronie |
|
||||
| ACF group access: get_field('grupa','option')['pole'] | Phase 3 | Parking ceny w grupach ACF |
|
||||
| Osobna tabela wp_parking_price_history | Phase 3 | Separacja od apartamentow (parking_type vs post_id) |
|
||||
| Cena m2 ukryta w widgecie parkingowym | Phase 3 | Dane nadal w cronie/XML, UI uproszczone |
|
||||
|
||||
### Deferred Issues
|
||||
- Klient musi recznie zglosic URL /dane-gov-pl.xml do kontakt@dane.gov.pl
|
||||
@@ -45,14 +50,13 @@ Brak.
|
||||
|
||||
## Session Continuity
|
||||
|
||||
Last session: 2026-03-12
|
||||
Stopped at: Milestone v0.1 Initial Release ukonczone
|
||||
Last session: 2026-03-25
|
||||
Stopped at: Milestone v0.2 complete
|
||||
Next action: /paul:discuss-milestone
|
||||
Resume file: .paul/MILESTONES.md
|
||||
Resume file: .paul/phases/03-miejsca-postojowe/03-01-SUMMARY.md
|
||||
Resume context:
|
||||
- v0.1 kompletny: historia cen (popup AJAX) + jawnosc cen (XML endpoints)
|
||||
- Dokumentacja klienta: docs/readme.md + docs/jawnosc-cen.md
|
||||
- Git tag v0.1.0 utworzony
|
||||
- v0.2 kompletny: widget Miejsca Postojowe + historia cen + cron + XML
|
||||
- Gotowy na nowy milestone
|
||||
|
||||
---
|
||||
*STATE.md — Aktualizowany po kazdej istotnej akcji*
|
||||
|
||||
256
.paul/phases/03-miejsca-postojowe/03-01-PLAN.md
Normal file
256
.paul/phases/03-miejsca-postojowe/03-01-PLAN.md
Normal file
@@ -0,0 +1,256 @@
|
||||
---
|
||||
phase: 03-miejsca-postojowe
|
||||
plan: 01
|
||||
type: execute
|
||||
wave: 1
|
||||
depends_on: []
|
||||
files_modified:
|
||||
- wp-content/plugins/elementor-addon/elementor-addon.php
|
||||
- wp-content/plugins/elementor-addon/widgets/parking-spots.php
|
||||
- wp-content/plugins/elementor-addon/assets/js/main.js
|
||||
- wp-content/plugins/elementor-addon/assets/css/main.css
|
||||
autonomous: false
|
||||
---
|
||||
|
||||
<objective>
|
||||
## Goal
|
||||
Stworzyc widget Elementor "Miejsca postojowe" wyswietlajacy ceny dwoch typow miejsc (zwykle i rodzinne) z przyciskiem "Historia cen" oraz popup, a takze rozszerzyc cron, AJAX i XML export o dane parkingowe.
|
||||
|
||||
## Purpose
|
||||
Deweloper musi prezentowac ceny miejsc postojowych na stronie oferty oraz raportowac je do dane.gov.pl zgodnie z ustawa o jawnosci cen.
|
||||
|
||||
## Output
|
||||
- Nowy widget Elementor: `Elementor_Parking_Spots`
|
||||
- Tabela DB: `wp_parking_price_history`
|
||||
- Rozszerzony cron o zapis cen parkingowych
|
||||
- Rozszerzony AJAX o historie cen parkingowych
|
||||
- Rozszerzony XML export o miejsca postojowe
|
||||
</objective>
|
||||
|
||||
<context>
|
||||
## Project Context
|
||||
@.paul/PROJECT.md
|
||||
@.paul/ROADMAP.md
|
||||
@.paul/STATE.md
|
||||
|
||||
## Source Files
|
||||
@wp-content/plugins/elementor-addon/elementor-addon.php
|
||||
@wp-content/plugins/elementor-addon/widgets/apartaments.php
|
||||
@wp-content/plugins/elementor-addon/assets/js/main.js
|
||||
@wp-content/plugins/elementor-addon/assets/css/main.css
|
||||
|
||||
## Existing Patterns
|
||||
- Apartament widget: klasa Elementor_Apartaments w widgets/apartaments.php
|
||||
- Historia cen popup: globalny overlay #price-history-overlay, wypelniany przez AJAX
|
||||
- AJAX endpoint: wp_ajax_apartamenty_get_price_history → JSON {title, price, price_m2, history}
|
||||
- Cron: apartamenty_record_prices → INSERT IGNORE do wp_price_history
|
||||
- XML: apartamenty_generate_price_xml() → <lokale> z <lokal> per apartament
|
||||
- ACF: parking prices sa w opcjach ACF (options page), klucze do odkrycia w DB
|
||||
- Garage map: get_field('garage_spots', 'option') → parking-N → status
|
||||
</context>
|
||||
|
||||
<skills>
|
||||
No specialized flows configured.
|
||||
</skills>
|
||||
|
||||
<acceptance_criteria>
|
||||
|
||||
## AC-1: Widget wyswietla ceny parkingowe
|
||||
```gherkin
|
||||
Given widget "Miejsca postojowe" jest dodany na stronie w Elementorze
|
||||
When uzytkownik otwiera strone
|
||||
Then widzi sekcje z dwoma typami: "Miejsce postojowe zwykle" i "Miejsce postojowe Rodzinne"
|
||||
And kazdy typ ma wyswietlona aktualna cene z ACF
|
||||
And kazdy typ ma przycisk "HISTORIA CEN"
|
||||
```
|
||||
|
||||
## AC-2: Popup historia cen dziala dla miejsc postojowych
|
||||
```gherkin
|
||||
Given uzytkownik widzi widget miejsc postojowych
|
||||
When klika przycisk "HISTORIA CEN" przy dowolnym typie
|
||||
Then otwiera sie popup z aktualna cena i tabela historii zmian cen
|
||||
And popup zamyka sie przyciskiem X, klikajac overlay lub klawiszem Escape
|
||||
```
|
||||
|
||||
## AC-3: Cron zapisuje ceny parkingowe codziennie
|
||||
```gherkin
|
||||
Given WP Cron jest zaplanowany
|
||||
When uruchamia sie dzienne zadanie
|
||||
Then aktualne ceny obu typow miejsc postojowych sa zapisywane do wp_parking_price_history
|
||||
And zapis jest idempotentny (INSERT IGNORE, jeden rekord na typ na dzien)
|
||||
```
|
||||
|
||||
## AC-4: XML export zawiera miejsca postojowe
|
||||
```gherkin
|
||||
Given endpoint /ceny-mieszkan.xml jest dostepny
|
||||
When uzytkownik pobiera XML
|
||||
Then XML zawiera sekcje <miejsca_postojowe> z oboma typami
|
||||
And kazdy typ ma aktualna cene i historie zmian
|
||||
```
|
||||
|
||||
</acceptance_criteria>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 1: Odkryj klucze ACF dla cen parkingowych i stworz tabele DB + cron + AJAX</name>
|
||||
<files>wp-content/plugins/elementor-addon/elementor-addon.php</files>
|
||||
<action>
|
||||
1. **Odkryj ACF meta keys** — sprawdz w bazie (wp_options) jakie klucze ACF sa uzywane dla cen miejsc postojowych. Prawdopodobnie sa w opcjach ACF (get_field z 'option'). Poszukaj kluczy zawierajacych "parking", "postojow", "garage_price" lub podobnych.
|
||||
|
||||
2. **Stworz tabele wp_parking_price_history:**
|
||||
```sql
|
||||
CREATE TABLE wp_parking_price_history (
|
||||
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
parking_type VARCHAR(50) NOT NULL, -- 'zwykle' lub 'rodzinne'
|
||||
price VARCHAR(50) NOT NULL DEFAULT '',
|
||||
recorded_at DATE NOT NULL,
|
||||
UNIQUE KEY unique_daily (parking_type, recorded_at),
|
||||
KEY idx_type (parking_type)
|
||||
)
|
||||
```
|
||||
- Dodaj funkcje tworzaca tabele analogicznie do elementor_addon_create_price_history_table()
|
||||
- Zaktualizuj elementor_addon_maybe_update_db() — zmien wersje DB na '1.1'
|
||||
|
||||
3. **Rozszerz cron** — w elementor_addon_record_prices() dodaj po petli apartamentow:
|
||||
- Pobierz ceny parkingowe z ACF options (odkryte klucze)
|
||||
- INSERT IGNORE do wp_parking_price_history dla kazdego typu
|
||||
|
||||
4. **Dodaj AJAX endpoint** — nowa funkcja `elementor_addon_get_parking_price_history_ajax()`:
|
||||
- Akcja: wp_ajax_parking_get_price_history / wp_ajax_nopriv_parking_get_price_history
|
||||
- Parametr: parking_type ('zwykle' lub 'rodzinne')
|
||||
- Zwraca: JSON {type_label, price, history: [{recorded_at, price}]}
|
||||
- Uzywa tego samego nonce co apartamenty
|
||||
|
||||
5. **Rozszerz XML** — w apartamenty_generate_price_xml() po zamknieciu </lokale>... nie, lepiej:
|
||||
- Zmien root element na <nieruchomosci> opakowujacy <lokale> i nowy <miejsca_postojowe>
|
||||
- LUB dodaj <miejsca_postojowe> wewnatrz <lokale> (prostsze)
|
||||
- Kazdy typ: <miejsce_postojowe typ="zwykle">, <cena>, <historia_cen>
|
||||
- Wyczysc transient po aktualizacji
|
||||
|
||||
Avoid:
|
||||
- NIE zmieniaj istniejacego schematu wp_price_history (apartamenty)
|
||||
- NIE usuwaj istniejacych endpointow XML — rozszerzaj
|
||||
</action>
|
||||
<verify>
|
||||
- Tabela wp_parking_price_history istnieje po aktywacji/upgrade
|
||||
- Reczne wywolanie crona zapisuje rekordy parkingowe
|
||||
- AJAX endpoint zwraca poprawny JSON
|
||||
- XML zawiera sekcje miejsca_postojowe
|
||||
</verify>
|
||||
<done>AC-3 i AC-4 satisfied: cron zapisuje ceny, XML eksportuje miejsca postojowe</done>
|
||||
</task>
|
||||
|
||||
<task type="checkpoint:human-verify" gate="blocking">
|
||||
<what-built>Backend: tabela DB, cron, AJAX endpoint, XML export dla miejsc postojowych</what-built>
|
||||
<how-to-verify>
|
||||
1. Sprawdz w wp-admin > Narzedzia > Jawnosc Cen czy XML zawiera miejsca postojowe
|
||||
2. Otworz /ceny-mieszkan.xml w przegladarce i sprawdz sekcje miejsca_postojowe
|
||||
3. Potwierdz ze ACF klucze zostaly poprawnie odkryte
|
||||
</how-to-verify>
|
||||
<resume-signal>Type "approved" to continue to widget, or describe issues</resume-signal>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2: Stworz widget Elementor "Miejsca postojowe" z przyciskiem historia cen</name>
|
||||
<files>
|
||||
wp-content/plugins/elementor-addon/widgets/parking-spots.php,
|
||||
wp-content/plugins/elementor-addon/assets/js/main.js,
|
||||
wp-content/plugins/elementor-addon/assets/css/main.css,
|
||||
wp-content/plugins/elementor-addon/elementor-addon.php
|
||||
</files>
|
||||
<action>
|
||||
1. **Stworz widgets/parking-spots.php** — klasa Elementor_Parking_Spots:
|
||||
- get_name() → 'parking_spots'
|
||||
- get_title() → 'Miejsca Postojowe'
|
||||
- get_style_depends / get_script_depends → te same co apartaments
|
||||
- render():
|
||||
a) Pobierz ceny z ACF options (odkryte klucze)
|
||||
b) Wyrenderuj dwie karty: "Miejsce postojowe zwykle" i "Miejsce postojowe Rodzinne"
|
||||
c) Kazda karta: nazwa typu, cena, przycisk "HISTORIA CEN" z data-parking-type="zwykle/rodzinne"
|
||||
d) Uzyj stylistyki zblizonej do apartament-card__info (tabela z cenami)
|
||||
e) Wyrenderuj ten sam popup overlay co apartamenty (reuse #price-history-overlay)
|
||||
— popup juz istnieje globalnie, wiec NIE renderuj go ponownie jesli jest na tej samej stronie
|
||||
— dodaj warunek: renderuj popup tylko jesli nie ma go jeszcze w DOM
|
||||
|
||||
2. **Zarejestruj widget** w elementor-addon.php:
|
||||
- W register_hello_world_widget() dodaj require + register dla Elementor_Parking_Spots
|
||||
|
||||
3. **Rozszerz main.js** — dodaj obsluge klikniecia .btn-parking-historia-cen:
|
||||
- Analogicznie do .btn-historia-cen ale wysyla parking_type zamiast post_id
|
||||
- Uzywa akcji 'parking_get_price_history'
|
||||
- Wypelnia ten sam popup (reuse)
|
||||
- Tabela historii: data, cena (bez ceny/m2 — uproszczona)
|
||||
|
||||
4. **Rozszerz main.css** — dodaj style dla widgetu parkingowego:
|
||||
- Klasa .parking-spots z kartami
|
||||
- Styl zbliżony do apartament-card ale uproszczony (bez galerii, dokumentow)
|
||||
- Responsive
|
||||
|
||||
Avoid:
|
||||
- NIE duplikuj popupa historia cen — uzyj istniejacego
|
||||
- NIE zmieniaj stylow apartamentow
|
||||
- NIE dodawaj Swipera/FancyBoxa — widget parkingowy ich nie potrzebuje
|
||||
</action>
|
||||
<verify>
|
||||
- Widget pojawia sie w panelu Elementor w kategorii "basic"
|
||||
- Po dodaniu na strone wyswietla dwa typy z cenami
|
||||
- Klikniecie "Historia cen" otwiera popup z danymi
|
||||
</verify>
|
||||
<done>AC-1 i AC-2 satisfied: widget wyswietla ceny, popup historia cen dziala</done>
|
||||
</task>
|
||||
|
||||
<task type="checkpoint:human-verify" gate="blocking">
|
||||
<what-built>Widget Elementor "Miejsca Postojowe" z historia cen — design wg Figma Frame 16</what-built>
|
||||
<how-to-verify>
|
||||
1. Otworz Elementor na stronie /apartamenty/ lub testowej
|
||||
2. Dodaj widget "Miejsca Postojowe"
|
||||
3. Sprawdz czy wyswietla dwa typy z cenami
|
||||
4. Kliknij "Historia cen" — sprawdz popup
|
||||
5. Porownaj z designem na Figmie (Frame 16)
|
||||
6. Sprawdz responsywnosc (mobile)
|
||||
</how-to-verify>
|
||||
<resume-signal>Type "approved" or describe visual/functional issues</resume-signal>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<boundaries>
|
||||
|
||||
## DO NOT CHANGE
|
||||
- wp-content/plugins/elementor-addon/widgets/apartaments.php (widget apartamentow stabilny)
|
||||
- wp-content/themes/hello-elementor/template-parts/garage-map.php (mapa garazowa)
|
||||
- Istniejacy schemat tabeli wp_price_history (nie modyfikuj kolumn)
|
||||
- Istniejace endpointy XML — rozszerzaj, nie usuwaj
|
||||
|
||||
## SCOPE LIMITS
|
||||
- Tylko dwa typy miejsc postojowych: zwykle i rodzinne
|
||||
- Brak galerii/dokumentow w widgecie parkingowym
|
||||
- Brak edycji cen w widgecie — ceny z ACF options
|
||||
- Strona admina Jawnosc Cen — nie modyfikuj (XML sam sie rozszerzy)
|
||||
|
||||
</boundaries>
|
||||
|
||||
<verification>
|
||||
Before declaring plan complete:
|
||||
- [ ] Widget "Miejsca Postojowe" renderuje dwa typy z cenami ACF
|
||||
- [ ] Przycisk "Historia cen" otwiera popup z danymi z AJAX
|
||||
- [ ] Cron zapisuje ceny parkingowe do wp_parking_price_history
|
||||
- [ ] /ceny-mieszkan.xml zawiera sekcje z miejscami postojowymi
|
||||
- [ ] Brak bledow PHP (WP_DEBUG=true)
|
||||
- [ ] Istniejace funkcjonalnosci apartamentow nie naruszone
|
||||
- [ ] All acceptance criteria met
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- All tasks completed
|
||||
- All verification checks pass
|
||||
- No PHP errors or warnings introduced
|
||||
- Widget dostepny w Elementor i poprawnie renderuje
|
||||
- Historia cen popup dziala identycznie jak dla apartamentow
|
||||
- XML export zawiera miejsca postojowe z historia
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
After completion, create `.paul/phases/03-miejsca-postojowe/03-01-SUMMARY.md`
|
||||
</output>
|
||||
134
.paul/phases/03-miejsca-postojowe/03-01-SUMMARY.md
Normal file
134
.paul/phases/03-miejsca-postojowe/03-01-SUMMARY.md
Normal file
@@ -0,0 +1,134 @@
|
||||
---
|
||||
phase: 03-miejsca-postojowe
|
||||
plan: 01
|
||||
subsystem: ui, api, database
|
||||
tags: [elementor, acf, wp-cron, xml, ajax, parking]
|
||||
|
||||
requires:
|
||||
- phase: 01-historia-cen
|
||||
provides: price_history table, popup pattern, AJAX pattern, XML endpoint
|
||||
- phase: 02-jawnosc-cen
|
||||
provides: XML export structure, dane.gov.pl integration
|
||||
provides:
|
||||
- Elementor widget "Miejsca Postojowe" with price display and history popup
|
||||
- wp_parking_price_history DB table
|
||||
- Daily cron recording parking prices
|
||||
- AJAX endpoint for parking price history
|
||||
- XML export extended with parking spots
|
||||
affects: []
|
||||
|
||||
tech-stack:
|
||||
added: []
|
||||
patterns: [ACF group field access pattern]
|
||||
|
||||
key-files:
|
||||
created:
|
||||
- wp-content/plugins/elementor-addon/widgets/parking-spots.php
|
||||
modified:
|
||||
- wp-content/plugins/elementor-addon/elementor-addon.php
|
||||
- wp-content/plugins/elementor-addon/assets/js/main.js
|
||||
- wp-content/plugins/elementor-addon/assets/css/main.css
|
||||
|
||||
key-decisions:
|
||||
- "ACF groups: pola cen w grupach, nie flat — get_field('grupa', 'option')['pole']"
|
||||
- "Osobna tabela wp_parking_price_history zamiast rozszerzania wp_price_history"
|
||||
- "Cena m2 ukryta w widgecie na życzenie użytkownika (dane nadal w cronie/XML)"
|
||||
- "Layout bez ramek, kolumnowy zamiast grid 2-kolumnowego"
|
||||
|
||||
patterns-established:
|
||||
- "ACF group field access: get_field('group_name', 'option')['group_name_field']"
|
||||
- "Parking popup reuse: fallback overlay usuwany jeśli apartamentowy istnieje"
|
||||
|
||||
duration: ~30min
|
||||
completed: 2026-03-25
|
||||
---
|
||||
|
||||
# Phase 3 Plan 01: Miejsca Postojowe Summary
|
||||
|
||||
**Widget Elementor wyswietlajacy ceny miejsc postojowych (zwykle/rodzinne) z historia cen, cron i XML export**
|
||||
|
||||
## Performance
|
||||
|
||||
| Metric | Value |
|
||||
|--------|-------|
|
||||
| Duration | ~30min |
|
||||
| Completed | 2026-03-25 |
|
||||
| Tasks | 2 auto + 2 checkpoints |
|
||||
| Files modified | 4 (1 new, 3 modified) |
|
||||
|
||||
## Acceptance Criteria Results
|
||||
|
||||
| Criterion | Status | Notes |
|
||||
|-----------|--------|-------|
|
||||
| AC-1: Widget wyswietla ceny parkingowe | Pass | Dwa typy z cenami z ACF grup, layout kolumnowy |
|
||||
| AC-2: Popup historia cen dziala | Pass | Reuse popup apartamentow, AJAX endpoint dziala |
|
||||
| AC-3: Cron zapisuje ceny codziennie | Pass | INSERT IGNORE do wp_parking_price_history |
|
||||
| AC-4: XML export zawiera miejsca postojowe | Pass | Sekcja <miejsca_postojowe> w /ceny-mieszkan.xml |
|
||||
|
||||
## Accomplishments
|
||||
|
||||
- Widget "Miejsca Postojowe" w Elementorze — 2 karty (zwykle/rodzinne) z ceną i przyciskiem historia cen
|
||||
- Tabela wp_parking_price_history z codziennym zapisem cen przez WP Cron
|
||||
- AJAX endpoint parking_get_price_history z popupem reusujacym overlay apartamentow
|
||||
- XML /ceny-mieszkan.xml rozszerzony o sekcje <miejsca_postojowe> z historia
|
||||
|
||||
## Files Created/Modified
|
||||
|
||||
| File | Change | Purpose |
|
||||
|------|--------|---------|
|
||||
| `widgets/parking-spots.php` | Created | Elementor widget rendering parking spot cards |
|
||||
| `elementor-addon.php` | Modified | DB table, cron extension, AJAX endpoint, XML export |
|
||||
| `assets/js/main.js` | Modified | Parking price history click handler + AJAX |
|
||||
| `assets/css/main.css` | Modified | Parking widget styles (no border, column layout) |
|
||||
|
||||
## Decisions Made
|
||||
|
||||
| Decision | Rationale | Impact |
|
||||
|----------|-----------|--------|
|
||||
| Osobna tabela wp_parking_price_history | Parking to opcje ACF, nie CPT — inna struktura (parking_type vs post_id) | Czysta separacja, brak zmian w istniejącej tabeli |
|
||||
| ACF group access pattern | Pola cen są w grupach ACF, nie flat | Wymaga get_field('grupa', 'option')['pole'] |
|
||||
| Ukrycie ceny m2 w widgecie | Request użytkownika — uproszczony wygląd | Dane m2 nadal w cronie i XML |
|
||||
| Layout bez ramek, kolumnowy | Request użytkownika — bliższy designowi Figma | Prostszy CSS |
|
||||
|
||||
## Deviations from Plan
|
||||
|
||||
### Summary
|
||||
|
||||
| Type | Count | Impact |
|
||||
|------|-------|--------|
|
||||
| User-requested changes | 2 | Design adjustments, no functional impact |
|
||||
| Auto-fixed | 1 | ACF group access pattern |
|
||||
|
||||
**Total impact:** Essential fixes + design preferences, no scope creep
|
||||
|
||||
### Details
|
||||
|
||||
1. **ACF group fields** — Plan zakladal flat access (get_field('pole', 'option')). Odkryto ze pola sa w grupach ACF. Naprawione na get_field('grupa', 'option')['pole'].
|
||||
|
||||
2. **Ukrycie ceny m2** — User request. Usunieto z HTML widgetu, dane nadal zapisywane.
|
||||
|
||||
3. **Layout bez ramek, kolumnowy** — User request. Zmieniono z grid 2-kolumnowego z ramkami na flex column bez ramek.
|
||||
|
||||
## Issues Encountered
|
||||
|
||||
| Issue | Resolution |
|
||||
|-------|------------|
|
||||
| Figma MCP niedostępny (oryginalny plik) | User udostępnił kopię, pobrano screenshot Frame 16 |
|
||||
| Ceny nie wyświetlały się po wdrożeniu | ACF pola w grupach — naprawiony pattern dostępu |
|
||||
|
||||
## Next Phase Readiness
|
||||
|
||||
**Ready:**
|
||||
- Milestone v0.2 Miejsca Postojowe kompletny (1 faza, 1 plan)
|
||||
- Wszystkie AC spełnione
|
||||
|
||||
**Concerns:**
|
||||
- WP Cron pseudocron — na produkcji zalecany systemowy cron
|
||||
- Cena m2 ukryta — może wymagać przywrócenia w przyszłości
|
||||
|
||||
**Blockers:**
|
||||
- None
|
||||
|
||||
---
|
||||
*Phase: 03-miejsca-postojowe, Plan: 01*
|
||||
*Completed: 2026-03-25*
|
||||
365
.vscode/ftp-kr.sync.cache.json
vendored
365
.vscode/ftp-kr.sync.cache.json
vendored
@@ -84,8 +84,8 @@
|
||||
"css": {
|
||||
"custom.css": {
|
||||
"type": "-",
|
||||
"size": 1690,
|
||||
"lmtime": 1774269957773,
|
||||
"size": 1946,
|
||||
"lmtime": 1774339565638,
|
||||
"modified": false
|
||||
},
|
||||
"custom.css.map": {
|
||||
@@ -97,19 +97,19 @@
|
||||
"customizer.asset.php": {
|
||||
"type": "-",
|
||||
"size": 84,
|
||||
"lmtime": 0,
|
||||
"lmtime": 1774339388962,
|
||||
"modified": false
|
||||
},
|
||||
"customizer.css": {
|
||||
"type": "-",
|
||||
"size": 1263,
|
||||
"lmtime": 0,
|
||||
"lmtime": 1774339389031,
|
||||
"modified": false
|
||||
},
|
||||
"customizer-rtl.css": {
|
||||
"type": "-",
|
||||
"size": 1264,
|
||||
"lmtime": 0,
|
||||
"lmtime": 1774339389099,
|
||||
"modified": false
|
||||
},
|
||||
"custom.scss": {
|
||||
@@ -121,95 +121,407 @@
|
||||
"editor.asset.php": {
|
||||
"type": "-",
|
||||
"size": 84,
|
||||
"lmtime": 0,
|
||||
"lmtime": 1774339389171,
|
||||
"modified": false
|
||||
},
|
||||
"editor.css": {
|
||||
"type": "-",
|
||||
"size": 260,
|
||||
"lmtime": 0,
|
||||
"lmtime": 1774339389246,
|
||||
"modified": false
|
||||
},
|
||||
"editor-rtl.css": {
|
||||
"type": "-",
|
||||
"size": 260,
|
||||
"lmtime": 0,
|
||||
"lmtime": 1774339389324,
|
||||
"modified": false
|
||||
},
|
||||
"editor-styles.asset.php": {
|
||||
"type": "-",
|
||||
"size": 84,
|
||||
"lmtime": 0,
|
||||
"lmtime": 1774339389393,
|
||||
"modified": false
|
||||
},
|
||||
"editor-styles.css": {
|
||||
"type": "-",
|
||||
"size": 564,
|
||||
"lmtime": 0,
|
||||
"lmtime": 1774339389469,
|
||||
"modified": false
|
||||
},
|
||||
"editor-styles-rtl.css": {
|
||||
"type": "-",
|
||||
"size": 564,
|
||||
"lmtime": 0,
|
||||
"lmtime": 1774339389542,
|
||||
"modified": false
|
||||
},
|
||||
"header-footer.asset.php": {
|
||||
"type": "-",
|
||||
"size": 84,
|
||||
"lmtime": 0,
|
||||
"lmtime": 1774339389612,
|
||||
"modified": false
|
||||
},
|
||||
"header-footer.css": {
|
||||
"type": "-",
|
||||
"size": 7182,
|
||||
"lmtime": 0,
|
||||
"lmtime": 1774339389682,
|
||||
"modified": false
|
||||
},
|
||||
"header-footer-rtl.css": {
|
||||
"type": "-",
|
||||
"size": 7184,
|
||||
"lmtime": 0,
|
||||
"lmtime": 1774339389751,
|
||||
"modified": false
|
||||
},
|
||||
"reset.asset.php": {
|
||||
"type": "-",
|
||||
"size": 84,
|
||||
"lmtime": 0,
|
||||
"lmtime": 1774339389823,
|
||||
"modified": false
|
||||
},
|
||||
"reset.css": {
|
||||
"type": "-",
|
||||
"size": 5456,
|
||||
"lmtime": 0,
|
||||
"lmtime": 1774339389901,
|
||||
"modified": false
|
||||
},
|
||||
"reset-rtl.css": {
|
||||
"type": "-",
|
||||
"size": 5456,
|
||||
"lmtime": 0,
|
||||
"lmtime": 1774339389971,
|
||||
"modified": false
|
||||
},
|
||||
"theme.asset.php": {
|
||||
"type": "-",
|
||||
"size": 84,
|
||||
"lmtime": 0,
|
||||
"lmtime": 1774339390041,
|
||||
"modified": false
|
||||
},
|
||||
"theme.css": {
|
||||
"type": "-",
|
||||
"size": 5097,
|
||||
"lmtime": 0,
|
||||
"lmtime": 1774339390115,
|
||||
"modified": false
|
||||
},
|
||||
"theme-rtl.css": {
|
||||
"type": "-",
|
||||
"size": 5100,
|
||||
"lmtime": 1774339390202,
|
||||
"modified": false
|
||||
}
|
||||
},
|
||||
"images": {
|
||||
"ai.png": {
|
||||
"type": "-",
|
||||
"size": 8081,
|
||||
"lmtime": 0,
|
||||
"modified": false
|
||||
},
|
||||
"BrandYoutube.svg": {
|
||||
"type": "-",
|
||||
"size": 998,
|
||||
"lmtime": 1774339390272,
|
||||
"modified": false
|
||||
},
|
||||
"ElementorLogo.svg": {
|
||||
"type": "-",
|
||||
"size": 928,
|
||||
"lmtime": 1774339390341,
|
||||
"modified": false
|
||||
},
|
||||
"elementor-notice-icon.svg": {
|
||||
"type": "-",
|
||||
"size": 625,
|
||||
"lmtime": 0,
|
||||
"modified": false
|
||||
},
|
||||
"elementor.svg": {
|
||||
"type": "-",
|
||||
"size": 7002,
|
||||
"lmtime": 0,
|
||||
"modified": false
|
||||
},
|
||||
"go-pro.svg": {
|
||||
"type": "-",
|
||||
"size": 31373,
|
||||
"lmtime": 0,
|
||||
"modified": false
|
||||
},
|
||||
"image-optimization-bg.svg": {
|
||||
"type": "-",
|
||||
"size": 1196,
|
||||
"lmtime": 1774339390411,
|
||||
"modified": false
|
||||
},
|
||||
"image-optimizer.svg": {
|
||||
"type": "-",
|
||||
"size": 1004,
|
||||
"lmtime": 1774339390481,
|
||||
"modified": false
|
||||
},
|
||||
"install-elementor.png": {
|
||||
"type": "-",
|
||||
"size": 223712,
|
||||
"lmtime": 0,
|
||||
"modified": false
|
||||
},
|
||||
"plus.svg": {
|
||||
"type": "-",
|
||||
"size": 606,
|
||||
"lmtime": 0,
|
||||
"modified": false
|
||||
},
|
||||
"send-logo.gif": {
|
||||
"type": "-",
|
||||
"size": 574506,
|
||||
"lmtime": 0,
|
||||
"modified": false
|
||||
}
|
||||
},
|
||||
"js": {
|
||||
"271.js": {
|
||||
"type": "-",
|
||||
"size": 3186,
|
||||
"lmtime": 0,
|
||||
"modified": false
|
||||
},
|
||||
"299.js": {
|
||||
"type": "-",
|
||||
"size": 4003,
|
||||
"lmtime": 0,
|
||||
"modified": false
|
||||
},
|
||||
"380.js": {
|
||||
"type": "-",
|
||||
"size": 3310,
|
||||
"lmtime": 0,
|
||||
"modified": false
|
||||
},
|
||||
"387.js": {
|
||||
"type": "-",
|
||||
"size": 4549,
|
||||
"lmtime": 0,
|
||||
"modified": false
|
||||
},
|
||||
"415.js": {
|
||||
"type": "-",
|
||||
"size": 5588,
|
||||
"lmtime": 0,
|
||||
"modified": false
|
||||
},
|
||||
"468.js": {
|
||||
"type": "-",
|
||||
"size": 5460,
|
||||
"lmtime": 0,
|
||||
"modified": false
|
||||
},
|
||||
"495.js": {
|
||||
"type": "-",
|
||||
"size": 4197,
|
||||
"lmtime": 0,
|
||||
"modified": false
|
||||
},
|
||||
"502.js": {
|
||||
"type": "-",
|
||||
"size": 3938,
|
||||
"lmtime": 0,
|
||||
"modified": false
|
||||
},
|
||||
"516.js": {
|
||||
"type": "-",
|
||||
"size": 3331,
|
||||
"lmtime": 0,
|
||||
"modified": false
|
||||
},
|
||||
"612.js": {
|
||||
"type": "-",
|
||||
"size": 3817,
|
||||
"lmtime": 0,
|
||||
"modified": false
|
||||
},
|
||||
"763.js": {
|
||||
"type": "-",
|
||||
"size": 4073,
|
||||
"lmtime": 0,
|
||||
"modified": false
|
||||
},
|
||||
"768.js": {
|
||||
"type": "-",
|
||||
"size": 4289,
|
||||
"lmtime": 0,
|
||||
"modified": false
|
||||
},
|
||||
"770.js": {
|
||||
"type": "-",
|
||||
"size": 7063,
|
||||
"lmtime": 0,
|
||||
"modified": false
|
||||
},
|
||||
"799.js": {
|
||||
"type": "-",
|
||||
"size": 3674,
|
||||
"lmtime": 0,
|
||||
"modified": false
|
||||
},
|
||||
"835.js": {
|
||||
"type": "-",
|
||||
"size": 3823,
|
||||
"lmtime": 0,
|
||||
"modified": false
|
||||
},
|
||||
"91.js": {
|
||||
"type": "-",
|
||||
"size": 4896,
|
||||
"lmtime": 0,
|
||||
"modified": false
|
||||
},
|
||||
"998.js": {
|
||||
"type": "-",
|
||||
"size": 3381,
|
||||
"lmtime": 0,
|
||||
"modified": false
|
||||
},
|
||||
"hello-conversion-banner.asset.php": {
|
||||
"type": "-",
|
||||
"size": 152,
|
||||
"lmtime": 1774339390551,
|
||||
"modified": false
|
||||
},
|
||||
"hello-conversion-banner.js": {
|
||||
"type": "-",
|
||||
"size": 141082,
|
||||
"lmtime": 1774339390661,
|
||||
"modified": false
|
||||
},
|
||||
"hello-editor.asset.php": {
|
||||
"type": "-",
|
||||
"size": 84,
|
||||
"lmtime": 1774339390862,
|
||||
"modified": false
|
||||
},
|
||||
"hello-editor.js": {
|
||||
"type": "-",
|
||||
"size": 4098,
|
||||
"lmtime": 0,
|
||||
"modified": false
|
||||
},
|
||||
"hello-elementor-menu.asset.php": {
|
||||
"type": "-",
|
||||
"size": 84,
|
||||
"lmtime": 1774339390971,
|
||||
"modified": false
|
||||
},
|
||||
"hello-elementor-menu.js": {
|
||||
"type": "-",
|
||||
"size": 242,
|
||||
"lmtime": 0,
|
||||
"modified": false
|
||||
},
|
||||
"hello-elementor-settings.asset.php": {
|
||||
"type": "-",
|
||||
"size": 194,
|
||||
"lmtime": 1774339391040,
|
||||
"modified": false
|
||||
},
|
||||
"hello-elementor-settings.js": {
|
||||
"type": "-",
|
||||
"size": 229203,
|
||||
"lmtime": 1774339391196,
|
||||
"modified": false
|
||||
},
|
||||
"hello-elementor-topbar.asset.php": {
|
||||
"type": "-",
|
||||
"size": 136,
|
||||
"lmtime": 1774339391266,
|
||||
"modified": false
|
||||
},
|
||||
"hello-elementor-topbar.js": {
|
||||
"type": "-",
|
||||
"size": 92100,
|
||||
"lmtime": 0,
|
||||
"modified": false
|
||||
},
|
||||
"hello-frontend.asset.php": {
|
||||
"type": "-",
|
||||
"size": 84,
|
||||
"lmtime": 1774339391333,
|
||||
"modified": false
|
||||
},
|
||||
"hello-frontend.js": {
|
||||
"type": "-",
|
||||
"size": 1830,
|
||||
"lmtime": 0,
|
||||
"modified": false
|
||||
},
|
||||
"hello-home-app.asset.php": {
|
||||
"type": "-",
|
||||
"size": 152,
|
||||
"lmtime": 1774339391402,
|
||||
"modified": false
|
||||
},
|
||||
"hello-home-app.js": {
|
||||
"type": "-",
|
||||
"size": 197753,
|
||||
"lmtime": 1774339391509,
|
||||
"modified": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"comments.php": {
|
||||
"type": "-",
|
||||
"size": 1489,
|
||||
"lmtime": 0,
|
||||
"modified": false
|
||||
},
|
||||
"footer.php": {
|
||||
"type": "-",
|
||||
"size": 615,
|
||||
"lmtime": 0,
|
||||
"modified": false
|
||||
},
|
||||
"functions.php": {
|
||||
"type": "-",
|
||||
"size": 9024,
|
||||
"lmtime": 0,
|
||||
"modified": false
|
||||
},
|
||||
"header.php": {
|
||||
"type": "-",
|
||||
"size": 1458,
|
||||
"lmtime": 0,
|
||||
"modified": false
|
||||
},
|
||||
"includes": {},
|
||||
"index.php": {
|
||||
"type": "-",
|
||||
"size": 999,
|
||||
"lmtime": 0,
|
||||
"modified": false
|
||||
},
|
||||
"modules": {},
|
||||
"readme.txt": {
|
||||
"type": "-",
|
||||
"size": 16766,
|
||||
"lmtime": 0,
|
||||
"modified": false
|
||||
},
|
||||
"screenshot.png": {
|
||||
"type": "-",
|
||||
"size": 110816,
|
||||
"lmtime": 0,
|
||||
"modified": false
|
||||
},
|
||||
"sidebar.php": {
|
||||
"type": "-",
|
||||
"size": 270,
|
||||
"lmtime": 0,
|
||||
"modified": false
|
||||
},
|
||||
"style.css": {
|
||||
"type": "-",
|
||||
"size": 1215,
|
||||
"lmtime": 0,
|
||||
"modified": false
|
||||
},
|
||||
"template-parts": {
|
||||
"404.php": {
|
||||
"type": "-",
|
||||
@@ -265,7 +577,20 @@
|
||||
"lmtime": 0,
|
||||
"modified": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"theme.json": {
|
||||
"type": "-",
|
||||
"size": 533,
|
||||
"lmtime": 0,
|
||||
"modified": false
|
||||
},
|
||||
"theme.php": {
|
||||
"type": "-",
|
||||
"size": 4109,
|
||||
"lmtime": 0,
|
||||
"modified": false
|
||||
},
|
||||
"vendor": {}
|
||||
}
|
||||
},
|
||||
"plugins": {
|
||||
|
||||
BIN
wp-admin/.DS_Store
vendored
BIN
wp-admin/.DS_Store
vendored
Binary file not shown.
BIN
wp-content/.DS_Store
vendored
BIN
wp-content/.DS_Store
vendored
Binary file not shown.
BIN
wp-content/languages/.DS_Store
vendored
BIN
wp-content/languages/.DS_Store
vendored
Binary file not shown.
BIN
wp-content/plugins/.DS_Store
vendored
BIN
wp-content/plugins/.DS_Store
vendored
Binary file not shown.
@@ -316,3 +316,75 @@
|
||||
.price-history-modal__table tr:hover td {
|
||||
background: #f5f7fa;
|
||||
}
|
||||
|
||||
/* =========================================================== */
|
||||
/* MIEJSCA POSTOJOWE — WIDGET */
|
||||
/* =========================================================== */
|
||||
|
||||
.parking-spots {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
max-width: 1264px;
|
||||
margin: 0 auto;
|
||||
font-family: "Barlow", sans-serif;
|
||||
color: #192c44;
|
||||
}
|
||||
|
||||
.parking-spot-card {
|
||||
padding: 10px 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.parking-spot-card__header {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.parking-spot-card__title {
|
||||
font-family: "Barlow", sans-serif;
|
||||
font-weight: 600;
|
||||
font-size: 24px;
|
||||
line-height: 1.3;
|
||||
color: #192c44;
|
||||
margin: 0;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
@media (max-width: 768px) {
|
||||
.parking-spot-card__title {
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
.parking-spot-card__price {
|
||||
font-family: "Barlow", sans-serif;
|
||||
font-weight: 600;
|
||||
font-size: 28px;
|
||||
line-height: 1.3;
|
||||
color: #192c44;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
@media (max-width: 768px) {
|
||||
.parking-spot-card__price {
|
||||
font-size: 22px;
|
||||
}
|
||||
}
|
||||
|
||||
.parking-spot-card__history-btn {
|
||||
font-family: "Barlow", sans-serif;
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
line-height: 26px;
|
||||
color: #192c44;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 7px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.parking-spot-card__history-btn:hover {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.parking-spot-card__history-btn svg {
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
@@ -62,26 +62,23 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
if (e.key === 'Escape' && overlay.classList.contains('is-open')) closePopup();
|
||||
});
|
||||
|
||||
// Kliknięcie w przycisk Historia cen
|
||||
// Kliknięcie w przycisk Historia cen (apartamenty)
|
||||
document.querySelectorAll('.btn-historia-cen').forEach(function (btn) {
|
||||
btn.addEventListener('click', function () {
|
||||
var postId = this.dataset.postId;
|
||||
if (!postId) return;
|
||||
|
||||
// Reset i pokaż "Ładowanie..."
|
||||
elTitle.textContent = 'Ładowanie...';
|
||||
elPrice.textContent = '';
|
||||
elPriceM2.textContent = '';
|
||||
elTbody.innerHTML = '';
|
||||
openPopup();
|
||||
|
||||
// Sprawdź dostępność danych globalnych (wp_localize_script)
|
||||
if (typeof apartamentsData === 'undefined') {
|
||||
elTitle.textContent = 'Błąd konfiguracji';
|
||||
return;
|
||||
}
|
||||
|
||||
// Buduj FormData
|
||||
var formData = new FormData();
|
||||
formData.append('action', 'apartamenty_get_price_history');
|
||||
formData.append('post_id', postId);
|
||||
@@ -123,4 +120,63 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Kliknięcie w przycisk Historia cen (miejsca postojowe)
|
||||
document.querySelectorAll('.btn-parking-historia-cen').forEach(function (btn) {
|
||||
btn.addEventListener('click', function () {
|
||||
var parkingType = this.dataset.parkingType;
|
||||
if (!parkingType) return;
|
||||
|
||||
elTitle.textContent = 'Ładowanie...';
|
||||
elPrice.textContent = '';
|
||||
elPriceM2.textContent = '';
|
||||
elTbody.innerHTML = '';
|
||||
openPopup();
|
||||
|
||||
if (typeof apartamentsData === 'undefined') {
|
||||
elTitle.textContent = 'Błąd konfiguracji';
|
||||
return;
|
||||
}
|
||||
|
||||
var formData = new FormData();
|
||||
formData.append('action', 'parking_get_price_history');
|
||||
formData.append('parking_type', parkingType);
|
||||
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) {
|
||||
elTitle.textContent = 'Brak danych';
|
||||
return;
|
||||
}
|
||||
|
||||
var d = json.data;
|
||||
|
||||
elTitle.textContent = d.title || '';
|
||||
elPrice.textContent = d.price ? d.price + ' zł' : '—';
|
||||
elPriceM2.textContent = d.price_m2 ? d.price_m2 + ' zł' : '—';
|
||||
|
||||
if (!d.history || d.history.length === 0) {
|
||||
elTbody.innerHTML = '<tr><td colspan="3">Brak historii cen</td></tr>';
|
||||
return;
|
||||
}
|
||||
|
||||
elTbody.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 () {
|
||||
elTitle.textContent = 'Błąd ładowania';
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -21,8 +21,10 @@ if ( ! defined( 'ABSPATH' ) ) {
|
||||
*/
|
||||
function register_hello_world_widget( $widgets_manager ) {
|
||||
require_once( __DIR__ . '/widgets/apartaments.php' );
|
||||
require_once( __DIR__ . '/widgets/parking-spots.php' );
|
||||
|
||||
$widgets_manager->register( new \Elementor_Apartaments() );
|
||||
$widgets_manager->register( new \Elementor_Parking_Spots() );
|
||||
}
|
||||
add_action( 'elementor/widgets/register', 'register_hello_world_widget' );
|
||||
|
||||
@@ -149,17 +151,48 @@ function elementor_addon_create_price_history_table() {
|
||||
|
||||
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
|
||||
dbDelta( $sql );
|
||||
|
||||
update_option( 'elementor_addon_db_version', '1.0' );
|
||||
}
|
||||
register_activation_hook( __FILE__, 'elementor_addon_create_price_history_table' );
|
||||
|
||||
/**
|
||||
* Sprawdza wersję DB przy każdym init i tworzy tabelę jeśli brakuje.
|
||||
* Tworzy tabelę wp_parking_price_history dla miejsc postojowych.
|
||||
*/
|
||||
function elementor_addon_create_parking_price_history_table() {
|
||||
global $wpdb;
|
||||
|
||||
$table_name = $wpdb->prefix . 'parking_price_history';
|
||||
$charset_collate = $wpdb->get_charset_collate();
|
||||
|
||||
$sql = "CREATE TABLE {$table_name} (
|
||||
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
parking_type VARCHAR(50) NOT NULL,
|
||||
price VARCHAR(50) NOT NULL DEFAULT '',
|
||||
price_m2 VARCHAR(50) NOT NULL DEFAULT '',
|
||||
recorded_at DATE NOT NULL,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY unique_daily (parking_type, recorded_at),
|
||||
KEY idx_type (parking_type)
|
||||
) ENGINE=InnoDB {$charset_collate};";
|
||||
|
||||
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
|
||||
dbDelta( $sql );
|
||||
}
|
||||
|
||||
/**
|
||||
* Tworzy obie tabele przy aktywacji pluginu.
|
||||
*/
|
||||
function elementor_addon_create_tables() {
|
||||
elementor_addon_create_price_history_table();
|
||||
elementor_addon_create_parking_price_history_table();
|
||||
update_option( 'elementor_addon_db_version', '1.1' );
|
||||
}
|
||||
register_activation_hook( __FILE__, 'elementor_addon_create_tables' );
|
||||
|
||||
/**
|
||||
* Sprawdza wersję DB przy każdym init i tworzy tabele jeśli brakuje.
|
||||
*/
|
||||
function elementor_addon_maybe_update_db() {
|
||||
if ( get_option( 'elementor_addon_db_version' ) !== '1.0' ) {
|
||||
elementor_addon_create_price_history_table();
|
||||
if ( get_option( 'elementor_addon_db_version' ) !== '1.1' ) {
|
||||
elementor_addon_create_tables();
|
||||
}
|
||||
}
|
||||
add_action( 'init', 'elementor_addon_maybe_update_db' );
|
||||
@@ -218,6 +251,32 @@ function elementor_addon_record_prices() {
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Miejsca postojowe — zapis cen z ACF options (pola w grupach)
|
||||
$parking_table = $wpdb->prefix . 'parking_price_history';
|
||||
$parking_groups = [
|
||||
'zwykle' => 'miejsce_postojowe_zwykle',
|
||||
'rodzinne' => 'miejsce_postojowe_rodzinne',
|
||||
];
|
||||
|
||||
foreach ( $parking_groups as $type => $group_name ) {
|
||||
$group = get_field( $group_name, 'option' ) ?: [];
|
||||
$price = $group[ $group_name . '_cena' ] ?? '';
|
||||
$price_m2 = $group[ $group_name . '_cena_m2' ] ?? '';
|
||||
|
||||
if ( $price || $price_m2 ) {
|
||||
$wpdb->query(
|
||||
$wpdb->prepare(
|
||||
"INSERT IGNORE INTO {$parking_table} (parking_type, price, price_m2, recorded_at)
|
||||
VALUES (%s, %s, %s, %s)",
|
||||
$type,
|
||||
(string) $price,
|
||||
(string) $price_m2,
|
||||
$today
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
add_action( 'apartamenty_record_prices', 'elementor_addon_record_prices' );
|
||||
|
||||
@@ -267,6 +326,60 @@ function elementor_addon_get_price_history_ajax() {
|
||||
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' );
|
||||
|
||||
/**
|
||||
* Zwraca historię cen dla miejsca postojowego jako JSON.
|
||||
* Parametr: parking_type ('zwykle' lub 'rodzinne')
|
||||
*/
|
||||
function elementor_addon_get_parking_price_history_ajax() {
|
||||
global $wpdb;
|
||||
|
||||
if ( ! check_ajax_referer( 'apartamenty_price_history_nonce', 'nonce', false ) ) {
|
||||
wp_send_json_error( [ 'message' => 'Invalid nonce' ] );
|
||||
die();
|
||||
}
|
||||
|
||||
$parking_type = sanitize_text_field( $_POST['parking_type'] ?? '' );
|
||||
if ( ! in_array( $parking_type, [ 'zwykle', 'rodzinne' ], true ) ) {
|
||||
wp_send_json_error( [ 'message' => 'Invalid parking_type' ] );
|
||||
die();
|
||||
}
|
||||
|
||||
$labels = [
|
||||
'zwykle' => 'Miejsce postojowe zwykłe',
|
||||
'rodzinne' => 'Miejsce postojowe rodzinne',
|
||||
];
|
||||
|
||||
$group_names = [
|
||||
'zwykle' => 'miejsce_postojowe_zwykle',
|
||||
'rodzinne' => 'miejsce_postojowe_rodzinne',
|
||||
];
|
||||
|
||||
$table_name = $wpdb->prefix . 'parking_price_history';
|
||||
|
||||
$history = $wpdb->get_results(
|
||||
$wpdb->prepare(
|
||||
"SELECT recorded_at, price, price_m2
|
||||
FROM {$table_name}
|
||||
WHERE parking_type = %s
|
||||
ORDER BY recorded_at DESC
|
||||
LIMIT 50",
|
||||
$parking_type
|
||||
)
|
||||
);
|
||||
|
||||
$group_name = $group_names[ $parking_type ];
|
||||
$group = get_field( $group_name, 'option' ) ?: [];
|
||||
|
||||
wp_send_json_success( [
|
||||
'title' => $labels[ $parking_type ],
|
||||
'price' => $group[ $group_name . '_cena' ] ?? '',
|
||||
'price_m2' => $group[ $group_name . '_cena_m2' ] ?? '',
|
||||
'history' => $history,
|
||||
] );
|
||||
}
|
||||
add_action( 'wp_ajax_parking_get_price_history', 'elementor_addon_get_parking_price_history_ajax' );
|
||||
add_action( 'wp_ajax_nopriv_parking_get_price_history', 'elementor_addon_get_parking_price_history_ajax' );
|
||||
|
||||
// ===========================================================
|
||||
// JAWNOŚĆ CEN — XML ENDPOINTS
|
||||
// ===========================================================
|
||||
@@ -368,6 +481,58 @@ function apartamenty_generate_price_xml() {
|
||||
}
|
||||
}
|
||||
|
||||
// Miejsca postojowe
|
||||
$parking_table = $wpdb->prefix . 'parking_price_history';
|
||||
$parking_configs = [
|
||||
'zwykle' => [
|
||||
'label' => 'Miejsce postojowe zwykłe',
|
||||
'group_name' => 'miejsce_postojowe_zwykle',
|
||||
],
|
||||
'rodzinne' => [
|
||||
'label' => 'Miejsce postojowe rodzinne',
|
||||
'group_name' => 'miejsce_postojowe_rodzinne',
|
||||
],
|
||||
];
|
||||
|
||||
$xml .= "\t<miejsca_postojowe>\n";
|
||||
|
||||
foreach ( $parking_configs as $type => $cfg ) {
|
||||
$group = get_field( $cfg['group_name'], 'option' ) ?: [];
|
||||
$price = $group[ $cfg['group_name'] . '_cena' ] ?? '';
|
||||
$price_m2 = $group[ $cfg['group_name'] . '_cena_m2' ] ?? '';
|
||||
|
||||
$p_history = $wpdb->get_results(
|
||||
$wpdb->prepare(
|
||||
"SELECT recorded_at, price, price_m2
|
||||
FROM {$parking_table}
|
||||
WHERE parking_type = %s
|
||||
ORDER BY recorded_at DESC",
|
||||
$type
|
||||
)
|
||||
);
|
||||
|
||||
$last_update = ! empty( $p_history ) ? $p_history[0]->recorded_at : date( 'Y-m-d' );
|
||||
|
||||
$xml .= "\t\t" . '<miejsce_postojowe typ="' . $x( $type ) . '">' . "\n";
|
||||
$xml .= "\t\t\t<nazwa>" . $x( $cfg['label'] ) . "</nazwa>\n";
|
||||
$xml .= "\t\t\t<cena_brutto>" . $x( $price ) . "</cena_brutto>\n";
|
||||
$xml .= "\t\t\t<cena_za_m2>" . $x( $price_m2 ) . "</cena_za_m2>\n";
|
||||
$xml .= "\t\t\t<data_aktualizacji>" . $x( $last_update ) . "</data_aktualizacji>\n";
|
||||
$xml .= "\t\t\t<historia_cen>\n";
|
||||
|
||||
foreach ( $p_history as $row ) {
|
||||
$xml .= "\t\t\t\t" . '<zmiana data="' . $x( $row->recorded_at ) . '">' . "\n";
|
||||
$xml .= "\t\t\t\t\t<cena_brutto>" . $x( $row->price ) . "</cena_brutto>\n";
|
||||
$xml .= "\t\t\t\t\t<cena_za_m2>" . $x( $row->price_m2 ) . "</cena_za_m2>\n";
|
||||
$xml .= "\t\t\t\t</zmiana>\n";
|
||||
}
|
||||
|
||||
$xml .= "\t\t\t</historia_cen>\n";
|
||||
$xml .= "\t\t</miejsce_postojowe>\n";
|
||||
}
|
||||
|
||||
$xml .= "\t</miejsca_postojowe>\n";
|
||||
|
||||
$xml .= '</lokale>';
|
||||
|
||||
set_transient( 'apartamenty_price_xml_cache', $xml, HOUR_IN_SECONDS );
|
||||
|
||||
151
wp-content/plugins/elementor-addon/widgets/parking-spots.php
Normal file
151
wp-content/plugins/elementor-addon/widgets/parking-spots.php
Normal file
@@ -0,0 +1,151 @@
|
||||
<?php
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
class Elementor_Parking_Spots extends \Elementor\Widget_Base {
|
||||
|
||||
public function get_name() {
|
||||
return 'parking_spots';
|
||||
}
|
||||
|
||||
public function get_title() {
|
||||
return esc_html__( 'Miejsca Postojowe', 'elementor-addon' );
|
||||
}
|
||||
|
||||
public function get_icon() {
|
||||
return 'eicon-car';
|
||||
}
|
||||
|
||||
public function get_categories() {
|
||||
return [ 'basic' ];
|
||||
}
|
||||
|
||||
public function get_keywords() {
|
||||
return [ 'parking', 'miejsca', 'postojowe', 'garaz' ];
|
||||
}
|
||||
|
||||
protected function register_controls() {
|
||||
$this->start_controls_section(
|
||||
'section_setting',
|
||||
[
|
||||
'label' => esc_html__( 'Settings', 'elementor-addon' ),
|
||||
'tab' => \Elementor\Controls_Manager::TAB_CONTENT,
|
||||
]
|
||||
);
|
||||
|
||||
$this->end_controls_section();
|
||||
}
|
||||
|
||||
public function get_style_depends() {
|
||||
return [ 'elementor-addon-main-css' ];
|
||||
}
|
||||
|
||||
public function get_script_depends() {
|
||||
return [ 'elementor-addon-main-js' ];
|
||||
}
|
||||
|
||||
protected function render() {
|
||||
$group_zwykle = get_field( 'miejsce_postojowe_zwykle', 'option' ) ?: [];
|
||||
$group_rodzinne = get_field( 'miejsce_postojowe_rodzinne', 'option' ) ?: [];
|
||||
|
||||
$parking_types = [
|
||||
'zwykle' => [
|
||||
'label' => 'Miejsce postojowe zwykłe',
|
||||
'price' => $group_zwykle['miejsce_postojowe_zwykle_cena'] ?? '',
|
||||
'price_m2' => $group_zwykle['miejsce_postojowe_zwykle_cena_m2'] ?? '',
|
||||
],
|
||||
'rodzinne' => [
|
||||
'label' => 'Miejsce postojowe rodzinne',
|
||||
'price' => $group_rodzinne['miejsce_postojowe_rodzinne_cena'] ?? '',
|
||||
'price_m2' => $group_rodzinne['miejsce_postojowe_rodzinne_cena_m2'] ?? '',
|
||||
],
|
||||
];
|
||||
?>
|
||||
|
||||
<div class="parking-spots">
|
||||
<?php foreach ( $parking_types as $type => $data ) : ?>
|
||||
<div class="parking-spot-card">
|
||||
<div class="parking-spot-card__header">
|
||||
<h3 class="parking-spot-card__title"><?php echo esc_html( $data['label'] ); ?></h3>
|
||||
</div>
|
||||
<?php if ( ! empty( $data['price'] ) ) : ?>
|
||||
<div class="parking-spot-card__price">
|
||||
<?php echo esc_html( $data['price'] ); ?> zł
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<div class="parking-spot-card__history-btn btn-parking-historia-cen"
|
||||
data-parking-type="<?php echo esc_attr( $type ); ?>">
|
||||
HISTORIA CEN
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="7" viewBox="0 0 12 7" fill="none">
|
||||
<g clip-path="url(#clip0_parking_<?php echo esc_attr( $type ); ?>)">
|
||||
<path d="M6 7L0.370835 0.25L11.6292 0.250001L6 7Z" fill="#192C44"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_parking_<?php echo esc_attr( $type ); ?>">
|
||||
<rect width="12" height="7" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
// Popup historia cen — renderuj tylko jeśli nie ma go w DOM (apartamenty mogą go już mieć)
|
||||
?>
|
||||
<div class="price-history-overlay price-history-overlay--parking-fallback" id="price-history-overlay-parking" aria-hidden="true" style="display:none;">
|
||||
<div class="price-history-modal" role="dialog" aria-modal="true">
|
||||
<button class="price-history-modal__close" id="price-history-close-parking" 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-parking"></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-parking"></span>
|
||||
</div>
|
||||
<div class="price-history-modal__row">
|
||||
<span>Cena m²:</span>
|
||||
<span class="price-history-modal__val" id="price-history-sqm-parking"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="price-history-modal__table-wrap">
|
||||
<table class="price-history-modal__table">
|
||||
<tbody id="price-history-tbody-parking"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
// Jeśli popup apartamentowy istnieje, ukryj fallback parkingowy
|
||||
var mainOverlay = document.getElementById('price-history-overlay');
|
||||
var parkingFallback = document.getElementById('price-history-overlay-parking');
|
||||
if (mainOverlay && parkingFallback) {
|
||||
parkingFallback.remove();
|
||||
} else if (parkingFallback) {
|
||||
parkingFallback.style.display = '';
|
||||
parkingFallback.id = 'price-history-overlay';
|
||||
var closeBtn = parkingFallback.querySelector('#price-history-close-parking');
|
||||
if (closeBtn) closeBtn.id = 'price-history-close';
|
||||
var title = parkingFallback.querySelector('#price-history-title-parking');
|
||||
if (title) title.id = 'price-history-title';
|
||||
var price = parkingFallback.querySelector('#price-history-price-parking');
|
||||
if (price) price.id = 'price-history-price';
|
||||
var sqm = parkingFallback.querySelector('#price-history-sqm-parking');
|
||||
if (sqm) sqm.id = 'price-history-sqm';
|
||||
var tbody = parkingFallback.querySelector('#price-history-tbody-parking');
|
||||
if (tbody) tbody.id = 'price-history-tbody';
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
||||
<?php
|
||||
}
|
||||
}
|
||||
BIN
wp-content/themes/.DS_Store
vendored
BIN
wp-content/themes/.DS_Store
vendored
Binary file not shown.
BIN
wp-includes/.DS_Store
vendored
BIN
wp-includes/.DS_Store
vendored
Binary file not shown.
BIN
wp-includes/Requests/.DS_Store
vendored
BIN
wp-includes/Requests/.DS_Store
vendored
Binary file not shown.
BIN
wp-includes/SimplePie/.DS_Store
vendored
BIN
wp-includes/SimplePie/.DS_Store
vendored
Binary file not shown.
BIN
wp-includes/blocks/.DS_Store
vendored
BIN
wp-includes/blocks/.DS_Store
vendored
Binary file not shown.
BIN
wp-includes/images/.DS_Store
vendored
BIN
wp-includes/images/.DS_Store
vendored
Binary file not shown.
BIN
wp-includes/js/.DS_Store
vendored
BIN
wp-includes/js/.DS_Store
vendored
Binary file not shown.
BIN
wp-includes/rest-api/.DS_Store
vendored
BIN
wp-includes/rest-api/.DS_Store
vendored
Binary file not shown.
BIN
wp-includes/sodium_compat/.DS_Store
vendored
BIN
wp-includes/sodium_compat/.DS_Store
vendored
Binary file not shown.
Reference in New Issue
Block a user