Клиенты — Список

Роут: /customers API: GET /api/v1/admin/customers

Статус реализации

  • Backend: готов — Customer Service имеет полный CRUD (erp-customer-service, все endpoints из API.md реализованы)
  • Фронт: не начат — в erp-admin/web/src/pages/ нет папки customers/
  • Sidebar-пункт: ещё не добавлен в components/layout/Layout.tsx
  • Permission-guard: customers.read уже в permission-каталоге User Service (миграция 024), но не используется

Источники


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

Страница со списком клиентов в виде таблицы. Вверху — заголовок «Клиенты», строка поиска и фильтры. Справа от заголовка — кнопка «+ Создать клиента».

Поля LTV, orders_count, last_visit_at в таблице — агрегации из Order Service, клеятся Admin BFF’ом (Customer Service их не возвращает).


Таблица

Колонки

КолонкаДанныеПримечание
ФИОfirst_name + last_nameКликабельное — переход в карточку. Если last_name null — показываем только first_name
ТелефонphoneВ формате +7 (999) 123-45-67 для отображения, под капотом E.164
EmailemailИли «—»
ДРbirthdayВ формате DD.MM (без года для краткости); или «—». Если ДР в ±14 дней — иконка 🎂 рядом
Дата регистрацииregistered_atФормат DD.MM.YYYY
Источникregistration_sourceChip: POS / Веб / Приложение / Админка / Импорт. Цвет по типу
Группыgroups[].nameChips групп; max 3 видимых, остальные — «+N» с tooltip-полным списком. Пустой список — «—»
LTVагрегат из Order ServiceСумма closed заказов клиента. Формат N 500 ₽. Пусто если 0 заказов
ДействияМеню с действиями (см. ниже)

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

  • Soft-deleted клиенты (deleted_at IS NOT NULL) в списке не отображаются (API фильтрует)
  • Клиент без заказов — колонка LTV пустая, без «0 ₽»

Фильтры

ФильтрТипЗначенияDefault
ГруппаSelectВсе / {список активных групп франшизы}Все (query group_id)
ИсточникSelectВсе / POS / Веб / Приложение / Админка / ИмпортВсе (query source)
Дата регистрации сDate picker— (query registered_from)
Дата регистрации поDate picker— (query registered_to)

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


Поиск

  • Поле ввода с placeholder «Поиск по ФИО, телефону или email»
  • Debounce 300ms
  • Query param: search
  • Ищет по подстроке всех трёх полей одновременно на бэке

Сортировка

  • По умолчанию — по дате регистрации DESC (новые клиенты сверху)
  • Клик по заголовку «ФИО» — переключение A-Я / Я-A (query sort=name_asc|name_desc)
  • Клик по заголовку «Дата регистрации» — переключение ASC / DESC

Пагинация

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

Действия

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

КнопкаПереходВидимость
«+ Создать клиента»/customers/newPermission customers.edit

Меню действий строки

Трёхточечное меню в последней колонке:

ДействиеВидимостьЧто происходит
Редактироватьcustomers.editПереход в /customers/{id}/edit
Удалитьcustomers.delete + только owner франшизыМодалка подтверждения (см. карточку)

Состояния

СостояниеЧто показываем
ЗагрузкаSkeleton-таблица (placeholder строк)
Пусто (первый раз)«Клиенты пока не добавлены» + CTA «Создать клиента» (если permission) или пояснение «Дождитесь когда кассиры заведут клиентов через POS»
Ошибка загрузки«Не удалось загрузить данные» + кнопка «Повторить»
Пустой поиск«Ничего не найдено по запросу «…»» + ссылка «Сбросить фильтры»

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

По Ролевой матрице:

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

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

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

  • Видит всех клиентов франшизы (пул общий — см. бизнес-спеку § Scope)
  • Может создавать, редактировать. Удаление и merge — только у Владельца франшизы

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

  • Без customers.read пункт меню «Клиенты» скрыт (см. UI-гейтинг)
  • Прямой URL → 403 NoAccessPage

Кассир


Переходы

ОткудаКудаТриггер
СписокКарточка (view)Клик по ФИО
СписокФорма создания«+ Создать клиента»
СписокФорма редактированияМеню → «Редактировать»
Карточка группыСписок с фильтром по группеИз /customer-groups/{id} ссылка «Участники в списке»

Ссылки