feat(admin): implement order and customer operations experience
Some checks failed
Architecture Governance / architecture (push) Has been cancelled

Orders dashboard (real orders-today/pending/paid/cancelled/refunded/customers/returning-customers/average-order/recent-activity/system-alerts, computed from the full order book not just the current page); orders list gains density, saved column visibility, bulk status change/archive/delete/export/print alongside existing search+status filter+pagination, sticky table header; order detail gets a visual status workflow stepper in business language (Pending/Packing/Shipping/Completed, with Cancelled/Refunded as terminal states) and a reusable OrderTimelineComponent replacing the flat text log; added a derived Customers feature (list + profile detail: statistics/lifetime value/addresses/orders/activity/notes) built entirely by grouping existing AdminOrder records by email - no new backend, no invented gateway, since no customer entity existed. Added archive/restore/delete to the orders gateway (soft-delete pattern mirroring categories) and filled in the adminOrders/adminCustomers i18n namespaces, which previously didn't exist at all (every adminOrders.* label was rendering as a raw key).
This commit is contained in:
sdarbinyan
2026-07-18 21:47:54 +04:00
parent ac615837ab
commit 097927f124
30 changed files with 1218 additions and 36 deletions

View File

@@ -1140,6 +1140,82 @@ export const en: Translations = {
optionalDataMissingDescription: 'Optional configuration data is absent.',
optionalDataMissingResolution: 'Add optional value if UX depends on it.',
},
adminOrders: {
back: 'Back to orders',
search: 'Search by order number, name, email, or phone…',
export: 'Export',
view: 'View',
orderNumber: 'Order',
customer: 'Customer',
payment: 'Payment',
delivery: 'Delivery',
trackingNumber: 'Tracking number',
createdAt: 'Placed',
items: 'Items',
timeline: 'Timeline',
notes: 'Notes to customer',
internalNotes: 'Internal notes',
addNote: 'Add note',
changeStatus: 'Change status',
applyStatus: 'Apply',
archiveSelected: 'Archive',
printSelected: 'Print',
printInvoice: 'Print invoice',
requestRefund: 'Request refund',
cancelOrder: 'Cancel order',
confirmCancel: 'Cancel this order?',
confirmRefund: 'Request a refund for this order?',
emptyTitle: 'No orders yet',
emptyDescription: 'Orders will show up here as customers check out.',
column_customer: 'Customer',
column_status: 'Status',
column_payment: 'Payment',
column_total: 'Total',
column_created: 'Placed',
status: {
pending: 'Pending',
processing: 'Packing',
shipped: 'Shipping',
delivered: 'Completed',
cancelled: 'Cancelled',
refunded: 'Refunded',
},
paymentStatus: {
unpaid: 'Unpaid',
paid: 'Paid',
refund_requested: 'Refund requested',
refunded: 'Refunded',
},
ordersToday: 'Orders today',
paidCount: 'Paid',
customersCount: 'Customers',
returningCustomers: 'Returning customers',
averageOrder: 'Average order',
recentActivity: 'Recent activity',
noRecentActivity: 'No activity yet.',
alertStuckPending: 'orders have been pending for over 48 hours',
alertRefundRequested: 'orders have a refund request waiting',
},
adminCustomers: {
search: 'Search by name, email, or phone…',
name: 'Name',
email: 'Email',
phone: 'Phone',
orderCount: 'Orders',
lifetimeValue: 'Lifetime value',
returning: 'Returning',
profile: 'Profile',
statistics: 'Statistics',
firstOrder: 'First order',
lastOrder: 'Last order',
addresses: 'Addresses',
notes: 'Notes',
notesUnavailable: 'No customer-level notes yet — add a note on one of their orders instead.',
orders: 'Orders',
activity: 'Activity',
emptyTitle: 'No customers yet',
emptyDescription: 'Customers appear here once they place their first order.',
},
mediaLibrary: {
title: 'Media Library',
upload: 'Upload',
@@ -1464,6 +1540,7 @@ export const en: Translations = {
products: 'Products',
categories: 'Categories',
orders: 'Orders',
customers: 'Customers',
transactions: 'Transactions',
reviews: 'Reviews',
reports: 'Reports',
@@ -1502,6 +1579,8 @@ export const en: Translations = {
categoryEdit: { title: 'Edit category', description: 'Change this category\'s details' },
orders: { title: 'Orders', description: 'View and process customer orders' },
orderDetail: { title: 'Order', description: 'Order details and status' },
customers: { title: 'Customers', description: 'Everyone who has placed an order' },
customerDetail: { title: 'Customer', description: 'Customer profile, orders, and activity' },
transactions: { title: 'Transactions', description: 'Payments, refunds, and fraud review' },
media: { title: 'Media Library', description: 'Images and files for products and pages' },
users: { title: 'Users', description: 'Admin team members and permissions' },

View File

@@ -1135,6 +1135,82 @@ export const hy: Translations = {
optionalDataMissingDescription: 'Optional config արժեքը բացակայում է։',
optionalDataMissingResolution: 'Ավելացրեք արժեքը, եթե UX-ը կախված է դրանից։',
},
adminOrders: {
back: 'Վերադառնալ պատվերներին',
search: 'Փնտրել ըստ պատվերի համարի, անվան, էլ. փոստի կամ հեռախոսի…',
export: 'Արտահանել',
view: 'Դիտել',
orderNumber: 'Պատվեր',
customer: 'Հաճախորդ',
payment: 'Վճարում',
delivery: 'Առաքում',
trackingNumber: 'Հետևման համար',
createdAt: 'Ստեղծված է',
items: 'Ապրանքներ',
timeline: 'Ժամանակագրություն',
notes: 'Նշումներ հաճախորդի համար',
internalNotes: 'Ներքին նշումներ',
addNote: 'Ավելացնել նշում',
changeStatus: 'Փոխել կարգավիճակը',
applyStatus: 'Կիրառել',
archiveSelected: 'Արխիվացնել',
printSelected: 'Տպել',
printInvoice: 'Տպել հաշիվ-ապրանքագիրը',
requestRefund: 'Հայցել վերադարձ',
cancelOrder: 'Չեղարկել պատվերը',
confirmCancel: 'Չեղարկե՞լ այս պատվերը։',
confirmRefund: 'Հայցե՞լ վերադարձ այս պատվերի համար։',
emptyTitle: 'Պատվերներ դեռ չկան',
emptyDescription: 'Պատվերները կհայտնվեն այստեղ հաճախորդների գնումներից հետո։',
column_customer: 'Հաճախորդ',
column_status: 'Կարգավիճակ',
column_payment: 'Վճարում',
column_total: 'Գումար',
column_created: 'Ստեղծված է',
status: {
pending: 'Սպասման մեջ',
processing: 'Փաթեթավորում',
shipped: 'Առաքվում է',
delivered: 'Ավարտված',
cancelled: 'Չեղարկված',
refunded: 'Վերադարձված',
},
paymentStatus: {
unpaid: 'Չվճարված',
paid: 'Վճարված',
refund_requested: 'Հայցվել է վերադարձ',
refunded: 'Վերադարձված',
},
ordersToday: 'Այսօրվա պատվերներ',
paidCount: 'Վճարված',
customersCount: 'Հաճախորդներ',
returningCustomers: 'Կրկնվող հաճախորդներ',
averageOrder: 'Միջին պատվեր',
recentActivity: 'Վերջին ակտիվությունը',
noRecentActivity: 'Ակտիվություն դեռ չկա։',
alertStuckPending: 'պատվեր սպասման մեջ է 48 ժամից ավելի',
alertRefundRequested: 'պատվեր սպասում է վերադարձի հայցի',
},
adminCustomers: {
search: 'Փնտրել ըստ անվան, էլ. փոստի կամ հեռախոսի…',
name: 'Անուն',
email: 'Էլ. փոստ',
phone: 'Հեռախոս',
orderCount: 'Պատվերներ',
lifetimeValue: 'Ընդհանուր գնումների գումար',
returning: 'Կրկնվող',
profile: 'Պրոֆիլ',
statistics: 'Վիճակագրություն',
firstOrder: 'Առաջին պատվեր',
lastOrder: 'Վերջին պատվեր',
addresses: 'Հասցեներ',
notes: 'Նշումներ',
notesUnavailable: 'Հաճախորդի մակարդակի նշումներ դեռ չկան․ ավելացրեք նշում նրա որևէ պատվերի վրա։',
orders: 'Պատվերներ',
activity: 'Ակտիվություն',
emptyTitle: 'Հաճախորդներ դեռ չկան',
emptyDescription: 'Հաճախորդները կհայտնվեն այստեղ առաջին պատվերից հետո։',
},
mediaLibrary: {
title: 'Մեդիագրադարան',
upload: 'Վերբեռնել',
@@ -1459,6 +1535,7 @@ export const hy: Translations = {
products: 'Ապրանքներ',
categories: 'Կատեգորիաներ',
orders: 'Պատվերներ',
customers: 'Հաճախորդներ',
transactions: 'Գործարքներ',
reviews: 'Կարծիքներ',
reports: 'Հաշվետվություններ',
@@ -1497,6 +1574,8 @@ export const hy: Translations = {
categoryEdit: { title: 'Կատեգորիայի խմբագրում', description: 'Փոփոխեք կատեգորիայի տվյալները' },
orders: { title: 'Պատվերներ', description: 'Դիտեք և մշակեք գնորդների պատվերները' },
orderDetail: { title: 'Պատվեր', description: 'Պատվերի մանրամասներն ու կարգավիճակը' },
customers: { title: 'Հաճախորդներ', description: 'Բոլորը, ովքեր պատվեր են կատարել' },
customerDetail: { title: 'Հաճախորդ', description: 'Հաճախորդի պրոֆիլ, պատվերներ և ակտիվություն' },
transactions: { title: 'Գործարքներ', description: 'Վճարումներ, վերադարձներ և կեղծիքի ստուգում' },
media: { title: 'Մեդիադարան', description: 'Ապրանքների և էջերի պատկերներ ու ֆայլեր' },
users: { title: 'Օգտատերեր', description: 'Ադմինիստրատորների թիմը և իրավունքները' },

View File

@@ -1135,6 +1135,82 @@ export const ru: Translations = {
optionalDataMissingDescription: 'Необязательное конфигурационное значение отсутствует.',
optionalDataMissingResolution: 'Добавьте значение, если оно нужно UX.',
},
adminOrders: {
back: 'Назад к заказам',
search: 'Поиск по номеру заказа, имени, email или телефону…',
export: 'Экспорт',
view: 'Просмотр',
orderNumber: 'Заказ',
customer: 'Клиент',
payment: 'Оплата',
delivery: 'Доставка',
trackingNumber: 'Номер отслеживания',
createdAt: 'Оформлен',
items: 'Товары',
timeline: 'Хронология',
notes: 'Заметки для клиента',
internalNotes: 'Внутренние заметки',
addNote: 'Добавить заметку',
changeStatus: 'Изменить статус',
applyStatus: 'Применить',
archiveSelected: 'В архив',
printSelected: 'Печать',
printInvoice: 'Печать накладной',
requestRefund: 'Запросить возврат',
cancelOrder: 'Отменить заказ',
confirmCancel: 'Отменить этот заказ?',
confirmRefund: 'Запросить возврат по этому заказу?',
emptyTitle: 'Заказов пока нет',
emptyDescription: 'Заказы появятся здесь после оформления покупок.',
column_customer: 'Клиент',
column_status: 'Статус',
column_payment: 'Оплата',
column_total: 'Сумма',
column_created: 'Оформлен',
status: {
pending: 'Ожидает',
processing: 'Сборка',
shipped: 'Доставляется',
delivered: 'Завершён',
cancelled: 'Отменён',
refunded: 'Возвращён',
},
paymentStatus: {
unpaid: 'Не оплачен',
paid: 'Оплачен',
refund_requested: 'Запрошен возврат',
refunded: 'Возвращён',
},
ordersToday: 'Заказов сегодня',
paidCount: 'Оплачено',
customersCount: 'Клиенты',
returningCustomers: 'Повторные клиенты',
averageOrder: 'Средний чек',
recentActivity: 'Недавняя активность',
noRecentActivity: 'Активности пока нет.',
alertStuckPending: 'заказов ожидают более 48 часов',
alertRefundRequested: 'заказов ждут запроса на возврат',
},
adminCustomers: {
search: 'Поиск по имени, email или телефону…',
name: 'Имя',
email: 'Email',
phone: 'Телефон',
orderCount: 'Заказы',
lifetimeValue: 'Общая сумма покупок',
returning: 'Повторный',
profile: 'Профиль',
statistics: 'Статистика',
firstOrder: 'Первый заказ',
lastOrder: 'Последний заказ',
addresses: 'Адреса',
notes: 'Заметки',
notesUnavailable: 'Заметок по клиенту пока нет — добавьте заметку к одному из его заказов.',
orders: 'Заказы',
activity: 'Активность',
emptyTitle: 'Клиентов пока нет',
emptyDescription: 'Клиенты появятся здесь после первого заказа.',
},
mediaLibrary: {
title: 'Медиатека',
upload: 'Загрузить',
@@ -1459,6 +1535,7 @@ export const ru: Translations = {
products: 'Товары',
categories: 'Категории',
orders: 'Заказы',
customers: 'Клиенты',
transactions: 'Транзакции',
reviews: 'Отзывы',
reports: 'Отчёты',
@@ -1497,6 +1574,8 @@ export const ru: Translations = {
categoryEdit: { title: 'Редактирование категории', description: 'Измените данные категории' },
orders: { title: 'Заказы', description: 'Просматривайте и обрабатывайте заказы покупателей' },
orderDetail: { title: 'Заказ', description: 'Детали и статус заказа' },
customers: { title: 'Клиенты', description: 'Все, кто оформлял заказ' },
customerDetail: { title: 'Клиент', description: 'Профиль клиента, заказы и активность' },
transactions: { title: 'Транзакции', description: 'Платежи, возвраты и проверка мошенничества' },
media: { title: 'Медиатека', description: 'Изображения и файлы для товаров и страниц' },
users: { title: 'Пользователи', description: 'Команда администраторов и права доступа' },

View File

@@ -1138,6 +1138,82 @@ export interface Translations {
optionalDataMissingDescription: string;
optionalDataMissingResolution: string;
};
adminOrders: {
back: string;
search: string;
export: string;
view: string;
orderNumber: string;
customer: string;
payment: string;
delivery: string;
trackingNumber: string;
createdAt: string;
items: string;
timeline: string;
notes: string;
internalNotes: string;
addNote: string;
changeStatus: string;
applyStatus: string;
archiveSelected: string;
printSelected: string;
printInvoice: string;
requestRefund: string;
cancelOrder: string;
confirmCancel: string;
confirmRefund: string;
emptyTitle: string;
emptyDescription: string;
column_customer: string;
column_status: string;
column_payment: string;
column_total: string;
column_created: string;
status: {
pending: string;
processing: string;
shipped: string;
delivered: string;
cancelled: string;
refunded: string;
};
paymentStatus: {
unpaid: string;
paid: string;
refund_requested: string;
refunded: string;
};
ordersToday: string;
paidCount: string;
customersCount: string;
returningCustomers: string;
averageOrder: string;
recentActivity: string;
noRecentActivity: string;
alertStuckPending: string;
alertRefundRequested: string;
};
adminCustomers: {
search: string;
name: string;
email: string;
phone: string;
orderCount: string;
lifetimeValue: string;
returning: string;
profile: string;
statistics: string;
firstOrder: string;
lastOrder: string;
addresses: string;
notes: string;
notesUnavailable: string;
orders: string;
activity: string;
emptyTitle: string;
emptyDescription: string;
};
mediaLibrary: {
title: string;
upload: string;
@@ -1471,6 +1547,7 @@ export interface Translations {
products: string;
categories: string;
orders: string;
customers: string;
transactions: string;
reviews: string;
reports: string;
@@ -1509,6 +1586,8 @@ export interface Translations {
categoryEdit: { title: string; description: string };
orders: { title: string; description: string };
orderDetail: { title: string; description: string };
customers: { title: string; description: string };
customerDetail: { title: string; description: string };
transactions: { title: string; description: string };
media: { title: string; description: string };
users: { title: string; description: string };