шаг 4 завершен
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/** @var array<int, array<string, mixed>> $categories */
|
||||
/** @var array<int, array<string, mixed>> $flatCategories */
|
||||
/** @var string|null $message */
|
||||
/** @var string|null $error */
|
||||
?>
|
||||
<section class="admin-layout">
|
||||
<div class="panel">
|
||||
<p class="eyebrow">Админка</p>
|
||||
<h1>Категории</h1>
|
||||
<p class="muted">Первичная форма для добавления категории или подкатегории.</p>
|
||||
|
||||
<?php if ($message !== null): ?>
|
||||
<div class="status-box success"><?= e($message) ?></div>
|
||||
<?php endif; ?>
|
||||
<?php if ($error !== null): ?>
|
||||
<div class="status-box danger"><?= e($error) ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<form class="admin-form" method="post">
|
||||
<label>
|
||||
Родительская категория
|
||||
<select name="parent_id">
|
||||
<option value="">Без родителя</option>
|
||||
<?php foreach ($flatCategories as $category): ?>
|
||||
<option value="<?= e((string) $category['id']) ?>"><?= e($category['name']) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
Название
|
||||
<input name="name" required>
|
||||
</label>
|
||||
<label>
|
||||
Slug
|
||||
<input name="slug" required>
|
||||
</label>
|
||||
<label>
|
||||
H1
|
||||
<input name="h1">
|
||||
</label>
|
||||
<label>
|
||||
Title
|
||||
<input name="seo_title">
|
||||
</label>
|
||||
<label>
|
||||
Description
|
||||
<textarea name="seo_description" rows="3"></textarea>
|
||||
</label>
|
||||
<label>
|
||||
Keywords
|
||||
<textarea name="seo_keywords" rows="2"></textarea>
|
||||
</label>
|
||||
<label>
|
||||
Сортировка
|
||||
<input name="sort_order" type="number" value="100">
|
||||
</label>
|
||||
<label class="checkbox-label">
|
||||
<input name="is_active" type="checkbox" checked>
|
||||
Активна
|
||||
</label>
|
||||
<button class="primary-button" type="submit">Добавить категорию</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="panel">
|
||||
<h2>Текущее дерево</h2>
|
||||
<?php
|
||||
$items = $categories;
|
||||
$depth = 0;
|
||||
$currentSlug = '';
|
||||
require base_path('views/partials/category-tree.php');
|
||||
?>
|
||||
</div>
|
||||
</section>
|
||||
@@ -0,0 +1,120 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/** @var array<int, array<string, mixed>> $categories */
|
||||
/** @var array<int, array<string, mixed>> $products */
|
||||
/** @var string|null $message */
|
||||
/** @var string|null $error */
|
||||
?>
|
||||
<section class="admin-layout">
|
||||
<div class="panel">
|
||||
<p class="eyebrow">Админка</p>
|
||||
<h1>Товары</h1>
|
||||
<p class="muted">Первичная форма для добавления товара с одной стартовой фасовкой.</p>
|
||||
|
||||
<?php if ($message !== null): ?>
|
||||
<div class="status-box success"><?= e($message) ?></div>
|
||||
<?php endif; ?>
|
||||
<?php if ($error !== null): ?>
|
||||
<div class="status-box danger"><?= e($error) ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<form class="admin-form" method="post">
|
||||
<label>
|
||||
Категория
|
||||
<select name="category_id">
|
||||
<option value="">Без категории</option>
|
||||
<?php foreach ($categories as $category): ?>
|
||||
<option value="<?= e((string) $category['id']) ?>"><?= e($category['name']) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
Название
|
||||
<input name="name" required>
|
||||
</label>
|
||||
<label>
|
||||
Slug
|
||||
<input name="slug" required>
|
||||
</label>
|
||||
<label>
|
||||
Краткое описание
|
||||
<textarea name="short_description" rows="3"></textarea>
|
||||
</label>
|
||||
<label>
|
||||
Title
|
||||
<input name="seo_title">
|
||||
</label>
|
||||
<label>
|
||||
Description
|
||||
<textarea name="seo_description" rows="3"></textarea>
|
||||
</label>
|
||||
<label>
|
||||
Keywords
|
||||
<textarea name="seo_keywords" rows="2"></textarea>
|
||||
</label>
|
||||
<label>
|
||||
Базовая единица
|
||||
<select name="base_unit">
|
||||
<option value="kg">кг</option>
|
||||
<option value="g">г</option>
|
||||
<option value="l">л</option>
|
||||
<option value="ml">мл</option>
|
||||
<option value="pcs">шт</option>
|
||||
<option value="pack">упаковка</option>
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
Название фасовки
|
||||
<input name="variant_name" placeholder="Например: короб 10 кг">
|
||||
</label>
|
||||
<label>
|
||||
Количество в фасовке
|
||||
<input name="package_quantity" type="number" step="0.001" value="1">
|
||||
</label>
|
||||
<label>
|
||||
Шаг количества
|
||||
<input name="step_quantity" type="number" step="0.001" value="1">
|
||||
</label>
|
||||
<label>
|
||||
Цена за единицу
|
||||
<input name="price_per_unit" type="number" step="0.01">
|
||||
</label>
|
||||
<label>
|
||||
Цена за фасовку
|
||||
<input name="package_price" type="number" step="0.01" value="0">
|
||||
</label>
|
||||
<label>
|
||||
Старая цена для перечеркивания
|
||||
<input name="old_package_price" type="number" step="0.01" placeholder="Например: 4550">
|
||||
</label>
|
||||
<label>
|
||||
Скидка в процентах
|
||||
<input name="discount_percent" type="number" min="0" max="99" step="0.01" placeholder="Например: 10">
|
||||
</label>
|
||||
<label class="checkbox-label">
|
||||
<input name="is_published" type="checkbox">
|
||||
Опубликовать
|
||||
</label>
|
||||
<button class="primary-button" type="submit">Добавить товар</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="panel">
|
||||
<h2>Последние товары</h2>
|
||||
<?php if ($products === []): ?>
|
||||
<p class="muted">Товаров пока нет.</p>
|
||||
<?php else: ?>
|
||||
<div class="admin-list">
|
||||
<?php foreach ($products as $product): ?>
|
||||
<div>
|
||||
<strong><?= e($product['name']) ?></strong>
|
||||
<span><?= e($product['category_name'] ?? 'Без категории') ?></span>
|
||||
<span><?= !empty($product['is_published']) ? 'Опубликован' : 'Черновик' ?></span>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</section>
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/** @var array<int, array<string, mixed>> $categories */
|
||||
/** @var array<string, mixed>|null $currentCategory */
|
||||
/** @var array<int, array<string, mixed>> $products */
|
||||
|
||||
$heading = $currentCategory['h1'] ?? $currentCategory['name'] ?? 'Каталог продукции';
|
||||
?>
|
||||
<section class="catalog-layout">
|
||||
<aside class="catalog-sidebar">
|
||||
<div class="panel sticky-panel">
|
||||
<h2>Категории</h2>
|
||||
<?php
|
||||
$items = $categories;
|
||||
$depth = 0;
|
||||
$currentSlug = $currentCategory['slug'] ?? '';
|
||||
require base_path('views/partials/category-tree.php');
|
||||
?>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<section class="catalog-content">
|
||||
<div class="catalog-heading">
|
||||
<p class="eyebrow"><?= $currentCategory === null ? 'Полный каталог' : 'Категория' ?></p>
|
||||
<h1><?= e($heading) ?></h1>
|
||||
<?php if (!empty($currentCategory['description'])): ?>
|
||||
<p><?= e($currentCategory['description']) ?></p>
|
||||
<?php else: ?>
|
||||
<p class="muted">Выберите категорию слева или смотрите товары сразу в каталоге.</p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php if ($products === []): ?>
|
||||
<div class="panel">
|
||||
<p class="muted">В этой категории товары появятся после переноса каталога со старого сайта.</p>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<div class="product-grid catalog-product-grid">
|
||||
<?php foreach ($products as $product): ?>
|
||||
<?php require base_path('views/partials/product-card.php'); ?>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</section>
|
||||
</section>
|
||||
@@ -0,0 +1,6 @@
|
||||
<section class="start-screen">
|
||||
<p class="eyebrow">404</p>
|
||||
<h1>Страница не найдена</h1>
|
||||
<p>Возможно, ссылка изменилась. Вернитесь в каталог или на главную страницу.</p>
|
||||
<p><a class="primary-link" href="/catalog">Перейти в каталог</a></p>
|
||||
</section>
|
||||
+3
-38
@@ -34,6 +34,7 @@ declare(strict_types=1);
|
||||
<?php
|
||||
$items = $categories;
|
||||
$depth = 0;
|
||||
$currentSlug = '';
|
||||
require base_path('views/partials/category-tree.php');
|
||||
?>
|
||||
<?php endif; ?>
|
||||
@@ -76,27 +77,7 @@ declare(strict_types=1);
|
||||
<?php else: ?>
|
||||
<div class="product-grid">
|
||||
<?php foreach ($catalogProducts as $product): ?>
|
||||
<article class="product-card">
|
||||
<p class="product-category"><?= e($product['category_name'] ?? 'Каталог') ?></p>
|
||||
<h3><?= e($product['name']) ?></h3>
|
||||
<p><?= e($product['variant_name'] ?? 'Выберите доступную фасовку') ?></p>
|
||||
<?php if ($product['price_per_unit'] !== null): ?>
|
||||
<span class="price-line">за <?= e($product['unit'] ?? 'ед.') ?>: <?= e((string) $product['price_per_unit']) ?> руб.</span>
|
||||
<?php endif; ?>
|
||||
<?php if ($product['package_price'] !== null): ?>
|
||||
<strong><?= e((string) $product['package_price']) ?> руб. за фасовку</strong>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($product['price_tiers'])): ?>
|
||||
<ul class="price-tiers">
|
||||
<?php foreach ($product['price_tiers'] as $tier): ?>
|
||||
<li>
|
||||
<span><?= e($tier['label']) ?></span>
|
||||
<strong><?= e((string) $tier['price']) ?> руб.</strong>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
<?php endif; ?>
|
||||
</article>
|
||||
<?php require base_path('views/partials/product-card.php'); ?>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
@@ -109,23 +90,7 @@ declare(strict_types=1);
|
||||
<?php else: ?>
|
||||
<div class="product-grid">
|
||||
<?php foreach ($popularProducts as $product): ?>
|
||||
<article class="product-card">
|
||||
<h3><?= e($product['name']) ?></h3>
|
||||
<p><?= e($product['variant_name'] ?? 'Фасовка не выбрана') ?></p>
|
||||
<?php if ($product['package_price'] !== null): ?>
|
||||
<strong><?= e((string) $product['package_price']) ?> руб.</strong>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($product['price_tiers'])): ?>
|
||||
<ul class="price-tiers">
|
||||
<?php foreach ($product['price_tiers'] as $tier): ?>
|
||||
<li>
|
||||
<span><?= e($tier['label']) ?></span>
|
||||
<strong><?= e((string) $tier['price']) ?> руб.</strong>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
<?php endif; ?>
|
||||
</article>
|
||||
<?php require base_path('views/partials/product-card.php'); ?>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
@@ -4,20 +4,54 @@ declare(strict_types=1);
|
||||
|
||||
/** @var array<int, array<string, mixed>> $items */
|
||||
/** @var int $depth */
|
||||
/** @var string $currentSlug */
|
||||
|
||||
if (!function_exists('category_tree_has_current')) {
|
||||
/**
|
||||
* @param array<string, mixed> $category
|
||||
*/
|
||||
function category_tree_has_current(array $category, string $currentSlug): bool
|
||||
{
|
||||
if ($currentSlug === '') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (($category['slug'] ?? '') === $currentSlug) {
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach (($category['children'] ?? []) as $child) {
|
||||
if (category_tree_has_current($child, $currentSlug)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
?>
|
||||
<ul class="category-list depth-<?= e((string) $depth) ?>">
|
||||
<?php foreach ($items as $category): ?>
|
||||
<li>
|
||||
<a href="/catalog/<?= e($category['slug']) ?>"><?= e($category['name']) ?></a>
|
||||
<?php if (!empty($category['children'])): ?>
|
||||
<?php
|
||||
$previousItems = $items;
|
||||
$items = $category['children'];
|
||||
$depth++;
|
||||
require __DIR__ . '/category-tree.php';
|
||||
$depth--;
|
||||
$items = $previousItems;
|
||||
?>
|
||||
<?php $isCurrent = ($currentSlug ?? '') === $category['slug']; ?>
|
||||
<?php $hasChildren = !empty($category['children']); ?>
|
||||
<?php $shouldOpen = category_tree_has_current($category, $currentSlug ?? ''); ?>
|
||||
<li class="<?= $isCurrent ? 'is-current' : '' ?>">
|
||||
<?php if ($hasChildren): ?>
|
||||
<details class="category-details" <?= $shouldOpen ? 'open' : '' ?>>
|
||||
<summary>
|
||||
<a class="<?= $isCurrent ? 'is-current-link' : '' ?>" href="/catalog/<?= e($category['slug']) ?>"><?= e($category['name']) ?></a>
|
||||
</summary>
|
||||
<?php
|
||||
$previousItems = $items;
|
||||
$items = $category['children'];
|
||||
$depth++;
|
||||
require __DIR__ . '/category-tree.php';
|
||||
$depth--;
|
||||
$items = $previousItems;
|
||||
?>
|
||||
</details>
|
||||
<?php else: ?>
|
||||
<a href="/catalog/<?= e($category['slug']) ?>"><?= e($category['name']) ?></a>
|
||||
<?php endif; ?>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/** @var array<string, mixed> $product */
|
||||
?>
|
||||
<article class="product-card">
|
||||
<p class="product-category"><?= e($product['category_name'] ?? 'Каталог') ?></p>
|
||||
<h3><a href="/product/<?= e($product['slug']) ?>"><?= e($product['name']) ?></a></h3>
|
||||
<p><?= e($product['variant_name'] ?? 'Выберите доступную фасовку') ?></p>
|
||||
|
||||
<?php if ($product['price_per_unit'] !== null): ?>
|
||||
<span class="price-line">за <?= e($product['unit'] ?? 'ед.') ?>: <?= e((string) $product['price_per_unit']) ?> руб.</span>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($product['package_price'] !== null): ?>
|
||||
<strong>
|
||||
<?php if (!empty($product['old_package_price'])): ?>
|
||||
<span class="old-price"><?= e((string) $product['old_package_price']) ?> руб.</span>
|
||||
<?php endif; ?>
|
||||
<?= e((string) $product['package_price']) ?> руб. за фасовку
|
||||
</strong>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($product['price_tiers'])): ?>
|
||||
<ul class="price-tiers">
|
||||
<?php foreach ($product['price_tiers'] as $tier): ?>
|
||||
<li>
|
||||
<span><?= e($tier['label']) ?></span>
|
||||
<strong><?= e((string) $tier['price']) ?> руб.</strong>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
<?php endif; ?>
|
||||
</article>
|
||||
Reference in New Issue
Block a user