2026-05-10 04:46:14 +03:00
|
|
|
<script setup lang="ts">
|
|
|
|
|
/**
|
|
|
|
|
* TransactionsTable — VDataTable истории транзакций с табами фильтрации
|
|
|
|
|
* (Все / Пополнения / Списания / Возвраты). Sprint 4 Phase B/2 — split BillingView.
|
|
|
|
|
*/
|
|
|
|
|
import { computed, ref } from 'vue';
|
2026-05-12 20:24:33 +03:00
|
|
|
import { BILLING_TABS, MOCK_TRANSACTIONS, type BillingTransaction } from '../../composables/mockBilling';
|
|
|
|
|
import { formatCost, statusChipColor, statusLabel, txAmountClass } from '../../composables/billingFormatters';
|
2026-05-10 04:46:14 +03:00
|
|
|
|
|
|
|
|
const activeTab = ref<(typeof BILLING_TABS)[number]['id']>('all');
|
|
|
|
|
|
|
|
|
|
const filteredTransactions = computed<BillingTransaction[]>(() => {
|
|
|
|
|
const tab = BILLING_TABS.find((t) => t.id === activeTab.value);
|
|
|
|
|
const types = tab?.types;
|
|
|
|
|
if (!types) return MOCK_TRANSACTIONS;
|
|
|
|
|
return MOCK_TRANSACTIONS.filter((tx) => types.includes(tx.type));
|
|
|
|
|
});
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<template>
|
|
|
|
|
<v-card variant="outlined" class="mt-4 panel">
|
|
|
|
|
<div class="panel-h pa-4">
|
|
|
|
|
<h2 class="text-h6 panel-title ma-0">История транзакций</h2>
|
|
|
|
|
<v-btn-toggle v-model="activeTab" mandatory color="primary" density="comfortable" variant="text">
|
|
|
|
|
<v-btn v-for="tab in BILLING_TABS" :key="tab.id" :value="tab.id" size="small">
|
|
|
|
|
{{ tab.label }}
|
|
|
|
|
</v-btn>
|
|
|
|
|
</v-btn-toggle>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<v-data-table
|
|
|
|
|
:items="filteredTransactions"
|
|
|
|
|
:headers="[
|
|
|
|
|
{ title: 'Дата', key: 'when', sortable: false },
|
|
|
|
|
{ title: 'Операция', key: 'description', sortable: false },
|
|
|
|
|
{ title: 'ID', key: 'code', sortable: false },
|
|
|
|
|
{ title: 'Статус', key: 'status', sortable: false },
|
|
|
|
|
{ title: 'Сумма', key: 'amount', align: 'end', sortable: false },
|
|
|
|
|
]"
|
|
|
|
|
items-per-page="-1"
|
|
|
|
|
hide-default-footer
|
|
|
|
|
density="comfortable"
|
|
|
|
|
>
|
|
|
|
|
<template #[`item.when`]="{ item }">
|
|
|
|
|
<span class="tx-when num">{{ item.when }}</span>
|
|
|
|
|
</template>
|
|
|
|
|
<template #[`item.code`]="{ item }">
|
|
|
|
|
<span class="tx-id">#{{ item.code }}</span>
|
|
|
|
|
</template>
|
|
|
|
|
<template #[`item.status`]="{ item }">
|
|
|
|
|
<v-chip size="small" variant="tonal" :color="statusChipColor(item.status)">
|
|
|
|
|
{{ statusLabel(item.status) }}
|
|
|
|
|
</v-chip>
|
|
|
|
|
</template>
|
|
|
|
|
<template #[`item.amount`]="{ item }">
|
|
|
|
|
<span class="num" :class="txAmountClass(item)">
|
|
|
|
|
{{ item.status === 'rejected' ? '— 0 ₽' : formatCost(item.amount) }}
|
|
|
|
|
</span>
|
|
|
|
|
</template>
|
|
|
|
|
</v-data-table>
|
|
|
|
|
</v-card>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
.num {
|
|
|
|
|
font-family: 'JetBrains Mono', ui-monospace, monospace;
|
|
|
|
|
font-feature-settings: 'tnum';
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.panel {
|
|
|
|
|
background: #fff;
|
|
|
|
|
}
|
|
|
|
|
.panel-h {
|
|
|
|
|
display: flex;
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
align-items: center;
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
gap: 12px;
|
|
|
|
|
}
|
|
|
|
|
.panel-title {
|
|
|
|
|
font-variation-settings: 'opsz' 18;
|
|
|
|
|
letter-spacing: -0.01em;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.tx-when {
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
color: #66635c;
|
|
|
|
|
}
|
|
|
|
|
.tx-id {
|
|
|
|
|
font-family: 'JetBrains Mono', ui-monospace, monospace;
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
color: #66635c;
|
|
|
|
|
}
|
|
|
|
|
.tx-amount-up {
|
2026-05-12 22:09:48 +03:00
|
|
|
color: #1b6e3b;
|
2026-05-10 04:46:14 +03:00
|
|
|
}
|
|
|
|
|
.tx-amount-down {
|
|
|
|
|
color: #b83a3a;
|
|
|
|
|
}
|
|
|
|
|
.tx-amount-neutral {
|
|
|
|
|
color: #66635c;
|
|
|
|
|
}
|
|
|
|
|
</style>
|