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