Files
web-frontend/app/pages/clients/index.vue
2026-04-06 11:27:51 +07:00

162 lines
4.7 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 {
ManagerUsersDocument,
RegistrationRequestsDocument,
type RegistrationRequestsQuery,
} from '~/composables/graphql/generated';
import { messengerConnectionAvatarSrc } from '~/composables/useMessengerConnectionPresentation';
definePageMeta({
middleware: ['manager-only'],
});
type RequestItem = RegistrationRequestsQuery['registrationRequests'][number];
const route = useRoute();
const search = ref('');
const usersQuery = useQuery(ManagerUsersDocument);
const requestsQuery = useQuery(RegistrationRequestsDocument, {
status: null,
});
const activeTab = computed<'users' | 'requests'>(() => (
route.query.tab === 'requests' ? 'requests' : 'users'
));
const filteredUsers = computed(() => {
const items = usersQuery.result.value?.managerUsers ?? [];
const query = search.value.trim().toLowerCase();
return items.filter((item) => {
if (!query) {
return true;
}
return item.fullName.toLowerCase().includes(query);
});
});
function requestStatusLabel(status: RequestItem['status']) {
if (status === 'APPROVED') {
return 'Одобрена';
}
if (status === 'REJECTED') {
return 'Отклонена';
}
return 'На проверке';
}
function requestStatusClass(status: RequestItem['status']) {
if (status === 'APPROVED') {
return 'badge badge-success border-0';
}
if (status === 'REJECTED') {
return 'badge badge-error border-0';
}
return 'badge badge-warning border-0';
}
const filteredRequests = computed(() => {
const items = requestsQuery.result.value?.registrationRequests ?? [];
const query = search.value.trim().toLowerCase();
return items.filter((item) => {
if (!query) {
return true;
}
return [
item.companyName,
item.contactName,
item.email,
item.inn || '',
]
.join(' ')
.toLowerCase()
.includes(query);
});
});
function userInitials(fullName: string) {
const parts = fullName
.trim()
.split(/\s+/)
.filter(Boolean)
.slice(0, 2);
if (!parts.length) {
return 'FR';
}
return parts.map((part) => part[0]?.toUpperCase() ?? '').join('');
}
</script>
<template>
<section class="space-y-6">
<UiSectionSearchHero
v-model="search"
title="Пользователи"
:search-placeholder="activeTab === 'users' ? 'Имя пользователя' : 'Компания, контакт, email или ИНН'"
>
<template #controls>
<NuxtLink to="/clients/invite" class="btn btn-primary border-0">
Пригласить
</NuxtLink>
</template>
</UiSectionSearchHero>
<template v-if="activeTab === 'users'">
<div v-if="usersQuery.loading.value" class="manager-empty-state">
Загружаем пользователей...
</div>
<div v-else-if="filteredUsers.length === 0" class="manager-empty-state">
Пользователи по текущему запросу не найдены.
</div>
<div v-else class="grid gap-4 sm:grid-cols-2 xl:grid-cols-4 2xl:grid-cols-6">
<UsersGridCard
v-for="user in filteredUsers"
:key="user.id"
:to="`/clients/${user.id}`"
:full-name="user.fullName"
:avatar-src="messengerConnectionAvatarSrc(user.telegramConnection)"
:initials="userInitials(user.fullName)"
/>
</div>
</template>
<template v-else>
<div v-if="requestsQuery.loading.value" class="manager-empty-state">
Загружаем заявки...
</div>
<div v-else-if="filteredRequests.length === 0" class="manager-empty-state">
Заявки по текущему запросу не найдены.
</div>
<div v-else class="grid gap-4 lg:grid-cols-2 xl:grid-cols-3">
<NuxtLink
v-for="request in filteredRequests"
:key="request.id"
:to="`/clients/${request.id}?tab=requests`"
class="surface-card surface-card-interactive rounded-3xl p-5"
>
<div class="flex items-start justify-between gap-3">
<div class="space-y-1">
<h2 class="text-lg font-bold text-[#123824]">{{ request.companyName }}</h2>
<p class="text-sm text-[#466653]">{{ request.contactName }}</p>
</div>
<span :class="requestStatusClass(request.status)">{{ requestStatusLabel(request.status) }}</span>
</div>
<div class="mt-4 space-y-2 text-sm text-[#355947]">
<p>{{ request.email }}</p>
<p v-if="request.inn">ИНН: {{ request.inn }}</p>
<p>{{ new Date(request.createdAt).toLocaleDateString() }}</p>
</div>
</NuxtLink>
</div>
</template>
</section>
</template>