CAT-09: Категории дерево с inline CRUD

Что сделать

Создать страницу категорий с рекурсивным деревом и inline-действиями.

Смотри спеку

Каталог — Категории — полная спека.

Конкретно

Страница /catalog/categories. API: GET /api/v1/admin/categories.

Заголовок “Категории”. Дерево отображается рекурсивно (неограниченная вложенность).

Отображение узла:

  • Стрелка свёртки / (если есть дочерние)
  • Название категории
  • Индикатор статуса: зелёная точка (active) / серая точка (inactive)
  • Inline-действия (Franchise only, видны при hover)

Inline-действия (Franchise only):

ДействиеИконкаЧто происходит
Добавить подкатегориюInline text input под узлом. Enter → POST /api/v1/admin/categories с parent_id. Esc → отмена
ПереименоватьНазвание → inline text input. Enter → PATCH /api/v1/admin/categories/:id. Esc → отмена
УдалитьМодалка подтверждения
Деактивировать/АктивироватьToggleПереключение статуса PATCH /api/v1/admin/categories/:id

Кнопка “Добавить корневую категорию” внизу дерева (Franchise only). При клике — inline text input на корневом уровне. Enter → POST /api/v1/admin/categories без parent_id. Esc → отмена.

Модалка удаления:

  • CATEGORY_HAS_CHILDREN: “Невозможно удалить. Категория содержит подкатегории. Сначала удалите или перенесите их.” → кнопка “Понятно”
  • CATEGORY_HAS_PRODUCTS: “Невозможно удалить. В категории есть товары. Сначала перенесите их.” → кнопка “Понятно”
  • Нет препятствий: “Удаление категории. Удалить категорию [название]? Это действие нельзя отменить.” → “Отмена” / “Удалить” (красная). После успеха: убрать узел, toast “Категория удалена”

Модалка каскадной деактивации (при деактивации узла с дочерними):

  • “Деактивация категории. Деактивировать [название] и все подкатегории? Товары в них тоже будут деактивированы.”
  • Кнопки: “Отмена” / “Деактивировать” (оранжевая)
  • После успеха: обновить статусы в дереве, toast “Категория и подкатегории деактивированы”
  • Активация — НЕ каскадная (только выбранная категория)

Состояния:

  • Загрузка: skeleton-дерево (3-4 placeholder-строки с отступами)
  • Пусто (Franchise): “Категории пока не созданы” + кнопка “Добавить корневую категорию”
  • Пусто (Franchisee/Manager): “Категории пока не созданы”
  • Ошибка: “Не удалось загрузить данные” + кнопка “Повторить”

Ролевая видимость:

  • Franchise: полное дерево + все действия
  • Franchisee: readonly дерево
  • Manager: readonly дерево
  • Cashier: 403 → редирект на дашборд

Файлы

  • web/src/pages/catalog/CategoriesPage.tsx — создать

Зависит от

  • CAT-01
  • CAT-02