04. Админка каталога — ТЗ¶
React 18 + TypeScript + Ant Design 5, отдельное SPA, работает с /api/admin/v1.
4.1. Принципы¶
- Разделение источника и витрины видно глазами. Всё, что пришло из iiko, отображается с пометкой «из iiko» и недоступно для ввода. Попытка «поправить цену в админке» не должна даже выглядеть возможной.
- Ни одного «зависшего» блюда. Любая позиция iiko находится ровно в одном из трёх состояний: привязана к товару, помечена «не для витрины», или лежит в очереди новых. Счётчик очереди — в главном меню.
- Работа списками, а не по одной карточке. Массовое назначение категории, массовая публикация, массовая загрузка картинок — базовые операции, а не «когда-нибудь потом».
- Сортировка мышью. Порядок категорий и товаров внутри категории — drag & drop, с сохранением одной кнопкой и явным индикатором несохранённых изменений.
- Изменения обратимы. Публикация/снятие — переключатель, история — в аудите, удаление — архивирование.
4.2. Навигация¶
Каталог
├── Категории
├── Товары
├── Новые позиции iiko ● 3 ← бейдж с количеством NEW
├── Проблемы привязок ● 2 ← бейдж с открытыми инцидентами
├── Модификаторы
└── Бейджи
Данные iiko
├── Импорты ● ошибка ← бейдж при неуспешном последнем импорте
├── Внешние меню
├── Организации
├── Терминалы
├── Номенклатура iiko (read-only справочник)
└── Стоп-листы
Медиа
└── Библиотека изображений
Система
├── Пользователи
├── Роли
├── Аудит
└── Настройки
4.3. Роли и права¶
| Право | ADMIN | CONTENT_MANAGER | VIEWER |
|---|---|---|---|
catalog.read |
✓ | ✓ | ✓ |
catalog.write (категории, товары, сортировка, медиа) |
✓ | ✓ | |
catalog.publish |
✓ | ✓ | |
iiko.read |
✓ | ✓ | ✓ |
iiko.sync.run (ручной импорт) |
✓ | ||
iiko.settings.write (внешние меню, ценовые категории) |
✓ | ||
platform.users.write, platform.audit.read |
✓ |
Права проверяются на бэкенде; фронт по тем же правам скрывает кнопки (GET /api/admin/v1/me
возвращает список прав).
4.4. Экран «Категории»¶
┌─ Категории ────────────────────────────── [+ Категория] [Сохранить порядок] ─┐
│ ⣿ ▸ Пиццы /pizzy 42 товара ● активна ⋮ │
│ ⣿ ▾ Роллы /rolly 30 товаров ● активна ⋮ │
│ ⣿ ├─ Классические /rolly/klass 18 товаров ● активна ⋮ │
│ ⣿ └─ Запечённые /rolly/zap 12 товаров ○ скрыта ⋮ │
│ ⣿ ▸ Напитки /napitki 35 товаров ● активна ⋮ │
│ ⣿ ▸ Соусы /sousy 0 товаров ○ скрыта ⋮ │
└──────────────────────────────────────────────────────────────────────────────┘
- Дерево с drag & drop (перетаскивание меняет и порядок, и родителя), максимум 2 уровня.
- Кнопка «Сохранить порядок» активна только при изменениях; уход со страницы — предупреждение.
- В меню
⋮: редактировать, добавить подкатегорию, сортировать товары внутри, архивировать. - Категория с товарами не удаляется — только архивируется, с подсказкой сколько товаров осиротеет.
Форма категории: название, slug (генерируется из названия, редактируемый, проверка занятости на лету), родитель, краткое и полное описание, изображение и иконка (drag & drop, кроп), активность, «показывать в главном меню», SEO-блок (title, description, H1).
4.5. Экран «Товары»¶
┌─ Товары ──────────────────────────────────────────── [+ Товар] [Импорт ↻] ─┐
│ Поиск: [ панам ] Категория: [Пиццы ▾] Статус: [Все ▾] │
│ Быстрые фильтры: (Без картинки 12) (Не в категории 5) (Проблемы iiko 2) │
├───┬──────────┬──────────────┬──────────┬──────────────┬─────────┬──────────┤
│ ☐ │ фото │ Название │ Категория│ Варианты │ Цена от │ Статус │
├───┼──────────┼──────────────┼──────────┼──────────────┼─────────┼──────────┤
│ ☐ │ [img] │ Панам │ Пиццы │ 25/30/35/40 │ 530 ₽ │ ● Опубл. │
│ ☐ │ [img] │ Пепперони │ Пиццы │ 25/30/35/40 │ 530 ₽ │ ● Опубл. │
│ ☐ │ ⚠ нет │ Бургер Пицца │ Пиццы │ 25/30 │ 610 ₽ │ ○ Черн. │
│ ☐ │ [img] │ Филадельфия │ Роллы │ — │ 690 ₽ │ ● Опубл. │
└───┴──────────┴──────────────┴──────────┴──────────────┴─────────┴──────────┘
Выбрано 3: [Опубликовать] [Снять] [В категорию…] [Бейджи…] [Архивировать]
Колонки настраиваются, состояние таблицы (фильтры, сортировка, страница) — в query-параметрах URL, чтобы ссылку на отфильтрованный список можно было переслать коллеге.
Фильтры: текст (по названию товара, названию блюда iiko, SKU), категория, статус, наличие изображения, наличие вариантов, бейдж, «есть расхождение с iiko», организация (где виден).
4.6. Карточка товара¶
Вкладки: Основное · Варианты · Изображения · Модификаторы · Размещение · Видимость · SEO · История
Правая колонка постоянно показывает панель публикации:
┌ Публикация ───────────────┐
│ Статус: Черновик │
│ │
│ Чтобы опубликовать: │
│ ✓ есть вариант │
│ ✓ вариант привязан │
│ ✗ нет главного фото │
│ ✓ размещён в категории │
│ │
│ [ Опубликовать ] (disabled)
│ Последнее изменение: │
│ Иванов, 12.08 14:03 │
└───────────────────────────┘
Основное¶
Название, подзаголовок, slug, витринное описание, состав, бейджи, «секретный товар» (доступен по прямой ссылке, не в каталоге).
Под полем описания — блок «из iiko» со значением, которое будет использовано, если поле пустое:
Описание (пусто → возьмётся из iiko)
┌──────────────────────────────────────────────────────────┐
│ │
└──────────────────────────────────────────────────────────┘
из iiko: «Креветки, моцарелла, креметта, руккола, черри, соус
томатный, пармезан КБЖУ на 100гр: б:9г,ж:9г,у:21г,ккал: 196»
[Подставить очищенный текст] ← парсер отрезает КБЖУ-хвост
Варианты — ключевой экран¶
Здесь карточка связывается с блюдами iiko. Ось вариативности выбирается один раз («Размер»), дальше добавляются варианты.
┌ Варианты ─────── Ось: [Размер ▾] ───────────── [+ Добавить вариант] ─┐
│ ⣿ 25 см ▾ Панам 25 см SKU 219971 530 ₽ ● вкл ✓ по умолч. │
│ ⣿ 30 см ▾ Панам 30 см SKU 219972 730 ₽ ● вкл ⋮ │
│ ⣿ 35 см ▾ Панам 35 см SKU 219973 930 ₽ ● вкл ⋮ │
│ ⣿ 40 см ▾ Панам 40 см SKU 219974 1130 ₽ ● вкл ⋮ │
└────────────────────────────────────────────────────────────────────────────┘
Цены — из iiko, организация: [Панам Пермь, ул. Ленина ▾]
Диалог «Добавить вариант» — поиск по непривязанным позициям iiko:
┌ Привязать блюдо iiko ──────────────────────────────────────────┐
│ Поиск: [ панам ] Только непривязанные ☑ │
│ │
│ Подсказка: похоже, это варианты одного товара │
│ ☑ Панам 25 см SKU 219971 Пицца 25 см 530 ₽ │
│ ☑ Панам 30 см SKU 219972 Пицца 30 см 730 ₽ │
│ ☑ Панам 35 см SKU 219973 Пицца 35 см 930 ₽ │
│ ☑ Панам 40 см SKU 219974 Пицца 40 см 1130 ₽ │
│ │
│ Все результаты │
│ ☐ Панамыч 25 см SKU 221196 Пицца 25 см 560 ₽ │
│ ☐ Панамыч 30 см … │
│ [Отмена] [Привязать (4)] │
└────────────────────────────────────────────────────────────────┘
Алгоритм автоподсказки (только подсказка, решение всегда за человеком):
- Нормализация названия: нижний регистр, отбрасывание хвоста по словарю размеров
(
\s*\d{2}\s*см$,\s*\d+(\.\d+)?\s*л$,\s*\d+\s*г$), схлопывание пробелов, «ё»→«е». - Кандидаты — непривязанные позиции с той же нормализованной базой.
- Дополнительный сигнал — общий префикс SKU и одна учётная группа
productCategoryId. - Размер извлекается из отброшенного хвоста и сопоставляется с
variant_option.code. - При точном совпадении базы и различии только в размере — чекбоксы предзаполнены.
Правила экрана:
- Позиция, уже привязанная к другому товару, показывается заблокированной со ссылкой на товар —
ограничение unique (iiko_item_size_id) объясняется, а не выдаётся как ошибка после сохранения.
- Ровно один вариант помечен «по умолчанию»; при удалении такого — метка переносится на первый.
- Если позиция удалена из iiko, строка варианта краснеет: «блюдо отсутствует в меню iiko с 12.08»,
рядом — последнее известное состояние из снимка привязки (название, SKU, цена) и кнопка
«Перепривязать» с предложенным кандидатом. Подробнее — §4.8.
Изображения¶
Требования к нарезке, пресетам, валидации исходников и предпросмотру — в отдельном ТЗ 07-media.md. Здесь — только состав экрана.
- Главное фото, фото при наведении, галерея; drag & drop загрузка, сортировка мышью.
- Кроп с фиксированными соотношениями под пресеты витрины, точка фокуса.
- Предпросмотр во всех пресетах сразу — как картинка будет выглядеть в плитке каталога, на детальной странице и в корзине. Это главное средство против «загрузил — а на сайте обрезано».
- Возможность задать своё фото конкретному варианту (у пиццы 25 и 40 см может быть разное фото).
- Выбор из общей медиабиблиотеки без повторной загрузки; дедупликация по sha256.
- Предупреждение, если исходник меньше требуемого разрешения пресета
detail@2x.
Модификаторы¶
Read-only состав из iiko + витринные настройки:
┌ Модификаторы (из iiko, состав менять нельзя) ───────────────────────────────┐
│ ▾ Пицца Тесто 25 выбор 1 из 3, обязательно │
│ Отображать как: [ Тесто ] Вид: [Переключатель ▾] │
│ ├ ⣿ Традиционное +0 ₽ [фото] ● показывать │
│ ├ ⣿ Тонкое +0 ₽ [фото] ● показывать │
│ └ ⣿ Пышное +50 ₽ [фото] ● показывать │
│ ▾ Без ингредиента 0..10 │
│ Отображать как: [ Убрать из состава ] Вид: [Чекбоксы ▾] │
│ ⚠ 24 позиции — рекомендуется свернуть по умолчанию ☑ свернуть │
└──────────────────────────────────────────────────────────────────────────────┘
Настройки хранятся в catalog.modifier_group_profile / catalog.modifier_profile по ключу
группы/позиции iiko и применяются ко всем товарам сразу — иначе название «Пицца Тесто 25» пришлось
бы переименовывать в каждой из 20 пицц.
Размещение¶
Список категорий с чекбоксами, одна помечена основной (участвует в URL и хлебных крошках), рядом — позиция товара в каждой категории с возможностью перейти к сортировке категории.
Видимость¶
Таблица организаций с переключателем «показывать». По умолчанию видно везде, где у блюда есть цена. Строки, где цены в iiko нет, показаны отдельно: «нет цены в этой организации — не будет отображаться независимо от настройки».
История¶
Записи аудита по товару: кто, когда, какие поля, до/после.
4.7. Экран «Новые позиции iiko»¶
Главный инструмент против «блюдо появилось в iiko и потерялось».
┌─ Новые позиции iiko ───────────────────── найдено при импорте 12.08 04:30 ─┐
│ ☐ │ Название │ SKU │ Категория iiko │ Цена │ Действие │
│ ☐ │ Пицца Трюфельная 25 │ 2220401 │ Пицца 25 см │ 690 ₽ │ [Создать][…] │
│ ☐ │ Пицца Трюфельная 30 │ 2220402 │ Пицца 30 см │ 890 ₽ │ [Создать][…] │
│ ☐ │ Соус Сырный 30г │ 2219712 │ Соусы Доставка │ 60 ₽ │ [Создать][…] │
│ ☐ │ Коробка 25 │ 2210033 │ Прочее │ 0 ₽ │ [Не для сайта]│
└────────────────────────────────────────────────────────────────────────────┘
Выбрано 2: [Создать один товар с вариантами] [Добавить в существующий…] [Не для сайта]
Действия: создать товар (форма предзаполнена названием без суффикса размера, категория
предложена по маппингу категорий iiko), добавить вариантом к существующему товару, пометить
«не для сайта» (item_triage.status = IGNORED, с комментарием — тарные позиции, полуфабрикаты,
служебные блюда).
Выделение нескольких строк, отличающихся только размером, предлагает создать один товар с вариантами — это основной сценарий при появлении новой пиццы.
4.8. Экран «Проблемы привязок»¶
Обратная сторона экрана «Новые позиции»: там появляются блюда, здесь — пропадают. Закрывает реальный сценарий, когда сотрудник случайно удалил или пересоздал блюдо в iiko.
┌─ Проблемы привязок ──────────────────── открытых: 4 [Все ▾] [Только критичные] ─┐
│ ● КРИТИЧНО Блюдо удалено из iiko 12.08 04:30 │
│ Товар: Панам · вариант 30 см │
│ Было в iiko: «Панам 30 см», SKU 219972, 730 ₽ — последний раз 12.08 04:12 │
│ ▸ Найдена возможная замена: «Панам 30 см», SKU 219972, новый id (совпал SKU) │
│ [Перепривязать] [Отключить вариант] [Отложить] [Не важно] │
├───────────────────────────────────────────────────────────────────────────────────┤
│ ● КРИТИЧНО Товар снят с публикации: не осталось живых вариантов 12.08 04:30 │
│ Товар: Соус Сырный │
│ [Открыть товар] [Подобрать замену] [Не важно] │
├───────────────────────────────────────────────────────────────────────────────────┤
│ ○ ВНИМАНИЕ Пропала цена в организации «Панам Березники» 12.08 04:30 │
│ Товар: Пепперони · вариант 40 см │
├───────────────────────────────────────────────────────────────────────────────────┤
│ · ИНФО Название в iiko изменилось 11.08 04:30 │
│ Товар: Филадельфия. Было «Филадельфия», стало «Филадельфия классическая» │
│ [Принять для витрины] [Оставить своё] │
└───────────────────────────────────────────────────────────────────────────────────┘
Поведение:
- Ничего не удаляется. Карточка, изображения, тексты, SEO и позиция в категории остаются на месте, даже если блюдо в iiko исчезло полностью.
- Перепривязка в одно действие. Если блюдо пересоздали и оно пришло с новым
itemId, система сама предлагает кандидата (совпадение SKU — сильный сигнал, совпадение названия и учётной группы — слабый). Нажатие «Перепривязать» подставляет новую позицию в существующий вариант; прежний id сохраняется в истории привязок. - Ручной подбор. Если автоматика кандидата не нашла — тот же диалог поиска по номенклатуре, что и при добавлении варианта, но с предзаполненным запросом по последнему известному названию.
- Отложить — инцидент скрывается на сутки (частая ситуация: блюдо выключено временно).
- Не важно — закрытие с резолюцией
DISMISSEDи обязательным комментарием. - Товар с открытым критичным инцидентом помечается в списке товаров значком, фильтр «Проблемы iiko» на экране товаров ведёт сюда же.
- «Мигание» выгрузки (позиция пропала и вернулась в пределах окна ожидания) инцидента не создаёт — иначе экран превращается в шум и его перестают читать.
История инцидентов сохраняется после закрытия: по товару всегда видно, что с ним происходило, когда и кто принял решение.
4.9. Экран «Импорты»¶
┌─ Импорты ──────────────────────── [Запустить: Меню ▾] [Полный импорт] ─────┐
│ Меню 41188 ✓ SUCCESS 12.08 14:05 4.2 c rev 128390 +3 ~240 −1 │
│ Стоп-листы ✓ SUCCESS 12.08 14:05 0.3 c 12 позиций │
│ Меню 50865 ⊘ SKIPPED 12.08 14:00 ревизия не изменилась │
│ Меню 41188 ✗ FAILED 12.08 04:30 SUSPICIOUS_SHRINK: −38% позиций │
│ [Подробности] [Повторить] │
└────────────────────────────────────────────────────────────────────────────┘
Карточка запуска: параметры запроса, статистика по сущностям, лог этапов, текст ошибки, кнопка повтора. Вверху страниц каталога — предупреждающая полоса, если последний импорт неуспешен или стоп-листы не обновлялись больше 5 минут.
4.10. Экран «Внешние меню»¶
Настройка того, что в legacy было хардкодом: список внешних меню, для каждого — название, ценовая категория (выбор из справочника iiko), включённость, набор организаций, ревизия, время последнего успешного импорта, кнопка ручного запуска.
4.11. Справочные экраны (read-only)¶
- Организации — id, название, код, адрес, координаты, активность; редактируемых полей нет.
- Терминалы — по организациям, статус
is_alive. - Номенклатура iiko — плоский список всех позиций с фильтром по типу (блюдо/модификатор), учётной группе, привязанности к товару; из строки — переход к товару. Инструмент для ответа на вопрос «почему этого блюда нет на сайте».
- Стоп-листы — по организации и терминалу, с временем последнего обновления.
4.12. Экран «Модификаторы»¶
Плоский список всех позиций-модификаторов с их витринными профилями: название для сайта, фото, скрытие. Позволяет один раз загрузить фото ко всем соусам и топпингам, не заходя в каждую пиццу.
4.13. Медиабиблиотека¶
Сетка изображений, фильтр по папке и дате, поиск по имени, показ «где используется», удаление запрещено для используемых файлов, массовая загрузка.
4.14. Общие требования к UI¶
- Все списки — серверная пагинация, размер страницы 20/50/100, состояние в URL.
- Ошибки бэкенда (
problem+json) показываются полем формы, если естьfield, иначе — уведомлением. - Оптимистичная блокировка: при сохранении отправляется
version; при конфликте — диалог «запись изменена пользователем X, показать различия / перезагрузить». - Автосохранение не используется — только явное сохранение и предупреждение о несохранённых изменениях при уходе.
- Локализация: интерфейс на русском; строки вынесены в словарь, чтобы не переписывать код при появлении второго языка.
- Тёмная тема — по системной настройке (antd
theme.algorithm), без отдельного бэклога.