feat(product-attribute-selector): add show_in_selector property to filter attributes in selector

This commit is contained in:
2026-08-12 16:12:47 -03:00
parent caa1fddcc1
commit f428a88ba8
5 changed files with 51 additions and 5 deletions

View File

@@ -41,6 +41,7 @@ export interface ProductAttribute {
nombre: string;
is_required: boolean;
allow_multi_select?: boolean;
show_in_selector?: boolean;
metadata_schema: Record<string, unknown> | null;
type: ProductAttributeType;
options: ProductAttributeOption[];

View File

@@ -1,5 +1,5 @@
<div class="attribute-selector">
@for (attribute of attributes(); track attribute.codigo) {
@for (attribute of visibleAttributes(); track attribute.codigo) {
<div class="attribute-selector__row">
<span class="attribute-selector__label">{{ attribute.nombre }}:</span>

View File

@@ -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<number | null> = [];
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);
});
});

View File

@@ -32,12 +32,16 @@ export class ProductAttributeSelectorComponent {
public variantChange = output<CatalogItemVariant | null>();
protected readonly visibleAttributes = computed(() =>
this.attributes().filter((attribute) => attribute.show_in_selector !== false),
);
protected readonly selectedAttributeOptions = signal<Record<string, number[]>>({});
protected readonly availableOptions = computed(() => {
const selections = this.selectedAttributeOptions();
const variants = this.variants();
const attributes = this.attributes();
const attributes = this.visibleAttributes();
const availability: Record<string, Record<number, boolean>> = {};
@@ -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);

View File

@@ -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,