| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195 |
- <?php
- namespace App\Support;
- /*
- * BlockLayoutRegistry — реестр макетов блоков.
- *
- * Каждый макет задаёт набор полей, которые пользователь заполняет в админке.
- * Разработчик добавляет новый макет сюда и создаёт соответствующий Blade-шаблон
- * resources/views/blocks/{key}.blade.php для рендеринга на фронтенде.
- *
- * Типы полей:
- * text — однострочный текст
- * textarea — многострочный текст
- * url — ссылка
- * image — URL изображения
- * repeater — группа повторяющихся строк (sub_fields — список дочерних полей)
- */
- class BlockLayoutRegistry
- {
- public static function all(): array
- {
- // Встроенные макеты
- $builtin = [
- // ---------------------------------------------------------------
- // Блок «Почему мы» — секция с карточками преимуществ
- // ---------------------------------------------------------------
- 'why_us' => [
- 'title' => 'Почему мы',
- 'fields' => [
- ['name' => 'label', 'label' => 'Надпись над заголовком', 'type' => 'text'],
- ['name' => 'heading', 'label' => 'Заголовок секции', 'type' => 'text'],
- ['name' => 'items', 'label' => 'Карточки преимуществ', 'type' => 'repeater', 'sub_fields' => [
- ['name' => 'icon', 'label' => 'Иконка (emoji)', 'type' => 'text'],
- ['name' => 'title', 'label' => 'Заголовок карточки', 'type' => 'text'],
- ['name' => 'text', 'label' => 'Описание', 'type' => 'textarea'],
- ]],
- ],
- ],
- // ---------------------------------------------------------------
- // Блок «Этапы работ» — интерактивный JS-степпер
- // Шапка секции + repeater шагов (title, desc, image_url, image_alt)
- // Blade-шаблон сам генерирует nav-кнопки и STEPS-массив для JS
- // ---------------------------------------------------------------
- 'steps_section' => [
- 'title' => 'Этапы работ (степпер)',
- 'fields' => [
- ['name' => 'label', 'label' => 'Надпись над заголовком', 'type' => 'text'],
- ['name' => 'heading', 'label' => 'Заголовок секции', 'type' => 'text'],
- ['name' => 'subtext', 'label' => 'Описание секции', 'type' => 'textarea'],
- ['name' => 'steps', 'label' => 'Шаги', 'type' => 'repeater', 'sub_fields' => [
- ['name' => 'title', 'label' => 'Название шага', 'type' => 'text'],
- ['name' => 'desc', 'label' => 'Описание шага', 'type' => 'textarea'],
- // aspect-ratio 4/3 из CSS .step-right → cover-кроп 800×600
- ['name' => 'image_url', 'label' => 'Изображение', 'type' => 'image', 'width' => 800, 'height' => 600],
- ['name' => 'image_alt', 'label' => 'Alt изображения', 'type' => 'text'],
- ]],
- ],
- ],
- // ---------------------------------------------------------------
- // Блок «Главный баннер» — hero-секция с фото, заголовком, кнопками и статами
- // ---------------------------------------------------------------
- 'hero_banner' => [
- 'title' => 'Главный баннер (Hero)',
- 'fields' => [
- ['name' => 'eyebrow', 'label' => 'Надпись сверху (маленьким шрифтом)', 'type' => 'text'],
- ['name' => 'line1', 'label' => 'Заголовок — строка 1', 'type' => 'text'],
- ['name' => 'line2', 'label' => 'Заголовок — строка 2 (красная)', 'type' => 'text'],
- ['name' => 'line3', 'label' => 'Заголовок — строка 3', 'type' => 'text'],
- ['name' => 'subtext', 'label' => 'Подзаголовок', 'type' => 'textarea'],
- ['name' => 'btn1_text', 'label' => 'Кнопка 1 — текст', 'type' => 'text'],
- ['name' => 'btn1_url', 'label' => 'Кнопка 1 — ссылка', 'type' => 'url'],
- ['name' => 'btn2_text', 'label' => 'Кнопка 2 — текст', 'type' => 'text'],
- ['name' => 'btn2_url', 'label' => 'Кнопка 2 — ссылка', 'type' => 'url'],
- // aspect-ratio 16/9 → 1920×1080
- ['name' => 'image', 'label' => 'Фоновое изображение', 'type' => 'image', 'width' => 1920, 'height' => 1080],
- ['name' => 'show_quick_search', 'label' => 'Показать форму быстрого подбора', 'type' => 'checkbox'],
- ['name' => 'stats', 'label' => 'Статистика внизу баннера', 'type' => 'repeater', 'sub_fields' => [
- ['name' => 'value', 'label' => 'Число (напр. 500)', 'type' => 'text'],
- ['name' => 'suffix', 'label' => 'Суффикс красным (+ / к / лет)', 'type' => 'text'],
- ['name' => 'label', 'label' => 'Подпись', 'type' => 'text'],
- ]],
- ],
- ],
- // ---------------------------------------------------------------
- // Блок «Витрина автомобилей» — выбор конкретных авто из каталога
- // ---------------------------------------------------------------
- 'featured_cars' => [
- 'title' => 'Витрина автомобилей',
- 'fields' => [
- ['name' => 'label', 'label' => 'Надпись над заголовком', 'type' => 'text'],
- ['name' => 'heading', 'label' => 'Заголовок секции', 'type' => 'text'],
- // cars_picker — мультиселект авто из таблицы cars
- ['name' => 'car_ids', 'label' => 'Автомобили (выбрать из каталога)', 'type' => 'cars_picker'],
- ],
- ],
- // ---------------------------------------------------------------
- // Блок «Марки автомобилей» — сетка марок с выбором из БД
- // ---------------------------------------------------------------
- 'brands_grid' => [
- 'title' => 'Сетка марок',
- 'fields' => [
- ['name' => 'label', 'label' => 'Надпись над заголовком', 'type' => 'text'],
- ['name' => 'heading', 'label' => 'Заголовок секции', 'type' => 'text'],
- // makes_picker — чекбоксы марок из таблицы cars
- ['name' => 'makes', 'label' => 'Марки для отображения', 'type' => 'makes_picker'],
- ],
- ],
- // ---------------------------------------------------------------
- // Блок «CTA-полоса» — тёмный баннер с призывом к действию
- // ---------------------------------------------------------------
- 'cta_banner' => [
- 'title' => 'CTA-полоса (призыв к действию)',
- 'fields' => [
- ['name' => 'title', 'label' => 'Заголовок', 'type' => 'text'],
- ['name' => 'subtext', 'label' => 'Подзаголовок', 'type' => 'textarea'],
- ['name' => 'btn1_text', 'label' => 'Кнопка 1 — текст', 'type' => 'text'],
- ['name' => 'btn1_url', 'label' => 'Кнопка 1 — ссылка', 'type' => 'url'],
- ['name' => 'btn2_text', 'label' => 'Кнопка 2 — текст', 'type' => 'text'],
- ['name' => 'btn2_url', 'label' => 'Кнопка 2 — ссылка', 'type' => 'url'],
- ],
- ],
- // ---------------------------------------------------------------
- // Блок «Сетка услуг» — список услуг компании с ссылками на детальные страницы.
- // Режимы: выбрать конкретные услуги (services_picker) или показать последние N.
- // ---------------------------------------------------------------
- 'services_grid' => [
- 'title' => 'Сетка услуг',
- 'fields' => [
- ['name' => 'label', 'label' => 'Надпись над заголовком', 'type' => 'text'],
- ['name' => 'heading', 'label' => 'Заголовок секции', 'type' => 'text'],
- ['name' => 'subtext', 'label' => 'Описание секции', 'type' => 'textarea'],
- // services_picker — чекбоксы услуг; если пусто — берутся последние по limit
- ['name' => 'service_ids', 'label' => 'Конкретные услуги (пусто = последние N)', 'type' => 'services_picker'],
- ['name' => 'limit', 'label' => 'Максимум (если не выбраны конкретные)', 'type' => 'text'],
- ],
- ],
- // ---------------------------------------------------------------
- // Блок «Отзывы клиентов» — сетка карточек отзывов из таблицы reviews
- // Отзывы управляются через раздел «Отзывы» в AdminLTE (ReviewAdminController).
- // Здесь выбираются конкретные записи для вывода в блоке.
- // ---------------------------------------------------------------
- 'reviews' => [
- 'title' => 'Отзывы клиентов',
- 'fields' => [
- ['name' => 'label', 'label' => 'Надпись над заголовком', 'type' => 'text'],
- ['name' => 'heading', 'label' => 'Заголовок секции', 'type' => 'text'],
- // reviews_picker — выбор активных отзывов из таблицы reviews
- ['name' => 'review_ids', 'label' => 'Отзывы для отображения в блоке', 'type' => 'reviews_picker'],
- ],
- ],
- ];
- // Сгенерированные макеты — файлы из app/Support/layouts/*.php
- $custom = [];
- $dir = app_path('Support/layouts');
- if (is_dir($dir)) {
- foreach (glob($dir.'/*.php') as $file) {
- $key = pathinfo($file, PATHINFO_FILENAME);
- if (! isset($builtin[$key])) {
- $custom[$key] = require $file;
- }
- }
- }
- return array_merge($builtin, $custom);
- }
- // Получить определение макета по ключу (null если не найден)
- public static function get(string $key): ?array
- {
- return static::all()[$key] ?? null;
- }
- // Проверить, существует ли макет
- public static function exists(string $key): bool
- {
- return isset(static::all()[$key]);
- }
- // Список ключей для валидации (in:key1,key2,...)
- public static function keys(): array
- {
- return array_keys(static::all());
- }
- }
|