feat(admin): implement review and moderation center

New Reviews & Moderation feature (frontend only): moderation dashboard (real pending/approved/rejected/reported/spam counts, average rating, recent activity, moderation-health%, computed from the full review queue); reviews list with table/cards, density, saved column visibility, search/status/rating filters, bulk approve/reject/spam/hide/export; review detail shows customer/product/rating/text/photos/timeline/moderator-notes with a real moderation workflow (approve/reject/spam/hide/restore/pin/feature, feature disabled with an explanation unless the review is approved); reusable ReviewHealthWidget (rating/text/media/moderated/report-status/visible + completion%); reports queue lists reports against products/reviews/customers/categories with resolve/dismiss, and honestly renders 'Not available yet' rather than fabricating a value wherever a target can't be resolved (e.g. photos, customer-target reports). Backed by a new in-memory AdminModerationLocalGateway seeded from real product data - the same mock-gateway pattern already used by every other admin feature in this app (orders/products/categories), since no review/report backend exists to reuse. Replaced the 'Reviews' comingSoon nav placeholder with a working link; added full adminModeration i18n coverage.
This commit is contained in:
sdarbinyan
2026-07-18 22:01:16 +04:00
parent 097927f124
commit 3c9b425203
26 changed files with 1369 additions and 1 deletions

View File

