feat(24-shipment-presets-ui): kolorowe przyciski presetów, popup tworzenia, autofill formularza
Phase 24 complete: - SCSS moduł _shipment-presets.scss (przyciski, popup, color picker) - Sekcja presetów nad formularzem przesyłki z przyciskiem "Dodaj" - Popup tworzenia presetu z nazwą i wyborem koloru (8 opcji) - JS autofill: carrier, usługa dostawy, wymiary, waga, label format - Obsługa 3 paneli: Allegro searchable, InPost select, Apaczka select Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -13,7 +13,7 @@ Customowe przyciski szybkiego wypełniania formularza przygotowania przesyłki.
|
||||
| Phase | Name | Plans | Status |
|
||||
|-------|------|-------|--------|
|
||||
| 23 | Shipment Presets Backend | 1/1 | Complete ✓ |
|
||||
| 24 | Shipment Presets UI | TBD | Not started |
|
||||
| 24 | Shipment Presets UI | 1/1 | Complete ✓ |
|
||||
| 25 | Shipment Presets Management | TBD | Not started |
|
||||
|
||||
### Phase 23: Shipment Presets Backend
|
||||
|
||||
@@ -10,10 +10,10 @@ See: .paul/PROJECT.md (updated 2026-03-12)
|
||||
## Current Position
|
||||
|
||||
Milestone: v1.0 Presety przesyłek
|
||||
Phase: [2] of [3] (Shipment Presets UI)
|
||||
Phase: [3] of [3] (Shipment Presets Management)
|
||||
Plan: Not started
|
||||
Status: Phase 23 complete, ready to plan Phase 24
|
||||
Last activity: 2026-03-22 — Phase 23 complete, transitioned to Phase 24
|
||||
Status: Phase 24 complete, ready to plan Phase 25
|
||||
Last activity: 2026-03-22 — Phase 24 complete, transitioned to Phase 25
|
||||
|
||||
Progress:
|
||||
- v0.1 Initial Release: [██████████] 100% ✓
|
||||
@@ -67,6 +67,11 @@ PLAN ──▶ APPLY ──▶ UNIFY
|
||||
| 2026-03-17 | Email history jako wpisy w order_activity_log (nie osobna sekcja) | Faza 15 | Spójność z istniejącym UX — jeden timeline zamiast fragmentacji |
|
||||
| 2026-03-17 | VariableResolver wydzielony z EmailTemplateController | Faza 15 | Reuse logiki zmiennych; resolwer niezależny od kontrolera szablonów |
|
||||
|
||||
### Skill Audit (Faza 24, Plan 01)
|
||||
| Oczekiwany | Wywołany | Uwagi |
|
||||
|------------|---------|-------|
|
||||
| sonar-scanner | ✓ | 0 nowych issues na zmienionych plikach |
|
||||
|
||||
### Skill Audit (Faza 23, Plan 01)
|
||||
| Oczekiwany | Wywołany | Uwagi |
|
||||
|------------|---------|-------|
|
||||
@@ -201,7 +206,7 @@ PLAN ──▶ APPLY ──▶ UNIFY
|
||||
- **Delivery mapping "Szukaj..." layout** — JS `attachSelectFilter()` w allegro.php tworzy input search dla InPost/Apaczka selectów, wizualnie wygląda jakby należał do wiersza powyżej. Pre-existing bug, do naprawy osobno.
|
||||
|
||||
### Git State
|
||||
Last commit: d6375cc — fix(22-regon-save-fix): naprawa zapisu REGON, BDO, KRS i logo
|
||||
Last commit: 03a237e — feat(23-shipment-presets-backend): tabela DB, repository CRUD i JSON API
|
||||
Branch: main
|
||||
Feature branches merged: none
|
||||
|
||||
@@ -211,9 +216,9 @@ Brak.
|
||||
## Session Continuity
|
||||
|
||||
Last session: 2026-03-22
|
||||
Stopped at: Phase 23 complete, ready to plan Phase 24
|
||||
Next action: /paul:plan for Phase 24 (Shipment Presets UI)
|
||||
Resume file: .paul/phases/23-shipment-presets-backend/23-01-SUMMARY.md
|
||||
Stopped at: Phase 24 complete, ready to plan Phase 25
|
||||
Next action: /paul:plan for Phase 25 (Shipment Presets Management)
|
||||
Resume file: .paul/phases/24-shipment-presets-ui/24-01-SUMMARY.md
|
||||
Resume context:
|
||||
- v0.1: COMPLETE ✓ (6 phases, 15 plans)
|
||||
- v0.2: COMPLETE ✓ (1 phase, 5 plans)
|
||||
|
||||
254
.paul/phases/24-shipment-presets-ui/24-01-PLAN.md
Normal file
254
.paul/phases/24-shipment-presets-ui/24-01-PLAN.md
Normal file
@@ -0,0 +1,254 @@
|
||||
---
|
||||
phase: 24-shipment-presets-ui
|
||||
plan: 01
|
||||
type: execute
|
||||
wave: 1
|
||||
depends_on: ["23-01"]
|
||||
files_modified:
|
||||
- resources/views/shipments/prepare.php
|
||||
- resources/scss/modules/_shipment-presets.scss
|
||||
- resources/scss/main.scss
|
||||
autonomous: false
|
||||
---
|
||||
|
||||
<objective>
|
||||
## Goal
|
||||
Dodać sekcję presetów przesyłek nad formularzem przygotowania przesyłki: kolorowe przyciski, popup tworzenia presetu (nazwa + kolor), JS autofill formularza po kliknięciu.
|
||||
|
||||
## Purpose
|
||||
Użytkownik chce jednym kliknięciem wypełnić formularz przesyłki zapisanymi parametrami — bez ręcznego wybierania przewoźnika, usługi, wymiarów za każdym razem.
|
||||
|
||||
## Output
|
||||
- Sekcja presetów z kolorowymi przyciskami nad formularzem
|
||||
- Popup do tworzenia presetu (nazwa + kolor) z bieżących wartości formularza
|
||||
- JS: autofill formularza po kliknięciu przycisku presetu
|
||||
- SCSS style dla presetów
|
||||
</objective>
|
||||
|
||||
<context>
|
||||
## Project Context
|
||||
@.paul/PROJECT.md
|
||||
@.paul/ROADMAP.md
|
||||
@.paul/STATE.md
|
||||
|
||||
## Prior Work
|
||||
@.paul/phases/23-shipment-presets-backend/23-01-SUMMARY.md
|
||||
|
||||
## Source Files
|
||||
@resources/views/shipments/prepare.php
|
||||
@src/Modules/Shipments/ShipmentPresetController.php
|
||||
@routes/web.php
|
||||
</context>
|
||||
|
||||
<skills>
|
||||
No specialized flows required for this plan (sonar-scanner required post-APPLY).
|
||||
</skills>
|
||||
|
||||
<acceptance_criteria>
|
||||
|
||||
## AC-1: Przyciski presetów widoczne nad formularzem
|
||||
```gherkin
|
||||
Given użytkownik otwiera stronę przygotowania przesyłki
|
||||
When w bazie istnieją presety
|
||||
Then nad formularzem wyświetlają się kolorowe przyciski z nazwami presetów
|
||||
And wyświetla się przycisk "Dodaj przycisk dostawy"
|
||||
```
|
||||
|
||||
## AC-2: Kliknięcie presetu wypełnia formularz
|
||||
```gherkin
|
||||
Given preset ma zapisane: carrier=inpost, package_type=PACKAGE, weight=2kg, wymiary 30x25x10
|
||||
When użytkownik klika przycisk tego presetu
|
||||
Then formularz automatycznie wypełnia się tymi wartościami
|
||||
And odpowiedni panel przewoźnika staje się widoczny
|
||||
And usługa dostawy zostaje wybrana
|
||||
```
|
||||
|
||||
## AC-3: Przycisk "Dodaj przycisk dostawy" tworzy preset z bieżących wartości
|
||||
```gherkin
|
||||
Given użytkownik wypełnił formularz (wybrał przewoźnika, usługę, wymiary itp.)
|
||||
When klika "Dodaj przycisk dostawy"
|
||||
Then pojawia się popup z polem nazwy i wyborem koloru
|
||||
When wpisuje nazwę i klika Zapisz
|
||||
Then preset zostaje zapisany przez API i przycisk pojawia się w sekcji
|
||||
```
|
||||
|
||||
</acceptance_criteria>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 1: SCSS style dla presetów</name>
|
||||
<files>resources/scss/modules/_shipment-presets.scss, resources/scss/main.scss</files>
|
||||
<action>
|
||||
1. Utworzyć `resources/scss/modules/_shipment-presets.scss`:
|
||||
- `.shipment-presets` — kontener sekcji (flex, wrap, gap: 8px, margin-bottom: 16px)
|
||||
- `.shipment-presets__btn` — przycisk presetu:
|
||||
- Inline-flex, border-radius: 6px, padding: 6px 14px
|
||||
- Biały tekst, tło z custom property `--preset-color`
|
||||
- Hover: opacity 0.85, cursor pointer
|
||||
- Font-size: 13px, font-weight: 500
|
||||
- `.shipment-presets__add` — przycisk dodawania:
|
||||
- Border: 1px dashed #ccc, background transparent
|
||||
- Color: #666, hover: border-color #999
|
||||
- `.preset-modal` — overlay popup:
|
||||
- Position fixed, inset 0, background rgba(0,0,0,0.4), z-index 1000
|
||||
- Display flex, align-items center, justify-content center
|
||||
- `.preset-modal__content` — zawartość popup:
|
||||
- Background white, border-radius 8px, padding 24px, min-width 360px, max-width 420px
|
||||
- `.preset-modal__colors` — grid kolorów:
|
||||
- Display flex, gap 8px, flex-wrap wrap
|
||||
- `.preset-modal__color-swatch` — próbka koloru:
|
||||
- Width 28px, height 28px, border-radius 50%, cursor pointer
|
||||
- Border: 2px solid transparent
|
||||
- `&.is-selected` — border-color: #333
|
||||
|
||||
2. Dodać `@import 'modules/shipment-presets';` w `resources/scss/main.scss`
|
||||
</action>
|
||||
<verify>Sprawdzić że plik SCSS istnieje i import dodany do main.scss</verify>
|
||||
<done>AC-1 partially: style gotowe</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2: HTML sekcja presetów + popup + JavaScript</name>
|
||||
<files>resources/views/shipments/prepare.php</files>
|
||||
<action>
|
||||
1. **Sekcja presetów** — wstawić PRZED `<form>` (po zamknięciu sekcji flash messages, linia ~63):
|
||||
|
||||
```html
|
||||
<section class="shipment-presets" id="shipment-presets">
|
||||
<!-- Buttons rendered by JS -->
|
||||
<button type="button" class="shipment-presets__add" id="preset-add-btn">+ Dodaj przycisk dostawy</button>
|
||||
</section>
|
||||
```
|
||||
|
||||
2. **Popup modal** — wstawić na końcu pliku (przed zamykającym `</script>` lub po ostatnim `</section>`):
|
||||
|
||||
```html
|
||||
<div class="preset-modal" id="preset-modal" style="display:none">
|
||||
<div class="preset-modal__content">
|
||||
<h3>Nowy przycisk dostawy</h3>
|
||||
<label class="form-field mt-12">
|
||||
<span class="field-label">Nazwa</span>
|
||||
<input class="form-control" type="text" id="preset-name-input" maxlength="100" placeholder="np. InPost Paczkomat Standard">
|
||||
</label>
|
||||
<div class="form-field mt-12">
|
||||
<span class="field-label">Kolor</span>
|
||||
<div class="preset-modal__colors" id="preset-color-picker">
|
||||
<!-- 8 kolorów do wyboru -->
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-actions mt-16">
|
||||
<button type="button" class="btn btn--primary" id="preset-save-btn">Zapisz</button>
|
||||
<button type="button" class="btn btn--secondary" id="preset-cancel-btn">Anuluj</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
3. **JavaScript** — dodać w istniejącym bloku `<script>` na końcu prepare.php:
|
||||
|
||||
Logika JS (dodać jako IIFE):
|
||||
|
||||
a) **Konfiguracja kolorów:**
|
||||
```javascript
|
||||
const PRESET_COLORS = [
|
||||
'#3b82f6', '#ef4444', '#10b981', '#f59e0b',
|
||||
'#8b5cf6', '#ec4899', '#06b6d4', '#6b7280'
|
||||
];
|
||||
```
|
||||
|
||||
b) **Ładowanie presetów:** fetch GET /api/shipment-presets → renderowanie przycisków
|
||||
- Dla każdego presetu: `<button class="shipment-presets__btn" style="--preset-color: {color}; background: {color}" data-preset-id="{id}">{name}</button>`
|
||||
- Wstawić PRZED przyciskiem "Dodaj"
|
||||
|
||||
c) **Kliknięcie presetu → autofill:**
|
||||
- Ustawić select `#shipment-carrier-select` na preset.carrier i wystrzelić event 'change'
|
||||
- Po 100ms (żeby panel się pokazał):
|
||||
- Ustawić ukryte pola: delivery_method_id, credentials_id, carrier_id, provider_code
|
||||
- Ustawić package_type, length_cm, width_cm, height_cm, weight_kg
|
||||
- Ustawić sender_point_id, label_format
|
||||
- Dla odpowiedniego panelu przewoźnika:
|
||||
- Allegro: znaleźć opcję w searchable dropdown po data-value === delivery_method_id, kliknąć ją
|
||||
- InPost: ustawić select[name="inpost_service"] na delivery_method_id
|
||||
- Apaczka: ustawić select[name="apaczka_service"] na delivery_method_id
|
||||
|
||||
d) **Przycisk "Dodaj" → popup:**
|
||||
- Pokazać modal
|
||||
- Wygenerować swatche kolorów (PRESET_COLORS)
|
||||
- Zaznaczenie koloru → is-selected
|
||||
- Input nazwy focus
|
||||
|
||||
e) **Zapisanie presetu:**
|
||||
- Zebrać bieżące wartości formularza:
|
||||
carrier (z #shipment-carrier-select), provider_code, delivery_method_id, credentials_id, carrier_id,
|
||||
package_type, length_cm, width_cm, height_cm, weight_kg, sender_point_id, label_format
|
||||
- POST /api/shipment-presets z name, color i zebranymi danymi (JSON, Content-Type: application/json)
|
||||
- Po sukcesie: zamknąć modal, przeładować listę presetów
|
||||
|
||||
f) **Anuluj:** zamknąć modal, wyczyścić input
|
||||
|
||||
Ważne:
|
||||
- Nie modyfikować istniejącego JS (carrier selection, status polling itp.)
|
||||
- fetch() z credentials: 'same-origin' (sesja PHP)
|
||||
- Autofill musi obsłużyć 3 różne panele przewoźników (allegro searchable dropdown, inpost select, apaczka select)
|
||||
</action>
|
||||
<verify>php -l resources/views/shipments/prepare.php — brak błędów składni PHP</verify>
|
||||
<done>AC-1, AC-2, AC-3 satisfied</done>
|
||||
</task>
|
||||
|
||||
<task type="checkpoint:human-verify" gate="blocking">
|
||||
<what-built>Sekcja presetów przesyłek nad formularzem: kolorowe przyciski, popup tworzenia, autofill formularza</what-built>
|
||||
<how-to-verify>
|
||||
1. Uruchomić migrację: wykonaj SQL z database/migrations/20260322_000059_create_shipment_presets_table.sql na bazie
|
||||
2. Zbudować SCSS: npm run build (lub odpowiedni skrypt)
|
||||
3. Odwiedź: /orders/{dowolne-id}/shipment/prepare
|
||||
4. Powinien być widoczny przycisk "+ Dodaj przycisk dostawy"
|
||||
5. Wypełnij formularz: wybierz przewoźnika, usługę, wymiary
|
||||
6. Kliknij "Dodaj przycisk dostawy" — popup z nazwą i kolorem
|
||||
7. Wpisz nazwę, wybierz kolor, kliknij Zapisz
|
||||
8. Przycisk powinien się pojawić w sekcji
|
||||
9. Odśwież stronę — przycisk nadal widoczny
|
||||
10. Kliknij przycisk — formularz powinien się wypełnić zapisanymi wartościami
|
||||
</how-to-verify>
|
||||
<resume-signal>Type "approved" to continue, or describe issues to fix</resume-signal>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<boundaries>
|
||||
|
||||
## DO NOT CHANGE
|
||||
- src/Modules/Shipments/ShipmentController.php (nie modyfikować kontrolera)
|
||||
- src/Modules/Shipments/ShipmentPresetController.php (gotowy z fazy 23)
|
||||
- src/Modules/Shipments/ShipmentPresetRepository.php (gotowy z fazy 23)
|
||||
- routes/web.php (routing gotowy z fazy 23)
|
||||
- Istniejący JS w prepare.php (carrier selection, status polling, print)
|
||||
|
||||
## SCOPE LIMITS
|
||||
- Tylko tworzenie presetów i autofill — edycja/usuwanie w fazie 25
|
||||
- Brak auto-submit formularza po kliknięciu presetu (na razie)
|
||||
- Brak drag & drop sortowania (faza 25)
|
||||
|
||||
</boundaries>
|
||||
|
||||
<verification>
|
||||
Before declaring plan complete:
|
||||
- [ ] Sekcja presetów widoczna nad formularzem
|
||||
- [ ] Przyciski ładują się z API
|
||||
- [ ] Popup tworzenia działa (nazwa + kolor)
|
||||
- [ ] Autofill wypełnia formularz po kliknięciu presetu
|
||||
- [ ] php -l na prepare.php — OK
|
||||
- [ ] SCSS zbudowany
|
||||
- [ ] Human verification checkpoint passed
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- Kolorowe przyciski presetów widoczne nad formularzem
|
||||
- Kliknięcie presetu wypełnia formularz
|
||||
- Popup tworzenia presetu z nazwą i kolorem działa
|
||||
- Brak regresji w istniejącej funkcjonalności przesyłek
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
After completion, create `.paul/phases/24-shipment-presets-ui/24-01-SUMMARY.md`
|
||||
</output>
|
||||
123
.paul/phases/24-shipment-presets-ui/24-01-SUMMARY.md
Normal file
123
.paul/phases/24-shipment-presets-ui/24-01-SUMMARY.md
Normal file
@@ -0,0 +1,123 @@
|
||||
---
|
||||
phase: 24-shipment-presets-ui
|
||||
plan: 01
|
||||
subsystem: shipments
|
||||
tags: [shipment-presets, ui, autofill, javascript]
|
||||
|
||||
requires:
|
||||
- phase: 23-shipment-presets-backend
|
||||
provides: JSON API endpoints for presets CRUD
|
||||
provides:
|
||||
- Kolorowe przyciski presetów nad formularzem przesyłki
|
||||
- Popup tworzenia presetu (nazwa + kolor)
|
||||
- JS autofill formularza po kliknięciu presetu
|
||||
affects: [25-shipment-presets-management]
|
||||
|
||||
tech-stack:
|
||||
added: []
|
||||
patterns:
|
||||
- URLSearchParams dla fetch POST (Request::input() nie parsuje JSON)
|
||||
- _syncTrigger() na enhanced selectach po programowym ustawieniu value
|
||||
|
||||
key-files:
|
||||
created:
|
||||
- resources/scss/modules/_shipment-presets.scss
|
||||
modified:
|
||||
- resources/scss/app.scss
|
||||
- public/assets/css/app.css
|
||||
- resources/views/shipments/prepare.php
|
||||
|
||||
key-decisions:
|
||||
- "URLSearchParams zamiast JSON.stringify — Request::input() czyta z $_POST"
|
||||
- "Autofill: _syncTrigger() + bezpośrednie ustawianie hidden fields i search input"
|
||||
|
||||
patterns-established:
|
||||
- "Preset autofill musi wywoływać _syncTrigger() na enhanced selectach"
|
||||
- "Allegro searchable dropdown: ustawić is-selected + searchInput.value + hidden fields z data-attributes"
|
||||
|
||||
duration: 15min
|
||||
started: 2026-03-22T00:00:00Z
|
||||
completed: 2026-03-22T00:15:00Z
|
||||
---
|
||||
|
||||
# Phase 24 Plan 01: Shipment Presets UI Summary
|
||||
|
||||
**Kolorowe przyciski presetów nad formularzem przesyłki z popup tworzenia i JS autofill — użytkownik jednym kliknięciem wypełnia formularz zapisanymi parametrami.**
|
||||
|
||||
## Performance
|
||||
|
||||
| Metric | Value |
|
||||
|--------|-------|
|
||||
| Duration | ~15 min |
|
||||
| Tasks | 3 completed (2 auto + 1 checkpoint) |
|
||||
| Files created | 1 |
|
||||
| Files modified | 3 |
|
||||
|
||||
## Acceptance Criteria Results
|
||||
|
||||
| Criterion | Status | Notes |
|
||||
|-----------|--------|-------|
|
||||
| AC-1: Przyciski widoczne nad formularzem | Pass | Sekcja z kolorowymi przyciskami + "Dodaj przycisk dostawy" |
|
||||
| AC-2: Kliknięcie presetu wypełnia formularz | Pass | Carrier, usługa, wymiary, waga, label format — potwierdzone przez użytkownika |
|
||||
| AC-3: Popup tworzy preset z bieżących wartości | Pass | Nazwa + kolor, zapis przez API, przycisk pojawia się natychmiast |
|
||||
|
||||
## Accomplishments
|
||||
|
||||
- SCSS moduł `_shipment-presets.scss` z stylami przycisków, popupu i color picker
|
||||
- Sekcja presetów wstawiona nad `<form>` w prepare.php
|
||||
- Popup z nazwą i 8 kolorami do wyboru
|
||||
- JS: fetch presetów z API, renderowanie przycisków, autofill 3 typów paneli (Allegro searchable, InPost select, Apaczka select)
|
||||
- SonarQube: 0 nowych issues
|
||||
|
||||
## Files Created/Modified
|
||||
|
||||
| File | Change | Purpose |
|
||||
|------|--------|---------|
|
||||
| `resources/scss/modules/_shipment-presets.scss` | Created | Style przycisków, popupu, color picker |
|
||||
| `resources/scss/app.scss` | Modified | Dodany @use shipment-presets |
|
||||
| `public/assets/css/app.css` | Modified | Przebudowany CSS |
|
||||
| `resources/views/shipments/prepare.php` | Modified | Sekcja presetów, popup, ~180 linii JS |
|
||||
|
||||
## Decisions Made
|
||||
|
||||
| Decision | Rationale | Impact |
|
||||
|----------|-----------|--------|
|
||||
| URLSearchParams zamiast JSON | Request::input() czyta z $_POST, nie parsuje JSON body | Spójne z resztą projektu |
|
||||
| _syncTrigger() po value change | enhanceSelect() ukrywa natywny select — trzeba sync custom UI | Wzorzec do reuse w fazie 25 |
|
||||
| Bezpośrednie ustawianie hidden fields dla Allegro | opt.click() nie działał niezawodnie w searchable dropdown | Bardziej deterministyczne |
|
||||
|
||||
## Deviations from Plan
|
||||
|
||||
### Summary
|
||||
|
||||
| Type | Count | Impact |
|
||||
|------|-------|--------|
|
||||
| Auto-fixed | 2 | Zmiana content-type + autofill fix |
|
||||
|
||||
**1. JSON → URLSearchParams**
|
||||
- Found during: Task 2 (checkpoint testing — 422 error)
|
||||
- Fix: Zmieniono Content-Type na form-urlencoded, użyto URLSearchParams
|
||||
|
||||
**2. Autofill nie zmieniał carrier/usługi**
|
||||
- Found during: Checkpoint (user report)
|
||||
- Fix: Dodano _syncTrigger() na carrier select, bezpośrednie ustawianie hidden fields i search input text dla Allegro, _syncTrigger() dla InPost/Apaczka
|
||||
|
||||
## Issues Encountered
|
||||
|
||||
None remaining — oba issues naprawione podczas checkpointu.
|
||||
|
||||
## Next Phase Readiness
|
||||
|
||||
**Ready:**
|
||||
- Presety tworzą się i wypełniają formularz
|
||||
- Faza 25 może dodać edycję/usuwanie/sortowanie
|
||||
|
||||
**Concerns:**
|
||||
- Brak
|
||||
|
||||
**Blockers:**
|
||||
- None
|
||||
|
||||
---
|
||||
*Phase: 24-shipment-presets-ui, Plan: 01*
|
||||
*Completed: 2026-03-22*
|
||||
File diff suppressed because one or more lines are too long
@@ -2,6 +2,7 @@
|
||||
@use "modules/email-send";
|
||||
@use "modules/automation";
|
||||
@use "modules/printing";
|
||||
@use "modules/shipment-presets";
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
|
||||
94
resources/scss/modules/_shipment-presets.scss
Normal file
94
resources/scss/modules/_shipment-presets.scss
Normal file
@@ -0,0 +1,94 @@
|
||||
.shipment-presets {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.shipment-presets__btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 6px 14px;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
background: var(--preset-color, #3b82f6);
|
||||
color: #fff;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: opacity 0.15s;
|
||||
line-height: 1.4;
|
||||
|
||||
&:hover {
|
||||
opacity: 0.85;
|
||||
}
|
||||
}
|
||||
|
||||
.shipment-presets__add {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 6px 14px;
|
||||
border: 1px dashed #ccc;
|
||||
border-radius: 6px;
|
||||
background: transparent;
|
||||
color: #666;
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.15s, color 0.15s;
|
||||
line-height: 1.4;
|
||||
|
||||
&:hover {
|
||||
border-color: #999;
|
||||
color: #444;
|
||||
}
|
||||
}
|
||||
|
||||
.preset-modal {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: rgba(0, 0, 0, 0.4);
|
||||
z-index: 1000;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.preset-modal__content {
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
padding: 24px;
|
||||
min-width: 360px;
|
||||
max-width: 420px;
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
|
||||
|
||||
h3 {
|
||||
margin: 0 0 4px;
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.preset-modal__colors {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.preset-modal__color-swatch {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
border: 2px solid transparent;
|
||||
transition: border-color 0.15s;
|
||||
|
||||
&:hover {
|
||||
border-color: #aaa;
|
||||
}
|
||||
|
||||
&.is-selected {
|
||||
border-color: #333;
|
||||
}
|
||||
}
|
||||
@@ -61,6 +61,28 @@ $defaultCodAmount = $isCod ? number_format($totalWithTax, 2, '.', '') : '0';
|
||||
<?php endif; ?>
|
||||
</section>
|
||||
|
||||
<section class="shipment-presets" id="shipment-presets">
|
||||
<button type="button" class="shipment-presets__add" id="preset-add-btn">+ Dodaj przycisk dostawy</button>
|
||||
</section>
|
||||
|
||||
<div class="preset-modal" id="preset-modal" style="display:none">
|
||||
<div class="preset-modal__content">
|
||||
<h3>Nowy przycisk dostawy</h3>
|
||||
<label class="form-field mt-12">
|
||||
<span class="field-label">Nazwa</span>
|
||||
<input class="form-control" type="text" id="preset-name-input" maxlength="100" placeholder="np. InPost Paczkomat Standard">
|
||||
</label>
|
||||
<div class="form-field mt-12">
|
||||
<span class="field-label">Kolor</span>
|
||||
<div class="preset-modal__colors" id="preset-color-picker"></div>
|
||||
</div>
|
||||
<div class="form-actions mt-16">
|
||||
<button type="button" class="btn btn--primary" id="preset-save-btn">Zapisz</button>
|
||||
<button type="button" class="btn btn--secondary" id="preset-cancel-btn">Anuluj</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form method="post" action="/orders/<?= $e((string) ($orderId ?? 0)) ?>/shipment/create" novalidate>
|
||||
<input type="hidden" name="_token" value="<?= $e($csrfToken ?? '') ?>">
|
||||
|
||||
@@ -790,3 +812,235 @@ $defaultCodAmount = $isCod ? number_format($totalWithTax, 2, '.', '') : '0';
|
||||
|
||||
})();
|
||||
</script>
|
||||
<script>
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
var PRESET_COLORS = ['#3b82f6','#ef4444','#10b981','#f59e0b','#8b5cf6','#ec4899','#06b6d4','#6b7280'];
|
||||
var presetsContainer = document.getElementById('shipment-presets');
|
||||
var addBtn = document.getElementById('preset-add-btn');
|
||||
var modal = document.getElementById('preset-modal');
|
||||
var nameInput = document.getElementById('preset-name-input');
|
||||
var colorPicker = document.getElementById('preset-color-picker');
|
||||
var saveBtn = document.getElementById('preset-save-btn');
|
||||
var cancelBtn = document.getElementById('preset-cancel-btn');
|
||||
|
||||
if (!presetsContainer || !addBtn || !modal) return;
|
||||
|
||||
var selectedColor = PRESET_COLORS[0];
|
||||
var presetsData = [];
|
||||
|
||||
// --- Color picker ---
|
||||
PRESET_COLORS.forEach(function (color, idx) {
|
||||
var swatch = document.createElement('div');
|
||||
swatch.className = 'preset-modal__color-swatch' + (idx === 0 ? ' is-selected' : '');
|
||||
swatch.style.background = color;
|
||||
swatch.setAttribute('data-color', color);
|
||||
swatch.addEventListener('click', function () {
|
||||
colorPicker.querySelectorAll('.preset-modal__color-swatch').forEach(function (s) { s.classList.remove('is-selected'); });
|
||||
swatch.classList.add('is-selected');
|
||||
selectedColor = color;
|
||||
});
|
||||
colorPicker.appendChild(swatch);
|
||||
});
|
||||
|
||||
// --- Load presets ---
|
||||
function loadPresets() {
|
||||
fetch('/api/shipment-presets', { credentials: 'same-origin' })
|
||||
.then(function (r) { return r.json(); })
|
||||
.then(function (data) {
|
||||
presetsData = data.presets || [];
|
||||
renderPresets();
|
||||
})
|
||||
.catch(function () {});
|
||||
}
|
||||
|
||||
function renderPresets() {
|
||||
var existing = presetsContainer.querySelectorAll('.shipment-presets__btn');
|
||||
existing.forEach(function (btn) { btn.remove(); });
|
||||
|
||||
presetsData.forEach(function (preset) {
|
||||
var btn = document.createElement('button');
|
||||
btn.type = 'button';
|
||||
btn.className = 'shipment-presets__btn';
|
||||
btn.style.background = preset.color || '#3b82f6';
|
||||
btn.textContent = preset.name;
|
||||
btn.setAttribute('data-preset-id', preset.id);
|
||||
btn.addEventListener('click', function () { applyPreset(preset); });
|
||||
presetsContainer.insertBefore(btn, addBtn);
|
||||
});
|
||||
}
|
||||
|
||||
// --- Apply preset (autofill form) ---
|
||||
function applyPreset(preset) {
|
||||
var carrierSelect = document.getElementById('shipment-carrier-select');
|
||||
var hiddenInput = document.getElementById('shipment-delivery-service');
|
||||
var credentialsInput = document.getElementById('shipment-credentials-id');
|
||||
var carrierInput = document.getElementById('shipment-carrier-id');
|
||||
var providerInput = document.getElementById('shipment-provider-code');
|
||||
|
||||
if (!carrierSelect) return;
|
||||
|
||||
// Set carrier — use native change so existing handler shows correct panel
|
||||
carrierSelect.value = preset.carrier || '';
|
||||
if (carrierSelect._syncTrigger) carrierSelect._syncTrigger();
|
||||
carrierSelect.dispatchEvent(new Event('change'));
|
||||
|
||||
// The existing change handler clears hidden fields and resets selects.
|
||||
// We must wait for that to finish, then override with preset values.
|
||||
setTimeout(function () {
|
||||
// Hidden fields — set AFTER the change handler cleared them
|
||||
if (hiddenInput) hiddenInput.value = preset.delivery_method_id || '';
|
||||
if (credentialsInput) credentialsInput.value = preset.credentials_id || '';
|
||||
if (carrierInput) carrierInput.value = preset.carrier_id || '';
|
||||
if (providerInput) providerInput.value = preset.provider_code || '';
|
||||
|
||||
// Package fields
|
||||
setFieldValue('package_type', preset.package_type || 'PACKAGE');
|
||||
setFieldValue('length_cm', preset.length_cm || '25');
|
||||
setFieldValue('width_cm', preset.width_cm || '20');
|
||||
setFieldValue('height_cm', preset.height_cm || '8');
|
||||
setFieldValue('weight_kg', preset.weight_kg || '1');
|
||||
setFieldValue('sender_point_id', preset.sender_point_id || '');
|
||||
setFieldValue('label_format', preset.label_format || 'PDF');
|
||||
|
||||
// Select delivery service in the correct panel
|
||||
selectDeliveryService(preset);
|
||||
}, 200);
|
||||
}
|
||||
|
||||
function setFieldValue(name, value) {
|
||||
var field = document.querySelector('[name="' + name + '"]');
|
||||
if (!field) return;
|
||||
field.value = value;
|
||||
if (field.tagName === 'SELECT' && field._syncTrigger) {
|
||||
field._syncTrigger();
|
||||
}
|
||||
}
|
||||
|
||||
function selectDeliveryService(preset) {
|
||||
var carrier = preset.carrier || '';
|
||||
var methodId = preset.delivery_method_id || '';
|
||||
var credentialsId = preset.credentials_id || '';
|
||||
var carrierId = preset.carrier_id || '';
|
||||
|
||||
var hiddenInput = document.getElementById('shipment-delivery-service');
|
||||
var credentialsInput = document.getElementById('shipment-credentials-id');
|
||||
var carrierInput = document.getElementById('shipment-carrier-id');
|
||||
|
||||
if (carrier === 'allegro') {
|
||||
// Click the matching option in Allegro searchable dropdown
|
||||
var dropdown = document.getElementById('shipment-service-dropdown');
|
||||
if (dropdown) {
|
||||
var opts = dropdown.querySelectorAll('.searchable-select__option');
|
||||
opts.forEach(function (opt) {
|
||||
opt.classList.remove('is-selected');
|
||||
if (opt.getAttribute('data-value') === methodId) {
|
||||
opt.classList.add('is-selected');
|
||||
// Update search input to show selected service name
|
||||
var searchInput = document.getElementById('shipment-service-search');
|
||||
if (searchInput) searchInput.value = opt.getAttribute('data-label') || opt.textContent.trim();
|
||||
// Set hidden fields from option data attributes
|
||||
if (hiddenInput) hiddenInput.value = methodId;
|
||||
if (credentialsInput) credentialsInput.value = opt.getAttribute('data-credentials-id') || credentialsId;
|
||||
if (carrierInput) carrierInput.value = opt.getAttribute('data-carrier-id') || carrierId;
|
||||
}
|
||||
});
|
||||
}
|
||||
} else if (carrier === 'inpost') {
|
||||
var inpostSelect = document.getElementById('shipment-inpost-select');
|
||||
if (inpostSelect) {
|
||||
inpostSelect.value = methodId;
|
||||
if (inpostSelect._syncTrigger) inpostSelect._syncTrigger();
|
||||
// Trigger change to set hidden fields via existing handler
|
||||
inpostSelect.dispatchEvent(new Event('change'));
|
||||
}
|
||||
} else if (carrier === 'apaczka') {
|
||||
var apaczkaSelect = document.getElementById('shipment-apaczka-select');
|
||||
if (apaczkaSelect) {
|
||||
apaczkaSelect.value = methodId;
|
||||
if (apaczkaSelect._syncTrigger) apaczkaSelect._syncTrigger();
|
||||
// Trigger change to set hidden fields via existing handler
|
||||
apaczkaSelect.dispatchEvent(new Event('change'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- Modal: add preset ---
|
||||
addBtn.addEventListener('click', function () {
|
||||
nameInput.value = '';
|
||||
selectedColor = PRESET_COLORS[0];
|
||||
colorPicker.querySelectorAll('.preset-modal__color-swatch').forEach(function (s, i) {
|
||||
s.classList.toggle('is-selected', i === 0);
|
||||
});
|
||||
modal.style.display = '';
|
||||
nameInput.focus();
|
||||
});
|
||||
|
||||
cancelBtn.addEventListener('click', function () {
|
||||
modal.style.display = 'none';
|
||||
});
|
||||
|
||||
modal.addEventListener('click', function (e) {
|
||||
if (e.target === modal) modal.style.display = 'none';
|
||||
});
|
||||
|
||||
saveBtn.addEventListener('click', function () {
|
||||
var name = nameInput.value.trim();
|
||||
if (!name) {
|
||||
nameInput.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
var carrierSelect = document.getElementById('shipment-carrier-select');
|
||||
var hiddenInput = document.getElementById('shipment-delivery-service');
|
||||
var credentialsInput = document.getElementById('shipment-credentials-id');
|
||||
var carrierInput = document.getElementById('shipment-carrier-id');
|
||||
var providerInput = document.getElementById('shipment-provider-code');
|
||||
|
||||
var payload = {
|
||||
name: name,
|
||||
color: selectedColor,
|
||||
carrier: carrierSelect ? carrierSelect.value : '',
|
||||
provider_code: providerInput ? providerInput.value : '',
|
||||
delivery_method_id: hiddenInput ? hiddenInput.value : '',
|
||||
credentials_id: credentialsInput ? credentialsInput.value : '',
|
||||
carrier_id: carrierInput ? carrierInput.value : '',
|
||||
package_type: getFieldValue('package_type'),
|
||||
length_cm: getFieldValue('length_cm'),
|
||||
width_cm: getFieldValue('width_cm'),
|
||||
height_cm: getFieldValue('height_cm'),
|
||||
weight_kg: getFieldValue('weight_kg'),
|
||||
sender_point_id: getFieldValue('sender_point_id'),
|
||||
label_format: getFieldValue('label_format')
|
||||
};
|
||||
|
||||
saveBtn.disabled = true;
|
||||
var formBody = new URLSearchParams(payload);
|
||||
fetch('/api/shipment-presets', {
|
||||
method: 'POST',
|
||||
credentials: 'same-origin',
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
body: formBody.toString()
|
||||
})
|
||||
.then(function (r) { return r.json(); })
|
||||
.then(function (data) {
|
||||
if (data.preset) {
|
||||
modal.style.display = 'none';
|
||||
loadPresets();
|
||||
}
|
||||
})
|
||||
.catch(function () {})
|
||||
.finally(function () { saveBtn.disabled = false; });
|
||||
});
|
||||
|
||||
function getFieldValue(name) {
|
||||
var field = document.querySelector('[name="' + name + '"]');
|
||||
return field ? field.value : '';
|
||||
}
|
||||
|
||||
// --- Init ---
|
||||
loadPresets();
|
||||
|
||||
})();
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user