Маркетинговая информация — Список
Роуты:
/marketing— выбор ТТ (если у пользователя > 1 ТТ в scope)/marketing/stores/{storeId}— список слайдов выбранной ТТ
API: GET /api/v1/admin/stores/{storeId}/marketing-slides
Статус реализации
- Backend: не начат — эндпоинты
marketing-slides/*ещё не реализованы вerp-store-service(см. декомпозиция Store Service)- Фронт: не начат — в
erp-admin/web/src/pages/нет папкиmarketing/- Sidebar-пункт: ещё не добавлен в
components/layout/Layout.tsx- Permission-guard:
marketing.read/marketing.writeещё не в каталоге User Service (миграция требуется)
Источники
- Бизнес: Маркетинговая информация
- BR: BR 6.1
- API: Store Service API
- Связка с AI: BR 6.2 (отдельно)
Sidebar-навигация
Новый пункт в TOP-секции левого сайдбара (Layout.tsx):
А Альфа ERP
├─ Dashboard
├─ Юридические лица
├─ Торговые точки
├─ Маркетинг ← новый
├─ ...
- Permission-guard:
marketing.read(если permission нет — пункт скрыт через UI-гейтинг) - Иконка — постер / picture
- Клик →
/marketing
Экран 1: Выбор ТТ (/marketing)
Если у пользователя в scope больше одной ТТ — показываем выбор. Если scope = одна ТТ (типичный кейс владельца партнёра с одной ТТ или менеджера) — сразу redirect на /marketing/stores/{его_единственная_ТТ}.
Layout
Заголовок «Маркетинговая информация», под ним — таблица ТТ доступных пользователю.
| Колонка | Данные |
|---|---|
| Название ТТ | Кликабельно — переход на /marketing/stores/{id} |
| Адрес | |
| Город | |
| Активных слайдов | Целое число (агрегат из Store Service — отдельный запрос или денормализованное поле в списке ТТ) |
| Конфиг standby | «5 мин · 9 сек» — standby_idle_minutes + standby_transition_seconds. Клик → форма редактирования ТТ (вкладка «Настройки») |
Источник данных
GET /api/v1/stores?per_page=100 — берём список ТТ scope’а пользователя.
Для каждого ряда — счётчик активных слайдов:
- Вариант 1 (предпочтительно): в
/admin/stores/{id}/marketing-slides?active=trueберёмmeta.totalилиdata.lengthпараллельно (N запросов = N ТТ; для MVP с ≤ 10 ТТ норм) - Вариант 2: добавить агрегат в
GET /api/v1/stores—marketing_active_countполе (требует расширения Store Service)
В MVP — Вариант 1.
Фильтры / Поиск
- Поиск по названию ТТ
- Фильтр по городу
Состояния
| Состояние | Что показываем |
|---|---|
| Загрузка | Skeleton-таблица |
| Пусто | «У вас нет торговых точек» (для пользователей с пустым scope) |
| Одна ТТ | Авто-редирект на /marketing/stores/{id} |
Экран 2: Список слайдов ТТ (/marketing/stores/{storeId})
Layout
[← Все ТТ] Маркетинговая информация → ТТ «Арбат-флагман»
[Превью карусели] [+ Загрузить слайд] [✨ Сгенерировать через AI]
┌─────────────────────────────────────────────────────────────┐
│ ⋮⋮ │ [thumb] │ Заголовок │ Источник │ Активен │ ... │
├─────────────────────────────────────────────────────────────┤
│ ⋮⋮ │ 16:9 │ Hero CTA │ Загружен │ ☑ │ ⋮ │
│ ⋮⋮ │ 16:9 │ Замена стека │ Загружен │ ☑ │ ⋮ │
│ ⋮⋮ │ 16:9 │ AI inside │ AI Photo │ ☑ │ ⋮ │
└─────────────────────────────────────────────────────────────┘
(пагинация если > 20)
Колонки таблицы
| Колонка | Данные | Примечание |
|---|---|---|
| Drag-handle | — | Иконка ⋮⋮ для grab-and-drop |
| Превью | image_url | Miniature 60×60 с object-fit:cover, ratio 16:9 letterbox |
| Заголовок | title | Тёмный текст |
| Источник | source | Chip: Загружено (нейтральный) / AI Photo Studio (с лого AI). Клик в AI-источник → tooltip с source_ref.preset_id / source_ref.job_id |
| Активен | active | Toggle (switch). Изменение → PUT с { active: true/false } |
| Создан | created_at | DD.MM.YYYY HH:mm |
| Действия | — | Меню (см. ниже) |
Сортировка
- По умолчанию —
order ASC(как на кассе) - Drag-and-drop изменяет порядок — клиент перестраивает массив локально, при отпускании отправляет PATCH
reorderс массивом[{id, order}]
Действия
Заголовок страницы:
| Кнопка | Action | Видимость |
|---|---|---|
| ← Все ТТ | Back to /marketing | Если scope > 1 ТТ |
| Превью карусели | Открывает модалку с auto-transition (как на POS) для проверки | Всегда |
| + Загрузить слайд | Открывает форму создания (см. ниже) | marketing.write |
| ✨ Сгенерировать через AI | UI-заглушка с tooltip «Будет в BR 6.2» (disabled) | marketing.write + gensvc.photo.create |
Меню действий строки (трёхточечное):
| Действие | Видимость | Что происходит |
|---|---|---|
| Редактировать | marketing.write | Переход на [[09-Frontend Specs/Админка Франшизы/Маркетинговая информация — Редактирование слайда|Редактирование]] |
| Скачать оригинал | marketing.read | window.open(image_url) |
| Удалить | marketing.write | Модалка подтверждения → DELETE → обновить список |
Drag-and-drop
Реализуется по образцу TablesSection.tsx (ручной handler без библиотек):
onMouseDownна drag-handle — фиксируемdragIndexonMouseMove— отслеживаем над какой строкой курсор, перерисовываем placeholderonMouseUp— берём новый порядок, отправляем PATCHreorder:PATCH /api/v1/admin/stores/{storeId}/marketing-slides/reorder { "order": [{ "id": "uuid-1", "order": 0 }, { "id": "uuid-2", "order": 1 }, ...] }- Optimistic UI — порядок применяется до ответа. При ошибке — откат
Загрузка нового слайда
Кнопка «+ Загрузить слайд» открывает модалку.
Поля формы
| Поле | Тип | Required | Валидация |
|---|---|---|---|
| Картинка | File input (drag-and-drop zone) | Да | Только image/jpeg, image/png, image/webp; ≤ 10 МБ; разрешение ≥ 1280×720 |
| Заголовок | Input | Да | ≤ 200 символов |
| Активен | Switch | Нет | Default true |
Submit
POST /api/v1/admin/stores/{storeId}/marketing-slides через admin-bff как multipart:
POST /admin/api/v1/stores/{storeId}/marketing-slides
Content-Type: multipart/form-data
file: <binary>
title: "Hero CTA"
active: true
После успеха — закрыть модалку, добавить слайд в список (или reload).
Состояния формы
| Состояние | Что показываем |
|---|---|
| Default | Поля + drag-and-drop зона с подсказкой «Перетащите картинку или нажмите» |
| Файл выбран | Превью + размер + разрешение, валидация на лету |
| Submitting | Disabled-форма, лоадер на кнопке |
| Ошибка | Toast с текстом ошибки (VALIDATION_ERROR, MARKETING_SLIDES_LIMIT_REACHED, S3_UPLOAD_FAILED) |
Лимит активных слайдов
При попытке создать 21-й активный слайд бэк вернёт MARKETING_SLIDES_LIMIT_REACHED. UI показывает:
Достигнут лимит активных слайдов (20)
Деактивируйте один из существующих, чтобы добавить новый.
Превью карусели (модалка)
При клике «Превью карусели»:
- Открывается модалка с aspect-ratio 16:9 (full-screen на мобиле, центральный блок на десктопе)
- В неё подгружаются те же активные слайды, что отдаст бэк по
GET .../marketing-slides?active=true - Auto-transition через
standby_transition_seconds(берём из карточки ТТ) - Кнопки: «Закрыть», «← Предыдущий», «Следующий →»
- Эта модалка имитирует то, что увидит кассир. Не использует SSE — это локальный preview.
Empty state
Если у ТТ нет ни одного слайда:
Здесь пока ничего нет
Загрузите первый слайд для standby-карусели,
чтобы на кассе крутился ваш бренд
[+ Загрузить слайд] [✨ Сгенерировать через AI]
Если есть слайды, но все неактивные:
☑ Покажите хотя бы один слайд
У вас 5 слайдов, но все деактивированы.
Касса в standby показывает только активные.
Конфигурация standby (standby_idle_minutes / standby_transition_seconds)
Поля не на этом экране. Они управляются в карточке ТТ (см. Торговые точки — Карточка) или вкладке «Настройки» в той же карточке. На странице слайдов — только информационная плашка:
ⓘ Standby запускается через 5 мин, между слайдами 9 сек.
Изменить → /stores/{id}/edit#standby
Ролевой доступ
По Ролевой матрице:
| Роль | Поведение |
|---|---|
| Владелец франшизы | Видит все ТТ франшизы, может всё |
| Владелец партнёра | Видит свои ТТ, может всё в их пределах |
Обычный с marketing.read | Видит свои ТТ (из роли), список read-only |
Обычный с marketing.write | Видит свои ТТ, может всё |
| Без permission | Пункт меню скрыт; прямой URL → 403 NoAccessPage |
Переходы
| Откуда | Куда | Триггер |
|---|---|---|
| Sidebar | /marketing | Клик «Маркетинг» |
/marketing | /marketing/stores/{id} | Клик по строке ТТ |
/marketing/stores/{id} | Редактирование слайда | Меню → «Редактировать» |
/marketing/stores/{id} | Карточка ТТ | Клик по плашке конфига standby |