feat(external): фронт — плитка «Внешние сервисы» (баланс + живость)
Accessibility (Pa11y live) / a11y (push) Has been cancelled
SAST — Semgrep / Semgrep SAST scan (push) Has been cancelled

Статус-текст для сервисов без денежного баланса (жив/не отвечает/выключено),
метки и иконки почты/ЮKassa/Jivo/капчи, обновлённые подписи плитки и дрилла.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Дмитрий
2026-07-02 08:28:12 +03:00
parent 21e1b7982f
commit b7bc52be85
@@ -103,11 +103,19 @@ const SERVICE_LABELS: Record<string, string> = {
dadata: 'DaData',
supplier: 'Поставщик',
yandex_cloud: 'Yandex Cloud',
email: 'Почта',
yookassa: 'ЮKassa',
jivosite: 'JivoSite',
captcha: 'Капча',
};
const SERVICE_ICONS: Record<string, string> = {
dadata: '🧭',
supplier: '📦',
yandex_cloud: '☁️',
email: '✉️',
yookassa: '💳',
jivosite: '💬',
captcha: '🛡',
};
function serviceLabel(key: string): string {
@@ -122,6 +130,17 @@ function daysLeftLabel(days: number | null): string {
return days === null ? '—' : `~${days} дн.`;
}
/** Значение в строке сервиса: деньги (если есть баланс) или статус живости. */
function serviceValue(s: { balance_amount: string | null; light: string; ok: boolean }): string {
if (s.balance_amount !== null) {
return s.ok ? rub(s.balance_amount) : '—';
}
// Сервис без денег — показываем статус живости.
if (s.light === 'green') return 'жив';
if (s.light === 'red') return 'не отвечает';
return 'выключено'; // grey
}
/** Подпись светофора Клиентов на плитке. */
function clientsLightLabel(): string {
const d = summary.value?.clients.dormant ?? 0;
@@ -542,7 +561,7 @@ defineExpose({ period, dateFrom, dateTo, showCustom, selected, summary, finance,
<span
class="num font-weight-bold"
:class="{ 'text-error': s.light === 'red' }"
>{{ s.ok ? rub(s.balance_amount) : 'нет данных' }}</span>
>{{ serviceValue(s) }}</span>
<v-icon :color="lightColor(s.light)" size="11" icon="mdi-circle" />
</span>
</div>
@@ -870,12 +889,13 @@ v-for="g in supply?.groups ?? []" :key="g.signal_type + '|' + g.identifier"
<!-- DRILL: БАЛАНСЫ СЕРВИСОВ -->
<v-card v-else-if="selected === 'balances'" variant="outlined" class="drill mt-5" data-testid="drill-balances">
<v-card-title class="drill__head">💳 Балансы внешних сервисов детали</v-card-title>
<v-card-title class="drill__head">🌐 Внешние сервисы баланс и доступность</v-card-title>
<v-card-text>
<v-alert variant="tonal" density="compact" class="mb-4" type="info">
Баланс платных сервисов проверяется раз в сутки (06:30 МСК). Светофор: 🔴 мало денег
или хватит меньше 3 дней, 🟡 меньше 7 дней, не удалось обновить.
Кнопка «Пополнить» открывает страницу оплаты сервиса.
Внешние сервисы проверяются раз в сутки (06:30 МСК): у платных остаток денег,
у остальных жив ли сервис. Светофор: 🔴 упал / мало денег / хватит меньше 3 дней,
🟡 меньше 7 дней, не удалось проверить или выключен. При переходе в 🔴 приходит
письмо на ops-адрес.
</v-alert>
<v-table density="compact">
<thead>
@@ -891,9 +911,9 @@ v-for="g in supply?.groups ?? []" :key="g.signal_type + '|' + g.identifier"
<tr v-for="s in balances?.services ?? []" :key="s.service_key">
<td>{{ serviceIcon(s.service_key) }} {{ serviceLabel(s.service_key) }}</td>
<td class="text-right num" :class="{ 'text-error': s.light === 'red' }">
{{ s.ok ? rub(s.balance_amount) : '—' }}
{{ serviceValue(s) }}
</td>
<td class="text-right num">{{ s.ok ? daysLeftLabel(s.days_left) : '—' }}</td>
<td class="text-right num">{{ s.balance_amount !== null && s.ok ? daysLeftLabel(s.days_left) : '—' }}</td>
<td class="text-center">
<v-chip
:color="lightColor(s.light)"