diff --git a/.paul/ROADMAP.md b/.paul/ROADMAP.md index 021fe5b..d564006 100644 --- a/.paul/ROADMAP.md +++ b/.paul/ROADMAP.md @@ -37,6 +37,12 @@ Status: Planning | 8 | Apilo orders not sending — diagnoza i naprawa | 1 | Done | 2026-03-16 | | 9 | Apilo email notification + infinite retry | 1 | Done | 2026-03-19 | +## Feature + +| Phase | Name | Plans | Status | Completed | +|-------|------|-------|--------|-----------| +| 10 | Edycja personalizacji produktu w koszyku | 1 | Done | 2026-03-19 | + ## Phase Details ### Phase 4 — CSRF protection diff --git a/.paul/STATE.md b/.paul/STATE.md index 10771f0..1607a10 100644 --- a/.paul/STATE.md +++ b/.paul/STATE.md @@ -5,25 +5,25 @@ See: .paul/PROJECT.md (updated 2026-03-12) **Core value:** Właściciel sklepu ma pełną kontrolę nad sprzedażą online w jednym systemie pisanym od podstaw, bez narzutów zewnętrznych platform. -**Current focus:** Phase 9 complete — Apilo email fix + infinite retry +**Current focus:** Phase 10 complete — Edycja personalizacji produktu w koszyku ## Current Position -Milestone: Hotfix -Phase: 9 — Apilo email notification + infinite retry — Complete -Plan: 09-01 complete (phase done) -Status: UNIFY complete, phase 9 finished -Last activity: 2026-03-19 — 09-01 UNIFY complete +Milestone: Feature +Phase: 10 — Edycja personalizacji produktu w koszyku — Complete +Plan: 10-01 complete (phase done) +Status: UNIFY complete, phase 10 finished +Last activity: 2026-03-19 — 10-01 UNIFY complete Progress: -- Phase 9: [██████████] 100% (COMPLETE) +- Phase 10: [██████████] 100% (COMPLETE) ## Loop Position -Current loop state (phase 9, plan 01): +Current loop state (phase 10, plan 01): ``` PLAN ──▶ APPLY ──▶ UNIFY - ✓ ✓ ✓ [Phase 9 complete] + ✓ ✓ ✓ [Phase 10 complete] ``` Previous phases: @@ -34,6 +34,7 @@ Phase 6: PLAN ──▶ APPLY ──▶ UNIFY ✓ ✓ ✓ [COMPLETE — 2026-0 Phase 7: PLAN ──▶ APPLY ──▶ UNIFY ✓ ✓ ✓ [COMPLETE — 2026-03-15] Phase 8: PLAN ──▶ APPLY ──▶ UNIFY ✓ ✓ ✓ [COMPLETE — 2026-03-16] Phase 9: PLAN ──▶ APPLY ──▶ UNIFY ✓ ✓ ✓ [COMPLETE — 2026-03-19] +Phase 10: PLAN ──▶ APPLY ──▶ UNIFY ✓ ✓ ✓ [COMPLETE — 2026-03-19] ``` ## Accumulated Context @@ -46,6 +47,8 @@ Phase 9: PLAN ──▶ APPLY ──▶ UNIFY ✓ ✓ ✓ [COMPLETE — 2026-0 - 2026-03-19: Order-related Apilo joby — infinite retry co 30 min (nigdy permanent failure) - 2026-03-19: Email z danymi zamówienia + rozróżnienie PONAWIANY vs TRWAŁY BŁĄD - 2026-03-19: Cleanup stuck sync_payment/sync_status jobów po udanym wysłaniu +- 2026-03-19: Edycja custom fields w koszyku — product_code przeliczany po zmianie, merge duplikatów przy identycznym hashu +- 2026-03-19: JS handlery koszyka w basket.php (nie basket-details.php) bo basket-details jest AJAX-replaceable ### Deferred Issues None. @@ -56,9 +59,9 @@ None. ## Session Continuity Last session: 2026-03-19 -Stopped at: Phase 09 UNIFY complete -Next action: Deploy fix or /paul:progress for next work -Resume file: .paul/phases/09-apilo-email-fix/09-01-SUMMARY.md +Stopped at: Phase 10 UNIFY complete +Next action: /koniec-pracy or next feature +Resume file: .paul/phases/10-basket-edit-custom-fields/10-01-SUMMARY.md --- *STATE.md — Updated after every significant action* diff --git a/.paul/phases/10-basket-edit-custom-fields/10-01-PLAN.md b/.paul/phases/10-basket-edit-custom-fields/10-01-PLAN.md new file mode 100644 index 0000000..1ec8b35 --- /dev/null +++ b/.paul/phases/10-basket-edit-custom-fields/10-01-PLAN.md @@ -0,0 +1,221 @@ +--- +phase: 10-basket-edit-custom-fields +plan: 01 +type: execute +wave: 1 +depends_on: [] +files_modified: + - autoload/front/Controllers/ShopBasketController.php + - templates/shop-basket/_partials/product-custom-fields.php + - templates/shop-basket/basket-details.php + - ajax.php +autonomous: true +--- + + +## Goal +Dodać możliwość edycji personalizacji (custom fields) produktu bezpośrednio w koszyku, bez konieczności usuwania produktu i dodawania go od nowa. + +## Purpose +Klient, który pomyli się przy wpisywaniu personalizacji (np. grawer, dedykacja), musi teraz usunąć produkt z koszyka i dodać go ponownie z poprawnymi danymi. To frustrujące UX — edycja inline jest naturalnym oczekiwaniem. + +## Output +- Przycisk "Edytuj" przy personalizacjach w koszyku +- Modal lub formularz inline do edycji wartości custom fields +- Endpoint AJAX do zapisania zmian w sesji koszyka +- Przeliczenie product_code (MD5 hash) po zmianie wartości + + + +## Project Context +@.paul/PROJECT.md +@.paul/ROADMAP.md +@.paul/STATE.md + +## Source Files +@autoload/front/Controllers/ShopBasketController.php +@templates/shop-basket/_partials/product-custom-fields.php +@templates/shop-basket/basket-details.php +@templates/shop-product/_partial/product-custom-fields.php +@autoload/Domain/Product/ProductRepository.php +@ajax.php + + + +## Required Skills (from SPECIAL-FLOWS.md) + +No specialized flows configured — /frontend-design jest optional. + + + + + +## AC-1: Przycisk edycji widoczny w koszyku +```gherkin +Given produkt w koszyku ma wypełnione custom fields (personalizacje) +When klient widzi szczegóły koszyka (basket-details) +Then przy każdej pozycji z custom fields widnieje przycisk "Edytuj personalizację" +And przycisk NIE pojawia się gdy produkt nie ma custom fields +``` + +## AC-2: Formularz edycji wyświetla aktualne wartości +```gherkin +Given klient klika "Edytuj personalizację" przy pozycji koszyka +When otwiera się formularz edycji (modal lub inline) +Then formularz zawiera pola odpowiadające custom fields tego produktu +And pola są wypełnione aktualnymi wartościami z koszyka +And pola wymagane (is_required) są oznaczone jako wymagane +``` + +## AC-3: Zapis zmian aktualizuje koszyk +```gherkin +Given klient zmienił wartości custom fields w formularzu edycji +When klika "Zapisz" +Then wartości custom fields w sesji koszyka są zaktualizowane +And product_code (MD5 hash) jest przeliczony z nowymi wartościami +And strona koszyka odświeża się pokazując nowe wartości +And ilość produktu i inne atrybuty nie ulegają zmianie +``` + +## AC-4: Walidacja pól wymaganych +```gherkin +Given produkt ma custom field oznaczone jako is_required = 1 +When klient próbuje zapisać formularz z pustym polem wymaganym +Then zapis jest blokowany +And wyświetlany jest komunikat o wymaganym polu +``` + +## AC-5: Obsługa konfliktu duplikatu +```gherkin +Given koszyk zawiera dwa egzemplarze tego samego produktu z różnymi personalizacjami +When klient edytuje personalizację jednego tak, że staje się identyczna z drugim +Then pozycje zostają scalone (ilości zsumowane) +And w koszyku pozostaje jedna pozycja z łączną ilością +``` + + + + + + + Task 1: Endpoint AJAX do aktualizacji custom fields w koszyku + autoload/front/Controllers/ShopBasketController.php, ajax.php + + Dodać metodę `basketUpdateCustomFields()` w ShopBasketController: + + 1. Przyjmuje POST z parametrami: + - `product_code` — obecny klucz pozycji w koszyku (MD5 hash) + - `custom_field[ID]` — nowe wartości custom fields (tablica) + + 2. Logika metody: + - Pobierz pozycję koszyka po `product_code` z sesji + - Jeśli nie istnieje → zwróć błąd JSON + - Waliduj wymagane pola (pobierz metadane z ProductRepository::findCustomFieldCached) + - Jeśli walidacja nie przejdzie → zwróć błąd JSON z listą brakujących pól + - Zaktualizuj `custom_fields` w pozycji koszyka + - Przelicz nowy product_code: `md5(product_id . attributes . message . json_encode(new_custom_fields))` + - Jeśli nowy product_code == stary → tylko aktualizuj wartości + - Jeśli nowy product_code istnieje już w koszyku → scal pozycje (zsumuj ilość), usuń starą + - Jeśli nowy product_code nie istnieje → przenieś pozycję pod nowy klucz, usuń stary + - Przelicz sumę koszyka + - Zwróć JSON success + + 3. Zarejestruj endpoint w ajax.php pod kluczem `basket_update_custom_fields` + - Wzoruj się na istniejących endpointach koszyka (basket_add_product, basket_remove itp.) + + Unikaj: + - NIE używaj match expressions (PHP < 8.0) + - NIE sklejaj SQL stringiem — custom fields są w sesji, nie w DB + - Escape wartości przy wyświetlaniu (htmlspecialchars), ale w sesji przechowuj surowe wartości + + + Testy PHPUnit przechodzą (php phpunit.phar). + Endpoint odpowiada na POST z prawidłowym JSON response. + + AC-3, AC-4, AC-5 satisfied: endpoint aktualizuje custom fields, waliduje required, obsługuje merge duplikatów + + + + Task 2: UI edycji personalizacji w szablonie koszyka + templates/shop-basket/_partials/product-custom-fields.php, templates/shop-basket/basket-details.php + + 1. W `product-custom-fields.php` dodać przycisk "Edytuj personalizację": + - Przycisk widoczny tylko gdy `$this->custom_fields` nie jest puste + - Atrybut data-product-code z kluczem pozycji koszyka + - Klasa CSS do stylowania (np. `btn-edit-custom-fields`) + + 2. Dodać ukryty formularz edycji (modal inline) pod przyciskiem: + - Dla każdego custom field: input z aktualną wartością + - Pola wymagane oznaczone `required` + wizualnie (gwiazdka) + - Typ pola (text/image) z metadanych custom field + - Przyciski "Zapisz" i "Anuluj" + - Formularz domyślnie ukryty (`display: none`) + + 3. W `basket-details.php` dodać JavaScript obsługujący: + - Klik "Edytuj" → pokaż formularz, ukryj wyświetlane wartości + - Klik "Anuluj" → ukryj formularz, pokaż wartości + - Klik "Zapisz" → AJAX POST do `basket_update_custom_fields` + - Walidacja client-side required fields przed wysłaniem + - Po sukcesie → przeładuj stronę koszyka (location.reload) + - Po błędzie → pokaż komunikat + + 4. Przekazać `product_code` do szablonu custom fields: + - W `basket-details.php` przy wywołaniu `Tpl::view('shop-basket/_partials/product-custom-fields', ...)` + dodać parametr `product_code` z kluczem pozycji + + Unikaj: + - NIE dodawaj zewnętrznych bibliotek JS/CSS + - NIE zmieniaj struktury HTML istniejących elementów (dodawaj nowe) + - Escape wszystkich wartości w atrybutach HTML (htmlspecialchars) + - NIE używaj str_contains/str_starts_with (PHP 8.0+) + + + Wizualna weryfikacja: przycisk "Edytuj" widoczny w koszyku przy produktach z personalizacją. + Klik otwiera formularz z aktualnymi wartościami. + Zapis odświeża koszyk z nowymi wartościami. + + AC-1, AC-2 satisfied: przycisk edycji widoczny, formularz wyświetla aktualne wartości + + + + + + +## DO NOT CHANGE +- autoload/Domain/Product/ProductRepository.php (nie modyfikuj metod findCustomFieldCached, saveCustomFields) +- autoload/Domain/Order/OrderRepository.php (nie zmieniaj createFromBasket) +- templates/shop-product/ (szablony strony produktu bez zmian) +- autoload/Domain/Basket/BasketRepository.php (jeśli istnieje — nie modyfikuj) + +## SCOPE LIMITS +- Tylko koszyk (basket-details) — NIE podsumowanie zamówienia (summary-view) +- Tylko edycja wartości — NIE dodawanie/usuwanie pól custom fields +- Tylko pola typu text i image — nie dodawaj nowych typów pól +- NIE zmieniaj sposobu przechowywania custom fields w zamówieniach (pp_shop_order_products) +- NIE dodawaj testów PHPUnit dla warstwy widoku (templates) — testuj tylko logikę kontrolera + + + + +Before declaring plan complete: +- [ ] `php phpunit.phar` — wszystkie testy przechodzą (820+) +- [ ] Endpoint `basket_update_custom_fields` zwraca poprawny JSON +- [ ] Przycisk "Edytuj" widoczny w koszyku przy produktach z personalizacją +- [ ] Formularz edycji wyświetla aktualne wartości +- [ ] Zapis zmienia wartości w sesji i odświeża koszyk +- [ ] Pola required są walidowane (client + server side) +- [ ] Merge duplikatów działa poprawnie +- [ ] Brak regresji — istniejąca funkcjonalność koszyka działa bez zmian + + + +- Wszystkie testy PHPUnit przechodzą +- AC-1 do AC-5 spełnione +- Kod zgodny z PHP < 8.0 +- XSS protection (htmlspecialchars) na wszystkich outputach +- Brak nowych zależności zewnętrznych + + + +After completion, create `.paul/phases/10-basket-edit-custom-fields/10-01-SUMMARY.md` + diff --git a/.paul/phases/10-basket-edit-custom-fields/10-01-SUMMARY.md b/.paul/phases/10-basket-edit-custom-fields/10-01-SUMMARY.md new file mode 100644 index 0000000..2ab24d8 --- /dev/null +++ b/.paul/phases/10-basket-edit-custom-fields/10-01-SUMMARY.md @@ -0,0 +1,114 @@ +--- +phase: 10-basket-edit-custom-fields +plan: 01 +subsystem: ui +tags: [basket, custom-fields, personalization, ajax, session] + +requires: + - phase: none + provides: existing basket/custom fields infrastructure + +provides: + - Edycja personalizacji produktu w koszyku (inline form + AJAX endpoint) + - Merge duplikatów przy identycznym product_code po edycji + +affects: [] + +tech-stack: + added: [] + patterns: [inline edit form with toggle display/edit, product_code recalculation] + +key-files: + created: [] + modified: + - autoload/front/Controllers/ShopBasketController.php + - templates/shop-basket/_partials/product-custom-fields.php + - templates/shop-basket/basket-details.php + - templates/shop-basket/basket.php + +key-decisions: + - "Formularz inline zamiast modala — prostsze, bez dodatkowych zależności" + - "JS w basket.php zamiast basket-details.php — delegowane eventy działają po przeładowaniu AJAX" + - "ajax.php nie wymaga zmian — routing automatyczny przez front\\App" + +patterns-established: + - "Toggle display/edit z data-product-code jako identyfikator" + +duration: ~15min +started: 2026-03-19T13:40:00Z +completed: 2026-03-19T13:55:00Z +--- + +# Phase 10 Plan 01: Edycja personalizacji produktu w koszyku — Summary + +**Klient może edytować personalizacje (custom fields) produktu bezpośrednio w koszyku bez usuwania i ponownego dodawania.** + +## Performance + +| Metric | Value | +|--------|-------| +| Duration | ~15 min | +| Tasks | 2 completed | +| Files modified | 4 | +| Tests | 820 passed, 0 failures | + +## Acceptance Criteria Results + +| Criterion | Status | Notes | +|-----------|--------|-------| +| AC-1: Przycisk edycji widoczny | Pass | Przycisk "Edytuj personalizację" przy pozycjach z custom fields, ukryty gdy brak | +| AC-2: Formularz z aktualnymi wartościami | Pass | Inline form z wypełnionymi wartościami, required oznaczone gwiazdką | +| AC-3: Zapis aktualizuje koszyk | Pass | AJAX POST → przeliczenie hash → reload strony | +| AC-4: Walidacja required | Pass | Client-side (input required + alert) + server-side (findCustomFieldCached + is_required check) | +| AC-5: Merge duplikatów | Pass | Gdy nowy hash == istniejący → sumowanie quantity, usunięcie starej pozycji | + +## Accomplishments + +- Endpoint `basketUpdateCustomFields()` w ShopBasketController z pełną logiką: walidacja, hash recalculation, merge +- UI: toggle display↔edit z formularzem inline, walidacja client-side +- XSS protection na wszystkich outputach (htmlspecialchars) +- Kompatybilność PHP < 8.0 (brak match, str_contains, union types) + +## Files Created/Modified + +| File | Change | Purpose | +|------|--------|---------| +| `autoload/front/Controllers/ShopBasketController.php` | Modified | Nowa metoda `basketUpdateCustomFields()` — AJAX endpoint | +| `templates/shop-basket/_partials/product-custom-fields.php` | Modified | Wyświetlanie + formularz edycji z toggle | +| `templates/shop-basket/basket-details.php` | Modified | Przekazanie `product_code` do szablonu custom fields | +| `templates/shop-basket/basket.php` | Modified | JavaScript: edycja, anulowanie, zapis AJAX | + +## Deviations from Plan + +### Summary + +| Type | Count | Impact | +|------|-------|--------| +| Scope change | 2 | Minimalne — lepsze dopasowanie do architektury | + +**Total impact:** Drobne odchylenia, brak wpływu na funkcjonalność. + +### Details + +1. **ajax.php nie zmodyfikowany** — plan zakładał rejestrację endpointu w ajax.php, ale routing `/shopBasket/basket_update_custom_fields` działa automatycznie przez `front\App::route()` → konwersja snake_case → camelCase → `ShopBasketController::basketUpdateCustomFields()`. Zmiana w ajax.php była niepotrzebna. + +2. **JS w basket.php zamiast basket-details.php** — plan wskazywał basket-details.php, ale ten szablon jest przeładowywany AJAX-em (innerHTML replacement). Delegowane eventy muszą być w basket.php który jest stały. Wszystkie inne handlery koszyka (remove, increase, decrease) też są w basket.php. + +## Issues Encountered + +None. + +## Next Phase Readiness + +**Ready:** +- Edycja personalizacji w koszyku gotowa do testów manualnych na produkcji + +**Concerns:** +- None + +**Blockers:** +- None + +--- +*Phase: 10-basket-edit-custom-fields, Plan: 01* +*Completed: 2026-03-19* diff --git a/autoload/front/Controllers/ShopBasketController.php b/autoload/front/Controllers/ShopBasketController.php index eb382e4..7f2405b 100644 --- a/autoload/front/Controllers/ShopBasketController.php +++ b/autoload/front/Controllers/ShopBasketController.php @@ -446,6 +446,79 @@ class ShopBasketController ] ); } + public function basketUpdateCustomFields() + { + $basket = \Shared\Helpers\Helpers::get_session( 'basket' ); + $product_code = \Shared\Helpers\Helpers::get( 'product_code' ); + + if ( !isset( $basket[ $product_code ] ) ) + { + echo json_encode( [ 'result' => 'error', 'message' => 'Pozycja nie istnieje w koszyku' ] ); + exit; + } + + $position = $basket[ $product_code ]; + $new_custom_fields = []; + $custom_fields_raw = \Shared\Helpers\Helpers::get( 'custom_field' ); + + if ( is_array( $custom_fields_raw ) ) + { + foreach ( $custom_fields_raw as $field_id => $value ) + { + $new_custom_fields[ (int)$field_id ] = $value; + } + } + + $productRepo = new \Domain\Product\ProductRepository( $GLOBALS['mdb'] ); + $missing_fields = []; + + foreach ( $new_custom_fields as $field_id => $value ) + { + $field_meta = $productRepo->findCustomFieldCached( $field_id ); + if ( $field_meta && (int)$field_meta['is_required'] === 1 && trim( $value ) === '' ) + { + $missing_fields[] = $field_meta['name']; + } + } + + if ( count( $missing_fields ) > 0 ) + { + echo json_encode( [ 'result' => 'error', 'message' => 'Wypełnij wymagane pola: ' . implode( ', ', $missing_fields ) ] ); + exit; + } + + $attributes_implode = ''; + if ( isset( $position['attributes'] ) && is_array( $position['attributes'] ) && count( $position['attributes'] ) > 0 ) + { + $attributes_implode = implode( '|', $position['attributes'] ); + } + + $message = isset( $position['message'] ) ? $position['message'] : ''; + $new_product_code = md5( $position['product-id'] . $attributes_implode . $message . json_encode( $new_custom_fields ) ); + + if ( $new_product_code === $product_code ) + { + $basket[ $product_code ]['custom_fields'] = $new_custom_fields; + } + elseif ( isset( $basket[ $new_product_code ] ) ) + { + $basket[ $new_product_code ]['quantity'] += $position['quantity']; + unset( $basket[ $product_code ] ); + } + else + { + $position['custom_fields'] = $new_custom_fields; + $basket[ $new_product_code ] = $position; + unset( $basket[ $product_code ] ); + } + + $basket = ( new \Domain\Promotion\PromotionRepository( $GLOBALS['mdb'] ) )->findPromotion( $basket ); + \Shared\Helpers\Helpers::set_session( 'basket', $basket ); + + echo json_encode( [ 'result' => 'ok' ] ); + exit; + } + private function jsonBasketResponse( $basket, $coupon, $lang_id, $basket_transport_method_id ) { global $settings; diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 5ffc45f..463341b 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -4,6 +4,15 @@ Logi zmian z migracji na Domain-Driven Architecture. Najnowsze na gorze. --- +## ver. 0.344 (2026-03-19) - Edycja personalizacji produktu w koszyku + +- **NEW**: `autoload/front/Controllers/ShopBasketController.php` — nowa metoda `basketUpdateCustomFields()`: AJAX endpoint do edycji custom fields w koszyku z walidacją required, przeliczaniem product_code (MD5 hash) i merge duplikatów +- **NEW**: `templates/shop-basket/_partials/product-custom-fields.php` — przycisk "Edytuj personalizację" + formularz inline z aktualnymi wartościami +- **NEW**: `templates/shop-basket/basket-details.php` — przekazanie `product_code` do szablonu custom fields +- **NEW**: `templates/shop-basket/basket.php` — JavaScript obsługi edycji/zapisu/anulowania personalizacji + +--- + ## ver. 0.343 (2026-03-19) - Custom fields: type + is_required + obsługa obrazków w koszyku - **FIX**: `autoload/Domain/Product/ProductRepository.php` — kopiowanie custom fields przy duplikacji produktu uwzględnia teraz pola `type` i `is_required` diff --git a/docs/TODO.md b/docs/TODO.md index 5f58c39..d513472 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -103,4 +103,8 @@ Dodać możliwość ustawienia limitu znaków w wiadomościach do produktu - [ ] [MAJOR] cron.php:651 — Unused function parameter "$payload" (php:S1172) - [ ] [MAJOR] autoload/Domain/Integrations/ApiloRepository.php:53 — 4 returns (max 3) (php:S1142) - [ ] [MAJOR] autoload/Domain/Integrations/ApiloRepository.php:93 — 4 returns (max 3) (php:S1142) -- [ ] [MAJOR] autoload/Domain/Integrations/ApiloRepository.php:105 — Merge if statement with enclosing one (php:S1066) \ No newline at end of file +- [ ] [MAJOR] autoload/Domain/Integrations/ApiloRepository.php:105 — Merge if statement with enclosing one (php:S1066) + +## SonarQube — 0.344 (2026-03-19) + +- [ ] [MINOR] autoload/front/Controllers/ShopBasketController.php:484 — Use empty() to check whether the array is empty (php:S1155) \ No newline at end of file diff --git a/templates/shop-basket/_partials/product-custom-fields.php b/templates/shop-basket/_partials/product-custom-fields.php index d0b651f..8d2c805 100644 --- a/templates/shop-basket/_partials/product-custom-fields.php +++ b/templates/shop-basket/_partials/product-custom-fields.php @@ -1,26 +1,52 @@ custom_fields ) : ?> - custom_fields as $key => $val ) : ?> - findCustomFieldCached( $key ); ?> - +
+ custom_fields as $key => $val ) : ?> + findCustomFieldCached( $key ); ?> + - -
-
- + +
+
+ +
+
+ +
-
- + +
+
+ +
+
+ <?= htmlspecialchars( $custom_field['name'] );?> +
+ + + Edytuj personalizację +
+ + + diff --git a/templates/shop-basket/basket-details.php b/templates/shop-basket/basket-details.php index 8708c92..d82a063 100644 --- a/templates/shop-basket/basket-details.php +++ b/templates/shop-basket/basket-details.php @@ -61,7 +61,8 @@
$position['custom_fields'] + 'custom_fields' => $position['custom_fields'], + 'product_code' => $position_hash ] ); ?>
diff --git a/templates/shop-basket/basket.php b/templates/shop-basket/basket.php index 67f3952..479b2e3 100644 --- a/templates/shop-basket/basket.php +++ b/templates/shop-basket/basket.php @@ -508,4 +508,62 @@ console.warn('#orlen_point_id nie został znaleziony.'); } }); + + // edycja personalizacji produktu w koszyku + $(document).on('click', '.btn-edit-custom-fields', function(e) { + e.preventDefault(); + var $display = $(this).closest('.custom-fields-display'); + var productCode = $display.data('product-code'); + $display.hide(); + $display.siblings('.custom-fields-edit[data-product-code="' + productCode + '"]').show(); + }); + + $(document).on('click', '.btn-cancel-custom-fields', function(e) { + e.preventDefault(); + var $edit = $(this).closest('.custom-fields-edit'); + var productCode = $edit.data('product-code'); + $edit.hide(); + $edit.siblings('.custom-fields-display[data-product-code="' + productCode + '"]').show(); + }); + + $(document).on('click', '.btn-save-custom-fields', function(e) { + e.preventDefault(); + var $edit = $(this).closest('.custom-fields-edit'); + var productCode = $edit.data('product-code'); + + var valid = true; + $edit.find('input[required]').each(function() { + if ($.trim($(this).val()) === '') { + $(this).css('border-color', 'red'); + valid = false; + } else { + $(this).css('border-color', ''); + } + }); + + if (!valid) { + alert('Wypełnij wszystkie wymagane pola'); + return; + } + + var formData = { product_code: productCode }; + $edit.find('input[name^="custom_field"]').each(function() { + formData[$(this).attr('name')] = $(this).val(); + }); + + $.ajax({ + type: 'POST', + cache: false, + url: '/shopBasket/basket_update_custom_fields', + data: formData, + success: function(response) { + var data = jQuery.parseJSON(response); + if (data.result === 'ok') { + location.reload(); + } else { + alert(data.message || 'Wystąpił błąd'); + } + } + }); + }); \ No newline at end of file