Files
web-frontend/app/pages/bonus-system/referrals/new.vue
2026-04-04 14:59:02 +07:00

181 lines
6.5 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 { useMutation, useQuery } from '@vue/apollo-composable';
import {
CreateReferralDocument,
ManagerReferralLinksDocument,
ManagerUsersDocument,
type ManagerReferralLinksQuery,
type ManagerUsersQuery,
} from '~/composables/graphql/generated';
definePageMeta({
middleware: ['manager-only'],
});
type ManagerUserItem = ManagerUsersQuery['managerUsers'][number];
type ManagerReferralLinkItem = ManagerReferralLinksQuery['managerReferralLinks'][number];
const referrerUserId = ref('');
const refereeUserId = ref('');
const bonusPercent = ref(5);
const createdReferralId = ref('');
const errorMessage = ref('');
const usersQuery = useQuery(ManagerUsersDocument);
const linksQuery = useQuery(ManagerReferralLinksDocument);
const createReferralMutation = useMutation(CreateReferralDocument);
const clientOptions = computed<ManagerUserItem[]>(() => (
(usersQuery.result.value?.managerUsers ?? [])
.filter((user) => user.role === 'CLIENT')
.slice()
.sort((left, right) => left.fullName.localeCompare(right.fullName, 'ru'))
));
const referralLinks = computed<ManagerReferralLinkItem[]>(() => (
linksQuery.result.value?.managerReferralLinks ?? []
));
function userOptionLabel(user: ManagerUserItem) {
return [user.fullName, user.companyName || user.email]
.filter(Boolean)
.join(' • ');
}
async function createReferral() {
createdReferralId.value = '';
errorMessage.value = '';
if (!referrerUserId.value || !refereeUserId.value) {
errorMessage.value = 'Выберите обоих клиентов для связки.';
return;
}
if (referrerUserId.value === refereeUserId.value) {
errorMessage.value = 'Нельзя связать клиента с самим собой.';
return;
}
const normalizedBonusPercent = Number(bonusPercent.value);
if (!Number.isFinite(normalizedBonusPercent) || normalizedBonusPercent <= 0 || normalizedBonusPercent > 100) {
errorMessage.value = 'Укажите процент бонуса от 0.01 до 100.';
return;
}
try {
const response = await createReferralMutation.mutate({
input: {
referrerUserId: referrerUserId.value,
refereeUserId: refereeUserId.value,
bonusPercent: normalizedBonusPercent,
},
});
createdReferralId.value = response?.data?.createReferral.id ?? '';
refereeUserId.value = '';
await linksQuery.refetch();
} catch (error: any) {
errorMessage.value = error?.message || 'Не удалось создать бонусную связку.';
}
}
</script>
<template>
<section class="space-y-6 max-w-3xl">
<NuxtLink to="/bonus-system" class="text-sm font-semibold text-[#0d854a]"> Назад к бонусам</NuxtLink>
<div class="manager-hero">
<p class="manager-eyebrow">Бонусы</p>
<h1 class="manager-title">Создать бонусную связку клиентов</h1>
<p class="max-w-2xl text-sm text-[#466653]">
Первый клиент получает процент бонуса, когда заказ второго клиента переходит в статус доставленного.
</p>
</div>
<div class="surface-card rounded-3xl p-5 space-y-4">
<label class="form-control">
<span class="label-text">Клиент, который получает бонус</span>
<select v-model="referrerUserId" class="select manager-field w-full">
<option value="">Выберите клиента</option>
<option v-for="user in clientOptions" :key="user.id" :value="user.id">
{{ userOptionLabel(user) }}
</option>
</select>
</label>
<label class="form-control">
<span class="label-text">Клиент, с чьих заказов начисляется бонус</span>
<select v-model="refereeUserId" class="select manager-field w-full">
<option value="">Выберите клиента</option>
<option v-for="user in clientOptions" :key="user.id" :value="user.id">
{{ userOptionLabel(user) }}
</option>
</select>
</label>
<label class="form-control">
<span class="label-text">Процент бонусной программы</span>
<input
v-model="bonusPercent"
type="number"
min="0.01"
max="100"
step="0.01"
class="input manager-field w-full"
placeholder="5"
>
</label>
<div class="mt-4">
<button
class="btn btn-primary border-0"
:disabled="createReferralMutation.loading.value || usersQuery.loading.value"
@click="createReferral"
>
{{ createReferralMutation.loading.value ? 'Сохраняем...' : 'Создать связь' }}
</button>
</div>
</div>
<div v-if="errorMessage" class="surface-card rounded-3xl border border-[#d27d7d] bg-[#fff4f4] p-5 text-sm text-[#8b2a2a]">
{{ errorMessage }}
</div>
<div v-if="createdReferralId" class="surface-card rounded-3xl p-5 text-sm text-[#123824]">
Создана связь: <span class="font-semibold">{{ createdReferralId }}</span>
</div>
<div class="space-y-4">
<div class="flex items-center justify-between gap-3">
<h2 class="text-lg font-bold text-[#123824]">Текущие бонусные связки</h2>
<span class="text-sm text-[#466653]">{{ referralLinks.length }}</span>
</div>
<div v-if="linksQuery.loading.value" class="manager-empty-state">
Загружаем связки...
</div>
<div v-else-if="referralLinks.length === 0" class="manager-empty-state">
Бонусных связок пока нет.
</div>
<div v-else class="space-y-3">
<article
v-for="link in referralLinks"
:key="link.id"
class="surface-card rounded-3xl p-5"
>
<div class="space-y-2">
<p class="text-sm font-semibold text-[#123824]">
{{ link.referrerName }} получает {{ link.bonusPercent }}% с заказов {{ link.refereeName }}
</p>
<p class="text-sm text-[#466653]">
{{ link.referrerCompanyName || link.referrerEmail }} {{ link.refereeCompanyName || link.refereeEmail }}
</p>
<p class="text-xs text-[#5c7b69]">
Создано {{ new Date(link.createdAt).toLocaleString() }}
</p>
</div>
</article>
</div>
</div>
</section>
</template>