45239f6602
- DealDetailDrawer (v-navigation-drawer right temporary 480px):
- hero (#id eyebrow + name h5 + close + tel:link + clock + status-chip)
- section Параметры (2-col grid: Проект/Стоимость/Менеджер/Источник)
- section Активность (timeline 6 events с iconified vertical-line)
- mockDealEvents.ts: 6 mock-events (created/balance_charged/assigned/viewed/
status_changed/commented) - соответствуют ActivityLog event-константам v8.7.
- Интеграция в DealsView (@click:row) и KanbanView (через @open-deal от карточки).
- cspell-words.txt: iconified, мапы, резолвятся, резолвером, stub'ить, инлайнен.
Vue3 quirk: v-navigation-drawer требует layout-injection от v-app/v-layout,
но в Vitest vite-plugin-vuetify auto-import не работает. Решение:
- DealsView/KanbanView тесты: stubs:{DealDetailDrawer:true}
- DealDetailDrawer тесты: stubs:{VNavigationDrawer:passthrough-div}
Vitest +8 (всего 79/79 за 7.57s):
- DealDetailDrawer 8 (open=false скрытие, deal=null no-content, hero+id,
tel:link, status-chip, params, timeline 6 items, emit update:open(false)).
Регресс: lint+type+format OK; vitest 79/79; vite build (drawer инлайнен в
DealsView+KanbanView lazy-chunks); story:build 15/22 за 31.55s; Pest 48/48.
CLAUDE.md v1.25->v1.26, реестр Открытых_вопросов v1.34->v1.35.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
93 lines
3.0 KiB
TypeScript
93 lines
3.0 KiB
TypeScript
/**
|
|
* Mock activity-events для DealDetailDrawer timeline.
|
|
*
|
|
* На API будет `GET /api/deals/{id}/events` — выборка из `activity_log` по
|
|
* tenant_id (RLS) + deal_id. По схеме v8.7 §10.2 (activity_log table).
|
|
*
|
|
* Типы событий — соответствуют ActivityLog event-константам:
|
|
* `deal.created`, `deal.status_changed`, `deal.viewed`, `deal.commented`,
|
|
* `deal.assigned`, `deal.balance_charged` и т.п.
|
|
*/
|
|
export interface DealEvent {
|
|
id: number;
|
|
type:
|
|
| 'deal.created'
|
|
| 'deal.status_changed'
|
|
| 'deal.viewed'
|
|
| 'deal.commented'
|
|
| 'deal.assigned'
|
|
| 'deal.balance_charged';
|
|
actor: { initials: string; name: string } | null; // null = system
|
|
minutesAgo: number;
|
|
detail: string;
|
|
}
|
|
|
|
export const MOCK_EVENTS: DealEvent[] = [
|
|
{
|
|
id: 1,
|
|
type: 'deal.created',
|
|
actor: null,
|
|
minutesAgo: 28,
|
|
detail: 'Лид принят с источника Я.Директ → landing-1',
|
|
},
|
|
{
|
|
id: 2,
|
|
type: 'deal.balance_charged',
|
|
actor: null,
|
|
minutesAgo: 28,
|
|
detail: 'Списано 1 лид с баланса (стоимость 1 850 ₽)',
|
|
},
|
|
{
|
|
id: 3,
|
|
type: 'deal.assigned',
|
|
actor: { initials: 'СА', name: 'Система автораспределения' },
|
|
minutesAgo: 27,
|
|
detail: 'Назначен менеджер: Иван П.',
|
|
},
|
|
{
|
|
id: 4,
|
|
type: 'deal.viewed',
|
|
actor: { initials: 'ИП', name: 'Иван П.' },
|
|
minutesAgo: 18,
|
|
detail: 'Менеджер открыл карточку',
|
|
},
|
|
{
|
|
id: 5,
|
|
type: 'deal.status_changed',
|
|
actor: { initials: 'ИП', name: 'Иван П.' },
|
|
minutesAgo: 12,
|
|
detail: 'Новые → Просмотрено',
|
|
},
|
|
{
|
|
id: 6,
|
|
type: 'deal.commented',
|
|
actor: { initials: 'ИП', name: 'Иван П.' },
|
|
minutesAgo: 8,
|
|
detail: 'Дозвонился, заинтересована — перезвоню после 14:00',
|
|
},
|
|
];
|
|
|
|
export function eventTypeLabel(type: DealEvent['type']): string {
|
|
const map: Record<DealEvent['type'], string> = {
|
|
'deal.created': 'Создана',
|
|
'deal.status_changed': 'Смена статуса',
|
|
'deal.viewed': 'Просмотр',
|
|
'deal.commented': 'Комментарий',
|
|
'deal.assigned': 'Назначение',
|
|
'deal.balance_charged': 'Списание',
|
|
};
|
|
return map[type];
|
|
}
|
|
|
|
export function eventTypeIcon(type: DealEvent['type']): string {
|
|
const map: Record<DealEvent['type'], string> = {
|
|
'deal.created': 'mdi-plus-circle-outline',
|
|
'deal.status_changed': 'mdi-swap-horizontal',
|
|
'deal.viewed': 'mdi-eye-outline',
|
|
'deal.commented': 'mdi-comment-outline',
|
|
'deal.assigned': 'mdi-account-arrow-right-outline',
|
|
'deal.balance_charged': 'mdi-currency-rub',
|
|
};
|
|
return map[type];
|
|
}
|