feat(products): implement professional product management experience

Products dashboard (real total/published/drafts/out-of-stock/hidden/missing-images/missing-SEO/low-quality counts, recently-edited list, recommended next action); list gains table/grid view toggle, density, saved column visibility and saved sort (persisted via LocalStorageService), plus real bulk assign-category/assign-tags/duplicate/CSV-export alongside existing publish/hide/delete; per-row and per-editor reusable ProductHealthWidget (images/SEO/price/category/description/inventory checklist + completion %); editor reorganized into General/Media/Pricing/Inventory/Categories/Attributes/SEO/Visibility/Advanced tabs, media now uses the shared MediaPickerComponent/ImageFieldComponent (primary image + reorderable gallery) instead of raw URL textareas, specifications/attributes/variants moved off pipe-delimited textareas onto the shared KeyValueEditorComponent, SEO tab explains fields in plain language with a live search-result preview, inventory relabeled in business language, toggles/badges use the shared Toggle/Badge components. Filled in the adminProducts i18n namespace (previously ~98% missing, rendering raw translation keys) across en/ru/hy.
This commit is contained in:
sdarbinyan
2026-07-18 16:34:06 +04:00
parent 5d52d81c7d
commit aeb48504c5
19 changed files with 1561 additions and 212 deletions

View File

