feat(автоподбор/UI): понятные метки источников + впечатываемый регион + групповое создание
FieldCompetitorScreen: 1) метки ✓ настоящий номер / 🎭 рекламный подменный + плейн-язык «где нашли» + легенда «как читать» (данные в БД и сверка с ЭТАЛОНом НЕ тронуты — перевод только в показе); 2) регион «впечатывается» — свой тайп-ахед в стиле экрана (без Vuetify, чтобы не ломать тесты) в окнах «Создать проект» и «Настройки проекта»; чипы для мультивыбора, подпись округа; 3) галка «Вся РФ (все регионы)» даёт баннер-объявление; 4) групповое создание проектов из всех выбранных источников без проекта (source_ids пачкой), заголовок/текст под «несколько». +тест на групповое создание. Фронт-спеки 24/24 зелёные. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
import { computed, inject, onMounted, reactive, ref } from 'vue';
|
||||
import { useAutopodborStore } from '../../../stores/autopodborStore';
|
||||
import { autopodborErrorMessage, type FieldSourceDto, type WhereFound } from '../../../api/autopodbor';
|
||||
import { REGIONS } from '../../../constants/regions';
|
||||
import { REGIONS, FEDERAL_DISTRICT_NAMES } from '../../../constants/regions';
|
||||
import { useOverlayDismiss } from '../../../composables/useOverlayDismiss';
|
||||
|
||||
interface AutopodborNav {
|
||||
@@ -102,9 +102,22 @@ function phoneTypeLabel(s: FieldSourceDto) {
|
||||
if (s.signal_type !== 'call') return '';
|
||||
return { city: 'городской', mobile: 'мобильный', tollfree: '8-800' }[s.phone_type ?? ''] ?? '';
|
||||
}
|
||||
function phoneBadge(s: FieldSourceDto) {
|
||||
if (s.signal_type !== 'call') return '';
|
||||
return s.phone_kind === 'substitute' ? '🎭' : '✓';
|
||||
// Понятная клиенту метка вида номера: настоящий vs рекламный подменный.
|
||||
function kindTag(s: FieldSourceDto): { text: string; cls: string } | null {
|
||||
if (s.signal_type !== 'call') return null;
|
||||
return s.phone_kind === 'substitute'
|
||||
? { text: '🎭 рекламный подменный', cls: 'ld-b-stop' }
|
||||
: { text: '✓ настоящий номер', cls: 'ld-b-run' };
|
||||
}
|
||||
// Технические метки «где нашли» → человеческий язык (данные в БД не трогаем — только показ).
|
||||
const WF_LABELS: Record<string, string> = {
|
||||
'в коде сайта': 'на сайте',
|
||||
коллтрекинг: 'показан на сайте',
|
||||
'коллтрекинг (пул)': 'показан на сайте',
|
||||
'на сайте (код города?)': 'на сайте (проверить)',
|
||||
};
|
||||
function wfLabel(l: string): string {
|
||||
return WF_LABELS[l] ?? l;
|
||||
}
|
||||
function sourceValue(s: FieldSourceDto) {
|
||||
return s.project?.signal_identifier ?? s.identifier;
|
||||
@@ -289,9 +302,53 @@ async function saveAdd() {
|
||||
|
||||
// ——— создать проект из источника ———
|
||||
// regions=[] + vsyaRf=true → «Вся РФ» (как в обычных проектах: пустой массив = вся страна).
|
||||
const create = reactive({ open: false, srcId: null as number | null, srcLabel: '', regions: [] as number[], vsyaRf: true, limit: 5, mask: 127 });
|
||||
const create = reactive({ open: false, srcIds: [] as number[], srcLabel: '', regions: [] as number[], vsyaRf: true, limit: 5, mask: 127 });
|
||||
function openCreate(s: FieldSourceDto) {
|
||||
Object.assign(create, { open: true, srcId: s.id, srcLabel: sourceValue(s), regions: [], vsyaRf: true, limit: 5, mask: 127 });
|
||||
regQuery.value = '';
|
||||
Object.assign(create, { open: true, srcIds: [s.id], srcLabel: sourceValue(s), regions: [], vsyaRf: true, limit: 5, mask: 127 });
|
||||
}
|
||||
// Групповое создание: проекты для всех выбранных источников БЕЗ проекта — одним окном.
|
||||
function openBulkCreate() {
|
||||
const ids = selWork.value.filter((s) => !s.project).map((s) => s.id);
|
||||
if (!ids.length) return;
|
||||
regQuery.value = '';
|
||||
Object.assign(create, { open: true, srcIds: ids, srcLabel: `выбрано источников: ${ids.length}`, regions: [], vsyaRf: true, limit: 5, mask: 127 });
|
||||
}
|
||||
function onCreateVsyaRf() {
|
||||
if (create.vsyaRf) create.regions = [];
|
||||
}
|
||||
|
||||
// ——— свой тайп-ахед региона («впечатывается», как на слайде 1; в стиле экрана, без Vuetify) ———
|
||||
function regionName(code: number): string {
|
||||
return regions.find((r) => r.code === code)?.name ?? String(code);
|
||||
}
|
||||
// создание — мультивыбор
|
||||
const regQuery = ref('');
|
||||
const regOpen = ref(false);
|
||||
const regFiltered = computed(() => {
|
||||
const q = regQuery.value.trim().toLowerCase();
|
||||
const chosen = new Set(create.regions);
|
||||
return regions.filter((r) => !chosen.has(r.code) && (q === '' || r.name.toLowerCase().includes(q))).slice(0, 8);
|
||||
});
|
||||
function pickRegion(code: number) {
|
||||
if (!create.regions.includes(code)) create.regions.push(code);
|
||||
create.vsyaRf = false;
|
||||
regQuery.value = '';
|
||||
}
|
||||
function removeRegion(code: number) {
|
||||
create.regions = create.regions.filter((c) => c !== code);
|
||||
}
|
||||
// настройки — одиночный
|
||||
const setQuery = ref('');
|
||||
const setOpen = ref(false);
|
||||
const setFiltered = computed(() => {
|
||||
const q = setQuery.value.trim().toLowerCase();
|
||||
return regions.filter((r) => q === '' || r.name.toLowerCase().includes(q)).slice(0, 8);
|
||||
});
|
||||
function pickSetRegion(code: number) {
|
||||
psettings.regionCode = code;
|
||||
setQuery.value = regionName(code);
|
||||
setOpen.value = false;
|
||||
}
|
||||
function isDayOn(mask: number, i: number) {
|
||||
return (mask & (1 << i)) !== 0;
|
||||
@@ -303,24 +360,34 @@ function setCreateDays(all: boolean) {
|
||||
create.mask = all ? 127 : 31;
|
||||
}
|
||||
async function doCreate(launch: boolean) {
|
||||
if (create.srcId === null) return;
|
||||
if (create.srcIds.length === 0) return;
|
||||
// «Вся РФ» снята, но регион не выбран — не создаём молча, просим выбрать.
|
||||
if (!create.vsyaRf && create.regions.length === 0) {
|
||||
flash('Выберите хотя бы один регион или отметьте «Вся РФ».');
|
||||
return;
|
||||
}
|
||||
const many = create.srcIds.length > 1;
|
||||
busy.value = true;
|
||||
try {
|
||||
await store.makeProjects({
|
||||
source_ids: [create.srcId],
|
||||
source_ids: create.srcIds,
|
||||
regions: create.vsyaRf ? [] : create.regions,
|
||||
daily_limit_target: create.limit,
|
||||
delivery_days_mask: create.mask,
|
||||
launch,
|
||||
});
|
||||
create.open = false;
|
||||
selected.value = [];
|
||||
await reload();
|
||||
flash(launch ? 'Проект создан, идёт сбор ▶' : 'Проект создан (на паузе — запустите, когда готовы)');
|
||||
flash(
|
||||
many
|
||||
? launch
|
||||
? `Создано проектов: ${create.srcIds.length}, идёт сбор ▶`
|
||||
: `Создано проектов: ${create.srcIds.length} (на паузе — запустите, когда готовы)`
|
||||
: launch
|
||||
? 'Проект создан, идёт сбор ▶'
|
||||
: 'Проект создан (на паузе — запустите, когда готовы)',
|
||||
);
|
||||
} catch (e) {
|
||||
flash(autopodborErrorMessage(e, 'Не удалось создать проект. Попробуйте ещё раз.'));
|
||||
} finally {
|
||||
@@ -337,6 +404,7 @@ function openSettings(s: FieldSourceDto) {
|
||||
regionCode: s.project.regions?.[0] ?? regions[0]?.code ?? 24,
|
||||
limit: s.project.daily_limit_target, mask: s.project.delivery_days_mask || 127,
|
||||
});
|
||||
setQuery.value = regionName(psettings.regionCode);
|
||||
}
|
||||
function toggleSetDay(i: number) {
|
||||
psettings.mask ^= 1 << i;
|
||||
@@ -445,7 +513,7 @@ onMounted(() => {
|
||||
void reload();
|
||||
});
|
||||
|
||||
defineExpose({ ctab, selected, inWork, props: props, toast, create, switchTab, toggle, openEdit, openCollect, openAdd, openCreate, doCreate, toggleProject, bulkWork, bulkMoveToWork, moveToWork, sourceValue });
|
||||
defineExpose({ ctab, selected, inWork, props: props, toast, create, switchTab, toggle, openEdit, openCollect, openAdd, openCreate, openBulkCreate, doCreate, toggleProject, bulkWork, bulkMoveToWork, moveToWork, sourceValue });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -474,6 +542,13 @@ defineExpose({ ctab, selected, inWork, props: props, toast, create, switchTab, t
|
||||
<button class="ld-tab" :class="{ 'ld-tab--on': ctab === 'sugg' }" @click="switchTab('sugg')">Предложения <span class="ld-tab__c">{{ props.length }}</span></button>
|
||||
</div>
|
||||
|
||||
<div v-if="inWork.length || props.length" class="ld-legend">
|
||||
<b>Как читать:</b>
|
||||
<span class="ld-bdg ld-b-run">✓ настоящий номер</span> — живой номер фирмы, по нему дозвонитесь;
|
||||
<span class="ld-bdg ld-b-stop">🎭 рекламный подменный</span> — номер из рекламы, он меняется, в работу брать не стоит;
|
||||
<b>«где нашли»</b> — источник номера: кликните и проверьте сами.
|
||||
</div>
|
||||
|
||||
<!-- ===== РАБОТА ===== -->
|
||||
<template v-if="ctab === 'work'">
|
||||
<div v-if="inWork.length === 0" class="ld-note">Источников в работе пока нет — добавьте вручную или попросите собрать.</div>
|
||||
@@ -491,16 +566,16 @@ defineExpose({ ctab, selected, inWork, props: props, toast, create, switchTab, t
|
||||
<div class="ld-srcicon">{{ s.signal_type === 'call' ? '📞' : '🌐' }}</div>
|
||||
<div class="ld-srcmain">
|
||||
<div class="ld-srctitle">
|
||||
<span v-if="phoneBadge(s)">{{ phoneBadge(s) }}</span>
|
||||
{{ sourceValue(s) }}
|
||||
<span v-if="phoneTypeLabel(s)" class="ld-bdg" :class="s.phone_type === 'mobile' ? 'ld-bdg--fed' : 'ld-bdg--loc'">{{ phoneTypeLabel(s) }}</span>
|
||||
<span v-if="kindTag(s)" class="ld-bdg" :class="kindTag(s)!.cls">{{ kindTag(s)!.text }}</span>
|
||||
</div>
|
||||
<div v-if="s.office" class="ld-srcprov">🏢 {{ s.office }}</div>
|
||||
<div class="ld-found">
|
||||
<span class="ld-found__lbl">где нашли:</span>
|
||||
<template v-for="(wf, i) in whereFound(s)" :key="i">
|
||||
<a v-if="safeUrl(wf.url)" class="ld-fchip" :href="safeUrl(wf.url)!" target="_blank" rel="noopener">{{ wf.label }}</a>
|
||||
<span v-else class="ld-fchip ld-fchip--plain">{{ wf.label }}</span>
|
||||
<a v-if="safeUrl(wf.url)" class="ld-fchip" :href="safeUrl(wf.url)!" target="_blank" rel="noopener">{{ wfLabel(wf.label) }}</a>
|
||||
<span v-else class="ld-fchip ld-fchip--plain">{{ wfLabel(wf.label) }}</span>
|
||||
</template>
|
||||
<span v-if="!whereFound(s).length" class="ld-found__lbl">—</span>
|
||||
<span v-if="confirms(s) > 1" class="ld-found__cnt">✓{{ confirms(s) }} подтвержд.</span>
|
||||
@@ -532,7 +607,7 @@ defineExpose({ ctab, selected, inWork, props: props, toast, create, switchTab, t
|
||||
<span>Выбрано источников без проекта: <b>{{ selected.length }}</b> — создадим проекты разом</span>
|
||||
<div class="ld-bulkbar__acts">
|
||||
<button class="ld-btn gray sm" @click="clearSel">Снять выбор</button>
|
||||
<button class="ld-btn primary sm" :disabled="busy" @click="openCreate(selWork[0])">Создать проекты для выбранных →</button>
|
||||
<button class="ld-btn primary sm" :disabled="busy" @click="openBulkCreate()">Создать проекты для выбранных →</button>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="selAllHaveProj">
|
||||
@@ -566,17 +641,17 @@ defineExpose({ ctab, selected, inWork, props: props, toast, create, switchTab, t
|
||||
<div class="ld-srcicon">{{ s.signal_type === 'call' ? '📞' : '🌐' }}</div>
|
||||
<div class="ld-srcmain">
|
||||
<div class="ld-srctitle">
|
||||
<span v-if="phoneBadge(s)">{{ phoneBadge(s) }}</span>
|
||||
{{ s.identifier }}
|
||||
<span v-if="phoneTypeLabel(s)" class="ld-bdg" :class="s.phone_type === 'mobile' ? 'ld-bdg--fed' : 'ld-bdg--loc'">{{ phoneTypeLabel(s) }}</span>
|
||||
<span v-if="kindTag(s)" class="ld-bdg" :class="kindTag(s)!.cls">{{ kindTag(s)!.text }}</span>
|
||||
<span class="ld-bdg ld-bdg--sug">предложение</span>
|
||||
</div>
|
||||
<div v-if="s.office" class="ld-srcprov">🏢 {{ s.office }}</div>
|
||||
<div class="ld-found">
|
||||
<span class="ld-found__lbl">где нашли:</span>
|
||||
<template v-for="(wf, i) in whereFound(s)" :key="i">
|
||||
<a v-if="safeUrl(wf.url)" class="ld-fchip" :href="safeUrl(wf.url)!" target="_blank" rel="noopener">{{ wf.label }}</a>
|
||||
<span v-else class="ld-fchip ld-fchip--plain">{{ wf.label }}</span>
|
||||
<a v-if="safeUrl(wf.url)" class="ld-fchip" :href="safeUrl(wf.url)!" target="_blank" rel="noopener">{{ wfLabel(wf.label) }}</a>
|
||||
<span v-else class="ld-fchip ld-fchip--plain">{{ wfLabel(wf.label) }}</span>
|
||||
</template>
|
||||
<span v-if="!whereFound(s).length" class="ld-found__lbl">—</span>
|
||||
<span v-if="confirms(s) > 1" class="ld-found__cnt">✓{{ confirms(s) }} подтвержд.</span>
|
||||
@@ -660,8 +735,8 @@ defineExpose({ ctab, selected, inWork, props: props, toast, create, switchTab, t
|
||||
<!-- ===== Окно: Создать проект ===== -->
|
||||
<div v-if="create.open" class="ld-ovl" @mousedown="onOverlayMousedown" @mouseup="(e) => onOverlayMouseup(e, () => { create.open = false; })">
|
||||
<div class="ld-modal">
|
||||
<h3 class="ld-modal__h">Создать проект из источника</h3>
|
||||
<p class="ld-modal__m">{{ create.srcLabel }}. 📣 Лидерра поставит проект в сбор сразу. Первые лиды пойдут с {{ leadStartText() }}.</p>
|
||||
<h3 class="ld-modal__h">{{ create.srcIds.length > 1 ? 'Создать проекты из выбранных источников' : 'Создать проект из источника' }}</h3>
|
||||
<p class="ld-modal__m">{{ create.srcLabel }}. 📣 Лидерра поставит {{ create.srcIds.length > 1 ? 'проекты' : 'проект' }} в сбор сразу. Первые лиды пойдут с {{ leadStartText() }}.</p>
|
||||
<div class="ld-rules">
|
||||
<h4>Как заполнить</h4>
|
||||
<ul>
|
||||
@@ -671,9 +746,21 @@ defineExpose({ ctab, selected, inWork, props: props, toast, create, switchTab, t
|
||||
</ul>
|
||||
</div>
|
||||
<div class="ld-fld"><label>Регионы <span class="ld-req">*</span></label>
|
||||
<label class="ld-selall" style="margin-bottom: 6px"><input type="checkbox" v-model="create.vsyaRf" /> Вся РФ</label>
|
||||
<select v-if="!create.vsyaRf" v-model="create.regions" multiple class="ld-in" size="6"><option v-for="r in regions" :key="r.code" :value="r.code">{{ r.name }}</option></select>
|
||||
<div class="ld-hint">Отметьте «Вся РФ» или снимите галочку и выберите один или несколько регионов (Ctrl/⌘ — несколько).</div></div>
|
||||
<div class="ld-ta" :class="{ 'ld-ta--off': create.vsyaRf }">
|
||||
<div v-if="create.regions.length" class="ld-ta__chips">
|
||||
<span v-for="code in create.regions" :key="code" class="ld-ta__chip">{{ regionName(code) }}<button type="button" class="ld-ta__x" @click="removeRegion(code)">×</button></span>
|
||||
</div>
|
||||
<input v-model="regQuery" class="ld-in ld-ta__inp" :disabled="create.vsyaRf" placeholder="Начните вводить регион…" @focus="regOpen = true" @blur="regOpen = false" />
|
||||
<div v-if="regOpen && !create.vsyaRf && regFiltered.length" class="ld-ta__menu">
|
||||
<button v-for="r in regFiltered" :key="r.code" type="button" class="ld-ta__opt" @mousedown.prevent="pickRegion(r.code)">
|
||||
<span class="ld-ta__opt-name">{{ r.name }}</span>
|
||||
<span class="ld-ta__opt-sub">{{ FEDERAL_DISTRICT_NAMES[r.federalDistrict] || '' }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<label class="ld-selall" style="margin-top: 8px"><input type="checkbox" v-model="create.vsyaRf" @change="onCreateVsyaRf" /> Вся РФ (все регионы)</label>
|
||||
<div v-if="create.vsyaRf" class="ld-banner" style="margin-top: 6px"><div class="ld-banner__txt">ℹ️ Проект будет получать лиды по всей России — по всем субъектам страны.</div></div>
|
||||
<div v-else class="ld-hint">Начните вводить название региона — можно выбрать один или несколько.</div></div>
|
||||
<div class="ld-fld"><label>Лимит — заявок в день <span class="ld-req">*</span></label><input v-model.number="create.limit" type="number" min="1" class="ld-in" /><div class="ld-hint">Если лимит превысит баланс — проект приостановится, пока не пополните счёт.</div></div>
|
||||
<div class="ld-fld"><label>Дни недели приёма</label>
|
||||
<div class="ld-days"><button v-for="(d, i) in DAYS" :key="i" class="ld-day" :class="{ 'ld-day--on': isDayOn(create.mask, i) }" @click="toggleCreateDay(i)">{{ d }}</button></div>
|
||||
@@ -693,7 +780,16 @@ defineExpose({ ctab, selected, inWork, props: props, toast, create, switchTab, t
|
||||
<h3 class="ld-modal__h">Настройки проекта</h3>
|
||||
<p class="ld-modal__m">{{ psettings.label }}. Меняйте объём, регионы и дни приёма.</p>
|
||||
<div class="ld-banner" style="margin: 0 0 14px"><div class="ld-banner__txt">⏳ Ближайший сбор уже идёт по текущим настройкам. Новые количество, регионы и дни вступят в силу с <b>{{ appliesFromText() }}</b> в 21:00 МСК. Пауза действует сразу.</div></div>
|
||||
<div class="ld-fld"><label>Регион <span class="ld-req">*</span></label><select v-model="psettings.regionCode" class="ld-in"><option v-for="r in regions" :key="r.code" :value="r.code">{{ r.name }}</option></select></div>
|
||||
<div class="ld-fld"><label>Регион <span class="ld-req">*</span></label>
|
||||
<div class="ld-ta">
|
||||
<input v-model="setQuery" class="ld-in ld-ta__inp" placeholder="Начните вводить регион…" @focus="setOpen = true; setQuery = ''" @blur="setOpen = false; setQuery = regionName(psettings.regionCode)" />
|
||||
<div v-if="setOpen && setFiltered.length" class="ld-ta__menu">
|
||||
<button v-for="r in setFiltered" :key="r.code" type="button" class="ld-ta__opt" @mousedown.prevent="pickSetRegion(r.code)">
|
||||
<span class="ld-ta__opt-name">{{ r.name }}</span>
|
||||
<span class="ld-ta__opt-sub">{{ FEDERAL_DISTRICT_NAMES[r.federalDistrict] || '' }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div></div>
|
||||
<div class="ld-fld"><label>Лимит — заявок в день <span class="ld-req">*</span></label><input v-model.number="psettings.limit" type="number" min="1" class="ld-in" /><div class="ld-hint">Если лимит превысит баланс — проект приостановится, пока не пополните счёт.</div></div>
|
||||
<div class="ld-fld"><label>Дни недели приёма</label>
|
||||
<div class="ld-days"><button v-for="(d, i) in DAYS" :key="i" class="ld-day" :class="{ 'ld-day--on': isDayOn(psettings.mask, i) }" @click="toggleSetDay(i)">{{ d }}</button></div>
|
||||
@@ -764,4 +860,88 @@ defineExpose({ ctab, selected, inWork, props: props, toast, create, switchTab, t
|
||||
padding: 22px 0 96px;
|
||||
max-width: 1080px;
|
||||
}
|
||||
.ld-legend {
|
||||
margin: 4px 0 16px;
|
||||
padding: 10px 14px;
|
||||
background: #fff;
|
||||
border: 1px solid #e4e0d6;
|
||||
border-radius: 10px;
|
||||
font-size: 12.5px;
|
||||
line-height: 2;
|
||||
color: #5b6b64;
|
||||
}
|
||||
.ld-legend .ld-bdg {
|
||||
margin: 0 3px;
|
||||
}
|
||||
/* Свой тайп-ахед региона («впечатывается») */
|
||||
.ld-ta {
|
||||
position: relative;
|
||||
}
|
||||
.ld-ta--off {
|
||||
opacity: 0.5;
|
||||
}
|
||||
.ld-ta__chips {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
.ld-ta__chip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
background: #eef4f1;
|
||||
color: #0f6e56;
|
||||
border-radius: 999px;
|
||||
padding: 3px 6px 3px 11px;
|
||||
font-size: 13px;
|
||||
}
|
||||
.ld-ta__x {
|
||||
border: 0;
|
||||
background: transparent;
|
||||
color: #0f6e56;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
line-height: 1;
|
||||
padding: 0 3px;
|
||||
}
|
||||
.ld-ta__inp {
|
||||
width: 100%;
|
||||
}
|
||||
.ld-ta__menu {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 100%;
|
||||
z-index: 40;
|
||||
margin-top: 4px;
|
||||
max-height: 240px;
|
||||
overflow-y: auto;
|
||||
background: #fff;
|
||||
border: 1px solid #e4e0d6;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 8px 24px rgba(1, 32, 25, 0.14);
|
||||
}
|
||||
.ld-ta__opt {
|
||||
display: block;
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
padding: 8px 12px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.ld-ta__opt:hover {
|
||||
background: #f2f7f4;
|
||||
}
|
||||
.ld-ta__opt-name {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
color: #012019;
|
||||
}
|
||||
.ld-ta__opt-sub {
|
||||
display: block;
|
||||
font-size: 11px;
|
||||
color: #8a948f;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -131,4 +131,25 @@ describe('FieldCompetitorScreen — создание проекта из ист
|
||||
expect(String(vm.toast).toLowerCase()).toContain('реквизит');
|
||||
expect(String(vm.toast).toLowerCase()).not.toContain('баланс');
|
||||
});
|
||||
|
||||
it('групповое создание: doCreate шлёт source_ids всех выбранных источников без проекта', async () => {
|
||||
(api.fetchCompetitor as ReturnType<typeof vi.fn>).mockResolvedValue({
|
||||
competitor: { id: 10, name: 'Окна', is_federal: false, relevance_pct: 90, origin: 'auto', box: 'field', site_url: 'okna.ru', directory_urls: [], studied_at: '2026-01-01', study_run_id: 1, search_run_id: 1, description: '' },
|
||||
sources: [makeSource(1), makeSource(2)],
|
||||
});
|
||||
(api.createProjects as ReturnType<typeof vi.fn>).mockResolvedValue({ data: [], launch: null });
|
||||
const store = useAutopodborStore();
|
||||
await store.loadCompetitor(10);
|
||||
const wrapper = mount(FieldCompetitorScreen, {
|
||||
global: { provide: { autopodborNav: makeNav() }, stubs: { Transition: false } },
|
||||
});
|
||||
await new Promise((r) => setTimeout(r, 50));
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const vm = wrapper.vm as any;
|
||||
vm.selected = [1, 2];
|
||||
vm.openBulkCreate();
|
||||
await wrapper.vm.$nextTick();
|
||||
await vm.doCreate(true);
|
||||
expect(api.createProjects).toHaveBeenLastCalledWith(expect.objectContaining({ source_ids: [1, 2], launch: true }));
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user