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 96f43aa..ac7b1b3 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 @@ -10,10 +10,12 @@ type="button" class="attribute-selector__swatch" [class.attribute-selector__swatch--selected]="hasSelectedOption(attribute, option)" + [class.attribute-selector__swatch--disabled]="!availableOptions()[attribute.id][option.id]" [style.background-color]="getOptionSwatchColor(option)" [attr.aria-label]="option.label" [attr.aria-pressed]="hasSelectedOption(attribute, option)" [title]="option.label" + [disabled]="!availableOptions()[attribute.id][option.id]" (click)="selectAttributeOption(attribute, option)" > {{ option.label }} @@ -23,7 +25,9 @@ type="button" class="attribute-selector__text-option" [class.attribute-selector__text-option--selected]="hasSelectedOption(attribute, option)" + [class.attribute-selector__text-option--disabled]="!availableOptions()[attribute.id][option.id]" [attr.aria-pressed]="hasSelectedOption(attribute, option)" + [disabled]="!availableOptions()[attribute.id][option.id]" (click)="selectAttributeOption(attribute, option)" > {{ option.label }} diff --git a/src/app/features/store/components/product-attribute-selector/product-attribute-selector.component.scss b/src/app/features/store/components/product-attribute-selector/product-attribute-selector.component.scss index 4d4a831..8812d34 100644 --- a/src/app/features/store/components/product-attribute-selector/product-attribute-selector.component.scss +++ b/src/app/features/store/components/product-attribute-selector/product-attribute-selector.component.scss @@ -45,6 +45,12 @@ 0 0 0 2px rgba(255, 255, 255, 1), 0 0 0 3px var(--tenant-primary); } + + &--disabled { + opacity: 0.3; + cursor: not-allowed; + pointer-events: none; + } } &__text-option { @@ -65,5 +71,13 @@ border-color: var(--tenant-primary); color: #ffffff; } + + &--disabled { + opacity: 0.4; + cursor: not-allowed; + background-color: #f9f9f9; + color: #b0b0b0; + pointer-events: none; + } } } 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 d4f6bde..cd84bba 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 @@ -1,6 +1,7 @@ import { ChangeDetectionStrategy, Component, + computed, effect, input, output, @@ -32,6 +33,50 @@ export class ProductAttributeSelectorComponent { protected readonly selectedAttributeOptions = signal>({}); + protected readonly availableOptions = computed(() => { + const selections = this.selectedAttributeOptions(); + const variants = this.variantsMap(); + const attributes = this.attributes(); + + const availability: Record> = {}; + + for (const attribute of attributes) { + availability[attribute.id] = {}; + for (const option of attribute.options) { + const optionNormalized = this.normalizeText(option.value || option.label); + + const isAvailable = variants.some(variant => { + if (variant.stock <= 0) return false; + + const variantAttrValue = this.getDefaultVariantAttributeValue(attribute, variant.attributes); + if (!variantAttrValue || this.normalizeText(variantAttrValue) !== optionNormalized) { + return false; + } + + for (const otherAttr of attributes) { + if (otherAttr.id === attribute.id) continue; + const selectedOptionId = selections[otherAttr.id]; + if (selectedOptionId !== undefined) { + const selectedOption = otherAttr.options.find(o => o.id === selectedOptionId); + if (selectedOption) { + const selectedNormalized = this.normalizeText(selectedOption.value || selectedOption.label); + const vAttrValue = this.getDefaultVariantAttributeValue(otherAttr, variant.attributes); + if (!vAttrValue || this.normalizeText(vAttrValue) !== selectedNormalized) { + return false; + } + } + } + } + return true; + }); + + availability[attribute.id][option.id] = isAvailable; + } + } + + return availability; + }); + constructor() { effect(() => { const attributes = this.attributes(); diff --git a/src/app/features/store/pages/product-detail-page/product-detail-page.component.html b/src/app/features/store/pages/product-detail-page/product-detail-page.component.html index a919b5d..e927df7 100644 --- a/src/app/features/store/pages/product-detail-page/product-detail-page.component.html +++ b/src/app/features/store/pages/product-detail-page/product-detail-page.component.html @@ -69,6 +69,7 @@ type="button" class="product-detail__quantity-button" aria-label="Aumentar cantidad" + [disabled]="quantity() >= (selectedVariant()?.stock ?? 1)" (click)="increaseQuantity()" > + @@ -80,11 +81,12 @@ class="product-detail__cta" variant="secondary" type="button" + [disabled]="!selectedVariant()" > Agregar al carrito - + Comprar 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 d4fdde9..e0ae664 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 @@ -154,10 +154,14 @@ export class ProductDetailPageComponent implements OnInit, OnDestroy { protected onVariantChange(variant: ProductVariantMap | null): void { this.selectedVariant.set(variant); + if (variant && this.quantity() > variant.stock) { + this.quantity.set(Math.max(1, variant.stock)); + } } protected increaseQuantity(): void { - this.quantity.update((current) => current + 1); + const currentStock = this.selectedVariant()?.stock ?? 1; + this.quantity.update((current) => current < currentStock ? current + 1 : current); } protected decreaseQuantity(): void {