Внешние меню — Список

Роут: /external-menus API: GET /api/v1/external-menus

Источник

BR 4.1 Бизнес-спека: Внешние меню

Список внешних меню франшизы. Видна владельцу при external_menus.read. Создание/редактирование требует external_menus.edit.


Что видит пользователь

Страница со списком меню в виде таблицы. Вверху — заголовок «Внешние меню», переключатель вкладок (Активные / Корзина), строка поиска и фильтры. Справа от заголовка — кнопка «Добавить меню».


Вкладки

ВкладкаЧто показаноУсловие видимости
Активные (default)status IN (draft, published)Всегда для external_menus.read
Корзинаstatus = archived (за последние 30 дней)Только при external_menus.edit, badge с числом если есть

Переключение через query param: /external-menus?archived=true.


Таблица

Колонки

КолонкаДанныеПримечание
ИмяnameКликабельное — переход в редактор /external-menus/{id}/edit. Если канал tv_screen — рядом мини-иконка монитора, если json — иконка {}
КаналchannelБейдж: «Монитор» / «JSON» (в BR 4.2 + «Yandex.Eda» / «Коала»)
ТТstore_nameЕсли store_id IS NULL → текст «Вся сеть» серым курсивом
ШаблонtemplateТолько для tv_screen: «Сетка» / «Слайдер» / «Список». Для json — прочерк
СтатусstatusБейдж: «Черновик» (серый) / «Опубликовано» (зелёный) / «В архиве» (красный, только в Корзине)
Дата измененияupdated_atОтносительный формат («3 мин назад» / «вчера в 14:32» / «12 апр.»)
ДействияМеню с действиями (см. ниже)

Особенности отображения

  • Опубликованные меню — рядом с именем кнопка «📋 Скопировать live URL» (видна при external_menus.read). При клике копирует live_url в буфер + toast «Ссылка скопирована»
  • Архивные меню в Корзине — серый цвет, плюс отдельная колонка «Удалено» с датой archived_at и обратным отсчётом «Будет удалено через N дней»
  • Меню без items — индикатор «(пусто)» серым после имени, чтобы напомнить владельцу что нечего публиковать

Фильтры

ФильтрТипЗначенияDefault
КаналSelectВсе / Монитор / JSONВсе (query param channel)
СтатусSelectВсе / Черновик / ОпубликованоВсе (только во вкладке «Активные»)
Торговая точкаSelectВсе / список ТТ + «Вся сеть»Все

Фильтры применяются мгновенно (без кнопки «Применить»). При смене фильтра — сброс на page=1.


Поиск

  • Поле ввода с placeholder «Поиск по имени»
  • Поиск с debounce (300 ms)
  • Query param: search
  • Ищет по name (на бэкенде, ILIKE)

Сортировка

  • По дате изменения (default: sort=updated_desc)
  • Клик по заголовку колонки «Имя» переключает A-Я / Я-A
  • Клик по «Дата изменения» переключает desc / asc

Пагинация

  • 20 записей на страницу
  • Постраничная навигация внизу (номера страниц + стрелки)
  • Query params: page, per_page

Действия

Кнопки в шапке

КнопкаДействиеВидимость
«Добавить меню» (primary)Открывает мастер создания (см. ниже)external_menus.edit
«Корзина»Переключает вкладку на архивныеexternal_menus.edit AND есть архивные за последние 30 дней

Меню действий строки (вкладка «Активные»)

ДействиеВидимостьЧто происходит
Открыть редакторВсегда (external_menus.read)Переход в /external-menus/{id}/edit
Опубликоватьexternal_menus.edit AND status=draft AND есть itemsPOST /publish → toast «Опубликовано», live URL стал активен
Снять с публикацииexternal_menus.edit AND status=publishedМодалка подтверждения → POST /unpublish
Скопировать live URLstatus=published AND channel=tv_screenКопирует URL + toast
Скачать offline ZIPexternal_menus.edit AND status=published AND channel=tv_screenGET /export.zip → скачивание файла
Скачать JSONexternal_menus.read AND status=published AND channel=jsonGET /export.json → скачивание файла
Превью на новой вкладкеexternal_menus.read AND channel=tv_screenОткрывает /r/{slug}?preview=1 в новой вкладке
Дублироватьexternal_menus.editМодалка с подтверждением → POST /duplicate → переход в редактор копии
Удалить (в корзину)external_menus.editМодалка подтверждения → DELETE

Меню действий строки (вкладка «Корзина»)

ДействиеВидимостьЧто происходит
Восстановитьexternal_menus.editPOST /restore → меню в draft, переход в редактор
ПодробнееВсегдаRead-only просмотр (тот же редактор, но только смотреть)

Hard delete недоступен ручной

Удаление из корзины — только автоматическое cron’ом по истечении 30 дней. Это упрощение для MVP.


Создание меню (мастер)

Триггер: клик на «Добавить меню»

Реализация: Двухшаговый wizard в модалке ИЛИ полноэкранный (на выбор UX-проектировщика — рекомендую модалку).

Шаг 1: Базовая информация

