Commit Graph

40 Commits

Author SHA1 Message Date
Дмитрий 793b20a39c feat(конкурентное поле): доводка фронта до прототипа — F1/F2/F3 + чистка M2
Сверка прототипа с реализацией показала расхождения — закрыты по TDD (dev, фронт):

- F1: экран «Предложения» (FieldProposalsScreen) переписан под вид «Поля» —
  карточки-плитки field-shared, тип+«предложение», крупная похожесть, Сайт +
  Справочник 2ГИС·Яндекс, править/удалять в карточке, массовый перенос; кнопка
  «Собрать конкурентов» открывает единое окно сбора 300 ₽ вместо старого autoform.
- F2: новый дружелюбный админ-экран AdminAutopodborPricingView (правка цен
  доп.услуг через PUT /api/admin/system-settings/{key} с обоснованием для аудита,
  сетка лидов для справки) + маршрут /admin/autopodbor-pricing + пункт меню.
- F3: колонка «когда списывается» в панели доп.услуг биллинга.
- M2: удалён мёртвый экран FieldManualCompetitorScreen (+ спека) — на него не
  было переходов; ручное добавление живёт окном на «Поле».

Тесты автоподбор+админ 43/43 зелёные, продакшен-вёрстка eslint-чистая, vite build .
НЕ на проде. M1 (18:00/21:00 МСК) — не баг, реальный инвариант продукта, не трогал.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 04:57:58 +03:00
Дмитрий 6536c19c96 feat(дашборд): Этап A — сквозная вложенность Лиды до источника
Экран «Лиды» (/admin/leads): серверный список с фильтрами (дата/канал/поставщик/
статус/поиск) + пагинация (масштаб 10⁴+ лидов). Карточка лида (/admin/leads/{id}):
полная цепочка — ОТКУДА (поставщик B1/B2/B3 + канал + источник + регион) → КОМУ
(сделки клиентов через deals.source_crm_id = supplier_leads.vid). Дашборд: drill
Лиды +топ-10 последних + «Открыть все лиды →». Nav-пункт «Лиды». ПДн-телефон
маскируется (152-ФЗ). Тесты: backend 3 + FE 5 (38 FE всего зелёные).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 10:14:47 +03:00
Дмитрий d961d1617a feat(админка): пункт «Командный центр» в левом меню
Дашборд не было видно в сайдбаре — уйдя с него, нельзя было быстро вернуться.
Добавлен первый nav-пункт «Командный центр» → /admin/dashboard (иконка dashboard).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 08:42:19 +03:00
Дмитрий 813b58447e feat/onboarding: приветственный тур при первом входе Фаза 3
Лёгкий тур без сторонних зависимостей: подсвечивает пункты меню по порядку
пополнить→создать проект→где заявки→помощь. Показывается один раз localStorage,
можно пропустить. Якоря data-tour на AppSidebar, монтируется в AppLayout.
Тест WelcomeTour 4/4. NB: пиксельное позиционирование проверить визуально на выкате.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 12:36:15 +03:00
Дмитрий 73d4c8c14f fix/ui: убрать жаргон в клиентском UI — без конкурент/синхронизация/crm.bp-gr.ru/Pay-per-lead
U1 остаток: пояснения источника проекта без слова конкурент.
U5: баннер списка проектов про результат а не синхронизацию + статус Собирает заявки.
Деалы/импорт/логин: убрано имя поставщика и англ Pay-per-lead.
Активность сделки: технический supplier_webhook заменён понятной фразой.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 12:02:52 +03:00
Дмитрий 146174a2e2 feat: публичные юр-страницы оферта/политика/возврат + реквизиты + страница цен под модерацию ЮKassa
Accessibility (Pa11y live) / a11y (push) Has been cancelled
SAST — Semgrep / Semgrep SAST scan (push) Has been cancelled
2026-06-24 09:31:52 +03:00
Дмитрий a2e73a3d4d feat(support): виджет онлайн-чата JivoSite (спит без ключа)
Accessibility (Pa11y live) / a11y (push) Has been cancelled
SAST — Semgrep / Semgrep SAST scan (push) Has been cancelled
- JivoWidget.vue: подгружает скрипт Jivo при заданном VITE_JIVO_WIDGET_ID
  (по образцу SmartCaptchaWidget); ключ пуст → ничего не грузит, виджет спит.
