Files
web-frontend/app/pages/client-orders/index.vue
2026-04-04 08:39:01 +07:00

117 lines
4.1 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 OrderStatusBadge from '~/components/orders/OrderStatusBadge.vue';
import {
ManagerOrdersDocument,
type ManagerOrdersQuery,
} from '~/composables/graphql/generated';
definePageMeta({
middleware: ['manager-only'],
});
type ManagerOrderItem = ManagerOrdersQuery['managerOrders'][number];
const ACTIVE_STATUSES = new Set(['NEW', 'MANAGER_PROCESSING', 'WAITING_DOUBLE_CONFIRM', 'CONFIRMED', 'IN_PROGRESS']);
const CLOSED_STATUSES = new Set(['COMPLETED', 'CLIENT_REJECTED', 'MANAGER_REJECTED', 'MANAGER_BLOCKED']);
const ordersQuery = useQuery(ManagerOrdersDocument, { status: null });
const search = ref('');
const statusFilter = ref<'ALL' | 'WAITING' | 'ACTIVE' | 'CLOSED'>('ALL');
function matchesFilter(order: ManagerOrderItem) {
if (statusFilter.value === 'ALL') {
return true;
}
if (statusFilter.value === 'WAITING') {
return order.status === 'WAITING_DOUBLE_CONFIRM';
}
if (statusFilter.value === 'ACTIVE') {
return ACTIVE_STATUSES.has(order.status);
}
return CLOSED_STATUSES.has(order.status);
}
const filteredOrders = computed(() => {
const orders = ordersQuery.result.value?.managerOrders ?? [];
const query = search.value.trim().toLowerCase();
return orders.filter((order) => {
const text = [
order.code,
order.customerId,
order.deliveryAddress || '',
...order.items.map((item) => item.productName),
]
.join(' ')
.toLowerCase();
const matchesSearch = !query || text.includes(query);
return matchesSearch && matchesFilter(order);
});
});
</script>
<template>
<section class="space-y-6">
<UiSectionSearchHero
v-model="search"
title="Заказы клиентов"
search-placeholder="Номер заказа, клиент, адрес или товар"
>
<div class="surface-card rounded-3xl p-4 md:p-5">
<div class="grid gap-3 md:grid-cols-[1fr_auto]">
<label class="form-control md:min-w-60">
<span class="label-text">Фильтр</span>
<select v-model="statusFilter" class="select manager-field w-full">
<option value="ALL">Все заказы</option>
<option value="WAITING">Ожидают подтверждения</option>
<option value="ACTIVE">Активные</option>
<option value="CLOSED">Закрытые</option>
</select>
</label>
</div>
</div>
</UiSectionSearchHero>
<div v-if="ordersQuery.loading.value" class="manager-empty-state">
Загружаем заказы...
</div>
<div v-else-if="filteredOrders.length === 0" class="manager-empty-state">
Заказы по текущим условиям не найдены.
</div>
<div v-else class="space-y-4">
<NuxtLink
v-for="order in filteredOrders"
:key="order.id"
:to="`/client-orders/${order.id}`"
class="surface-card block rounded-3xl p-5"
>
<div class="flex flex-wrap items-start justify-between gap-3">
<div class="space-y-1">
<h2 class="text-lg font-bold text-[#123824]">{{ order.code }}</h2>
<div class="flex flex-wrap gap-3 text-sm text-[#5c7b69]">
<span>Клиент: {{ order.customerId }}</span>
<span>{{ new Date(order.createdAt).toLocaleString() }}</span>
</div>
</div>
<OrderStatusBadge :status="order.status" />
</div>
<div class="mt-4 grid gap-3 md:grid-cols-3">
<div class="manager-mini-card text-sm text-[#123824]">
Позиций: <span class="font-semibold">{{ order.items.length }}</span>
</div>
<div class="manager-mini-card text-sm text-[#123824]">
Итого: <span class="font-semibold">{{ order.totalPrice ?? 'без оффера' }}</span>
</div>
<div class="manager-mini-card text-sm text-[#123824]">
Адрес: <span class="font-semibold">{{ order.deliveryAddress || 'не выбран' }}</span>
</div>
</div>
</NuxtLink>
</div>
</section>
</template>