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>
52 lines
2.1 KiB
TypeScript
52 lines
2.1 KiB
TypeScript
/**
|
||
* Форматтеры для биллинга. Экспортируются для использования в нескольких
|
||
* sub-components BillingView (BalanceCard, TransactionsTable, InvoicesTable).
|
||
* Sprint 4 Phase B/2 — split BillingView.
|
||
*/
|
||
import type { BillingTransaction, InvoiceFormat, TxStatus } from './mockBilling';
|
||
|
||
/** «5000» → «5 000 ₽» (без знака). */
|
||
export function formatPlain(cost: number): string {
|
||
return new Intl.NumberFormat('ru-RU').format(cost) + ' ₽';
|
||
}
|
||
|
||
/** Знаковый формат: «+ 5 000 ₽» / «− 6 600 ₽» / «0 ₽». */
|
||
export function formatCost(cost: number): string {
|
||
const sign = cost > 0 ? '+ ' : cost < 0 ? '− ' : '';
|
||
return sign + new Intl.NumberFormat('ru-RU').format(Math.abs(cost)) + ' ₽';
|
||
}
|
||
|
||
/** CSS-класс для суммы транзакции по статусу/знаку. */
|
||
export function txAmountClass(tx: BillingTransaction): string {
|
||
if (tx.status === 'rejected') return 'tx-amount-neutral';
|
||
if (tx.amount > 0) return 'tx-amount-up';
|
||
if (tx.amount < 0) return 'tx-amount-down';
|
||
return 'tx-amount-neutral';
|
||
}
|
||
|
||
/** Vuetify-цвет чипа статуса транзакции. */
|
||
export function statusChipColor(status: TxStatus): string {
|
||
if (status === 'pending') return 'warning';
|
||
if (status === 'completed') return 'success';
|
||
return 'error';
|
||
}
|
||
|
||
/** Локализованный лейбл статуса транзакции. */
|
||
export function statusLabel(status: TxStatus): string {
|
||
if (status === 'pending') return 'В обработке';
|
||
if (status === 'completed') return 'Проведён';
|
||
return 'Отклонено';
|
||
}
|
||
|
||
/** Лейбл формата файла счёта/УПД (PDF / 1С 8.3 XML). */
|
||
export function formatLabel(format: InvoiceFormat): string {
|
||
if (format === 'pdf') return 'PDF';
|
||
return '1С 8.3 XML';
|
||
}
|
||
|
||
/** Иконка формата файла счёта/УПД. */
|
||
export function formatIcon(format: InvoiceFormat): string {
|
||
if (format === 'pdf') return 'mdi-file-pdf-box';
|
||
return 'mdi-xml';
|
||
}
|