- 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.
197 lines
8.6 KiB
Markdown
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
|