Files
web-frontend/app/pages/bonus-system/[userId].vue
2026-04-07 10:10:03 +07:00

169 lines
6.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">
import { useQuery } from '@vue/apollo-composable';
import {
ManagerBonusAccountDocument,
type ManagerBonusAccountQuery,
} from '~/composables/graphql/generated';
definePageMeta({
middleware: ['manager-only'],
path: '/admin/bonuses/balances/:userId',
alias: ['/bonus-system/:userId'],
});
type TransactionItem = ManagerBonusAccountQuery['managerBonusAccount']['transactions'][number];
type PendingWithdrawalItem = ManagerBonusAccountQuery['managerBonusAccount']['pendingWithdrawals'][number];
const route = useRoute();
const userId = computed(() => String(route.params.userId || ''));
const bonusAccountQuery = useQuery(ManagerBonusAccountDocument, () => ({
userId: userId.value,
}));
const bonusAccount = computed(() => bonusAccountQuery.result.value?.managerBonusAccount ?? null);
const transactions = computed<TransactionItem[]>(() => bonusAccount.value?.transactions ?? []);
const pendingWithdrawals = computed<PendingWithdrawalItem[]>(() => bonusAccount.value?.pendingWithdrawals ?? []);
function formatAmount(value: number) {
return new Intl.NumberFormat('ru-RU', {
minimumFractionDigits: 0,
maximumFractionDigits: 2,
}).format(value);
}
function formatDateTime(value: string) {
return new Date(value).toLocaleString('ru-RU');
}
function withdrawalStatusLabel(status: string) {
if (status === 'APPROVED') {
return 'Проведена';
}
if (status === 'REJECTED') {
return 'Отклонена';
}
return 'На проверке';
}
function withdrawalStatusClass(status: string) {
if (status === 'APPROVED') {
return 'bg-[#def7e8] text-[#0d854a]';
}
if (status === 'REJECTED') {
return 'bg-[#fde8ea] text-[#b73742]';
}
return 'bg-[#fff3d8] text-[#9a6100]';
}
</script>
<template>
<section class="space-y-6">
<div v-if="bonusAccountQuery.loading.value" class="manager-empty-state">
Загружаем бонусный счёт...
</div>
<div v-else-if="!bonusAccount" class="manager-empty-state">
Бонусный счёт не найден.
</div>
<template v-else>
<div class="space-y-4">
<UiBackHeader
to="/admin/bonuses/balances"
back-label="Назад к бонусам"
:title="`Бонусный счёт ${bonusAccount.fullName}`"
:subtitle="bonusAccount.companyName || bonusAccount.email || undefined"
>
<template #actions>
<div class="text-left md:text-right">
<p class="text-[11px] font-semibold uppercase tracking-[0.18em] text-[#6a8a76]">Доступный бонус</p>
<p class="mt-2 text-3xl font-black leading-none text-[#123824]">{{ formatAmount(bonusAccount.balance) }}</p>
</div>
</template>
</UiBackHeader>
<div v-if="pendingWithdrawals.length" class="space-y-3">
<div class="space-y-1">
<p class="text-lg font-bold text-[#123824]">Заявки на выплату</p>
<p class="text-sm text-[#5c7b69]">Все активные выплаты по этому бонусному счёту.</p>
</div>
<NuxtLink
v-for="withdrawal in pendingWithdrawals"
:key="withdrawal.id"
:to="`/admin/bonuses/requests/${withdrawal.id}`"
class="surface-card surface-card-interactive block rounded-[28px] px-5 py-4"
>
<div class="grid gap-4 md:grid-cols-[180px_minmax(0,1fr)_170px_140px] md:items-center md:gap-6">
<div class="space-y-1">
<p class="text-xs font-bold uppercase tracking-[0.16em] text-[#6a8a76]">Заявка</p>
<p class="text-base font-bold text-[#123824]">WD-{{ withdrawal.id.slice(-6).toUpperCase() }}</p>
</div>
<div class="min-w-0 space-y-1">
<p class="text-sm font-semibold text-[#123824]">Создано {{ formatDateTime(withdrawal.createdAt) }}</p>
<p v-if="withdrawal.reviewComment" class="truncate text-sm text-[#5c7b69]">{{ withdrawal.reviewComment }}</p>
</div>
<div>
<span class="rounded-full px-3 py-1 text-sm font-semibold" :class="withdrawalStatusClass(withdrawal.status)">
{{ withdrawalStatusLabel(withdrawal.status) }}
</span>
</div>
<div class="text-left md:text-right">
<p class="text-base font-bold text-[#123824]">{{ formatAmount(withdrawal.amount) }} </p>
</div>
</div>
</NuxtLink>
</div>
<div class="space-y-3">
<div class="space-y-1">
<p class="text-lg font-bold text-[#123824]">Транзакции</p>
<p class="text-sm text-[#5c7b69]">История начислений и операций по бонусному счёту.</p>
</div>
<div v-if="transactions.length === 0" class="manager-empty-state mt-4">
Начислений пока нет.
</div>
<div v-else class="space-y-3">
<article
v-for="transaction in transactions"
:key="transaction.id"
class="surface-card rounded-[28px] px-5 py-4"
>
<div class="grid gap-4 md:grid-cols-[140px_minmax(0,1fr)_180px_140px] md:items-center md:gap-6">
<div>
<p class="text-base font-bold text-[#123824]">+{{ formatAmount(transaction.amount) }} </p>
</div>
<div class="min-w-0 space-y-1">
<p class="text-sm font-semibold text-[#123824]">Начисление</p>
<p class="text-sm text-[#355947]">{{ transaction.reason }}</p>
</div>
<div class="text-sm text-[#5c7b69]">
{{ formatDateTime(transaction.createdAt) }}
</div>
<div class="text-left md:text-right">
<NuxtLink
v-if="transaction.orderId"
:to="`/admin/orders/${transaction.orderId}`"
class="text-sm font-semibold text-[#0d854a]"
>
Открыть заказ
</NuxtLink>
</div>
</div>
</article>
</div>
</div>
</div>
</template>
</section>
</template>