diff --git a/admin/templates/components/form-edit.php b/admin/templates/components/form-edit.php
index ca5ed57..1ed2713 100644
--- a/admin/templates/components/form-edit.php
+++ b/admin/templates/components/form-edit.php
@@ -61,6 +61,10 @@ $_SESSION['can_use_rfm'] = true;
Wstecz
+ name === 'preview'): ?>
+
+ = htmlspecialchars($action->label) ?>
+
= htmlspecialchars($action->label) ?>
diff --git a/autoload/Domain/Product/ProductRepository.php b/autoload/Domain/Product/ProductRepository.php
index 6ed92f8..919f0d9 100644
--- a/autoload/Domain/Product/ProductRepository.php
+++ b/autoload/Domain/Product/ProductRepository.php
@@ -3359,12 +3359,18 @@ class ProductRepository
$attributes = \Shared\Helpers\Helpers::removeDuplicates($attributes, 'id');
- $sorted = [];
+ $toSort = [];
foreach ($attributes as $key => $val) {
$row = [];
$row['id'] = $key;
$row['values'] = $val;
- $sorted[$attrRepo->getAttributeOrder((int) $key)] = $row;
+ $toSort[] = ['order' => (int) $attrRepo->getAttributeOrder((int) $key), 'data' => $row];
+ }
+ usort($toSort, function ($a, $b) { return $a['order'] - $b['order']; });
+
+ $sorted = [];
+ foreach ($toSort as $i => $item) {
+ $sorted[$i + 1] = $item['data'];
}
return $sorted;
diff --git a/autoload/admin/Controllers/ShopProductController.php b/autoload/admin/Controllers/ShopProductController.php
index 6524a91..c3d104a 100644
--- a/autoload/admin/Controllers/ShopProductController.php
+++ b/autoload/admin/Controllers/ShopProductController.php
@@ -547,6 +547,11 @@ class ShopProductController
FormAction::cancel( $backUrl ),
];
+ if ( $productId > 0 ) {
+ $previewUrl = $this->repository->getProductUrl( $productId );
+ $actions[] = FormAction::preview( $previewUrl );
+ }
+
return new FormEditViewModel(
'product-edit',
$title,
diff --git a/autoload/admin/ViewModels/Forms/FormAction.php b/autoload/admin/ViewModels/Forms/FormAction.php
index 98d6529..afd147f 100644
--- a/autoload/admin/ViewModels/Forms/FormAction.php
+++ b/autoload/admin/ViewModels/Forms/FormAction.php
@@ -56,6 +56,22 @@ class FormAction
);
}
+ /**
+ * Predefiniowana akcja Podgląd (otwiera w nowej karcie)
+ */
+ public static function preview(string $url, string $label = 'Podgląd'): self
+ {
+ return new self(
+ 'preview',
+ $label,
+ $url,
+ null,
+ 'btn btn-info',
+ 'link',
+ ['target' => '_blank']
+ );
+ }
+
/**
* Predefiniowana akcja Anuluj
*/
diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md
index d22b751..71b2953 100644
--- a/docs/CHANGELOG.md
+++ b/docs/CHANGELOG.md
@@ -4,6 +4,14 @@ Logi zmian z migracji na Domain-Driven Architecture. Najnowsze na gorze.
---
+## ver. 0.303 (2026-02-22) - Fix: wyswietlanie atrybutow produktu na froncie + podglad produktu w adminie
+
+- **FIX**: Naprawiono wyswietlanie atrybutow produktu na froncie — gdy dwa atrybuty mialy te sama wartosc kolejnosci (`o`), jeden nadpisywal drugi (kolizja kluczy tablicy). Teraz atrybuty sortowane przez `usort()` z unikalnymi kluczami sekwencyjnymi.
+- **NEW**: Przycisk "Podglad" w formularzu edycji produktu — otwiera strone produktu w nowej karcie
+- **NEW**: `FormAction::preview()` — nowy typ akcji formularza z `target="_blank"`
+
+---
+
## ver. 0.302 (2026-02-22) - REST API: warianty produktow, atrybuty, filtrowanie
- **NEW**: API wariantow produktow — CRUD: `variants`, `create_variant`, `update_variant`, `delete_variant`
diff --git a/docs/UPDATE_INSTRUCTIONS.md b/docs/UPDATE_INSTRUCTIONS.md
index 46ed6a7..2ed4a2b 100644
--- a/docs/UPDATE_INSTRUCTIONS.md
+++ b/docs/UPDATE_INSTRUCTIONS.md
@@ -69,45 +69,5 @@ Build script automatycznie je wczyta i umieści w manifeście + legacy `_sql.txt
### .updateignore
Plik w katalogu głównym projektu, wzorce plików wykluczonych z paczek (jak `.gitignore`).
----
-
-## Stary sposób (do v0.300) — ręczne pakowanie
-
-### Struktura aktualizacji
-
-Aktualizacje znajdują się w folderze `updates/0.XX/` gdzie XX oznacza dziesiątki wersji.
-
-#### Pliki aktualizacji:
-- `ver_X.XXX.zip` - paczka ZIP ze zmienionymi plikami (BEZ folderu wersji, bezpośrednio struktura katalogów)
-- `ver_X.XXX_sql.txt` - opcjonalny plik z zapytaniami SQL (jeśli wymagane zmiany w bazie)
-- `ver_X.XXX_files.txt` - opcjonalny plik z listą plików do **USUNIĘCIA** przy aktualizacji (format: `F: ../sciezka/do/pliku.php`)
-- `changelog.php` - historia zmian
-- `versions.php` - konfiguracja wersji (zmienna `$current_ver`)
-
-#### Zasada pakowania plików
-- Do paczek aktualizacji **nie dodajemy plików `*.md`** (dokumentacja jest tylko wewnętrzna/deweloperska).
-- Do paczek aktualizacji **nie dodajemy `updates/changelog.php`** (to plik serwisowy po stronie repozytorium aktualizacji, nie runtime klienta).
-- Do paczek aktualizacji **nie dodajemy głównego `.htaccess` z katalogu projektu** (ten plik wdrażamy osobno, poza ZIP aktualizacji).
-
-### Procedura ręczna
-
-1. Określ numer wersji
-2. Utwórz folder tymczasowy: `mkdir -p temp/temp_XXX/sciezka/do/pliku`
-3. Skopiuj zmienione pliki do folderu tymczasowego
-4. Utwórz ZIP z zawartości folderu (nie z samego folderu!)
-5. Usuń folder tymczasowy
-6. Zaktualizuj `changelog.php` i `versions.php`
-7. (Opcjonalnie) Utwórz `_sql.txt` i `_files.txt`
-
-**WAŻNE:** W archiwum ZIP NIE powinno być folderu z nazwą wersji. Struktura ZIP zaczyna się bezpośrednio od katalogów projektu (admin/, autoload/, itp.).
-
-## Status bieżącej aktualizacji (ver. 0.302)
-
-- Wersja udostępniona: `0.302` (data: 2026-02-22).
-- Pliki publikacyjne:
- - `updates/0.30/ver_0.302.zip`
-- Pliki metadanych aktualizacji:
- - `updates/changelog.php`
- - `updates/versions.php` (`$current_ver = 302`)
-- Weryfikacja testów przed publikacją:
- - `OK (730 tests, 2066 assertions)`
+### INFO
+pamiętaj że push czasem zwraca błąd autoryzacji, wtedy spróbuj ponownie
\ No newline at end of file