feat(05-admin-panel): Admin panel z historią rezerwacji
Phase 5 complete — CPT carei_reservation z automatycznym zapisem, lista z kolumnami i filtrem statusu, meta box szczegółów, system statusów nowe/przeczytane/zrealizowane, auto-mark-read. Milestone v0.1 Formularz Rezerwacji MVP — all 5 phases complete. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -24,6 +24,30 @@ Plugin Elementor do rezerwacji samochodu na stronie carei.pagedev.pl, zintegrowa
|
|||||||
- Formularz NIE jest natywnym formularzem Elementor Pro — to custom widget
|
- Formularz NIE jest natywnym formularzem Elementor Pro — to custom widget
|
||||||
- Brak dodatkowych zależności npm/composer — czysty PHP + JS
|
- Brak dodatkowych zależności npm/composer — czysty PHP + JS
|
||||||
|
|
||||||
|
## Validated Requirements (Milestone v0.1)
|
||||||
|
- ✓ Elementor Widget z modalem rezerwacji — Phase 1-2
|
||||||
|
- ✓ Integracja z Softra Rent API (auth, branch, carclass, pricelist, booking) — Phase 1, 3
|
||||||
|
- ✓ Multi-step form: formularz → podsumowanie → sukces — Phase 2-3
|
||||||
|
- ✓ Responsive modal (desktop overlay + mobile) — Phase 2
|
||||||
|
- ✓ Error handling: token retry, timeout, network errors — Phase 4
|
||||||
|
- ✓ Accessibility: ARIA dialog, focus trap, aria-live — Phase 4
|
||||||
|
- ✓ Admin panel: CPT carei_reservation, lista, szczegóły, statusy — Phase 5
|
||||||
|
|
||||||
|
## Key Decisions
|
||||||
|
| Decision | Phase | Rationale |
|
||||||
|
|----------|-------|-----------|
|
||||||
|
| CPT + post_meta (nie custom table) | 5 | WordPress-native, prostsze dla MVP |
|
||||||
|
| Fire-and-forget save | 5 | Nie blokuj response — rezerwacja już w Softra |
|
||||||
|
| Meta-based status (nie taxonomy) | 5 | Prosty 3-wartościowy enum |
|
||||||
|
| Token retry on 401/403 | 4 | Automatyczny re-auth bez interwencji usera |
|
||||||
|
| Inline display:none for steps | 4 | CSS class conflict resolution |
|
||||||
|
|
||||||
|
## Out of Scope (backlog)
|
||||||
|
- Ubezpieczenie (pakiet Soft/Premium) — czeka na API Softra
|
||||||
|
- Wyjazd zagraniczny (lista krajów + ceny) — czeka na API Softra
|
||||||
|
- Eksport CSV/PDF rezerwacji
|
||||||
|
- Email notyfikacje
|
||||||
|
|
||||||
## API Endpoints (kluczowe)
|
## API Endpoints (kluczowe)
|
||||||
| Endpoint | Metoda | Użycie |
|
| Endpoint | Metoda | Użycie |
|
||||||
|----------|--------|--------|
|
|----------|--------|--------|
|
||||||
|
|||||||
@@ -7,11 +7,18 @@
|
|||||||
### Phase 1: Plugin Skeleton + API Proxy ✅ Complete
|
### Phase 1: Plugin Skeleton + API Proxy ✅ Complete
|
||||||
Utworzenie pluginu WordPress z proxy REST API do Softra Rent. Backend obsługujący autoryzację JWT, pobieranie oddziałów, klas pojazdów, cenników i dodatków. Rejestracja widgetu Elementor.
|
Utworzenie pluginu WordPress z proxy REST API do Softra Rent. Backend obsługujący autoryzację JWT, pobieranie oddziałów, klas pojazdów, cenników i dodatków. Rejestracja widgetu Elementor.
|
||||||
|
|
||||||
### Phase 2: Form UI — Krok 1 (Formularz) ⬜ Not started
|
### Phase 2: Form UI — Krok 1 (Formularz) ✅ Complete
|
||||||
Frontend formularza rezerwacji zgodny z Figmą: modal/bottom-sheet, pola (segment, daty, lokalizacja, opcje dodatkowe, dane osobowe, wiadomość, zgoda RODO). Dynamiczne ładowanie danych z API (oddziały, klasy). Responsywny desktop + mobile.
|
Frontend formularza rezerwacji w modalu Elementor. Dynamiczne segmenty (ze wszystkich lokalizacji), oddziały filtrowane po segmencie, opcje dodatkowe z cennika API, walidacja, responsive. Pominięto ubezpieczenie i wyjazd zagraniczny (brak w API — backlog).
|
||||||
|
|
||||||
### Phase 3: Form UI — Krok 2 (Overlay / Podsumowanie) ⬜ Not started
|
### Phase 3: Submit + Booking Flow ✅ Complete
|
||||||
Rozwinięcie formularza po wypełnieniu kroku 1: podsumowanie kosztów (pricing summary z API), tworzenie klienta, złożenie rezerwacji, potwierdzenie. Obsługa błędów i stanów ładowania.
|
Pełny flow: formularz → customer/add → pricing summary overlay → makebooking → confirm → success z numerem. Pola adresowe + PESEL. Error handling. Fixy: boolean API params, timeout mapowania, drivers[] required.
|
||||||
|
|
||||||
### Phase 4: Polish & Integration Testing ⬜ Not started
|
### Phase 4: Polish & Integration Testing ✅ Complete
|
||||||
Testy end-to-end na carei.pagedev.pl, walidacja formularza, obsługa edge cases (brak dostępności, timeout tokenu, błędy API), animacje przejść między krokami, a11y.
|
Edge cases (token retry, timeout, network errors), animacje przejść form↔summary↔success, accessibility (ARIA dialog, focus trap, focus management, aria-live), CSS fix.
|
||||||
|
|
||||||
|
### Phase 5: Admin Panel — Historia Formularzy ✅ Complete
|
||||||
|
CPT `carei_reservation` z automatycznym zapisem po booking, lista z kolumnami i filtrem statusu, meta box szczegółów, system statusów nowe/przeczytane/zrealizowane, auto-mark-read.
|
||||||
|
|
||||||
|
### Backlog (do realizacji gdy API będzie gotowe)
|
||||||
|
- **Wyjazd zagraniczny:** Checkbox "Planuję trasę poza granicę Polski" + wyszukiwarka krajów z cenami. Wymaga endpointu listy krajów i cennika per kraj w API Softra.
|
||||||
|
- **Ubezpieczenie:** Sekcja "Pakiet ochrony Soft/Premium" jak na Figmie. Wymaga dedykowanych pozycji ubezpieczeniowych w API pricelist.
|
||||||
|
|||||||
@@ -2,33 +2,35 @@
|
|||||||
|
|
||||||
## Current Position
|
## Current Position
|
||||||
|
|
||||||
Milestone: v0.1 Formularz Rezerwacji MVP
|
Milestone: v0.1 Formularz Rezerwacji MVP — COMPLETE
|
||||||
Phase: 2 of 4 (Form UI — Krok 1) — APPLY in progress
|
Phase: 5 of 5 (Admin Panel — Historia Formularzy) — Complete
|
||||||
Plan: 02-01 tasks 1+2 done, task 3 (checkpoint:human-verify) pending
|
Plan: 05-01 — Complete
|
||||||
Status: Awaiting deploy + visual test on carei.pagedev.pl
|
Status: Milestone v0.1 complete — all 5 phases finished
|
||||||
Last activity: 2026-03-25 — Fixed API (native cURL), added car-classes-all endpoint, built full form UI
|
Last activity: 2026-03-25 — Phase 5 unified, milestone complete
|
||||||
|
|
||||||
Progress:
|
Progress:
|
||||||
- Milestone: [██░░░░░░░░] 25%
|
- Milestone: [██████████] 100% ✅
|
||||||
- Phase 1: [██████████] 100% ✅
|
- Phase 1: [██████████] 100% ✅
|
||||||
- Phase 2: [██████░░░░] 66% (2/3 tasks, checkpoint pending)
|
- Phase 2: [██████████] 100% ✅
|
||||||
|
- Phase 3: [██████████] 100% ✅
|
||||||
|
- Phase 4: [██████████] 100% ✅
|
||||||
|
- Phase 5: [██████████] 100% ✅
|
||||||
|
|
||||||
## Loop Position
|
## Loop Position
|
||||||
|
|
||||||
Current loop state:
|
Current loop state:
|
||||||
```
|
```
|
||||||
PLAN ──▶ APPLY ──▶ UNIFY
|
PLAN ──▶ APPLY ──▶ UNIFY
|
||||||
✓ ◐ ○ [Apply in progress — checkpoint waiting]
|
✓ ✓ ✓ [Loop complete — milestone v0.1 finished]
|
||||||
```
|
```
|
||||||
|
|
||||||
## Session Continuity
|
## Session Continuity
|
||||||
|
|
||||||
Last session: 2026-03-25
|
Last session: 2026-03-25
|
||||||
Stopped at: Phase 2 Plan 02-01 — Tasks 1+2 complete, Task 3 checkpoint:human-verify pending
|
Stopped at: Milestone v0.1 complete — all phases unified
|
||||||
Next action: Deploy files to server, test form visually, then "approved" or describe issues
|
Next action: /paul:complete-milestone or start next milestone
|
||||||
Resume file: .paul/HANDOFF-2026-03-25.md
|
Resume file: .paul/ROADMAP.md
|
||||||
Resume context:
|
Resume context:
|
||||||
- 5 plugin files need deploying (API, proxy, widget, CSS, JS)
|
- All 5 phases complete: skeleton, form UI, booking flow, polish, admin panel
|
||||||
- Key fix: native cURL instead of wp_remote_post
|
- Plugin fully functional: formularz → API Softra → admin panel
|
||||||
- Key fix: car-classes-all endpoint loads segments without requiring branch
|
- Backlog: ubezpieczenie + wyjazd zagraniczny (czeka na API)
|
||||||
- After approved → /paul:unify → /paul:plan Phase 3
|
|
||||||
|
|||||||
108
.paul/handoffs/archive/HANDOFF-2026-03-25-phase3.md
Normal file
108
.paul/handoffs/archive/HANDOFF-2026-03-25-phase3.md
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
# PAUL Handoff
|
||||||
|
|
||||||
|
**Date:** 2026-03-25
|
||||||
|
**Status:** paused — Phase 3 complete, Phase 4 not started
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## READ THIS FIRST
|
||||||
|
|
||||||
|
You have no prior context. This document tells you everything.
|
||||||
|
|
||||||
|
**Project:** Carei — Formularz rezerwacji samochodu jako plugin Elementor, zintegrowany z Softra Rent API
|
||||||
|
**Core value:** Klient klika "Złóż zapytanie o rezerwację" → modal z formularzem → dane z API Softra → rezerwacja
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Current State
|
||||||
|
|
||||||
|
**Milestone:** v0.1 Formularz Rezerwacji MVP
|
||||||
|
**Phase:** 3 of 5 — Complete ✅
|
||||||
|
**Plan:** 03-01 — Complete
|
||||||
|
|
||||||
|
**Loop Position:**
|
||||||
|
```
|
||||||
|
PLAN ──▶ APPLY ──▶ UNIFY
|
||||||
|
✓ ✓ ✓ [Loop complete — ready for next PLAN]
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## What Was Done
|
||||||
|
|
||||||
|
**This session:**
|
||||||
|
- Refactored form layout: segment+dates in one row, pickup+same-return checkbox in one row
|
||||||
|
- New endpoint `/segments-branches-map` (simplified: 2 API calls, all branches for all segments)
|
||||||
|
- Segment→branch filtering in pickup select (all branches shown, API verifies at booking)
|
||||||
|
- Extras hidden until segment AND pickup selected
|
||||||
|
- Investigated API for insurance (none) and foreign travel (none) → deferred to backlog
|
||||||
|
- Added Phase 5 to roadmap: Admin Panel historia formularzy
|
||||||
|
- **Phase 3 complete:** full booking flow end-to-end
|
||||||
|
- Added address fields (city, zipCode, street) + PESEL
|
||||||
|
- Pricing summary overlay with cost breakdown table
|
||||||
|
- Success view with reservation number
|
||||||
|
- customer/add → princingSummary → makebooking → confirm
|
||||||
|
- Error handling at each step
|
||||||
|
|
||||||
|
**API discoveries (critical for future work):**
|
||||||
|
- Boolean params: must send `true`/`false`, NOT `'T'`/`'N'` (Java deserialization)
|
||||||
|
- `drivers[]` is required in makebooking (not documented as required)
|
||||||
|
- Penalty items in pricelist (BRAK/BRUD/KARA codes) filtered from display
|
||||||
|
- No insurance or foreign travel endpoints in current API version
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## What's Next
|
||||||
|
|
||||||
|
**Immediate:** `/paul:plan` for Phase 4 — Polish & Integration Testing
|
||||||
|
- Edge cases (brak dostępności, timeout tokenu, błędy API)
|
||||||
|
- Animacje przejść form↔summary↔success
|
||||||
|
- Accessibility
|
||||||
|
- End-to-end testing on carei.pagedev.pl
|
||||||
|
|
||||||
|
**After that:**
|
||||||
|
- Phase 5: Admin Panel — zapisywanie formularzy w WP, przeglądanie w wp-admin
|
||||||
|
|
||||||
|
**Backlog (czeka na nową wersję API):**
|
||||||
|
- Ubezpieczenie (Pakiet Soft/Premium)
|
||||||
|
- Wyjazd zagraniczny (checkbox + wyszukiwarka krajów)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Key Decisions Made
|
||||||
|
|
||||||
|
| Decision | Rationale |
|
||||||
|
|----------|-----------|
|
||||||
|
| Boolean API params (true/false) | Java backend rejects 'T'/'N' strings |
|
||||||
|
| Simplified branch mapping (no per-branch filtering) | 39 branches × API call = timeout; all branches shown, API verifies at booking |
|
||||||
|
| drivers[] = najemca | API requires NotNull, form collects same data |
|
||||||
|
| skipAccountCreate: true | MVP — no customer accounts needed |
|
||||||
|
| Penalty items filtered by code prefix | BRAK/BRUD/KARA not customer-facing options |
|
||||||
|
| Ubezpieczenie + wyjazd zagraniczny → backlog | API has no dedicated endpoints |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Key Files
|
||||||
|
|
||||||
|
| File | Purpose |
|
||||||
|
|------|---------|
|
||||||
|
| `.paul/STATE.md` | Live project state |
|
||||||
|
| `.paul/ROADMAP.md` | Phase overview (1-3 ✅, 4-5 ⬜, backlog) |
|
||||||
|
| `.paul/phases/03-form-submit-booking/03-01-SUMMARY.md` | Phase 3 summary |
|
||||||
|
| `wp-content/plugins/carei-reservation/` | Plugin directory (all code) |
|
||||||
|
| `docs/rent-api-02-klienci-i-konta.md` | Customer API docs |
|
||||||
|
| `docs/rent-api-03-rezerwacje-i-platnosci.md` | Booking API docs |
|
||||||
|
| `softra-test.php` | API test reference |
|
||||||
|
| `.env` | API credentials |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Resume Instructions
|
||||||
|
|
||||||
|
1. Read `.paul/STATE.md` for latest position
|
||||||
|
2. Phase 3 complete — ready for Phase 4 planning
|
||||||
|
3. Run `/paul:resume` or `/paul:plan`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Handoff created: 2026-03-25*
|
||||||
122
.paul/handoffs/archive/HANDOFF-2026-03-25-phase5.md
Normal file
122
.paul/handoffs/archive/HANDOFF-2026-03-25-phase5.md
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
# PAUL Handoff
|
||||||
|
|
||||||
|
**Date:** 2026-03-25
|
||||||
|
**Status:** paused — Phase 5 APPLY in progress, checkpoint pending
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## READ THIS FIRST
|
||||||
|
|
||||||
|
You have no prior context. This document tells you everything.
|
||||||
|
|
||||||
|
**Project:** Carei — Formularz rezerwacji samochodu jako plugin Elementor, zintegrowany z Softra Rent API
|
||||||
|
**Core value:** Klient klika "Złóż zapytanie o rezerwację" → modal z formularzem → dane z API Softra → rezerwacja
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Current State
|
||||||
|
|
||||||
|
**Milestone:** v0.1 Formularz Rezerwacji MVP
|
||||||
|
**Phase:** 5 of 5 — Admin Panel — Historia Formularzy
|
||||||
|
**Plan:** 05-01 — APPLY in progress (2/3 tasks done, checkpoint pending)
|
||||||
|
|
||||||
|
**Loop Position:**
|
||||||
|
```
|
||||||
|
PLAN ──▶ APPLY ──▶ UNIFY
|
||||||
|
✓ ◐ ○ [APPLY in progress — checkpoint pending]
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## What Was Done
|
||||||
|
|
||||||
|
**This session (Phase 4 + Phase 5):**
|
||||||
|
|
||||||
|
### Phase 4: Polish & Integration Testing ✅ COMPLETE
|
||||||
|
- Token retry on 401/403 (auto-retry 1x)
|
||||||
|
- AbortController timeout 15s + Polish error messages
|
||||||
|
- Network error detection (TypeError → retry → user message)
|
||||||
|
- Extended reject reason translations (6 codes)
|
||||||
|
- Animated step transitions: form ↔ summary ↔ success (fade+translate 250ms)
|
||||||
|
- Modal open/close animations (fade + scale)
|
||||||
|
- ARIA dialog with focus trap and focus management
|
||||||
|
- aria-live announcements for screen readers
|
||||||
|
- CSS bug fix (orphaned media query styles)
|
||||||
|
- Fix: inline display:none vs CSS class conflict (auto-fixed during checkpoint)
|
||||||
|
|
||||||
|
### Phase 5: Admin Panel (in progress)
|
||||||
|
- Created `includes/class-admin-panel.php` — full admin panel:
|
||||||
|
- CPT `carei_reservation` registered (menu "Rezerwacje", dashicons-car)
|
||||||
|
- Admin columns: Nr rezerwacji, Klient, Segment, Daty, Oddział, Status, Data
|
||||||
|
- Status filter dropdown above list
|
||||||
|
- Meta box "Szczegóły rezerwacji" with full data table
|
||||||
|
- Status dropdown (nowe/przeczytane/zrealizowane) with colored badges
|
||||||
|
- Auto-mark-read on post edit open
|
||||||
|
- Inline admin CSS for badges and meta table
|
||||||
|
- Static `save_reservation()` method for fire-and-forget save
|
||||||
|
- Modified `class-rest-proxy.php` — `make_booking()` calls `save_reservation()` on success
|
||||||
|
- Modified `carei-reservation.php` — includes + initializes `Carei_Admin_Panel`
|
||||||
|
- All PHP syntax verified clean
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## What's In Progress
|
||||||
|
|
||||||
|
- **Phase 5 Plan 05-01 checkpoint:** Human verification in wp-admin pending
|
||||||
|
- All code written, PHP syntax OK
|
||||||
|
- Need user to test: CPT in menu, submit reservation, check admin list, meta box, status changes
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## What's Next
|
||||||
|
|
||||||
|
**Immediate:** Resume Phase 5 checkpoint verification
|
||||||
|
1. User tests admin panel on carei.pagedev.pl
|
||||||
|
2. If approved → `/paul:unify .paul/phases/05-admin-panel/05-01-PLAN.md`
|
||||||
|
3. Unify closes Phase 5 → Milestone v0.1 complete!
|
||||||
|
|
||||||
|
**After milestone:**
|
||||||
|
- Backlog: ubezpieczenie + wyjazd zagraniczny (czeka na nową wersję API Softra)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Key Decisions Made
|
||||||
|
|
||||||
|
| Decision | Rationale |
|
||||||
|
|----------|-----------|
|
||||||
|
| CPT + post_meta (not custom table) | WordPress-native, simpler for MVP, full admin UI for free |
|
||||||
|
| Fire-and-forget save | Never block user response — rezerwacja already in Softra |
|
||||||
|
| Meta-based status (not taxonomy) | Simple 3-value enum, no UI overhead |
|
||||||
|
| Auto-mark-read on edit | Reduce manual clicks, natural workflow |
|
||||||
|
| Hidden title input | Title auto-generated, not user-editable |
|
||||||
|
| style.display for step transitions | Inline display:none overrides CSS classes |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Key Files
|
||||||
|
|
||||||
|
| File | Purpose |
|
||||||
|
|------|---------|
|
||||||
|
| `.paul/STATE.md` | Live project state |
|
||||||
|
| `.paul/ROADMAP.md` | Phase overview (1-4 ✅, 5 in progress) |
|
||||||
|
| `.paul/phases/05-admin-panel/05-01-PLAN.md` | Phase 5 plan (checkpoint pending) |
|
||||||
|
| `.paul/phases/04-polish-testing/04-01-SUMMARY.md` | Phase 4 summary |
|
||||||
|
| `wp-content/plugins/carei-reservation/` | Plugin directory (all code) |
|
||||||
|
| `includes/class-admin-panel.php` | **NEW** — Admin panel CPT + columns + meta box |
|
||||||
|
| `includes/class-rest-proxy.php` | Modified — save_reservation() call |
|
||||||
|
| `includes/class-elementor-widget.php` | ARIA attrs (Phase 4) |
|
||||||
|
| `assets/js/carei-reservation.js` | Edge cases + animations + a11y (Phase 4) |
|
||||||
|
| `assets/css/carei-reservation.css` | Transitions + CSS fix (Phase 4) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Resume Instructions
|
||||||
|
|
||||||
|
1. Read `.paul/STATE.md` for latest position
|
||||||
|
2. Phase 5 APPLY in progress — checkpoint pending
|
||||||
|
3. Run `/paul:resume` — will route to checkpoint verification
|
||||||
|
4. After approved → `/paul:unify` → milestone complete
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Handoff created: 2026-03-25*
|
||||||
112
.paul/handoffs/archive/HANDOFF-2026-03-25.md
Normal file
112
.paul/handoffs/archive/HANDOFF-2026-03-25.md
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
# PAUL Handoff
|
||||||
|
|
||||||
|
**Date:** 2026-03-25
|
||||||
|
**Status:** paused — checkpoint human-verify pending (awaiting deploy + test)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## READ THIS FIRST
|
||||||
|
|
||||||
|
You have no prior context. This document tells you everything.
|
||||||
|
|
||||||
|
**Project:** Carei — Formularz rezerwacji samochodu jako plugin Elementor, zintegrowany z Softra Rent API
|
||||||
|
**Core value:** Klient klika "Złóż zapytanie o rezerwację" → modal z formularzem → dane z API Softra → rezerwacja
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Current State
|
||||||
|
|
||||||
|
**Milestone:** v0.1 Formularz Rezerwacji MVP
|
||||||
|
**Phase:** 2 of 4 — Form UI Krok 1 (Formularz)
|
||||||
|
**Plan:** 02-01 — APPLY in progress (Tasks 1+2 done, Task 3 checkpoint pending)
|
||||||
|
|
||||||
|
**Loop Position:**
|
||||||
|
```
|
||||||
|
PLAN ──▶ APPLY ──▶ UNIFY
|
||||||
|
✓ ◐ ○ [Apply in progress — checkpoint:human-verify waiting]
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## What Was Done
|
||||||
|
|
||||||
|
**Phase 1 (complete):**
|
||||||
|
- Plugin `carei-reservation` z natywnym cURL proxy do Softra Rent API
|
||||||
|
- 10 WP REST endpoints w namespace `carei/v1`
|
||||||
|
- Widget Elementor z przyciskiem CTA + modal overlay
|
||||||
|
- Fix: widget require_once przeniesiony do hooka elementor/widgets/register
|
||||||
|
- Fix: zamiana wp_remote_post na natywny cURL (matching softra-test.php)
|
||||||
|
|
||||||
|
**Phase 2 (in progress — Tasks 1+2 done):**
|
||||||
|
- HTML formularza z wszystkimi sekcjami z Figmy (widget render)
|
||||||
|
- CSS 541 linii (responsive, custom checkboxy, karty opcji, walidacja)
|
||||||
|
- JS 580+ linii (API integration, walidacja, interakcje)
|
||||||
|
- Nowy endpoint GET /car-classes-all (listAll) — segmenty ładują się od razu bez wymagania oddziału
|
||||||
|
- Opcje dodatkowe ładowane dynamicznie z cennika API
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## What's In Progress
|
||||||
|
|
||||||
|
- **Task 3 checkpoint:human-verify** — użytkownik musi wgrać pliki na serwer i przetestować wizualnie
|
||||||
|
- Pliki do wgrania:
|
||||||
|
- `includes/class-softra-api.php` (natywny cURL + get_all_car_classes)
|
||||||
|
- `includes/class-rest-proxy.php` (nowy endpoint car-classes-all)
|
||||||
|
- `includes/class-elementor-widget.php` (pełny HTML formularza)
|
||||||
|
- `assets/css/carei-reservation.css` (kompletne style)
|
||||||
|
- `assets/js/carei-reservation.js` (logika + API)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## What's Next
|
||||||
|
|
||||||
|
**Immediate:** Wgrać pliki na serwer → przetestować formularz → "approved" lub opisać problemy
|
||||||
|
|
||||||
|
**After that:**
|
||||||
|
- Jeśli approved → /paul:unify → Phase 3 (Overlay/podsumowanie + submit rezerwacji do API)
|
||||||
|
- Jeśli issues → fix → re-verify
|
||||||
|
|
||||||
|
**Remaining phases:**
|
||||||
|
- Phase 3: Form UI Krok 2 (Overlay z podsumowaniem, tworzenie klienta, booking)
|
||||||
|
- Phase 4: Polish & integration testing
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Key Decisions Made
|
||||||
|
|
||||||
|
| Decision | Rationale |
|
||||||
|
|----------|-----------|
|
||||||
|
| Natywny cURL zamiast wp_remote_post | softra-test.php działa z cURL, WP HTTP API timeout |
|
||||||
|
| GET /car/class/listAll dla segmentów | Segment jest pierwszym polem w Figmie, nie powinien wymagać oddziału |
|
||||||
|
| Osobny plugin carei-reservation | Czystsza separacja od elementor-addon |
|
||||||
|
| sslverify nie ustawiane (domyślne cURL) | Tak jak w softra-test.php który działa na produkcji |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Key Files
|
||||||
|
|
||||||
|
| File | Purpose |
|
||||||
|
|------|---------|
|
||||||
|
| `.paul/STATE.md` | Live project state |
|
||||||
|
| `.paul/ROADMAP.md` | Phase overview (Phase 1 ✅, Phase 2-4 ⬜) |
|
||||||
|
| `.paul/phases/02-form-ui-step1/02-01-PLAN.md` | Current plan |
|
||||||
|
| `.paul/phases/01-reservation-form-plugin/01-01-SUMMARY.md` | Phase 1 summary |
|
||||||
|
| `wp-content/plugins/carei-reservation/` | Plugin directory (all code) |
|
||||||
|
| `docs/figma-formularz/README.md` | Figma design spec |
|
||||||
|
| `docs/figma-formularz/screenshot-desktop.png` | Desktop reference |
|
||||||
|
| `docs/figma-formularz/screenshot-mobile.png` | Mobile reference |
|
||||||
|
| `softra-test.php` | Working Softra API test (reference for cURL config) |
|
||||||
|
| `.env` | API credentials (url, username, password) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Resume Instructions
|
||||||
|
|
||||||
|
1. Read `.paul/STATE.md` for latest position
|
||||||
|
2. User needs to deploy files to server and test
|
||||||
|
3. After test: "approved" → run `/paul:unify` then `/paul:plan` for Phase 3
|
||||||
|
4. Or run `/paul:resume` or `/paul:progress`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Handoff created: 2026-03-25*
|
||||||
274
.paul/phases/05-admin-panel/05-01-PLAN.md
Normal file
274
.paul/phases/05-admin-panel/05-01-PLAN.md
Normal file
@@ -0,0 +1,274 @@
|
|||||||
|
---
|
||||||
|
phase: 05-admin-panel
|
||||||
|
plan: 01
|
||||||
|
type: execute
|
||||||
|
wave: 1
|
||||||
|
depends_on: []
|
||||||
|
files_modified:
|
||||||
|
- wp-content/plugins/carei-reservation/includes/class-admin-panel.php
|
||||||
|
- wp-content/plugins/carei-reservation/includes/class-rest-proxy.php
|
||||||
|
- wp-content/plugins/carei-reservation/carei-reservation.php
|
||||||
|
autonomous: false
|
||||||
|
---
|
||||||
|
|
||||||
|
<objective>
|
||||||
|
## Goal
|
||||||
|
Admin panel w wp-admin do przeglądania rezerwacji: Custom Post Type `carei_reservation`, automatyczny zapis przy udanej rezerwacji, lista z kolumnami i filtrami, podgląd szczegółów, statusy (nowe/przeczytane/zrealizowane).
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
Właściciel firmy musi mieć przegląd wszystkich rezerwacji złożonych przez formularz — bez logowania do panelu Softra. Dane zapisane w WordPress = szybki podgląd, filtrowanie, zmiana statusu.
|
||||||
|
|
||||||
|
## Output
|
||||||
|
- Nowy plik: `includes/class-admin-panel.php` (CPT, kolumny, meta box, statusy)
|
||||||
|
- Zmodyfikowany: `includes/class-rest-proxy.php` (zapis rezerwacji po sukcesie)
|
||||||
|
- Zmodyfikowany: `carei-reservation.php` (include nowej klasy)
|
||||||
|
</objective>
|
||||||
|
|
||||||
|
<context>
|
||||||
|
## Project Context
|
||||||
|
@.paul/PROJECT.md
|
||||||
|
@.paul/ROADMAP.md
|
||||||
|
@.paul/STATE.md
|
||||||
|
|
||||||
|
## Prior Work
|
||||||
|
@.paul/phases/03-form-submit-booking/03-01-SUMMARY.md
|
||||||
|
- Booking flow: customer/add → pricingSummary → makebooking → confirm
|
||||||
|
- Dane zbierane: segment, daty, oddział, extras, imię, nazwisko, adres, email, telefon, PESEL, wiadomość
|
||||||
|
- Po sukcesie: reservationId + reservationNo z API
|
||||||
|
|
||||||
|
## Source Files
|
||||||
|
@wp-content/plugins/carei-reservation/carei-reservation.php
|
||||||
|
@wp-content/plugins/carei-reservation/includes/class-rest-proxy.php
|
||||||
|
</context>
|
||||||
|
|
||||||
|
<acceptance_criteria>
|
||||||
|
|
||||||
|
## AC-1: CPT carei_reservation zarejestrowany
|
||||||
|
```gherkin
|
||||||
|
Given plugin jest aktywny
|
||||||
|
When otworzę wp-admin
|
||||||
|
Then w menu bocznym widzę "Rezerwacje" z ikoną dashicons-car
|
||||||
|
And CPT nie jest publiczny (brak na frontendzie)
|
||||||
|
And supports: title (auto-generowany, nie edytowalny)
|
||||||
|
```
|
||||||
|
|
||||||
|
## AC-2: Rezerwacja zapisuje się automatycznie po sukcesie
|
||||||
|
```gherkin
|
||||||
|
Given użytkownik wypełnił formularz i potwierdził rezerwację
|
||||||
|
When API makebooking + confirm zwraca sukces
|
||||||
|
Then w wp-admin pojawia się nowy wpis z danymi rezerwacji
|
||||||
|
And tytuł: "Rezerwacja #[reservationNo] — [Imię Nazwisko]"
|
||||||
|
And wszystkie dane formularza zapisane w post_meta
|
||||||
|
And status: "nowe"
|
||||||
|
```
|
||||||
|
|
||||||
|
## AC-3: Lista rezerwacji z kolumnami
|
||||||
|
```gherkin
|
||||||
|
Given jestem w wp-admin → Rezerwacje
|
||||||
|
When widzę listę wpisów
|
||||||
|
Then kolumny: Nr rezerwacji | Klient | Segment | Daty | Oddział | Status | Data
|
||||||
|
And kolumna Status pokazuje kolorowy badge (nowe=niebieski, przeczytane=żółty, zrealizowane=zielony)
|
||||||
|
And mogę sortować po dacie
|
||||||
|
And mogę filtrować po statusie (dropdown nad listą)
|
||||||
|
```
|
||||||
|
|
||||||
|
## AC-4: Podgląd szczegółów rezerwacji
|
||||||
|
```gherkin
|
||||||
|
Given kliknę na rezerwację w liście
|
||||||
|
When otwiera się edycja wpisu
|
||||||
|
Then widzę meta box "Szczegóły rezerwacji" z tabelą:
|
||||||
|
- Nr rezerwacji, ID klienta Softra
|
||||||
|
- Segment, daty od-do, oddział odbioru, oddział zwrotu
|
||||||
|
- Imię, nazwisko, email, telefon, PESEL, adres
|
||||||
|
- Wybrane opcje dodatkowe
|
||||||
|
- Wiadomość klienta
|
||||||
|
And widzę dropdown do zmiany statusu (nowe → przeczytane → zrealizowane)
|
||||||
|
And status zmienia się po kliknięciu "Zaktualizuj"
|
||||||
|
```
|
||||||
|
|
||||||
|
## AC-5: Przy otwarciu rezerwacji status zmienia się na "przeczytane"
|
||||||
|
```gherkin
|
||||||
|
Given rezerwacja ma status "nowe"
|
||||||
|
When otwieram ją w wp-admin
|
||||||
|
Then status automatycznie zmienia się na "przeczytane"
|
||||||
|
And badge w liście aktualizuje się
|
||||||
|
```
|
||||||
|
|
||||||
|
</acceptance_criteria>
|
||||||
|
|
||||||
|
<tasks>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>Task 1: CPT registration + admin panel class</name>
|
||||||
|
<files>wp-content/plugins/carei-reservation/includes/class-admin-panel.php, wp-content/plugins/carei-reservation/carei-reservation.php</files>
|
||||||
|
<action>
|
||||||
|
1. **Utwórz `includes/class-admin-panel.php`:**
|
||||||
|
|
||||||
|
Klasa `Carei_Admin_Panel` z metodami:
|
||||||
|
|
||||||
|
a) `register_post_type()` — hook na `init`:
|
||||||
|
- CPT: `carei_reservation`
|
||||||
|
- label: "Rezerwacje" / "Rezerwacja"
|
||||||
|
- menu_icon: `dashicons-car`
|
||||||
|
- public: false, show_ui: true, show_in_menu: true
|
||||||
|
- supports: `['title']` (title auto-generowany)
|
||||||
|
- capability_type: 'post'
|
||||||
|
- menu_position: 26 (pod Komentarze)
|
||||||
|
|
||||||
|
b) `register_statuses()` — custom taxonomy NIE jest potrzebna, użyj post_meta `_carei_status` z wartościami: `nowe`, `przeczytane`, `zrealizowane`
|
||||||
|
|
||||||
|
c) `add_admin_columns($columns)` — hook `manage_carei_reservation_posts_columns`:
|
||||||
|
- Kolumny: cb, reservation_no, client, segment, dates, branch, status, date
|
||||||
|
- Usuń domyślny title
|
||||||
|
|
||||||
|
d) `render_admin_column($column, $post_id)` — hook `manage_carei_reservation_posts_custom_column`:
|
||||||
|
- reservation_no: get_post_meta `_carei_reservation_no`
|
||||||
|
- client: `_carei_first_name` + `_carei_last_name`
|
||||||
|
- segment: `_carei_segment`
|
||||||
|
- dates: `_carei_date_from` + `_carei_date_to` (format: d.m.Y H:i)
|
||||||
|
- branch: `_carei_pickup_branch`
|
||||||
|
- status: badge z kolorem (nowe=#2F2482, przeczytane=#f59e0b, zrealizowane=#22c55e)
|
||||||
|
|
||||||
|
e) `add_status_filter()` — hook `restrict_manage_posts`:
|
||||||
|
- Dropdown filtra statusu nad listą
|
||||||
|
- Opcje: Wszystkie, Nowe, Przeczytane, Zrealizowane
|
||||||
|
|
||||||
|
f) `filter_by_status($query)` — hook `pre_get_posts`:
|
||||||
|
- Filtruj po `_carei_status` meta_query
|
||||||
|
|
||||||
|
g) `add_meta_box()` — hook `add_meta_boxes`:
|
||||||
|
- Meta box "Szczegóły rezerwacji" w edycji wpisu
|
||||||
|
- Tabela z wszystkimi danymi
|
||||||
|
- Dropdown statusu z save
|
||||||
|
|
||||||
|
h) `save_meta_box($post_id)` — hook `save_post_carei_reservation`:
|
||||||
|
- Zapisz wybrany status
|
||||||
|
|
||||||
|
i) `auto_mark_read($post_id)` — hook `edit_form_after_title` lub `admin_head`:
|
||||||
|
- Jeśli status == 'nowe' i jesteśmy na ekranie edycji → zmień na 'przeczytane'
|
||||||
|
|
||||||
|
j) **Inline CSS** dla badge'ów statusu i meta box — dodaj przez `admin_head` hook, scoped do CPT screen
|
||||||
|
|
||||||
|
2. **Zmodyfikuj `carei-reservation.php`:**
|
||||||
|
- Dodaj `require_once` dla nowej klasy
|
||||||
|
- Zainicjalizuj `new Carei_Admin_Panel()` w `plugins_loaded`
|
||||||
|
|
||||||
|
Avoid: Nie twórz oddzielnych template files. Wszystko w jednej klasie. Nie dodawaj custom taxonomy — meta jest wystarczające.
|
||||||
|
</action>
|
||||||
|
<verify>
|
||||||
|
- Otwórz wp-admin → "Rezerwacje" widoczne w menu
|
||||||
|
- Lista pusta, kolumny widoczne
|
||||||
|
- Filtr statusu nad listą
|
||||||
|
</verify>
|
||||||
|
<done>AC-1, AC-3, AC-4, AC-5 satisfied: CPT zarejestrowany, kolumny, meta box, auto-read</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>Task 2: Zapis rezerwacji po sukcesie booking</name>
|
||||||
|
<files>wp-content/plugins/carei-reservation/includes/class-rest-proxy.php</files>
|
||||||
|
<action>
|
||||||
|
1. **W `class-rest-proxy.php`**, w callback dla `/booking` endpoint:
|
||||||
|
- Po udanym `make_booking()` i `confirm_booking()` (jeśli confirm jest w tym samym request) LUB:
|
||||||
|
- Lepiej: dodaj nowy statyczny helper `Carei_Admin_Panel::save_reservation($data, $result)`
|
||||||
|
|
||||||
|
2. **W callback `/booking`:**
|
||||||
|
- Po `$result = $api->make_booking($body)` z sukcesem:
|
||||||
|
- Wywołaj `Carei_Admin_Panel::save_reservation($body, $result)`
|
||||||
|
|
||||||
|
3. **Metoda `save_reservation($booking_data, $api_result)`:**
|
||||||
|
- `wp_insert_post()` z:
|
||||||
|
- post_type: `carei_reservation`
|
||||||
|
- post_title: `Rezerwacja #{reservationNo} — {firstName} {lastName}`
|
||||||
|
- post_status: `publish`
|
||||||
|
- `update_post_meta()` dla:
|
||||||
|
- `_carei_reservation_no` ← reservationNo
|
||||||
|
- `_carei_reservation_id` ← reservationId
|
||||||
|
- `_carei_customer_id` ← customerId
|
||||||
|
- `_carei_segment` ← carParameters.categoryName
|
||||||
|
- `_carei_date_from` ← dateFrom
|
||||||
|
- `_carei_date_to` ← dateTo
|
||||||
|
- `_carei_pickup_branch` ← pickUpLocation.branchName
|
||||||
|
- `_carei_return_branch` ← returnLocation.branchName
|
||||||
|
- `_carei_first_name` ← drivers[0].firstName (lub z booking_data)
|
||||||
|
- `_carei_last_name` ← drivers[0].lastName
|
||||||
|
- `_carei_email` ← drivers[0].email
|
||||||
|
- `_carei_phone` ← drivers[0].phone
|
||||||
|
- `_carei_pesel` ← drivers[0].pesel
|
||||||
|
- `_carei_address` ← JSON encode drivers[0].address
|
||||||
|
- `_carei_extras` ← JSON encode priceItems
|
||||||
|
- `_carei_comments` ← comments
|
||||||
|
- `_carei_status` ← 'nowe'
|
||||||
|
- `_carei_raw_response` ← JSON encode api_result (backup)
|
||||||
|
|
||||||
|
4. **Error handling:** Jeśli wp_insert_post() fail, loguj do error_log ale NIE blokuj response do usera (rezerwacja już w Softra jest OK).
|
||||||
|
|
||||||
|
Avoid: Nie modyfikuj flow rezerwacji. Zapis jest fire-and-forget — nigdy nie blokuje odpowiedzi do klienta.
|
||||||
|
</action>
|
||||||
|
<verify>
|
||||||
|
- Złóż testową rezerwację przez formularz
|
||||||
|
- Sprawdź wp-admin → Rezerwacje → nowy wpis z danymi
|
||||||
|
- Kliknij wpis → meta box ze szczegółami
|
||||||
|
</verify>
|
||||||
|
<done>AC-2 satisfied: rezerwacja zapisuje się automatycznie po sukcesie</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
<task type="checkpoint:human-verify" gate="blocking">
|
||||||
|
<what-built>Admin panel rezerwacji w wp-admin: CPT, kolumny, filtr statusu, meta box szczegółów, auto-zapis po rezerwacji, auto-mark-read</what-built>
|
||||||
|
<how-to-verify>
|
||||||
|
1. Otwórz wp-admin na carei.pagedev.pl
|
||||||
|
2. W menu bocznym: "Rezerwacje" z ikoną samochodu
|
||||||
|
3. Złóż rezerwację przez formularz na frontendzie
|
||||||
|
4. Wróć do wp-admin → Rezerwacje → nowy wpis ze statusem "nowe" (niebieski badge)
|
||||||
|
5. Kolumny: Nr rezerwacji, Klient, Segment, Daty, Oddział, Status, Data
|
||||||
|
6. Filtr statusu nad listą (dropdown)
|
||||||
|
7. Kliknij wpis → meta box "Szczegóły rezerwacji" z pełnymi danymi
|
||||||
|
8. Status zmienił się na "przeczytane" (żółty)
|
||||||
|
9. Zmień status na "zrealizowane" → Zaktualizuj → zielony badge
|
||||||
|
</how-to-verify>
|
||||||
|
<resume-signal>Type "approved" to continue, or describe issues to fix</resume-signal>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
</tasks>
|
||||||
|
|
||||||
|
<boundaries>
|
||||||
|
|
||||||
|
## DO NOT CHANGE
|
||||||
|
- wp-content/plugins/carei-reservation/assets/js/carei-reservation.js (Phase 4 stable)
|
||||||
|
- wp-content/plugins/carei-reservation/assets/css/carei-reservation.css (Phase 4 stable)
|
||||||
|
- wp-content/plugins/carei-reservation/includes/class-elementor-widget.php (Phase 4 stable)
|
||||||
|
- wp-content/plugins/carei-reservation/includes/class-softra-api.php (API proxy stable)
|
||||||
|
- .env (credentials)
|
||||||
|
|
||||||
|
## SCOPE LIMITS
|
||||||
|
- Nie dodawaj eksportu CSV/PDF (przyszły feature)
|
||||||
|
- Nie dodawaj email notyfikacji (zależy od Softra)
|
||||||
|
- Nie modyfikuj frontend formularza
|
||||||
|
- Nie dodawaj REST API endpoints dla admin panelu (nie potrzebne — natywny WP admin)
|
||||||
|
- Nie twórz custom database table — CPT + post_meta wystarczy dla MVP
|
||||||
|
|
||||||
|
</boundaries>
|
||||||
|
|
||||||
|
<verification>
|
||||||
|
Before declaring plan complete:
|
||||||
|
- [ ] CPT `carei_reservation` zarejestrowany w wp-admin
|
||||||
|
- [ ] Menu "Rezerwacje" widoczne z ikoną
|
||||||
|
- [ ] Rezerwacja zapisuje się po sukcesie booking
|
||||||
|
- [ ] Lista: kolumny Nr, Klient, Segment, Daty, Oddział, Status, Data
|
||||||
|
- [ ] Filtr statusu działa
|
||||||
|
- [ ] Meta box ze wszystkimi danymi
|
||||||
|
- [ ] Auto-mark-read przy otwarciu
|
||||||
|
- [ ] Zmiana statusu przez dropdown + save
|
||||||
|
- [ ] Badge kolory: nowe=niebieski, przeczytane=żółty, zrealizowane=zielony
|
||||||
|
- [ ] Brak regresji w booking flow na frontendzie
|
||||||
|
</verification>
|
||||||
|
|
||||||
|
<success_criteria>
|
||||||
|
- All 5 acceptance criteria met
|
||||||
|
- All verification checks pass
|
||||||
|
- No regressions in frontend booking flow
|
||||||
|
- Human verification approved on carei.pagedev.pl
|
||||||
|
</success_criteria>
|
||||||
|
|
||||||
|
<output>
|
||||||
|
After completion, create `.paul/phases/05-admin-panel/05-01-SUMMARY.md`
|
||||||
|
</output>
|
||||||
129
.paul/phases/05-admin-panel/05-01-SUMMARY.md
Normal file
129
.paul/phases/05-admin-panel/05-01-SUMMARY.md
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
---
|
||||||
|
phase: 05-admin-panel
|
||||||
|
plan: 01
|
||||||
|
subsystem: admin
|
||||||
|
tags: [wordpress, cpt, post-meta, admin-panel]
|
||||||
|
|
||||||
|
requires:
|
||||||
|
- phase: 03-form-submit-booking
|
||||||
|
provides: booking flow z reservationId/reservationNo w API response
|
||||||
|
provides:
|
||||||
|
- Admin panel CPT carei_reservation w wp-admin
|
||||||
|
- Automatyczny zapis rezerwacji po sukcesie booking
|
||||||
|
- Lista z kolumnami, filtr statusu, meta box szczegółów
|
||||||
|
- System statusów nowe/przeczytane/zrealizowane
|
||||||
|
affects: []
|
||||||
|
|
||||||
|
tech-stack:
|
||||||
|
added: []
|
||||||
|
patterns: [CPT + post_meta for data storage, fire-and-forget save, static helper method pattern]
|
||||||
|
|
||||||
|
key-files:
|
||||||
|
created: [includes/class-admin-panel.php]
|
||||||
|
modified: [includes/class-rest-proxy.php, carei-reservation.php]
|
||||||
|
|
||||||
|
key-decisions:
|
||||||
|
- "CPT + post_meta zamiast custom table — WordPress-native, prostsze dla MVP"
|
||||||
|
- "Fire-and-forget save — nigdy nie blokuje response do usera"
|
||||||
|
- "Meta-based status zamiast taxonomy — prosty 3-wartościowy enum"
|
||||||
|
- "Auto-mark-read on edit — naturalny workflow bez dodatkowych kliknięć"
|
||||||
|
|
||||||
|
patterns-established:
|
||||||
|
- "Static save helper: Carei_Admin_Panel::save_reservation() wywoływany z REST proxy"
|
||||||
|
- "Inline admin CSS scoped do CPT screen"
|
||||||
|
|
||||||
|
duration: ~2h
|
||||||
|
started: 2026-03-25
|
||||||
|
completed: 2026-03-25
|
||||||
|
---
|
||||||
|
|
||||||
|
# Phase 5 Plan 01: Admin Panel — Historia Formularzy Summary
|
||||||
|
|
||||||
|
**CPT carei_reservation z automatycznym zapisem rezerwacji, listą z kolumnami/filtrami, meta boxem szczegółów i systemem statusów nowe/przeczytane/zrealizowane.**
|
||||||
|
|
||||||
|
## Performance
|
||||||
|
|
||||||
|
| Metric | Value |
|
||||||
|
|--------|-------|
|
||||||
|
| Duration | ~2h |
|
||||||
|
| Started | 2026-03-25 |
|
||||||
|
| Completed | 2026-03-25 |
|
||||||
|
| Tasks | 3 completed (2 auto + 1 human-verify) |
|
||||||
|
| Files modified | 3 |
|
||||||
|
|
||||||
|
## Acceptance Criteria Results
|
||||||
|
|
||||||
|
| Criterion | Status | Notes |
|
||||||
|
|-----------|--------|-------|
|
||||||
|
| AC-1: CPT carei_reservation zarejestrowany | Pass | Menu "Rezerwacje" z dashicons-car, public: false, show_ui: true |
|
||||||
|
| AC-2: Rezerwacja zapisuje się automatycznie | Pass | save_reservation() wywoływane po make_booking success |
|
||||||
|
| AC-3: Lista z kolumnami | Pass | Nr, Klient, Segment, Daty, Oddział, Status, Data + filtr statusu |
|
||||||
|
| AC-4: Podgląd szczegółów | Pass | Meta box z pełną tabelą danych + dropdown statusu |
|
||||||
|
| AC-5: Auto-mark-read | Pass | Status "nowe" → "przeczytane" przy otwarciu edycji |
|
||||||
|
|
||||||
|
## Accomplishments
|
||||||
|
|
||||||
|
- CPT `carei_reservation` z pełnym admin UI: kolumny, filtr, meta box, statusy z kolorowymi badge'ami
|
||||||
|
- Fire-and-forget zapis rezerwacji po sukcesie booking (nie blokuje response)
|
||||||
|
- Klikalny nr rezerwacji w liście (link do edycji)
|
||||||
|
|
||||||
|
## Files Created/Modified
|
||||||
|
|
||||||
|
| File | Change | Purpose |
|
||||||
|
|------|--------|---------|
|
||||||
|
| `includes/class-admin-panel.php` | Created | CPT registration, admin columns, meta box, status system, auto-mark-read, save helper |
|
||||||
|
| `includes/class-rest-proxy.php` | Modified | Wywołanie save_reservation() po udanym make_booking |
|
||||||
|
| `carei-reservation.php` | Modified | Include + inicjalizacja Carei_Admin_Panel |
|
||||||
|
|
||||||
|
## Decisions Made
|
||||||
|
|
||||||
|
| Decision | Rationale | Impact |
|
||||||
|
|----------|-----------|--------|
|
||||||
|
| CPT + post_meta (nie custom table) | WordPress-native, pełne admin UI za darmo | Prostsze dla MVP, ewentualna migracja jeśli skala wzrośnie |
|
||||||
|
| Fire-and-forget save | Rezerwacja już w Softra — WP save nie może blokować usera | Bezpieczne — error_log w razie problemu |
|
||||||
|
| Meta-based status (nie taxonomy) | 3 wartości enum, zero UI overhead | Proste, wystarczające dla MVP |
|
||||||
|
| Auto-mark-read on edit | Naturalny workflow, mniej kliknięć | UX improvement |
|
||||||
|
| Klikalny nr rezerwacji | Brak kolumny title = brak linku do edycji | Naprawione podczas checkpoint — link w kolumnie reservation_no |
|
||||||
|
|
||||||
|
## Deviations from Plan
|
||||||
|
|
||||||
|
### Summary
|
||||||
|
|
||||||
|
| Type | Count | Impact |
|
||||||
|
|------|-------|--------|
|
||||||
|
| Auto-fixed | 1 | Kliknięcie w rezerwację — minor UX fix |
|
||||||
|
| Scope additions | 0 | — |
|
||||||
|
| Deferred | 0 | — |
|
||||||
|
|
||||||
|
**Total impact:** Minimalne — jeden fix UX podczas checkpoint
|
||||||
|
|
||||||
|
### Auto-fixed Issues
|
||||||
|
|
||||||
|
**1. Brak linku do edycji w liście rezerwacji**
|
||||||
|
- **Found during:** Checkpoint (Task 3)
|
||||||
|
- **Issue:** Bez kolumny `title` nie było klikalnego linku do szczegółów
|
||||||
|
- **Fix:** Owinięcie nr rezerwacji w `<a>` z `get_edit_post_link()`
|
||||||
|
- **Files:** `includes/class-admin-panel.php`
|
||||||
|
- **Verification:** User potwierdził "Teraz jest ok"
|
||||||
|
|
||||||
|
## Issues Encountered
|
||||||
|
|
||||||
|
| Issue | Resolution |
|
||||||
|
|-------|------------|
|
||||||
|
| Brak linku do edycji rezerwacji | Dodano link w kolumnie reservation_no |
|
||||||
|
|
||||||
|
## Next Phase Readiness
|
||||||
|
|
||||||
|
**Ready:**
|
||||||
|
- Milestone v0.1 kompletny — wszystkie 5 faz zakończone
|
||||||
|
- Plugin w pełni funkcjonalny: formularz → API → admin panel
|
||||||
|
|
||||||
|
**Concerns:**
|
||||||
|
- Brak — MVP complete
|
||||||
|
|
||||||
|
**Blockers:**
|
||||||
|
- None
|
||||||
|
|
||||||
|
---
|
||||||
|
*Phase: 05-admin-panel, Plan: 01*
|
||||||
|
*Completed: 2026-03-25*
|
||||||
@@ -50,6 +50,7 @@ function carei_parse_env() {
|
|||||||
*/
|
*/
|
||||||
require_once CAREI_RESERVATION_PATH . 'includes/class-softra-api.php';
|
require_once CAREI_RESERVATION_PATH . 'includes/class-softra-api.php';
|
||||||
require_once CAREI_RESERVATION_PATH . 'includes/class-rest-proxy.php';
|
require_once CAREI_RESERVATION_PATH . 'includes/class-rest-proxy.php';
|
||||||
|
require_once CAREI_RESERVATION_PATH . 'includes/class-admin-panel.php';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize plugin on plugins_loaded
|
* Initialize plugin on plugins_loaded
|
||||||
@@ -73,6 +74,9 @@ add_action( 'plugins_loaded', function () {
|
|||||||
|
|
||||||
// Initialize REST proxy
|
// Initialize REST proxy
|
||||||
new Carei_REST_Proxy();
|
new Carei_REST_Proxy();
|
||||||
|
|
||||||
|
// Initialize admin panel
|
||||||
|
new Carei_Admin_Panel();
|
||||||
} );
|
} );
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -0,0 +1,412 @@
|
|||||||
|
<?php
|
||||||
|
if ( ! defined( 'ABSPATH' ) ) {
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
class Carei_Admin_Panel {
|
||||||
|
|
||||||
|
const POST_TYPE = 'carei_reservation';
|
||||||
|
const META_PREFIX = '_carei_';
|
||||||
|
|
||||||
|
private static $statuses = array(
|
||||||
|
'nowe' => array( 'label' => 'Nowe', 'color' => '#2F2482' ),
|
||||||
|
'przeczytane' => array( 'label' => 'Przeczytane', 'color' => '#f59e0b' ),
|
||||||
|
'zrealizowane' => array( 'label' => 'Zrealizowane', 'color' => '#22c55e' ),
|
||||||
|
);
|
||||||
|
|
||||||
|
public function __construct() {
|
||||||
|
add_action( 'init', array( $this, 'register_post_type' ) );
|
||||||
|
add_filter( 'manage_' . self::POST_TYPE . '_posts_columns', array( $this, 'admin_columns' ) );
|
||||||
|
add_action( 'manage_' . self::POST_TYPE . '_posts_custom_column', array( $this, 'render_column' ), 10, 2 );
|
||||||
|
add_action( 'restrict_manage_posts', array( $this, 'status_filter_dropdown' ) );
|
||||||
|
add_action( 'pre_get_posts', array( $this, 'filter_by_status' ) );
|
||||||
|
add_action( 'add_meta_boxes', array( $this, 'add_meta_boxes' ) );
|
||||||
|
add_action( 'save_post_' . self::POST_TYPE, array( $this, 'save_meta_box' ), 10, 2 );
|
||||||
|
add_action( 'edit_form_after_title', array( $this, 'auto_mark_read' ) );
|
||||||
|
add_action( 'admin_head', array( $this, 'admin_styles' ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
public function register_post_type() {
|
||||||
|
register_post_type( self::POST_TYPE, array(
|
||||||
|
'labels' => array(
|
||||||
|
'name' => 'Rezerwacje',
|
||||||
|
'singular_name' => 'Rezerwacja',
|
||||||
|
'menu_name' => 'Rezerwacje',
|
||||||
|
'all_items' => 'Wszystkie rezerwacje',
|
||||||
|
'view_item' => 'Zobacz rezerwację',
|
||||||
|
'edit_item' => 'Szczegóły rezerwacji',
|
||||||
|
'search_items' => 'Szukaj rezerwacji',
|
||||||
|
'not_found' => 'Nie znaleziono rezerwacji',
|
||||||
|
'not_found_in_trash' => 'Brak rezerwacji w koszu',
|
||||||
|
),
|
||||||
|
'public' => false,
|
||||||
|
'show_ui' => true,
|
||||||
|
'show_in_menu' => true,
|
||||||
|
'menu_icon' => 'dashicons-car',
|
||||||
|
'menu_position' => 26,
|
||||||
|
'supports' => array( 'title' ),
|
||||||
|
'capability_type' => 'post',
|
||||||
|
'has_archive' => false,
|
||||||
|
'hierarchical' => false,
|
||||||
|
'show_in_rest' => false,
|
||||||
|
) );
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Admin Columns ──────────────────────────────────────────
|
||||||
|
|
||||||
|
public function admin_columns( $columns ) {
|
||||||
|
return array(
|
||||||
|
'cb' => '<input type="checkbox" />',
|
||||||
|
'reservation_no' => 'Nr rezerwacji',
|
||||||
|
'client' => 'Klient',
|
||||||
|
'segment' => 'Segment',
|
||||||
|
'dates' => 'Daty',
|
||||||
|
'branch' => 'Oddział',
|
||||||
|
'carei_status' => 'Status',
|
||||||
|
'date' => 'Data',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render_column( $column, $post_id ) {
|
||||||
|
switch ( $column ) {
|
||||||
|
case 'reservation_no':
|
||||||
|
$no = get_post_meta( $post_id, self::META_PREFIX . 'reservation_no', true ) ?: '—';
|
||||||
|
printf(
|
||||||
|
'<a class="row-title" href="%s"><strong>%s</strong></a>',
|
||||||
|
esc_url( get_edit_post_link( $post_id ) ),
|
||||||
|
esc_html( $no )
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case 'client':
|
||||||
|
$first = get_post_meta( $post_id, self::META_PREFIX . 'first_name', true );
|
||||||
|
$last = get_post_meta( $post_id, self::META_PREFIX . 'last_name', true );
|
||||||
|
echo esc_html( trim( $first . ' ' . $last ) ?: '—' );
|
||||||
|
break;
|
||||||
|
case 'segment':
|
||||||
|
echo esc_html( get_post_meta( $post_id, self::META_PREFIX . 'segment', true ) ?: '—' );
|
||||||
|
break;
|
||||||
|
case 'dates':
|
||||||
|
$from = get_post_meta( $post_id, self::META_PREFIX . 'date_from', true );
|
||||||
|
$to = get_post_meta( $post_id, self::META_PREFIX . 'date_to', true );
|
||||||
|
if ( $from && $to ) {
|
||||||
|
$from_fmt = date_i18n( 'd.m.Y H:i', strtotime( $from ) );
|
||||||
|
$to_fmt = date_i18n( 'd.m.Y H:i', strtotime( $to ) );
|
||||||
|
echo esc_html( $from_fmt . ' — ' . $to_fmt );
|
||||||
|
} else {
|
||||||
|
echo '—';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'branch':
|
||||||
|
echo esc_html( get_post_meta( $post_id, self::META_PREFIX . 'pickup_branch', true ) ?: '—' );
|
||||||
|
break;
|
||||||
|
case 'carei_status':
|
||||||
|
$status = get_post_meta( $post_id, self::META_PREFIX . 'status', true ) ?: 'nowe';
|
||||||
|
$info = isset( self::$statuses[ $status ] ) ? self::$statuses[ $status ] : self::$statuses['nowe'];
|
||||||
|
printf(
|
||||||
|
'<span class="carei-status-badge" style="background:%s;">%s</span>',
|
||||||
|
esc_attr( $info['color'] ),
|
||||||
|
esc_html( $info['label'] )
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Status Filter ──────────────────────────────────────────
|
||||||
|
|
||||||
|
public function status_filter_dropdown( $post_type ) {
|
||||||
|
if ( $post_type !== self::POST_TYPE ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$current = isset( $_GET['carei_status'] ) ? sanitize_text_field( $_GET['carei_status'] ) : '';
|
||||||
|
echo '<select name="carei_status">';
|
||||||
|
echo '<option value="">Wszystkie statusy</option>';
|
||||||
|
foreach ( self::$statuses as $key => $info ) {
|
||||||
|
printf(
|
||||||
|
'<option value="%s"%s>%s</option>',
|
||||||
|
esc_attr( $key ),
|
||||||
|
selected( $current, $key, false ),
|
||||||
|
esc_html( $info['label'] )
|
||||||
|
);
|
||||||
|
}
|
||||||
|
echo '</select>';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function filter_by_status( $query ) {
|
||||||
|
if ( ! is_admin() || ! $query->is_main_query() ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ( ( $query->get( 'post_type' ) !== self::POST_TYPE ) ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ( ! empty( $_GET['carei_status'] ) ) {
|
||||||
|
$status = sanitize_text_field( $_GET['carei_status'] );
|
||||||
|
if ( isset( self::$statuses[ $status ] ) ) {
|
||||||
|
$query->set( 'meta_query', array(
|
||||||
|
array(
|
||||||
|
'key' => self::META_PREFIX . 'status',
|
||||||
|
'value' => $status,
|
||||||
|
),
|
||||||
|
) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Default sort by date desc
|
||||||
|
if ( ! $query->get( 'orderby' ) ) {
|
||||||
|
$query->set( 'orderby', 'date' );
|
||||||
|
$query->set( 'order', 'DESC' );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Meta Box ───────────────────────────────────────────────
|
||||||
|
|
||||||
|
public function add_meta_boxes() {
|
||||||
|
add_meta_box(
|
||||||
|
'carei_reservation_details',
|
||||||
|
'Szczegóły rezerwacji',
|
||||||
|
array( $this, 'render_meta_box' ),
|
||||||
|
self::POST_TYPE,
|
||||||
|
'normal',
|
||||||
|
'high'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render_meta_box( $post ) {
|
||||||
|
wp_nonce_field( 'carei_save_reservation', 'carei_reservation_nonce' );
|
||||||
|
|
||||||
|
$meta = array(
|
||||||
|
'reservation_no' => get_post_meta( $post->ID, self::META_PREFIX . 'reservation_no', true ),
|
||||||
|
'reservation_id' => get_post_meta( $post->ID, self::META_PREFIX . 'reservation_id', true ),
|
||||||
|
'customer_id' => get_post_meta( $post->ID, self::META_PREFIX . 'customer_id', true ),
|
||||||
|
'segment' => get_post_meta( $post->ID, self::META_PREFIX . 'segment', true ),
|
||||||
|
'date_from' => get_post_meta( $post->ID, self::META_PREFIX . 'date_from', true ),
|
||||||
|
'date_to' => get_post_meta( $post->ID, self::META_PREFIX . 'date_to', true ),
|
||||||
|
'pickup_branch' => get_post_meta( $post->ID, self::META_PREFIX . 'pickup_branch', true ),
|
||||||
|
'return_branch' => get_post_meta( $post->ID, self::META_PREFIX . 'return_branch', true ),
|
||||||
|
'first_name' => get_post_meta( $post->ID, self::META_PREFIX . 'first_name', true ),
|
||||||
|
'last_name' => get_post_meta( $post->ID, self::META_PREFIX . 'last_name', true ),
|
||||||
|
'email' => get_post_meta( $post->ID, self::META_PREFIX . 'email', true ),
|
||||||
|
'phone' => get_post_meta( $post->ID, self::META_PREFIX . 'phone', true ),
|
||||||
|
'pesel' => get_post_meta( $post->ID, self::META_PREFIX . 'pesel', true ),
|
||||||
|
'address' => get_post_meta( $post->ID, self::META_PREFIX . 'address', true ),
|
||||||
|
'extras' => get_post_meta( $post->ID, self::META_PREFIX . 'extras', true ),
|
||||||
|
'comments' => get_post_meta( $post->ID, self::META_PREFIX . 'comments', true ),
|
||||||
|
'status' => get_post_meta( $post->ID, self::META_PREFIX . 'status', true ) ?: 'nowe',
|
||||||
|
);
|
||||||
|
|
||||||
|
$address = $meta['address'] ? json_decode( $meta['address'], true ) : null;
|
||||||
|
$address_str = '';
|
||||||
|
if ( $address ) {
|
||||||
|
$parts = array_filter( array(
|
||||||
|
isset( $address['street'] ) ? $address['street'] : '',
|
||||||
|
isset( $address['zipCode'] ) ? $address['zipCode'] : '',
|
||||||
|
isset( $address['city'] ) ? $address['city'] : '',
|
||||||
|
) );
|
||||||
|
$address_str = implode( ', ', $parts );
|
||||||
|
}
|
||||||
|
|
||||||
|
$extras = $meta['extras'] ? json_decode( $meta['extras'], true ) : array();
|
||||||
|
$extras_str = '';
|
||||||
|
if ( is_array( $extras ) && ! empty( $extras ) ) {
|
||||||
|
$names = array_map( function ( $e ) {
|
||||||
|
return isset( $e['name'] ) ? $e['name'] : '';
|
||||||
|
}, $extras );
|
||||||
|
$extras_str = implode( ', ', array_filter( $names ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
$from_fmt = $meta['date_from'] ? date_i18n( 'd.m.Y H:i', strtotime( $meta['date_from'] ) ) : '—';
|
||||||
|
$to_fmt = $meta['date_to'] ? date_i18n( 'd.m.Y H:i', strtotime( $meta['date_to'] ) ) : '—';
|
||||||
|
|
||||||
|
?>
|
||||||
|
<table class="carei-meta-table">
|
||||||
|
<tr><th>Nr rezerwacji</th><td><?php echo esc_html( $meta['reservation_no'] ?: '—' ); ?></td></tr>
|
||||||
|
<tr><th>ID rezerwacji (Softra)</th><td><?php echo esc_html( $meta['reservation_id'] ?: '—' ); ?></td></tr>
|
||||||
|
<tr><th>ID klienta (Softra)</th><td><?php echo esc_html( $meta['customer_id'] ?: '—' ); ?></td></tr>
|
||||||
|
<tr class="carei-meta-divider"><td colspan="2"><hr></td></tr>
|
||||||
|
<tr><th>Segment</th><td><?php echo esc_html( $meta['segment'] ?: '—' ); ?></td></tr>
|
||||||
|
<tr><th>Data od</th><td><?php echo esc_html( $from_fmt ); ?></td></tr>
|
||||||
|
<tr><th>Data do</th><td><?php echo esc_html( $to_fmt ); ?></td></tr>
|
||||||
|
<tr><th>Oddział odbioru</th><td><?php echo esc_html( $meta['pickup_branch'] ?: '—' ); ?></td></tr>
|
||||||
|
<tr><th>Oddział zwrotu</th><td><?php echo esc_html( $meta['return_branch'] ?: $meta['pickup_branch'] ?: '—' ); ?></td></tr>
|
||||||
|
<tr class="carei-meta-divider"><td colspan="2"><hr></td></tr>
|
||||||
|
<tr><th>Imię</th><td><?php echo esc_html( $meta['first_name'] ?: '—' ); ?></td></tr>
|
||||||
|
<tr><th>Nazwisko</th><td><?php echo esc_html( $meta['last_name'] ?: '—' ); ?></td></tr>
|
||||||
|
<tr><th>Email</th><td><?php echo esc_html( $meta['email'] ?: '—' ); ?></td></tr>
|
||||||
|
<tr><th>Telefon</th><td><?php echo esc_html( $meta['phone'] ?: '—' ); ?></td></tr>
|
||||||
|
<tr><th>PESEL</th><td><?php echo esc_html( $meta['pesel'] ?: '—' ); ?></td></tr>
|
||||||
|
<tr><th>Adres</th><td><?php echo esc_html( $address_str ?: '—' ); ?></td></tr>
|
||||||
|
<tr class="carei-meta-divider"><td colspan="2"><hr></td></tr>
|
||||||
|
<tr><th>Opcje dodatkowe</th><td><?php echo esc_html( $extras_str ?: 'Brak' ); ?></td></tr>
|
||||||
|
<tr><th>Wiadomość</th><td><?php echo esc_html( $meta['comments'] ?: '—' ); ?></td></tr>
|
||||||
|
<tr class="carei-meta-divider"><td colspan="2"><hr></td></tr>
|
||||||
|
<tr>
|
||||||
|
<th>Status</th>
|
||||||
|
<td>
|
||||||
|
<select name="carei_status">
|
||||||
|
<?php foreach ( self::$statuses as $key => $info ) : ?>
|
||||||
|
<option value="<?php echo esc_attr( $key ); ?>" <?php selected( $meta['status'], $key ); ?>>
|
||||||
|
<?php echo esc_html( $info['label'] ); ?>
|
||||||
|
</option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<?php
|
||||||
|
}
|
||||||
|
|
||||||
|
public function save_meta_box( $post_id, $post ) {
|
||||||
|
if ( ! isset( $_POST['carei_reservation_nonce'] ) ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ( ! wp_verify_nonce( $_POST['carei_reservation_nonce'], 'carei_save_reservation' ) ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ( ! current_user_can( 'edit_post', $post_id ) ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( isset( $_POST['carei_status'] ) ) {
|
||||||
|
$status = sanitize_text_field( $_POST['carei_status'] );
|
||||||
|
if ( isset( self::$statuses[ $status ] ) ) {
|
||||||
|
update_post_meta( $post_id, self::META_PREFIX . 'status', $status );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Auto Mark Read ─────────────────────────────────────────
|
||||||
|
|
||||||
|
public function auto_mark_read( $post ) {
|
||||||
|
if ( $post->post_type !== self::POST_TYPE ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$status = get_post_meta( $post->ID, self::META_PREFIX . 'status', true );
|
||||||
|
if ( $status === 'nowe' ) {
|
||||||
|
update_post_meta( $post->ID, self::META_PREFIX . 'status', 'przeczytane' );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Admin Styles ───────────────────────────────────────────
|
||||||
|
|
||||||
|
public function admin_styles() {
|
||||||
|
$screen = get_current_screen();
|
||||||
|
if ( ! $screen || $screen->post_type !== self::POST_TYPE ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
<style>
|
||||||
|
.carei-status-badge {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 4px 10px;
|
||||||
|
border-radius: 12px;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 1.4;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.carei-meta-table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
.carei-meta-table th {
|
||||||
|
text-align: left;
|
||||||
|
padding: 8px 12px;
|
||||||
|
width: 180px;
|
||||||
|
color: #1d2327;
|
||||||
|
font-weight: 600;
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
.carei-meta-table td {
|
||||||
|
padding: 8px 12px;
|
||||||
|
color: #50575e;
|
||||||
|
}
|
||||||
|
.carei-meta-table tr:nth-child(odd):not(.carei-meta-divider) {
|
||||||
|
background: #f9f9f9;
|
||||||
|
}
|
||||||
|
.carei-meta-divider td {
|
||||||
|
padding: 4px 0;
|
||||||
|
}
|
||||||
|
.carei-meta-divider hr {
|
||||||
|
border: none;
|
||||||
|
border-top: 1px solid #e0e0e0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.carei-meta-table select {
|
||||||
|
min-width: 180px;
|
||||||
|
}
|
||||||
|
/* Hide title input — auto-generated */
|
||||||
|
#post-body-content #titlediv { display: none; }
|
||||||
|
</style>
|
||||||
|
<?php
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Save Reservation (called from REST proxy) ──────────────
|
||||||
|
|
||||||
|
public static function save_reservation( $booking_data, $api_result ) {
|
||||||
|
$reservation_no = isset( $api_result['reservationNo'] ) ? $api_result['reservationNo'] : '';
|
||||||
|
$reservation_id = isset( $api_result['reservationId'] ) ? $api_result['reservationId'] : '';
|
||||||
|
|
||||||
|
$driver = array();
|
||||||
|
if ( isset( $booking_data['drivers'] ) && is_array( $booking_data['drivers'] ) && ! empty( $booking_data['drivers'] ) ) {
|
||||||
|
$driver = $booking_data['drivers'][0];
|
||||||
|
}
|
||||||
|
|
||||||
|
$first_name = isset( $driver['firstName'] ) ? $driver['firstName'] : '';
|
||||||
|
$last_name = isset( $driver['lastName'] ) ? $driver['lastName'] : '';
|
||||||
|
|
||||||
|
$title = sprintf( 'Rezerwacja #%s — %s %s', $reservation_no ?: $reservation_id, $first_name, $last_name );
|
||||||
|
|
||||||
|
$post_id = wp_insert_post( array(
|
||||||
|
'post_type' => self::POST_TYPE,
|
||||||
|
'post_title' => trim( $title ),
|
||||||
|
'post_status' => 'publish',
|
||||||
|
), true );
|
||||||
|
|
||||||
|
if ( is_wp_error( $post_id ) ) {
|
||||||
|
error_log( 'Carei: Failed to save reservation — ' . $post_id->get_error_message() );
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$pickup_branch = '';
|
||||||
|
if ( isset( $booking_data['pickUpLocation']['branchName'] ) ) {
|
||||||
|
$pickup_branch = $booking_data['pickUpLocation']['branchName'];
|
||||||
|
}
|
||||||
|
$return_branch = '';
|
||||||
|
if ( isset( $booking_data['returnLocation']['branchName'] ) ) {
|
||||||
|
$return_branch = $booking_data['returnLocation']['branchName'];
|
||||||
|
}
|
||||||
|
$segment = '';
|
||||||
|
if ( isset( $booking_data['carParameters']['categoryName'] ) ) {
|
||||||
|
$segment = $booking_data['carParameters']['categoryName'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$meta = array(
|
||||||
|
'reservation_no' => $reservation_no,
|
||||||
|
'reservation_id' => $reservation_id,
|
||||||
|
'customer_id' => isset( $booking_data['customerId'] ) ? $booking_data['customerId'] : '',
|
||||||
|
'segment' => $segment,
|
||||||
|
'date_from' => isset( $booking_data['dateFrom'] ) ? $booking_data['dateFrom'] : '',
|
||||||
|
'date_to' => isset( $booking_data['dateTo'] ) ? $booking_data['dateTo'] : '',
|
||||||
|
'pickup_branch' => $pickup_branch,
|
||||||
|
'return_branch' => $return_branch,
|
||||||
|
'first_name' => $first_name,
|
||||||
|
'last_name' => $last_name,
|
||||||
|
'email' => isset( $driver['email'] ) ? $driver['email'] : '',
|
||||||
|
'phone' => isset( $driver['phone'] ) ? $driver['phone'] : '',
|
||||||
|
'pesel' => isset( $driver['pesel'] ) ? $driver['pesel'] : '',
|
||||||
|
'address' => isset( $driver['address'] ) ? wp_json_encode( $driver['address'] ) : '',
|
||||||
|
'extras' => isset( $booking_data['priceItems'] ) ? wp_json_encode( $booking_data['priceItems'] ) : '',
|
||||||
|
'comments' => isset( $booking_data['comments'] ) ? $booking_data['comments'] : '',
|
||||||
|
'status' => 'nowe',
|
||||||
|
'raw_response' => wp_json_encode( $api_result ),
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach ( $meta as $key => $value ) {
|
||||||
|
update_post_meta( $post_id, self::META_PREFIX . $key, $value );
|
||||||
|
}
|
||||||
|
|
||||||
|
return $post_id;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -30,6 +30,13 @@ class Carei_REST_Proxy {
|
|||||||
'permission_callback' => '__return_true',
|
'permission_callback' => '__return_true',
|
||||||
) );
|
) );
|
||||||
|
|
||||||
|
// GET /segments-branches-map (cached segment→branches mapping)
|
||||||
|
register_rest_route( self::NAMESPACE, '/segments-branches-map', array(
|
||||||
|
'methods' => 'GET',
|
||||||
|
'callback' => array( $this, 'get_segments_branches_map' ),
|
||||||
|
'permission_callback' => '__return_true',
|
||||||
|
) );
|
||||||
|
|
||||||
// POST /car-classes
|
// POST /car-classes
|
||||||
register_rest_route( self::NAMESPACE, '/car-classes', array(
|
register_rest_route( self::NAMESPACE, '/car-classes', array(
|
||||||
'methods' => 'POST',
|
'methods' => 'POST',
|
||||||
@@ -99,6 +106,17 @@ class Carei_REST_Proxy {
|
|||||||
),
|
),
|
||||||
) );
|
) );
|
||||||
|
|
||||||
|
// POST /booking/cancel
|
||||||
|
register_rest_route( self::NAMESPACE, '/booking/cancel', array(
|
||||||
|
'methods' => 'POST',
|
||||||
|
'callback' => array( $this, 'cancel_booking' ),
|
||||||
|
'permission_callback' => array( $this, 'check_nonce' ),
|
||||||
|
'args' => array(
|
||||||
|
'reservationId' => array( 'required' => true, 'sanitize_callback' => 'sanitize_text_field' ),
|
||||||
|
'reason' => array( 'required' => true, 'sanitize_callback' => 'sanitize_text_field' ),
|
||||||
|
),
|
||||||
|
) );
|
||||||
|
|
||||||
// GET /agreements
|
// GET /agreements
|
||||||
register_rest_route( self::NAMESPACE, '/agreements', array(
|
register_rest_route( self::NAMESPACE, '/agreements', array(
|
||||||
'methods' => 'GET',
|
'methods' => 'GET',
|
||||||
@@ -141,6 +159,14 @@ class Carei_REST_Proxy {
|
|||||||
|
|
||||||
// ─── Callbacks ────────────────────────────────────────────────
|
// ─── Callbacks ────────────────────────────────────────────────
|
||||||
|
|
||||||
|
public function get_segments_branches_map( WP_REST_Request $request ) {
|
||||||
|
$api = $this->api();
|
||||||
|
if ( is_wp_error( $api ) ) {
|
||||||
|
return $api;
|
||||||
|
}
|
||||||
|
return $this->respond( $api->get_segments_branches_map() );
|
||||||
|
}
|
||||||
|
|
||||||
public function get_all_car_classes( WP_REST_Request $request ) {
|
public function get_all_car_classes( WP_REST_Request $request ) {
|
||||||
$api = $this->api();
|
$api = $this->api();
|
||||||
if ( is_wp_error( $api ) ) {
|
if ( is_wp_error( $api ) ) {
|
||||||
@@ -218,8 +244,15 @@ class Carei_REST_Proxy {
|
|||||||
if ( is_wp_error( $api ) ) {
|
if ( is_wp_error( $api ) ) {
|
||||||
return $api;
|
return $api;
|
||||||
}
|
}
|
||||||
$data = $request->get_json_params();
|
$data = $request->get_json_params();
|
||||||
return $this->respond( $api->make_booking( $data ) );
|
$result = $api->make_booking( $data );
|
||||||
|
|
||||||
|
// Save reservation to WP on success (fire-and-forget)
|
||||||
|
if ( ! is_wp_error( $result ) && isset( $result['success'] ) && $result['success'] ) {
|
||||||
|
Carei_Admin_Panel::save_reservation( $data, $result );
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->respond( $result );
|
||||||
}
|
}
|
||||||
|
|
||||||
public function confirm_booking( WP_REST_Request $request ) {
|
public function confirm_booking( WP_REST_Request $request ) {
|
||||||
@@ -232,6 +265,17 @@ class Carei_REST_Proxy {
|
|||||||
) );
|
) );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function cancel_booking( WP_REST_Request $request ) {
|
||||||
|
$api = $this->api();
|
||||||
|
if ( is_wp_error( $api ) ) {
|
||||||
|
return $api;
|
||||||
|
}
|
||||||
|
return $this->respond( $api->cancel_booking(
|
||||||
|
$request->get_param( 'reservationId' ),
|
||||||
|
$request->get_param( 'reason' )
|
||||||
|
) );
|
||||||
|
}
|
||||||
|
|
||||||
public function get_agreements( WP_REST_Request $request ) {
|
public function get_agreements( WP_REST_Request $request ) {
|
||||||
$api = $this->api();
|
$api = $this->api();
|
||||||
if ( is_wp_error( $api ) ) {
|
if ( is_wp_error( $api ) ) {
|
||||||
|
|||||||
Reference in New Issue
Block a user