# Дашборд: сквозная вложенность до источника + масштаб + выбор периода

**Дата:** 2026-06-28
**Автор:** Claude (по запросу владельца)
**Статус:** одобрено владельцем (брейншторм 28.06)

## Цель

С дашборда «Командный центр» по логике вложенности доходить до **конечного источника**
(лид → откуда пришёл и кому ушёл), все списки должны оставаться читаемыми при больших
объёмах (1000 клиентов, десятки тысяч лидов), а период анализа — выбираемым (пресеты +
свой диапазон дат).

## Решения владельца

1. **Полная цепочка лида:** Дашборд → Лиды → список (фильтры) → конкретный лид →
   *откуда* (поставщик-проект B1/B2/B3 + канал сайт/звонок/sms + регион) → *кому*
   (клиент + сделка + статус).
2. **Масштаб:** топ-N на дашборде + «Открыть всё →» в полноэкранный список с
   **серверными** поиском/фильтрами/страницами. Дашборд остаётся лёгким.
3. **Период:** пресеты (Сегодня / 7 / 30 / 60 / 90 дней) **+ свой период** (дата с — дата по).
   Все плитки, drill'ы и списки считаются по выбранному периоду.

## Принцип вложенности (3 уровня)

```
L1 плитка (сводка, светофор)
  → L2 drill (топ-N + смысл + «Открыть всё →»)
      → L3 полноэкранный список (поиск + фильтры + страницы)
          → L4 карточка / конечный источник
```

Дашборд НЕ становится браузером данных: тяжёлые списки живут на полноэкранных
экранах, которые держат объём (серверная пагинация). Карточки клиента/проекта
переиспользуем существующие.

## Кросс-режущее: период

- Query-параметры всех эндпоинтов: `period=today|7d|30d|60d|90d` ИЛИ
  `date_from=YYYY-MM-DD&date_to=YYYY-MM-DD` (свой диапазон; приоритет над preset).
- `periodStart()` в контроллере дополняется разбором `date_from/date_to` →
  возвращает `[from, to]`. Все запросы фильтруют `>= from AND < to+1day`.
- Фронт: тумблер пресетов + кнопка «Свой период» открывает два date-picker'а
  (Vuetify `v-date-input`/диалог); выбор шлёт `date_from/date_to`. Подпись периода
  показывается в шапке.

## Кросс-режущее: серверная пагинация (масштаб)

- Полноэкранные списки отдают `{ data: [...], total, page, per_page }`
  (Laravel paginator). Параметры: `?page`, `?per_page` (дефолт 25, max 100),
  `?search`, плюс специфичные фильтры.
- Индексы под фильтры — только аддитивные миграции (CREATE INDEX), без блокировок
  (CONCURRENTLY на проде, в окне). Сверять с `db/CHANGELOG_schema.md`.
- На дашборде (L2) — только топ-N (10–20), без пагинации; «Открыть всё» уводит в L3.

## Этап A — Лиды (полная цепочка) ⭐ главное

**L2 (drill Лиды):** топ-10 последних лидов: время · источник (канал+identifier) ·
поставщик (B1/B2/B3) · клиент-получатель · статус. Кнопка «Открыть все лиды →».

**L3 — новый экран «Лиды»** (`/admin/leads`, nav-пункт):
- Серверная таблица: время · источник (signal_type+identifier) · поставщик (platform) ·
  регион · клиент(ы) · статус (доставлен/завис/ошибка).
- Фильтры: диапазон дат, канал (signal_type: site/call/sms), поставщик (B1/B2/B3/direct),
  клиент (tenant), статус; поиск по identifier/телефону(маска)/id.
- Пагинация серверная.

**L4 — карточка лида** (`/admin/leads/{id}`):
- *Откуда:* поставщик-платформа + signal (канал+identifier) + регион (resolved_subject_code)
  + ссылка на проект у поставщика.
