feat: enhance variant handling in selector and layout components for improved display and selection
This commit is contained in:
@@ -6,7 +6,7 @@ import { StoreFooterComponent, StoreFooterSection } from './store-footer/store-f
|
||||
import { StoreHeaderComponent } from './store-header/store-header.component';
|
||||
import { CartComponent, CartItemMock } from '../../../shared/components/cart/cart.component';
|
||||
import { ButtonComponent } from '../../../shared/components/button/button.component';
|
||||
import { CartItem } from '../../services/cart/cart.interface';
|
||||
import { CartItem, CartItemVariantValue } from '../../services/cart/cart.interface';
|
||||
import { AuthService } from '../../services/auth/auth.service';
|
||||
import { findMenu } from '../../services/menu.utils';
|
||||
import { CheckoutService } from '../../services/checkout.service';
|
||||
@@ -80,7 +80,7 @@ export class StoreLayoutComponent implements OnInit {
|
||||
if (selectedVariant) {
|
||||
attributes = Object.entries(selectedVariant.values).map(([label, value]) => ({
|
||||
label: this.formatAttributeLabel(label),
|
||||
value: Array.isArray(value) ? value.join(', ') : value,
|
||||
value: this.formatAttributeValue(value),
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -103,6 +103,11 @@ export class StoreLayoutComponent implements OnInit {
|
||||
return label.charAt(0).toUpperCase() + label.slice(1);
|
||||
}
|
||||
|
||||
private formatAttributeValue(value: CartItemVariantValue): string {
|
||||
const values = Array.isArray(value) ? value : [value];
|
||||
return values.map((item) => (typeof item === 'string' ? item : item.label)).join(', ');
|
||||
}
|
||||
|
||||
protected readonly currentYear = new Date().getFullYear();
|
||||
protected readonly tenant = this.tenantService.tenant;
|
||||
protected readonly user = this.authService.user;
|
||||
|
||||
@@ -4,11 +4,21 @@ export interface CartItemProduct {
|
||||
variants?: CartItemVariant[];
|
||||
}
|
||||
|
||||
export interface CartItemVariantOption {
|
||||
value: string;
|
||||
label: string;
|
||||
}
|
||||
|
||||
export type CartItemVariantValue =
|
||||
| string
|
||||
| CartItemVariantOption
|
||||
| Array<string | CartItemVariantOption>;
|
||||
|
||||
export interface CartItemVariant {
|
||||
id: number;
|
||||
precio: string;
|
||||
stock_tecnico: number | null;
|
||||
values: Record<string, string | string[]>;
|
||||
values: Record<string, CartItemVariantValue>;
|
||||
}
|
||||
|
||||
export interface CartItem {
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
class="form-select variant-selector__select"
|
||||
[attr.aria-label]="selector.label"
|
||||
[disabled]="disabled()"
|
||||
[compareWith]="compareValues"
|
||||
[ngModel]="selectedValues()[selector.key]"
|
||||
(ngModelChange)="onValueChange(selector.key, $event)"
|
||||
>
|
||||
|
||||
@@ -38,4 +38,45 @@ describe('VariantSelectorComponent', () => {
|
||||
servicio: 'Cena',
|
||||
});
|
||||
});
|
||||
|
||||
it('renders backend option labels and compares equivalent option objects by value', async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [VariantSelectorComponent],
|
||||
}).compileComponents();
|
||||
const fixture = TestBed.createComponent(VariantSelectorComponent);
|
||||
fixture.componentRef.setInput('variants', [
|
||||
{
|
||||
value: 1,
|
||||
values: {
|
||||
color: { value: 'red', label: 'Rojo' },
|
||||
talle: { value: 's', label: 'Small' },
|
||||
},
|
||||
},
|
||||
{
|
||||
value: 2,
|
||||
values: {
|
||||
color: { value: 'red', label: 'Rojo' },
|
||||
talle: { value: 'm', label: 'Medium' },
|
||||
},
|
||||
},
|
||||
]);
|
||||
fixture.componentRef.setInput('selectedVariant', 2);
|
||||
fixture.detectChanges();
|
||||
|
||||
const selects = Array.from(
|
||||
fixture.nativeElement.querySelectorAll('select'),
|
||||
) as HTMLSelectElement[];
|
||||
|
||||
expect(selects).toHaveLength(2);
|
||||
expect(selects[0].selectedOptions[0]?.textContent).toBe('Rojo');
|
||||
expect(selects[1].selectedOptions[0]?.textContent).toBe('Medium');
|
||||
expect(fixture.nativeElement.textContent).not.toContain('[object Object]');
|
||||
|
||||
(fixture.componentInstance as any).onValueChange('talle', {
|
||||
value: 's',
|
||||
label: 'Small',
|
||||
});
|
||||
|
||||
expect(fixture.componentInstance.selectedVariant()).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -10,7 +10,13 @@ import {
|
||||
} from '@angular/core';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
|
||||
export type VariantAttributeValue = string | string[];
|
||||
export interface VariantAttributeOption {
|
||||
value: string;
|
||||
label: string;
|
||||
}
|
||||
|
||||
export type VariantAttributeScalar = string | VariantAttributeOption;
|
||||
export type VariantAttributeValue = VariantAttributeScalar | VariantAttributeScalar[];
|
||||
|
||||
export interface VariantSelectorVariant {
|
||||
value: unknown;
|
||||
@@ -44,6 +50,10 @@ export class VariantSelectorComponent {
|
||||
readonly compact = input(false);
|
||||
|
||||
protected readonly selectedValues = signal<Record<string, VariantAttributeValue>>({});
|
||||
protected readonly compareValues = (
|
||||
left: VariantAttributeValue | null,
|
||||
right: VariantAttributeValue | null,
|
||||
): boolean => left !== null && right !== null && this.sameValue(left, right);
|
||||
protected readonly attributeKeys = computed(() =>
|
||||
Array.from(new Set(this.variants().flatMap((variant) => Object.keys(variant.values)))),
|
||||
);
|
||||
@@ -132,7 +142,7 @@ export class VariantSelectorComponent {
|
||||
if (!options.has(optionKey)) {
|
||||
options.set(optionKey, {
|
||||
key: optionKey,
|
||||
label: Array.isArray(value) ? value.join(', ') : value,
|
||||
label: this.valueLabel(value),
|
||||
value,
|
||||
});
|
||||
}
|
||||
@@ -151,7 +161,20 @@ export class VariantSelectorComponent {
|
||||
}
|
||||
|
||||
private valueKey(value: VariantAttributeValue): string {
|
||||
return JSON.stringify(value);
|
||||
const comparableValue = Array.isArray(value)
|
||||
? value.map((item) => this.scalarValue(item))
|
||||
: this.scalarValue(value);
|
||||
|
||||
return JSON.stringify(comparableValue);
|
||||
}
|
||||
|
||||
private valueLabel(value: VariantAttributeValue): string {
|
||||
const values = Array.isArray(value) ? value : [value];
|
||||
return values.map((item) => (typeof item === 'string' ? item : item.label)).join(', ');
|
||||
}
|
||||
|
||||
private scalarValue(value: VariantAttributeScalar): string {
|
||||
return typeof value === 'string' ? value : value.value;
|
||||
}
|
||||
|
||||
private formatVariantLabel(key: string): string {
|
||||
|
||||
Reference in New Issue
Block a user