Admin Franchise web — BR 4.1

Контракты

Что делаем

Раздел и роутинг

  • web/src/App.tsx:
    • <Route path="external-menus" element={<PermissionRoute permission="external_menus.read"><ExternalMenusListPage /></PermissionRoute>} />
    • <Route path="external-menus/:id/edit" element={<PermissionRoute permission="external_menus.read"><ExternalMenuEditorPage /></PermissionRoute>} />
  • web/src/components/layout/Sidebar.tsx — добавить пункт «Внешние меню» в группу «Каталог» (если есть) или отдельным пунктом. Видимость по external_menus.read.

Список меню

  • web/src/pages/external-menus/ListPage.tsx:
    • Таблица с колонками + фильтры + поиск + пагинация (по образцу pages/employees/ListPage.tsx)
    • Вкладки «Активные» / «Корзина» (через query param archived=true)
    • Кнопки в шапке: «Добавить меню» (открывает мастер) + «Корзина» (toggle)
    • Меню действий строки: Открыть / Опубликовать / Снять / Скопировать URL / Скачать ZIP / Скачать JSON / Превью / Дублировать / Удалить
    • 4 модалки подтверждений (см. фронт-спеку)

Wizard создания

  • web/src/components/external-menus/CreateMenuWizard.tsx:
    • 2-шаговая модалка
    • Шаг 1: имя, канал, ТТ, шаблон (для tv_screen)
    • Шаг 2: slug (только для tv_screen) — auto/custom toggle
    • Валидация уникальности имени + slug на лету (debounce 500 ms)
    • На submit → createExternalMenu() → редирект на /external-menus/{id}/edit

Редактор (главная страница)

  • web/src/pages/external-menus/EditorPage.tsx:

    • Двухпанельный layout
    • Загрузка через getExternalMenu(id) + listProducts({ franchise_id }) + listCategories()
    • Шапка: имя (inline-edit), live URL копируемый, действия меню, кнопка публикации
    • Левая панель: дерево каталога с фильтрами и поиском
    • Правая панель: дерево external_menu
  • web/src/components/external-menus/CatalogPanel.tsx:

    • Дерево категорий с раскрытием
    • Поиск товаров (debounce 300 ms)
    • Фильтр по категории
    • Toggle «Скрывать уже добавленные»
    • Иконки для добавленных (⚿) / не-добавленных (•)
    • Drag handler с использованием react-dnd или @dnd-kit/core
  • web/src/components/external-menus/StructurePanel.tsx:

    • Дерево категорий (accordion)
    • Drop-зона для drag из CatalogPanel → addItem()
    • Reorder категорий drag-n-drop → reorderCategories()
    • Перенос item между категориями → updateItem() с новым category_id
    • Кнопка «+ Добавить категорию» с inline-инпутом
    • Меню действий категории (✏ переименовать, 🎨 иконка, ❌ удалить)
  • web/src/components/external-menus/ItemRow.tsx:

    • Строка item: drag-handle, имя (с пометкой override), цена (effective + small caption если override), toggle visibility, ⚙ override-форма
    • Состояния: ok / orphan (красный banner) / в стоп-листе (жёлтый banner)
  • web/src/components/external-menus/OverrideForm.tsx:

    • Inline-форма с полями: override_name, override_description, override_price, visible
    • Подсказки про эффективную цену (3 значения: catalog / price_list / effective)
    • Кнопки: Сохранить / Очистить overrides / Удалить из меню
    • Auto-save через debounce 600 ms (опционально)

Модалки

  • web/src/components/external-menus/PublishModal.tsx — подтверждение публикации
  • web/src/components/external-menus/DeleteModal.tsx — soft delete
  • web/src/components/external-menus/RestoreModal.tsx — восстановление с обработкой 409 (NAME/SLUG conflict с inline-полем)
  • web/src/components/external-menus/MenuSettingsModal.tsx — настройки меню (открывается по ✏)

State управление

  • Использовать существующий паттерн (zustand / react-query — что в проекте). У erp-admin/web уже используется state, посмотреть в существующих pages
  • Локальный state в EditorPage для текущей раскрытой override-формы, drag-state, etc.

Permissions

  • Расширить usePermissions hook (если есть):
    • useCanReadExternalMenus() → boolean
    • useCanEditExternalMenus() → boolean
  • Все edit-действия гейтятся через эти хуки

Тесты

  • React Testing Library:
    • ListPage — рендер списка с разными статусами, переход между вкладками, кнопки гейтятся по permission
    • CreateMenuWizard — валидация имени, slug regex, navigation между шагами
    • EditorPage — drag-drop добавление, override-форма сохраняется
    • PublishModal — disabled для пустого меню, активна для непустого

Не делаем

  • Embedded live preview iframe в редакторе — отложено в P1. В P0 владелец открывает превью отдельной вкладкой
  • Drag-drop reorder с touch-events для планшетов — P1
  • Bulk actions в списке (Mass delete, Mass publish) — P1
  • Версионирование UI — нет в BR 4.1

Verification

  1. На erp-test.nirbi.ru → /external-menus → список открывается, кнопка «Добавить меню» работает
  2. Создать меню «Test Bar» канал tv_screen → попадаем в редактор
  3. Drag товар из левой в правую → появляется item в категории «Без категории» (или дефолтной)
  4. Создать категорию «Кофе» → перетянуть item в неё
  5. Open override-форму → ввести цену 290 → сохранить → effective_price обновился
  6. Опубликовать → live URL стал кликабельный, скопировался в буфер
  7. Открыть live URL в новой вкладке (in kiosk-mode preview) → меню рендерится
  8. Изменить override_name → ждать ≤30 сек → меню в kiosk-вкладке обновилось
  9. Удалить меню → перешло в Корзину
  10. Восстановить → вернулось в Активные как draft

Ссылки