- *Что пришло:* received_at, ключевые поля raw_payload (ПДн — маскируются по 152-ФЗ:
  телефон `+7 9** *** ** 21`, имя — инициалы).
- *Кому ушло:* deals_created_count → список сделок (клиент + статус сделки + ссылка на
  карточку тенанта / сделку).
- *Обработка:* processed_at, error (если был).

**Данные:** `supplier_leads` (platform, signal_type, signal_identifier?, raw_payload,
received_at, processed_at, error, deals_created_count, resolved_subject_code, region_source)
+ `deals` (tenant_id, status, project_id, received_at) связь по лиду. Точную связь
lead↔deal уточнить по схеме при реализации (deal.source_lead_id / по телефону+времени).

**Безопасность:** ПДн маскируются в списке и карточке (152-ФЗ); полный показ — только
если уже так делается в клиентской карточке сделки. Не логировать ПДн.

## Этап B — Заказ у поставщика (кликабельные группы)

- Строки таблицы групп → кликабельны → **детали группы**: какие клиентские проекты
  входят (спрос по каждому) + проект(ы) у поставщика (факт) + «Открыть в Проектах
  у поставщика →» (`/admin/supplier-projects` с фильтром по группе).
- «Открыть всё →» из drill → `/admin/supplier-projects`.
- Конечный источник заказа = запись проекта у поставщика (существующий экран) +
  клиентские проекты группы.

## Этап C — Здоровье + «Открыть всё» на Финансы/Клиенты

- **Здоровье:** подсистемы кликабельны → Инциденты (`/admin/incidents`),
  очереди/джобы → экран системы/упавших джоб, синхрон/CSV → `/admin/supplier-integration`,
  вебхуки → соответствующий экран. (Куда именно — по наличию экранов.)
- **Финансы:** «Требуют внимания»/«Топ» → уже карточка тенанта; добавить «Открыть всё →»
  → `/admin/tenants` (фильтр «в минусе») и `/admin/billing`.
- **Клиенты:** новые/спящие → карточка тенанта; «Открыть всё →» → `/admin/tenants`
  (фильтры новые / спящие). Проверить, что `/admin/tenants` уже серверно-пагинирован и
  ищет; если нет — добавить (масштаб 1000 клиентов).

## Масштаб — что проверить отдельно (1000 клиентов / 10⁴ лидов)

- `/admin/tenants` (куда ведут Финансы/Клиенты): серверный поиск + пагинация + фильтры
  (новые/спящие/в минусе). Если сейчас клиентский список тянет всё — переделать на сервер.
- Дашбордовые агрегаты (counts) — по индексам; тяжёлые DISTINCT/JOIN проверить на плане.
- Лиды L3 — обязательно серверная пагинация + индексы по received_at (партиции уже есть),
  signal_type, platform.

## Порядок работ

Владелец: **«сразу всё» (A+B+C)** + масштаб + выбор периода. Реализация по этапам с
выкатом каждого (чтобы ценность приходила частями и риск был мал):

1. Кросс: выбор периода (свой диапазон) + контракт серверной пагинации.
2. Этап A: экран «Лиды» + карточка лида + связка из дашборда (главное).
3. Этап B: Заказ кликабельный + «Открыть всё».
4. Этап C: Здоровье-ссылки + «Открыть всё» на Финансы/Клиенты + аудит масштаба `/admin/tenants`.

Каждый этап: TDD (Pest + Vitest), показ глазами, выкат по «выкатывай» + prod-deploy-validator.

## Известные ограничения / уточнить при реализации

- Точная связь lead↔deal (по какому полю) — уточнить по `db/schema.sql` (Этап A Step 1).
- Маскирование ПДн в админ-карточке лида — согласовать уровень (полный/маска).
- Экраны-приёмники для подсистем «Здоровья» (упавшие джобы/вебхуки) — есть не все;
  вести в ближайший существующий, недостающее — отдельный follow-up.
- «Свой период» для очень больших диапазонов на партиционированных таблицах — следить
  за планом запроса (партиции по received_at помогают).