- Смонтирован в AppLayout (клиентский портал). +VITE_JIVO_WIDGET_ID в vite-env.d.ts.
- TDD: JivoWidget.spec (грузит при ключе / спит без) GREEN; AppLayout.spec 16/16; eslint 0.
- Активируется вставкой VITE_JIVO_WIDGET_ID (твой ID канала Jivo). На прод не выкачено.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 19:19:58 +03:00
Дмитрий c1d61068f9 chore(frontend): убрать «Напоминания» из устаревшего комментария AppLayout
F-REMIND хвост (ревью раунда 2): комментарий currentPageTitle приводил
«Напоминания» как пример страницы вне sidebar — фича снята (G3). Оставлен
живой пример «Импорт данных».

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 05:26:16 +03:00
Дмитрий 173b089629 feat(G7-B): клиентская плашка impersonation + редирект/ключ в диалоге + leave 2026-06-19 17:09:59 +03:00
Дмитрий 4c3e57bf9b feat(G3): убрать преференцию «Напоминание» + хвосты фронт-тестов
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 11:29:46 +03:00
Дмитрий a49b201d33 feat(G3): убрать экран «Напоминания» и раздел в карточке сделки
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 11:18:49 +03:00
Дмитрий f94552d452 WIP чекпойнт: lead-region/supplier бэкенд + фронт-редизайн + Pint + тесты
92 файла одной пачкой. Исключены чужие зоны: CLAUDE.md, .claude/settings.json, docs/observer/.pii-counters.json.
gitleaks staged: no leaks found. Не верифицировано тестами - сохранение труда в историю.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 05:17:12 +03:00
Дмитрий e1601e7862 feat(billing-v2-c): UI префлайт Task 1.10 — баннер заморозки, индикатор ёмкости, диалог перегрузки
Spec C §3.6/§6.2. Бэкенд: GET /api/billing/balance-status (frozen + capacity + required + дефицит ₽/leads), Pest 6. Фронт: BalanceFrozenBanner (в AppLayout, глобально), BalanceCapacityIndicator (в BillingView под балансом), ProjectLimitOverloadDialog (409-перехват в NewProjectDialog: save-blocked/set-zero), tenantStore + api getBalanceStatus. Vitest +18.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 20:39:21 +03:00
Дмитрий 77e98afaa6 feat(pd): 152-ФЗ право на удаление — минимум (hole #4)
Закрывает дыру #4 аудита журналирования. Объём по выбору заказчика — МИНИМУМ:
 Админ-API + кнопка в админке для удаления ПДн субъекта
 Сервис анонимизации (users + supplier_leads + deals + webhook_log)
 Журнал факта удаления в pd_processing_log
 БЕЗ формы самообслуживания на стороне субъекта
 БЕЗ email-подтверждения
 БЕЗ 30-дневного SLA (trigger deadline_at уже в схеме)

Что добавлено:
* Eloquent-модель `App\Models\PdSubjectRequest` (таблица уже была в схеме)
* Сервис `App\Services\Pd\PdErasureService::eraseSubject()`:
  - cross-tenant через pgsql_supplier (BYPASSRLS)
  - транзакционно (rollback при ошибке)
  - users: email→erased-{id}@deleted.local, first_name→Удалено, last_name→null,
    phone→+7000{id}
  - supplier_leads: phone→+7000XXXXXXX, raw_payload→{erased:true}
  - deals: phone→+7000XXXXXXX, contact_name→Удалено (только если есть phone)
  - webhook_log: batched UPDATE по 500, raw_payload→{erased,erased_at}
  - pd_processing_log запись action=deleted за каждого user/lead с
    actor_admin_user_id (hash-chain audit_chain_hash триггером сам подписывает)
  - При requestId — pd_subject_requests SET status=completed, completed_at,
    response_text счёт
* Контроллер `AdminPdSubjectRequestsController`: index/show/store/executeErasure
* Маршруты под middleware(saas-admin): GET/POST /api/admin/pd-subject-requests,
  GET /{id}, POST /{id}/erase
* Vue: `AdminPdSubjectRequestsView` (Quiet Luxury, таблица + диалог создания +
  кнопка Анонимизировать для request_type=deletion); ESLint требует
  v-slot:[`item.X`]= вместо #item.X для динамических slot-имён с точкой
* Пункт меню в AdminLayout.vue + route /admin/pd-subject-requests

NB: реальная схема — users.first_name/last_name/phone/email; supplier_leads
имеет только phone (нет contact_*); deals имеет phone+contact_name (нет
contact_email); webhook_log JSONB. PdErasureService адаптирован под факт.

Тесты: 12/12 passed (63 assertions, ~2.6s) — index pagination, store +
deadline trigger (+30 дней), eraseSubject анонимизация user/lead/deal/log,
pd_processing_log запись, request status→completed, отклонение
не-deletion типов, gate saas-admin, InvalidArgumentException.

Plan: docs/superpowers/plans/2026-05-23-7-holes-overview.md (#4).
2026-05-23 12:21:21 +03:00
Дмитрий b7466ebfbd fix(admin): убраны захардкоженные mock-счётчики в админ-меню (Тенанты 142 / Инциденты 3)
Бейджи показывали фиксированные 142/3, расходящиеся с реальными данными
(5 тенантов, 0 открытых инцидентов) — вводили в заблуждение. Удалены; неверный
бейдж хуже отсутствия. Живые счётчики — отдельная фича. TDD: AdminLayout.spec.ts (RED→GREEN).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 19:24:17 +03:00
Дмитрий 17e3c04f24 fix(layout): topbar title из route.meta.title для страниц вне sidebar-nav
AppLayout брал заголовок топбара только из sidebar navItems → /reminders и
/import (которых нет в боковом меню) показывали fallback «Страница». Добавлен
fallback на route.meta.title перед «Страница». TDD: AppLayout.spec.ts (RED→GREEN).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 19:23:58 +03:00
Дмитрий f1a3e9f02f feat(admin): supplier projects cleanup screen (list + bulk delete)
План 4 Task 3 эпика project-migration-redesign.

- AdminSupplierProjectsView.vue — v-data-table (источник/платформа/регион/
  лимит/кто заказывал/последняя поставка) + bulk-delete с v-dialog
  подтверждением + snackbar (deleted/failures).
- Роут /admin/supplier-projects (layout admin, requiresAuth, devIndex 31).
- AdminLayout nav-пункт «Проекты у поставщика».
- Vitest 3/3 (mount GET, bulk-delete confirm POST {ids}, disabled when empty).

NB: type-check имеет 3 pre-existing ошибки в DealDetailHero.spec.ts
(коммит 1412d3f, не Plan 4); файлы T3 type-check-чисты.
2026-05-20 14:34:25 +03:00
Дмитрий 66320166b8 feat(supplier): UI-экран «Интеграция с поставщиком» — здоровье CSV-канала 2026-05-18 17:58:53 +03:00
Дмитрий 8a7314d198 refactor(polling): вынести интервалы в constants/polling.ts — Sprint 6 F4 2026-05-17 08:18:44 +03:00
Дмитрий 11baaefe21 feat(admin): DEV-only баннер о застабленном auth-gate — Sprint 6 B6 2026-05-17 08:18:43 +03:00
Дмитрий 8b3ea3ed2e feat(ui): B3 — минимальная ⌘K command-palette навигации 2026-05-17 03:28:05 +03:00
Дмитрий 4a851a2d40 docs(admin): AdminLayout JSDoc — 4→7 nav-пунктов (Sprint 3A final review fixup)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 10:15:11 +03:00
Дмитрий eebcaf1912 docs+test(admin): ImpersonationBanner — убрать stale JSDoc + тест poll→render (B5 review fixup)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 10:09:29 +03:00
Дмитрий 346c4843b0 feat(admin): ImpersonationBanner — глобальный индикатор активных сессий (audit B5)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 09:59:43 +03:00
Дмитрий 9b1ac10309 feat(admin): AdminLayout nav — Тарифная сетка + Цены поставщиков (audit B4)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 09:43:53 +03:00
Дмитрий 012053a783 feat(auth): /legal/offer + /legal/privacy stub pages (closes A7)
Audit A7: the «Оферта» / «Политика» links in the AuthLayout footer were
raw <a href> pointing at unrouted paths -> 404 via the SPA catch-all.
Adds a single DRY LegalDocView served by /legal/:doc(offer|privacy),
rendering an honest «document being finalized» stub (real legal text
needs юр. редактура — реестр K3 / blocker Б-1). Footer links upgraded
to <RouterLink> for SPA navigation. Also refreshes two stale auth-layout
doc-comments left by the /recovery removal (review M1).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 19:37:20 +03:00
Дмитрий 143cc458c1 fix(a11y): Q.DEFER.002 sub-B — 12 patterns fixed across 16 auth views
Q.DEFER.002 sub-B closure: manual Pa11y audit-pass via Playwright MCP login +
axe-core CDN inject on 16 auth-required views. Found ~13 unique violation
patterns, 12 fixed, 3 deferred to Q.DEFER.004.

ROOT CAUSE found: AdminLayout `<v-navigation-drawer color="secondary"
theme="dark">` resolved to Vuetify default-dark `secondary=#54b6b2` (Teal
mid) instead of liderraForest `#012019` теало-нуар. Switching to direct hex
preserves design intent + restores white-text contrast across all 8 admin
views (~50 nodes color-contrast violations cleared).

Patterns fixed:

1. AdminLayout sidebar palette (8 admin views):
   - color="secondary" → color="#012019" (root cause)
   - .brand-sub red #b94837 → #e06155 (3.41 → 5.08)
   - .nav-count gray #7a8c87 → #8a9c95 (4.26 → 5.34)
   - <v-list nav> + role="navigation" + aria-label (aria-required-children
     fix: <v-list role=list> had [role=link] children — undefined для list)

2. DashboardBalance .runway-bar — role="img" (aria-prohibited-attr fix)

3. DashboardKpiRow .delta-up — #2e8b57 → #1b6e3b (4.27 → 6.25)

4. TransactionsTable .tx-amount-up — #2e8b57 → #1b6e3b (same fix)

5. RemindersList .empty-hint — #9a9690 → #6b6356 (2.98 → 5.74; +liderra-muted alignment)

6. KanbanView .kanban-board — tabindex="0" role="region" aria-label
   (scrollable-region-focusable fix)

7. ProjectCard:
   - .v-progress-linear + :aria-label="Прогресс дневной нормы: N%"
   - icon menu :aria-label="Меню действий проекта «...»"
   - bulk-select .card-check input :aria-label="Выбрать проект «...»"

8. useStatusPill in_progress #3F7C95 → #2A5A6E (4.07 → 6.11);
   useStatusPill.spec.ts sync

9. ProjectsView toolbar select-all input aria-label

10. AdminTenants impersonate v-btn aria-label

11. Global app.css:
    `.v-messages, .v-field-label { --v-medium-emphasis-opacity: 0.7; }`
    Vuetify default ~0.52 → rendered #7a7a7a/#767471 fails 4.20-4.29:1;
    0.7 → rendered ≈#595959 → 7.9:1+ passes WCAG AA.

Re-verified post-fix via axe-core on all affected views: all clean except
DEV-only `.dev-index-num` chip (tree-shaked в prod, not a real violation).

Vitest verified post-fix: 79 files / 614 passed / 3 skipped / 0 failed
(baseline preserved).

3 patterns deferred to Q.DEFER.004:
- DealsTable VDataTable show-select bulk-checkboxes (6 nodes) — Vuetify
  slot rewrite needed
- AdminSupplierPrices 9 form inputs — v-text-field/v-switch label props
- Vuetify v-tooltip eager-mount aria-tooltip-name — library-level cosmetic

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 22:09:48 +03:00
Дмитрий 5c8ad2738a feat(layout): dark topbar + sidebar cleanup + DevIndexBadge moved below
Sidebar: убраны Менеджеры/Напоминания; Работа в порядке
Проекты/Сделки/Канбан/Дашборд; Команда — только Настройки;
снят useRemindersStore (был только под reminders badge).

Topbar: тёмный фон linear-gradient(noir → #04261E) совпадающий
с sidebar #1271; убран breadcrumb «Рабочая область»;
v-toolbar__content padding-left:240 (не уходит под sidebar).

DevIndexBadge: top:64 (ниже топбара, не перекрывает user-chip).

Vitest AppLayout 15/15 PASS.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 14:32:03 +03:00
Дмитрий f4ec5dcafa fix(redesign): sidebar position:fixed + main padding-left — restore main content visibility
Hotfix: после Task 12 замены v-navigation-drawer на plain <aside> sidebar остался position:static и толкал v-main в flow ниже (y=901), весь контент уезжал за viewport. Добавлен .ld-sidebar position:fixed top:0 left:0 height:100vh z-index:1006 + .app-main padding-left:232px. Verified via Playwright snapshot — Dashboard KPI/charts отрисованы корректно.
2026-05-12 10:48:15 +03:00
Дмитрий a09434eca0 feat(redesign): Task 13 — page transition wiring (Vue Transition + CSS fadeup, motion #6) 2026-05-12 09:59:22 +03:00
Дмитрий 0245f12b51 chore(dev): inject DevIndexBadge for visual feature feedback on localhost 2026-05-12 04:45:18 +03:00
Дмитрий c9ee8d866e feat(frontend): Plan 5 Task 7 — router + nav + regions + ProjectCard + story 2026-05-11 19:31:23 +03:00
Дмитрий 30ef61dff8 refactor(frontend): Sprint 4 Phase B/1 — split 3 admin/layout views (audit O-refactor-04 хвост)
3 view'а с >300 строк разделены на shell + sub-components:

AdminTenantsView 377→155 (+ TenantsStatsHeader 82 / TenantsFilters 93 / TenantsTable 116).
AdminTenantDetailView 436→109 (+ TenantDetailHeader 158 / TenantDetailTabs 176
+ adminTenantDetailFormatters 43 composable).
AppLayout 466→78 (+ AppSidebar 155 / AppTopbar 269; R0.6 hard-стоп снят
явным запросом заказчика 10.05.2026).

State (filterStatuses, tenantsState, activeTab, tenant, drawerOpen) остаётся
в parent view'ах ради `defineExpose`-контракта Vitest тестов. Sub-components
читают Pinia stores напрямую (auth + notifications + reminders) — без
prop-drilling.

AppTopbar 269 строк <300 — acceptance threshold выдержан (можно дальше split на
NotificationsDropdown + UserMenu в отдельном flow, не критично).

Регрессия: ESLint 0 + vue-tsc 0 + Vitest 416/416 + build OK 1.17 сек.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 04:38:08 +03:00
Дмитрий dc1457a008 phase2(reminders-frontend): RemindersView + DealDetailDrawer + nav-badge
P0 этап 5 — frontend для reminders (после backend-этапа 4).
Пользователь может создавать/просматривать/завершать/удалять напоминания
из UI с inline-create в DealDetailDrawer.

Frontend:
- api/reminders.ts: типизированные helpers для 5 endpoints + ensureCsrfCookie
  для mutating. Types ReminderFilter/ApiReminder/ReminderCounts.
- stores/reminders.ts: Pinia с items/counts/currentFilter +
  load/refreshCounts/create/update/complete/remove. Optimistic для
  complete/remove с revert на reject.
- components/reminders/ReminderDialog.vue: dual-mode (create/edit) modal
  с native datetime-local input. Props dealId?/reminder? (edit),
  ISO-конверсия при submit.
- views/RemindersView.vue: page-head с stats (active/overdue) + reload-btn;
  4 tabs (today/upcoming/overdue/completed) с counts на бейджах
  (overdue=error color); v-list с complete-btn / dropdown
  Изменить/Удалить с confirm-dialog; empty-state.
- router: /reminders маршрут (lazy).
- AppLayout: nav-badge «Напоминания» биндит count из store
  (replace static «12»); скрыт при count=0; polling 60 сек для
  refreshCounts.
- DealDetailDrawer: секция «Напоминания» (только при tenantId+deal):
  inline + create-btn / список / complete / встроенный ReminderDialog.

Vitest +20 (369/369 за 21.20 сек):
- reminders-store 11: initial / load+reject / refreshCounts /
  create+reject / complete optimistic+revert / remove+reject / reset.
- RemindersView 7: mount / 4 tabs / counts / empty-state /
  список / reload-btn / filter=today default.
- AppLayout +2: бейдж скрыт при count=0 / показывает count при >0.

Pest 347/347 (без изменений — backend нетронут).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 12:41:41 +03:00
Дмитрий 508de4eaf3 phase2(notifications-stage2b): API + Pinia + bell в AppLayout (P0 этап 2b)
Закрывает этап 2 P0 целиком (UI bell с unread badge + polling).

Backend:
- App\Http\Controllers\Api\InAppNotificationController под auth:sanctum:
  GET /api/notifications?unread_only=&limit= (1..100 default 50);
  PATCH /api/notifications/{id}/read (idempotent);
  POST /api/notifications/mark-all-read (bulk + count);
  DELETE /api/notifications/{id}.
- Route::middleware('auth:sanctum')->prefix('/api/notifications') в web.php.
- DB::transaction + SET LOCAL app.current_tenant_id для RLS.
- Защита от кражи чужого id через where('user_id', $auth->id).
- Pest +14 (305/305 за 34.71 сек, 1099 assertions).

Frontend:
- api/notifications.ts — типизированные axios-helpers + ensureCsrfCookie.
- stores/notifications.ts — Pinia: items/unreadCount/total/loading +
  optimistic markRead/markAllRead/remove с revert на reject.
- AppLayout: bell-icon → v-menu offset=8 location=bottom-end:
  pip badge показывает unreadDisplay (1..99 / 99+ / hidden);
  v-list последних 10 из sortedItems с event-icon + formatRelative;
  Mark-all-read btn только при unreadCount > 0;
  click на item → markRead + router.push('/deals') если deal_id.
- usePolling(loadNotifications, {intervalMs: 30_000}) с Page Visibility.
- loadNotifications no-op без auth.user.
- Vitest +18 (339/339 за 20.03 сек): store 12 + AppLayout +6
  (bell-btn / pip скрыт при 0 / pip count / 99+ / listNotifications
  на mount с user / no-op без user).

PHPStan baseline регенерирован (50 Pest false-positives подавлены).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 11:27:57 +03:00
Дмитрий 768628d914 phase2(7-features): bulk-actions / new-deal / tenant-card / system-edit / webhook / smart-filters / impersonation-list
7-фичный auto-mode пакет согласно «карте что осталось» (после v1.54).

(1) Bulk-actions DealsView:
- dealsState reactive-копия MOCK_DEALS (deep-clone) для безопасного bulk-edit.
- Bulk-bar (sticky, теало-нуар, theme=dark) при selected.length > 0:
  count + Сменить статус (v-menu × 14 lead_statuses) + Экспорт (snackbar) +
  Удалить (v-dialog confirm) + ✕ clear.
- На production: smart status-transition с проверкой allowed-переходов;
  soft-delete (архив 30 дней); реальный CSV/XLSX export через xlsx-lib.

(2) NewDealDialog (used in DealsView+KanbanView):
- 6 полей: name/phone/project (MOCK_PROJECTS) / manager (MOCK_MANAGERS) /
  cost / status (default 'new' или presetStatus). Phone-валидация ≥10 цифр.
- emit('created', deal) → DealsView push в начало dealsState; KanbanView push
  в правильную колонку по statusSlug + totalDeals++.

(3) AdminTenantDetailView (/admin/tenants/:code):
- 4 KPI cards (Баланс/runway / Тариф+MRR/мес / Лиды сегодня+неделя+месяц /
  Средняя цена). 4 v-tabs: Финансы (balance-history) / Пользователи /
  Проекты / Активность с event-кодами.
- Кнопка «Войти как клиент» (использует ImpersonationDialog из v1.54).
  404-fallback. composables/mockTenantDetail.ts с expandTenantDetail.
- AdminTenantsView получил @click:row → router.push.

(4) Edit-flow AdminSystemView (audit-log + 2-step):
- Backend: SystemSetting + SaasAdminAuditLog Eloquent (append-only,
  payload_before/after JSONB casts).
- AdminSystemSettingsController с GET (list) + PUT (update в DB::transaction
  + INSERT в saas_admin_audit_log; hash-chain trigger BEFORE INSERT
  заполняет log_hash).
- Type-validation: int/decimal/bool/json. Reason ≥30 chars. No-op → 422.
- Frontend SystemSettingEditDialog — 3-step (edit → confirm с diff
  before/after → done).

(5) Webhook receive endpoint (POST /api/webhook/{token}):
- WebhookReceiveController::receive. Token = tenants.webhook_token.
- 404 unknown / 422 bad payload / 202 success + dispatch ProcessWebhookJob.
- Stub-INSERT в webhook_log через DB::table обёрнут в DB::transaction +
  SET LOCAL app.current_tenant_id для RLS.
- CSRF-исключение для api/webhook/* в bootstrap/app.php.
- На prod: + HMAC X-Webhook-Signature + per-token rate-limit.

(6) Smart-filters:
- DealsView: multi-select v-select Проект+Менеджер с auto availableProjects/
  availableManagers computed.
- AdminTenantsView: filterStatuses (4 STATUS_OPTIONS) + filterTariffs
  (computed availableTariffs).
- Кнопка «Сбросить» появляется только когда фильтры активны.

(7) AdminImpersonationView (/admin/impersonation):
- Backend +2 GET endpoints: /active (used_at != null AND session_ended_at
  == null) + /recent (last 20 завершённых с duration_seconds через
  abs(diffInSeconds) — Carbon signed по умолчанию).
- ImpersonationToken получил belongsTo(Tenant).
- Frontend view: 2 секции (Активные с end-кнопкой / Недавно завершённые
  read-only) + refresh + onMounted load.
- Маршрут /admin/impersonation + 5-й nav-пункт «Impersonation» в AdminLayout.

Vitest +48 (всего 238/238 за 15.31 сек).
Pest +16 (всего 136/136 за 15.8 сек, 495 assertions).
PHPStan baseline регенерирован (0 errors после фикса nullsafe.neverNull).

Регресс: lint+type-check+format ; vite build 937 ms; Pint+PHPStan passed;
Pest 136/136. Реестр v1.54→v1.55, CLAUDE.md v1.45→v1.46.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 05:33:21 +03:00
Дмитрий 1d1353931d phase2(user-chip): реальный user в AppLayout/AdminLayout + Logout-menu
- AppLayout: userInitials/userShortName computed из auth-store, fallback цепочка → email → '?' / 'Гость'
- AdminLayout: тот же паттерн с админ-defaults 'АО' / 'Админ Оператор'
- v-menu offset=8 на user-chip: email + Настройки/Выйти из админки + Выйти
- handleLogout async: auth.logout() (swallows API errors) → router.push('/login')
- Vitest +3 в AppLayout.spec.ts (всего 145/145): store-mock + null-user + email-fallback
- AppShell.spec.ts получил createPinia в plugins
- Регресс: lint+type+format OK, vitest 145/145 за 11.01с, build 855ms, story:build 21/28 за 32.11с, Pest 67/67 за 6.16с
- CLAUDE.md v1.34→v1.35, реестр v1.43→v1.44

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 20:29:05 +03:00
Дмитрий da65cf4bf7 phase2(admin): AdminLayout + AdminTenantsView - админка SaaS (12/13 концептов)
- AdminLayout: отдельный sidebar теало-нуар с под-брендом ADMIN (red error
  10px JBM uppercase) + 4 nav (Тенанты 142 / Биллинг / Инциденты 3 / Система) +
  topbar с crumb «Админка → currentPage» + admin-user-chip с error-color avatar.
- AdminTenantsView (/admin/tenants): page-head + 5-stats + Экспорт +
  search/Статус/Тариф фильтры + v-data-table 7 колонок (Тенант с двухстрочным
  name+inn / Статус-chip 4 цвета / Тариф / Баланс ₽ с error-color при <0 /
  Желаем×факт / MRR с «—» / Активность).
- mockTenants.ts соответствует schema v8.7 §3: 4 статуса × 5 тарифов, 7 mock
  с разнообразием (active/trial/overdue/suspended) + AdminStats (142/128/9/5/
  1 248 600 ₽).
- AdminPlaceholderView универсальный для Биллинг/Инциденты/Система с
  описаниями ссылающимися на schema v8.7 (incidents_log §9 / system_settings §10).
- AppShell расширен meta.layout='admin'. Router: /admin redirect на /tenants +
  4 admin-route'а с lazy-imports. Web.php fallback покрывает /admin/*.
- cspell-words.txt: Екб.

Vitest +11 (всего 129/129 за 10.02s):
- заголовок + 5 stats (regex nbsp в 1 248 600 ₽) + 7 columns + 7 rows +
  Окна Москва ИНН + overdue −1 200 + trial 4 дня + suspended + search filter
  «Натяжные» → 1 row + Экспорт/Статус/Тариф кнопки.

Регресс: lint+type+format OK; vitest 129/129; vite build (admin views
в lazy-chunks; main 104.99 KB); story:build 21/28 за 30.32s; Pest 48/48 за 4.89s.

CLAUDE.md v1.30->v1.31, реестр Открытых_вопросов v1.39->v1.40.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 19:23:28 +03:00
Дмитрий 1e233a70c8 phase2(dashboard): AppLayout + DashboardView - default-layout приложения
- AppLayout: v-navigation-drawer (теало-нуар sidebar 240px) + brand-block
  + nav-tree из 8 пунктов в 3 группах (Работа/Финансы/Команда), v-app-bar
  с crumb «Рабочая область → currentPage» + search ⌘K + bell + user-chip.
  Mobile (md<): drawer toggleable.
- DashboardView: page-head «Доброе утро, Иван» + page-meta + range-toggle
  4 опции (Сегодня/7д/30д/Период). KPI-row из 4 cards: 3 outlined (получено
  лидов/конверсия/активные проекты) + 1 hero balance с runway-bar 4/7
  заполненных сегментов teal #32C8A9.
- AppShell упрощён до layout-mapper (route.meta.layout 'app'/'auth').
- Маршрут /dashboard (meta.layout='app') в router + web.php.
- histoire.setup расширен 8 app-stub-маршрутами для AppLayout.
- Vitest +11 тестов: AppLayout 6 (brand+3 группы+8 пунктов+счётчики+crumb),
  DashboardView 5, AppShell.spec.ts переписан под layout-mapper.
- cspell-words.txt: JBM.

Регресс: lint+type-check+format OK; vitest 35/35 за 4.92s; vite build
DashboardView lazy-chunk 14.9KB; story:build 8/8 за 28.97s; Pest 48/48 за 4.88s.

CLAUDE.md v1.20->v1.21, реестр Открытых_вопросов v1.29->v1.30.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 17:21:19 +03:00
Дмитрий e909a95a8d phase2(login): vue-router 4 + AuthLayout + LoginView - первый реальный экран
- vue-router@^4.6.4 (--legacy-peer-deps из-за Histoire vs Vite 8 peerDep).
- resources/js/router/index.ts: createWebHistory + lazy-imports + meta.layout.
  Маршрут / -> /login, /login -> LoginView (meta.layout=auth).
- resources/js/layouts/AuthLayout.vue: двухпанельный (brand-pane тёмный с
  radial-gradient акцентами + form-pane warm ivory). На mobile brand-pane скрыт.
- resources/js/views/auth/LoginView.vue: форма Vuetify по v8_login.html
  секция #form-login - email/password (autocomplete + eye-icon), primary submit,
  Yandex 360 SSO, RouterLink на /register и /forgot.
- AppShell.vue: layout-mapper по route.meta.layout (default/auth).
- routes/web.php: явные Route::view для 6 SPA-путей (НЕ catch-all - он перехватывал
  /_test/* в Pest beforeEach и валил 5 SetTenantContextTest).
- Vitest: +LoginView.spec.ts (5), +router.spec.ts (2), AppShell.spec.ts переписан (3).
  Vitest 10/10 за 3.01s.
- LoginView.story.vue для Histoire. Setup расширен memory-router'ом.
- cspell-words.txt: рендерят, коммиты, Лидерру.
- Регресс: lint:vue OK, type-check OK, format:check OK, vitest 10/10,
  vite build 212 модулей за 383ms (LoginView lazy-chunk 43.5KB JS / 51.7KB CSS),
  story:build 2/2 за 29.94s, Pest 48/48 за 4.86s.

CLAUDE.md v1.18->v1.19, реестр Открытых_вопросов v1.27->v1.28.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 16:59:00 +03:00