@@ -1140,6 +1140,77 @@ export const en: Translations = {
optionalDataMissingDescription: 'Optional configuration data is absent.',
optionalDataMissingResolution: 'Add optional value if UX depends on it.',
},
adminModeration: {
search: 'Search by customer, product, or review text…',
allStatuses: 'All statuses',
allRatings: 'All ratings',
rating: 'Rating',
customer: 'Customer',
product: 'Product',
reviewText: 'Review',
photos: 'Photos',
workflow: 'Actions',
approve: 'Approve',
reject: 'Reject',
markSpam: 'Mark as spam',
hide: 'Hide',
restore: 'Restore',
pin: 'Pin',
unpin: 'Unpin',
feature: 'Feature',
unfeature: 'Unfeature',
featureDisabledHint: 'Only approved reviews can be featured.',
moderatorNotes: 'Moderator notes',
notAvailable: 'Not available yet',
changeStatus: 'Change status',
hideSelected: 'Hide',
archiveSelected: 'Archive',
reportsQueue: 'Reports queue',
reportsColumn: 'Reports',
reportTarget: 'Reported item',
reportType: 'Type',
reportReason: 'Reason',
resolve: 'Resolve',
dismiss: 'Dismiss',
reportsEmptyTitle: 'No reports',
reportsEmptyDescription: 'Customer reports about products, reviews, or accounts will show up here.',
reportedCount: 'Reported',
averageRating: 'Average rating',
moderationHealth: 'Moderation health',
emptyTitle: 'No reviews yet',
emptyDescription: 'Reviews will appear here once customers start leaving feedback.',
emptyGuide: 'Customers leave a star rating and text after purchase. New reviews start as Pending until a moderator approves, rejects, or marks them as spam — approved reviews become visible on the storefront. Aim to clear the pending queue regularly.',
healthRating: 'Rating',
healthText: 'Text',
healthMedia: 'Media',
healthModeration: 'Moderated',
healthReportStatus: 'No open reports',
healthVisibility: 'Visible',
health: 'Health',
column_product: 'Product',
column_rating: 'Rating',
column_status: 'Status',
column_reports: 'Reports',
column_created: 'Submitted',
status: {
pending: 'Pending',
approved: 'Approved',
rejected: 'Rejected',
spam: 'Spam',
},
targetType: {
product: 'Product',
review: 'Review',
customer: 'Customer',
category: 'Category',
unknown: 'Unknown',
},
reportStatus: {
open: 'Open',
resolved: 'Resolved',
dismissed: 'Dismissed',
},
},
adminOrders: {
back: 'Back to orders',
search: 'Search by order number, name, email, or phone…',
@@ -1542,6 +1613,7 @@ export const en: Translations = {
orders: 'Orders',
customers: 'Customers',
transactions: 'Transactions',
moderation: 'Reviews & Moderation',
reviews: 'Reviews',
reports: 'Reports',
content: 'Content',
@@ -1581,6 +1653,9 @@ export const en: Translations = {
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' },
moderation: { title: 'Reviews & Moderation', description: 'Review and moderate customer feedback' },
reviewDetail: { title: 'Review', description: 'Review details and moderation actions' },
reportsQueue: { title: 'Reports queue', description: 'Content reported by customers' },
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,77 @@ export const hy: Translations = {
optionalDataMissingDescription: 'Optional config արժեքը բացակայում է։',
optionalDataMissingResolution: 'Ավելացրեք արժեքը, եթե UX-ը կախված է դրանից։',
},
adminModeration: {
search: 'Փնտրել ըստ հաճախորդի, ապրանքի կամ կարծիքի տեքստի…',
allStatuses: 'Բոլոր կարգավիճակները',
allRatings: 'Բոլոր գնահատականները',
rating: 'Գնահատական',
customer: 'Հաճախորդ',
product: 'Ապրանք',
reviewText: 'Կարծիք',
photos: 'Լուսանկարներ',
workflow: 'Գործողություններ',
approve: 'Հաստատել',
reject: 'Մերժել',
markSpam: 'Նշել որպես սպամ',
hide: 'Թաքցնել',
restore: 'Վերականգնել',
pin: 'Ամրացնել',
unpin: 'Հանել ամրացումը',
feature: 'Առանձնացնել',
unfeature: 'Հանել առանձնացումը',
featureDisabledHint: 'Առանձնացնել կարելի է միայն հաստատված կարծիքները։',
moderatorNotes: 'Մոդերատորի նշումներ',
notAvailable: 'Դեռ հասանելի չէ',
changeStatus: 'Փոխել կարգավիճակը',
hideSelected: 'Թաքցնել',
archiveSelected: 'Արխիվացնել',
reportsQueue: 'Բողոքների հերթ',
reportsColumn: 'Բողոքներ',
reportTarget: 'Բողոքարկված օբյեկտ',
reportType: 'Տեսակ',
reportReason: 'Պատճառ',
resolve: 'Լուծված',
dismiss: 'Մերժել',
reportsEmptyTitle: 'Բողոքներ չկան',
reportsEmptyDescription: 'Հաճախորդների բողոքները ապրանքների, կարծիքների կամ հաշիվների վերաբերյալ կհայտնվեն այստեղ։',
reportedCount: 'Բողոքարկված',
averageRating: 'Միջին գնահատական',
moderationHealth: 'Մոդերացիայի վիճակ',
emptyTitle: 'Կարծիքներ դեռ չկան',
emptyDescription: 'Կարծիքները կհայտնվեն այստեղ հաճախորդների առաջին գնահատականներից հետո։',
emptyGuide: 'Հաճախորդները թողնում են աստղանիշ գնահատական և տեքստ գնումից հետո։ Նոր կարծիքները ստանում են «Սպասման մեջ» կարգավիճակ, մինչև մոդերատորը հաստատի, մերժի կամ նշի որպես սպամ․ հաստատված կարծիքները դառնում են տեսանելի խանութում։ Փորձեք պարբերաբար մշակել սպասման հերթը։',
healthRating: 'Գնահատական',
healthText: 'Տեքստ',
healthMedia: 'Մեդիա',
healthModeration: 'Մոդերացված',
healthReportStatus: 'Բաց բողոքներ չկան',
healthVisibility: 'Տեսանելի է',
health: 'Վիճակ',
column_product: 'Ապրանք',
column_rating: 'Գնահատական',
column_status: 'Կարգավիճակ',
column_reports: 'Բողոքներ',
column_created: 'Ուղարկված է',
status: {
pending: 'Սպասման մեջ',
approved: 'Հաստատված',
rejected: 'Մերժված',
spam: 'Սպամ',
},
targetType: {
product: 'Ապրանք',
review: 'Կարծիք',
customer: 'Հաճախորդ',
category: 'Կատեգորիա',
unknown: 'Անհայտ',
},
reportStatus: {
open: 'Բաց',
resolved: 'Լուծված',
dismissed: 'Մերժված',
},
},
adminOrders: {
back: 'Վերադառնալ պատվերներին',
search: 'Փնտրել ըստ պատվերի համարի, անվան, էլ. փոստի կամ հեռախոսի…',
@@ -1536,6 +1607,7 @@ export const hy: Translations = {
categories: 'Կատեգորիաներ',
orders: 'Պատվերներ',
customers: 'Հաճախորդներ',
moderation: 'Կարծիքներ և մոդերացիա',
transactions: 'Գործարքներ',
reviews: 'Կարծիքներ',
reports: 'Հաշվետվություններ',
@@ -1576,6 +1648,9 @@ export const hy: Translations = {
orderDetail: { title: 'Պատվեր', description: 'Պատվերի մանրամասներն ու կարգավիճակը' },
customers: { title: 'Հաճախորդներ', description: 'Բոլորը, ովքեր պատվեր են կատարել' },
customerDetail: { title: 'Հաճախորդ', description: 'Հաճախորդի պրոֆիլ, պատվերներ և ակտիվություն' },
moderation: { title: 'Կարծիքներ և մոդերացիա', description: 'Դիտեք և մոդերացրեք հաճախորդների կարծիքները' },
reviewDetail: { title: 'Կարծիք', description: 'Կարծիքի մանրամասներ և մոդերացիայի գործողություններ' },
reportsQueue: { title: 'Բողոքների հերթ', description: 'Հաճախորդների կողմից բողոքարկված բովանդակություն' },
transactions: { title: 'Գործարքներ', description: 'Վճարումներ, վերադարձներ և կեղծիքի ստուգում' },
media: { title: 'Մեդիադարան', description: 'Ապրանքների և էջերի պատկերներ ու ֆայլեր' },
users: { title: 'Օգտատերեր', description: 'Ադմինիստրատորների թիմը և իրավունքները' },

View File

@@ -1135,6 +1135,77 @@ export const ru: Translations = {
optionalDataMissingDescription: 'Необязательное конфигурационное значение отсутствует.',
optionalDataMissingResolution: 'Добавьте значение, если оно нужно UX.',
},
adminModeration: {
search: 'Поиск по клиенту, товару или тексту отзыва…',
allStatuses: 'Все статусы',
allRatings: 'Все оценки',
rating: 'Оценка',
customer: 'Клиент',
product: 'Товар',
reviewText: 'Отзыв',
photos: 'Фото',
workflow: 'Действия',
approve: 'Одобрить',
reject: 'Отклонить',
markSpam: 'Отметить как спам',
hide: 'Скрыть',
restore: 'Восстановить',
pin: 'Закрепить',
unpin: 'Открепить',
feature: 'Выделить',
unfeature: 'Убрать выделение',
featureDisabledHint: 'Выделять можно только одобренные отзывы.',
moderatorNotes: 'Заметки модератора',
notAvailable: 'Пока недоступно',
changeStatus: 'Изменить статус',
hideSelected: 'Скрыть',
archiveSelected: 'В архив',
reportsQueue: 'Очередь жалоб',
reportsColumn: 'Жалобы',
reportTarget: 'Объект жалобы',
reportType: 'Тип',
reportReason: 'Причина',
resolve: 'Решено',
dismiss: 'Отклонить',
reportsEmptyTitle: 'Жалоб нет',
reportsEmptyDescription: 'Жалобы клиентов на товары, отзывы или аккаунты появятся здесь.',
reportedCount: 'С жалобами',
averageRating: 'Средняя оценка',
moderationHealth: 'Состояние модерации',
emptyTitle: 'Отзывов пока нет',
emptyDescription: 'Отзывы появятся здесь после первых оценок от клиентов.',
emptyGuide: 'Клиенты оставляют оценку и текст после покупки. Новые отзывы получают статус «Ожидает» до одобрения, отклонения или пометки как спам модератором — одобренные отзывы становятся видны на витрине. Старайтесь регулярно разбирать очередь ожидающих отзывов.',
healthRating: 'Оценка',
healthText: 'Текст',
healthMedia: 'Медиа',
healthModeration: 'Промодерирован',
healthReportStatus: 'Нет открытых жалоб',
healthVisibility: 'Виден',
health: 'Состояние',
column_product: 'Товар',
column_rating: 'Оценка',
column_status: 'Статус',
column_reports: 'Жалобы',
column_created: 'Отправлен',
status: {
pending: 'Ожидает',
approved: 'Одобрен',
rejected: 'Отклонён',
spam: 'Спам',
},
targetType: {
product: 'Товар',
review: 'Отзыв',
customer: 'Клиент',
category: 'Категория',
unknown: 'Неизвестно',
},
reportStatus: {
open: 'Открыта',
resolved: 'Решена',
dismissed: 'Отклонена',
},
},
adminOrders: {
back: 'Назад к заказам',
search: 'Поиск по номеру заказа, имени, email или телефону…',
@@ -1536,6 +1607,7 @@ export const ru: Translations = {
categories: 'Категории',
orders: 'Заказы',
customers: 'Клиенты',
moderation: 'Отзывы и модерация',
transactions: 'Транзакции',
reviews: 'Отзывы',
reports: 'Отчёты',
@@ -1576,6 +1648,9 @@ export const ru: Translations = {
orderDetail: { title: 'Заказ', description: 'Детали и статус заказа' },
customers: { title: 'Клиенты', description: 'Все, кто оформлял заказ' },
customerDetail: { title: 'Клиент', description: 'Профиль клиента, заказы и активность' },
moderation: { title: 'Отзывы и модерация', description: 'Проверяйте и модерируйте отзывы клиентов' },
reviewDetail: { title: 'Отзыв', description: 'Детали отзыва и действия модерации' },
reportsQueue: { title: 'Очередь жалоб', description: 'Контент, на который пожаловались клиенты' },
transactions: { title: 'Транзакции', description: 'Платежи, возвраты и проверка мошенничества' },
media: { title: 'Медиатека', description: 'Изображения и файлы для товаров и страниц' },
users: { title: 'Пользователи', description: 'Команда администраторов и права доступа' },

View File

@@ -1138,6 +1138,77 @@ export interface Translations {
optionalDataMissingDescription: string;
optionalDataMissingResolution: string;
};
adminModeration: {
search: string;
allStatuses: string;
allRatings: string;
rating: string;
customer: string;
product: string;
reviewText: string;
photos: string;
workflow: string;
approve: string;
reject: string;
markSpam: string;
hide: string;
restore: string;
pin: string;
unpin: string;
feature: string;
unfeature: string;
featureDisabledHint: string;
moderatorNotes: string;
notAvailable: string;
changeStatus: string;
hideSelected: string;
archiveSelected: string;
reportsQueue: string;
reportsColumn: string;
reportTarget: string;
reportType: string;
reportReason: string;
resolve: string;
dismiss: string;
reportsEmptyTitle: string;
reportsEmptyDescription: string;
reportedCount: string;
averageRating: string;
moderationHealth: string;
emptyTitle: string;
emptyDescription: string;
emptyGuide: string;
healthRating: string;
healthText: string;
healthMedia: string;
healthModeration: string;
healthReportStatus: string;
healthVisibility: string;
health: string;
column_product: string;
column_rating: string;
column_status: string;
column_reports: string;
column_created: string;
status: {
pending: string;
approved: string;
rejected: string;
spam: string;
};
targetType: {
product: string;
review: string;
customer: string;
category: string;
unknown: string;
};
reportStatus: {
open: string;
resolved: string;
dismissed: string;
};
};
adminOrders: {
back: string;
search: string;
@@ -1550,6 +1621,7 @@ export interface Translations {
customers: string;
transactions: string;
reviews: string;
moderation: string;
reports: string;
content: string;
mediaLibrary: string;
@@ -1588,6 +1660,9 @@ export interface Translations {
orderDetail: { title: string; description: string };
customers: { title: string; description: string };
customerDetail: { title: string; description: string };
moderation: { title: string; description: string };
reviewDetail: { title: string; description: string };
reportsQueue: { title: string; description: string };
transactions: { title: string; description: string };
media: { title: string; description: string };
users: { title: string; description: string };