feat(53-mobile-status-panel-toggle): zwijany panel statusow na mobile

Native details/summary toggle — panel zwiniety domyslnie na mobile,
rozwijany kliknieciem. Desktop: zawsze otwarty, bez zmian.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-29 23:32:07 +02:00
parent 1c87489824
commit 775ede52d3
8 changed files with 433 additions and 38 deletions

View File

@@ -61,6 +61,7 @@ Sprzedawca moĹĽe obsĹugiwać zamĂłwienia ze wszystkich kanaĹĂłw
- [x] Allegro: automatyczne przekazywanie numeru przesylki do checkout form po utworzeniu paczki (tylko source=allegro) - Phase 50
- [x] Email HTML Layout: header/footer HTML per skrzynka pocztowa, dual-mode edytor (Quill + HTML source), kompozycja header+body+footer, podglad — Phase 51
- [x] Mobile Main Menu: hamburger, slide-in overlay sidebar, backdrop na mobile <=768px — Phase 52
- [x] Mobile Status Panel Toggle: zwijany/rozwijany panel statusow na /orders/list — Phase 53
### Active (In Progress)

View File

@@ -13,6 +13,7 @@ Wersja mobilna aplikacji, modul po module. Cel: pelna uzywalnosc orderPRO na tel
| Phase | Name | Plans | Status |
|-------|------|-------|--------|
| 52 | Mobile Main Menu | 1/1 | Complete |
| 53 | Mobile Status Panel Toggle | 1/1 | Complete |
| TBD | Mobile Orders List | - | Not started |
| TBD | Mobile Order Details | - | Not started |
| TBD | Mobile Settings | - | Not started |

View File

