diff --git a/app/components/catalog/CatalogConfigurator.vue b/app/components/catalog/CatalogConfigurator.vue index 185f64d..d87dcdb 100644 --- a/app/components/catalog/CatalogConfigurator.vue +++ b/app/components/catalog/CatalogConfigurator.vue @@ -10,11 +10,13 @@ type ParamValue = number | string; type ParsedProduct = ProductNode & { productTypeLabel: string; quantityPerBoxOptions: string[]; + normalizedTags: string[]; }; type ProductGroup = { key: string; typeLabel: string; + tags: string[]; products: ParsedProduct[]; }; @@ -89,6 +91,7 @@ function hydrateProduct(product: ProductNode): ParsedProduct { ...product, productTypeLabel: normalizeText(product.productType) || 'Без типа', quantityPerBoxOptions: splitBoxValues(product.quantityPerBox), + normalizedTags: product.tags.map((tag) => normalizeText(tag)).filter(Boolean).sort((a, b) => a.localeCompare(b, 'ru')), }; } @@ -140,6 +143,7 @@ const parsedProducts = computed(() => { String(product.thicknessMicron ?? ''), normalizeText(product.sleeveBrand), normalizeText(product.quantityPerBox), + ...product.normalizedTags, ].some((part) => part.toLowerCase().includes(query)); }) .sort(compareProducts); @@ -149,21 +153,43 @@ const productGroups = computed(() => { const map = new Map(); for (const product of parsedProducts.value) { - const existing = map.get(product.productTypeLabel); + const tagsKey = product.normalizedTags.join('|'); + const groupKey = `${product.productTypeLabel}::${tagsKey}`; + const existing = map.get(groupKey); if (existing) { existing.push(product); } else { - map.set(product.productTypeLabel, [product]); + map.set(groupKey, [product]); } } return [...map.entries()] - .sort((a, b) => a[0].localeCompare(b[0], 'ru')) - .map(([typeLabel, products]) => ({ - key: typeLabel.toLowerCase().replaceAll(/\s+/g, '-'), - typeLabel, + .sort((a, b) => { + const firstProduct = a[1][0]; + const secondProduct = b[1][0]; + const byType = firstProduct.productTypeLabel.localeCompare(secondProduct.productTypeLabel, 'ru'); + if (byType !== 0) { + return byType; + } + + return firstProduct.normalizedTags.join('|').localeCompare(secondProduct.normalizedTags.join('|'), 'ru'); + }) + .map(([groupSignature, products]) => { + const firstProduct = products[0]; + const key = groupSignature + .toLowerCase() + .replaceAll(/[^a-z0-9а-яё|]+/gi, '-') + .replaceAll('|', '--') + .replaceAll(/-+/g, '-') + .replaceAll(/^-|-$/g, ''); + + return { + key, + typeLabel: firstProduct.productTypeLabel, + tags: firstProduct.normalizedTags, products: [...products].sort(compareProducts), - })); + }; + }); }); function sortParamValues(values: ParamValue[]) { @@ -461,6 +487,16 @@ function decrementSelected(group: ProductGroup) {

{{ group.typeLabel }}

+ +
+ + {{ tag }} + +
@@ -585,6 +621,7 @@ function decrementSelected(group: ProductGroup) { Толщина Втулка Короб + Теги Действие @@ -596,6 +633,18 @@ function decrementSelected(group: ProductGroup) { {{ product.thicknessMicron ?? '—' }} {{ product.sleeveBrand ?? '—' }} {{ product.quantityPerBox ?? '—' }} + +
+ + {{ tag }} + +
+ +