From 691990f9921ccfc836e16d396e460b7e93d8f206 Mon Sep 17 00:00:00 2001 From: Ruslan Bakiev Date: Sat, 4 Apr 2026 10:42:40 +0700 Subject: [PATCH] Add mock order status timeline --- .../orders/OrderStatusTimelineCard.vue | 85 +++++++ app/composables/useOrderStatusPresentation.ts | 213 ++++++++++++++++++ app/pages/client-orders/[id].vue | 15 +- app/pages/orders/[id].vue | 14 +- 4 files changed, 306 insertions(+), 21 deletions(-) create mode 100644 app/components/orders/OrderStatusTimelineCard.vue create mode 100644 app/composables/useOrderStatusPresentation.ts diff --git a/app/components/orders/OrderStatusTimelineCard.vue b/app/components/orders/OrderStatusTimelineCard.vue new file mode 100644 index 0000000..36bc266 --- /dev/null +++ b/app/components/orders/OrderStatusTimelineCard.vue @@ -0,0 +1,85 @@ + + + diff --git a/app/composables/useOrderStatusPresentation.ts b/app/composables/useOrderStatusPresentation.ts new file mode 100644 index 0000000..45fd482 --- /dev/null +++ b/app/composables/useOrderStatusPresentation.ts @@ -0,0 +1,213 @@ +type OrderStatusCode = + | 'NEW' + | 'MANAGER_PROCESSING' + | 'WAITING_DOUBLE_CONFIRM' + | 'CLIENT_REJECTED' + | 'MANAGER_REJECTED' + | 'MANAGER_BLOCKED' + | 'CONFIRMED' + | 'IN_PROGRESS' + | 'COMPLETED'; + +type TimelineStage = { + code: string; + label: string; + note: string; + dateLabel: string; + state: 'done' | 'current' | 'upcoming'; +}; + +type StatusPresentation = { + title: string; + summary: string; + stages: TimelineStage[]; +}; + +const STAGE_ORDER: OrderStatusCode[] = [ + 'NEW', + 'MANAGER_PROCESSING', + 'WAITING_DOUBLE_CONFIRM', + 'CONFIRMED', + 'IN_PROGRESS', + 'COMPLETED', +]; + +const DAY_FORMATTER = new Intl.DateTimeFormat('ru-RU', { + day: 'numeric', + month: 'long', +}); + +function addDays(date: Date, days: number) { + const next = new Date(date); + next.setDate(next.getDate() + days); + return next; +} + +function formatDay(date: Date) { + return DAY_FORMATTER.format(date); +} + +function stageIndex(status: string) { + const index = STAGE_ORDER.indexOf(status as OrderStatusCode); + return index >= 0 ? index : 0; +} + +function buildDates(createdAt: string | Date) { + const base = new Date(createdAt); + + return { + created: base, + offer: addDays(base, 1), + approval: addDays(base, 2), + production: addDays(base, 4), + shipment: addDays(base, 6), + delivered: addDays(base, 8), + }; +} + +export function getOrderStatusPresentation(status: string, createdAt: string | Date): StatusPresentation { + const dates = buildDates(createdAt); + + if (status === 'CLIENT_REJECTED') { + return { + title: 'Заказ остановлен клиентом', + summary: 'Согласование остановлено. Если нужно, заказ можно собрать заново с новыми условиями.', + stages: [ + { + code: 'NEW', + label: 'Заявка принята', + note: 'Заказ попал в обработку.', + dateLabel: formatDay(dates.created), + state: 'done', + }, + { + code: 'CLIENT_REJECTED', + label: 'Клиент отказался от продолжения', + note: 'Текущий заказ закрыт без запуска в работу.', + dateLabel: formatDay(dates.approval), + state: 'current', + }, + ], + }; + } + + if (status === 'MANAGER_REJECTED' || status === 'MANAGER_BLOCKED') { + return { + title: status === 'MANAGER_BLOCKED' ? 'Заказ ждёт уточнения' : 'Заказ остановлен менеджером', + summary: status === 'MANAGER_BLOCKED' + ? 'Сейчас ждём уточнение параметров, после него заказ вернётся в работу.' + : 'Менеджер остановил обработку. При необходимости можно собрать новый заказ.', + stages: [ + { + code: 'NEW', + label: 'Заявка принята', + note: 'Заказ попал в обработку.', + dateLabel: formatDay(dates.created), + state: 'done', + }, + { + code: status, + label: status === 'MANAGER_BLOCKED' ? 'Нужно уточнение по заказу' : 'Обработка остановлена', + note: status === 'MANAGER_BLOCKED' + ? 'Менеджер запросил уточнение перед продолжением.' + : 'Текущий заказ завершён без запуска в производство.', + dateLabel: formatDay(dates.approval), + state: 'current', + }, + ], + }; + } + + const currentIndex = stageIndex(status); + + const stages: TimelineStage[] = [ + { + code: 'NEW', + label: 'Заявка принята', + note: 'Получили состав заказа и начали обработку.', + dateLabel: formatDay(dates.created), + state: currentIndex > 0 ? 'done' : 'current', + }, + { + code: 'MANAGER_PROCESSING', + label: 'Готовим предложение', + note: 'Уточняем цену, сроки и условия доставки.', + dateLabel: formatDay(dates.offer), + state: currentIndex > 1 ? 'done' : currentIndex === 1 ? 'current' : 'upcoming', + }, + { + code: 'WAITING_DOUBLE_CONFIRM', + label: 'Ждём подтверждение', + note: 'Показываем согласованные условия и ждём финальное подтверждение.', + dateLabel: formatDay(dates.approval), + state: currentIndex > 2 ? 'done' : currentIndex === 2 ? 'current' : 'upcoming', + }, + { + code: 'CONFIRMED', + label: 'Заказ подтверждён', + note: `Планируем передать в производство и ориентируемся на выпуск ${formatDay(dates.production)}.`, + dateLabel: formatDay(dates.production), + state: currentIndex > 3 ? 'done' : currentIndex === 3 ? 'current' : 'upcoming', + }, + { + code: 'IN_PROGRESS', + label: 'Заказ в работе', + note: `Сейчас идёт исполнение. Следующее ожидаемое обновление около ${formatDay(dates.shipment)}.`, + dateLabel: formatDay(dates.shipment), + state: currentIndex > 4 ? 'done' : currentIndex === 4 ? 'current' : 'upcoming', + }, + { + code: 'COMPLETED', + label: 'Заказ завершён', + note: 'Исполнение закрыто, заказ готов к выдаче или уже доставлен.', + dateLabel: formatDay(dates.delivered), + state: currentIndex === 5 ? 'current' : currentIndex > 5 ? 'done' : 'upcoming', + }, + ]; + + if (status === 'NEW') { + return { + title: 'Собираем предложение по заказу', + summary: `Сейчас уточняем стоимость и комплектацию. Следующее обновление ориентировочно ${formatDay(dates.offer)}.`, + stages, + }; + } + + if (status === 'MANAGER_PROCESSING') { + return { + title: 'Готовим условия по заказу', + summary: `Менеджер собирает финальные условия. Ориентир по следующему апдейту ${formatDay(dates.approval)}.`, + stages, + }; + } + + if (status === 'WAITING_DOUBLE_CONFIRM') { + return { + title: 'Ожидаем подтверждение условий', + summary: 'Нужно подтвердить текущие условия, после этого сразу двинем заказ дальше.', + stages, + }; + } + + if (status === 'CONFIRMED') { + return { + title: 'Заказ подтверждён', + summary: `Ожидаем выпуск с производства ориентировочно ${formatDay(dates.production)}.`, + stages, + }; + } + + if (status === 'IN_PROGRESS') { + return { + title: 'Заказ в работе', + summary: `Сейчас заказ исполняется. Следующее ожидаемое обновление около ${formatDay(dates.shipment)}.`, + stages, + }; + } + + return { + title: 'Заказ завершён', + summary: 'Работы по заказу завершены, история этапов сохранена ниже.', + stages, + }; +} diff --git a/app/pages/client-orders/[id].vue b/app/pages/client-orders/[id].vue index 060d642..d29c6d7 100644 --- a/app/pages/client-orders/[id].vue +++ b/app/pages/client-orders/[id].vue @@ -1,6 +1,5 @@