Внешние меню — Список
Роут: /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 есть items | POST /publish → toast «Опубликовано», live URL стал активен |
| Снять с публикации | external_menus.edit AND status=published | Модалка подтверждения → POST /unpublish |
| Скопировать live URL | status=published AND channel=tv_screen | Копирует URL + toast |
| Скачать offline ZIP | external_menus.edit AND status=published AND channel=tv_screen | GET /export.zip → скачивание файла |
| Скачать JSON | external_menus.read AND status=published AND channel=json | GET /export.json → скачивание файла |
| Превью на новой вкладке | external_menus.read AND channel=tv_screen | Открывает /r/{slug}?preview=1 в новой вкладке |
| Дублировать | external_menus.edit | Модалка с подтверждением → POST /duplicate → переход в редактор копии |
| Удалить (в корзину) | external_menus.edit | Модалка подтверждения → DELETE |
Меню действий строки (вкладка «Корзина»)
| Действие | Видимость | Что происходит |
|---|---|---|
| Восстановить | external_menus.edit | POST /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 msGET /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 URLhttps://...сразу перестанет работать.» - Кнопки: «Отмена» / «В корзину» (красная)
- После успеха: обновить таблицу, 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 |