@@ -1225,6 +1225,141 @@ export const en: Translations = {
adminProducts: {
emptyTitle: 'No products found',
emptyDescription: 'Try adjusting your filters, or create a new product.',
emptyGuide: 'Good products have a clear title, at least one photo, a price, and a short description — that\'s enough to publish. You can always add more detail later.',
search: 'Search products…',
category: 'Category',
allCategories: 'All categories',
visibility: 'Visibility',
allVisibility: 'All visibility',
visible: 'Visible',
hidden: 'Hidden',
stockStatus: 'Stock status',
allStock: 'All stock levels',
inStock: 'In stock',
lowStock: 'Low stock',
outOfStock: 'Out of stock',
sortTitle: 'Name',
sortPrice: 'Price',
sortPriority: 'Priority',
sortStock: 'Stock',
sortUpdated: 'Last updated',
showArchived: 'Show archived',
infiniteScroll: 'Infinite scroll',
viewMode: 'View',
viewTable: 'Table',
viewGrid: 'Grid',
density: 'Density',
densityComfortable: 'Comfortable',
densityCompact: 'Compact',
columns: 'Columns',
column_sku: 'SKU',
column_brand: 'Brand',
column_price: 'Price',
column_stock: 'Stock',
column_visibility: 'Visibility',
column_updated: 'Last updated',
create: 'Create product',
edit: 'Edit product',
duplicate: 'Duplicate',
selectedCount: 'selected',
bulkShow: 'Publish',
bulkHide: 'Hide',
bulkAssignCategory: 'Assign category',
bulkAssignTags: 'Assign tags',
bulkDuplicateAction: 'Duplicate',
bulkExportAction: 'Export',
bulkDelete: 'Delete',
tagsPlaceholder: 'e.g. summer, sale',
name: 'Name',
brand: 'Brand',
healthColumn: 'Health',
actions: 'Actions',
archive: 'Archive',
delete: 'Delete',
items: 'products',
loadMore: 'Load more',
noImage: 'No image',
slug: 'URL slug',
barcode: 'Barcode',
priority: 'Priority',
shortDescription: 'Short description',
translations: 'Translations',
primaryImage: 'Primary image',
primaryImageHint: 'The main photo shown in listings and search results.',
gallery: 'Gallery',
galleryHint: 'Additional photos shown on the product page. Drag order with the arrows.',
moveLeft: 'Move earlier',
moveRight: 'Move later',
videos: 'Videos (one URL per line)',
discount: 'Discount %',
currency: 'Currency',
stockQuantity: 'Stock quantity',
stockQuantityHint: 'How many units are available to sell right now.',
availabilityStatus: 'Availability',
availabilityStatusHint: 'What customers see: in stock, running low, or out of stock.',
availabilityNote: 'Availability note',
availabilityNoteHint: 'Optional extra detail shown to customers, e.g. "Ships in 3 days".',
reservedNote: 'Reserved stock isn\'t tracked separately yet — quantity shown here is total available stock.',
relatedProducts: 'Related products',
specifications: 'Specifications',
attributes: 'Attributes',
addRow: 'Add row',
removeRow: 'Remove',
rowKey: 'Name',
rowValue: 'Value',
addVariant: 'Add variant',
variantName: 'Variant name',
variantsHint: 'Each variant is a purchasable option of this product, e.g. a size or color, with its own price and stock.',
seoExplain: 'This is what shows up in search results — a clear title and description help customers find this product.',
searchTitle: 'Search title',
searchTitleHint: 'The headline shown in search results. Keep it short and descriptive.',
searchDescription: 'Search description',
searchDescriptionHint: 'A short summary shown under the title in search results.',
seoMissingTitle: 'Add a search title so this product can be found.',
seoMissingDescription: 'Add a search description to improve how this product appears in results.',
keywords: 'Keywords',
featured: 'Featured',
recommended: 'Recommended',
new: 'New',
bestseller: 'Bestseller',
badges: 'Badges (comma-separated)',
htmlDescription: 'Detailed description',
customer: 'Customer feedback',
reviewsReadonly: 'Reviews',
noReviews: 'No reviews yet.',
questionsReadonly: 'Questions',
noQuestions: 'No questions yet.',
save: 'Save product',
groupGeneral: 'General',
groupMedia: 'Media',
groupPricing: 'Pricing',
groupInventory: 'Inventory',
groupCategories: 'Categories',
groupAttributes: 'Attributes',
groupSeo: 'SEO',
groupVisibility: 'Visibility',
groupAdvanced: 'Advanced',
healthImages: 'Images',
healthSeo: 'SEO',
healthPrice: 'Price',
healthCategory: 'Category',
healthDescription: 'Description',
healthInventory: 'Inventory',
totalProducts: 'Total products',
publishedCount: 'Published',
draftsCount: 'Drafts',
outOfStockCount: 'Out of stock',
hiddenCount: 'Hidden',
missingImagesCount: 'Missing images',
missingSeoCount: 'Missing SEO',
lowQualityCount: 'Low-quality',
recentlyEdited: 'Recently edited',
recommendedNext: 'Recommended next step',
openAction: 'Open',
recommendAddImages: 'A product has no photos — add at least one image so it looks trustworthy.',
recommendAddSeo: 'A product is missing a search title or description — add one so it can be found.',
recommendRestock: 'A product is out of stock — restock it or mark it unavailable.',
recommendNone: 'Nice work — your catalog is in good shape.',
},
adminUsers: {
emptyTitle: 'No users found',

View File

@@ -1220,6 +1220,141 @@ export const hy: Translations = {
adminProducts: {
emptyTitle: 'Ապրանքներ չեն գտնվել',
emptyDescription: 'Փոխեք ֆիլտրերը կամ ստեղծեք նոր ապրանք։',
emptyGuide: 'Լավ ապրանքին բավական է հստակ վերնագիր, առնվազն մեկ լուսանկար, գին և կարճ նկարագրություն՝ հրապարակելու համար։ Մնացածը կարող եք ավելացնել հետո։',
search: 'Փնտրել ապրանքներ…',
category: 'Կատեգորիա',
allCategories: 'Բոլոր կատեգորիաները',
visibility: 'Տեսանելիություն',
allVisibility: 'Ցանկացած տեսանելիություն',
visible: 'Տեսանելի',
hidden: 'Թաքցված',
stockStatus: 'Պահեստի կարգավիճակ',
allStock: 'Ցանկացած մնացորդ',
inStock: 'Առկա է',
lowStock: 'Քիչ է մնացել',
outOfStock: 'Առկա չէ',
sortTitle: 'Անուն',
sortPrice: 'Գին',
sortPriority: 'Առաջնահերթություն',
sortStock: 'Մնացորդ',
sortUpdated: 'Վերջին փոփոխություն',
showArchived: 'Ցույց տալ արխիվայինները',
infiniteScroll: 'Անվերջ ոլորում',
viewMode: 'Տեսք',
viewTable: 'Աղյուսակ',
viewGrid: 'Ցանց',
density: 'Խտություն',
densityComfortable: 'Հարմարավետ',
densityCompact: 'Կոմպակտ',
columns: 'Սյունակներ',
column_sku: 'SKU',
column_brand: 'Ապրանքանիշ',
column_price: 'Գին',
column_stock: 'Մնացորդ',
column_visibility: 'Տեսանելիություն',
column_updated: 'Վերջին փոփոխություն',
create: 'Ստեղծել ապրանք',
edit: 'Խմբագրել ապրանքը',
duplicate: 'Կրկնօրինակել',
selectedCount: 'ընտրված է',
bulkShow: 'Հրապարակել',
bulkHide: 'Թաքցնել',
bulkAssignCategory: 'Նշանակել կատեգորիա',
bulkAssignTags: 'Նշանակել պիտակներ',
bulkDuplicateAction: 'Կրկնօրինակել',
bulkExportAction: 'Արտահանել',
bulkDelete: 'Ջնջել',
tagsPlaceholder: 'օր․՝ ամառ, զեղչ',
name: 'Անուն',
brand: 'Ապրանքանիշ',
healthColumn: 'Որակ',
actions: 'Գործողություններ',
archive: 'Արխիվացնել',
delete: 'Ջնջել',
items: 'ապրանք',
loadMore: 'Բեռնել ավելին',
noImage: 'Առանց պատկերի',
slug: 'URL հասցե',
barcode: 'Շտրիխկոդ',
priority: 'Առաջնահերթություն',
shortDescription: 'Կարճ նկարագրություն',
translations: 'Թարգմանություններ',
primaryImage: 'Հիմնական պատկեր',
primaryImageHint: 'Հիմնական լուսանկարը, որ երևում է ցանկերում և որոնման արդյունքներում։',
gallery: 'Պատկերասրահ',
galleryHint: 'Լրացուցիչ լուսանկարներ ապրանքի էջում։ Կարգը փոխեք սլաքներով։',
moveLeft: 'Տեղափոխել ավելի վաղ',
moveRight: 'Տեղափոխել ավելի ուշ',
videos: 'Տեսանյութեր (մեկ հղում մեկ տողում)',
discount: 'Զեղչ %',
currency: 'Արժույթ',
stockQuantity: 'Պահեստի քանակ',
stockQuantityHint: 'Քանի հատ է հասանելի վաճառքի համար հենց հիմա։',
availabilityStatus: 'Հասանելիություն',
availabilityStatusHint: 'Ինչ է տեսնում հաճախորդը՝ առկա է, քիչ է մնացել, թե առկա չէ։',
availabilityNote: 'Հասանելիության նշում',
availabilityNoteHint: 'Լրացուցիչ մանրամասն հաճախորդի համար, օր․՝ «Առաքում 3 օրում»։',
reservedNote: 'Ամրագրված պաշարը դեռ առանձին չի հաշվառվում․ այստեղ ցուցադրվում է ընդհանուր հասանելի քանակը։',
relatedProducts: 'Առնչվող ապրանքներ',
specifications: 'Բնութագրեր',
attributes: 'Հատկանիշներ',
addRow: 'Ավելացնել տող',
removeRow: 'Հեռացնել',
rowKey: 'Անուն',
rowValue: 'Արժեք',
addVariant: 'Ավելացնել տարբերակ',
variantName: 'Տարբերակի անուն',
variantsHint: 'Յուրաքանչյուր տարբերակ այս ապրանքի առանձին գնվող տարբերակ է (օր․՝ չափս կամ գույն)՝ իր գնով և մնացորդով։',
seoExplain: 'Սա այն է, ինչ երևում է որոնման արդյունքներում․ հստակ վերնագիրն ու նկարագրությունը օգնում են գտնել այս ապրանքը։',
searchTitle: 'Որոնման վերնագիր',
searchTitleHint: 'Վերնագիրը, որ երևում է որոնման արդյունքներում։ Պահեք կարճ։',
searchDescription: 'Որոնման նկարագրություն',
searchDescriptionHint: 'Կարճ նկարագրություն՝ վերնագրի տակ, որոնման արդյունքներում։',
seoMissingTitle: 'Ավելացրեք որոնման վերնագիր, որպեսզի ապրանքը գտնվի։',
seoMissingDescription: 'Ավելացրեք որոնման նկարագրություն՝ ապրանքի ցուցադրումը բարելավելու համար։',
keywords: 'Հիմնաբառեր',
featured: 'Ընտրված',
recommended: 'Երաշխավորված',
new: 'Նոր',
bestseller: 'Բեսթսելլեր',
badges: 'Կրծքանշաններ (ստորակետերով)',
htmlDescription: 'Մանրամասն նկարագրություն',
customer: 'Հաճախորդների արձագանք',
reviewsReadonly: 'Կարծիքներ',
noReviews: 'Կարծիքներ դեռ չկան։',
questionsReadonly: 'Հարցեր',
noQuestions: 'Հարցեր դեռ չկան։',
save: 'Պահպանել ապրանքը',
groupGeneral: 'Հիմնական',
groupMedia: 'Մեդիա',
groupPricing: 'Գին',
groupInventory: 'Պահեստ',
groupCategories: 'Կատեգորիաներ',
groupAttributes: 'Հատկանիշներ',
groupSeo: 'SEO',
groupVisibility: 'Տեսանելիություն',
groupAdvanced: 'Լրացուցիչ',
healthImages: 'Պատկերներ',
healthSeo: 'SEO',
healthPrice: 'Գին',
healthCategory: 'Կատեգորիա',
healthDescription: 'Նկարագրություն',
healthInventory: 'Պահեստ',
totalProducts: 'Ընդհանուր ապրանքներ',
publishedCount: 'Հրապարակված',
draftsCount: 'Սևագրեր',
outOfStockCount: 'Առկա չէ',
hiddenCount: 'Թաքցված',
missingImagesCount: 'Առանց պատկերի',
missingSeoCount: 'Առանց SEO-ի',
lowQualityCount: 'Ցածր որակի',
recentlyEdited: 'Վերջերս խմբագրված',
recommendedNext: 'Հաջորդ առաջարկվող քայլը',
openAction: 'Բացել',
recommendAddImages: 'Ապրանքը լուսանկար չունի․ ավելացրեք առնվազն մեկ պատկեր։',
recommendAddSeo: 'Ապրանքը չունի որոնման վերնագիր կամ նկարագրություն․ ավելացրեք դրանք։',
recommendRestock: 'Ապրանքը սպառված է․ համալրեք պաշարը կամ թաքցրեք այն։',
recommendNone: 'Հիանալի է․ կատալոգը լավ վիճակում է։',
},
adminUsers: {
emptyTitle: 'Օգտատերեր չեն գտնվել',

View File

@@ -1220,6 +1220,141 @@ export const ru: Translations = {
adminProducts: {
emptyTitle: 'Товары не найдены',
emptyDescription: 'Измените фильтры или создайте новый товар.',
emptyGuide: 'Хорошему товару нужны понятное название, минимум одно фото, цена и короткое описание — этого достаточно для публикации. Остальное можно добавить позже.',
search: 'Поиск товаров…',
category: 'Категория',
allCategories: 'Все категории',
visibility: 'Видимость',
allVisibility: 'Любая видимость',
visible: 'Видимый',
hidden: 'Скрытый',
stockStatus: 'Статус наличия',
allStock: 'Любой остаток',
inStock: 'В наличии',
lowStock: 'Мало на складе',
outOfStock: 'Нет в наличии',
sortTitle: 'Имя',
sortPrice: 'Цена',
sortPriority: 'Приоритет',
sortStock: 'Остаток',
sortUpdated: 'Последнее изменение',
showArchived: 'Показать архивные',
infiniteScroll: 'Бесконечная прокрутка',
viewMode: 'Вид',
viewTable: 'Таблица',
viewGrid: 'Сетка',
density: 'Плотность',
densityComfortable: 'Комфортная',
densityCompact: 'Компактная',
columns: 'Столбцы',
column_sku: 'Артикул',
column_brand: 'Бренд',
column_price: 'Цена',
column_stock: 'Остаток',
column_visibility: 'Видимость',
column_updated: 'Последнее изменение',
create: 'Создать товар',
edit: 'Редактировать товар',
duplicate: 'Дублировать',
selectedCount: 'выбрано',
bulkShow: 'Опубликовать',
bulkHide: 'Скрыть',
bulkAssignCategory: 'Назначить категорию',
bulkAssignTags: 'Назначить теги',
bulkDuplicateAction: 'Дублировать',
bulkExportAction: 'Экспорт',
bulkDelete: 'Удалить',
tagsPlaceholder: 'например, лето, скидка',
name: 'Название',
brand: 'Бренд',
healthColumn: 'Качество',
actions: 'Действия',
archive: 'В архив',
delete: 'Удалить',
items: 'товаров',
loadMore: 'Загрузить ещё',
noImage: 'Нет изображения',
slug: 'URL-адрес',
barcode: 'Штрихкод',
priority: 'Приоритет',
shortDescription: 'Краткое описание',
translations: 'Переводы',
primaryImage: 'Главное изображение',
primaryImageHint: 'Основное фото, показываемое в списках и результатах поиска.',
gallery: 'Галерея',
galleryHint: 'Дополнительные фото на странице товара. Порядок меняется стрелками.',
moveLeft: 'Переместить раньше',
moveRight: 'Переместить позже',
videos: 'Видео (по одной ссылке на строку)',
discount: 'Скидка %',
currency: 'Валюта',
stockQuantity: 'Количество на складе',
stockQuantityHint: 'Сколько единиц доступно для продажи прямо сейчас.',
availabilityStatus: 'Доступность',
availabilityStatusHint: 'Что видит покупатель: в наличии, заканчивается или нет в наличии.',
availabilityNote: 'Примечание о доступности',
availabilityNoteHint: 'Дополнительная информация для покупателя, например «Доставка за 3 дня».',
reservedNote: 'Резерв пока не учитывается отдельно — здесь показано общее доступное количество.',
relatedProducts: 'Похожие товары',
specifications: 'Характеристики',
attributes: 'Атрибуты',
addRow: 'Добавить строку',
removeRow: 'Удалить',
rowKey: 'Название',
rowValue: 'Значение',
addVariant: 'Добавить вариант',
variantName: 'Название варианта',
variantsHint: 'Каждый вариант — это отдельный покупаемый вариант товара (например, размер или цвет) со своей ценой и остатком.',
seoExplain: 'Это то, что видно в результатах поиска — понятные заголовок и описание помогают найти товар.',
searchTitle: 'Заголовок для поиска',
searchTitleHint: 'Заголовок, показываемый в результатах поиска. Делайте его коротким.',
searchDescription: 'Описание для поиска',
searchDescriptionHint: 'Краткое описание под заголовком в результатах поиска.',
seoMissingTitle: 'Добавьте заголовок для поиска, чтобы товар можно было найти.',
seoMissingDescription: 'Добавьте описание для поиска, чтобы улучшить отображение товара.',
keywords: 'Ключевые слова',
featured: 'Рекомендуемый',
recommended: 'В подборке',
new: 'Новинка',
bestseller: 'Хит продаж',
badges: 'Значки (через запятую)',
htmlDescription: 'Подробное описание',
customer: 'Отзывы покупателей',
reviewsReadonly: 'Отзывы',
noReviews: 'Пока нет отзывов.',
questionsReadonly: 'Вопросы',
noQuestions: 'Пока нет вопросов.',
save: 'Сохранить товар',
groupGeneral: 'Основное',
groupMedia: 'Медиа',
groupPricing: 'Цена',
groupInventory: 'Склад',
groupCategories: 'Категории',
groupAttributes: 'Атрибуты',
groupSeo: 'SEO',
groupVisibility: 'Видимость',
groupAdvanced: 'Дополнительно',
healthImages: 'Изображения',
healthSeo: 'SEO',
healthPrice: 'Цена',
healthCategory: 'Категория',
healthDescription: 'Описание',
healthInventory: 'Склад',
totalProducts: 'Всего товаров',
publishedCount: 'Опубликовано',
draftsCount: 'Черновики',
outOfStockCount: 'Нет в наличии',
hiddenCount: 'Скрыто',
missingImagesCount: 'Без изображений',
missingSeoCount: 'Без SEO',
lowQualityCount: 'Низкое качество',
recentlyEdited: 'Недавно изменённые',
recommendedNext: 'Рекомендуемый следующий шаг',
openAction: 'Открыть',
recommendAddImages: 'У товара нет фото — добавьте хотя бы одно изображение.',
recommendAddSeo: 'У товара нет заголовка или описания для поиска — добавьте их.',
recommendRestock: 'Товар закончился на складе — пополните запас или скройте его.',
recommendNone: 'Отлично — каталог в хорошем состоянии.',
},
adminUsers: {
emptyTitle: 'Пользователи не найдены',

View File

@@ -1232,6 +1232,141 @@ export interface Translations {
adminProducts: {
emptyTitle: string;
emptyDescription: string;
emptyGuide: string;
search: string;
category: string;
allCategories: string;
visibility: string;
allVisibility: string;
visible: string;
hidden: string;
stockStatus: string;
allStock: string;
inStock: string;
lowStock: string;
outOfStock: string;
sortTitle: string;
sortPrice: string;
sortPriority: string;
sortStock: string;
sortUpdated: string;
showArchived: string;
infiniteScroll: string;
viewMode: string;
viewTable: string;
viewGrid: string;
density: string;
densityComfortable: string;
densityCompact: string;
columns: string;
column_sku: string;
column_brand: string;
column_price: string;
column_stock: string;
column_visibility: string;
column_updated: string;
create: string;
edit: string;
duplicate: string;
selectedCount: string;
bulkShow: string;
bulkHide: string;
bulkAssignCategory: string;
bulkAssignTags: string;
bulkDuplicateAction: string;
bulkExportAction: string;
bulkDelete: string;
tagsPlaceholder: string;
name: string;
brand: string;
healthColumn: string;
actions: string;
archive: string;
delete: string;
items: string;
loadMore: string;
noImage: string;
slug: string;
barcode: string;
priority: string;
shortDescription: string;
translations: string;
primaryImage: string;
primaryImageHint: string;
gallery: string;
galleryHint: string;
moveLeft: string;
moveRight: string;
videos: string;
discount: string;
currency: string;
stockQuantity: string;
stockQuantityHint: string;
availabilityStatus: string;
availabilityStatusHint: string;
availabilityNote: string;
availabilityNoteHint: string;
reservedNote: string;
relatedProducts: string;
specifications: string;
attributes: string;
addRow: string;
removeRow: string;
rowKey: string;
rowValue: string;
addVariant: string;
variantName: string;
variantsHint: string;
seoExplain: string;
searchTitle: string;
searchTitleHint: string;
searchDescription: string;
searchDescriptionHint: string;
seoMissingTitle: string;
seoMissingDescription: string;
keywords: string;
featured: string;
recommended: string;
new: string;
bestseller: string;
badges: string;
htmlDescription: string;
customer: string;
reviewsReadonly: string;
noReviews: string;
questionsReadonly: string;
noQuestions: string;
save: string;
groupGeneral: string;
groupMedia: string;
groupPricing: string;
groupInventory: string;
groupCategories: string;
groupAttributes: string;
groupSeo: string;
groupVisibility: string;
groupAdvanced: string;
healthImages: string;
healthSeo: string;
healthPrice: string;
healthCategory: string;
healthDescription: string;
healthInventory: string;
totalProducts: string;
publishedCount: string;
draftsCount: string;
outOfStockCount: string;
hiddenCount: string;
missingImagesCount: string;
missingSeoCount: string;
lowQualityCount: string;
recentlyEdited: string;
recommendedNext: string;
openAction: string;
recommendAddImages: string;
recommendAddSeo: string;
recommendRestock: string;
recommendNone: string;
};
adminUsers: {
emptyTitle: string;