From f428a88ba8ddc1e5dd8c4110292ca95c71f13fd1 Mon Sep 17 00:00:00 2001 From: ncoronel Date: Wed, 12 Aug 2026 16:12:47 -0300 Subject: [PATCH] feat(product-attribute-selector): add show_in_selector property to filter attributes in selector --- .../services/catalog/catalog.interface.ts | 1 + .../product-attribute-selector.component.html | 2 +- ...oduct-attribute-selector.component.spec.ts | 39 +++++++++++++++++++ .../product-attribute-selector.component.ts | 10 +++-- .../product-detail-page.component.ts | 4 +- 5 files changed, 51 insertions(+), 5 deletions(-) diff --git a/src/app/core/services/catalog/catalog.interface.ts b/src/app/core/services/catalog/catalog.interface.ts index 4bf3e55..ae5c276 100644 --- a/src/app/core/services/catalog/catalog.interface.ts +++ b/src/app/core/services/catalog/catalog.interface.ts @@ -41,6 +41,7 @@ export interface ProductAttribute { nombre: string; is_required: boolean; allow_multi_select?: boolean; + show_in_selector?: boolean; metadata_schema: Record | null; type: ProductAttributeType; options: ProductAttributeOption[]; diff --git a/src/app/features/store/components/product-attribute-selector/product-attribute-selector.component.html b/src/app/features/store/components/product-attribute-selector/product-attribute-selector.component.html index 5701661..39d5dc2 100644 --- a/src/app/features/store/components/product-attribute-selector/product-attribute-selector.component.html +++ b/src/app/features/store/components/product-attribute-selector/product-attribute-selector.component.html @@ -1,5 +1,5 @@
- @for (attribute of attributes(); track attribute.codigo) { + @for (attribute of visibleAttributes(); track attribute.codigo) {
{{ attribute.nombre }}: diff --git a/src/app/features/store/components/product-attribute-selector/product-attribute-selector.component.spec.ts b/src/app/features/store/components/product-attribute-selector/product-attribute-selector.component.spec.ts index 7143490..4a7910c 100644 --- a/src/app/features/store/components/product-attribute-selector/product-attribute-selector.component.spec.ts +++ b/src/app/features/store/components/product-attribute-selector/product-attribute-selector.component.spec.ts @@ -110,4 +110,43 @@ describe('ProductAttributeSelectorComponent', () => { expect(buttons[1].getAttribute('aria-pressed')).toBe('true'); expect(emittedIds).toContain(3); }); + + it('hides configured attributes and matches variants using only visible selections', () => { + const fixture = TestBed.createComponent(ProductAttributeSelectorComponent); + const emittedIds: Array = []; + fixture.componentInstance.variantChange.subscribe((variant) => + emittedIds.push(variant?.id ?? null), + ); + fixture.componentRef.setInput('attributes', [ + sizeAttribute, + { + id: 2, + codigo: 'internal_type', + nombre: 'Internal type', + is_required: true, + show_in_selector: false, + metadata_schema: null, + type: 'select', + options: [ + { id: 30, value: 'adult', label: 'Adult', sort_order: 0, metadata: null }, + { id: 40, value: 'child', label: 'Child', sort_order: 1, metadata: null }, + ], + } satisfies ProductAttribute, + ]); + fixture.componentRef.setInput('inventoryPolicy', 'unlimited'); + fixture.componentRef.setInput('variants', [ + { id: 1, stock_tecnico: null, values: { size: 'S', internal_type: 'adult' } }, + { id: 2, stock_tecnico: null, values: { size: 'M', internal_type: 'child' } }, + ]); + fixture.detectChanges(); + + const labels = fixture.nativeElement.querySelectorAll('.attribute-selector__label'); + const buttons = fixture.nativeElement.querySelectorAll('.attribute-selector__text-option'); + + expect(labels).toHaveLength(1); + expect(labels[0].textContent).toContain('Size'); + buttons[1].click(); + fixture.detectChanges(); + expect(emittedIds).toContain(2); + }); }); diff --git a/src/app/features/store/components/product-attribute-selector/product-attribute-selector.component.ts b/src/app/features/store/components/product-attribute-selector/product-attribute-selector.component.ts index 3df839d..d856169 100644 --- a/src/app/features/store/components/product-attribute-selector/product-attribute-selector.component.ts +++ b/src/app/features/store/components/product-attribute-selector/product-attribute-selector.component.ts @@ -32,12 +32,16 @@ export class ProductAttributeSelectorComponent { public variantChange = output(); + protected readonly visibleAttributes = computed(() => + this.attributes().filter((attribute) => attribute.show_in_selector !== false), + ); + protected readonly selectedAttributeOptions = signal>({}); protected readonly availableOptions = computed(() => { const selections = this.selectedAttributeOptions(); const variants = this.variants(); - const attributes = this.attributes(); + const attributes = this.visibleAttributes(); const availability: Record> = {}; @@ -103,7 +107,7 @@ export class ProductAttributeSelectorComponent { constructor() { effect(() => { - const attributes = this.attributes(); + const attributes = this.visibleAttributes(); const defaultVariant = this.selectedVariant(); untracked(() => { @@ -114,7 +118,7 @@ export class ProductAttributeSelectorComponent { effect(() => { const selections = this.selectedAttributeOptions(); const variants = this.variants(); - const attributes = this.attributes(); + const attributes = this.visibleAttributes(); untracked(() => { this.emitMatchingVariant(selections, variants, attributes); diff --git a/src/app/features/store/pages/product-detail-page/product-detail-page.component.ts b/src/app/features/store/pages/product-detail-page/product-detail-page.component.ts index 1e73d8a..8a5e96b 100644 --- a/src/app/features/store/pages/product-detail-page/product-detail-page.component.ts +++ b/src/app/features/store/pages/product-detail-page/product-detail-page.component.ts @@ -124,7 +124,9 @@ export class ProductDetailPageComponent implements OnInit, OnDestroy { protected readonly descriptionMaxHeight = signal(0); protected readonly descriptionHasOverflow = signal(false); protected readonly renderableAttributes = computed(() => - (this.product()?.attributes ?? []).filter((attribute) => attribute.options.length > 0), + (this.product()?.attributes ?? []).filter( + (attribute) => attribute.show_in_selector !== false && attribute.options.length > 0, + ), ); protected readonly hasRenderableAttributes = computed( () => this.renderableAttributes().length > 0,