feat: implement new universal form edit system and remove legacy banner classes

This commit is contained in:
2026-02-08 18:12:54 +01:00
parent 926b6fcbca
commit 3c50440cb2
6 changed files with 173 additions and 134 deletions

File diff suppressed because one or more lines are too long

View File

@@ -343,6 +343,174 @@ vendor/bin/phpstan analyse autoload/Domain
5. **Category** 5. **Category**
6. **ShopAttribute** 6. **ShopAttribute**
- **Form Edit System** - Nowy uniwersalny system formularzy edycji
- ✅ Klasy ViewModel: `FormFieldType`, `FormField`, `FormTab`, `FormAction`, `FormEditViewModel`
- ✅ Walidacja: `FormValidator` z obsługą reguł per pole i sekcje językowe
- ✅ Persist: `FormRequestHandler` - zapamiętywanie danych przy błędzie walidacji
- ✅ Renderer: `FormFieldRenderer` - renderowanie wszystkich typów pól
- ✅ Szablon: `admin/templates/components/form-edit.php` - uniwersalny layout
- ✅ BannerController - przerobiony na nowy system formularzy
- Wspierane typy pól: text, number, email, password, date, datetime, switch, select, textarea, editor, image, file, hidden, lang_section
- Obsługa zakładek (vertical) i sekcji językowych (horizontal)
- **Do zrobienia**: Przerobić pozostałe kontrolery (Articles, Settings, Product, Category, itd.)
--- ---
*Rozpoczęto: 2025-02-05* *Rozpoczęto: 2025-02-05*
*Ostatnia aktualizacja: 2026-02-07* *Ostatnia aktualizacja: 2026-02-08*
## Form Edit System - Dokumentacja użycia
### Architektura
```
┌─────────────────────────────────────────────────────────────┐
│ Controller │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ edit() │ │ save() │ │
│ │ - buduje VM │ │ - walidacja │ │
│ │ - renderuje │ │ - zapis │ │
│ └────────┬────────┘ └─────────────────┘ │
└───────────┼─────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ FormEditViewModel │
│ - title, formId, data, fields, tabs, actions │
│ - validationErrors, persist, languages │
└───────────┬─────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ components/form-edit.php (szablon) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ FormFieldRenderer - renderuje każde pole │ │
│ │ ├─ input, select, textarea, switch │ │
│ │ ├─ date, datetime, editor, image │ │
│ │ └─ lang_section (zagnieżdżone pola) │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
```
### Przykład użycia w kontrolerze
```php
use admin\ViewModels\Forms\FormEditViewModel;
use admin\ViewModels\Forms\FormField;
use admin\ViewModels\Forms\FormTab;
use admin\ViewModels\Forms\FormAction;
use admin\Support\Forms\FormRequestHandler;
class BannerController
{
public function edit(): string
{
$banner = $this->repository->find($id);
$languages = \admin\factory\Languages::languages_list();
$viewModel = new FormEditViewModel(
formId: 'banner-edit',
title: 'Edycja banera',
data: $banner,
tabs: [
new FormTab('settings', 'Ustawienia', 'fa-wrench'),
new FormTab('content', 'Zawartość', 'fa-file'),
],
fields: [
// Zakładka Ustawienia
FormField::text('name', [
'label' => 'Nazwa',
'tab' => 'settings',
'required' => true,
]),
FormField::switch('status', [
'label' => 'Aktywny',
'tab' => 'settings',
]),
FormField::date('date_start', [
'label' => 'Data rozpoczęcia',
'tab' => 'settings',
]),
// Sekcja językowa w zakładce Zawartość
FormField::langSection('translations', 'content', [
FormField::image('src', ['label' => 'Obraz']),
FormField::text('url', ['label' => 'Url']),
FormField::editor('text', ['label' => 'Treść']),
]),
],
actions: [
FormAction::save('/admin/banners/save', '/admin/banners'),
FormAction::cancel('/admin/banners'),
],
languages: $languages,
persist: true,
);
return \Tpl::view('components/form-edit', ['form' => $viewModel]);
}
public function save(): void
{
$formHandler = new FormRequestHandler();
$viewModel = $this->buildFormViewModel(); // jak w edit()
$result = $formHandler->handleSubmit($viewModel, $_POST);
if (!$result['success']) {
// Błędy walidacji - zapisane automatycznie do sesji
echo json_encode(['success' => false, 'errors' => $result['errors']]);
exit;
}
// Sukces - persist wyczyszczony automatycznie
$this->repository->save($result['data']);
echo json_encode(['success' => true]);
exit;
}
}
```
### Dostępne typy pól
| Typ | Metoda | Opcje |
|-----|--------|-------|
| `text` | `FormField::text(name, ['label' => '...', 'required' => true])` | placeholder, help |
| `number` | `FormField::number(name, [...])` | - |
| `email` | `FormField::email(name, [...])` | walidacja formatu |
| `password` | `FormField::password(name, [...])` | - |
| `date` | `FormField::date(name, [...])` | datetimepicker |
| `datetime` | `FormField::datetime(name, [...])` | datetimepicker z czasem |
| `switch` | `FormField::switch(name, [...])` | checked (bool) |
| `select` | `FormField::select(name, ['options' => [...]])` | options: [key => label] |
| `textarea` | `FormField::textarea(name, ['rows' => 4])` | rows |
| `editor` | `FormField::editor(name, ['toolbar' => 'MyTool'])` | CKEditor |
| `image` | `FormField::image(name, ['filemanager' => true])` | filemanager URL |
| `file` | `FormField::file(name, [...])` | filemanager |
| `hidden` | `FormField::hidden(name, value)` | - |
| `lang_section` | `FormField::langSection(name, 'tab', [fields])` | pola per język |
### Walidacja
Walidacja jest automatyczna na podstawie właściwości pól:
- `required` - pole wymagane
- `type` = `email` - walidacja formatu e-mail
- `type` = `number` - walidacja liczby
- `type` = `date` - walidacja formatu YYYY-MM-DD
Dla sekcji językowych walidacja jest powtarzana dla każdego aktywnego języka.
### Persist (zapamiętywanie danych)
Gdy `persist = true`:
1. Przy błędzie walidacji dane są zapisywane w `$_SESSION['form_persist'][$formId]`
2. Formularz automatycznie przywraca dane z sesji przy ponownym wyświetleniu
3. Po udanym zapisie sesja jest czyszczona automatycznie przez `FormRequestHandler`
### Przerabianie istniejących formularzy
1. **Kontroler** - zamień `view\Xxx::edit()` na `FormEditViewModel`
2. **Repository** - dostosuj `save()` do formatu z `FormRequestHandler` (lub dodaj wsparcie dla obu formatów)
3. **Szablon** - usuń stary szablon lub zostaw jako fallback
4. **Testy** - zaktualizuj testy jeśli zmienił się format danych

