Refine manager bonus and order detail views
This commit is contained in:
@@ -35,6 +35,26 @@ function formatAmount(value: number) {
|
||||
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>
|
||||
@@ -63,60 +83,81 @@ function formatDateTime(value: string) {
|
||||
</template>
|
||||
</UiBackHeader>
|
||||
|
||||
<div v-if="pendingWithdrawals.length" class="surface-card rounded-[32px] p-6">
|
||||
<p class="text-lg font-bold text-[#123824]">Выводы</p>
|
||||
|
||||
<div class="mt-4 space-y-3">
|
||||
<article
|
||||
v-for="withdrawal in pendingWithdrawals"
|
||||
:key="withdrawal.id"
|
||||
class="rounded-[24px] bg-[#f6fbf8] px-4 py-4"
|
||||
>
|
||||
<div class="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
|
||||
<div class="space-y-1">
|
||||
<p class="text-sm font-semibold text-[#123824]">{{ formatAmount(withdrawal.amount) }}</p>
|
||||
<p class="text-sm text-[#355947]">Создано {{ formatDateTime(withdrawal.createdAt) }}</p>
|
||||
<p v-if="withdrawal.reviewComment" class="text-sm text-[#355947]">{{ withdrawal.reviewComment }}</p>
|
||||
</div>
|
||||
|
||||
<NuxtLink
|
||||
:to="`/admin/bonuses/requests/${withdrawal.id}`"
|
||||
class="text-sm font-semibold text-[#0d854a]"
|
||||
>
|
||||
Проверить выплату
|
||||
</NuxtLink>
|
||||
</div>
|
||||
</article>
|
||||
<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="surface-card rounded-[32px] p-6">
|
||||
<p class="text-lg font-bold text-[#123824]">Транзакции</p>
|
||||
<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="mt-4 space-y-3">
|
||||
<div v-else class="space-y-3">
|
||||
<article
|
||||
v-for="transaction in transactions"
|
||||
:key="transaction.id"
|
||||
class="rounded-[24px] bg-[#f6fbf8] px-4 py-4"
|
||||
class="surface-card rounded-[28px] px-5 py-4"
|
||||
>
|
||||
<div class="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
|
||||
<div class="space-y-1">
|
||||
<p class="text-base font-semibold text-[#123824]">+{{ formatAmount(transaction.amount) }}</p>
|
||||
<p class="text-sm text-[#355947]">{{ transaction.reason }}</p>
|
||||
<p class="text-xs text-[#5c7b69]">{{ formatDateTime(transaction.createdAt) }}</p>
|
||||
<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>
|
||||
|
||||
<NuxtLink
|
||||
v-if="transaction.orderId"
|
||||
:to="`/admin/orders/${transaction.orderId}`"
|
||||
class="text-sm font-semibold text-[#0d854a]"
|
||||
>
|
||||
Открыть заказ
|
||||
</NuxtLink>
|
||||
<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>
|
||||
|
||||
@@ -292,6 +292,10 @@ function productVisualLabel(product: ProductCard) {
|
||||
.map((part) => part.charAt(0).toUpperCase())
|
||||
.join('');
|
||||
}
|
||||
|
||||
function compactProductTitle(product: ProductCard) {
|
||||
return `Подарочная карта ${product.store}`;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -356,14 +360,15 @@ function productVisualLabel(product: ProductCard) {
|
||||
{{ productVisualLabel(product) }}
|
||||
</div>
|
||||
<div class="min-w-0 flex-1">
|
||||
<p class="text-sm font-semibold text-[#6a8a76]">{{ product.store }}</p>
|
||||
<h2 class="mt-1 text-lg font-bold leading-tight text-[#123824]">{{ product.title }}</h2>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-5 flex items-center justify-between gap-3">
|
||||
<p class="text-sm text-[#557562]">Номинал</p>
|
||||
<div class="rounded-full bg-[#fff8dc] px-4 py-2 text-sm font-bold text-[#123824]">
|
||||
{{ formatAmount(product.amount) }} ₽
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<span class="rounded-full bg-[#eef5f0] px-3 py-1 text-xs font-bold uppercase tracking-[0.12em] text-[#4e7060]">
|
||||
{{ product.store }}
|
||||
</span>
|
||||
<span class="rounded-full bg-[#fff8dc] px-3 py-1 text-xs font-bold text-[#7a5b00]">
|
||||
{{ formatAmount(product.amount) }} ₽
|
||||
</span>
|
||||
</div>
|
||||
<h2 class="mt-3 text-lg font-bold leading-tight text-[#123824]">{{ compactProductTitle(product) }}</h2>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
@@ -21,14 +21,22 @@ const withdrawalsQuery = useQuery(ManagerWithdrawalRequestsDocument, {
|
||||
});
|
||||
const reviewMutation = useMutation(ReviewRewardWithdrawalDocument);
|
||||
|
||||
const decision = ref<'APPROVE' | 'REJECT'>('APPROVE');
|
||||
const reviewComment = ref('');
|
||||
const reviewResult = ref('');
|
||||
const isProcessed = ref(true);
|
||||
const savePending = computed(() => reviewMutation.loading.value);
|
||||
|
||||
const currentWithdrawal = computed(() =>
|
||||
(withdrawalsQuery.result.value?.managerWithdrawalRequests ?? []).find((item: WithdrawalItem) => item.id === withdrawalId.value),
|
||||
);
|
||||
|
||||
watch(currentWithdrawal, (withdrawal) => {
|
||||
if (!withdrawal) {
|
||||
return;
|
||||
}
|
||||
|
||||
isProcessed.value = withdrawal.status !== 'REJECTED';
|
||||
}, { immediate: true });
|
||||
|
||||
async function reviewWithdrawal() {
|
||||
if (!currentWithdrawal.value) {
|
||||
return;
|
||||
@@ -37,8 +45,7 @@ async function reviewWithdrawal() {
|
||||
const response = await reviewMutation.mutate({
|
||||
input: {
|
||||
withdrawalId: currentWithdrawal.value.id,
|
||||
decision: decision.value,
|
||||
reviewComment: reviewComment.value || undefined,
|
||||
decision: isProcessed.value ? 'APPROVE' : 'REJECT',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -48,7 +55,7 @@ async function reviewWithdrawal() {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="space-y-6 max-w-3xl">
|
||||
<section class="space-y-6">
|
||||
<div v-if="withdrawalsQuery.loading.value" class="manager-empty-state">
|
||||
Загружаем заявку на вывод...
|
||||
</div>
|
||||
@@ -65,22 +72,35 @@ async function reviewWithdrawal() {
|
||||
:subtitle="`${currentWithdrawal.requesterFullName} · ${currentWithdrawal.requesterEmail} · Сумма: ${currentWithdrawal.amount}`"
|
||||
/>
|
||||
|
||||
<div class="surface-card rounded-3xl p-5 space-y-3">
|
||||
<label class="form-control">
|
||||
<span class="label-text">Решение</span>
|
||||
<select v-model="decision" class="select manager-field w-full">
|
||||
<option value="APPROVE">Одобрить</option>
|
||||
<option value="REJECT">Отклонить</option>
|
||||
</select>
|
||||
</label>
|
||||
<div class="surface-card rounded-3xl p-5 md:p-6">
|
||||
<div class="space-y-5">
|
||||
<label class="flex items-start gap-4 rounded-[24px] bg-[#f5faf7] px-4 py-4">
|
||||
<input
|
||||
v-model="isProcessed"
|
||||
type="checkbox"
|
||||
class="checkbox mt-1 border-[#b9d7c5] bg-white [--chkbg:#123824] [--chkfg:#ffffff]"
|
||||
>
|
||||
<span class="space-y-1">
|
||||
<span class="block text-base font-bold text-[#123824]">Проведено</span>
|
||||
<span class="block text-sm leading-6 text-[#5c7b69]">
|
||||
Отметьте выплату как проведённую. Если галочка снята, заявка будет отклонена.
|
||||
</span>
|
||||
</span>
|
||||
</label>
|
||||
|
||||
<label class="form-control">
|
||||
<span class="label-text">Комментарий</span>
|
||||
<textarea v-model="reviewComment" class="textarea manager-field min-h-28 w-full" placeholder="Комментарий для заявки" />
|
||||
</label>
|
||||
|
||||
<div>
|
||||
<button class="btn btn-primary border-0" @click="reviewWithdrawal">Сохранить решение</button>
|
||||
<button
|
||||
class="btn h-12 w-full rounded-full border-0 bg-[#123824] text-white shadow-[0_16px_32px_rgba(18,56,36,0.18)] hover:bg-[#0f2f20] disabled:border-0 disabled:bg-[#cfd8d2] disabled:text-[#6f8579]"
|
||||
:disabled="savePending"
|
||||
@click="reviewWithdrawal"
|
||||
>
|
||||
{{
|
||||
savePending
|
||||
? 'Сохраняем...'
|
||||
: isProcessed
|
||||
? 'Провести выплату'
|
||||
: 'Отклонить заявку'
|
||||
}}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user