Compare commits

..

2 Commits

4 changed files with 39 additions and 28 deletions

View File

@@ -1,6 +1,5 @@
export interface Product { export interface Product {
id: number; id: number;
tenant_codigo: string;
category_id: number; category_id: number;
brand_id: number | null; brand_id: number | null;
slug: string; slug: string;
@@ -17,7 +16,7 @@ export interface ProductAttributeOption {
value: string; value: string;
label: string; label: string;
sort_order: number; sort_order: number;
metadata: Record<string, any> | null; metadata: Record<string, unknown> | null;
} }
export interface ProductAttribute { export interface ProductAttribute {
@@ -25,19 +24,27 @@ export interface ProductAttribute {
codigo: string; codigo: string;
nombre: string; nombre: string;
is_required: boolean; is_required: boolean;
metadata_schema: Record<string, any> | null; metadata_schema: Record<string, unknown> | null;
type: string; type: string;
options: ProductAttributeOption[]; options: ProductAttributeOption[];
} }
export interface ProductVariant { export interface ProductVariant {
variant_id: number; id: number;
stock: number;
definitions: Record<string, string>;
images: string[]; images: string[];
}
export interface ProductVariantMap {
variant_id: number;
stock: number;
attributes: Record<string, string>; attributes: Record<string, string>;
} }
export interface ProductDetail extends Product { export interface ProductDetail extends Product {
attributes: ProductAttribute[]; attributes: ProductAttribute[];
default_variant: ProductVariant | null; variants_map: ProductVariantMap[];
variant: ProductVariant | null;
} }

View File

@@ -16,7 +16,6 @@ import { ProductDetailPageComponent } from './product-detail-page.component';
describe('ProductDetailPageComponent', () => { describe('ProductDetailPageComponent', () => {
const mockProduct: ProductDetail = { const mockProduct: ProductDetail = {
id: 1, id: 1,
tenant_codigo: 'test',
category_id: 10, category_id: 10,
brand_id: null, brand_id: null,
slug: 'auriculares-bluetooth', slug: 'auriculares-bluetooth',
@@ -27,7 +26,8 @@ describe('ProductDetailPageComponent', () => {
brand: 'Sony', brand: 'Sony',
images: ['https://example.com/image.png'], images: ['https://example.com/image.png'],
attributes: [], attributes: [],
default_variant: null variants_map: [],
variant: null
}; };
let paramMapSubject: BehaviorSubject<any>; let paramMapSubject: BehaviorSubject<any>;
@@ -132,10 +132,11 @@ describe('ProductDetailPageComponent', () => {
const detailProduct: ProductDetail = { const detailProduct: ProductDetail = {
...mockProduct, ...mockProduct,
images: ['https://example.com/product.png'], images: ['https://example.com/product.png'],
default_variant: { variant: {
variant_id: 123, id: 123,
stock: 10,
images: ['https://example.com/variant1.png', 'https://example.com/variant2.png'], images: ['https://example.com/variant1.png', 'https://example.com/variant2.png'],
attributes: {} definitions: {}
} }
}; };
catalogServiceStub.getProducto.mockReturnValue(of(detailProduct)); catalogServiceStub.getProducto.mockReturnValue(of(detailProduct));
@@ -152,7 +153,7 @@ describe('ProductDetailPageComponent', () => {
const detailProduct: ProductDetail = { const detailProduct: ProductDetail = {
...mockProduct, ...mockProduct,
images: ['https://example.com/product.png'], images: ['https://example.com/product.png'],
default_variant: null variant: null
}; };
catalogServiceStub.getProducto.mockReturnValue(of(detailProduct)); catalogServiceStub.getProducto.mockReturnValue(of(detailProduct));
@@ -168,7 +169,7 @@ describe('ProductDetailPageComponent', () => {
const detailProduct: ProductDetail = { const detailProduct: ProductDetail = {
...mockProduct, ...mockProduct,
images: [], images: [],
default_variant: null variant: null
}; };
catalogServiceStub.getProducto.mockReturnValue(of(detailProduct)); catalogServiceStub.getProducto.mockReturnValue(of(detailProduct));
@@ -233,10 +234,11 @@ describe('ProductDetailPageComponent', () => {
] ]
} }
], ],
default_variant: { variant: {
variant_id: 123, id: 123,
stock: 10,
images: ['https://example.com/variant1.png'], images: ['https://example.com/variant1.png'],
attributes: { definitions: {
color: 'beige', color: 'beige',
material: 'Cuero' material: 'Cuero'
} }

View File

@@ -8,9 +8,10 @@ import {
effect, effect,
inject, inject,
signal, signal,
viewChild viewChild,
PLATFORM_ID
} from '@angular/core'; } from '@angular/core';
import { CommonModule } from '@angular/common'; import { CommonModule, isPlatformBrowser } from '@angular/common';
import { ActivatedRoute, Router, RouterModule } from '@angular/router'; import { ActivatedRoute, Router, RouterModule } from '@angular/router';
import { combineLatest, Subscription } from 'rxjs'; import { combineLatest, Subscription } from 'rxjs';
@@ -38,6 +39,8 @@ export class ProductDetailPageComponent implements OnInit, OnDestroy {
private readonly catalogService = inject(CatalogService); private readonly catalogService = inject(CatalogService);
private readonly carouselHost = viewChild<ElementRef<HTMLElement>>('carouselHost'); private readonly carouselHost = viewChild<ElementRef<HTMLElement>>('carouselHost');
private readonly descriptionBody = viewChild<ElementRef<HTMLElement>>('descriptionBody'); private readonly descriptionBody = viewChild<ElementRef<HTMLElement>>('descriptionBody');
private readonly platformId = inject(PLATFORM_ID);
private readonly isBrowser = isPlatformBrowser(this.platformId);
private routeSub: Subscription | null = null; private routeSub: Subscription | null = null;
private productSub: Subscription | null = null; private productSub: Subscription | null = null;
@@ -49,8 +52,8 @@ export class ProductDetailPageComponent implements OnInit, OnDestroy {
protected readonly carouselImages = computed(() => { protected readonly carouselImages = computed(() => {
const prod = this.product(); const prod = this.product();
if (!prod) return []; if (!prod) return [];
if (prod.default_variant?.images && prod.default_variant.images.length > 0) { if (prod.variant?.images && prod.variant.images.length > 0) {
return prod.default_variant.images; return prod.variant.images;
} }
if (prod.images && prod.images.length > 0) { if (prod.images && prod.images.length > 0) {
return prod.images; return prod.images;
@@ -82,10 +85,12 @@ export class ProductDetailPageComponent implements OnInit, OnDestroy {
this.product(); this.product();
this.descriptionExpanded(); this.descriptionExpanded();
queueMicrotask(() => { if (this.isBrowser) {
this.bindCarouselResizeObserver(); queueMicrotask(() => {
this.refreshMeasurements(); this.bindCarouselResizeObserver();
}); this.refreshMeasurements();
});
}
}); });
} }
@@ -181,7 +186,7 @@ export class ProductDetailPageComponent implements OnInit, OnDestroy {
} }
private initializeSelections(product: ProductDetail): void { private initializeSelections(product: ProductDetail): void {
const defaultVariant = product.default_variant; const defaultVariant = product.variant;
const selections: Record<number, number> = {}; const selections: Record<number, number> = {};
for (const attribute of product.attributes) { for (const attribute of product.attributes) {
@@ -202,7 +207,7 @@ export class ProductDetailPageComponent implements OnInit, OnDestroy {
return null; return null;
} }
const defaultValue = this.getDefaultVariantAttributeValue(attribute, variant.attributes); const defaultValue = this.getDefaultVariantAttributeValue(attribute, variant.definitions);
if (!defaultValue) { if (!defaultValue) {
return null; return null;
} }

View File

@@ -37,7 +37,6 @@ describe('StoreHomePageComponent', () => {
const pageOneProducts: Product[] = [ const pageOneProducts: Product[] = [
{ {
id: 1, id: 1,
tenant_codigo: 'test',
category_id: 10, category_id: 10,
brand_id: null, brand_id: null,
slug: 'auriculares-bluetooth', slug: 'auriculares-bluetooth',
@@ -50,7 +49,6 @@ describe('StoreHomePageComponent', () => {
}, },
{ {
id: 2, id: 2,
tenant_codigo: 'test',
category_id: 11, category_id: 11,
brand_id: null, brand_id: null,
slug: 'teclado-mecanico', slug: 'teclado-mecanico',
@@ -106,7 +104,6 @@ describe('StoreHomePageComponent', () => {
[ [
{ {
id: 3, id: 3,
tenant_codigo: 'test',
category_id: 12, category_id: 12,
brand_id: null, brand_id: null,
slug: 'mouse-gamer', slug: 'mouse-gamer',