View File

@@ -1,108 +0,0 @@
<?php
namespace admin\factory;
class Banners
{
public static function banner_delete( $banner_id )
{
global $mdb;
$result = $mdb -> delete( 'pp_banners', [ 'id' => (int) $banner_id ] );
\S::delete_dir( '../temp/' );
return $result;
}
public static function banner_save( $banner_id, $name, $status, $date_start, $date_end, $home_page, $src, $url, $html, $text )
{
global $mdb;
if ( !$banner_id )
{
$mdb -> insert( 'pp_banners', [
'name' => $name,
'status' => $status == 'on' ? 1 : 0,
'date_start' => $date_start != '' ? $date_start : null,
'date_end' => $date_end != '' ? $date_end : null,
'home_page' => $home_page == 'on' ? 1 : 0
] );
$id = $mdb -> id();
if ( $id )
{
foreach ( $src as $key => $val )
{
$mdb -> insert( 'pp_banners_langs', [
'id_banner' => (int)$id,
'id_lang' => $key,
'src' => $src[$key],
'url' => $url[$key],
'html' => $html[$key],
'text' => $text[$key]
] );
}
\S::delete_dir( '../temp/' );
return $id;
}
}
else
{
$mdb -> update( 'pp_banners', [
'name' => $name,
'status' => $status == 'on' ? 1 : 0,
'date_start' => $date_start != '' ? $date_start : null,
'date_end' => $date_end != '' ? $date_end : null,
'home_page' => $home_page == 'on' ? 1 : 0
], [
'id' => (int)$banner_id
] );
foreach ( $src as $key => $val )
{
if ( $translation_id = $mdb -> get( 'pp_banners_langs', 'id', [ 'AND' => [ 'banner_id' => $banner_id, 'lang_id' => $key ] ] ) )
$mdb -> update( 'pp_banners_langs', [
'id_lang' => $key,
'src' => $src[$key],
'url' => $url[$key],
'html' => $html[$key],
'text' => $text[$key]
], [
'id' => $translation_id
] );
else
$mdb -> insert( 'pp_banners_langs', [
'id_banner' => (int)$banner_id,
'id_lang' => $key,
'src' => $src[$key],
'url' => $url[$key],
'html' => $html[$key],
'text' => $text[$key]
] );
}
\S::delete_dir( '../temp/' );
return $banner_id;
}
return false;
}
public static function banner_details( $id_banner )
{
global $mdb;
$banner = $mdb -> get( 'pp_banners', '*', [ 'id' => (int)$id_banner ] );
$results = $mdb -> select( 'pp_banners_langs', '*', [ 'id_banner' => (int)$id_banner ] );
if ( is_array( $results ) ) foreach ( $results as $row )
$banner['languages'][$row['id_lang']] = $row;
return $banner;
}
}
?>

