849bc73290
BillingView 416→114 (+ BalanceCard 155 + TransactionsTable 113 + InvoicesTable 90 + billingFormatters 51 composable: formatPlain/formatCost/statusChipColor/ statusLabel/formatLabel/formatIcon/txAmountClass). SecurityTab 354→39 (+ ChangePasswordCard 17 + TwoFactorCard 218 + RecoveryCodesCard 104 + SessionsTable 66; auth-store читается напрямую в каждом sub-component). RemindersView 345→183 (+ RemindersFilters 51 + RemindersList 173; ReminderDialog уже отдельный с прошлой фазы — служит как ReminderForm). State (`activeTab`, `editingReminder`, `deletingReminderId` в RemindersView) остаётся в parent ради единого reload-flow + confirm-dialog'ов. Auth-store читается напрямую в TwoFactorCard/RecoveryCodesCard через useAuthStore() — без prop-drilling. Reminders-store читается напрямую в RemindersFilters/ RemindersList. Все sub-components <250 строк (acceptance threshold). 3 view-shells: 114/39/183. Регрессия: ESLint 0 + vue-tsc 0 + Vitest 416/416 + build OK 968 ms. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
156 lines
4.5 KiB
Vue
156 lines
4.5 KiB
Vue
<script setup lang="ts">
|
|
/**
|
|
* BalanceCard — 3 wallet-cards в одной строке: Кошелёк ₽ (primary, dark) +
|
|
* Баланс лидов + Тариф. Sprint 4 Phase B/2 — split BillingView (audit O-refactor-04 хвост).
|
|
*/
|
|
defineProps<{
|
|
walletRub: number;
|
|
leadsBalance: number;
|
|
tariffName: string;
|
|
tariffPrice: number;
|
|
tariffFeatures: string[];
|
|
}>();
|
|
</script>
|
|
|
|
<template>
|
|
<v-row dense class="wallet-row mt-4">
|
|
<v-col cols="12" md="4">
|
|
<v-card variant="flat" color="secondary" class="wallet-card primary pa-4">
|
|
<div class="wallet-h">
|
|
<span class="wallet-label">Кошелёк ₽</span>
|
|
<v-chip size="x-small" color="primary" variant="elevated">LIVE</v-chip>
|
|
</div>
|
|
<div class="wallet-amount mt-2">
|
|
<span class="num">{{ new Intl.NumberFormat('ru-RU').format(walletRub) }}</span>
|
|
<span class="ru"> ₽</span>
|
|
</div>
|
|
<div class="wallet-foot mt-3">мин. пополнение <strong>100 ₽</strong> · округление вниз ₽→лиды</div>
|
|
<div class="wallet-actions mt-3">
|
|
<v-btn color="primary" variant="flat" prepend-icon="mdi-plus" size="small">Пополнить</v-btn>
|
|
<v-btn variant="outlined" prepend-icon="mdi-autorenew" size="small"> Автопополнение </v-btn>
|
|
</div>
|
|
</v-card>
|
|
</v-col>
|
|
|
|
<v-col cols="12" md="4">
|
|
<v-card variant="outlined" class="wallet-card pa-4">
|
|
<div class="wallet-h">
|
|
<span class="wallet-label">Баланс лидов (ГЦК)</span>
|
|
</div>
|
|
<div class="wallet-amount mt-2">
|
|
<span class="num">{{ leadsBalance }}</span>
|
|
<span class="ru-text"> лидов</span>
|
|
</div>
|
|
<div class="wallet-foot mt-3">средняя цена <strong>50 ₽/лид</strong> · потрачено за месяц 412</div>
|
|
</v-card>
|
|
</v-col>
|
|
|
|
<v-col cols="12" md="4">
|
|
<v-card variant="outlined" class="wallet-card pa-4 d-flex flex-column">
|
|
<span class="wallet-label">Тариф</span>
|
|
<div class="tariff-name mt-1">
|
|
{{ tariffName }}
|
|
<span class="tariff-price">· {{ tariffPrice }} ₽/мес</span>
|
|
</div>
|
|
<ul class="tariff-feats mt-3">
|
|
<li v-for="f in tariffFeatures" :key="f">
|
|
<v-icon size="14" color="success" class="mr-1">mdi-check</v-icon>{{ f }}
|
|
</li>
|
|
</ul>
|
|
<v-btn variant="outlined" size="small" class="mt-auto">Сменить тариф →</v-btn>
|
|
</v-card>
|
|
</v-col>
|
|
</v-row>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.num {
|
|
font-family: 'JetBrains Mono', ui-monospace, monospace;
|
|
font-feature-settings: 'tnum';
|
|
font-weight: 500;
|
|
}
|
|
|
|
.wallet-card {
|
|
height: 100%;
|
|
background: #fff;
|
|
}
|
|
.wallet-card.primary {
|
|
color: #fff;
|
|
background: #012019 !important;
|
|
}
|
|
.wallet-h {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
}
|
|
.wallet-label {
|
|
font-size: 12px;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.06em;
|
|
color: #7a8c87;
|
|
font-family: 'JetBrains Mono', ui-monospace, monospace;
|
|
}
|
|
.wallet-card:not(.primary) .wallet-label {
|
|
color: #66635c;
|
|
}
|
|
.wallet-amount {
|
|
font-size: 32px;
|
|
font-weight: 600;
|
|
line-height: 1.1;
|
|
}
|
|
.wallet-amount .num {
|
|
color: inherit;
|
|
letter-spacing: -0.01em;
|
|
}
|
|
.wallet-amount .ru,
|
|
.wallet-amount .ru-text {
|
|
color: #66635c;
|
|
font-weight: 500;
|
|
font-size: 18px;
|
|
}
|
|
.wallet-card.primary .wallet-amount .ru {
|
|
color: #7a8c87;
|
|
}
|
|
.wallet-foot {
|
|
color: inherit;
|
|
opacity: 0.7;
|
|
font-size: 12px;
|
|
}
|
|
.wallet-card.primary .wallet-foot {
|
|
color: #b1c2bd;
|
|
opacity: 1;
|
|
}
|
|
.wallet-actions {
|
|
display: flex;
|
|
gap: 8px;
|
|
}
|
|
|
|
.tariff-name {
|
|
font-weight: 600;
|
|
font-size: 17px;
|
|
color: #081319;
|
|
}
|
|
.tariff-price {
|
|
font-family: 'JetBrains Mono', ui-monospace, monospace;
|
|
color: #0f6e56;
|
|
font-size: 13px;
|
|
font-weight: 500;
|
|
margin-left: 4px;
|
|
}
|
|
.tariff-feats {
|
|
list-style: none;
|
|
padding: 0;
|
|
margin: 0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 6px;
|
|
flex: 1;
|
|
}
|
|
.tariff-feats li {
|
|
font-size: 13px;
|
|
color: #343c41;
|
|
display: flex;
|
|
align-items: center;
|
|
}
|
|
</style>
|