Files
newwalls.pl/plan.md
Jacek Pyziak e888c81aef Add product carousel module with template and database structure
- Created `pp_carousel.tpl` for rendering product carousel with Swiper integration.
- Added `plan.md` detailing module architecture, database schema, and implementation steps.
- Initialized log files for development and production environments.
2026-02-25 09:23:54 +01:00

197 lines
8.6 KiB
Markdown

# Plan: Moduł Project-Pro Karuzela Produktów
## Analiza screenshota
Karuzela wyświetla produkty jako karty z: dużym zdjęciem, etykietą kategorii (label w lewym górnym rogu), nazwą produktu, ceną z sufiksem `/m2`. Układ 3 kolumny na desktop, slider Swiper.
## Kluczowe decyzje architektoniczne
**Dlaczego tabela SQL zamiast Configuration?**
Moduł wymaga *dowolnej liczby* karuzeli — Configuration przechowuje pary klucz-wartość, nie nadaje się do list rekordów o zmiennej długości. Dedykowana tabela `pp_carousel` to jedyne poprawne rozwiązanie.
**Dlaczego dynamiczne hooki?**
Użytkownik chce przypisywać karuzele do hooków, w tym niestandardowych. Moduł rejestruje się na `displayHeader` + catch-all `actionDispatcher`, a wyświetlanie realizuje poprzez `Hook::exec()` z warunkiem `WHERE hook_name = ?` w bazie. Niestandardowe hooki dodawane są przez `Hook::getIdByName()` / ręczne INSERT.
## Struktura plików modułu
```
modules/pp_carousel/
├── pp_carousel.php # Główna klasa modułu
├── config.xml
├── logo.png
├── sql/
│ ├── install.sql # CREATE TABLE
│ └── uninstall.sql # DROP TABLE
├── views/
│ ├── css/
│ │ └── pp_carousel.css # Style karuzeli (front)
│ ├── js/
│ │ └── pp_carousel.js # Inicjalizacja Swiper (front)
│ ├── lib/
│ │ └── swiper/ # swiper-bundle.min.js/css (kopiujemy z project_pro_news)
│ └── templates/
│ ├── hook/
│ │ └── pp_carousel.tpl # Szablon frontu karuzeli
│ └── admin/
│ ├── list.tpl # Lista karuzeli w BO
│ └── form.tpl # Formularz dodawania/edycji karuzeli
```
## Tabela bazy danych: `ps_pp_carousel`
| Kolumna | Typ | Opis |
|---------|-----|------|
| `id_carousel` | INT AUTO_INCREMENT PK | ID karuzeli |
| `hook_name` | VARCHAR(128) | Nazwa hooka (np. `displayHome`, custom) |
| `source_type` | ENUM('new','bestseller','category','manual') | Źródło produktów |
| `id_category` | INT DEFAULT 0 | ID kategorii (gdy source_type=category) |
| `product_ids` | TEXT | Ręczne ID produktów rozdzielone przecinkami |
| `limit_products` | INT DEFAULT 12 | Limit produktów |
| `title` | VARCHAR(255) | Tytuł karuzeli |
| `subtitle` | VARCHAR(255) | Podtytuł |
| `button_label` | VARCHAR(255) | Tekst przycisku |
| `button_url` | VARCHAR(512) | URL przycisku |
| `price_suffix` | VARCHAR(64) | Sufiks ceny (np. `/m²`) |
| `position` | INT DEFAULT 0 | Kolejność sortowania |
| `active` | TINYINT(1) DEFAULT 1 | Aktywna/nieaktywna |
| `id_shop` | INT DEFAULT 1 | Multishop |
| `date_add` | DATETIME | Data utworzenia |
| `date_upd` | DATETIME | Data modyfikacji |
> Wielojęzyczność: pola `title`, `subtitle`, `button_label`, `price_suffix` — dodatkowa tabela `ps_pp_carousel_lang` z kolumnami `id_carousel`, `id_lang`, `title`, `subtitle`, `button_label`, `price_suffix`.
## Implementacja krok po kroku
### Krok 1: Plik główny `pp_carousel.php` — szkielet
- Klasa `Pp_Carousel extends Module`
- Konstruktor: name=`pp_carousel`, author=`Project-Pro`, version=`1.0.0`, displayName=`Project-Pro Karuzela Produktów`
- `install()`: wykonuje SQL, rejestruje hooki: `displayHeader`, `displayHome`, `displayBackOfficeHeader`
- `uninstall()`: DROP tabeli, usunięcie konfiguracji
- Metoda `getContent()` → panel admin z listą karuzeli + formularz CRUD
### Krok 2: SQL install/uninstall
**install.sql:**
```sql
CREATE TABLE IF NOT EXISTS `PREFIX_pp_carousel` (
`id_carousel` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT,
`hook_name` VARCHAR(128) NOT NULL DEFAULT 'displayHome',
`source_type` VARCHAR(20) NOT NULL DEFAULT 'new',
`id_category` INT(11) UNSIGNED NOT NULL DEFAULT 0,
`product_ids` TEXT,
`limit_products` INT(11) UNSIGNED NOT NULL DEFAULT 12,
`button_url` VARCHAR(512) DEFAULT '',
`position` INT(11) UNSIGNED NOT NULL DEFAULT 0,
`active` TINYINT(1) UNSIGNED NOT NULL DEFAULT 1,
`id_shop` INT(11) UNSIGNED NOT NULL DEFAULT 1,
`date_add` DATETIME NOT NULL,
`date_upd` DATETIME NOT NULL,
PRIMARY KEY (`id_carousel`),
KEY `hook_name` (`hook_name`),
KEY `active` (`active`)
) ENGINE=ENGINE_TYPE DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS `PREFIX_pp_carousel_lang` (
`id_carousel` INT(11) UNSIGNED NOT NULL,
`id_lang` INT(11) UNSIGNED NOT NULL,
`title` VARCHAR(255) DEFAULT '',
`subtitle` VARCHAR(255) DEFAULT '',
`button_label` VARCHAR(255) DEFAULT '',
`price_suffix` VARCHAR(64) DEFAULT '',
PRIMARY KEY (`id_carousel`, `id_lang`)
) ENGINE=ENGINE_TYPE DEFAULT CHARSET=utf8mb4;
```
### Krok 3: Panel administracyjny (`getContent()`)
Widok listy (domyślny):
- Tabela HelperList: ID | Tytuł | Hook | Źródło | Limit | Aktywna | Akcje (edytuj/usuń)
- Przycisk "Dodaj karuzelę"
Widok formularza (dodaj/edytuj):
- **Hook** — select z listą dostępnych hooków + pole tekstowe "Niestandardowy hook" (jeśli wpisany, tworzony jest nowy hook w `ps_hook`)
- **Źródło produktów** — select: Nowości / Bestsellery / Kategoria / Ręczne ID
- **Kategoria** — select z drzewem kategorii (widoczny gdy source_type=category)
- **Ręczne ID produktów** — textarea, oddzielone przecinkami (widoczny gdy source_type=manual)
- **Limit produktów** — input number
- **Tytuł** — input text (wielojęzyczny)
- **Podtytuł** — input text (wielojęzyczny)
- **Tekst przycisku** — input text (wielojęzyczny)
- **URL przycisku** — input text
- **Sufiks ceny** — input text (wielojęzyczny), np. `/m²`
- **Aktywna** — switch tak/nie
### Krok 4: Pobieranie produktów — metoda `getProductsByCarousel($carousel)`
Switch po `source_type`:
- `new``Product::getNewProducts($idLang, 0, $limit)` (wzorzec z `ps_newproducts`)
- `bestseller``ProductSale::getBestSales($idLang, 0, $limit)` (wzorzec z `ps_bestsellers`)
- `category``Category::getProducts($idCategory, $idLang, 1, $limit)` (wzorzec z `project_pro_news`)
- `manual``SELECT` po `id_product IN (...)`, potem prezentacja
Wszystkie wyniki przechodzą przez `ProductAssembler` + `ProductListingPresenter` (identycznie jak w istniejących modułach).
### Krok 5: Renderowanie hooków
Mechanizm: moduł rejestruje się na najczęstsze hooki (`displayHome`, `displayFooterBefore`, `displayLeftColumn` itp.). W każdym hooku:
```php
public function hookDisplayHome($params) {
return $this->renderCarouselsForHook('displayHome');
}
```
Metoda `renderCarouselsForHook($hookName)`:
1. SELECT z `ps_pp_carousel` WHERE `hook_name` = $hookName AND `active` = 1 ORDER BY `position`
2. Dla każdej karuzeli: pobierz produkty, przypisz do Smarty, fetch szablonu
3. Zwróć połączony HTML
Niestandardowe hooki: przy zapisie karuzeli z nowym hookiem → `$this->registerHook($hookName)` + ewentualnie INSERT do `ps_hook`.
### Krok 6: Szablon frontu `pp_carousel.tpl`
Wzorowany na `project_pro_news.tpl` — ten sam layout Swiper:
- Section z unikalnym `id="pp-carousel-{$carousel.id}"`
- Nagłówek: tytuł + podtytuł
- Swiper container z kartami produktów
- Karta: zdjęcie (home_default), etykieta kategorii, nazwa, cena + sufiks
- Nawigacja prev/next
- Przycisk "Zobacz więcej"
### Krok 7: CSS + JS
**CSS** — bazowany na `project_pro_news.css`, dostosowany do nowego namespace `.pp-carousel`.
**JS** — inicjalizacja wielu instancji Swipera:
```js
document.querySelectorAll('.pp-carousel__slider').forEach(function(el) {
new Swiper(el, { /* config */ });
});
```
### Krok 8: Rejestracja assetów
`hookDisplayHeader()` — rejestracja CSS/JS (swiper + pp_carousel) identycznie jak w `project_pro_news`.
## Hooki do rejestracji (install)
Obowiązkowe:
- `displayHeader` (assety CSS/JS)
- `displayHome` (główna pozycja)
- `displayBackOfficeHeader` (admin CSS/JS jeśli potrzebne)
Dodatkowe (rejestrowane dynamicznie przy tworzeniu karuzeli z niestandardowym hookiem):
- `displayFooterBefore`, `displayTopColumn`, `displayLeftColumn`, `displayFooter` itd.
- Dowolne niestandardowe hooki
## Kolejność wdrożenia
1. **Pliki SQL** — tabele `pp_carousel` + `pp_carousel_lang`
2. **pp_carousel.php** — konstruktor, install/uninstall, hookDisplayHeader
3. **Panel admin** — getContent() z listą + formularzem CRUD
4. **Logika produktów** — getProductsByCarousel() z 4 źródłami
5. **Rendering hooków** — renderCarouselsForHook() + rejestracja dynamiczna
6. **Szablon .tpl** — karta produktu zgodna ze screenshotem
7. **CSS/JS** — style + Swiper init (wieloinstancyjny)
8. **Testowanie** — dodanie karuzeli w BO, weryfikacja na froncie