View File

@@ -1,24 +0,0 @@
<?php
namespace admin\view;
class Banners
{
public static function banners_list()
{
// Fallback dla legacy wywolan widoku.
global $mdb;
$controller = new \admin\Controllers\BannerController(
new \Domain\Banner\BannerRepository( $mdb )
);
return $controller->list();
}
public static function banner_edit( $banner, $languages )
{
$tpl = new \Tpl;
$tpl -> banner = $banner;
$tpl -> languages = $languages;
return $tpl -> render( 'banners/banner-edit' );
}
}
?>

View File

@@ -0,0 +1,2 @@
F: ../autoload/admin/view/class.Banners.php
F: ../autoload/admin/factory/class.Banners.php

View File

@@ -4,6 +4,7 @@
- FIX - filemanager: przywrocono dzialanie popupa wyboru obrazka z banera - FIX - filemanager: przywrocono dzialanie popupa wyboru obrazka z banera
- UPDATE - komunikaty zapisu w nowym formularzu sa wyswietlane w stylu panelu (bez natywnego alertu JS) - UPDATE - komunikaty zapisu w nowym formularzu sa wyswietlane w stylu panelu (bez natywnego alertu JS)
- UPDATE - lista banerow: dodano kolumne miniatury oraz podglad duzego obrazka w popup po najechaniu - UPDATE - lista banerow: dodano kolumne miniatury oraz podglad duzego obrazka w popup po najechaniu
- UPDATE - usunieto nieuzywane legacy klasy banerow: `admin\\view\\Banners`, `admin\\factory\\Banners`
<hr><b>ver. 0.248</b><br /> <hr><b>ver. 0.248</b><br />
- UPDATE - filtry w nowych tabelach dzialaja automatycznie na `onchange` - UPDATE - filtry w nowych tabelach dzialaja automatycznie na `onchange`
- UPDATE - `components/table-list`: auto-submit formularza filtrow po zmianie pola (select, date, text) - UPDATE - `components/table-list`: auto-submit formularza filtrow po zmianie pola (select, date, text)