feat: implement new universal form edit system and remove legacy banner classes
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
?>
|
|
||||||
@@ -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' );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
?>
|
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
F: ../autoload/admin/view/class.Banners.php
|
||||||
|
F: ../autoload/admin/factory/class.Banners.php
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user