diff --git a/app/resources/js/components/advertising/CampaignMessages.vue b/app/resources/js/components/advertising/CampaignMessages.vue index d3f183da..9651ba3a 100644 --- a/app/resources/js/components/advertising/CampaignMessages.vue +++ b/app/resources/js/components/advertising/CampaignMessages.vue @@ -3,7 +3,7 @@ * CampaignMessages — окно передачи между Яндексом и клиентом по одной кампании. * Слова Яндекса показываем как есть, без своей трактовки. */ -import { ref, watch } from 'vue'; +import { computed, ref, watch } from 'vue'; import { fetchCampaignMessages, sendCampaignMessage, type CampaignMessage } from '../../api/advertising'; const props = defineProps<{ campaignId: number | null }>(); @@ -55,6 +55,66 @@ async function send(): Promise { } } +/** + * Одинаковый текст подряд — одна строка на экране. + * + * Яндекс отвечает по каждому объявлению отдельно, и на общую беду («предоставьте + * документы») приходит пятнадцать одинаковых слов сразу. Записаны они по объявлениям + * не зря — по записи разбирают, кого именно не пустили, — но клиенту показывать стену + * из одинакового текста нельзя: в ней теряется второе, РАЗНОЕ сообщение. + * Замер 03.08.2026 на кампании #6: 15 сообщений, из них разных всего два. + * + * 🪤 Склеиваем только соседей: разнести одинаковые ответы, между которыми клиент писал + * своё, значило бы переставить переписку местами. И никогда не склеиваем сообщения + * с приложенным файлом — иначе ссылка на документ исчезнет с экрана. + */ +type SklejennoeSoobshchenie = { + id: number; + author: CampaignMessage['author']; + body: string; + file_name: string | null; + povtory: number; +}; + +const sklejennye = computed(() => { + const rows: SklejennoeSoobshchenie[] = []; + + for (const m of messages.value) { + const last = rows[rows.length - 1]; + const povtor = + last !== undefined && + last.file_name === null && + (m.file_name ?? null) === null && + last.author === m.author && + last.body === m.body; + + if (povtor && last !== undefined) { + last.povtory += 1; + continue; + } + + rows.push({ id: m.id, author: m.author, body: m.body, file_name: m.file_name ?? null, povtory: 0 }); + } + + return rows; +}); + +/** «и ещё 2 объявления» — с правильным русским окончанием. */ +function povtoryuSlovami(n: number): string { + const posledn = n % 10; + const desyatki = n % 100; + const slovo = + desyatki >= 11 && desyatki <= 14 + ? 'объявлений' + : posledn === 1 + ? 'объявление' + : posledn >= 2 && posledn <= 4 + ? 'объявления' + : 'объявлений'; + + return `и ещё ${n} ${slovo}`; +} + watch(() => props.campaignId, reload, { immediate: true }); @@ -65,13 +125,20 @@ watch(() => props.campaignId, reload, { immediate: true });

- + {{ AUTHOR_LABEL[m.author] }} {{ m.body }} + + {{ povtoryuSlovami(m.povtory) }} +