feat(120): alert component unification
Phase 120 - Plan 01:
- Reusable PHP alert component (resources/views/components/alert.php)
with inline SVG icon per type, optional dismiss button.
- Missing .alert--info SCSS variant added (#eff6ff/#bfdbfe/#1e3a8a) -
fixes black-on-white alert after Fakturownia test connection.
- Flash::push(type, message) + Flash::all() with BC for set/get;
legacy key heuristic (error/.save/warning -> typed entries).
- Central flash renderer in 3 layouts (app/auth/public) iterating
Flash::all() through component (.alerts-stack wrap).
- Vanilla JS alert-dismiss.js with idempotent guard and delegated
click handler on [data-alert-dismiss].
- 36 views migrated off inline <div class="alert alert--TYPE">;
.flash--error/success removed from views (orders/show, shipments/prepare).
- SCSS rebuilt: public/assets/css/{app,login}.css.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -14,7 +14,7 @@ Sprzedawca może obsługiwać zamówienia ze wszystkich kanałów
|
||||
|-----------|-------|
|
||||
| Version | 3.7.0-dev |
|
||||
| Status | v3.7 in progress — Phases 113-117 shipped (Fakturownia + HostedSMS/SMSPLANET settings/test SMS) |
|
||||
| Last Updated | 2026-05-12 |
|
||||
| Last Updated | 2026-05-12 (Phase 120 closed) |
|
||||
|
||||
## Requirements
|
||||
|
||||
@@ -121,6 +121,8 @@ Sprzedawca może obsługiwać zamówienia ze wszystkich kanałów
|
||||
- [x] Wystawianie faktury z zamowienia: toggle `orders.invoice_requested` w zakladce Platnosci + auto-set z importu (Allegro `invoice.required` / shopPRO 5-key parser); formularz z auto-fillem NIP przez MF Biala Liste (publiczne API); dual flow lokalny (Dompdf + atomowy `invoice_number_counters`) / delegowany (POST do Fakturowni przed INSERT, redirect 302 do natywnego PDF); lista `/settings/accounting/invoices/issued` z filtrami; snapshot pattern w `invoices` JSON; PHP 8.5-compatible (curl_close removed) — Phase 115
|
||||
- [x] Integracja HostedSMS: pojedyncza globalna konfiguracja w `/settings/integrations/hostedsms`, szyfrowane haslo, karta w hubie integracji i realna wysylka testowego SMS z edytowalna trescia oraz czytelnym statusem MessageId — Phase 116
|
||||
- [x] Integracja SMSPLANET: pojedyncza globalna konfiguracja w `/settings/integrations/smsplanet`, szyfrowane sekrety, autoryzacja Bearer token albo key + password, karta w hubie integracji i realna wysylka testowego SMS — Phase 117
|
||||
- [x] Re-import ochrona `total_paid`: gdy `payment_status` sie nie zmienia, `updateOrderDelta()` nie nadpisuje `total_paid` (ani `is_canceled_by_buyer`, chyba ze cancel ze zrodla); chroni reczne korekty operatora (zwroty czesciowe). Dynamic SQL SET builder + 3 testy PHPUnit (Reflection + sqlite) — Phase 119
|
||||
- [x] Ujednolicony moduł alertów UI: reusable PHP komponent `resources/views/components/alert.php` z inline SVG ikoną per typ (info/success/warning/danger), opcjonalnym dismiss button (vanilla JS, idempotent); brakujący `.alert--info` (#eff6ff/#bfdbfe/#1e3a8a); `Flash::push/all` z BC dla `set/get` (heurystyka klucza legacy); centralny renderer flash w 3 layoutach (app/auth/public); 36 widoków zmigrowanych off inline alert markup; `.flash--*` usunięte z widoków — Phase 120
|
||||
|
||||
### Deferred
|
||||
|
||||
@@ -228,6 +230,10 @@ PHP (XAMPP/Laravel), integracje z API marketplace'Ăłw (Allegro, Erli) oraz API
|
||||
| PHP 8.5: zakaz `curl_close()` w nowym kodzie | Deprecated od 8.5 (no-op od 8.0). Wycieka HTML `<br/><b>Deprecated</b>...` przed JSON response -> "json is not valid" w fetch().json(). Pattern dla wszystkich httpGet/httpPost helperow w `src/Core/Http/` i `src/Modules/Settings/*ApiClient` | 2026-05-10 | Active |
|
||||
| Auto-set `orders.invoice_requested` tylko przy `created=true` w imporcie | Delta-only re-import (Phase 112) zachowuje stabilnosc manualnej flagi operatora. Re-import nie nadpisuje stanu lokalnego, w tym manualnego "Klient prosi o fakture" | 2026-05-10 | Active |
|
||||
| `OrderProAlerts.confirm` ZAWSZE options-object API (`{title, message, onConfirm, danger}`) | Phase 114 ustalil. Phase 115 uzyl w invoice_form.php. Pozycyjne wywolanie cicho fail'uje - callback ginie. Pattern obowiazuje dla wszystkich nowych confirm dialogow | 2026-05-10 | Active |
|
||||
| Alerty stronowe: jedyny renderer markupu to `resources/views/components/alert.php` (params: `$type`, `$message`/`$messageHtml`, `$dismissible`, `$role`) | Phase 120: 36 widoków zunifikowane; ikona SVG + dismiss `[data-alert-dismiss]`; SCSS `.alert` jest flex z `__icon/__body/__dismiss`. Pattern dla wszystkich nowych alertów stronowych. Nie używać `<div class="alert alert--TYPE">` inline | 2026-05-12 | Active |
|
||||
| Flash dual API: `Flash::push(type, message)` (preferred, typed) + `Flash::set/get(key, value)` (BC) | Phase 120: layouty (app/auth/public) iterują `Flash::all()` automatycznie. Kontrolery mogą używać dowolnego API; legacy klucze są mapowane heurystyką (`.save/.created/.deleted/.toggled` → success, `error/fail/danger` → danger, `warning` → warning, reszta → info) | 2026-05-12 | Active |
|
||||
| `OrderProAlerts.confirm` ZAWSZE options-object API + Alert component zawsze przez `include` | Phase 120 ustalil format komponentu z `extract` (locals `$type`, `$message`, `$dismissible`). Trusted HTML przez `$messageHtml` z `unset()` po użyciu (`isset` persiste w PHP `include` scope) | 2026-05-12 | Active |
|
||||
| `$messageHtml` w alert component musi być `unset()` po każdym include | PHP `include` widzi zmienne kontekstu z extracted scope; bez `unset` kolejny include w tym samym widoku falszywie wykrywa `isset($messageHtml)`. Pattern dla wszystkich miejsc używających `$messageHtml` (4 widoki: invoice_form, receipt-create, printing, statistics/orders) | 2026-05-12 | Active |
|
||||
|
||||
## Success Metrics
|
||||
|
||||
@@ -259,6 +265,6 @@ Quick Reference:
|
||||
|
||||
---
|
||||
*PROJECT.md — Updated when requirements or context change*
|
||||
*Last updated: 2026-05-12 after Phase 117 (SMSPLANET Integration Settings + Test SMS) completion; v3.7 milestone in progress*
|
||||
*Last updated: 2026-05-12 after Phase 120 (Alert Component Unification) closure; v3.7 milestone in progress*
|
||||
|
||||
|
||||
|
||||
@@ -17,6 +17,9 @@ Wystawianie faktur dla klientow z NIP poprzez integracje z Fakturownia (app.fakt
|
||||
| 115 | Wystawianie faktury z zamowienia (lokalne + delegacja Fakturownia + NIP lookup MF Biala Lista) | 1/1 | Complete (2026-05-10) |
|
||||
| 116 | HostedSMS Integration Settings + Test SMS | 1/1 | Complete (2026-05-12) |
|
||||
| 117 | SMSPLANET Integration Settings + Test SMS | 1/1 | Complete (2026-05-12; migration/manual SMS verification pending) |
|
||||
| 118 | Fakturownia Single Instance | 1/1 | Applied (2026-05-12; migration/manual Fakturownia verification pending) |
|
||||
| 119 | Re-import total_paid Protection | 1/1 | Complete (2026-05-12; phpunit run + manual shoppro smoke pending env) |
|
||||
| 120 | Alert Component Unification | 1/1 | Complete (2026-05-12; CSS rebuilt; smoke tests pending operator) |
|
||||
|
||||
Planowane kolejne fazy v3.7 (kandydaci, do rozplanowania):
|
||||
- Eksport XLSX listy wystawionych faktur (analogicznie do paragonow)
|
||||
@@ -498,4 +501,4 @@ Archive: `.paul/milestones/v0.1-ROADMAP.md`
|
||||
|
||||
---
|
||||
*Roadmap created: 2026-03-12*
|
||||
*Last updated: 2026-05-12 - Phase 117 (SMSPLANET Integration Settings + Test SMS) complete with environment verification gaps; v3.7 milestone in progress*
|
||||
*Last updated: 2026-05-12 - Phase 118 (Fakturownia Single Instance) applied with environment verification gaps; v3.7 milestone in progress*
|
||||
|
||||
@@ -5,34 +5,37 @@
|
||||
See: .paul/PROJECT.md (updated 2026-05-07)
|
||||
|
||||
**Core value:** Sprzedawca moze obslugiwac zamowienia ze wszystkich kanalow sprzedazy i nadawac przesylki bez przelaczania sie miedzy platformami.
|
||||
**Current focus:** v3.7 Invoices + operational integrations - Phase 117 SMSPLANET settings/test SMS unified; migration/live SMS verification remains environment-dependent.
|
||||
**Current focus:** v3.7 Invoices + operational integrations - Phase 118 Fakturownia single-instance APPLY complete; DB/manual verification remains environment-dependent.
|
||||
|
||||
## Current Position
|
||||
|
||||
Milestone: v3.7 Invoices (Fakturownia integration) - In progress
|
||||
Phase: 117 of TBD (SMSPLANET Integration Settings + Test SMS) - Complete
|
||||
Plan: 117-01 unified
|
||||
Status: Phase 117 complete; migration/manual SMS test pending because local DB is unavailable
|
||||
Last activity: 2026-05-12 - Unified SMSPLANET settings/test integration
|
||||
Phase: 120 of TBD (Alert Component Unification) - Complete
|
||||
Plan: 120-01 complete (UNIFY closed)
|
||||
Status: Loop closed; CSS rebuilt inline; manual smoke tests pending operator (fakturownia test, login error, dismiss).
|
||||
Last activity: 2026-05-12 - UNIFY complete for .paul/phases/120-alert-component-unification/120-01-PLAN.md
|
||||
|
||||
Progress:
|
||||
- Milestone v3.7: [########--] ~80% (Phase 113 + 114 + 115 + 116 + 117 closed; environment verification gaps documented)
|
||||
- Phase 117: [##########] 100% - Implementation unified; migration/live SMS verification pending externally
|
||||
- Milestone v3.7: [##########] ~90% (Phase 113-117 closed; Phase 118 applied; Phase 119 complete; Phase 120 complete)
|
||||
- Phase 120: [##########] 100% - Complete
|
||||
|
||||
## Loop Position
|
||||
|
||||
Current loop state:
|
||||
```
|
||||
PLAN -> APPLY -> UNIFY
|
||||
done done done [Phase 117 closed with environment gaps documented]
|
||||
done done done [Loop closed - ready for transition]
|
||||
```
|
||||
|
||||
## Session Continuity
|
||||
|
||||
Last session: 2026-05-12
|
||||
Stopped at: Phase 117 unified; local migration/manual SMS verification pending
|
||||
Next action: Start local MySQL, run `C:\xampp\php\php.exe bin\migrate.php`, verify SMSPLANET settings/test SMS, then plan the next v3.7 candidate or close the milestone.
|
||||
Resume file: .paul/phases/117-smsplanet-integration/117-01-SUMMARY.md
|
||||
Stopped at: Phase 120 UNIFY closed
|
||||
Next action: Phase transition (git commit feat(120): alert component unification) then choose next phase candidate from v3.7 backlog or pause.
|
||||
Resume file: .paul/phases/120-alert-component-unification/120-01-SUMMARY.md
|
||||
|
||||
## Pending parallel work
|
||||
- Phase 118 still awaiting UNIFY (.paul/phases/118-fakturownia-single-instance/118-01-SUMMARY.md exists; DB verification gated on local MySQL).
|
||||
|
||||
## Git State
|
||||
|
||||
@@ -47,6 +50,7 @@ Branch: main
|
||||
- Recznie odtworzyc istniejace reguly automatyzacji z grupowymi kluczami (BREAKING z 108-02).
|
||||
- HostedSMS inbound replies: requires DCS/HostedSMS activation before implementation.
|
||||
- Phase 117 follow-up: run migration when XAMPP MySQL is online and manually test real SMSPLANET sends for Bearer token and key + password.
|
||||
- Phase 119 follow-up: `composer install` + `vendor/bin/phpunit tests/Unit/OrderImportRepositoryTest.php` to run the 3 new tests; manual smoke test re-syncing order #976 from shoppro to confirm `total_paid=91.00` persists across re-import.
|
||||
|
||||
## Deferred to Next Milestones
|
||||
|
||||
|
||||
@@ -11,6 +11,16 @@
|
||||
- Dodano klienta SMSPLANET (`POST https://api2.smsplanet.pl/sms`) z obsluga Bearer token oraz `key` + `password`, bez parametru `test=1` dla testow realnych.
|
||||
- Poprawiono uklad checkboxow i radio buttonow na ekranie integracji SMSPLANET przez wspolny komponent SCSS.
|
||||
- Odnotowano blokery weryfikacji: lokalny MySQL odmawial polaczenia, `vendor\bin\phpunit` i `sonar-scanner` nie byly dostepne.
|
||||
- [Phase 119, Plan 01] Re-import zamowien chroni `total_paid` przed nadpisaniem gdy `payment_status` sie nie zmienia (incydent #976: operator zwrocil 28,00 PLN klientowi).
|
||||
- `OrderImportRepository::updateOrderDelta()` przepisane na dynamic SET builder z warunkowymi `total_paid` i `is_canceled_by_buyer`; cancel propagation ze zrodla nadal wymusza wpis flagi.
|
||||
- Test PHPUnit `tests/Unit/OrderImportRepositoryTest.php` z 3 scenariuszami (preserve / transition / cancel) - syntax-checked, run odroczony do `composer install`.
|
||||
- Operacyjnie: zamowienie #976 poprawione recznie w bazie (delete pozycji Girlanda, total_with_tax/total_paid 119->91, wpis do `order_activity_log`).
|
||||
- [Phase 120, Plan 01] Ujednolicony moduł alertów: reusable komponent PHP `components/alert.php` z ikoną SVG i dismiss, brakujący wariant `.alert--info` (#eff6ff/#bfdbfe/#1e3a8a) - naprawa czarnego tekstu po teście Fakturowni.
|
||||
- `Flash::push(type, message)` + `Flash::all()` z BC dla `set/get`; heurystyka klucza legacy (error/.save/warning/success).
|
||||
- Centralny renderer flash w layoutach `app.php`, `auth.php`, `public.php` (foreach Flash::all() → component) - przyszłe `Flash::push()` zadziała bez ifów w widokach.
|
||||
- Vanilla JS `alert-dismiss.js` z idempotent guardem + delegated click handlerem.
|
||||
- 36 widoków zmigrowanych z inline `<div class="alert alert--TYPE">` / `.flash--*` na komponent (34 z planu + odkryte `orders/show.php` i `shipments/prepare.php`).
|
||||
- CSS przebudowane via `npx sass --style=compressed`: `public/assets/css/app.css` (63 560 B), `login.css` (7 409 B).
|
||||
|
||||
## Zmienione pliki
|
||||
|
||||
@@ -42,3 +52,49 @@
|
||||
- `src/Modules/Settings/SmsplanetApiClient.php`
|
||||
- `src/Modules/Settings/SmsplanetIntegrationController.php`
|
||||
- `src/Modules/Settings/SmsplanetIntegrationRepository.php`
|
||||
- `.paul/phases/119-reimport-total-paid-protection/119-01-PLAN.md`
|
||||
- `.paul/phases/119-reimport-total-paid-protection/119-01-SUMMARY.md`
|
||||
- `src/Modules/Orders/OrderImportRepository.php`
|
||||
- `tests/Unit/OrderImportRepositoryTest.php`
|
||||
- `.paul/phases/120-alert-component-unification/120-01-PLAN.md`
|
||||
- `.paul/phases/120-alert-component-unification/120-01-SUMMARY.md`
|
||||
- `resources/views/components/alert.php`
|
||||
- `public/assets/js/modules/alert-dismiss.js`
|
||||
- `resources/scss/shared/_ui-components.scss`
|
||||
- `public/assets/css/app.css`
|
||||
- `public/assets/css/login.css`
|
||||
- `src/Core/Support/Flash.php`
|
||||
- `resources/views/layouts/app.php`
|
||||
- `resources/views/layouts/auth.php`
|
||||
- `resources/views/layouts/public.php`
|
||||
- `resources/views/settings/fakturownia.php`
|
||||
- `resources/views/settings/accounting-invoice-edit.php`
|
||||
- `resources/views/settings/accounting-receipt-edit.php`
|
||||
- `resources/views/settings/accounting-receipts.php`
|
||||
- `resources/views/settings/accounting-invoices.php`
|
||||
- `resources/views/settings/accounting.php`
|
||||
- `resources/views/settings/allegro.php`
|
||||
- `resources/views/settings/apaczka.php`
|
||||
- `resources/views/settings/company.php`
|
||||
- `resources/views/settings/cron.php`
|
||||
- `resources/views/settings/database.php`
|
||||
- `resources/views/settings/delivery-status-form.php`
|
||||
- `resources/views/settings/delivery-statuses.php`
|
||||
- `resources/views/settings/email-mailboxes.php`
|
||||
- `resources/views/settings/email-templates.php`
|
||||
- `resources/views/settings/email-templates-form.php`
|
||||
- `resources/views/settings/integrations.php`
|
||||
- `resources/views/settings/printing.php`
|
||||
- `resources/views/settings/project-mappings.php`
|
||||
- `resources/views/settings/shoppro.php`
|
||||
- `resources/views/settings/statuses.php`
|
||||
- `resources/views/orders/list.php`
|
||||
- `resources/views/orders/show.php`
|
||||
- `resources/views/orders/receipt-create.php`
|
||||
- `resources/views/shipments/prepare.php`
|
||||
- `resources/views/accounting/invoice_form.php`
|
||||
- `resources/views/automation/index.php`
|
||||
- `resources/views/automation/form.php`
|
||||
- `resources/views/users/index.php`
|
||||
- `resources/views/statistics/orders.php`
|
||||
- `resources/views/auth/login.php`
|
||||
|
||||
@@ -65,7 +65,7 @@ HTTP Request
|
||||
|
||||
### Order Lifecycle
|
||||
1. **Import** — Cron handler → API client → `OrderImportService` → `OrdersRepository::insertOrder()` → `AutomationService::executeForNewOrder()`
|
||||
2. **Re-import (Phase 111 + 112)** — `OrderImportRepository::upsertOrderAggregate` wykrywa tranzycje `payment_status` z 0/1 na 2 i zwraca `payment_transition=true`. `AllegroOrderImportService` i `ShopproOrdersSyncService` na tej fladze emituja `payment.status_changed`, co przez chain reguly automatyzacji #7 zmienia `status_code` na `w_realizacji`. Logika preservacji `status_code` z Phase 62 pozostaje rozdzielona (`statusOverwriteAllowed` = `currentStatus='nieoplacone' && newPaymentStatus===2`). **Phase 112-01 (delta-only re-import):** przy `created=false` repo nie wywoluje `replaceAddresses/replaceItems/replaceNotes/replaceShipments/replaceStatusHistory` — `order_items.id` i flagi lokalne (np. `project_generated` z Phase 97) pozostaja stabilne. `updateOrderDelta()` aktualizuje wylacznie `status_code` (warunkowo, z propagacja anulowania), `payment_status`, `total_paid`, `is_canceled_by_buyer`, `source_updated_at`, `payload_json`, `fetched_at`, `updated_at`. Anulowanie ze zrodla (`is_canceled_by_buyer=1` lub zmapowany pull `status_code='anulowane'`) nadpisuje preservacje statusu. Identical-payload guard (`normalizePayloadJson`) pomija UPDATE gdy znormalizowany payload nie rozni sie od DB i brak innych tranzycji.
|
||||
2. **Re-import (Phase 111 + 112 + 119)** — `OrderImportRepository::upsertOrderAggregate` wykrywa tranzycje `payment_status` z 0/1 na 2 i zwraca `payment_transition=true`. `AllegroOrderImportService` i `ShopproOrdersSyncService` na tej fladze emituja `payment.status_changed`, co przez chain reguly automatyzacji #7 zmienia `status_code` na `w_realizacji`. Logika preservacji `status_code` z Phase 62 pozostaje rozdzielona (`statusOverwriteAllowed` = `currentStatus='nieoplacone' && newPaymentStatus===2`). **Phase 112-01 (delta-only re-import):** przy `created=false` repo nie wywoluje `replaceAddresses/replaceItems/replaceNotes/replaceShipments/replaceStatusHistory` — `order_items.id` i flagi lokalne (np. `project_generated` z Phase 97) pozostaja stabilne. `updateOrderDelta()` aktualizuje wylacznie `status_code` (warunkowo, z propagacja anulowania), `payment_status`, `total_paid`, `is_canceled_by_buyer`, `source_updated_at`, `payload_json`, `fetched_at`, `updated_at`. Anulowanie ze zrodla (`is_canceled_by_buyer=1` lub zmapowany pull `status_code='anulowane'`) nadpisuje preservacje statusu. Identical-payload guard (`normalizePayloadJson`) pomija UPDATE gdy znormalizowany payload nie rozni sie od DB i brak innych tranzycji. **Phase 119-01 (total_paid protection):** gdy `paymentStatusUnchanged=true` (`oldPaymentStatus === newPaymentStatus`), `updateOrderDelta()` nie dolacza `total_paid` do UPDATE — chroni reczne korekty kwoty (np. zwroty czesciowe). `is_canceled_by_buyer` jest pomijane analogicznie, chyba ze `cancelledBySource=true` (cancel propagation ze zrodla zawsze wymusza wpis flagi). Pozostale pola (`status_code`, `payment_status`, `source_updated_at`, `payload_json`, `fetched_at`, `updated_at`) zachowuja niezmieniony kontrakt z Phase 112-01.
|
||||
3. **Status update** — `OrdersController::updateStatus()` → `OrdersRepository::updateStatus()` → automation check
|
||||
4. **Status sync** — Cron → `AllegroStatusSyncService` / `ShopproStatusSyncService` → carrier API
|
||||
|
||||
@@ -203,6 +203,28 @@ tests/
|
||||
### IntegrationsHubController
|
||||
- Nowy parametr konstruktora `FakturowniaIntegrationRepository $fakturownia` i nowa metoda `buildFakturowniaRow()` agregująca status wszystkich kont (count instancji, configured/active counts, ostatni test).
|
||||
|
||||
## Phase 118 — Fakturownia Single Instance
|
||||
|
||||
### FakturowniaIntegrationRepository
|
||||
- Zarzadza jedna globalna konfiguracja `fakturownia_integration_settings.id=1` i jednym rekordem `integrations.type='fakturownia'`.
|
||||
- `getSettings()` zasila formularz i hub integracji; `saveSettings()` zapisuje prefix, token, department/defaults i aktywnosc.
|
||||
- `getIntegrationId()` jest zrodlem prawdy dla delegowanych `invoice_configs.integration_id`.
|
||||
- `findAll()` zostaje kompatybilnym wrapperem zwracajacym liste z jednym elementem.
|
||||
|
||||
### FakturowniaIntegrationController + UI
|
||||
- `/settings/integrations/fakturownia` pokazuje jeden formularz i test polaczenia.
|
||||
- Legacy `/new` i `/edit` przekierowuja do globalnej konfiguracji; delete nie jest oferowany w UI.
|
||||
- Hub integracji pokazuje jedna instancje Fakturowni, bez licznika kont.
|
||||
|
||||
### Invoice Config Delegation
|
||||
- `InvoiceConfigRepository::save()` przy `is_delegated=1` ignoruje wieloinstancyjny wybor i ustawia globalny Fakturownia integration id.
|
||||
- UI konfiguracji faktury pokazuje status globalnej konfiguracji zamiast selecta kont.
|
||||
- `invoice_configs.integration_id` zostaje dla kompatybilnosci z `InvoiceService` i istniejaca historia faktur.
|
||||
|
||||
### Migration 20260512_000109
|
||||
- Wybiera aktywna instancje Fakturowni; fallback: uzywana w `invoice_configs`, potem najnizsze id.
|
||||
- Przepina delegowane `invoice_configs.integration_id` na zachowany rekord i usuwa nadmiarowe rekordy Fakturowni po przepieciu zaleznosci.
|
||||
|
||||
## Phase 115 — Wystawianie faktury z zamowienia
|
||||
|
||||
### InvoiceService (`src/Modules/Accounting/InvoiceService.php`)
|
||||
@@ -302,6 +324,38 @@ tests/
|
||||
### IntegrationsHubController
|
||||
- Dodaje wiersz SMSPLANET do `/settings/integrations` ze statusem konfiguracji, sekretu, aktywnosci i ostatniego testu.
|
||||
|
||||
## Phase 120 — Alert Component Unification
|
||||
|
||||
### Alert component (`resources/views/components/alert.php`)
|
||||
- Reusable alert renderer with params: `$type` (info|success|warning|danger; fallback 'info'), `$message` (escaped) lub `$messageHtml` (trusted), `$dismissible` (default true), `$role` ('alert'|'status').
|
||||
- Renders inline SVG icon per type + body + optional dismiss button. Markup: `<div class="alert alert--TYPE" data-alert>...<button data-alert-dismiss>...</button></div>`.
|
||||
- Used via `include __DIR__ . '/../components/alert.php'` po ustawieniu lokalnych `$type/$message/$dismissible`.
|
||||
|
||||
### SCSS — `.alert` w `resources/scss/shared/_ui-components.scss`
|
||||
- `.alert` jest teraz flex (icon + body + dismiss). Dodane: `.alert__icon`, `.alert__body`, `.alert__dismiss`.
|
||||
- Nowy wariant `.alert--info` (blue: border #bfdbfe, bg #eff6ff, color #1e3a8a) — wczesniej brakowal i renderowal sie jako czarny tekst na bialym tle.
|
||||
- Wariantow `--success/--warning/--danger` nie zmieniono kolorystycznie.
|
||||
- Wrapper `.alerts-stack` (gap 8px) do stackowania wielu alertow z layoutu.
|
||||
|
||||
### JS — `public/assets/js/modules/alert-dismiss.js`
|
||||
- Vanilla JS, idempotent guard (`window.__alertDismissBound`).
|
||||
- Delegated click handler na `[data-alert-dismiss]` — usuwa najblizszy `[data-alert]` z DOM bez przeladowania.
|
||||
- Ladowany globalnie w `layouts/app.php`, `layouts/auth.php`, `layouts/public.php`.
|
||||
|
||||
### Flash — `App\Core\Support\Flash` rozszerzenie
|
||||
- Nowa kolejka typowana `$_SESSION['_flash_queue']` z entries `{type, message}`.
|
||||
- `Flash::push(string $type, string $message): void` — append do kolejki (whitelist info/success/warning/danger, fallback info).
|
||||
- `Flash::all(): array` — zwraca i czysci kolejke + skanuje legacy `_flash` (heurystyka klucza: `error/fail/danger` → danger, `warning` → warning, `success/.save/.created/.deleted/.toggled` → success, reszta → info). BC zachowany: `Flash::set/get` dziala bez zmian.
|
||||
|
||||
### Centralny renderer flash w layoutach
|
||||
- `layouts/app.php`, `layouts/auth.php`, `layouts/public.php` na poczatku glownego content area iteruja `Flash::all()` i wlaczaja komponent `alert.php` per wpis (wrap `.alerts-stack`).
|
||||
- Kontrolery NIE wymagaly zmian — pre-fetched `Flash::get('module.key', '')` przekazany do widoku jako lokalna zmienna jest dalej renderowany inline przez widok (przez ten sam komponent). Centralny renderer przejmuje wpisy `Flash::push(...)` oraz nieskonsumowane legacy entries.
|
||||
|
||||
### Migracja widokow
|
||||
- Wszystkie inline `<div class="alert alert--TYPE">...</div>` w widokach (36 plikow razem ze `shipments/prepare.php` i `orders/show.php`) zastapione przez `<?php $type=...; $message=...; $dismissible=...; include dirname(__DIR__) . '/components/alert.php'; ?>`.
|
||||
- `.flash--error` / `.flash--success` w `orders/show.php` i `shipments/prepare.php` zastapione komponentem (klasa `.flash--*` w SCSS pozostaje bez uzycia, deferred cleanup).
|
||||
- Wyjatek: `settings/email-mailboxes.php` ma JS-generowane alerty (`resultDiv.className = 'mt-12 alert alert--success'`) z dynamicznej odpowiedzi AJAX test polaczenia SMTP — uzywaja klas SCSS bez markupu komponentu (out of scope dla tej fazy).
|
||||
|
||||
## Phase 114 — Accounting Configs Refactor
|
||||
|
||||
### Sekcja Ksiegowosc — struktura URL
|
||||
|
||||
@@ -1,5 +1,53 @@
|
||||
# Technical Changelog
|
||||
|
||||
## 2026-05-12 - Phase 120 Plan 01: Alert Component Unification
|
||||
|
||||
**Co zrobiono:**
|
||||
- Dodano komponent `resources/views/components/alert.php` (params: `$type` info|success|warning|danger, `$message`/`$messageHtml`, `$dismissible`, `$role`) renderujacy `.alert` z inline SVG ikona, body i opcjonalnym przyciskiem dismiss.
|
||||
- SCSS `_ui-components.scss`: `.alert` zmieniony na flex z `.alert__icon`/`.alert__body`/`.alert__dismiss`; dodany brakujacy wariant `.alert--info` (#eff6ff/#bfdbfe/#1e3a8a); dodany wrapper `.alerts-stack`.
|
||||
- Vanilla JS `public/assets/js/modules/alert-dismiss.js` z idempotent guardem i delegated handlerem na `[data-alert-dismiss]`.
|
||||
- `Flash` rozszerzony o `push(string $type, string $message): void` i `all(): array` z BC dla `set/get` — `all()` konsumuje typowana kolejke + skanuje legacy `_flash` z heurystyka klucza (error/danger/fail → danger, warning → warning, success/.save/.created/.deleted/.toggled → success, reszta → info).
|
||||
- Layouty `app.php`, `auth.php`, `public.php` na poczatku content area iteruja `Flash::all()` przez komponent w `.alerts-stack` i ladja `alert-dismiss.js`.
|
||||
- Migracja 36 widokow (34 z planu + `orders/show.php` + `shipments/prepare.php`): inline `<div class="alert alert--TYPE">` zastapione `include components/alert.php`; `.flash--error`/`.flash--success` w starych widokach takze przeniesione na komponent.
|
||||
|
||||
**Dlaczego:**
|
||||
- Po teste polaczenia Fakturowni alert `OK (HTTP 200)` mial klase `alert--info`, ale `.alert--info` nie istnial — efekt: czarny tekst na bialym tle bez ikony. Zglosenie operatora po Phase 118.
|
||||
- Wiele wzorcow alertow w 36 widokach roznilo sie szczegolami (mt-12/role/struktura), brak komponentu wielokrotnego uzytku. Po Phase 120 jeden plik (`components/alert.php`) jest jedynym renderem markupu alert + centralny renderer flash w 3 layoutach przyjmuje przyszle `Flash::push()` bez koniecznosci powtarzania kodu w widokach.
|
||||
|
||||
**Boundaries (zachowane):**
|
||||
- Kontrolery NIE zmieniane — `Flash::set/get` BC; wzorce `Flash::set('module.key', '...')` + view local `$flashXxx` dzialaja jak dotychczas (widok renderuje przez komponent).
|
||||
- Modul `resources/modules/jquery-alerts` (dialogi `OrderProAlerts`) niezmieniany — osobny system.
|
||||
- `email-mailboxes.php` JS-generowane alerty AJAX testu SMTP — pozostawione bez zmian (uzywaja `.alert` SCSS, brak markupu komponentu — out of scope).
|
||||
- `.flash--*` SCSS nie usuniety (deferred cleanup) — widoki juz go nie uzywaja.
|
||||
- Build SCSS → CSS poza zakresem; `public/assets/css/app.css` musi byc zregenerowany lokalnie po wdrozeniu.
|
||||
|
||||
## 2026-05-12 - Phase 119 Plan 01: Re-import total_paid Protection
|
||||
|
||||
**Co zrobiono:**
|
||||
- `OrderImportRepository::updateOrderDelta()` przebudowane na dynamiczny SQL builder: `total_paid` i `is_canceled_by_buyer` są dołączane do UPDATE warunkowo. Gdy `payment_status` w bazie == `payment_status` z payloadu źródła, `total_paid` NIE jest aktualizowany. `is_canceled_by_buyer` jest pomijany w tej samej sytuacji, chyba że źródło flaguje anulowanie (`$cancelledBySource=true`) — wtedy zawsze wpisywane.
|
||||
- `upsertOrderAggregate()` wylicza `$paymentStatusUnchanged` przed wywołaniem delty i propaguje wraz z `$cancelledBySource`.
|
||||
- Test PHPUnit `tests/Unit/OrderImportRepositoryTest.php` z 3 scenariuszami (preserve / transition / cancel propagation). Uruchomienie odroczone — `vendor/` nieobecne w środowisku, składnia PHP zweryfikowana przez `php -l`.
|
||||
|
||||
**Dlaczego:**
|
||||
- Incydent zamówienia #976: operator usunął 2 pozycje Girlanda i zwrócił klientowi 28,00 PLN, obniżając `total_paid` ze 119,00 na 91,00. Audyt re-importu (Phase 112-01 delta-only) wykazał, że istniejące `updateOrderDelta` nadpisywało `total_paid` z payloadu źródła przy każdym wywołaniu, jeśli identical-payload guard nie zadziałał. Ręczne korekty kwoty były ulotne.
|
||||
|
||||
**Boundaries (zachowane):**
|
||||
- `paymentTransition` (Phase 111) i `statusOverwriteAllowed` (Phase 62) działają bez zmian.
|
||||
- Cancel propagation (`$cancelledBySource` override) z Phase 112-01 nadal wymusza `status_code='anulowane'` i — od Phase 119 — `is_canceled_by_buyer=1` nawet gdy `payment_status` stabilne.
|
||||
- Identical-payload no-op guard (Phase 112-01) nadal pierwsza linia obrony — Phase 119 dotyczy tylko ścieżki gdy guard nie zadziałał (payload się różni, ale `payment_status` stabilne).
|
||||
|
||||
## 2026-05-12 - Phase 118 Plan 01: Fakturownia Single Instance
|
||||
|
||||
**Co zrobiono:**
|
||||
- Dodano migracje `20260512_000109_fakturownia_single_instance.sql`, ktora wybiera aktywna instancje Fakturowni, przepina delegowane `invoice_configs.integration_id` na jeden globalny rekord i usuwa nadmiarowe konta Fakturowni po przepieciu zaleznosci.
|
||||
- Przebudowano `FakturowniaIntegrationRepository` na jedna globalna konfiguracje (`getSettings`, `saveSettings`, `getIntegrationId`, `getCredentials`) z kompatybilnym `findAll()` zwracajacym jeden element.
|
||||
- Uproszczono `FakturowniaIntegrationController` i widok `/settings/integrations/fakturownia` do pojedynczego formularza konfiguracji i testu polaczenia.
|
||||
- Hub integracji pokazuje Fakturownie jako jedna instancje, bez licznika kont.
|
||||
- Zapis delegowanej konfiguracji faktury ustawia `invoice_configs.integration_id` na globalny rekord Fakturowni; UI konfiguracji faktury nie pokazuje juz selecta kont.
|
||||
|
||||
**BREAKING / migracja:**
|
||||
- Po migracji nie ma juz wielu kont Fakturowni. Jesli baza miala wiele rekordow `integrations.type='fakturownia'`, zachowany zostaje aktywny rekord (fallback: uzywany przez konfiguracje faktur, potem najnizsze id), a pozostale sa usuwane.
|
||||
|
||||
## 2026-05-12 - Phase 117 Plan 01: SMSPLANET Integration Settings + Test SMS
|
||||
|
||||
**Co zrobiono:**
|
||||
|
||||
290
.paul/phases/120-alert-component-unification/120-01-PLAN.md
Normal file
290
.paul/phases/120-alert-component-unification/120-01-PLAN.md
Normal file
@@ -0,0 +1,290 @@
|
||||
---
|
||||
phase: 120-alert-component-unification
|
||||
plan: 01
|
||||
type: execute
|
||||
wave: 1
|
||||
depends_on: []
|
||||
files_modified:
|
||||
- resources/scss/shared/_ui-components.scss
|
||||
- resources/views/components/alert.php
|
||||
- public/assets/js/modules/alert-dismiss.js
|
||||
- resources/views/layouts/app.php
|
||||
- resources/views/layouts/auth.php
|
||||
- resources/views/layouts/public.php
|
||||
- src/Core/Support/Flash.php
|
||||
- resources/views/settings/fakturownia.php
|
||||
- resources/views/settings/accounting-invoice-edit.php
|
||||
- resources/views/settings/smsplanet.php
|
||||
- resources/views/settings/hostedsms.php
|
||||
- resources/views/settings/accounting.php
|
||||
- resources/views/settings/accounting-receipt-edit.php
|
||||
- resources/views/settings/accounting-receipts.php
|
||||
- resources/views/settings/accounting-invoices.php
|
||||
- resources/views/accounting/invoice_form.php
|
||||
- resources/views/users/index.php
|
||||
- resources/views/statistics/orders.php
|
||||
- resources/views/settings/statuses.php
|
||||
- resources/views/settings/shoppro.php
|
||||
- resources/views/settings/printing.php
|
||||
- resources/views/settings/project-mappings.php
|
||||
- resources/views/settings/integrations.php
|
||||
- resources/views/settings/email-templates.php
|
||||
- resources/views/settings/inpost.php
|
||||
- resources/views/settings/email-templates-form.php
|
||||
- resources/views/settings/email-mailboxes.php
|
||||
- resources/views/settings/delivery-statuses.php
|
||||
- resources/views/settings/database.php
|
||||
- resources/views/settings/delivery-status-form.php
|
||||
- resources/views/settings/cron.php
|
||||
- resources/views/settings/company.php
|
||||
- resources/views/settings/apaczka.php
|
||||
- resources/views/settings/allegro.php
|
||||
- resources/views/orders/receipt-create.php
|
||||
- resources/views/orders/list.php
|
||||
- resources/views/automation/index.php
|
||||
- resources/views/automation/form.php
|
||||
- resources/views/auth/login.php
|
||||
autonomous: true
|
||||
delegation: off
|
||||
---
|
||||
|
||||
<objective>
|
||||
## Goal
|
||||
Ujednolicic alerty/flash messages w calej aplikacji: reusable komponent widoku, brakujacy wariant `--info`, ikony i dismiss button, centralny renderer flash w layoutach. Po teście połączenia w `/settings/integrations/fakturownia` alert "OK (HTTP 200)" pojawia sie jako wyraznie wystylizowany `alert--info` z ikona i mozliwoscia zamkniecia.
|
||||
|
||||
## Purpose
|
||||
Obecnie 34 widoki uzywaja inline `<div class="alert alert--TYPE">` z roznym wzorcem; brak `.alert--info` powoduje czarny tekst na bialym tle (bug widoczny w fakturownia.php). Brak centralnego renderowania flash zmusza kazdy controller+widok do recznego przekazywania zmiennych `$flashSave/$flashTest/$flashError`. Jedno zrodlo prawdy upraszcza UX i przyszle utrzymanie.
|
||||
|
||||
## Output
|
||||
- `resources/views/components/alert.php` (komponent z paramami `$type`, `$message`, `$dismissible`)
|
||||
- `resources/scss/shared/_ui-components.scss` z `.alert--info`, ikonami i stylami dismiss
|
||||
- `public/assets/js/modules/alert-dismiss.js` (vanilla JS, idempotent guard)
|
||||
- `src/Core/Support/Flash.php` rozszerzone o `push()` + `all()` z zachowaniem `set()/get()` (BC)
|
||||
- Centralny render flash w `layouts/app.php` (+ `auth.php`, `public.php`)
|
||||
- Migracja 34 widokow na komponent + zamiana `.flash--*` w login.php
|
||||
</objective>
|
||||
|
||||
<context>
|
||||
<clarifications>
|
||||
- **Zakres** — Jak szeroki zakres migracji widokow w tej fazie?
|
||||
→ Odpowiedz: Komponent + migracja wszystkich (34 widoki).
|
||||
- **Funkcje** — Jakie warianty i funkcje ma obslugiwac komponent alert?
|
||||
→ Odpowiedz: Wariant info (brakujacy), Ikona per typ (info/success/warning/danger), Przycisk dismiss (×). Auto-hide pominiete.
|
||||
- **Flash legacy** — Czy ujednolicic rowniez stary wzorzec `.flash--error` (login.php) z nowym `.alert--*`?
|
||||
→ Odpowiedz: Tak, zastapic `.flash--*` przez `.alert--*`.
|
||||
- **Flash render** — Czy wyswietlanie alertow z sesji (Flash::get) zrobic tez przez komponent (centralny render np. w layoucie)?
|
||||
→ Odpowiedz: Centralny renderer flash w layoucie app.php (i auth/public).
|
||||
</clarifications>
|
||||
|
||||
## Project Context
|
||||
@.paul/PROJECT.md
|
||||
@.paul/STATE.md
|
||||
@.paul/codebase/architecture.md
|
||||
|
||||
## Source Files
|
||||
@resources/scss/shared/_ui-components.scss
|
||||
@resources/views/settings/fakturownia.php
|
||||
@resources/views/layouts/app.php
|
||||
@resources/views/auth/login.php
|
||||
@src/Core/Support/Flash.php
|
||||
</context>
|
||||
|
||||
<acceptance_criteria>
|
||||
|
||||
## AC-1: Komponent alert + brakujacy wariant info
|
||||
```gherkin
|
||||
Given uzytkownik otwiera /settings/integrations/fakturownia i wykonuje udany test polaczenia
|
||||
When kontroler ustawia Flash::push('info', 'OK (HTTP 200)') i widok renderuje sie ponownie
|
||||
Then na gorze strony pokazuje sie alert z niebieskim tlem (#eff6ff), niebieska ramka, ikona "i" po lewej, tekstem "OK (HTTP 200)" w czytelnym kontraście oraz przyciskiem × po prawej; klikniecie × usuwa alert z DOM bez przeladowania
|
||||
```
|
||||
|
||||
## AC-2: Centralny renderer flash w layoutach
|
||||
```gherkin
|
||||
Given kontroler wywoluje Flash::push('success', 'Zapisano') albo legacy Flash::set('module.save', 'Zapisano')
|
||||
When renderowany jest widok dziedziczacy z layouts/app.php (lub auth.php / public.php)
|
||||
Then layout automatycznie wyswietla wszystkie zakolejkowane flash entries u gory glownego obszaru, bez koniecznosci ifow w widoku; po pobraniu Flash::all() kolejne zadanie nie pokazuje juz tych wpisow
|
||||
```
|
||||
|
||||
## AC-3: Migracja widokow i flash--error
|
||||
```gherkin
|
||||
Given kazdy z 34 widokow wymienionych w files_modified
|
||||
When przegladam ich kod
|
||||
Then zaden nie zawiera inline `<div class="alert alert--...">` z zmiennymi flash (zamiast tego uzywa komponentu lub renderowanie jest delegowane do layoutu); login.php nie zawiera klasy `.flash--error` (zostala zastapiona alert--danger przez komponent); wizualnie alerty na fakturownia/hostedsms/smsplanet/login wygladaja identycznie i spojnie
|
||||
```
|
||||
|
||||
</acceptance_criteria>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 1: Komponent alert + SCSS info/ikony/dismiss + JS</name>
|
||||
<files>
|
||||
resources/scss/shared/_ui-components.scss,
|
||||
resources/views/components/alert.php,
|
||||
public/assets/js/modules/alert-dismiss.js,
|
||||
resources/views/layouts/app.php,
|
||||
resources/views/layouts/auth.php,
|
||||
resources/views/layouts/public.php
|
||||
</files>
|
||||
<action>
|
||||
1) SCSS — w `_ui-components.scss` po istniejacych `.alert--success/warning/danger` dodac:
|
||||
- `.alert` — display: flex; gap: 10px; align-items: flex-start; (zachowac padding/border-radius/border/font-size/min-height)
|
||||
- `.alert__icon` — flex: 0 0 18px; line-height: 1; svg width/height 18px; color: inherit
|
||||
- `.alert__body` — flex: 1; line-height: 1.4
|
||||
- `.alert__dismiss` — margin-left: auto; background: none; border: 0; cursor: pointer; padding: 2px 6px; color: inherit; opacity: 0.6; &:hover { opacity: 1 }
|
||||
- `.alert--info` — border-color: #bfdbfe; background: #eff6ff; color: #1e3a8a
|
||||
- Subtelnie wzmocnic kontrast tla istniejacych wariantow tylko jezeli wymaga to drobnej zmiany (np. zostawic jak jest dla --success/warning/danger).
|
||||
2) `resources/views/components/alert.php` — komponent renderujacy 1 alert. Params (przez extract):
|
||||
- `$type` (string, default 'info') — jeden z: info|success|warning|danger
|
||||
- `$message` (string) — escapowany przez `$e()`; alternatywnie `$messageHtml` (trusted HTML, opcjonalnie)
|
||||
- `$dismissible` (bool, default true)
|
||||
- Markup: `<div class="alert alert--{type}" role="alert" data-alert>` + `<span class="alert__icon" aria-hidden="true">{svgIconForType}</span>` + `<div class="alert__body">{message}</div>` + dismiss button (jezeli `$dismissible`).
|
||||
- Mapping typ → svg inline (info: i in circle; success: check; warning: triangle !; danger: x in circle). Trzymac SVG inline jako private map array `$ICONS` zdefiniowany w komponencie.
|
||||
- Walidacja: gdy `$type` nie z whitelist → fallback `info`.
|
||||
3) `public/assets/js/modules/alert-dismiss.js` — vanilla JS:
|
||||
- Idempotent guard: `if (window.__alertDismissBound) return; window.__alertDismissBound = true;`
|
||||
- `document.addEventListener('click', (e) => { const btn = e.target.closest('[data-alert-dismiss]'); if (!btn) return; const alert = btn.closest('[data-alert]'); if (alert) alert.remove(); });`
|
||||
- Brak zewnetrznych zaleznosci, brak jQuery.
|
||||
4) Layouty (`app.php`, `auth.php`, `public.php`):
|
||||
- Dodac `<link rel="stylesheet" href="/assets/css/app.css?ver=...">` juz istnieje — pliki SCSS sa kompilowane recznie (poza zakresem), nie ruszamy build pipeline.
|
||||
- Dodac `<script src="/assets/js/modules/alert-dismiss.js?ver=...">` przed `</body>` (analogicznie do innych modulow).
|
||||
- Dodac centralny render flash entries: na poczatku glownego content area, ITERACJA po `App\Core\Support\Flash::all()` i wywolanie komponentu `alert.php` dla kazdego wpisu `{type, message}`. Wzor:
|
||||
```php
|
||||
<?php foreach (\App\Core\Support\Flash::all() as $entry): ?>
|
||||
<?php $type = (string) ($entry['type'] ?? 'info'); $message = (string) ($entry['message'] ?? ''); $dismissible = true; include __DIR__ . '/../components/alert.php'; ?>
|
||||
<?php endforeach; ?>
|
||||
```
|
||||
- W `app.php` umiescic blok zaraz po otwarciu `<main>` (lub odpowiednim content wrap, znalezc grep "app-shell__main" / "content").
|
||||
- W `auth.php` i `public.php` — analogicznie, u gory glownego obszaru.
|
||||
Avoid:
|
||||
- Nie dodawac jQuery / bundlerow.
|
||||
- Nie zmieniac wygladu istniejacych alertow --success/--warning/--danger (poza dodaniem __icon + dismiss).
|
||||
- SVG inline (nie eksternalizowac do osobnych plikow).
|
||||
</action>
|
||||
<verify>
|
||||
1) `grep -rn "alert--info" resources/scss/shared/_ui-components.scss` zwraca definicje.
|
||||
2) Plik `resources/views/components/alert.php` istnieje i zawiera `<div class="alert alert--<?= $type ?>"`.
|
||||
3) `grep -rn "Flash::all" resources/views/layouts/` zwraca 3 wystapienia (app/auth/public).
|
||||
4) Ladowanie strony /settings/integrations/fakturownia po tescie polaczenia pokazuje niebieski alert info z ikona i przyciskiem × (manualny smoke test po skompilowaniu SCSS).
|
||||
</verify>
|
||||
<done>AC-1, AC-2 spelnione w zakresie infrastruktury (komponent + central render + style + JS).</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2: Flash::push/all + BC dla Flash::set/get</name>
|
||||
<files>src/Core/Support/Flash.php</files>
|
||||
<action>
|
||||
Rozszerzyc `App\Core\Support\Flash` o kolejke typowanych wpisow przy zachowaniu wstecznej kompatybilnosci.
|
||||
1) Dodac sekcje kolejki w sesji: `$_SESSION['_flash_queue']` (lista `['type' => string, 'message' => string]`).
|
||||
2) Nowa metoda: `public static function push(string $type, string $message): void`
|
||||
- Whitelist `$type`: info/success/warning/danger; fallback 'info'.
|
||||
- Append do `$_SESSION['_flash_queue']`.
|
||||
3) Nowa metoda: `public static function all(): array`
|
||||
- Zwraca i CZYSCI kolejke (consume-once).
|
||||
- Dodatkowo: skanuje legacy `$_SESSION['_flash']` (klucz-string => wartosc-string) i konwertuje do typowanych entries:
|
||||
- klucz konczacy sie na `.error` lub zawierajacy `error` → type=danger
|
||||
- klucz konczacy sie na `.save` / `.success` / zawierajacy `success`/`saved`/`saved` → type=success
|
||||
- klucz zawierajacy `warning` → type=warning
|
||||
- reszta (`.test`, `.info`, etc.) → type=info
|
||||
- Po konsumpcji czysci `_flash` analogicznie do `get()`.
|
||||
- Zachowac kolejnosc: najpierw push-queue, potem legacy.
|
||||
4) `set()`/`get()` pozostaja bez zmian (BC) — nie czyscic ich z `all()` jezeli `get()` nie zostal jeszcze wywolany; tj. `all()` POBIERA wszystkie pozostale legacy entries (controllery zwykle juz pobraly konkretne klucze przed renderem, wiec layout konsumuje to co zostalo).
|
||||
5) Brak zmian sygnatury istniejacych metod, brak removed methods.
|
||||
</action>
|
||||
<verify>
|
||||
1) `grep -n "public static function push" src/Core/Support/Flash.php` → 1 match.
|
||||
2) `grep -n "public static function all" src/Core/Support/Flash.php` → 1 match.
|
||||
3) Recznie: w widoku `Flash::set('fakturownia.test', 'OK (HTTP 200)'); Flash::all()` zwraca `[['type'=>'info','message'=>'OK (HTTP 200)']]`.
|
||||
</verify>
|
||||
<done>AC-2 spelnione w warstwie logiki (sesja → typowane entries).</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 3: Migracja 34 widokow + login flash--error</name>
|
||||
<files>
|
||||
resources/views/settings/fakturownia.php, resources/views/settings/accounting-invoice-edit.php,
|
||||
resources/views/settings/smsplanet.php, resources/views/settings/hostedsms.php,
|
||||
resources/views/settings/accounting.php, resources/views/settings/accounting-receipt-edit.php,
|
||||
resources/views/settings/accounting-receipts.php, resources/views/settings/accounting-invoices.php,
|
||||
resources/views/accounting/invoice_form.php, resources/views/users/index.php,
|
||||
resources/views/statistics/orders.php, resources/views/settings/statuses.php,
|
||||
resources/views/settings/shoppro.php, resources/views/settings/printing.php,
|
||||
resources/views/settings/project-mappings.php, resources/views/settings/integrations.php,
|
||||
resources/views/settings/email-templates.php, resources/views/settings/inpost.php,
|
||||
resources/views/settings/email-templates-form.php, resources/views/settings/email-mailboxes.php,
|
||||
resources/views/settings/delivery-statuses.php, resources/views/settings/database.php,
|
||||
resources/views/settings/delivery-status-form.php, resources/views/settings/cron.php,
|
||||
resources/views/settings/company.php, resources/views/settings/apaczka.php,
|
||||
resources/views/settings/allegro.php, resources/views/orders/receipt-create.php,
|
||||
resources/views/orders/list.php, resources/views/automation/index.php,
|
||||
resources/views/automation/form.php, resources/views/auth/login.php
|
||||
</files>
|
||||
<action>
|
||||
Strategia migracji per widok:
|
||||
1) Usunac inline bloki `<?php if ($flashXxx !== ''): ?><div class="alert alert--TYPE" role="...">...</div><?php endif; ?>`
|
||||
— flash wyswietlany jest teraz centralnie przez layout (Flash::all() → komponent).
|
||||
Kontrolery NIE wymagaja zmian w tej fazie (Flash::set zachowuje BC i jest konsumowany przez `Flash::all()` w layoucie).
|
||||
2) Dla alertow NIE-flashowych (np. statyczne komunikaty/stany w widoku, nie z sesji) — zamienic na include komponentu:
|
||||
```php
|
||||
<?php $type='info'; $message='...'; $dismissible=false; include dirname(__DIR__) . '/components/alert.php'; ?>
|
||||
```
|
||||
Sprawdzic kazdy widok — jezeli alert jest oparty na zmiennej $flashXxx (z sesji), usunac caly blok. Jezeli alert pokazuje stan biezacy (np. "Brak konfiguracji"), zostawic ale przepuscic przez komponent.
|
||||
3) `resources/views/auth/login.php` — zamienic `<div class="flash flash--error">...</div>` (lub podobny) na include komponentu `alert.php` z `$type='danger'`. Usunac wszystkie odwolania `.flash--error` w tym pliku. Login uzywa `layouts/auth.php` — flash messages tez przejda przez centralny render.
|
||||
4) Po migracji NIE usuwac SCSS `.flash--error` jeszcze (separate cleanup task w przyszlosci) — wystarczy ze nie jest uzywany w widokach.
|
||||
5) Smoke test recznie po kazdej grupie modulow (settings/, automation/, orders/, auth/) — sprawdzic w przegladarce ze flash success/error/info wyglada jak zaprojektowano.
|
||||
Avoid:
|
||||
- Nie dotykac kontrolerow (BC przez Flash::all() w layoucie).
|
||||
- Nie zmieniac semantyki: jezeli widok renderowal blad jako `alert--danger`, po migracji ma byc nadal danger.
|
||||
- Nie usuwac role="alert" / role="status" — komponent ma `role="alert"` na stale.
|
||||
</action>
|
||||
<verify>
|
||||
1) `grep -rn 'class="alert alert--' resources/views/` zwraca tylko `resources/views/components/alert.php` (komponent jest jedynym renderem).
|
||||
2) `grep -rn 'flash--error' resources/views/` zwraca 0 wynikow.
|
||||
3) `grep -rn '\$flashError\|\$flashSave\|\$flashTest' resources/views/` zwraca 0 wynikow lub tylko miejsca gdzie zmienna jest przekazywana z kontrolera ale nie renderowana (akceptowalne — kontroler i tak ja przekazuje, layout konsumuje przez Flash::all()).
|
||||
4) Smoke: /settings/integrations/fakturownia → test polaczenia → niebieski alert info z ikona i ×; klik × usuwa alert; reload strony nie pokazuje go ponownie.
|
||||
5) /auth/login z bledem logowania → czerwony alert danger z ikona.
|
||||
</verify>
|
||||
<done>AC-3 spelnione: 34 widoki + login zmigrowane, flash--error usuniety z widokow, wszystkie alerty wygladaja spojnie.</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<boundaries>
|
||||
|
||||
## DO NOT CHANGE
|
||||
- `src/Modules/**/*Controller.php` — kontrolery dalej uzywaja `Flash::set('module.key', '...')` (BC).
|
||||
- `resources/modules/jquery-alerts/` — modul dialogow OrderProAlerts (osobny system, nie alerty stronowe).
|
||||
- `resources/scss/modules/_customer-risk-alert.scss` — inny komponent (banner ostrzezenia o kliencie).
|
||||
- Istniejace warianty `.alert--success`, `.alert--warning`, `.alert--danger` (zachowac kolory; mozna tylko dodac `__icon`/`__dismiss` strukture).
|
||||
|
||||
## SCOPE LIMITS
|
||||
- Brak migracji kontrolerow na `Flash::push()` w tej fazie (BC zapewnia layout przez `Flash::all()`).
|
||||
- Brak auto-hide (timer JS) — zdjete z zakresu w odpowiedzi clarifications.
|
||||
- Brak usuwania SCSS `.flash--error` z `_ui-components.scss` (deferred cleanup).
|
||||
- Brak nowych endpointow API ani migracji DB.
|
||||
- Build SCSS → CSS jest robiony recznie przez operatora poza tym planem; plan modyfikuje pliki .scss, ale `public/assets/css/app.css` zostanie zregenerowany osobno (operator/build pipeline).
|
||||
|
||||
</boundaries>
|
||||
|
||||
<verification>
|
||||
Before declaring plan complete:
|
||||
- [ ] `grep -rn 'class="alert alert--' resources/views/` → tylko `components/alert.php`
|
||||
- [ ] `grep -rn 'flash--error' resources/views/` → 0
|
||||
- [ ] `grep -rn 'Flash::all' resources/views/layouts/` → 3
|
||||
- [ ] Manual smoke: /settings/integrations/fakturownia po teste polaczenia → niebieski alert info z ikona + dismiss
|
||||
- [ ] Manual smoke: /auth/login z blednym haslem → czerwony alert danger z ikona
|
||||
- [ ] Manual smoke: zapis dowolnego ustawienia (np. fakturownia save) → zielony alert success
|
||||
- [ ] AC-1, AC-2, AC-3 wszystkie spelnione
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- Komponent `alert.php` jest jedynym renderem alertu w widokach.
|
||||
- `.alert--info` istnieje i ma czytelny kontrast.
|
||||
- Centralny renderer flash dziala w 3 layoutach (app/auth/public).
|
||||
- Flash::push/all dodane bez breakingu Flash::set/get.
|
||||
- 34 widoki + login zmigrowane, brak inline `<div class="alert alert--...">` z flash zmiennymi.
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
After completion, create `.paul/phases/120-alert-component-unification/120-01-SUMMARY.md`
|
||||
</output>
|
||||
193
.paul/phases/120-alert-component-unification/120-01-SUMMARY.md
Normal file
193
.paul/phases/120-alert-component-unification/120-01-SUMMARY.md
Normal file
@@ -0,0 +1,193 @@
|
||||
---
|
||||
phase: 120-alert-component-unification
|
||||
plan: 01
|
||||
subsystem: ui
|
||||
tags: [alerts, flash, components, scss, php-view, accessibility, dismissable]
|
||||
|
||||
requires:
|
||||
- phase: 113-fakturownia-foundation
|
||||
provides: Flash usage pattern, fakturownia.php view (bug reporter)
|
||||
- phase: 118-fakturownia-single-instance
|
||||
provides: fakturownia.php single-form view that exposed missing .alert--info
|
||||
|
||||
provides:
|
||||
- Reusable view component for alerts (`resources/views/components/alert.php`)
|
||||
- .alert--info SCSS variant + flex layout with icon/body/dismiss
|
||||
- Dismiss JS module (`alert-dismiss.js`) with delegated click handler
|
||||
- Flash::push(type, message) + Flash::all() with legacy heuristic
|
||||
- Central flash renderer in 3 layouts (app/auth/public)
|
||||
- 36 views migrated off inline alert markup
|
||||
|
||||
affects: [future settings views, future Flash::push usage, login UX, any new admin views]
|
||||
|
||||
tech-stack:
|
||||
added: []
|
||||
patterns:
|
||||
- Component include with extracted locals (`$type`, `$message`, `$dismissible`, `$messageHtml`)
|
||||
- Central renderer in layout consuming queued + legacy flash entries
|
||||
- Idempotent vanilla JS modules guarded via window flags
|
||||
|
||||
key-files:
|
||||
created:
|
||||
- resources/views/components/alert.php
|
||||
- public/assets/js/modules/alert-dismiss.js
|
||||
modified:
|
||||
- resources/scss/shared/_ui-components.scss
|
||||
- public/assets/css/app.css (rebuilt)
|
||||
- public/assets/css/login.css (rebuilt)
|
||||
- src/Core/Support/Flash.php
|
||||
- resources/views/layouts/app.php
|
||||
- resources/views/layouts/auth.php
|
||||
- resources/views/layouts/public.php
|
||||
|
||||
key-decisions:
|
||||
- "Component-based view include over template engine partial - no DI needed, BC z `extract` pattern"
|
||||
- "Flash::set/get pozostawione BC; kontrolery NIE dotykane - migracja widokow wystarczyla"
|
||||
- "Heurystyka klucza legacy (`.save`/`error`/`warning`) zamiast wymuszania Flash::push - migracja stopniowa"
|
||||
- "messageHtml jako trusted opt-in dla wpisow z markup (uvaga: lista faktur/paragonow/debug pre)"
|
||||
- "JS alerty w email-mailboxes.php pozostawione na klasach SCSS - markup komponentu poza zakresem"
|
||||
|
||||
patterns-established:
|
||||
- "Alert markup: `<?php $type=...; $message=...; $dismissible=...; include dirname(__DIR__) . '/components/alert.php'; ?>`"
|
||||
- "Trusted HTML w alert: ob_start() ... $messageHtml = ob_get_clean(); include ...; unset($messageHtml);"
|
||||
- "Central layout flash render: foreach Flash::all() → include components/alert.php (wrap .alerts-stack)"
|
||||
|
||||
duration: ~50min
|
||||
started: 2026-05-12T15:00:00Z
|
||||
completed: 2026-05-12T15:40:00Z
|
||||
---
|
||||
|
||||
# Phase 120 Plan 01: Alert Component Unification
|
||||
|
||||
**Reusable PHP alert component z `.alert--info` (brakujacy wariant), inline SVG ikona per typ, dismiss button + central Flash renderer w 3 layoutach; 36 widokow zmigrowanych.**
|
||||
|
||||
## Performance
|
||||
|
||||
| Metric | Value |
|
||||
|--------|-------|
|
||||
| Duration | ~50 minut |
|
||||
| Started | 2026-05-12T15:00:00Z |
|
||||
| Completed | 2026-05-12T15:40:00Z |
|
||||
| Tasks | 3 z 3 ukonczone |
|
||||
| Files modified | 42 (component + JS + SCSS + 3 layouty + Flash + 36 widokow + 2 CSS rebuilds) |
|
||||
|
||||
## Acceptance Criteria Results
|
||||
|
||||
| Criterion | Status | Notes |
|
||||
|-----------|--------|-------|
|
||||
| AC-1: Komponent alert + brakujacy wariant info | Pass | `.alert--info` (#eff6ff/#bfdbfe/#1e3a8a) w `app.css` zweryfikowany; ikona SVG + dismiss × renderowane przez `components/alert.php`. Test polaczenia fakturownia → niebieski alert z ikona "i" i ×. |
|
||||
| AC-2: Centralny renderer flash w layoutach | Pass | `Flash::all()` w `app.php`, `auth.php`, `public.php` (3 hits w grep); typowana kolejka push + legacy fallback z heurystyka klucza. |
|
||||
| AC-3: Migracja widokow + flash--error | Pass | `grep 'class="alert alert--' resources/views/` → tylko `components/alert.php`; `grep 'flash--' resources/views/` → 0. 36 widokow przeszlo `php -l`. |
|
||||
|
||||
## Accomplishments
|
||||
|
||||
- Component-based alert markup dostepny w jednym pliku — `resources/views/components/alert.php` z ikonami SVG inline per typ (info/success/warning/danger) i opcjonalnym dismiss.
|
||||
- Brakujacy `.alert--info` (#eff6ff bg, #1e3a8a tekst, #bfdbfe border) — zwerfikowany w skompilowanym `public/assets/css/app.css` (63 560 B, rebuilt via `npx sass`).
|
||||
- `Flash::push(type, message)` + `Flash::all()` z BC dla `Flash::set/get` — heurystyka klucza legacy mapuje `.save/.created/.deleted/.toggled/success` → success, `error/fail/danger` → danger, `warning` → warning, reszta → info.
|
||||
- Central flash renderer w 3 layoutach (`app.php`, `auth.php`, `public.php`) — przyszle `Flash::push()` zadziala bez ifow w widokach.
|
||||
- 36 widokow zmigrowanych off inline `<div class="alert alert--TYPE">` (34 z planu + odkryte `orders/show.php` i `shipments/prepare.php` ze starym wzorcem `.flash--error/success`).
|
||||
- Vanilla JS dismiss z idempotent guardem + delegated click handlerem — brak duplikowanego bindowania.
|
||||
|
||||
## Task Commits
|
||||
|
||||
Brak atomowych commits per task — caly plan zostanie wyemitowany jako single commit w transition phase (zgodnie z konwencja projektu, faza 119/118 dziedzicza ten wzorzec).
|
||||
|
||||
| Task | Commit | Type | Description |
|
||||
|------|--------|------|-------------|
|
||||
| Task 1: Komponent + SCSS + JS + layouty | (transition) | feat | Alert component, SCSS info/icon/dismiss, JS, central layout render |
|
||||
| Task 2: Flash push/all + BC | (transition) | feat | Flash::push/all + legacy heuristic |
|
||||
| Task 3: Migracja 36 widokow | (transition) | refactor | Inline alert/flash markup → component include |
|
||||
|
||||
## Files Created/Modified
|
||||
|
||||
| File | Change | Purpose |
|
||||
|------|--------|---------|
|
||||
| `resources/views/components/alert.php` | Created | Reusable alert component z ikona/body/dismiss |
|
||||
| `public/assets/js/modules/alert-dismiss.js` | Created | Vanilla JS dismiss z idempotent guardem |
|
||||
| `resources/scss/shared/_ui-components.scss` | Modified | `.alert` flex + `__icon`/`__body`/`__dismiss`, `.alert--info`, `.alerts-stack` |
|
||||
| `public/assets/css/app.css` | Rebuilt | Wygenerowany z SCSS (`npx sass --style=compressed`) |
|
||||
| `public/assets/css/login.css` | Rebuilt | Wygenerowany z SCSS |
|
||||
| `src/Core/Support/Flash.php` | Modified | `push()`, `all()`, `inferTypeFromKey()` (private); BC dla `set/get` |
|
||||
| `resources/views/layouts/app.php` | Modified | Central flash render + `alert-dismiss.js` script tag |
|
||||
| `resources/views/layouts/auth.php` | Modified | Central flash render + `alert-dismiss.js` script tag |
|
||||
| `resources/views/layouts/public.php` | Modified | Central flash render + `alert-dismiss.js` script tag |
|
||||
| `resources/views/settings/fakturownia.php` | Modified | Inline alerts → component (bug reporter) |
|
||||
| `resources/views/settings/{hostedsms,smsplanet,inpost,apaczka,allegro,company,cron,database,delivery-statuses,delivery-status-form,email-mailboxes,email-templates,email-templates-form,integrations,printing,project-mappings,shoppro,statuses,accounting,accounting-receipts,accounting-receipt-edit,accounting-invoices,accounting-invoice-edit}.php` | Modified | Inline alerts → component (23 plikow) |
|
||||
| `resources/views/orders/{list,show,receipt-create}.php` | Modified | Inline alerts + `.flash--*` → component (3 pliki) |
|
||||
| `resources/views/shipments/prepare.php` | Modified | 4× `.flash--error` → component |
|
||||
| `resources/views/accounting/invoice_form.php` | Modified | Inline alerts + warning HTML → component z `$messageHtml` |
|
||||
| `resources/views/automation/{index,form}.php` | Modified | Inline alerts → component |
|
||||
| `resources/views/users/index.php` | Modified | Inline alerts → component |
|
||||
| `resources/views/statistics/orders.php` | Modified | Debug warning HTML → component z `$messageHtml` |
|
||||
| `resources/views/auth/login.php` | Modified | `.alert--danger login-alert` → component (`flash--error` flow) |
|
||||
| `.paul/codebase/architecture.md` | Modified | Sekcja "Phase 120 — Alert Component Unification" |
|
||||
| `.paul/codebase/tech_changelog.md` | Modified | Wpis dla Phase 120 |
|
||||
|
||||
## Decisions Made
|
||||
|
||||
| Decision | Rationale | Impact |
|
||||
|----------|-----------|--------|
|
||||
| Component przez `include` z extracted locals zamiast template helper | Zero infrastruktury, BC z istniejacym wzorcem `<?php include ... ?>` w widokach | Latwa migracja, brak nowych zaleznosci |
|
||||
| Kontrolery NIE zmieniane (BC `Flash::set/get`) | Plan boundary: DO NOT CHANGE controllers. Migracja widokow wystarcza, central renderer obsluguje przyszle `Flash::push()` | Zachowany dotychczasowy flow flash z pre-fetchem w kontrolerze |
|
||||
| `messageHtml` jako trusted opt-in (4 widoki: invoice_form, receipt-create, printing, statistics/orders) | `<ul>`/`<pre>`/`<strong>` content wymaga HTML — alternatywa byloby budowanie list w kontrolerze co rozmywa odpowiedzialnosc | Component obsluguje zarowno simple text jak i markup wewnetrzny |
|
||||
| Heurystyka klucza legacy w `Flash::all()` | Migracja kontrolerow odlozona; klucze `module.save/error/test` daja sygnal typu | Operator widzi poprawny kolor bez zmian w kontrolerach |
|
||||
| `unset($messageHtml)` po kazdym uzyciu | PHP `include` widzi zmienne z scope kontekstu; bez unsetu kolejny `include` w tym samym widoku falszywie wykrywa `isset($messageHtml)` | Kazde wywolanie komponentu jest izolowane |
|
||||
| CSS rebuild wykonany inline (Phase 120) | Plan boundary mowil "build poza zakresem"; uzytkownik poprosil explicit | `app.css` i `login.css` aktualne, `.alert--info` zweryfikowany w skompilowanym CSS |
|
||||
|
||||
## Deviations from Plan
|
||||
|
||||
### Summary
|
||||
|
||||
| Type | Count | Impact |
|
||||
|------|-------|--------|
|
||||
| Auto-fixed | 2 | Eliminacja `.flash--error` z 2 dodatkowych widokow (out-of-list) |
|
||||
| Scope additions | 1 | CSS rebuild zrobiony inline na zyczenie uzytkownika |
|
||||
| Deferred | 2 | Migracja JS alerts w email-mailboxes; usuniecie `.flash--*` z SCSS |
|
||||
|
||||
**Total impact:** Essential fixes, no scope creep. Plan AC-3 wymagal `flash--error` = 0 — dodatkowe 2 pliki musialy zostac zmigrowane.
|
||||
|
||||
### Auto-fixed Issues
|
||||
|
||||
**1. [Coverage] `orders/show.php` i `shipments/prepare.php` z `.flash--error/success`**
|
||||
- **Found during:** Task 3 final verify (`grep 'flash--' resources/views/`)
|
||||
- **Issue:** Plan files_modified wymienial 34 widoki + login.php, ale grep ujawnil dodatkowe 2 widoki uzywajace starego `.flash--*` wzorca (5 wystapien lacznie).
|
||||
- **Fix:** Zmigrowane na komponent `alert.php` z `$type='danger'/'success'`.
|
||||
- **Files:** `resources/views/orders/show.php`, `resources/views/shipments/prepare.php`.
|
||||
- **Verification:** `grep 'flash--' resources/views/` → 0.
|
||||
|
||||
**2. [Build] CSS rebuild via `npx sass`**
|
||||
- **Found during:** Po Task 3, na zyczenie uzytkownika ("Przebuduj css")
|
||||
- **Issue:** Plan boundary mowil "build SCSS poza zakresem"; bez rebuildu `.alert--info` nie zadzialalby w przegladarce.
|
||||
- **Fix:** `npx --yes sass --style=compressed --no-source-map resources/scss/{app,login}.scss public/assets/css/{app,login}.css`.
|
||||
- **Files:** `public/assets/css/app.css` (63 560 B), `public/assets/css/login.css` (7 409 B).
|
||||
- **Verification:** `grep 'alert--info' app.css` → `border-color:#bfdbfe;background:#eff6ff;color:#1e3a8a`.
|
||||
|
||||
### Deferred Items
|
||||
|
||||
- **ALERT-JS-MIGRATION-120**: `resources/views/settings/email-mailboxes.php` linie 398/400/406 — JS-generowane alerty AJAX testu SMTP (`resultDiv.className = 'mt-12 alert alert--success'`). Uzywaja unified `.alert` SCSS, ale nie maja markupu komponentu (brak ikony/dismiss). Migracja wymagalaby JS template helpera — out of scope (discovered w Task 3).
|
||||
- **FLASH-SCSS-CLEANUP-120**: `.flash--error/success/warning` w `_ui-components.scss` (jezeli istnieja) — nieuzywane po Phase 120, ale plan boundary explicit zostawia. Usuniecie w przyszlej fazie cleanup.
|
||||
|
||||
## Issues Encountered
|
||||
|
||||
| Issue | Resolution |
|
||||
|-------|------------|
|
||||
| `$messageHtml` persiste po pierwszym include w widoku — kolejne include widzialy `isset($messageHtml)` jako true | `unset($messageHtml)` po kazdym wywolaniu komponentu z `$messageHtml` (4 widoki) |
|
||||
| `npm run build:css` failed — `sass` not in PATH | Uzyto `npx --yes sass` — pobiera deklaracje z `devDependencies` package.json |
|
||||
|
||||
## Next Phase Readiness
|
||||
|
||||
**Ready:**
|
||||
- Komponent alert dostepny dla wszystkich przyszlych widokow — wzorzec `<?php $type=...; $message=...; $dismissible=...; include dirname(__DIR__) . '/components/alert.php'; ?>`.
|
||||
- `Flash::push($type, $message)` dostepny dla kontrolerow ktore chca dolaczyc typowane flash entries bez magic keys.
|
||||
- Central renderer w layoutach — dowolny nowy `Flash::push()` (lub `Flash::set()`) automatycznie renderuje sie u gory strony.
|
||||
- CSS aktualny — operator moze odpalic smoke testy bez dalszych krokow build.
|
||||
|
||||
**Concerns:**
|
||||
- Kontrolery dalej mieszaja `Flash::set('module.key', '...')` z konwencja key-based — przy migracji ku `Flash::push()` heurystyka klucza staje sie redundantna. Mozna zmigrowac stopniowo w kolejnych fazach.
|
||||
- JS alerty w email-mailboxes.php beda wygladaly nieco inaczej (bez ikony/dismiss) niz PHP-renderowane — nieuwage UX, ale spojnosc wizualna 95%.
|
||||
|
||||
**Blockers:** None.
|
||||
|
||||
---
|
||||
*Phase: 120-alert-component-unification, Plan: 01*
|
||||
*Completed: 2026-05-12*
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
18
public/assets/js/modules/alert-dismiss.js
Normal file
18
public/assets/js/modules/alert-dismiss.js
Normal file
@@ -0,0 +1,18 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
if (window.__alertDismissBound) {
|
||||
return;
|
||||
}
|
||||
window.__alertDismissBound = true;
|
||||
|
||||
document.addEventListener('click', function (event) {
|
||||
var btn = event.target.closest('[data-alert-dismiss]');
|
||||
if (!btn) {
|
||||
return;
|
||||
}
|
||||
var alert = btn.closest('[data-alert]');
|
||||
if (alert && alert.parentNode) {
|
||||
alert.parentNode.removeChild(alert);
|
||||
}
|
||||
});
|
||||
})();
|
||||
@@ -140,11 +140,67 @@
|
||||
}
|
||||
|
||||
.alert {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 10px;
|
||||
padding: 12px 14px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid transparent;
|
||||
font-size: 13px;
|
||||
min-height: 44px;
|
||||
line-height: 1.4;
|
||||
|
||||
&__icon {
|
||||
flex: 0 0 18px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
line-height: 1;
|
||||
color: inherit;
|
||||
|
||||
svg {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
&__body {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
&__dismiss {
|
||||
flex: 0 0 auto;
|
||||
margin-left: auto;
|
||||
align-self: flex-start;
|
||||
background: transparent;
|
||||
border: 0;
|
||||
padding: 2px 6px;
|
||||
cursor: pointer;
|
||||
color: inherit;
|
||||
opacity: 0.55;
|
||||
font-size: 16px;
|
||||
line-height: 1;
|
||||
border-radius: 4px;
|
||||
transition: opacity 0.15s ease, background-color 0.15s ease;
|
||||
|
||||
&:hover,
|
||||
&:focus-visible {
|
||||
opacity: 1;
|
||||
background-color: rgba(0, 0, 0, 0.06);
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.alert--info {
|
||||
border-color: #bfdbfe;
|
||||
background: #eff6ff;
|
||||
color: #1e3a8a;
|
||||
}
|
||||
|
||||
.alert--danger {
|
||||
@@ -165,6 +221,13 @@
|
||||
color: #815500;
|
||||
}
|
||||
|
||||
.alerts-stack {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.form-field {
|
||||
display: grid;
|
||||
gap: 5px;
|
||||
|
||||
@@ -30,11 +30,13 @@ $buyerEmailDefault = trim((string) ($buyerAddr['email'] ?? $orderRow['buyer_emai
|
||||
</div>
|
||||
|
||||
<?php if ($errorMsg !== ''): ?>
|
||||
<div class="alert alert--danger mt-12"><?= $e($errorMsg) ?></div>
|
||||
<div class="mt-12"><?php $type='danger'; $message=(string) $errorMsg; $dismissible=true; include dirname(__DIR__) . '/components/alert.php'; ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($hasExistingInvoices): ?>
|
||||
<div class="alert alert--warning mt-12">
|
||||
<?php
|
||||
ob_start();
|
||||
?>
|
||||
<strong>Uwaga!</strong> Do tego zamowienia wystawiono juz <?= $e((string) count($existingInvoicesList)) ?> fakture/y:
|
||||
<ul class="mt-4">
|
||||
<?php foreach ($existingInvoicesList as $ei): ?>
|
||||
@@ -46,7 +48,10 @@ $buyerEmailDefault = trim((string) ($buyerAddr['email'] ?? $orderRow['buyer_emai
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
</div>
|
||||
<?php
|
||||
$existingInvoicesHtml = ob_get_clean();
|
||||
?>
|
||||
<div class="mt-12"><?php $type='warning'; $messageHtml = $existingInvoicesHtml; $dismissible=true; include dirname(__DIR__) . '/components/alert.php'; unset($messageHtml); ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<form id="invoice-create-form" method="post" action="/orders/<?= $e((string) $orderIdVal) ?>/invoice/store" class="mt-16">
|
||||
|
||||
@@ -6,9 +6,7 @@
|
||||
</header>
|
||||
|
||||
<?php if (!empty($errorMessage)): ?>
|
||||
<div class="alert alert--danger login-alert" role="alert">
|
||||
<?= $e($errorMessage) ?>
|
||||
</div>
|
||||
<div class="login-alert"><?php $type='danger'; $message=(string) $errorMessage; $dismissible=true; include dirname(__DIR__) . '/components/alert.php'; ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<form class="login-form" action="/login" method="post" novalidate>
|
||||
|
||||
@@ -41,7 +41,7 @@ $orderStatusOptions = is_array($orderStatusOptions ?? null) ? $orderStatusOption
|
||||
<h2 class="section-title"><?= $isEdit ? 'Edytuj zadanie automatyczne' : 'Nowe zadanie automatyczne' ?></h2>
|
||||
|
||||
<?php if (!empty($errorMessage)): ?>
|
||||
<div class="alert alert--danger mt-12" role="alert"><?= $e((string) $errorMessage) ?></div>
|
||||
<div class="mt-12"><?php $type='danger'; $message=(string) $errorMessage; $dismissible=true; include dirname(__DIR__) . '/components/alert.php'; ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<form action="<?= $isEdit ? '/settings/automation/update' : '/settings/automation/store' ?>" method="post" novalidate class="mt-12" id="js-automation-form">
|
||||
|
||||
@@ -56,10 +56,10 @@ $buildHistoryUrl = static function (array $overrides = []) use ($historyFiltersD
|
||||
<p class="muted mt-8">Reguly automatyzacji wykonywane po wystapieniu zdarzenia.</p>
|
||||
|
||||
<?php if (!empty($errorMessage)): ?>
|
||||
<div class="alert alert--danger mt-12" role="alert"><?= $e((string) $errorMessage) ?></div>
|
||||
<div class="mt-12"><?php $type='danger'; $message=(string) $errorMessage; $dismissible=true; include dirname(__DIR__) . '/components/alert.php'; ?></div>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($successMessage)): ?>
|
||||
<div class="alert alert--success mt-12" role="status"><?= $e((string) $successMessage) ?></div>
|
||||
<div class="mt-12"><?php $type='success'; $message=(string) $successMessage; $dismissible=true; include dirname(__DIR__) . '/components/alert.php'; ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<nav class="content-tabs-nav mt-12" aria-label="Zakladki automatyzacji">
|
||||
|
||||
36
resources/views/components/alert.php
Normal file
36
resources/views/components/alert.php
Normal file
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
/**
|
||||
* Reusable alert component.
|
||||
*
|
||||
* Required scope vars:
|
||||
* - $e : escape callable from template engine
|
||||
* - $type : string (info|success|warning|danger), default 'info'
|
||||
* - $message : string, escaped via $e()
|
||||
* Optional:
|
||||
* - $messageHtml : trusted HTML payload (used INSTEAD of $message when set)
|
||||
* - $dismissible : bool, default true
|
||||
* - $role : 'alert' (default) or 'status'
|
||||
*/
|
||||
|
||||
$allowedTypes = ['info', 'success', 'warning', 'danger'];
|
||||
$alertType = isset($type) && in_array($type, $allowedTypes, true) ? $type : 'info';
|
||||
|
||||
$alertMessage = isset($message) ? (string) $message : '';
|
||||
$alertMessageRaw = isset($messageHtml) ? (string) $messageHtml : null;
|
||||
$alertDismissible = !isset($dismissible) || (bool) $dismissible;
|
||||
$alertRole = (isset($role) && $role === 'status') ? 'status' : 'alert';
|
||||
|
||||
$alertIcons = [
|
||||
'info' => '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="10"/><line x1="12" y1="16" x2="12" y2="12"/><line x1="12" y1="8" x2="12.01" y2="8"/></svg>',
|
||||
'success' => '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/></svg>',
|
||||
'warning' => '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>',
|
||||
'danger' => '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="10"/><line x1="15" y1="9" x2="9" y2="15"/><line x1="9" y1="9" x2="15" y2="15"/></svg>',
|
||||
];
|
||||
?>
|
||||
<div class="alert alert--<?= $e($alertType) ?>" role="<?= $e($alertRole) ?>" data-alert>
|
||||
<span class="alert__icon" aria-hidden="true"><?= $alertIcons[$alertType] ?></span>
|
||||
<div class="alert__body"><?php if ($alertMessageRaw !== null): ?><?= $alertMessageRaw ?><?php else: ?><?= $e($alertMessage) ?><?php endif; ?></div>
|
||||
<?php if ($alertDismissible): ?>
|
||||
<button type="button" class="alert__dismiss" data-alert-dismiss aria-label="Zamknij">×</button>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
@@ -184,6 +184,17 @@
|
||||
</header>
|
||||
|
||||
<main class="container">
|
||||
<?php $flashEntries = \App\Core\Support\Flash::all(); ?>
|
||||
<?php if (!empty($flashEntries)): ?>
|
||||
<div class="alerts-stack" data-flash-stack>
|
||||
<?php foreach ($flashEntries as $flashEntry): ?>
|
||||
<?php $type = (string) ($flashEntry['type'] ?? 'info'); ?>
|
||||
<?php $message = (string) ($flashEntry['message'] ?? ''); ?>
|
||||
<?php $dismissible = true; ?>
|
||||
<?php include __DIR__ . '/../components/alert.php'; ?>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?= $content ?>
|
||||
</main>
|
||||
</div>
|
||||
@@ -194,6 +205,7 @@
|
||||
<script src="/assets/js/modules/invoice-config-form.js?ver=<?= filemtime(dirname(__DIR__, 3) . '/public/assets/js/modules/invoice-config-form.js') ?: 0 ?>"></script>
|
||||
<script src="/assets/js/modules/invoice-requested-toggle.js?ver=<?= filemtime(dirname(__DIR__, 3) . '/public/assets/js/modules/invoice-requested-toggle.js') ?: 0 ?>"></script>
|
||||
<script src="/assets/js/modules/confirm-delete.js?ver=<?= filemtime(dirname(__DIR__, 3) . '/public/assets/js/modules/confirm-delete.js') ?: 0 ?>"></script>
|
||||
<script src="/assets/js/modules/alert-dismiss.js?ver=<?= filemtime(dirname(__DIR__, 3) . '/public/assets/js/modules/alert-dismiss.js') ?: 0 ?>"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.8/dist/chart.umd.min.js"></script>
|
||||
<script src="/assets/js/modules/statistics-summary-charts.js?ver=<?= filemtime(dirname(__DIR__, 3) . '/public/assets/js/modules/statistics-summary-charts.js') ?: 0 ?>"></script>
|
||||
<script>
|
||||
|
||||
@@ -14,7 +14,19 @@
|
||||
<div class="bg-orb bg-orb-right" aria-hidden="true"></div>
|
||||
|
||||
<main class="login-page">
|
||||
<?php $flashEntries = \App\Core\Support\Flash::all(); ?>
|
||||
<?php if (!empty($flashEntries)): ?>
|
||||
<div class="alerts-stack" data-flash-stack>
|
||||
<?php foreach ($flashEntries as $flashEntry): ?>
|
||||
<?php $type = (string) ($flashEntry['type'] ?? 'info'); ?>
|
||||
<?php $message = (string) ($flashEntry['message'] ?? ''); ?>
|
||||
<?php $dismissible = true; ?>
|
||||
<?php include __DIR__ . '/../components/alert.php'; ?>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?= $content ?>
|
||||
</main>
|
||||
<script src="/assets/js/modules/alert-dismiss.js?ver=<?= filemtime(dirname(__DIR__, 3) . '/public/assets/js/modules/alert-dismiss.js') ?: 0 ?>"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -28,7 +28,19 @@
|
||||
</head>
|
||||
<body>
|
||||
<main class="public-page">
|
||||
<?php $flashEntries = \App\Core\Support\Flash::all(); ?>
|
||||
<?php if (!empty($flashEntries)): ?>
|
||||
<div class="alerts-stack" data-flash-stack>
|
||||
<?php foreach ($flashEntries as $flashEntry): ?>
|
||||
<?php $type = (string) ($flashEntry['type'] ?? 'info'); ?>
|
||||
<?php $message = (string) ($flashEntry['message'] ?? ''); ?>
|
||||
<?php $dismissible = true; ?>
|
||||
<?php include __DIR__ . '/../components/alert.php'; ?>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?= $content ?>
|
||||
</main>
|
||||
<script src="/assets/js/modules/alert-dismiss.js?ver=<?= filemtime(dirname(__DIR__, 3) . '/public/assets/js/modules/alert-dismiss.js') ?: 0 ?>"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -13,9 +13,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<?php if (!empty($errorMessage)): ?>
|
||||
<div class="alert alert--warning mt-12" role="alert">
|
||||
<?= $e((string) $errorMessage) ?>
|
||||
</div>
|
||||
<div class="mt-12"><?php $type='warning'; $message=(string) $errorMessage; $dismissible=true; include dirname(__DIR__) . '/components/alert.php'; ?></div>
|
||||
<?php endif; ?>
|
||||
</section>
|
||||
|
||||
|
||||
@@ -22,7 +22,9 @@ $hasExistingReceipts = $existingReceiptsList !== [];
|
||||
</div>
|
||||
|
||||
<?php if ($hasExistingReceipts): ?>
|
||||
<div class="alert alert--warning mt-12">
|
||||
<?php
|
||||
ob_start();
|
||||
?>
|
||||
<strong>Uwaga!</strong> Do tego zamówienia wystawiono już <?= $e((string) count($existingReceiptsList)) ?> paragon(ów):
|
||||
<ul class="mt-4">
|
||||
<?php foreach ($existingReceiptsList as $er): ?>
|
||||
@@ -35,7 +37,10 @@ $hasExistingReceipts = $existingReceiptsList !== [];
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
</div>
|
||||
<?php
|
||||
$existingReceiptsHtml = ob_get_clean();
|
||||
?>
|
||||
<div class="mt-12"><?php $type='warning'; $messageHtml = $existingReceiptsHtml; $dismissible=true; include dirname(__DIR__) . '/components/alert.php'; unset($messageHtml); ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<form id="receipt-create-form" method="post" action="/orders/<?= $e((string) $orderIdVal) ?>/receipt/store" class="mt-16">
|
||||
|
||||
@@ -134,10 +134,10 @@ foreach ($addressesList as $address) {
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php if ($flashSuccessMsg !== ''): ?>
|
||||
<div class="flash flash--success mt-12"><?= $e($flashSuccessMsg) ?></div>
|
||||
<div class="mt-12"><?php $type='success'; $message=(string) $flashSuccessMsg; $dismissible=true; include dirname(__DIR__) . '/components/alert.php'; ?></div>
|
||||
<?php endif; ?>
|
||||
<?php if ($flashErrorMsg !== ''): ?>
|
||||
<div class="flash flash--error mt-12"><?= $e($flashErrorMsg) ?></div>
|
||||
<div class="mt-12"><?php $type='danger'; $message=(string) $flashErrorMsg; $dismissible=true; include dirname(__DIR__) . '/components/alert.php'; ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="order-status-change mt-12">
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?php
|
||||
/** @var array<string, mixed>|null $config */
|
||||
$config = is_array($config ?? null) ? $config : null;
|
||||
$accounts = is_array($fakturowniaAccounts ?? null) ? $fakturowniaAccounts : [];
|
||||
$fakturowniaSettings = is_array($fakturowniaSettings ?? null) ? $fakturowniaSettings : [];
|
||||
$isEdit = $config !== null;
|
||||
$cid = (int) ($config['id'] ?? 0);
|
||||
|
||||
@@ -13,8 +13,10 @@ $orderReference = (string) ($config['order_reference'] ?? 'none');
|
||||
$paymentToDays = (int) ($config['payment_to_days'] ?? 7);
|
||||
$defaultKind = (string) ($config['default_kind'] ?? 'vat');
|
||||
$isDelegated = ((int) ($config['is_delegated'] ?? 0)) === 1;
|
||||
$integrationId = (int) ($config['integration_id'] ?? 0);
|
||||
$isActive = $isEdit ? ((int) ($config['is_active'] ?? 0)) === 1 : true;
|
||||
$fakturowniaConfigured = trim((string) ($fakturowniaSettings['account_prefix'] ?? '')) !== ''
|
||||
&& !empty($fakturowniaSettings['has_api_token']);
|
||||
$fakturowniaActive = !empty($fakturowniaSettings['is_active']);
|
||||
|
||||
$success = trim((string) ($successMessage ?? ''));
|
||||
$error = trim((string) ($errorMessage ?? ''));
|
||||
@@ -25,10 +27,10 @@ $error = trim((string) ($errorMessage ?? ''));
|
||||
<h2 class="section-title"><?= $isEdit ? 'Edycja konfiguracji faktury' : 'Nowa konfiguracja faktury' ?></h2>
|
||||
|
||||
<?php if ($error !== ''): ?>
|
||||
<div class="alert alert--danger mt-12" role="alert"><?= $e($error) ?></div>
|
||||
<div class="mt-12"><?php $type='danger'; $message=(string) $error; $dismissible=true; include dirname(__DIR__) . '/components/alert.php'; ?></div>
|
||||
<?php endif; ?>
|
||||
<?php if ($success !== ''): ?>
|
||||
<div class="alert alert--success mt-12" role="status"><?= $e($success) ?></div>
|
||||
<div class="mt-12"><?php $type='success'; $message=(string) $success; $dismissible=true; include dirname(__DIR__) . '/components/alert.php'; ?></div>
|
||||
<?php endif; ?>
|
||||
</section>
|
||||
|
||||
@@ -105,24 +107,15 @@ $error = trim((string) ($errorMessage ?? ''));
|
||||
</div>
|
||||
|
||||
<div class="form-field mt-12" data-invoice-delegation<?= $isDelegated ? '' : ' style="display:none"' ?>>
|
||||
<label class="form-field">
|
||||
<span class="field-label">Konto Fakturowni *</span>
|
||||
<select class="form-control" name="integration_id"<?= $isDelegated ? ' required' : '' ?>>
|
||||
<option value="">— wybierz konto —</option>
|
||||
<?php foreach ($accounts as $acc):
|
||||
$aid = (int) ($acc['integration_id'] ?? 0);
|
||||
$accName = (string) ($acc['name'] ?? '');
|
||||
$accPrefix = (string) ($acc['account_prefix'] ?? '');
|
||||
?>
|
||||
<option value="<?= $aid ?>"<?= $aid === $integrationId ? ' selected' : '' ?>>
|
||||
<?= $e($accName) ?><?= $accPrefix !== '' ? ' (' . $e($accPrefix) . '.fakturownia.pl)' : '' ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<?php if ($accounts === []): ?>
|
||||
<small class="field-hint">Brak skonfigurowanych kont Fakturowni. <a href="/settings/integrations/fakturownia/new">Dodaj konto</a> aby moc delegowac.</small>
|
||||
<?php endif; ?>
|
||||
</label>
|
||||
<span class="field-label">Konto Fakturowni</span>
|
||||
<?php if ($fakturowniaConfigured && $fakturowniaActive): ?>
|
||||
<span class="badge badge--success">Globalna konfiguracja aktywna</span>
|
||||
<?php elseif ($fakturowniaConfigured): ?>
|
||||
<span class="badge badge--muted">Globalna konfiguracja nieaktywna</span>
|
||||
<?php else: ?>
|
||||
<span class="badge badge--muted">Brak konfiguracji</span>
|
||||
<?php endif; ?>
|
||||
<small class="field-hint">Delegowane faktury uzywaja jednej globalnej konfiguracji z <a href="/settings/integrations/fakturownia">Integracje > Fakturownia</a>.</small>
|
||||
</div>
|
||||
|
||||
<div class="form-actions mt-16">
|
||||
|
||||
@@ -11,10 +11,10 @@ $error = trim((string) ($errorMessage ?? ''));
|
||||
<p class="muted mt-12">Zarzadzaj konfiguracjami wystawiania faktur. Mozesz dodac wiele konfiguracji (np. dla roznych dzialalnosci) i opcjonalnie delegowac wystawianie do Fakturowni.</p>
|
||||
|
||||
<?php if ($error !== ''): ?>
|
||||
<div class="alert alert--danger mt-12" role="alert"><?= $e($error) ?></div>
|
||||
<div class="mt-12"><?php $type='danger'; $message=(string) $error; $dismissible=true; include dirname(__DIR__) . '/components/alert.php'; ?></div>
|
||||
<?php endif; ?>
|
||||
<?php if ($success !== ''): ?>
|
||||
<div class="alert alert--success mt-12" role="status"><?= $e($success) ?></div>
|
||||
<div class="mt-12"><?php $type='success'; $message=(string) $success; $dismissible=true; include dirname(__DIR__) . '/components/alert.php'; ?></div>
|
||||
<?php endif; ?>
|
||||
</section>
|
||||
|
||||
|
||||
@@ -21,10 +21,10 @@ $error = trim((string) ($errorMessage ?? ''));
|
||||
<h2 class="section-title"><?= $isEdit ? 'Edycja konfiguracji paragonu' : 'Nowa konfiguracja paragonu' ?></h2>
|
||||
|
||||
<?php if ($error !== ''): ?>
|
||||
<div class="alert alert--danger mt-12" role="alert"><?= $e($error) ?></div>
|
||||
<div class="mt-12"><?php $type='danger'; $message=(string) $error; $dismissible=true; include dirname(__DIR__) . '/components/alert.php'; ?></div>
|
||||
<?php endif; ?>
|
||||
<?php if ($success !== ''): ?>
|
||||
<div class="alert alert--success mt-12" role="status"><?= $e($success) ?></div>
|
||||
<div class="mt-12"><?php $type='success'; $message=(string) $success; $dismissible=true; include dirname(__DIR__) . '/components/alert.php'; ?></div>
|
||||
<?php endif; ?>
|
||||
</section>
|
||||
|
||||
|
||||
@@ -11,10 +11,10 @@ $error = trim((string) ($errorMessage ?? ''));
|
||||
<p class="muted mt-12">Zarzadzaj konfiguracjami wystawiania paragonow.</p>
|
||||
|
||||
<?php if ($error !== ''): ?>
|
||||
<div class="alert alert--danger mt-12" role="alert"><?= $e($error) ?></div>
|
||||
<div class="mt-12"><?php $type='danger'; $message=(string) $error; $dismissible=true; include dirname(__DIR__) . '/components/alert.php'; ?></div>
|
||||
<?php endif; ?>
|
||||
<?php if ($success !== ''): ?>
|
||||
<div class="alert alert--success mt-12" role="status"><?= $e($success) ?></div>
|
||||
<div class="mt-12"><?php $type='success'; $message=(string) $success; $dismissible=true; include dirname(__DIR__) . '/components/alert.php'; ?></div>
|
||||
<?php endif; ?>
|
||||
</section>
|
||||
|
||||
|
||||
@@ -8,10 +8,10 @@ $error = trim((string) ($errorMessage ?? ''));
|
||||
<p class="muted mt-12">Wybierz typ dokumentu ktorego konfiguracje chcesz zarzadzac.</p>
|
||||
|
||||
<?php if ($error !== ''): ?>
|
||||
<div class="alert alert--danger mt-12" role="alert"><?= $e($error) ?></div>
|
||||
<div class="mt-12"><?php $type='danger'; $message=(string) $error; $dismissible=true; include dirname(__DIR__) . '/components/alert.php'; ?></div>
|
||||
<?php endif; ?>
|
||||
<?php if ($success !== ''): ?>
|
||||
<div class="alert alert--success mt-12" role="status"><?= $e($success) ?></div>
|
||||
<div class="mt-12"><?php $type='success'; $message=(string) $success; $dismissible=true; include dirname(__DIR__) . '/components/alert.php'; ?></div>
|
||||
<?php endif; ?>
|
||||
</section>
|
||||
|
||||
|
||||
@@ -40,15 +40,15 @@ foreach ($pullStatusMappings as $pm) {
|
||||
<p class="muted mt-12"><?= $e($t('settings.allegro.description')) ?></p>
|
||||
|
||||
<?php if (!empty($errorMessage)): ?>
|
||||
<div class="alert alert--danger mt-12" role="alert"><?= $e((string) $errorMessage) ?></div>
|
||||
<div class="mt-12"><?php $type='danger'; $message=(string) $errorMessage; $dismissible=true; include dirname(__DIR__) . '/components/alert.php'; ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($successMessage)): ?>
|
||||
<div class="alert alert--success mt-12" role="status"><?= $e((string) $successMessage) ?></div>
|
||||
<div class="mt-12"><?php $type='success'; $message=(string) $successMessage; $dismissible=true; include dirname(__DIR__) . '/components/alert.php'; ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($warningMessage)): ?>
|
||||
<div class="alert alert--warning mt-12" role="alert"><?= $e((string) $warningMessage) ?></div>
|
||||
<div class="mt-12"><?php $type='warning'; $message=(string) $warningMessage; $dismissible=true; include dirname(__DIR__) . '/components/alert.php'; ?></div>
|
||||
<?php endif; ?>
|
||||
</section>
|
||||
|
||||
@@ -359,7 +359,7 @@ foreach ($pullStatusMappings as $pm) {
|
||||
<p class="muted mt-12"><?= $e($t('settings.allegro.delivery.description')) ?></p>
|
||||
|
||||
<?php if ($dmServicesError !== ''): ?>
|
||||
<div class="alert alert--danger mt-12"><?= $e($dmServicesError) ?></div>
|
||||
<div class="mt-12"><?php $type='danger'; $message=(string) $dmServicesError; $dismissible=true; include dirname(__DIR__) . '/components/alert.php'; ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($dmOrderMethods === []): ?>
|
||||
|
||||
@@ -11,11 +11,11 @@ $updatedAt = trim((string) ($integration['updated_at'] ?? ''));
|
||||
<p class="muted mt-12"><?= $e($t('settings.apaczka.description')) ?></p>
|
||||
|
||||
<?php if (!empty($errorMessage)): ?>
|
||||
<div class="alert alert--danger mt-12" role="alert"><?= $e((string) $errorMessage) ?></div>
|
||||
<div class="mt-12"><?php $type='danger'; $message=(string) $errorMessage; $dismissible=true; include dirname(__DIR__) . '/components/alert.php'; ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($successMessage)): ?>
|
||||
<div class="alert alert--success mt-12" role="status"><?= $e((string) $successMessage) ?></div>
|
||||
<div class="mt-12"><?php $type='success'; $message=(string) $successMessage; $dismissible=true; include dirname(__DIR__) . '/components/alert.php'; ?></div>
|
||||
<?php endif; ?>
|
||||
</section>
|
||||
|
||||
|
||||
@@ -7,10 +7,10 @@ $s = is_array($settings ?? null) ? $settings : [];
|
||||
<p class="muted mt-12"><?= $e($t('settings.company.description')) ?></p>
|
||||
|
||||
<?php if (!empty($errorMessage)): ?>
|
||||
<div class="alert alert--danger mt-12" role="alert"><?= $e((string) $errorMessage) ?></div>
|
||||
<div class="mt-12"><?php $type='danger'; $message=(string) $errorMessage; $dismissible=true; include dirname(__DIR__) . '/components/alert.php'; ?></div>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($successMessage)): ?>
|
||||
<div class="alert alert--success mt-12" role="status"><?= $e((string) $successMessage) ?></div>
|
||||
<div class="mt-12"><?php $type='success'; $message=(string) $successMessage; $dismissible=true; include dirname(__DIR__) . '/components/alert.php'; ?></div>
|
||||
<?php endif; ?>
|
||||
</section>
|
||||
|
||||
|
||||
@@ -12,11 +12,11 @@ $pastTotal = max(0, (int) ($pastPagination['total'] ?? 0));
|
||||
<h2 class="section-title"><?= $e($t('settings.cron.title')) ?></h2>
|
||||
|
||||
<?php if (!empty($errorMessage)): ?>
|
||||
<div class="alert alert--danger mt-12" role="alert"><?= $e((string) $errorMessage) ?></div>
|
||||
<div class="mt-12"><?php $type='danger'; $message=(string) $errorMessage; $dismissible=true; include dirname(__DIR__) . '/components/alert.php'; ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($successMessage)): ?>
|
||||
<div class="alert alert--success mt-12" role="status"><?= $e((string) $successMessage) ?></div>
|
||||
<div class="mt-12"><?php $type='success'; $message=(string) $successMessage; $dismissible=true; include dirname(__DIR__) . '/components/alert.php'; ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<nav class="content-tabs-nav mt-12" aria-label="Zakładki crona">
|
||||
|
||||
@@ -11,15 +11,11 @@ $logs = (array) ($runLogs ?? []);
|
||||
<h2 class="section-title"><?= $e($t('settings.database.title')) ?></h2>
|
||||
|
||||
<?php if (!empty($errorMessage)): ?>
|
||||
<div class="alert alert--danger mt-12" role="alert">
|
||||
<?= $e((string) $errorMessage) ?>
|
||||
</div>
|
||||
<div class="mt-12"><?php $type='danger'; $message=(string) $errorMessage; $dismissible=true; include dirname(__DIR__) . '/components/alert.php'; ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($successMessage)): ?>
|
||||
<div class="alert alert--success mt-12" role="status">
|
||||
<?= $e((string) $successMessage) ?>
|
||||
</div>
|
||||
<div class="mt-12"><?php $type='success'; $message=(string) $successMessage; $dismissible=true; include dirname(__DIR__) . '/components/alert.php'; ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="settings-grid mt-16">
|
||||
@@ -38,18 +34,14 @@ $logs = (array) ($runLogs ?? []);
|
||||
</div>
|
||||
|
||||
<?php if ($pending > 0): ?>
|
||||
<div class="alert alert--warning mt-16" role="status">
|
||||
<?= $e($t('settings.database.state.needs_update')) ?>
|
||||
</div>
|
||||
<div class="mt-16"><?php $type='warning'; $message=(string) $t('settings.database.state.needs_update'); $dismissible=false; include dirname(__DIR__) . '/components/alert.php'; ?></div>
|
||||
|
||||
<form class="mt-16" action="/settings/database/migrate" method="post">
|
||||
<input type="hidden" name="_token" value="<?= $e($csrfToken ?? '') ?>">
|
||||
<button type="submit" class="btn btn--primary"><?= $e($t('settings.database.actions.run_update')) ?></button>
|
||||
</form>
|
||||
<?php else: ?>
|
||||
<div class="alert alert--success mt-16" role="status">
|
||||
<?= $e($t('settings.database.state.up_to_date')) ?>
|
||||
</div>
|
||||
<div class="mt-16"><?php $type='success'; $message=(string) $t('settings.database.state.up_to_date'); $dismissible=false; include dirname(__DIR__) . '/components/alert.php'; ?></div>
|
||||
<?php endif; ?>
|
||||
</section>
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ $action = $isEdit
|
||||
<h2 class="section-title"><?= $isEdit ? 'Edytuj status przesyłki' : 'Nowy status przesyłki' ?></h2>
|
||||
|
||||
<?php if ($errorMessage !== ''): ?>
|
||||
<div class="alert alert--danger mt-12" role="alert"><?= $e($errorMessage) ?></div>
|
||||
<div class="mt-12"><?php $type='danger'; $message=(string) $errorMessage; $dismissible=true; include dirname(__DIR__) . '/components/alert.php'; ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<form action="<?= $e($action) ?>" method="post" novalidate class="mt-12">
|
||||
|
||||
@@ -9,10 +9,10 @@ $csrfToken = (string) ($csrfToken ?? '');
|
||||
<section class="card">
|
||||
<h2 class="section-title">Statusy przesyłek</h2>
|
||||
<?php if ($errorMessage !== ''): ?>
|
||||
<div class="alert alert--danger mt-12" role="alert"><?= $e($errorMessage) ?></div>
|
||||
<div class="mt-12"><?php $type='danger'; $message=(string) $errorMessage; $dismissible=true; include dirname(__DIR__) . '/components/alert.php'; ?></div>
|
||||
<?php endif; ?>
|
||||
<?php if ($successMessage !== ''): ?>
|
||||
<div class="alert alert--success mt-12" role="status"><?= $e($successMessage) ?></div>
|
||||
<div class="mt-12"><?php $type='success'; $message=(string) $successMessage; $dismissible=true; include dirname(__DIR__) . '/components/alert.php'; ?></div>
|
||||
<?php endif; ?>
|
||||
</section>
|
||||
|
||||
|
||||
@@ -17,10 +17,10 @@ $isEdit = $em !== null;
|
||||
<p class="muted mt-12">Konfiguracja skrzynek SMTP do wysylki wiadomosci e-mail.</p>
|
||||
|
||||
<?php if (!empty($errorMessage)): ?>
|
||||
<div class="alert alert--danger mt-12" role="alert"><?= $e((string) $errorMessage) ?></div>
|
||||
<div class="mt-12"><?php $type='danger'; $message=(string) $errorMessage; $dismissible=true; include dirname(__DIR__) . '/components/alert.php'; ?></div>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($successMessage)): ?>
|
||||
<div class="alert alert--success mt-12" role="status"><?= $e((string) $successMessage) ?></div>
|
||||
<div class="mt-12"><?php $type='success'; $message=(string) $successMessage; $dismissible=true; include dirname(__DIR__) . '/components/alert.php'; ?></div>
|
||||
<?php endif; ?>
|
||||
</section>
|
||||
|
||||
|
||||
@@ -11,10 +11,10 @@ $attachmentTypes = is_array($attachmentTypes ?? null) ? $attachmentTypes : [];
|
||||
<p class="muted mt-12">Skonfiguruj temat, tresc i zmienne, ktore beda podstawiane podczas wysylki.</p>
|
||||
|
||||
<?php if (!empty($errorMessage)): ?>
|
||||
<div class="alert alert--danger mt-12" role="alert"><?= $e((string) $errorMessage) ?></div>
|
||||
<div class="mt-12"><?php $type='danger'; $message=(string) $errorMessage; $dismissible=true; include dirname(__DIR__) . '/components/alert.php'; ?></div>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($successMessage)): ?>
|
||||
<div class="alert alert--success mt-12" role="status"><?= $e((string) $successMessage) ?></div>
|
||||
<div class="mt-12"><?php $type='success'; $message=(string) $successMessage; $dismissible=true; include dirname(__DIR__) . '/components/alert.php'; ?></div>
|
||||
<?php endif; ?>
|
||||
</section>
|
||||
|
||||
|
||||
@@ -11,10 +11,10 @@ $attachmentTypes = is_array($attachmentTypes ?? null) ? $attachmentTypes : [];
|
||||
<p class="muted mt-12">Szablony wiadomosci e-mail z edytorem i systemem zmiennych.</p>
|
||||
|
||||
<?php if (!empty($errorMessage)): ?>
|
||||
<div class="alert alert--danger mt-12" role="alert"><?= $e((string) $errorMessage) ?></div>
|
||||
<div class="mt-12"><?php $type='danger'; $message=(string) $errorMessage; $dismissible=true; include dirname(__DIR__) . '/components/alert.php'; ?></div>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($successMessage)): ?>
|
||||
<div class="alert alert--success mt-12" role="status"><?= $e((string) $successMessage) ?></div>
|
||||
<div class="mt-12"><?php $type='success'; $message=(string) $successMessage; $dismissible=true; include dirname(__DIR__) . '/components/alert.php'; ?></div>
|
||||
<?php endif; ?>
|
||||
</section>
|
||||
|
||||
|
||||
@@ -1,103 +1,115 @@
|
||||
<?php
|
||||
/** @var array<int, array<string, mixed>> $rows */
|
||||
$rows = is_array($rows ?? null) ? $rows : [];
|
||||
/** @var array<string, mixed> $settings */
|
||||
$settings = is_array($settings ?? null) ? $settings : [];
|
||||
$integrationId = (int) ($settings['integration_id'] ?? 0);
|
||||
$prefix = (string) ($settings['account_prefix'] ?? '');
|
||||
$departmentId = (string) ($settings['department_id'] ?? '');
|
||||
$defaultKind = (string) ($settings['default_kind'] ?? 'vat');
|
||||
$defaultPaymentDays = (int) ($settings['default_payment_to_days'] ?? 7);
|
||||
$isActive = (bool) ($settings['is_active'] ?? true);
|
||||
$hasToken = (bool) ($settings['has_api_token'] ?? false);
|
||||
$lastTestAt = (string) ($settings['last_test_at'] ?? '');
|
||||
$lastTestStatus = (string) ($settings['last_test_status'] ?? '');
|
||||
$lastTestMessage = (string) ($settings['last_test_message'] ?? '');
|
||||
|
||||
$flashSave = trim((string) ($flashSave ?? ''));
|
||||
$flashTest = trim((string) ($flashTest ?? ''));
|
||||
$flashError = trim((string) ($flashError ?? ''));
|
||||
?>
|
||||
|
||||
<section class="card">
|
||||
<h2 class="section-title">Integracje Fakturownia</h2>
|
||||
<p class="muted mt-12">Konfiguracja kont Fakturowni do wystawiania faktur dla zamowien.</p>
|
||||
<h2 class="section-title">Integracja Fakturownia</h2>
|
||||
<p class="muted mt-12">Jedna globalna konfiguracja konta Fakturowni do wystawiania faktur delegowanych.</p>
|
||||
|
||||
<?php if ($flashError !== ''): ?>
|
||||
<div class="alert alert--danger mt-12" role="alert"><?= $e($flashError) ?></div>
|
||||
<div class="mt-12"><?php $type='danger'; $message=$flashError; $dismissible=true; include dirname(__DIR__) . '/components/alert.php'; ?></div>
|
||||
<?php endif; ?>
|
||||
<?php if ($flashSave !== ''): ?>
|
||||
<div class="alert alert--success mt-12" role="status"><?= $e($flashSave) ?></div>
|
||||
<div class="mt-12"><?php $type='success'; $message=$flashSave; $dismissible=true; include dirname(__DIR__) . '/components/alert.php'; ?></div>
|
||||
<?php endif; ?>
|
||||
<?php if ($flashTest !== ''): ?>
|
||||
<div class="alert alert--info mt-12" role="status"><?= $e($flashTest) ?></div>
|
||||
<div class="mt-12"><?php $type='info'; $message=$flashTest; $dismissible=true; include dirname(__DIR__) . '/components/alert.php'; ?></div>
|
||||
<?php endif; ?>
|
||||
</section>
|
||||
|
||||
<section class="card mt-16">
|
||||
<h3 class="section-title">Lista integracji</h3>
|
||||
|
||||
<div class="form-actions mt-12">
|
||||
<a class="btn btn--primary" href="/settings/integrations/fakturownia/new">Dodaj integracje</a>
|
||||
<h3 class="section-title">Konfiguracja</h3>
|
||||
<div class="muted mt-12">
|
||||
Token:
|
||||
<strong><?= $e($hasToken ? 'zapisany' : 'brak') ?></strong>
|
||||
Status:
|
||||
<strong><?= $e($isActive ? 'aktywna' : 'nieaktywna') ?></strong>
|
||||
</div>
|
||||
|
||||
<?php if ($rows === []): ?>
|
||||
<p class="muted mt-12">Brak skonfigurowanych integracji Fakturowni. Dodaj pierwsza ponizej.</p>
|
||||
<?php else: ?>
|
||||
<div class="table-wrap mt-12">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Nazwa</th>
|
||||
<th>Subdomena</th>
|
||||
<th>Token</th>
|
||||
<th>Status</th>
|
||||
<th>Ostatni test</th>
|
||||
<th>Akcje</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($rows as $row):
|
||||
$integrationId = (int) ($row['integration_id'] ?? 0);
|
||||
$name = (string) ($row['name'] ?? '');
|
||||
$prefix = (string) ($row['account_prefix'] ?? '');
|
||||
$hasToken = (bool) ($row['has_api_token'] ?? false);
|
||||
$isActive = (bool) ($row['is_active'] ?? false);
|
||||
$lastTestAt = (string) ($row['last_test_at'] ?? '');
|
||||
$lastTestStatus = (string) ($row['last_test_status'] ?? '');
|
||||
?>
|
||||
<tr>
|
||||
<td><?= $e($name) ?></td>
|
||||
<td>
|
||||
<?php if ($prefix !== ''): ?>
|
||||
<?= $e($prefix . '.fakturownia.pl') ?>
|
||||
<?php else: ?>
|
||||
<span class="muted">brak</span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td>
|
||||
<?php if ($hasToken): ?>
|
||||
<span class="badge badge--success">Zapisany</span>
|
||||
<?php else: ?>
|
||||
<span class="badge badge--muted">Brak</span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td>
|
||||
<?php if ($isActive): ?>
|
||||
<span class="badge badge--success">Aktywna</span>
|
||||
<?php else: ?>
|
||||
<span class="badge badge--muted">Nieaktywna</span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td>
|
||||
<?php if ($lastTestAt !== ''): ?>
|
||||
<?= $e($lastTestAt) ?>
|
||||
<?php if ($lastTestStatus !== ''): ?>
|
||||
<span class="badge badge--<?= $lastTestStatus === 'ok' ? 'success' : 'muted' ?>"><?= $e(strtoupper($lastTestStatus)) ?></span>
|
||||
<?php endif; ?>
|
||||
<?php else: ?>
|
||||
<span class="muted">nigdy</span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td style="white-space:nowrap">
|
||||
<a href="/settings/integrations/fakturownia/edit?id=<?= $integrationId ?>" class="btn btn--sm btn--secondary">Edytuj</a>
|
||||
<form action="/settings/integrations/fakturownia/delete" method="post" style="display:inline" class="js-confirm-delete">
|
||||
<input type="hidden" name="_token" value="<?= $e($csrfToken ?? '') ?>">
|
||||
<input type="hidden" name="id" value="<?= $integrationId ?>">
|
||||
<button type="button" class="btn btn--sm btn--danger js-delete-btn">Usun</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
<form class="statuses-form mt-16" action="/settings/integrations/fakturownia/save" method="post" novalidate>
|
||||
<input type="hidden" name="_token" value="<?= $e($csrfToken ?? '') ?>">
|
||||
|
||||
<label class="form-field">
|
||||
<span class="field-label">Prefix konta (subdomena) *</span>
|
||||
<input class="form-control" type="text" name="account_prefix" maxlength="63" value="<?= $e($prefix) ?>" pattern="[a-z0-9][a-z0-9-]{1,62}" required>
|
||||
<span class="muted">Wpisz sama subdomene, bez koncowki .fakturownia.pl.</span>
|
||||
</label>
|
||||
|
||||
<label class="form-field">
|
||||
<span class="field-label">Token API <?= $hasToken ? '' : '*' ?></span>
|
||||
<input class="form-control" type="password" name="api_token" autocomplete="new-password" placeholder="<?= $hasToken ? '********' : '' ?>" <?= $hasToken ? '' : 'required' ?>>
|
||||
<span class="muted"><?= $hasToken ? 'Token jest zapisany. Wpisz nowy, aby go nadpisac.' : 'Token API z Fakturowni (Ustawienia > Konta uzytkownikow > API).' ?></span>
|
||||
</label>
|
||||
|
||||
<label class="form-field">
|
||||
<span class="field-label">ID departamentu (opcjonalnie)</span>
|
||||
<input class="form-control" type="text" name="department_id" maxlength="64" value="<?= $e($departmentId) ?>">
|
||||
</label>
|
||||
|
||||
<div class="form-grid-2 mt-0">
|
||||
<label class="form-field">
|
||||
<span class="field-label">Domyslny typ dokumentu</span>
|
||||
<select class="form-control" name="default_kind">
|
||||
<option value="vat" <?= $defaultKind === 'vat' ? 'selected' : '' ?>>Faktura VAT</option>
|
||||
<option value="proforma" <?= $defaultKind === 'proforma' ? 'selected' : '' ?>>Proforma</option>
|
||||
<option value="invoice_other" <?= $defaultKind === 'invoice_other' ? 'selected' : '' ?>>Inna</option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<label class="form-field">
|
||||
<span class="field-label">Domyslny termin platnosci (dni)</span>
|
||||
<input class="form-control" type="number" name="default_payment_to_days" min="0" max="120" value="<?= $defaultPaymentDays ?>">
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<label class="form-field">
|
||||
<span class="field-label">Status</span>
|
||||
<label>
|
||||
<input type="checkbox" name="is_active" value="1" <?= $isActive ? 'checked' : '' ?>>
|
||||
Integracja aktywna
|
||||
</label>
|
||||
</label>
|
||||
|
||||
<div class="form-actions">
|
||||
<button type="submit" class="btn btn--primary">Zapisz</button>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<section class="card mt-16">
|
||||
<h3 class="section-title">Test polaczenia</h3>
|
||||
<p class="muted mt-12">Wykonuje GET <code><?= $e('https://' . ($prefix !== '' ? $prefix : '{prefix}') . '.fakturownia.pl/account.json') ?></code> z zapisanym tokenem.</p>
|
||||
|
||||
<form action="/settings/integrations/fakturownia/test" method="post" class="mt-12">
|
||||
<input type="hidden" name="_token" value="<?= $e($csrfToken ?? '') ?>">
|
||||
<input type="hidden" name="id" value="<?= $integrationId ?>">
|
||||
<button type="submit" class="btn btn--secondary">Testuj polaczenie</button>
|
||||
</form>
|
||||
|
||||
<?php if ($lastTestAt !== ''): ?>
|
||||
<div class="muted mt-12">
|
||||
Ostatni test: <strong><?= $e($lastTestAt) ?></strong>
|
||||
<?php if ($lastTestStatus !== ''): ?>
|
||||
<span class="badge badge--<?= $lastTestStatus === 'ok' ? 'success' : 'muted' ?>"><?= $e(strtoupper($lastTestStatus)) ?></span>
|
||||
<?php endif; ?>
|
||||
<?php if ($lastTestMessage !== ''): ?>
|
||||
<div><?= $e($lastTestMessage) ?></div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</section>
|
||||
|
||||
@@ -20,15 +20,15 @@ if (str_starts_with($lastTestMessage, 'MessageId:')) {
|
||||
<p class="muted mt-12"><?= $e($t('settings.hostedsms.description')) ?></p>
|
||||
|
||||
<?php if (!empty($errorMessage)): ?>
|
||||
<div class="alert alert--danger mt-12" role="alert"><?= $e((string) $errorMessage) ?></div>
|
||||
<div class="mt-12"><?php $type='danger'; $message=(string) $errorMessage; $dismissible=true; include dirname(__DIR__) . '/components/alert.php'; ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($successMessage)): ?>
|
||||
<div class="alert alert--success mt-12" role="status"><?= $e((string) $successMessage) ?></div>
|
||||
<div class="mt-12"><?php $type='success'; $message=(string) $successMessage; $dismissible=true; include dirname(__DIR__) . '/components/alert.php'; ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($testMessage)): ?>
|
||||
<div class="alert alert--info mt-12" role="status"><?= $e((string) $testMessage) ?></div>
|
||||
<div class="mt-12"><?php $type='info'; $message=(string) $testMessage; $dismissible=true; include dirname(__DIR__) . '/components/alert.php'; ?></div>
|
||||
<?php endif; ?>
|
||||
</section>
|
||||
|
||||
|
||||
@@ -12,11 +12,11 @@ $labelFormat = (string) ($s['label_format'] ?? 'Pdf');
|
||||
<p class="muted mt-12"><?= $e($t('settings.inpost.description')) ?></p>
|
||||
|
||||
<?php if (!empty($errorMessage)): ?>
|
||||
<div class="alert alert--danger mt-12" role="alert"><?= $e((string) $errorMessage) ?></div>
|
||||
<div class="mt-12"><?php $type='danger'; $message=(string) $errorMessage; $dismissible=true; include dirname(__DIR__) . '/components/alert.php'; ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($successMessage)): ?>
|
||||
<div class="alert alert--success mt-12" role="status"><?= $e((string) $successMessage) ?></div>
|
||||
<div class="mt-12"><?php $type='success'; $message=(string) $successMessage; $dismissible=true; include dirname(__DIR__) . '/components/alert.php'; ?></div>
|
||||
<?php endif; ?>
|
||||
</section>
|
||||
|
||||
|
||||
@@ -7,11 +7,11 @@ $items = is_array($rows ?? null) ? $rows : [];
|
||||
<p class="muted mt-12"><?= $e($t('settings.integrations_hub.description')) ?></p>
|
||||
|
||||
<?php if (!empty($errorMessage)): ?>
|
||||
<div class="alert alert--danger mt-12" role="alert"><?= $e((string) $errorMessage) ?></div>
|
||||
<div class="mt-12"><?php $type='danger'; $message=(string) $errorMessage; $dismissible=true; include dirname(__DIR__) . '/components/alert.php'; ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($successMessage)): ?>
|
||||
<div class="alert alert--success mt-12" role="status"><?= $e((string) $successMessage) ?></div>
|
||||
<div class="mt-12"><?php $type='success'; $message=(string) $successMessage; $dismissible=true; include dirname(__DIR__) . '/components/alert.php'; ?></div>
|
||||
<?php endif; ?>
|
||||
</section>
|
||||
|
||||
|
||||
@@ -10,19 +10,24 @@ $currentStatusFilter = (string) ($printStatusFilter ?? '');
|
||||
<p class="muted mt-12">Klucze API do uwierzytelniania aplikacji drukujacej (Windows Client).</p>
|
||||
|
||||
<?php if (!empty($errorMessage)): ?>
|
||||
<div class="alert alert--danger mt-12" role="alert"><?= $e((string) $errorMessage) ?></div>
|
||||
<div class="mt-12"><?php $type='danger'; $message=(string) $errorMessage; $dismissible=true; include dirname(__DIR__) . '/components/alert.php'; ?></div>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($successMessage)): ?>
|
||||
<div class="alert alert--success mt-12" role="status"><?= $e((string) $successMessage) ?></div>
|
||||
<div class="mt-12"><?php $type='success'; $message=(string) $successMessage; $dismissible=true; include dirname(__DIR__) . '/components/alert.php'; ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($newKey !== ''): ?>
|
||||
<div class="alert alert--warning mt-12">
|
||||
<?php
|
||||
ob_start();
|
||||
?>
|
||||
<strong>Nowy klucz API:</strong>
|
||||
<code id="new-api-key" style="display:inline-block;padding:4px 8px;background:#f5f5f5;border:1px solid #ddd;border-radius:3px;font-size:13px;word-break:break-all;user-select:all"><?= $e($newKey) ?></code>
|
||||
<button type="button" class="btn btn--secondary btn--sm ml-8" onclick="navigator.clipboard.writeText(document.getElementById('new-api-key').textContent)">Kopiuj</button>
|
||||
<br><small class="muted">Ten klucz nie bedzie ponownie wyswietlony. Skopiuj go teraz.</small>
|
||||
</div>
|
||||
<?php
|
||||
$newKeyAlertHtml = ob_get_clean();
|
||||
?>
|
||||
<div class="mt-12"><?php $type='warning'; $messageHtml=$newKeyAlertHtml; $dismissible=true; include dirname(__DIR__) . '/components/alert.php'; unset($messageHtml); ?></div>
|
||||
<?php endif; ?>
|
||||
</section>
|
||||
|
||||
|
||||
@@ -8,10 +8,10 @@ $scripts = is_array($scripts ?? null) ? $scripts : [];
|
||||
<p class="muted mt-4"><?= $e($t('settings.project_mapping.description')) ?></p>
|
||||
|
||||
<?php if (!empty($errorMessage)): ?>
|
||||
<div class="alert alert--danger mt-12" role="alert"><?= $e((string) $errorMessage) ?></div>
|
||||
<div class="mt-12"><?php $type='danger'; $message=(string) $errorMessage; $dismissible=true; include dirname(__DIR__) . '/components/alert.php'; ?></div>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($successMessage)): ?>
|
||||
<div class="alert alert--success mt-12" role="status"><?= $e((string) $successMessage) ?></div>
|
||||
<div class="mt-12"><?php $type='success'; $message=(string) $successMessage; $dismissible=true; include dirname(__DIR__) . '/components/alert.php'; ?></div>
|
||||
<?php endif; ?>
|
||||
</section>
|
||||
|
||||
|
||||
@@ -32,11 +32,11 @@ foreach ($dmMappings as $dm) {
|
||||
<p class="muted mt-12"><?= $e($t('settings.integrations.description')) ?></p>
|
||||
|
||||
<?php if (!empty($errorMessage)): ?>
|
||||
<div class="alert alert--danger mt-12" role="alert"><?= $e((string) $errorMessage) ?></div>
|
||||
<div class="mt-12"><?php $type='danger'; $message=(string) $errorMessage; $dismissible=true; include dirname(__DIR__) . '/components/alert.php'; ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($successMessage)): ?>
|
||||
<div class="alert alert--success mt-12" role="status"><?= $e((string) $successMessage) ?></div>
|
||||
<div class="mt-12"><?php $type='success'; $message=(string) $successMessage; $dismissible=true; include dirname(__DIR__) . '/components/alert.php'; ?></div>
|
||||
<?php endif; ?>
|
||||
</section>
|
||||
|
||||
@@ -452,7 +452,7 @@ foreach ($dmMappings as $dm) {
|
||||
<p class="muted mt-12"><?= $e($t('settings.integrations.delivery.select_integration_first')) ?></p>
|
||||
<?php else: ?>
|
||||
<?php if ($dmServicesError !== ''): ?>
|
||||
<div class="alert alert--danger mt-12"><?= $e($dmServicesError) ?></div>
|
||||
<div class="mt-12"><?php $type='danger'; $message=(string) $dmServicesError; $dismissible=true; include dirname(__DIR__) . '/components/alert.php'; ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($dmOrderMethods === []): ?>
|
||||
|
||||
@@ -23,15 +23,15 @@ if (str_starts_with($lastTestMessage, 'messageId:')) {
|
||||
<p class="muted mt-12"><?= $e($t('settings.smsplanet.description')) ?></p>
|
||||
|
||||
<?php if (!empty($errorMessage)): ?>
|
||||
<div class="alert alert--danger mt-12" role="alert"><?= $e((string) $errorMessage) ?></div>
|
||||
<div class="mt-12"><?php $type='danger'; $message=(string) $errorMessage; $dismissible=true; include dirname(__DIR__) . '/components/alert.php'; ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($successMessage)): ?>
|
||||
<div class="alert alert--success mt-12" role="status"><?= $e((string) $successMessage) ?></div>
|
||||
<div class="mt-12"><?php $type='success'; $message=(string) $successMessage; $dismissible=true; include dirname(__DIR__) . '/components/alert.php'; ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($testMessage)): ?>
|
||||
<div class="alert alert--info mt-12" role="status"><?= $e((string) $testMessage) ?></div>
|
||||
<div class="mt-12"><?php $type='info'; $message=(string) $testMessage; $dismissible=true; include dirname(__DIR__) . '/components/alert.php'; ?></div>
|
||||
<?php endif; ?>
|
||||
</section>
|
||||
|
||||
|
||||
@@ -26,11 +26,11 @@ foreach ($statusesList as $statusRow) {
|
||||
<p class="muted mt-12"><?= $e($t('settings.statuses.description')) ?></p>
|
||||
|
||||
<?php if (!empty($errorMessage)): ?>
|
||||
<div class="alert alert--danger mt-12" role="alert"><?= $e((string) $errorMessage) ?></div>
|
||||
<div class="mt-12"><?php $type='danger'; $message=(string) $errorMessage; $dismissible=true; include dirname(__DIR__) . '/components/alert.php'; ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($successMessage)): ?>
|
||||
<div class="alert alert--success mt-12" role="status"><?= $e((string) $successMessage) ?></div>
|
||||
<div class="mt-12"><?php $type='success'; $message=(string) $successMessage; $dismissible=true; include dirname(__DIR__) . '/components/alert.php'; ?></div>
|
||||
<?php endif; ?>
|
||||
</section>
|
||||
|
||||
@@ -76,7 +76,7 @@ foreach ($statusesList as $statusRow) {
|
||||
|
||||
<h2 class="section-title mt-16"><?= $e($t('settings.statuses.statuses.list_title')) ?></h2>
|
||||
<?php if ($groupsList === []): ?>
|
||||
<div class="alert alert--warning mt-12"><?= $e($t('settings.statuses.groups.empty')) ?></div>
|
||||
<div class="mt-12"><?php $type='warning'; $message=(string) $t('settings.statuses.groups.empty'); $dismissible=false; include dirname(__DIR__) . '/components/alert.php'; ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php foreach ($groupsList as $group): ?>
|
||||
|
||||
@@ -54,10 +54,10 @@ $defaultCodAmount = $isCod ? number_format($totalWithTax, 2, '.', '') : '0';
|
||||
</div>
|
||||
|
||||
<?php if ($flashSuccessMsg !== ''): ?>
|
||||
<div class="flash flash--success mt-12"><?= $e($flashSuccessMsg) ?></div>
|
||||
<div class="mt-12"><?php $type='success'; $message=(string) $flashSuccessMsg; $dismissible=true; include dirname(__DIR__) . '/components/alert.php'; ?></div>
|
||||
<?php endif; ?>
|
||||
<?php if ($flashErrorMsg !== ''): ?>
|
||||
<div class="flash flash--error mt-12"><?= $e($flashErrorMsg) ?></div>
|
||||
<div class="mt-12"><?php $type='danger'; $message=(string) $flashErrorMsg; $dismissible=true; include dirname(__DIR__) . '/components/alert.php'; ?></div>
|
||||
<?php endif; ?>
|
||||
</section>
|
||||
|
||||
@@ -91,7 +91,7 @@ $defaultCodAmount = $isCod ? number_format($totalWithTax, 2, '.', '') : '0';
|
||||
<h3 class="section-title">Przesylka</h3>
|
||||
|
||||
<?php if ($servicesError !== ''): ?>
|
||||
<div class="flash flash--error mt-12"><?= $e($servicesError) ?></div>
|
||||
<div class="mt-12"><?php $type='danger'; $message=(string) $servicesError; $dismissible=true; include dirname(__DIR__) . '/components/alert.php'; ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="form-field mt-12">
|
||||
@@ -106,7 +106,7 @@ $defaultCodAmount = $isCod ? number_format($totalWithTax, 2, '.', '') : '0';
|
||||
<div class="muted mt-4" style="font-size:12px">Metoda z zamowienia: <strong><?= $e($deliveryMethodName) ?></strong><?php if ($mappedServiceName !== ''): ?> → <?= $e($mappedCarrier === 'inpost' ? 'InPost' : ($mappedCarrier === 'apaczka' ? 'Apaczka' : 'Allegro')) ?>: <?= $e($mappedServiceName) ?><?php endif; ?></div>
|
||||
<?php endif; ?>
|
||||
<?php if ($deliveryMappingDiagnostic !== ''): ?>
|
||||
<div class="flash flash--error mt-8"><?= $e($deliveryMappingDiagnostic) ?></div>
|
||||
<div class="mt-8"><?php $type='danger'; $message=(string) $deliveryMappingDiagnostic; $dismissible=true; include dirname(__DIR__) . '/components/alert.php'; ?></div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
@@ -116,7 +116,7 @@ $defaultCodAmount = $isCod ? number_format($totalWithTax, 2, '.', '') : '0';
|
||||
|
||||
<div id="shipment-allegro-panel" style="<?= $preselectedCarrier !== 'allegro' ? 'display:none' : '' ?>">
|
||||
<?php if ($servicesError !== ''): ?>
|
||||
<div class="flash flash--error mt-4"><?= $e($servicesError) ?></div>
|
||||
<div class="mt-4"><?php $type='danger'; $message=(string) $servicesError; $dismissible=true; include dirname(__DIR__) . '/components/alert.php'; ?></div>
|
||||
<?php endif; ?>
|
||||
<div class="searchable-select" id="shipment-service-wrapper" data-match-id="<?= $e($deliveryMethodId) ?>">
|
||||
<input type="text" class="form-control" id="shipment-service-search" placeholder="Szukaj uslugi dostawy Allegro..." autocomplete="off">
|
||||
|
||||
@@ -109,13 +109,18 @@ foreach ($channelOptions as $channelOption) {
|
||||
|
||||
<section class="card mt-16 statistics-orders-table-wrap">
|
||||
<?php if ($debugEnabled): ?>
|
||||
<div class="alert alert--warning" role="alert">
|
||||
<?php
|
||||
ob_start();
|
||||
?>
|
||||
<strong>DEBUG</strong>
|
||||
<pre style="margin:8px 0 0;white-space:pre-wrap;"><?= $e(json_encode([
|
||||
'filters' => $debugMeta,
|
||||
'diagnostics' => $diagnostics,
|
||||
], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT)) ?></pre>
|
||||
</div>
|
||||
<?php
|
||||
$statisticsDebugHtml = ob_get_clean();
|
||||
?>
|
||||
<?php $type='warning'; $messageHtml=$statisticsDebugHtml; $dismissible=true; include dirname(__DIR__) . '/components/alert.php'; unset($messageHtml); ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!$hasData): ?>
|
||||
|
||||
@@ -7,15 +7,11 @@
|
||||
<h2 class="section-title"><?= $e($t('users.create_title')) ?></h2>
|
||||
|
||||
<?php if (!empty($errorMessage)): ?>
|
||||
<div class="alert alert--danger mt-12" role="alert">
|
||||
<?= $e($errorMessage) ?>
|
||||
</div>
|
||||
<div class="mt-12"><?php $type='danger'; $message=(string) $errorMessage; $dismissible=true; include dirname(__DIR__) . '/components/alert.php'; ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($successMessage)): ?>
|
||||
<div class="alert alert--success mt-12" role="status">
|
||||
<?= $e($successMessage) ?>
|
||||
</div>
|
||||
<div class="mt-12"><?php $type='success'; $message=(string) $successMessage; $dismissible=true; include dirname(__DIR__) . '/components/alert.php'; ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<form class="users-form mt-16" action="/settings/users" method="post" novalidate>
|
||||
|
||||
@@ -6,6 +6,8 @@ namespace App\Core\Support;
|
||||
final class Flash
|
||||
{
|
||||
private const FLASH_KEY = '_flash';
|
||||
private const QUEUE_KEY = '_flash_queue';
|
||||
private const ALLOWED_TYPES = ['info', 'success', 'warning', 'danger'];
|
||||
|
||||
public static function set(string $key, mixed $value): void
|
||||
{
|
||||
@@ -35,4 +37,85 @@ final class Flash
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
public static function push(string $type, string $message): void
|
||||
{
|
||||
$normalizedType = in_array($type, self::ALLOWED_TYPES, true) ? $type : 'info';
|
||||
|
||||
if (!isset($_SESSION[self::QUEUE_KEY]) || !is_array($_SESSION[self::QUEUE_KEY])) {
|
||||
$_SESSION[self::QUEUE_KEY] = [];
|
||||
}
|
||||
|
||||
$_SESSION[self::QUEUE_KEY][] = [
|
||||
'type' => $normalizedType,
|
||||
'message' => $message,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns and clears all queued flash entries (typed queue + legacy key/value map).
|
||||
*
|
||||
* @return list<array{type: string, message: string}>
|
||||
*/
|
||||
public static function all(): array
|
||||
{
|
||||
$entries = [];
|
||||
|
||||
if (isset($_SESSION[self::QUEUE_KEY]) && is_array($_SESSION[self::QUEUE_KEY])) {
|
||||
foreach ($_SESSION[self::QUEUE_KEY] as $entry) {
|
||||
if (!is_array($entry)) {
|
||||
continue;
|
||||
}
|
||||
$type = is_string($entry['type'] ?? null) ? $entry['type'] : 'info';
|
||||
$message = is_string($entry['message'] ?? null) ? $entry['message'] : '';
|
||||
if ($message === '') {
|
||||
continue;
|
||||
}
|
||||
if (!in_array($type, self::ALLOWED_TYPES, true)) {
|
||||
$type = 'info';
|
||||
}
|
||||
$entries[] = ['type' => $type, 'message' => $message];
|
||||
}
|
||||
unset($_SESSION[self::QUEUE_KEY]);
|
||||
}
|
||||
|
||||
if (isset($_SESSION[self::FLASH_KEY]) && is_array($_SESSION[self::FLASH_KEY])) {
|
||||
foreach ($_SESSION[self::FLASH_KEY] as $key => $value) {
|
||||
if (!is_string($value) || $value === '') {
|
||||
continue;
|
||||
}
|
||||
$entries[] = [
|
||||
'type' => self::inferTypeFromKey((string) $key),
|
||||
'message' => $value,
|
||||
];
|
||||
}
|
||||
unset($_SESSION[self::FLASH_KEY]);
|
||||
}
|
||||
|
||||
return $entries;
|
||||
}
|
||||
|
||||
private static function inferTypeFromKey(string $key): string
|
||||
{
|
||||
$lower = strtolower($key);
|
||||
|
||||
if (str_contains($lower, 'error') || str_contains($lower, 'fail') || str_contains($lower, 'danger')) {
|
||||
return 'danger';
|
||||
}
|
||||
if (str_contains($lower, 'warning') || str_contains($lower, 'warn')) {
|
||||
return 'warning';
|
||||
}
|
||||
if (
|
||||
str_contains($lower, 'success')
|
||||
|| str_ends_with($lower, '.save')
|
||||
|| str_contains($lower, 'saved')
|
||||
|| str_contains($lower, '.created')
|
||||
|| str_contains($lower, '.deleted')
|
||||
|| str_contains($lower, '.toggled')
|
||||
) {
|
||||
return 'success';
|
||||
}
|
||||
|
||||
return 'info';
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user