6387706be6
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>
136 lines
5.3 KiB
Vue
136 lines
5.3 KiB
Vue
<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>
|