@@ -10,14 +10,14 @@ See: .paul/PROJECT.md (updated 2026-03-28)
## Current Position
Milestone: v3.0 Mobile Responsive — In progress
Phase: 1 of N (52 - Mobile Main Menu) — Complete
Plan: 52-01 complete
Status: Loop complete — phase 52 done, ready for next PLAN
Last activity: 2026-03-29 — UNIFY closed for 52-01
Phase: 2 of N (53 - Mobile Status Panel Toggle) — Complete
Plan: 53-01 complete
Status: Loop complete — phase 53 done, ready for next PLAN
Last activity: 2026-03-29 — UNIFY closed for 53-01
Progress:
- Milestone: [#░░░░░░░░] ~10%
- Phase 52: [##########] 100%
- Milestone: [##░░░░░░░░] ~20%
- Phase 53: [##########] 100%
## Loop Position
@@ -30,9 +30,9 @@ PLAN ──▶ APPLY ──▶ UNIFY
## Session Continuity
Last session: 2026-03-29
Stopped at: Phase 52 complete
Next action: /paul:plan dla kolejnego modulu mobilnego (np. lista zamowien)
Resume file: .paul/phases/52-mobile-main-menu/52-01-SUMMARY.md
Stopped at: Phase 53 complete
Next action: /paul:plan dla kolejnego modulu mobilnego
Resume file: .paul/phases/53-mobile-status-panel-toggle/53-01-SUMMARY.md
## Accumulated Context

View File

@@ -0,0 +1,205 @@
---
phase: 53-mobile-status-panel-toggle
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
- resources/views/components/order-status-panel.php
- resources/scss/app.scss
- public/assets/css/app.css
autonomous: false
---
<objective>
## Goal
Na mobile (<=768px) panel statusow na stronie listy zamowien powinien byc domyslnie zwinity i rozwijany po kliknieciu w naglowek. Na desktop bez zmian.
## Purpose
Panel statusow zajmuje duzo miejsca na ekranie mobilnym, przesuwajac liste zamowien ponizej fold. Uzytkownik musi scrollowac, zeby zobaczyc zamowienia. Zwiniecie panelu daje natychmiastowy dostep do listy.
## Output
- Panel statusow zamkniety domyslnie na mobile
- Klikniecie w naglowek "Statusy" rozwija/zwija panel
- Strzalka wskazujaca stan (rozwiniety/zwiniety)
- Na desktop panel zawsze widoczny bez zmian
</objective>
<context>
## Project Context
@.paul/PROJECT.md
@.paul/STATE.md
## Source Files
@resources/views/components/order-status-panel.php
@resources/scss/app.scss
</context>
<acceptance_criteria>
## AC-1: Panel zwiniety domyslnie na mobile
```gherkin
Given urzadzenie z szerokosc viewportu <= 768px
When strona /orders/list sie laduje
Then panel statusow jest zwiniety (widoczny tylko naglowek "Statusy")
And lista zamowien jest widoczna bez scrollowania
```
## AC-2: Rozwijanie i zwijanie panelu
```gherkin
Given panel statusow na mobile
When uzytkownik klika naglowek "Statusy"
Then panel rozwija sie pokazujac drzewko statusow
And strzalka obraca sie wskazujac stan otwarty
When uzytkownik klika ponownie
Then panel zwija sie do samego naglowka
```
## AC-3: Desktop bez zmian
```gherkin
Given urzadzenie z szerokosc viewportu > 768px
When strona /orders/list sie laduje
Then panel statusow jest zawsze widoczny (jak dotychczas)
And naglowek nie reaguje na klikniecie jako toggle
```
</acceptance_criteria>
<tasks>
<task type="auto">
<name>Task 1: Zamiana order-status-panel na details/summary</name>
<files>resources/views/components/order-status-panel.php</files>
<action>
Zamien strukture HTML panelu na natywny element `<details>`:
1. Zamien `<aside class="card order-statuses-side">` na `<details class="card order-statuses-side" id="js-status-panel">`
2. Zamien `<div class="order-statuses-side__title">` na `<summary class="order-statuses-side__title">`
- Dodaj SVG strzalke (chevron down, 14x14) na koncu summary: `<svg class="order-statuses-side__arrow" ...>`
- Zamknij `</summary>` zamiast `</div>`
3. Opakuj cala zawartosc grup statusow (petla foreach) w `<div class="order-statuses-side__body">`
4. Zamknij `</details>` zamiast `</aside>`
Struktura wynikowa:
```
<details class="card order-statuses-side" id="js-status-panel">
<summary class="order-statuses-side__title">
Statusy
<svg class="order-statuses-side__arrow" ...chevron-down.../>
</summary>
<div class="order-statuses-side__body">
...grupy statusow...
</div>
</details>
```
Dodaj na koncu pliku maly blok `<script>` (inline):
- Na mobile (matchMedia 768px) panel domyslnie zamkniety (brak atrybutu open)
- Na desktop: dodaj atrybut `open` i zablokuj zamykanie (preventDefault na summary click)
- Listener na matchMedia change: reset stanu przy resize
Unikaj: zewnetrznych plikow JS, jQuery
</action>
<verify>Otworz /orders/list — panel renderuje sie jako details/summary z poprawna struktura HTML</verify>
<done>AC-1, AC-2, AC-3 satisfied (struktura HTML)</done>
</task>
<task type="auto">
<name>Task 2: SCSS dla toggle panelu statusow</name>
<files>resources/scss/app.scss</files>
<action>
1. Dodaj style do istniejacego bloku `.order-statuses-side`:
`.order-statuses-side__title` (jako summary):
- cursor: pointer na mobile
- display: flex, align-items: center, justify-content: space-between
- list-style: none (usun domyslny marker details)
- &::-webkit-details-marker { display: none }
`.order-statuses-side__arrow`:
- width: 14px, height: 14px
- transition: transform 0.2s ease
- opacity: 0.5
- Ukryj na desktop (display: none)
- Pokaz na mobile (display: block w media query)
`details[open] .order-statuses-side__arrow`:
- transform: rotate(180deg)
`.order-statuses-side__body`:
- Brak dodatkowych stylow (flow domyslny)
2. W media query `@media (max-width: 768px)`:
Dodaj:
```scss
.order-statuses-side__arrow {
display: block;
}
```
Unikaj: zmian w istniejacych stylach desktop panelu
</action>
<verify>Na mobile strzalka widoczna, na desktop ukryta. Klikniecie toggle'uje panel.</verify>
<done>AC-1, AC-2, AC-3 satisfied (style)</done>
</task>
<task type="auto">
<name>Task 3: Build CSS</name>
<files>public/assets/css/app.css</files>
<action>
Skompiluj SCSS do CSS: npx sass resources/scss/app.scss public/assets/css/app.css --no-source-map
</action>
<verify>Nowe klasy (order-statuses-side__arrow, order-statuses-side__body) obecne w wynikowym CSS</verify>
<done>Plik CSS aktualny</done>
</task>
<task type="checkpoint:human-verify" gate="blocking">
<what-built>Rozwijany/zwijany panel statusow na mobile</what-built>
<how-to-verify>
1. Otworz https://orderpro.projectpro.pl/orders/list (lub lokalnie)
2. Wlacz DevTools > Device Toolbar > 375px
3. Sprawdz: panel statusow zwiniety, widoczny tylko naglowek "Statusy" ze strzalka
4. Kliknij "Statusy" — panel rozwija sie, strzalka obraca sie
5. Kliknij ponownie — panel zwija sie
6. Przelacz na desktop (> 768px) — panel zawsze widoczny, bez strzalki
</how-to-verify>
<resume-signal>Type "approved" to continue, or describe issues to fix</resume-signal>
</task>
</tasks>
<boundaries>
## DO NOT CHANGE
- resources/views/orders/list.php (widok glowny nie wymaga zmian)
- resources/views/layouts/app.php (menu mobilne juz gotowe)
- Logika PHP generowania statusPanel (kontroler/repozytorium)
- Inne komponenty uzytkujace order-status-panel (sprawdzic czy jest reuse)
## SCOPE LIMITS
- Tylko panel statusow na mobile — zadne inne elementy listy zamowien
- Nie zmieniaj wygladu poszczegolnych statusow (kolory, badge, hover)
- Nie dodawaj nowych zaleznosci JS
</boundaries>
<verification>
Before declaring plan complete:
- [ ] Panel zwiniety domyslnie na mobile
- [ ] Klik na naglowek rozwija/zwija panel
- [ ] Strzalka obraca sie przy toggle
- [ ] Desktop: panel zawsze widoczny, strzalka ukryta
- [ ] Brak bledow JS w konsoli
- [ ] Inne strony uzywajace panelu statusow dzialaja poprawnie
</verification>
<success_criteria>
- Wszystkie 3 acceptance criteria spelnione
- Panel nie zajmuje miejsca na mobile gdy zwiniety
- Plynna animacja strzalki (CSS transition)
- Zero regresji na desktop
</success_criteria>
<output>
After completion, create `.paul/phases/53-mobile-status-panel-toggle/53-01-SUMMARY.md`
</output>

View File

@@ -0,0 +1,100 @@
---
phase: 53-mobile-status-panel-toggle
plan: 01
subsystem: ui
tags: [mobile, responsive, status-panel, details-summary, collapsible]
requires:
- phase: none
provides: n/a
provides:
- Collapsible status panel on mobile using native details/summary
affects: [mobile-orders-list, mobile-order-details]
tech-stack:
added: []
patterns: [native-details-summary-toggle, desktop-lock-open]
key-files:
created: []
modified:
- resources/views/components/order-status-panel.php
- resources/scss/app.scss
- public/assets/css/app.css
key-decisions:
- "Native <details>/<summary> zamiast custom JS toggle — zero zaleznosci, semantyczny HTML"
- "Desktop: open + preventDefault na summary click — panel zawsze widoczny"
patterns-established:
- "Mobile collapsible panel: details/summary + matchMedia JS + arrow visibility toggle"
duration: ~10min
started: 2026-03-29
completed: 2026-03-29
---
# Phase 53 Plan 01: Mobile Status Panel Toggle Summary
**Panel statusow na /orders/list zwijany/rozwijany na mobile — native details/summary z blokada zamykania na desktop**
## Performance
| Metric | Value |
|--------|-------|
| Duration | ~10min |
| Started | 2026-03-29 |
| Completed | 2026-03-29 |
| Tasks | 4 completed (3 auto + 1 checkpoint) |
| Files modified | 3 |
## Acceptance Criteria Results
| Criterion | Status | Notes |
|-----------|--------|-------|
| AC-1: Panel zwiniety domyslnie na mobile | Pass | Brak atrybutu open na mobile, lista zamowien widoczna |
| AC-2: Rozwijanie i zwijanie panelu | Pass | Klik na summary toggle'uje, strzalka obraca sie |
| AC-3: Desktop bez zmian | Pass | Panel zawsze otwarty, strzalka ukryta, click zablokowany |
## Accomplishments
- Zamiana aside na details/summary z natywnym toggle
- Chevron SVG strzalka widoczna tylko na mobile
- Inline JS: domyslnie zamkniety na mobile, otwarty na desktop z blokada click
- Reset stanu przy resize (matchMedia change listener)
## Files Created/Modified
| File | Change | Purpose |
|------|--------|---------|
| `resources/views/components/order-status-panel.php` | Modified | details/summary + chevron SVG + inline JS |
| `resources/scss/app.scss` | Modified | Strzalka, toggle styles, cursor pointer na mobile |
| `public/assets/css/app.css` | Modified | Kompilacja SCSS |
## Decisions Made
None — followed plan as specified
## Deviations from Plan
None — plan executed exactly as written
## Issues Encountered
None
## Next Phase Readiness
**Ready:**
- Pattern mobile collapsible panelu gotowy do reuse
- Lista zamowien dostepna na mobile bez scrollowania przez statusy
**Concerns:**
- None
**Blockers:**
- None
---
*Phase: 53-mobile-status-panel-toggle, Plan: 01*
*Completed: 2026-03-29*

View File

@@ -2106,6 +2106,22 @@ h4.section-title::before {
font-weight: 700;
color: #0f172a;
margin-bottom: 8px;
display: flex;
align-items: center;
justify-content: space-between;
list-style: none;
}
.order-statuses-side__title::-webkit-details-marker {
display: none;
}
.order-statuses-side__arrow {
display: none;
flex-shrink: 0;
opacity: 0.5;
transition: transform 0.2s ease;
}
details[open] > .order-statuses-side__title .order-statuses-side__arrow {
transform: rotate(180deg);
}
.order-status-group {
@@ -3042,6 +3058,12 @@ body.no-scroll {
position: static;
top: auto;
}
.order-statuses-side__title {
cursor: pointer;
}
.order-statuses-side__arrow {
display: block;
}
.order-details-actions {
justify-content: flex-start;
}

View File

@@ -1379,7 +1379,30 @@ h4.section-title {
font-weight: 700;
color: #0f172a;
margin-bottom: 8px;
display: flex;
align-items: center;
justify-content: space-between;
list-style: none;
&::-webkit-details-marker {
display: none;
}
}
&__arrow {
display: none;
flex-shrink: 0;
opacity: 0.5;
transition: transform 0.2s ease;
}
&__body {
// default flow
}
}
details[open] > .order-statuses-side__title .order-statuses-side__arrow {
transform: rotate(180deg);
}
.order-status-group {
@@ -2367,6 +2390,14 @@ body.no-scroll {
top: auto;
}
.order-statuses-side__title {
cursor: pointer;
}
.order-statuses-side__arrow {
display: block;
}
.order-details-actions {
justify-content: flex-start;
}

View File

@@ -3,34 +3,69 @@ $panelItems = is_array($statusPanelList ?? null) ? $statusPanelList : [];
$panelTitle = trim((string) ($statusPanelTitle ?? 'Statusy'));
?>
<aside class="card order-statuses-side">
<div class="order-statuses-side__title"><?= $e($panelTitle) ?></div>
<?php foreach ($panelItems as $group): ?>
<?php $groupItems = is_array($group['items'] ?? null) ? $group['items'] : []; ?>
<div class="order-status-group">
<?php if ((string) ($group['name'] ?? '') !== ''): ?>
<div class="order-status-group__name"><?= $e((string) ($group['name'] ?? '')) ?></div>
<?php endif; ?>
<?php foreach ($groupItems as $item): ?>
<?php
$tone = (string) ($item['tone'] ?? 'neutral');
$color = (string) ($item['color_hex'] ?? '#64748b');
$rowClass = 'order-status-row tone-' . $tone . (!empty($item['is_active']) ? ' is-active' : '');
$url = trim((string) ($item['url'] ?? ''));
?>
<?php if ($url !== ''): ?>
<a href="<?= $e($url) ?>" class="<?= $e($rowClass) ?>" style="--status-color: <?= $e($color) ?>;">
<span class="order-status-row__label"><?= $e((string) ($item['label'] ?? '')) ?></span>
<span class="order-status-row__count"><?= $e((string) ((int) ($item['count'] ?? 0))) ?></span>
</a>
<?php else: ?>
<div class="<?= $e($rowClass) ?>" style="--status-color: <?= $e($color) ?>;">
<span class="order-status-row__label"><?= $e((string) ($item['label'] ?? '')) ?></span>
<span class="order-status-row__count"><?= $e((string) ((int) ($item['count'] ?? 0))) ?></span>
</div>
<details class="card order-statuses-side" id="js-status-panel">
<summary class="order-statuses-side__title">
<?= $e($panelTitle) ?>
<svg class="order-statuses-side__arrow" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M6 9l6 6 6-6"/>
</svg>
</summary>
<div class="order-statuses-side__body">
<?php foreach ($panelItems as $group): ?>
<?php $groupItems = is_array($group['items'] ?? null) ? $group['items'] : []; ?>
<div class="order-status-group">
<?php if ((string) ($group['name'] ?? '') !== ''): ?>
<div class="order-status-group__name"><?= $e((string) ($group['name'] ?? '')) ?></div>
<?php endif; ?>
<?php endforeach; ?>
</div>
<?php endforeach; ?>
</aside>
<?php foreach ($groupItems as $item): ?>
<?php
$tone = (string) ($item['tone'] ?? 'neutral');
$color = (string) ($item['color_hex'] ?? '#64748b');
$rowClass = 'order-status-row tone-' . $tone . (!empty($item['is_active']) ? ' is-active' : '');
$url = trim((string) ($item['url'] ?? ''));
?>
<?php if ($url !== ''): ?>
<a href="<?= $e($url) ?>" class="<?= $e($rowClass) ?>" style="--status-color: <?= $e($color) ?>;">
<span class="order-status-row__label"><?= $e((string) ($item['label'] ?? '')) ?></span>
<span class="order-status-row__count"><?= $e((string) ((int) ($item['count'] ?? 0))) ?></span>
</a>
<?php else: ?>
<div class="<?= $e($rowClass) ?>" style="--status-color: <?= $e($color) ?>;">
<span class="order-status-row__label"><?= $e((string) ($item['label'] ?? '')) ?></span>
<span class="order-status-row__count"><?= $e((string) ((int) ($item['count'] ?? 0))) ?></span>
</div>
<?php endif; ?>
<?php endforeach; ?>
</div>
<?php endforeach; ?>
</div>
</details>
<script>
(function () {
var panel = document.getElementById('js-status-panel');
if (!panel) return;
var mq = window.matchMedia('(max-width: 768px)');
function applyMode(mobile) {
if (mobile) {
panel.removeAttribute('open');
} else {
panel.setAttribute('open', '');
}
}
applyMode(mq.matches);
panel.querySelector('summary').addEventListener('click', function (e) {
if (!mq.matches) {
e.preventDefault();
}
});
mq.addEventListener('change', function (e) {
applyMode(e.matches);
});
})();
</script>