Files
portal/app/resources/js/views/admin/AdminLeadDetailView.vue
T

148 lines
7.3 KiB
Vue
Raw Normal View History

<script setup lang="ts">
/**
* Админка → Карточка лида (L4) — конечный источник: ОТКУДА пришёл лид
* (поставщик-проект + канал + регион) → КОМУ ушёл (сделки клиентов).
* Завершает сквозную вложенность дашборда (плитка Лиды → список → сюда).
*/
import { onMounted, ref } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { getLead, type LeadDetail } from '../../api/adminLeads';
const route = useRoute();
const router = useRouter();
const detail = ref<LeadDetail | null>(null);
const loading = ref(false);
const fetchError = ref(false);
const STATUS_META: Record<string, { label: string; color: string }> = {
delivered: { label: 'доставлен', color: 'success' },
no_match: { label: 'без получателя', color: 'warning' },
stuck: { label: 'завис', color: 'error' },
pending: { label: 'в обработке', color: 'info' },
error: { label: 'ошибка', color: 'error' },
};
function statusMeta(s: string) {
return STATUS_META[s] ?? { label: s, color: 'grey' };
}
function channelLabel(c: string | null): string {
return c === 'site' ? 'Сайт' : c === 'call' ? 'Звонок' : c === 'sms' ? 'SMS' : '—';
}
function fmtDate(v: string | null): string {
if (!v) return '—';
const m = v.match(/^(\d{4})-(\d{2})-(\d{2})[ T](\d{2}:\d{2})/);
return m ? `${m[3]}.${m[2]}.${m[1]} ${m[4]}` : v;
}
async function load() {
loading.value = true;
fetchError.value = false;
try {
detail.value = await getLead(route.params.id as string);
} catch {
fetchError.value = true;
} finally {
loading.value = false;
}
}
function openTenant(subdomain: string) {
router.push({ name: 'admin-tenant-detail', params: { code: subdomain } });
}
onMounted(load);
defineExpose({ detail, loading, fetchError, load });
</script>
<template>
<v-container fluid class="lead-detail pa-6">
<div class="d-flex align-center justify-space-between mb-3 flex-wrap ga-3">
<h1 class="text-h5 font-weight-bold">Лид #{{ route.params.id }}</h1>
<v-btn variant="text" class="text-none" prepend-icon="mdi-arrow-left" to="/admin/leads">Все лиды</v-btn>
</div>
<v-alert v-if="fetchError" type="warning" variant="tonal" density="compact" class="mb-4">
Не удалось загрузить лид.
</v-alert>
<template v-if="detail">
<v-row>
<!-- ОТКУДА -->
<v-col cols="12" md="6">
<v-card variant="outlined" class="h-100" data-testid="lead-source">
<v-card-title class="card-h">📥 Откуда пришёл</v-card-title>
<v-card-text>
<div class="kv"><span>Поставщик</span><b>{{ detail.source.platform }}</b></div>
<div class="kv"><span>Канал</span><b>{{ channelLabel(detail.source.channel) }}</b></div>
<div class="kv"><span>Источник</span><b>{{ detail.source.identifier ?? '—' }}</b></div>
<div class="kv"><span>Регион (код РФ)</span><b>{{ detail.lead.region_code ?? '—' }}</b></div>
<div class="kv"><span>Оператор</span><b>{{ detail.lead.phone_operator ?? '—' }}</b></div>
<div class="kv"><span>Телефон</span><b class="num">{{ detail.lead.phone_masked }}</b></div>
<v-btn
v-if="detail.source.supplier_project_id"
variant="text" size="small" class="text-none mt-2 px-0"
to="/admin/supplier-projects"
>
Открыть в «Проектах у поставщика»
</v-btn>
</v-card-text>
</v-card>
</v-col>
<!-- ЧТО / СТАТУС -->
<v-col cols="12" md="6">
<v-card variant="outlined" class="h-100">
<v-card-title class="card-h"> Лид</v-card-title>
<v-card-text>
<div class="kv"><span>Получен</span><b class="num">{{ fmtDate(detail.lead.received_at) }}</b></div>
<div class="kv"><span>Обработан</span><b class="num">{{ fmtDate(detail.lead.processed_at) }}</b></div>
<div class="kv">
<span>Статус</span>
<v-chip :color="statusMeta(detail.lead.status).color" size="x-small" variant="tonal">
{{ statusMeta(detail.lead.status).label }}
</v-chip>
</div>
<div class="kv"><span>Создано сделок</span><b>{{ detail.lead.deals_created_count }}</b></div>
<div v-if="detail.lead.error" class="kv">
<span>Ошибка</span><b class="text-error">{{ detail.lead.error }}</b>
</div>
</v-card-text>
</v-card>
</v-col>
</v-row>
<!-- КОМУ -->
<v-card variant="outlined" class="mt-4" data-testid="lead-deals">
<v-card-title class="card-h">📤 Кому ушёл сделки клиентов</v-card-title>
<v-card-text>
<v-table density="compact">
<thead>
<tr><th>Клиент</th><th>Статус сделки</th><th>Получена</th></tr>
</thead>
<tbody>
<tr v-for="d in detail.deals" :key="d.id" class="clk" @click="openTenant(d.subdomain)">
<td>{{ d.tenant_name }}</td>
<td>{{ d.status }}</td>
<td class="num">{{ fmtDate(d.received_at) }}</td>
</tr>
<tr v-if="detail.deals.length === 0">
<td colspan="3" class="text-center text-medium-emphasis">
Сделок по этому лиду нет (не распределён или нет совпадений у клиентов).
</td>
</tr>
</tbody>
</v-table>
</v-card-text>
</v-card>
</template>
</v-container>
</template>
<style scoped>
.lead-detail { max-width: 1100px; }
.card-h { font-size: 15px; font-weight: 700; }
.kv { display: flex; justify-content: space-between; padding: 6px 0; border-bottom: 1px solid rgba(0,0,0,0.05); }
.kv span { color: rgba(0,0,0,0.6); }
.num { font-family: 'JetBrains Mono', 'Consolas', monospace; font-variant-numeric: tabular-nums; }
.clk:hover { background: rgba(15, 110, 86, 0.06); cursor: pointer; }
</style>