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**
|
||||
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*
|
||||
*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
|
||||
- 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 - usunieto nieuzywane legacy klasy banerow: `admin\\view\\Banners`, `admin\\factory\\Banners`
|
||||
<hr><b>ver. 0.248</b><br />
|
||||
- UPDATE - filtry w nowych tabelach dzialaja automatycznie na `onchange`
|
||||
- UPDATE - `components/table-list`: auto-submit formularza filtrow po zmianie pola (select, date, text)
|
||||
|
||||
Reference in New Issue
Block a user