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 { StoreHeaderComponent } from './store-header/store-header.component';
|
||||||
import { CartComponent, CartItemMock } from '../../../shared/components/cart/cart.component';
|
import { CartComponent, CartItemMock } from '../../../shared/components/cart/cart.component';
|
||||||
import { ButtonComponent } from '../../../shared/components/button/button.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 { AuthService } from '../../services/auth/auth.service';
|
||||||
import { findMenu } from '../../services/menu.utils';
|
import { findMenu } from '../../services/menu.utils';
|
||||||
import { CheckoutService } from '../../services/checkout.service';
|
import { CheckoutService } from '../../services/checkout.service';
|
||||||
@@ -80,7 +80,7 @@ export class StoreLayoutComponent implements OnInit {
|
|||||||
if (selectedVariant) {
|
if (selectedVariant) {
|
||||||
attributes = Object.entries(selectedVariant.values).map(([label, value]) => ({
|
attributes = Object.entries(selectedVariant.values).map(([label, value]) => ({
|
||||||
label: this.formatAttributeLabel(label),
|
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);
|
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 currentYear = new Date().getFullYear();
|
||||||
protected readonly tenant = this.tenantService.tenant;
|
protected readonly tenant = this.tenantService.tenant;
|
||||||
protected readonly user = this.authService.user;
|
protected readonly user = this.authService.user;
|
||||||
|
|||||||
@@ -4,11 +4,21 @@ export interface CartItemProduct {
|
|||||||
variants?: CartItemVariant[];
|
variants?: CartItemVariant[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface CartItemVariantOption {
|
||||||
|
value: string;
|
||||||
|
label: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CartItemVariantValue =
|
||||||
|
| string
|
||||||
|
| CartItemVariantOption
|
||||||
|
| Array<string | CartItemVariantOption>;
|
||||||
|
|
||||||
export interface CartItemVariant {
|
export interface CartItemVariant {
|
||||||
id: number;
|
id: number;
|
||||||
precio: string;
|
precio: string;
|
||||||
stock_tecnico: number | null;
|
stock_tecnico: number | null;
|
||||||
values: Record<string, string | string[]>;
|
values: Record<string, CartItemVariantValue>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CartItem {
|
export interface CartItem {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
class="form-select variant-selector__select"
|
class="form-select variant-selector__select"
|
||||||
[attr.aria-label]="selector.label"
|
[attr.aria-label]="selector.label"
|
||||||
[disabled]="disabled()"
|
[disabled]="disabled()"
|
||||||
|
[compareWith]="compareValues"
|
||||||
[ngModel]="selectedValues()[selector.key]"
|
[ngModel]="selectedValues()[selector.key]"
|
||||||
(ngModelChange)="onValueChange(selector.key, $event)"
|
(ngModelChange)="onValueChange(selector.key, $event)"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -38,4 +38,45 @@ describe('VariantSelectorComponent', () => {
|
|||||||
servicio: 'Cena',
|
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';
|
} from '@angular/core';
|
||||||
import { FormsModule } from '@angular/forms';
|
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 {
|
export interface VariantSelectorVariant {
|
||||||
value: unknown;
|
value: unknown;
|
||||||
@@ -44,6 +50,10 @@ export class VariantSelectorComponent {
|
|||||||
readonly compact = input(false);
|
readonly compact = input(false);
|
||||||
|
|
||||||
protected readonly selectedValues = signal<Record<string, VariantAttributeValue>>({});
|
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(() =>
|
protected readonly attributeKeys = computed(() =>
|
||||||
Array.from(new Set(this.variants().flatMap((variant) => Object.keys(variant.values)))),
|
Array.from(new Set(this.variants().flatMap((variant) => Object.keys(variant.values)))),
|
||||||
);
|
);
|
||||||
@@ -132,7 +142,7 @@ export class VariantSelectorComponent {
|
|||||||
if (!options.has(optionKey)) {
|
if (!options.has(optionKey)) {
|
||||||
options.set(optionKey, {
|
options.set(optionKey, {
|
||||||
key: optionKey,
|
key: optionKey,
|
||||||
label: Array.isArray(value) ? value.join(', ') : value,
|
label: this.valueLabel(value),
|
||||||
value,
|
value,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -151,7 +161,20 @@ export class VariantSelectorComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private valueKey(value: VariantAttributeValue): string {
|
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 {
|
private formatVariantLabel(key: string): string {
|
||||||
|
|||||||
Reference in New Issue
Block a user