Add user and manager order detail pages
This commit is contained in:
@@ -542,6 +542,7 @@ export type QueryManagerNotificationHistoryArgs = {
|
|||||||
|
|
||||||
|
|
||||||
export type QueryManagerOrdersArgs = {
|
export type QueryManagerOrdersArgs = {
|
||||||
|
customerId?: InputMaybe<Scalars['ID']['input']>;
|
||||||
status?: InputMaybe<OrderStatus>;
|
status?: InputMaybe<OrderStatus>;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -842,11 +843,17 @@ export type ManagerFinalizeOrderMutation = { __typename?: 'Mutation', managerFin
|
|||||||
|
|
||||||
export type ManagerOrdersQueryVariables = Exact<{
|
export type ManagerOrdersQueryVariables = Exact<{
|
||||||
status?: InputMaybe<OrderStatus>;
|
status?: InputMaybe<OrderStatus>;
|
||||||
|
customerId?: InputMaybe<Scalars['ID']['input']>;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
|
||||||
export type ManagerOrdersQuery = { __typename?: 'Query', managerOrders: Array<{ __typename?: 'Order', id: string, code: string, status: OrderStatus, kind: OrderKind, customerId: string, deliveryAddress?: string | null, deliveryTerms?: string | null, deliveryFee?: number | null, totalPrice?: number | null, createdAt: any, items: Array<{ __typename?: 'OrderItem', id: string, productName: string, quantity: number }> }> };
|
export type ManagerOrdersQuery = { __typename?: 'Query', managerOrders: Array<{ __typename?: 'Order', id: string, code: string, status: OrderStatus, kind: OrderKind, customerId: string, deliveryAddress?: string | null, deliveryTerms?: string | null, deliveryFee?: number | null, totalPrice?: number | null, createdAt: any, items: Array<{ __typename?: 'OrderItem', id: string, productName: string, quantity: number }> }> };
|
||||||
|
|
||||||
|
export type ManagerUsersDetailQueryVariables = Exact<{ [key: string]: never; }>;
|
||||||
|
|
||||||
|
|
||||||
|
export type ManagerUsersDetailQuery = { __typename?: 'Query', managerUsers: Array<{ __typename?: 'ManagerUser', id: string, email: string, fullName: string, companyName?: string | null, inn?: string | null, createdAt: any, orderCount: number, lastOrderAt?: any | null, telegramConnection?: { __typename?: 'MessengerConnection', id: string, type: MessengerType, channelId: string, displayName?: string | null, username?: string | null, avatarAvailable: boolean } | null }> };
|
||||||
|
|
||||||
export type ManagerUsersQueryVariables = Exact<{ [key: string]: never; }>;
|
export type ManagerUsersQueryVariables = Exact<{ [key: string]: never; }>;
|
||||||
|
|
||||||
|
|
||||||
@@ -1690,8 +1697,8 @@ export function useManagerFinalizeOrderMutation(options: VueApolloComposable.Use
|
|||||||
}
|
}
|
||||||
export type ManagerFinalizeOrderMutationCompositionFunctionResult = VueApolloComposable.UseMutationReturn<ManagerFinalizeOrderMutation, ManagerFinalizeOrderMutationVariables>;
|
export type ManagerFinalizeOrderMutationCompositionFunctionResult = VueApolloComposable.UseMutationReturn<ManagerFinalizeOrderMutation, ManagerFinalizeOrderMutationVariables>;
|
||||||
export const ManagerOrdersDocument = gql`
|
export const ManagerOrdersDocument = gql`
|
||||||
query ManagerOrders($status: OrderStatus) {
|
query ManagerOrders($status: OrderStatus, $customerId: ID) {
|
||||||
managerOrders(status: $status) {
|
managerOrders(status: $status, customerId: $customerId) {
|
||||||
id
|
id
|
||||||
code
|
code
|
||||||
status
|
status
|
||||||
@@ -1724,6 +1731,7 @@ export const ManagerOrdersDocument = gql`
|
|||||||
* @example
|
* @example
|
||||||
* const { result, loading, error } = useManagerOrdersQuery({
|
* const { result, loading, error } = useManagerOrdersQuery({
|
||||||
* status: // value for 'status'
|
* status: // value for 'status'
|
||||||
|
* customerId: // value for 'customerId'
|
||||||
* });
|
* });
|
||||||
*/
|
*/
|
||||||
export function useManagerOrdersQuery(variables: ManagerOrdersQueryVariables | VueCompositionApi.Ref<ManagerOrdersQueryVariables> | ReactiveFunction<ManagerOrdersQueryVariables> = {}, options: VueApolloComposable.UseQueryOptions<ManagerOrdersQuery, ManagerOrdersQueryVariables> | VueCompositionApi.Ref<VueApolloComposable.UseQueryOptions<ManagerOrdersQuery, ManagerOrdersQueryVariables>> | ReactiveFunction<VueApolloComposable.UseQueryOptions<ManagerOrdersQuery, ManagerOrdersQueryVariables>> = {}) {
|
export function useManagerOrdersQuery(variables: ManagerOrdersQueryVariables | VueCompositionApi.Ref<ManagerOrdersQueryVariables> | ReactiveFunction<ManagerOrdersQueryVariables> = {}, options: VueApolloComposable.UseQueryOptions<ManagerOrdersQuery, ManagerOrdersQueryVariables> | VueCompositionApi.Ref<VueApolloComposable.UseQueryOptions<ManagerOrdersQuery, ManagerOrdersQueryVariables>> | ReactiveFunction<VueApolloComposable.UseQueryOptions<ManagerOrdersQuery, ManagerOrdersQueryVariables>> = {}) {
|
||||||
@@ -1733,6 +1741,48 @@ export function useManagerOrdersLazyQuery(variables: ManagerOrdersQueryVariables
|
|||||||
return VueApolloComposable.useLazyQuery<ManagerOrdersQuery, ManagerOrdersQueryVariables>(ManagerOrdersDocument, variables, options);
|
return VueApolloComposable.useLazyQuery<ManagerOrdersQuery, ManagerOrdersQueryVariables>(ManagerOrdersDocument, variables, options);
|
||||||
}
|
}
|
||||||
export type ManagerOrdersQueryCompositionFunctionResult = VueApolloComposable.UseQueryReturn<ManagerOrdersQuery, ManagerOrdersQueryVariables>;
|
export type ManagerOrdersQueryCompositionFunctionResult = VueApolloComposable.UseQueryReturn<ManagerOrdersQuery, ManagerOrdersQueryVariables>;
|
||||||
|
export const ManagerUsersDetailDocument = gql`
|
||||||
|
query ManagerUsersDetail {
|
||||||
|
managerUsers {
|
||||||
|
id
|
||||||
|
email
|
||||||
|
fullName
|
||||||
|
companyName
|
||||||
|
inn
|
||||||
|
createdAt
|
||||||
|
orderCount
|
||||||
|
lastOrderAt
|
||||||
|
telegramConnection {
|
||||||
|
id
|
||||||
|
type
|
||||||
|
channelId
|
||||||
|
displayName
|
||||||
|
username
|
||||||
|
avatarAvailable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* __useManagerUsersDetailQuery__
|
||||||
|
*
|
||||||
|
* To run a query within a Vue component, call `useManagerUsersDetailQuery` and pass it any options that fit your needs.
|
||||||
|
* When your component renders, `useManagerUsersDetailQuery` returns an object from Apollo Client that contains result, loading and error properties
|
||||||
|
* you can use to render your UI.
|
||||||
|
*
|
||||||
|
* @param options that will be passed into the query, supported options are listed on: https://v4.apollo.vuejs.org/guide-composable/query.html#options;
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const { result, loading, error } = useManagerUsersDetailQuery();
|
||||||
|
*/
|
||||||
|
export function useManagerUsersDetailQuery(options: VueApolloComposable.UseQueryOptions<ManagerUsersDetailQuery, ManagerUsersDetailQueryVariables> | VueCompositionApi.Ref<VueApolloComposable.UseQueryOptions<ManagerUsersDetailQuery, ManagerUsersDetailQueryVariables>> | ReactiveFunction<VueApolloComposable.UseQueryOptions<ManagerUsersDetailQuery, ManagerUsersDetailQueryVariables>> = {}) {
|
||||||
|
return VueApolloComposable.useQuery<ManagerUsersDetailQuery, ManagerUsersDetailQueryVariables>(ManagerUsersDetailDocument, {}, options);
|
||||||
|
}
|
||||||
|
export function useManagerUsersDetailLazyQuery(options: VueApolloComposable.UseQueryOptions<ManagerUsersDetailQuery, ManagerUsersDetailQueryVariables> | VueCompositionApi.Ref<VueApolloComposable.UseQueryOptions<ManagerUsersDetailQuery, ManagerUsersDetailQueryVariables>> | ReactiveFunction<VueApolloComposable.UseQueryOptions<ManagerUsersDetailQuery, ManagerUsersDetailQueryVariables>> = {}) {
|
||||||
|
return VueApolloComposable.useLazyQuery<ManagerUsersDetailQuery, ManagerUsersDetailQueryVariables>(ManagerUsersDetailDocument, {}, options);
|
||||||
|
}
|
||||||
|
export type ManagerUsersDetailQueryCompositionFunctionResult = VueApolloComposable.UseQueryReturn<ManagerUsersDetailQuery, ManagerUsersDetailQueryVariables>;
|
||||||
export const ManagerUsersDocument = gql`
|
export const ManagerUsersDocument = gql`
|
||||||
query ManagerUsers {
|
query ManagerUsers {
|
||||||
managerUsers {
|
managerUsers {
|
||||||
|
|||||||
@@ -1,58 +1,94 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useMutation, useQuery } from '@vue/apollo-composable';
|
import { useMutation, useQuery } from '@vue/apollo-composable';
|
||||||
import {
|
import {
|
||||||
|
ManagerOrdersDocument,
|
||||||
|
ManagerUsersDetailDocument,
|
||||||
RegistrationRequestsDocument,
|
RegistrationRequestsDocument,
|
||||||
ReviewRegistrationRequestDocument,
|
ReviewRegistrationRequestDocument,
|
||||||
|
type ManagerOrdersQuery,
|
||||||
|
type ManagerUsersDetailQuery,
|
||||||
|
type RegistrationRequestsQuery,
|
||||||
} from '~/composables/graphql/generated';
|
} from '~/composables/graphql/generated';
|
||||||
|
import { messengerConnectionAvatarSrc } from '~/composables/useMessengerConnectionPresentation';
|
||||||
|
|
||||||
definePageMeta({
|
definePageMeta({
|
||||||
middleware: ['manager-only'],
|
middleware: ['manager-only'],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
type ManagerUserItem = ManagerUsersDetailQuery['managerUsers'][number];
|
||||||
|
type ManagerOrderItem = ManagerOrdersQuery['managerOrders'][number];
|
||||||
|
type RequestItem = RegistrationRequestsQuery['registrationRequests'][number];
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const requestId = computed(() => String(route.params.id || ''));
|
const entityId = computed(() => String(route.params.id || ''));
|
||||||
|
const isRequestMode = computed(() => route.query.tab === 'requests');
|
||||||
const backTarget = computed(() => (
|
const backTarget = computed(() => (
|
||||||
route.query.tab === 'requests' ? '/clients?tab=requests' : '/clients'
|
isRequestMode.value ? '/clients?tab=requests' : '/clients'
|
||||||
));
|
));
|
||||||
|
|
||||||
const clientQuery = useQuery(RegistrationRequestsDocument, {
|
const usersQuery = useQuery(ManagerUsersDetailDocument);
|
||||||
|
const requestsQuery = useQuery(RegistrationRequestsDocument, {
|
||||||
status: null,
|
status: null,
|
||||||
});
|
});
|
||||||
|
const userOrdersQuery = useQuery(ManagerOrdersDocument, () => ({
|
||||||
|
status: null,
|
||||||
|
customerId: isRequestMode.value ? null : entityId.value,
|
||||||
|
}));
|
||||||
const reviewMutation = useMutation(ReviewRegistrationRequestDocument);
|
const reviewMutation = useMutation(ReviewRegistrationRequestDocument);
|
||||||
|
|
||||||
const currentClient = computed(() =>
|
const currentUser = computed<ManagerUserItem | null>(() =>
|
||||||
(clientQuery.result.value?.registrationRequests ?? []).find((item) => item.id === requestId.value),
|
(usersQuery.result.value?.managerUsers ?? []).find((item: ManagerUserItem) => item.id === entityId.value) ?? null,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const currentRequest = computed(() =>
|
||||||
|
(requestsQuery.result.value?.registrationRequests ?? []).find((item: RequestItem) => item.id === entityId.value),
|
||||||
|
);
|
||||||
|
|
||||||
|
const currentUserOrders = computed<ManagerOrderItem[]>(() => userOrdersQuery.result.value?.managerOrders ?? []);
|
||||||
|
|
||||||
|
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('');
|
||||||
|
}
|
||||||
|
|
||||||
async function approveRequest() {
|
async function approveRequest() {
|
||||||
if (!currentClient.value) {
|
if (!currentRequest.value) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await reviewMutation.mutate({
|
await reviewMutation.mutate({
|
||||||
input: {
|
input: {
|
||||||
requestId: currentClient.value.id,
|
requestId: currentRequest.value.id,
|
||||||
decision: 'APPROVE',
|
decision: 'APPROVE',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
await clientQuery.refetch({ status: null });
|
await requestsQuery.refetch({ status: null });
|
||||||
}
|
}
|
||||||
|
|
||||||
async function rejectRequest() {
|
async function rejectRequest() {
|
||||||
if (!currentClient.value) {
|
if (!currentRequest.value) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await reviewMutation.mutate({
|
await reviewMutation.mutate({
|
||||||
input: {
|
input: {
|
||||||
requestId: currentClient.value.id,
|
requestId: currentRequest.value.id,
|
||||||
decision: 'REJECT',
|
decision: 'REJECT',
|
||||||
rejectionReason: 'Не хватает данных для регистрации.',
|
rejectionReason: 'Не хватает данных для регистрации.',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
await clientQuery.refetch({ status: null });
|
await requestsQuery.refetch({ status: null });
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -60,23 +96,24 @@ async function rejectRequest() {
|
|||||||
<section class="space-y-6">
|
<section class="space-y-6">
|
||||||
<NuxtLink :to="backTarget" class="text-sm font-semibold text-[#0d854a]">← Назад к пользователям</NuxtLink>
|
<NuxtLink :to="backTarget" class="text-sm font-semibold text-[#0d854a]">← Назад к пользователям</NuxtLink>
|
||||||
|
|
||||||
<div v-if="clientQuery.loading.value" class="manager-empty-state">
|
<template v-if="isRequestMode">
|
||||||
|
<div v-if="requestsQuery.loading.value" class="manager-empty-state">
|
||||||
Загружаем карточку клиента...
|
Загружаем карточку клиента...
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else-if="!currentClient" class="manager-empty-state">
|
<div v-else-if="!currentRequest" class="manager-empty-state">
|
||||||
Карточка клиента не найдена.
|
Карточка клиента не найдена.
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<div class="flex flex-col gap-3 md:flex-row md:items-start md:justify-between">
|
<div class="flex flex-col gap-3 md:flex-row md:items-start md:justify-between">
|
||||||
<div class="manager-hero">
|
<div class="manager-hero">
|
||||||
<p class="manager-eyebrow">Клиент</p>
|
<p class="manager-eyebrow">Заявка</p>
|
||||||
<h1 class="manager-title">{{ currentClient.companyName }}</h1>
|
<h1 class="manager-title">{{ currentRequest.companyName }}</h1>
|
||||||
<p class="manager-copy">Контакт: {{ currentClient.contactName }} · {{ currentClient.email }}</p>
|
<p class="manager-copy">Контакт: {{ currentRequest.contactName }} · {{ currentRequest.email }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="currentClient.status === 'PENDING'" class="flex flex-wrap gap-2">
|
<div v-if="currentRequest.status === 'PENDING'" class="flex flex-wrap gap-2">
|
||||||
<button class="btn btn-success border-0" @click="approveRequest">Одобрить</button>
|
<button class="btn btn-success border-0" @click="approveRequest">Одобрить</button>
|
||||||
<button class="btn btn-error border-0" @click="rejectRequest">Отклонить</button>
|
<button class="btn btn-error border-0" @click="rejectRequest">Отклонить</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -86,45 +123,79 @@ async function rejectRequest() {
|
|||||||
<div class="manager-stat-card">
|
<div class="manager-stat-card">
|
||||||
<p class="manager-stat-label">Статус</p>
|
<p class="manager-stat-label">Статус</p>
|
||||||
<p class="manager-stat-value text-lg">
|
<p class="manager-stat-value text-lg">
|
||||||
{{ currentClient.status === 'APPROVED' ? 'Активен' : currentClient.status === 'REJECTED' ? 'Отклонен' : 'На проверке' }}
|
{{ currentRequest.status === 'APPROVED' ? 'Активен' : currentRequest.status === 'REJECTED' ? 'Отклонен' : 'На проверке' }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="manager-stat-card">
|
<div class="manager-stat-card">
|
||||||
<p class="manager-stat-label">Дата заявки</p>
|
<p class="manager-stat-label">Дата заявки</p>
|
||||||
<p class="manager-stat-value text-lg">{{ new Date(currentClient.createdAt).toLocaleDateString() }}</p>
|
<p class="manager-stat-value text-lg">{{ new Date(currentRequest.createdAt).toLocaleDateString() }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="manager-stat-card">
|
<div class="manager-stat-card">
|
||||||
<p class="manager-stat-label">ИНН</p>
|
<p class="manager-stat-label">ИНН</p>
|
||||||
<p class="manager-stat-value text-lg">{{ currentClient.inn || 'Не указан' }}</p>
|
<p class="manager-stat-value text-lg">{{ currentRequest.inn || 'Не указан' }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-else>
|
||||||
|
<div v-if="usersQuery.loading.value || userOrdersQuery.loading.value" class="manager-empty-state">
|
||||||
|
Загружаем пользователя...
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else-if="!currentUser" class="manager-empty-state">
|
||||||
|
Пользователь не найден.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<template v-else>
|
||||||
|
<div class="surface-card flex flex-col gap-6 rounded-[36px] p-6 md:flex-row md:items-center">
|
||||||
|
<div class="shrink-0">
|
||||||
|
<img
|
||||||
|
v-if="messengerConnectionAvatarSrc(currentUser.telegramConnection)"
|
||||||
|
:src="messengerConnectionAvatarSrc(currentUser.telegramConnection)"
|
||||||
|
:alt="currentUser.fullName"
|
||||||
|
class="h-28 w-28 rounded-[36px] object-cover shadow-[0_12px_30px_rgba(18,56,36,0.14)]"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-else
|
||||||
|
class="flex h-28 w-28 items-center justify-center rounded-[36px] bg-[linear-gradient(135deg,#dff7e9_0%,#c2ead3_100%)] text-4xl font-black text-[#123824]"
|
||||||
|
>
|
||||||
|
{{ userInitials(currentUser.fullName) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="space-y-2">
|
||||||
|
<p class="manager-eyebrow">Пользователь</p>
|
||||||
|
<h1 class="manager-title">{{ currentUser.fullName }}</h1>
|
||||||
|
<p class="text-sm text-[#466653]">{{ currentUser.email }}</p>
|
||||||
|
<p v-if="currentUser.companyName" class="text-sm text-[#466653]">{{ currentUser.companyName }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="surface-card rounded-3xl p-5">
|
<div class="surface-card rounded-3xl p-5">
|
||||||
<h2 class="text-xl font-bold text-[#123824]">Информация</h2>
|
<h2 class="text-xl font-bold text-[#123824]">Заказы пользователя</h2>
|
||||||
<div class="mt-4 grid gap-3 md:grid-cols-2">
|
<div v-if="currentUserOrders.length === 0" class="manager-empty-state mt-4">
|
||||||
<div class="manager-mini-card">
|
У пользователя пока нет заказов.
|
||||||
<p class="text-xs font-semibold uppercase tracking-[0.12em] text-[#5c7b69]">Компания</p>
|
|
||||||
<p class="mt-2 text-sm text-[#123824]">{{ currentClient.companyName }}</p>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="manager-mini-card">
|
<div v-else class="mt-4 space-y-3">
|
||||||
<p class="text-xs font-semibold uppercase tracking-[0.12em] text-[#5c7b69]">Контакт</p>
|
<NuxtLink
|
||||||
<p class="mt-2 text-sm text-[#123824]">{{ currentClient.contactName }}</p>
|
v-for="order in currentUserOrders"
|
||||||
|
:key="order.id"
|
||||||
|
:to="`/client-orders/${order.id}`"
|
||||||
|
class="manager-mini-card block"
|
||||||
|
>
|
||||||
|
<div class="flex flex-wrap items-start justify-between gap-3">
|
||||||
|
<div class="space-y-1">
|
||||||
|
<p class="text-sm font-semibold text-[#123824]">{{ order.code }}</p>
|
||||||
|
<p class="text-sm text-[#355947]">{{ new Date(order.createdAt).toLocaleString() }}</p>
|
||||||
|
<p v-if="order.deliveryAddress" class="text-sm text-[#355947]">{{ order.deliveryAddress }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="manager-mini-card">
|
<p class="text-sm font-semibold text-[#466653]">{{ order.status }}</p>
|
||||||
<p class="text-xs font-semibold uppercase tracking-[0.12em] text-[#5c7b69]">Email</p>
|
|
||||||
<p class="mt-2 text-sm text-[#123824]">{{ currentClient.email }}</p>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="manager-mini-card">
|
</NuxtLink>
|
||||||
<p class="text-xs font-semibold uppercase tracking-[0.12em] text-[#5c7b69]">Обновлено</p>
|
|
||||||
<p class="mt-2 text-sm text-[#123824]">{{ new Date(currentClient.updatedAt).toLocaleString() }}</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</template>
|
||||||
|
|
||||||
<div v-if="currentClient.rejectionReason" class="surface-card rounded-3xl p-5">
|
|
||||||
<h2 class="text-xl font-bold text-[#123824]">Причина отказа</h2>
|
|
||||||
<p class="mt-3 text-sm text-[#a34a34]">{{ currentClient.rejectionReason }}</p>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -147,9 +147,10 @@ function userInitials(fullName: string) {
|
|||||||
Пользователи по текущему запросу не найдены.
|
Пользователи по текущему запросу не найдены.
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="grid gap-4 sm:grid-cols-2 xl:grid-cols-4 2xl:grid-cols-6">
|
<div v-else class="grid gap-4 sm:grid-cols-2 xl:grid-cols-4 2xl:grid-cols-6">
|
||||||
<article
|
<NuxtLink
|
||||||
v-for="user in filteredUsers"
|
v-for="user in filteredUsers"
|
||||||
:key="user.id"
|
:key="user.id"
|
||||||
|
:to="`/clients/${user.id}`"
|
||||||
class="surface-card flex min-h-[280px] flex-col rounded-[32px] p-6"
|
class="surface-card flex min-h-[280px] flex-col rounded-[32px] p-6"
|
||||||
>
|
>
|
||||||
<div class="flex justify-center">
|
<div class="flex justify-center">
|
||||||
@@ -172,7 +173,7 @@ function userInitials(fullName: string) {
|
|||||||
<div class="pt-8 text-center">
|
<div class="pt-8 text-center">
|
||||||
<h2 class="text-lg font-bold leading-tight text-[#123824]">{{ user.fullName }}</h2>
|
<h2 class="text-lg font-bold leading-tight text-[#123824]">{{ user.fullName }}</h2>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</NuxtLink>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -69,10 +69,11 @@ const filteredOrders = computed(() => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else class="space-y-3">
|
<div v-else class="space-y-3">
|
||||||
<article
|
<NuxtLink
|
||||||
v-for="order in filteredOrders"
|
v-for="order in filteredOrders"
|
||||||
:key="order.id"
|
:key="order.id"
|
||||||
class="surface-card rounded-3xl p-4 md:p-5"
|
:to="`/orders/${order.id}`"
|
||||||
|
class="surface-card block rounded-3xl p-4 md:p-5"
|
||||||
>
|
>
|
||||||
<div class="flex flex-wrap items-start justify-between gap-3">
|
<div class="flex flex-wrap items-start justify-between gap-3">
|
||||||
<div class="space-y-1">
|
<div class="space-y-1">
|
||||||
@@ -95,7 +96,7 @@ const filteredOrders = computed(() => {
|
|||||||
{{ item.productName }} × {{ item.quantity }}
|
{{ item.productName }} × {{ item.quantity }}
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</article>
|
</NuxtLink>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
76
app/pages/orders/[id].vue
Normal file
76
app/pages/orders/[id].vue
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { useQuery } from '@vue/apollo-composable';
|
||||||
|
import OrderStatusBadge from '~/components/orders/OrderStatusBadge.vue';
|
||||||
|
import {
|
||||||
|
MyOrdersDocument,
|
||||||
|
type MyOrdersQuery,
|
||||||
|
} from '~/composables/graphql/generated';
|
||||||
|
|
||||||
|
type OrderItem = MyOrdersQuery['myOrders'][number];
|
||||||
|
|
||||||
|
const route = useRoute();
|
||||||
|
const orderId = computed(() => String(route.params.id || ''));
|
||||||
|
const ordersQuery = useQuery(MyOrdersDocument);
|
||||||
|
|
||||||
|
const currentOrder = computed<OrderItem | null>(() =>
|
||||||
|
(ordersQuery.result.value?.myOrders ?? []).find((item: OrderItem) => item.id === orderId.value) ?? null,
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<section class="space-y-6">
|
||||||
|
<NuxtLink to="/orders" class="text-sm font-semibold text-[#0d854a]">← Назад к моим заказам</NuxtLink>
|
||||||
|
|
||||||
|
<div v-if="ordersQuery.loading.value" class="manager-empty-state">
|
||||||
|
Загружаем заказ...
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else-if="!currentOrder" class="manager-empty-state">
|
||||||
|
Заказ не найден.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<template v-else>
|
||||||
|
<div class="manager-hero">
|
||||||
|
<p class="manager-eyebrow">Заказ</p>
|
||||||
|
<h1 class="manager-title">{{ currentOrder.code }}</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="space-y-4">
|
||||||
|
<div class="surface-card rounded-3xl p-5">
|
||||||
|
<h2 class="text-xl font-bold text-[#123824]">Статус заказа</h2>
|
||||||
|
<div class="mt-4 flex flex-wrap items-center justify-between gap-3">
|
||||||
|
<div class="space-y-1 text-sm text-[#355947]">
|
||||||
|
<p>Создан: {{ new Date(currentOrder.createdAt).toLocaleString() }}</p>
|
||||||
|
</div>
|
||||||
|
<OrderStatusBadge :status="currentOrder.status" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="surface-card rounded-3xl p-5">
|
||||||
|
<h2 class="text-xl font-bold text-[#123824]">Состав заказа</h2>
|
||||||
|
<ul class="mt-4 space-y-3">
|
||||||
|
<li
|
||||||
|
v-for="item in currentOrder.items"
|
||||||
|
:key="item.id"
|
||||||
|
class="manager-mini-card text-sm text-[#123824]"
|
||||||
|
>
|
||||||
|
{{ item.productName }} × {{ item.quantity }}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="surface-card rounded-3xl p-5">
|
||||||
|
<h2 class="text-xl font-bold text-[#123824]">Доставка</h2>
|
||||||
|
<div class="mt-4 grid gap-3 md:grid-cols-2">
|
||||||
|
<div class="manager-mini-card text-sm text-[#123824]">
|
||||||
|
Адрес: {{ currentOrder.deliveryAddress || 'не выбран' }}
|
||||||
|
</div>
|
||||||
|
<div class="manager-mini-card text-sm text-[#123824]">
|
||||||
|
Условия: {{ currentOrder.deliveryTerms || 'еще не указаны' }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
query ManagerOrders($status: OrderStatus) {
|
query ManagerOrders($status: OrderStatus, $customerId: ID) {
|
||||||
managerOrders(status: $status) {
|
managerOrders(status: $status, customerId: $customerId) {
|
||||||
id
|
id
|
||||||
code
|
code
|
||||||
status
|
status
|
||||||
|
|||||||
20
graphql/operations/manager/manager-users-detail.graphql
Normal file
20
graphql/operations/manager/manager-users-detail.graphql
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
query ManagerUsersDetail {
|
||||||
|
managerUsers {
|
||||||
|
id
|
||||||
|
email
|
||||||
|
fullName
|
||||||
|
companyName
|
||||||
|
inn
|
||||||
|
createdAt
|
||||||
|
orderCount
|
||||||
|
lastOrderAt
|
||||||
|
telegramConnection {
|
||||||
|
id
|
||||||
|
type
|
||||||
|
channelId
|
||||||
|
displayName
|
||||||
|
username
|
||||||
|
avatarAvailable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -335,7 +335,7 @@ type Query {
|
|||||||
myOrders: [Order!]!
|
myOrders: [Order!]!
|
||||||
myCurrentOrders: [Order!]!
|
myCurrentOrders: [Order!]!
|
||||||
managerUsers: [ManagerUser!]!
|
managerUsers: [ManagerUser!]!
|
||||||
managerOrders(status: OrderStatus): [Order!]!
|
managerOrders(status: OrderStatus, customerId: ID): [Order!]!
|
||||||
managerBonusBalances: [ManagerBonusBalance!]!
|
managerBonusBalances: [ManagerBonusBalance!]!
|
||||||
managerWithdrawalRequests(status: WithdrawalStatus): [ManagerWithdrawalRequest!]!
|
managerWithdrawalRequests(status: WithdrawalStatus): [ManagerWithdrawalRequest!]!
|
||||||
registrationRequests(status: RegistrationStatus): [RegistrationRequest!]!
|
registrationRequests(status: RegistrationStatus): [RegistrationRequest!]!
|
||||||
|
|||||||
Reference in New Issue
Block a user