From 4fe3f72579243cc92283160cac277a0d6fbbee17 Mon Sep 17 00:00:00 2001 From: Ruslan Bakiev Date: Sat, 4 Apr 2026 11:20:51 +0700 Subject: [PATCH] Add manager order kanban view --- app/pages/client-orders/index.vue | 197 ++++++++++++++++++++++++++---- 1 file changed, 172 insertions(+), 25 deletions(-) diff --git a/app/pages/client-orders/index.vue b/app/pages/client-orders/index.vue index e116e9f..06b7a16 100644 --- a/app/pages/client-orders/index.vue +++ b/app/pages/client-orders/index.vue @@ -14,22 +14,24 @@ definePageMeta({ }); type ManagerOrderItem = ManagerOrdersQuery['managerOrders'][number]; +type StatusFilter = 'ALL' | 'NEW' | 'PRICED' | 'IN_PROGRESS' | 'CLOSED'; const route = useRoute(); const router = useRouter(); -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'); +const statusFilter = ref('ALL'); -const viewMode = computed<'cards' | 'calendar'>(() => ( - route.query.view === 'calendar' ? 'calendar' : 'cards' +const viewMode = computed<'cards' | 'calendar' | 'kanban'>(() => ( + route.query.view === 'calendar' + ? 'calendar' + : route.query.view === 'kanban' + ? 'kanban' + : 'cards' )); -function setViewMode(view: 'cards' | 'calendar') { +function setViewMode(view: 'cards' | 'calendar' | 'kanban') { void router.replace({ query: { ...route.query, @@ -38,20 +40,27 @@ function setViewMode(view: 'cards' | 'calendar') { }); } +function getOrderGroup(order: ManagerOrderItem) { + if (order.status === 'NEW' || order.status === 'MANAGER_PROCESSING') { + return 'NEW'; + } + if (order.status === 'WAITING_DOUBLE_CONFIRM' || order.status === 'CONFIRMED') { + return 'PRICED'; + } + if (order.status === 'IN_PROGRESS') { + return 'IN_PROGRESS'; + } + return 'CLOSED'; +} + 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); + return getOrderGroup(order) === statusFilter.value; } -const filteredOrders = computed(() => { +const searchedOrders = computed(() => { const orders = ordersQuery.result.value?.managerOrders ?? []; const query = search.value.trim().toLowerCase(); @@ -65,11 +74,72 @@ const filteredOrders = computed(() => { .join(' ') .toLowerCase(); - const matchesSearch = !query || text.includes(query); - return matchesSearch && matchesFilter(order); + return !query || text.includes(query); }); }); +const filteredOrders = computed(() => searchedOrders.value.filter((order) => matchesFilter(order))); + +const statusTabs = computed>(() => [ + { + id: 'ALL', + label: 'Все', + count: searchedOrders.value.length, + }, + { + id: 'NEW', + label: 'Новые', + count: searchedOrders.value.filter((order) => getOrderGroup(order) === 'NEW').length, + }, + { + id: 'PRICED', + label: 'Оценены', + count: searchedOrders.value.filter((order) => getOrderGroup(order) === 'PRICED').length, + }, + { + id: 'IN_PROGRESS', + label: 'В работе', + count: searchedOrders.value.filter((order) => getOrderGroup(order) === 'IN_PROGRESS').length, + }, + { + id: 'CLOSED', + label: 'Закрытые', + count: searchedOrders.value.filter((order) => getOrderGroup(order) === 'CLOSED').length, + }, +]); + +const kanbanColumns = computed(() => { + const columns = [ + { + id: 'NEW' as const, + title: 'Новые', + tone: 'from-[#f5fff8] to-[#eefaf2]', + }, + { + id: 'PRICED' as const, + title: 'Оценены', + tone: 'from-[#fff9ef] to-[#fff1d9]', + }, + { + id: 'IN_PROGRESS' as const, + title: 'В работе', + tone: 'from-[#eef7ff] to-[#e2f0ff]', + }, + { + id: 'CLOSED' as const, + title: 'Закрытые', + tone: 'from-[#f6f7f8] to-[#eceff2]', + }, + ]; + + return columns + .filter((column) => statusFilter.value === 'ALL' || statusFilter.value === column.id) + .map((column) => ({ + ...column, + orders: searchedOrders.value.filter((order) => getOrderGroup(order) === column.id), + })); +}); + const calendarOptions = computed(() => ({ plugins: [dayGridPlugin], locale: ruLocale, @@ -85,7 +155,7 @@ const calendarOptions = computed(() => ({ buttonText: { today: 'Сегодня', }, - events: filteredOrders.value.map((order) => ({ + events: filteredOrders.value.map((order: ManagerOrderItem) => ({ id: order.id, title: `${order.code} • ${order.customerId}`, start: new Date(order.createdAt).toISOString(), @@ -106,13 +176,6 @@ const calendarOptions = computed(() => ({ > + +
+ +
@@ -136,6 +222,67 @@ const calendarOptions = computed(() => ({
+
+
+
+
+
+

{{ column.title }}

+

Заказов: {{ column.orders.length }}

+
+ + {{ column.orders.length }} + +
+
+ +
+ В этой колонке пока пусто. +
+ +
+ +
+
+

{{ order.code }}

+

Клиент: {{ order.customerId }}

+
+ +
+ +

Создан: {{ new Date(order.createdAt).toLocaleString() }}

+

{{ order.deliveryAddress }}

+ +
    +
  • + {{ item.productName }} × {{ item.quantity }} +
  • +
+ +

+ ещё {{ order.items.length - 3 }} поз. +

+
+
+
+
+