Show product tags in catalog
This commit is contained in:
@@ -10,11 +10,13 @@ type ParamValue = number | string;
|
|||||||
type ParsedProduct = ProductNode & {
|
type ParsedProduct = ProductNode & {
|
||||||
productTypeLabel: string;
|
productTypeLabel: string;
|
||||||
quantityPerBoxOptions: string[];
|
quantityPerBoxOptions: string[];
|
||||||
|
normalizedTags: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
type ProductGroup = {
|
type ProductGroup = {
|
||||||
key: string;
|
key: string;
|
||||||
typeLabel: string;
|
typeLabel: string;
|
||||||
|
tags: string[];
|
||||||
products: ParsedProduct[];
|
products: ParsedProduct[];
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -89,6 +91,7 @@ function hydrateProduct(product: ProductNode): ParsedProduct {
|
|||||||
...product,
|
...product,
|
||||||
productTypeLabel: normalizeText(product.productType) || 'Без типа',
|
productTypeLabel: normalizeText(product.productType) || 'Без типа',
|
||||||
quantityPerBoxOptions: splitBoxValues(product.quantityPerBox),
|
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<ParsedProduct[]>(() => {
|
|||||||
String(product.thicknessMicron ?? ''),
|
String(product.thicknessMicron ?? ''),
|
||||||
normalizeText(product.sleeveBrand),
|
normalizeText(product.sleeveBrand),
|
||||||
normalizeText(product.quantityPerBox),
|
normalizeText(product.quantityPerBox),
|
||||||
|
...product.normalizedTags,
|
||||||
].some((part) => part.toLowerCase().includes(query));
|
].some((part) => part.toLowerCase().includes(query));
|
||||||
})
|
})
|
||||||
.sort(compareProducts);
|
.sort(compareProducts);
|
||||||
@@ -149,21 +153,43 @@ const productGroups = computed<ProductGroup[]>(() => {
|
|||||||
const map = new Map<string, ParsedProduct[]>();
|
const map = new Map<string, ParsedProduct[]>();
|
||||||
|
|
||||||
for (const product of parsedProducts.value) {
|
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) {
|
if (existing) {
|
||||||
existing.push(product);
|
existing.push(product);
|
||||||
} else {
|
} else {
|
||||||
map.set(product.productTypeLabel, [product]);
|
map.set(groupKey, [product]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return [...map.entries()]
|
return [...map.entries()]
|
||||||
.sort((a, b) => a[0].localeCompare(b[0], 'ru'))
|
.sort((a, b) => {
|
||||||
.map(([typeLabel, products]) => ({
|
const firstProduct = a[1][0];
|
||||||
key: typeLabel.toLowerCase().replaceAll(/\s+/g, '-'),
|
const secondProduct = b[1][0];
|
||||||
typeLabel,
|
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),
|
products: [...products].sort(compareProducts),
|
||||||
}));
|
};
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function sortParamValues(values: ParamValue[]) {
|
function sortParamValues(values: ParamValue[]) {
|
||||||
@@ -461,6 +487,16 @@ function decrementSelected(group: ProductGroup) {
|
|||||||
<div class="p-4 md:p-5 xl:col-span-4">
|
<div class="p-4 md:p-5 xl:col-span-4">
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<h2 class="text-2xl font-bold text-[#163624]">{{ group.typeLabel }}</h2>
|
<h2 class="text-2xl font-bold text-[#163624]">{{ group.typeLabel }}</h2>
|
||||||
|
|
||||||
|
<div v-if="group.tags.length" class="mt-3 flex flex-wrap gap-2">
|
||||||
|
<span
|
||||||
|
v-for="tag in group.tags"
|
||||||
|
:key="`${group.key}-${tag}`"
|
||||||
|
class="rounded-full bg-[#eef8f1] px-3 py-1 text-xs font-semibold uppercase tracking-[0.06em] text-[#15613d]"
|
||||||
|
>
|
||||||
|
{{ tag }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid gap-8 md:grid-cols-2">
|
<div class="grid gap-8 md:grid-cols-2">
|
||||||
@@ -585,6 +621,7 @@ function decrementSelected(group: ProductGroup) {
|
|||||||
<th class="border-b border-base-300">Толщина</th>
|
<th class="border-b border-base-300">Толщина</th>
|
||||||
<th class="border-b border-base-300">Втулка</th>
|
<th class="border-b border-base-300">Втулка</th>
|
||||||
<th class="border-b border-base-300">Короб</th>
|
<th class="border-b border-base-300">Короб</th>
|
||||||
|
<th class="border-b border-base-300">Теги</th>
|
||||||
<th class="border-b border-base-300 text-right">Действие</th>
|
<th class="border-b border-base-300 text-right">Действие</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@@ -596,6 +633,18 @@ function decrementSelected(group: ProductGroup) {
|
|||||||
<td class="border-b border-base-200">{{ product.thicknessMicron ?? '—' }}</td>
|
<td class="border-b border-base-200">{{ product.thicknessMicron ?? '—' }}</td>
|
||||||
<td class="border-b border-base-200">{{ product.sleeveBrand ?? '—' }}</td>
|
<td class="border-b border-base-200">{{ product.sleeveBrand ?? '—' }}</td>
|
||||||
<td class="border-b border-base-200">{{ product.quantityPerBox ?? '—' }}</td>
|
<td class="border-b border-base-200">{{ product.quantityPerBox ?? '—' }}</td>
|
||||||
|
<td class="border-b border-base-200">
|
||||||
|
<div v-if="product.normalizedTags.length" class="flex flex-wrap gap-2">
|
||||||
|
<span
|
||||||
|
v-for="tag in product.normalizedTags"
|
||||||
|
:key="`${product.id}-${tag}`"
|
||||||
|
class="rounded-full bg-[#eef8f1] px-2 py-1 text-[11px] font-semibold uppercase tracking-[0.06em] text-[#15613d]"
|
||||||
|
>
|
||||||
|
{{ tag }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<span v-else>—</span>
|
||||||
|
</td>
|
||||||
<td class="border-b border-base-200 text-right">
|
<td class="border-b border-base-200 text-right">
|
||||||
<button
|
<button
|
||||||
v-if="getQuantity(product.id) === 0"
|
v-if="getQuantity(product.id) === 0"
|
||||||
|
|||||||
@@ -579,6 +579,7 @@ export type Product = {
|
|||||||
quantityPerBox?: Maybe<Scalars['String']['output']>;
|
quantityPerBox?: Maybe<Scalars['String']['output']>;
|
||||||
sku: Scalars['String']['output'];
|
sku: Scalars['String']['output'];
|
||||||
sleeveBrand?: Maybe<Scalars['String']['output']>;
|
sleeveBrand?: Maybe<Scalars['String']['output']>;
|
||||||
|
tags: Array<Scalars['String']['output']>;
|
||||||
thicknessMicron?: Maybe<Scalars['Int']['output']>;
|
thicknessMicron?: Maybe<Scalars['Int']['output']>;
|
||||||
widthMm?: Maybe<Scalars['Int']['output']>;
|
widthMm?: Maybe<Scalars['Int']['output']>;
|
||||||
};
|
};
|
||||||
@@ -899,7 +900,7 @@ export type UpdateCartItemQuantityMutation = { __typename?: 'Mutation', updateCa
|
|||||||
export type ClientProductsQueryVariables = Exact<{ [key: string]: never; }>;
|
export type ClientProductsQueryVariables = Exact<{ [key: string]: never; }>;
|
||||||
|
|
||||||
|
|
||||||
export type ClientProductsQuery = { __typename?: 'Query', clientProducts: Array<{ __typename?: 'Product', id: string, sku: string, name: string, description?: string | null, productType?: string | null, widthMm?: number | null, lengthM?: number | null, thicknessMicron?: number | null, sleeveBrand?: string | null, quantityPerBox?: string | null, isCustomizable: boolean, availableInWarehouses: Array<{ __typename?: 'ProductWarehouseBalance', availableQty: number, warehouse: { __typename?: 'Warehouse', id: string, code: string, name: string } }> }> };
|
export type ClientProductsQuery = { __typename?: 'Query', clientProducts: Array<{ __typename?: 'Product', id: string, sku: string, name: string, description?: string | null, productType?: string | null, widthMm?: number | null, lengthM?: number | null, thicknessMicron?: number | null, sleeveBrand?: string | null, quantityPerBox?: string | null, tags: Array<string>, isCustomizable: boolean, availableInWarehouses: Array<{ __typename?: 'ProductWarehouseBalance', availableQty: number, warehouse: { __typename?: 'Warehouse', id: string, code: string, name: string } }> }> };
|
||||||
|
|
||||||
export type AddBonusTransactionMutationVariables = Exact<{
|
export type AddBonusTransactionMutationVariables = Exact<{
|
||||||
input: AddBonusTransactionInput;
|
input: AddBonusTransactionInput;
|
||||||
@@ -1629,6 +1630,7 @@ export const ClientProductsDocument = gql`
|
|||||||
thicknessMicron
|
thicknessMicron
|
||||||
sleeveBrand
|
sleeveBrand
|
||||||
quantityPerBox
|
quantityPerBox
|
||||||
|
tags
|
||||||
isCustomizable
|
isCustomizable
|
||||||
availableInWarehouses {
|
availableInWarehouses {
|
||||||
availableQty
|
availableQty
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ query ClientProducts {
|
|||||||
thicknessMicron
|
thicknessMicron
|
||||||
sleeveBrand
|
sleeveBrand
|
||||||
quantityPerBox
|
quantityPerBox
|
||||||
|
tags
|
||||||
isCustomizable
|
isCustomizable
|
||||||
availableInWarehouses {
|
availableInWarehouses {
|
||||||
availableQty
|
availableQty
|
||||||
|
|||||||
@@ -242,6 +242,7 @@ type Product {
|
|||||||
thicknessMicron: Int
|
thicknessMicron: Int
|
||||||
sleeveBrand: String
|
sleeveBrand: String
|
||||||
quantityPerBox: String
|
quantityPerBox: String
|
||||||
|
tags: [String!]!
|
||||||
isCustomizable: Boolean!
|
isCustomizable: Boolean!
|
||||||
isActive: Boolean!
|
isActive: Boolean!
|
||||||
availableInWarehouses: [ProductWarehouseBalance!]!
|
availableInWarehouses: [ProductWarehouseBalance!]!
|
||||||
|
|||||||
Reference in New Issue
Block a user