Files
portal/app/resources/js/views/BillingView.vue
T
Дмитрий 6387706be6 fix(a11y): .sep dot separator contrast 2.92:1 → 5.33:1 (Pattern B)
A11y rescan Pattern B — scoped CSS `.sep { color: #92907b; }` повторяется
в 8 компонентах (page-stats / page-meta / hero-meta containers с точкой-
разделителем `·`). На ivory page background #f6f3ec даёт contrast
2.92:1, ниже WCAG 2.1 AA 4.5:1 threshold.

Fix: #92907b → #6b6356 — same warm-grey hue, darker tone, gives
5.33:1 contrast. 8 files:

- views/{DealsView,BillingView,KanbanView,ReportsView}.vue
- components/dashboard/DashboardPageHead.vue
- components/deals/DealDetailHero.vue
- components/admin/tenants/TenantsStatsHeader.vue
- components/admin/tenant-detail/TenantDetailHeader.vue

Closes Pa11y «color-contrast» violations на /dashboard /billing /reports
(8 .sep elements total flagged).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 10:07:11 +03:00

136 lines
5.3 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
/**
* Биллинг и тарифы — финансовый экран. Кошелёк ₽, баланс лидов,
* текущий тариф, история транзакций и счета/УПД.
*
* Источник дизайна: liderra_v8_handoff/concepts/v8_billing.html.
* MVP: page-head + pending banner + 3 wallet-cards (BalanceCard) +
* transactions table с табами (TransactionsTable) + invoices list (InvoicesTable).
* Mock-данные из composables/mockBilling.ts.
*
* Sprint 4 Phase B/2 — split на shell + 3 sub-components (audit O-refactor-04 хвост).
*
* Plan 4 Task 11 — добавлен top-level v-tabs split:
* - "Обзор" — существующий контент (mock-balance, transactions, invoices).
* - "Списания" — ChargesTab, real backend ledger (GET /api/billing/charges).
*
* Не входит в MVP:
* - TopupDialog (диалог настройки автопополнения через ЮKassa).
* - Tariff change wizard (диалог смены тарифа с расчётом разницы).
* - Tariff comparison table (4 тарифа: Solo/Команда/Бизнес/Корпоративный).
* - Refund-request dialog (заявка на возврат).
*
* Backend (отдельный коммит):
* - GET /api/billing/wallet — балансы.
* - GET /api/billing/transactions?type=...&page=... — пагинация.
* - POST /api/billing/topup → ЮKassa-checkout.
* - GET /api/billing/invoices/{id}/file — PDF/XML download.
*/
import { ref } from 'vue';
import BalanceCard from '../components/billing/BalanceCard.vue';
import TransactionsTable from '../components/billing/TransactionsTable.vue';
import InvoicesTable from '../components/billing/InvoicesTable.vue';
import ChargesTab from './billing/ChargesTab.vue';
import { MOCK_PENDING } from '../composables/mockBilling';
import { formatPlain } from '../composables/billingFormatters';
const walletRub = 14250;
const leadsBalance = 285;
const runwayDays = 4;
const tariffName = 'Команда';
const tariffPrice = 990;
const tariffFeatures = ['до 10 проектов', '4 менеджера + расширение', 'Канбан, Webhook, API'];
const activeView = ref<'overview' | 'charges'>('overview');
</script>
<template>
<v-container fluid class="billing pa-6">
<header class="page-head">
<div>
<h1 class="text-h4 mb-2 page-title">Биллинг и тарифы</h1>
<div class="page-stats text-body-2 text-medium-emphasis">
<span
><span class="num text-primary">{{ formatPlain(walletRub) }}</span> кошелёк</span
>
<span class="sep">·</span>
<span
><span class="num">{{ leadsBalance }}</span> лидов запас</span
>
<span class="sep">·</span>
<span
>хватит на <span class="num">{{ runwayDays }} дня</span></span
>
</div>
</div>
<v-btn color="primary" variant="flat" prepend-icon="mdi-plus">Пополнить баланс</v-btn>
</header>
<v-tabs v-model="activeView" color="primary" class="mt-4">
<v-tab value="overview">Обзор</v-tab>
<v-tab value="charges">Списания</v-tab>
</v-tabs>
<v-tabs-window v-model="activeView">
<v-tabs-window-item value="overview">
<v-alert v-if="MOCK_PENDING" type="info" variant="tonal" density="compact" class="mt-4" role="status">
<strong>1 платёж в обработке</strong> {{ formatPlain(MOCK_PENDING.amount) }} от
{{ MOCK_PENDING.method }}, начат {{ MOCK_PENDING.startedAt }}. Авто-восстановление в
{{ MOCK_PENDING.autoCancelAt }} ({{ MOCK_PENDING.timeoutMinutes }} мин). Кнопки «Отменить» нет это
техническое решение.
</v-alert>
<BalanceCard
:wallet-rub="walletRub"
:leads-balance="leadsBalance"
:tariff-name="tariffName"
:tariff-price="tariffPrice"
:tariff-features="tariffFeatures"
/>
<TransactionsTable />
<InvoicesTable />
</v-tabs-window-item>
<v-tabs-window-item value="charges">
<ChargesTab />
</v-tabs-window-item>
</v-tabs-window>
</v-container>
</template>
<style scoped>
.billing {
max-width: 1440px;
}
.page-head {
display: flex;
align-items: flex-start;
justify-content: space-between;
flex-wrap: wrap;
gap: 16px;
}
.page-title {
font-variation-settings: 'opsz' 28;
letter-spacing: -0.018em;
}
.page-stats {
display: flex;
flex-wrap: wrap;
gap: 6px;
align-items: center;
}
.page-stats .sep {
/* WCAG2AA 4.5:1 fix (was #92907b → 2.92:1 on ivory; #6b6356 → 5.33:1). */
color: #6b6356;
}
.num {
font-family: 'JetBrains Mono', ui-monospace, monospace;
font-feature-settings: 'tnum';
font-weight: 500;
}
</style>