9ff19f3cc3
feat(bot): всеобъемлющая инструкция (36 статей) + 31 узкая экскурсия, которая открывает окна Живой урок владельца 12.07.2026: «дубли чистятся в 2-х местах, а бот выдумал третье», «стыдно такого бота показывать», «видео показываем полное, а я спрашиваю про одно — надо резать», «надо не только кнопку показывать, а и что за ней». База знаний: 17 обзорных статей → 36 узких по темам. Портал обойдён целиком (5 агентов): все экраны, окна, подсказки «(?)», тексты ошибок, внутренние правила. Новое, чего бот не знал: дубли в ДВУХ местах (Поле и Предложения) + окно «источник уже есть»; правило 18:00; почему приходит меньше лимита; оплата по счёту и акты; списания; безопасность и 2FA; отчёты; массовые действия; регистрация. Экскурсии: 15 → 31, каждая на свою тему (3–7 шагов). Раннер GuidedTour научен шагу `open` — сам открывает окно/вкладку и подсвечивает то, что ВНУТРИ (окно дублей, форму подбора, диалог пополнения, панель тарифов, карточку сделки). 99 новых якорей data-tour расставлено по всему порталу (4 агента). Поиск: веса в tsvector (заголовок A / синонимы B / текст C) — иначе на «чистка дублей» первой всплывала статья «Списания» из-за мимоходного упоминания «поля». Тесты: Pest бот 39/39, Vitest 1222/1222. Новые сторожа: у каждой статьи есть экскурсия; экскурсия не короче 3 и не длиннее 7 шагов; каждая цель экскурсии реально существует в разметке; экскурсии про действия обязаны открывать окно. Живая проверка в браузере: экскурсия про дубли сама открыла окно объединения и перешла на второе место чистки. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> @
81 lines
3.1 KiB
Vue
81 lines
3.1 KiB
Vue
<script setup lang="ts">
|
|
/**
|
|
* Постоянная подсказка под балансом (Billing v2 Spec C §3.6, Task 1.10).
|
|
*
|
|
* Чистый presentational-компонент: показывает, на сколько дней хватит ёмкости
|
|
* баланса (в лидах) при текущем дневном заказе всех eligible-проектов.
|
|
* Зелёный — хватает на 3+ дня; жёлтый — меньше 3 дней; красный — не хватает.
|
|
*/
|
|
import { computed } from 'vue';
|
|
|
|
const props = defineProps<{
|
|
/** Баланс в рублях (строка scale 2, например "1000.00"). */
|
|
balanceRub: string;
|
|
/** Сколько лидов покрывает баланс по текущему тарифу. */
|
|
capacityLeads: number;
|
|
/** Суммарный дневной заказ всех активных проектов (лидов/день). */
|
|
requiredLeadsPerDay: number;
|
|
}>();
|
|
|
|
const daysLeft = computed(() =>
|
|
props.requiredLeadsPerDay > 0 ? props.capacityLeads / props.requiredLeadsPerDay : Infinity,
|
|
);
|
|
|
|
const statusClass = computed(() => {
|
|
if (props.requiredLeadsPerDay > 0 && props.capacityLeads < props.requiredLeadsPerDay) {
|
|
return 'capacity-insufficient';
|
|
}
|
|
if (daysLeft.value < 3) return 'capacity-warning';
|
|
return 'capacity-ok';
|
|
});
|
|
|
|
const daysLabel = computed(() => (Number.isFinite(daysLeft.value) ? daysLeft.value.toFixed(1) : '∞'));
|
|
|
|
// Разделитель тысяч + без лишних копеек: "1000.00" → "1 000".
|
|
const balanceFormatted = computed(() =>
|
|
new Intl.NumberFormat('ru-RU', { maximumFractionDigits: 0 }).format(parseFloat(props.balanceRub) || 0),
|
|
);
|
|
</script>
|
|
|
|
<template>
|
|
<div
|
|
class="balance-capacity text-body-2"
|
|
:class="statusClass"
|
|
data-testid="balance-capacity-indicator"
|
|
data-tour="billing-capacity"
|
|
>
|
|
<div>Баланс: {{ balanceFormatted }} ₽ = до {{ capacityLeads }} лидов по тарифу</div>
|
|
<div>Проекты заказывают: {{ requiredLeadsPerDay }} лидов в день</div>
|
|
<div v-if="statusClass === 'capacity-insufficient'" class="capacity-note">
|
|
<v-icon size="14" class="mr-1">mdi-alert-outline</v-icon>Не хватает — пополните счёт
|
|
</div>
|
|
<div v-else-if="statusClass === 'capacity-warning'" class="capacity-note">
|
|
Хватит на ~{{ daysLabel }} дн. — скоро потребуется пополнение
|
|
</div>
|
|
<div v-else class="capacity-note">
|
|
<v-icon size="14" class="mr-1">mdi-check-circle-outline</v-icon>Хватит на ~{{ daysLabel }} дн.
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.balance-capacity {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 2px;
|
|
line-height: 1.4;
|
|
}
|
|
.capacity-note {
|
|
font-weight: 600;
|
|
}
|
|
.capacity-ok .capacity-note {
|
|
color: rgb(var(--v-theme-success));
|
|
}
|
|
.capacity-warning .capacity-note {
|
|
color: rgb(var(--v-theme-warning));
|
|
}
|
|
.capacity-insufficient .capacity-note {
|
|
color: rgb(var(--v-theme-error));
|
|
}
|
|
</style>
|