This commit is contained in:
2026-03-25 21:42:09 +01:00
parent 5d25faff51
commit b1eacccec5
24 changed files with 1231 additions and 50 deletions

BIN
.DS_Store vendored

Binary file not shown.

View File

@@ -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*

View File

@@ -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*

View File

@@ -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-25UNIFY 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*

View 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>

View 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*

View File

@@ -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

Binary file not shown.

BIN
wp-content/.DS_Store vendored

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -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;
}

View File

@@ -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';
});
});
});
});

View File

@@ -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 );

View 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
}
}

Binary file not shown.

BIN
wp-includes/.DS_Store vendored

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.