┌─ Создание меню — Шаг 1/2 ────────────────────────┐
│                                                  │
│  Имя *                                          │
│  [_________________________]                     │
│                                                  │
│  Канал *                                        │
│  ◉ Монитор кассы (tv_screen)                    │
│  ○ JSON-экспорт (json)                          │
│                                                  │
│  Привязка к ТТ                                  │
│  [Все ТТ (на всю сеть) ▾]                       │
│                                                  │
│  Шаблон* (только для Монитора)                  │
│  ◉ Сетка карточек                               │
│  ○ Слайдер                                      │
│  ○ Список                                       │
│                                                  │
│  [Отмена]                       [Далее →]       │
└──────────────────────────────────────────────────┘

Валидация:

  • Имя обязательно, мин 3 символа, макс 200
  • Канал обязателен
  • Шаблон обязателен для tv_screen
  • Дубликат имени → красное сообщение под полем «Имя уже занято»

Шаг 2: Slug (только для tv_screen)

┌─ Создание меню — Шаг 2/2 ────────────────────────┐
│                                                  │
│  Slug — уникальная часть URL для монитора       │
│  Live URL: https://erp-test.nirbi.ru/r/         │
│                                          [____] │
│                                                  │
│  ☑ Сгенерировать автоматически                  │
│                                                  │
│  Slug должен быть короткой латинской «строкой»  │
│  с дефисами. Регулярка: a-z, 0-9, -. От 3 до 40.│
│                                                  │
│  [← Назад]                  [Создать меню]      │
└──────────────────────────────────────────────────┘

Для канала json — Шаг 2 пропускается, сразу POST /external-menus без slug.

Поведение:

  • Чекбокс «Сгенерировать автоматически» pre-checked → поле slug disabled, при создании сервер генерирует menu-{8-char-id}
  • Снятие чекбокса → активирует поле, владелец вводит свой slug
  • Валидация: regex ^[a-z0-9-]{3,40}$, проверка уникальности при потере фокуса (спорится с сервером через debounce 500 ms GET /external-menus?slug=X)
  • Дубликат slug → красная подсказка «Slug уже используется»

Submit: POST /api/v1/external-menus → редирект в /external-menus/{id}/edit


Модалки подтверждений

Удаление в корзину

API: DELETE /api/v1/external-menus/{id}

  • Заголовок: «Удалить меню?»
  • Текст: «Меню «[Имя]» будет перемещено в Корзину и автоматически удалено через 30 дней. До этого его можно восстановить.»
  • Если меню published — дополнительная фраза: «⚠ Live URL https://... сразу перестанет работать.»
  • Кнопки: «Отмена» / «В корзину» (красная)
  • После успеха: обновить таблицу, toast «Меню перемещено в корзину»

Снять с публикации

API: POST /api/v1/external-menus/{id}/unpublish

  • Заголовок: «Снять с публикации?»
  • Текст: «Live URL https://... перестанет работать. Меню сохранится как черновик и его можно будет опубликовать снова.»
  • Кнопки: «Отмена» / «Снять с публикации» (warning стиль)
  • После успеха: toast «Снято с публикации»

Дублировать

API: POST /api/v1/external-menus/{id}/duplicate

  • Заголовок: «Создать копию?»
  • Текст: «Будет создано новое меню «[Имя] — копия» в статусе «Черновик» со всем содержимым. После создания вы попадёте в редактор копии.»
  • Кнопки: «Отмена» / «Создать копию» (primary)
  • После успеха: редирект в редактор копии

Восстановить из корзины

API: POST /api/v1/external-menus/{id}/restore

  • Заголовок: «Восстановить меню?»
  • Текст: «Меню вернётся в раздел «Активные» как черновик. Опубликовать вручную после проверки.»
  • Кнопки: «Отмена» / «Восстановить» (primary)
  • После успеха: переключиться на вкладку «Активные», открыть редактор

Edge cases (409):

  • NAME_EXISTS (имя занято за время архива) → модалка «Имя занято» с inline-полем нового имени
  • SLUG_TAKEN (для tv_screen) → модалка с предложением нового slug или переключения на авто-генерацию

Состояния

СостояниеЧто показываем
ЗагрузкаSkeleton-таблица
Пусто (Активные)Иллюстрация + текст «Нет внешних меню. Создайте первое чтобы повесить меню на монитор кассы или экспортировать в JSON» + кнопка «Добавить меню»
Пусто (Корзина)«Корзина пуста» серым
Ошибка загрузки«Не удалось загрузить данные» + кнопка «Повторить»
Пустой поиск«Ничего не найдено по запросу «…»»

Ролевой доступ

Владелец франшизы

  • Видит все меню франшизы
  • Все действия доступны

Владелец партнёра

  • Видит только меню своих ЮЛ (либо привязанные к свим ТТ, либо store_id=NULL если партнёр имеет полный scope франшизы — что редко)
  • Действия: создание / редактирование / удаление / восстановление — только в своих ТТ

Обычный сотрудник

  • Доступ к разделу — если есть permission external_menus.read. По дефолту обычно не выдаётся
  • Если есть external_menus.edit — все действия в рамках своих ТТ
  • Если нет ни read, ни edit — пункт меню «Внешние меню» в sidebar скрыт, прямой URL → 403

Переходы

ОткудаКудаТриггер
СписокРедакторКлик по имени, действие «Открыть»
СписокWizard создания (модалка)Кнопка «Добавить меню»
СписокLive preview (новая вкладка)Действие «Превью»
Список (после dublicate / restore)Редактор копии / восстановленногоAuto-redirect

Ссылки