diff --git a/src/app/core/services/catalog/catalog.interface.ts b/src/app/core/services/catalog/catalog.interface.ts index 39007a7..7c86f79 100644 --- a/src/app/core/services/catalog/catalog.interface.ts +++ b/src/app/core/services/catalog/catalog.interface.ts @@ -1,3 +1,5 @@ +import { ApiPaginatedResponse } from '../api-paginated-response.interface'; + export interface Product { id: number; category_id: number; @@ -53,3 +55,29 @@ export interface ProductDetail extends Product { variants_map: ProductVariantMap[]; variant: ProductVariant | null; } + +export type CatalogProductLayout = 'row' | 'column_with_image' | 'column_with_cart'; + +export interface CatalogFeaturedItemVariant { + id: number; + stock_tecnico: number | null; + values: Record; +} + +export interface CatalogFeaturedItem { + id: number; + nombre: string; + descripcion?: string | null; + precio: number | string; + image?: string | null; + stock_tecnico?: number | null; + variants?: CatalogFeaturedItemVariant[]; +} + +export interface CatalogFeaturedGroup { + id: number; + title: string; + layout: CatalogProductLayout; + group_order: number; + items: ApiPaginatedResponse; +} diff --git a/src/app/core/services/catalog/catalog.service.ts b/src/app/core/services/catalog/catalog.service.ts index 926f4c2..e25975f 100644 --- a/src/app/core/services/catalog/catalog.service.ts +++ b/src/app/core/services/catalog/catalog.service.ts @@ -6,16 +6,17 @@ import { ApiPaginationQueryParams } from '../api-pagination-query-params.interfa import { ApiPaginatedResponse } from '../api-paginated-response.interface'; import { ApiResponse } from '../api-response.interface'; import { TenantService } from '../tenant.service'; -import { Product, ProductDetail } from './catalog.interface'; +import { + CatalogFeaturedGroup, + CatalogFeaturedItem, + Product, + ProductDetail, +} from './catalog.interface'; -type HttpParamValue = - | string - | number - | boolean - | readonly (string | number | boolean)[]; +type HttpParamValue = string | number | boolean | readonly (string | number | boolean)[]; @Injectable({ - providedIn: 'root' + providedIn: 'root', }) export class CatalogService { private readonly http = inject(HttpClient); @@ -25,13 +26,23 @@ export class CatalogService { return this.tenantService.getTenantApiUrl(); } + getProductos(params?: ApiPaginationQueryParams): Observable> { + return this.http.get>(`${this.tenantApiUrl}/productos`, { + params: this.buildHttpParams(params), + }); + } - getProductos( - params?: ApiPaginationQueryParams - ): Observable> { - return this.http.get>( - `${this.tenantApiUrl}/productos`, - { params: this.buildHttpParams(params) } + getCatalog(): Observable { + return this.http.get(`${this.tenantApiUrl}/catalog`); + } + + getFeaturedGroupItems( + featuredGroupId: number, + params?: ApiPaginationQueryParams, + ): Observable> { + return this.http.get>( + `${this.tenantApiUrl}/catalog/featured-groups/${featuredGroupId}/items`, + { params: this.buildHttpParams(params) }, ); } @@ -40,7 +51,7 @@ export class CatalogService { if (variantId) { params = params.set('variant_id', variantId); } - + return this.http .get>(`${this.tenantApiUrl}/productos/${id}`, { params }) .pipe(map((response) => response.data)); @@ -59,7 +70,7 @@ export class CatalogService { return acc; }, - {} + {}, ); return new HttpParams({ fromObject }); diff --git a/src/app/features/store/pages/store-home-page/store-home-page.component.html b/src/app/features/store/pages/store-home-page/store-home-page.component.html index f386b07..6ecd0f1 100644 --- a/src/app/features/store/pages/store-home-page/store-home-page.component.html +++ b/src/app/features/store/pages/store-home-page/store-home-page.component.html @@ -1,51 +1,28 @@ @if (tenant()?.hero_config || tenant()?.event_config) { - } - -
- @if (error()) { -

{{ error() }}

- } @else if (loading() && !productCards().length) { -

Cargando productos...

- } @else if (!productCards().length) { -

- No hay productos disponibles en este momento. -

- } @else { -
- @for (product of productCards(); track product.id; let index = $index) { -
- -
- } -
- } - - @if (loading() && productCards().length) { -

Actualizando productos...

- } - - @if (totalPages() > 1) { -
- -
- } -
-
+@if (error()) { +

{{ error() }}

+} @else if (loading()) { +

Cargando productos...

+} @else if (!catalog().length) { +

+ No hay productos disponibles en este momento. +

+} @else { + @for (group of catalog(); track group.id) { + + + + } +} diff --git a/src/app/features/store/pages/store-home-page/store-home-page.component.spec.ts b/src/app/features/store/pages/store-home-page/store-home-page.component.spec.ts index 259ca7f..cf37162 100644 --- a/src/app/features/store/pages/store-home-page/store-home-page.component.spec.ts +++ b/src/app/features/store/pages/store-home-page/store-home-page.component.spec.ts @@ -4,196 +4,157 @@ import { Subject, of } from 'rxjs'; import { beforeEach, describe, expect, it, vi } from 'vitest'; import { ApiPaginatedResponse } from '../../../../core/services/api-paginated-response.interface'; -import { Product } from '../../../../core/services/catalog/catalog.interface'; +import { + CatalogFeaturedGroup, + CatalogFeaturedItem, +} from '../../../../core/services/catalog/catalog.interface'; import { CatalogService } from '../../../../core/services/catalog/catalog.service'; import { StoreHomePageComponent } from './store-home-page.component'; import { STORE_HOME_PRODUCTS_ERROR_MESSAGE, - StoreHomeProductsResolvedData, + StoreHomeCatalogResolvedData, } from './store-home-page.resolver'; -function createPaginatedResponse( - products: Product[], - currentPage: number, - lastPage: number, -): ApiPaginatedResponse { +function createItemsPage( + items: CatalogFeaturedItem[], + currentPage = 1, + lastPage = 1, +): ApiPaginatedResponse { return { - data: products, + data: items, meta: { current_page: currentPage, - from: products.length ? 1 : null, + from: items.length ? 1 : null, last_page: lastPage, links: [], - path: '/productos', + path: '/catalog/featured-groups/1/items', per_page: 12, - to: products.length || null, - total: products.length, + to: items.length || null, + total: items.length, }, links: { - first: '/productos?page=1', - last: `/productos?page=${lastPage}`, - prev: currentPage > 1 ? `/productos?page=${currentPage - 1}` : null, - next: currentPage < lastPage ? `/productos?page=${currentPage + 1}` : null, + first: '/catalog/featured-groups/1/items?page=1', + last: `/catalog/featured-groups/1/items?page=${lastPage}`, + prev: currentPage > 1 ? `/catalog/featured-groups/1/items?page=${currentPage - 1}` : null, + next: + currentPage < lastPage ? `/catalog/featured-groups/1/items?page=${currentPage + 1}` : null, }, }; } -function createResolvedData( - response: ApiPaginatedResponse, -): StoreHomeProductsResolvedData { - return { - response, - error: null, - }; -} - -function provideActivatedRoute(productsData: StoreHomeProductsResolvedData) { - return { - provide: ActivatedRoute, - useValue: { - snapshot: { - data: { productsData }, - }, - }, - }; -} - -describe('StoreHomePageComponent', () => { - const pageOneProducts: Product[] = [ +function createCatalog( + items = pageOneItems, + currentPage = 1, + lastPage = 2, +): CatalogFeaturedGroup[] { + return [ { - id: 1, - category_id: 10, - brand_id: null, - slug: 'auriculares-bluetooth', - nombre: 'Auriculares Bluetooth', - descripcion: 'Auriculares bluetooth de prueba', - precio: '24999', - category: 'Tecnologia', - brand: null, - images: ['/catalog/auriculares.jpg'], - }, - { - id: 2, - category_id: 11, - brand_id: null, - slug: 'teclado-mecanico', - nombre: 'Teclado Mecanico', - descripcion: 'Teclado mecanico de prueba', - precio: '18999', - category: 'Tecnologia', - brand: null, - images: [], + id: 7, + title: 'Destacados', + layout: 'column_with_image', + group_order: 0, + items: createItemsPage(items, currentPage, lastPage), }, ]; +} - beforeEach(() => { - vi.restoreAllMocks(); - }); +function provideActivatedRoute(catalogData: StoreHomeCatalogResolvedData) { + return { + provide: ActivatedRoute, + useValue: { snapshot: { data: { catalogData } } }, + }; +} - it('renders the products resolved by the route before component init', async () => { +const pageOneItems: CatalogFeaturedItem[] = [ + { + id: 1, + nombre: 'Auriculares Bluetooth', + precio: '24999.00', + image: '/catalog/auriculares.jpg', + }, + { + id: 2, + nombre: 'Teclado Mecanico', + precio: '18999.00', + image: null, + }, +]; + +describe('StoreHomePageComponent', () => { + beforeEach(() => vi.restoreAllMocks()); + + it('renders the CatalogNew groups resolved by the route', async () => { const catalogServiceStub = { - getProductos: vi.fn(), + getCatalog: vi.fn(), + getFeaturedGroupItems: vi.fn(), }; await TestBed.configureTestingModule({ imports: [StoreHomePageComponent], providers: [ - provideActivatedRoute(createResolvedData(createPaginatedResponse(pageOneProducts, 1, 3))), - { - provide: CatalogService, - useValue: catalogServiceStub, - }, + provideActivatedRoute({ response: createCatalog(), error: null }), + { provide: CatalogService, useValue: catalogServiceStub }, ], }).compileComponents(); const fixture = TestBed.createComponent(StoreHomePageComponent); fixture.detectChanges(); - const element = fixture.nativeElement as HTMLElement; - expect(catalogServiceStub.getProductos).not.toHaveBeenCalled(); - expect(element.querySelector('.store-section__title')?.textContent?.trim()).toBe('Productos'); + expect(catalogServiceStub.getCatalog).not.toHaveBeenCalled(); + expect(element.querySelector('.store-section__title')?.textContent?.trim()).toBe('Destacados'); expect(element.querySelectorAll('app-product-column-with-image')).toHaveLength(2); expect(element.textContent).toContain('Auriculares Bluetooth'); - expect(element.textContent).toContain('Teclado Mecanico'); expect(element.querySelector('[data-testid="paginator-status"]')?.textContent?.trim()).toBe( - '1/3', + '1/2', ); - expect(element.querySelector('img')?.getAttribute('fetchpriority')).toBe('high'); }); - it('requests the next page when the paginator emits a page change', async () => { + it('requests another page for the selected featured group', async () => { + const pageTwoItems = [{ id: 3, nombre: 'Mouse Gamer', precio: '15999.00', image: null }]; const catalogServiceStub = { - getProductos: vi.fn().mockReturnValueOnce( - of( - createPaginatedResponse( - [ - { - id: 3, - category_id: 12, - brand_id: null, - slug: 'mouse-gamer', - nombre: 'Mouse Gamer', - descripcion: 'Mouse gamer de prueba', - precio: '15999', - category: 'Tecnologia', - brand: null, - images: [], - }, - ], - 2, - 3, - ), - ), - ), + getCatalog: vi.fn(), + getFeaturedGroupItems: vi.fn().mockReturnValue(of(createItemsPage(pageTwoItems, 2, 2))), }; await TestBed.configureTestingModule({ imports: [StoreHomePageComponent], providers: [ - provideActivatedRoute(createResolvedData(createPaginatedResponse(pageOneProducts, 1, 3))), - { - provide: CatalogService, - useValue: catalogServiceStub, - }, + provideActivatedRoute({ response: createCatalog(), error: null }), + { provide: CatalogService, useValue: catalogServiceStub }, ], }).compileComponents(); const fixture = TestBed.createComponent(StoreHomePageComponent); fixture.detectChanges(); - const element = fixture.nativeElement as HTMLElement; (element.querySelector('[data-testid="paginator-next"]') as HTMLButtonElement).click(); fixture.detectChanges(); - expect(catalogServiceStub.getProductos).toHaveBeenCalledTimes(1); - expect(catalogServiceStub.getProductos).toHaveBeenCalledWith({ page: 2 }); - expect(element.querySelector('[data-testid="paginator-status"]')?.textContent?.trim()).toBe( - '2/3', - ); + expect(catalogServiceStub.getFeaturedGroupItems).toHaveBeenCalledWith(7, { page: 2 }); expect(element.textContent).toContain('Mouse Gamer'); + expect(element.querySelector('[data-testid="paginator-status"]')?.textContent?.trim()).toBe( + '2/2', + ); }); - it('disables the paginator while a new page request is in flight', async () => { - const nextPageSubject = new Subject>(); + it('disables only the group paginator while its page request is in flight', async () => { + const nextPage = new Subject>(); const catalogServiceStub = { - getProductos: vi.fn().mockReturnValueOnce(nextPageSubject.asObservable()), + getCatalog: vi.fn(), + getFeaturedGroupItems: vi.fn().mockReturnValue(nextPage.asObservable()), }; await TestBed.configureTestingModule({ imports: [StoreHomePageComponent], providers: [ - provideActivatedRoute(createResolvedData(createPaginatedResponse(pageOneProducts, 1, 3))), - { - provide: CatalogService, - useValue: catalogServiceStub, - }, + provideActivatedRoute({ response: createCatalog(), error: null }), + { provide: CatalogService, useValue: catalogServiceStub }, ], }).compileComponents(); const fixture = TestBed.createComponent(StoreHomePageComponent); fixture.detectChanges(); - const element = fixture.nativeElement as HTMLElement; (element.querySelector('[data-testid="paginator-next"]') as HTMLButtonElement).click(); fixture.detectChanges(); @@ -203,10 +164,9 @@ describe('StoreHomePageComponent', () => { (button) => (button as HTMLButtonElement).disabled, ), ).toBe(true); - expect(element.textContent).toContain('Actualizando productos...'); - nextPageSubject.next(createPaginatedResponse(pageOneProducts, 2, 3)); - nextPageSubject.complete(); + nextPage.next(createItemsPage(pageOneItems, 2, 2)); + nextPage.complete(); fixture.detectChanges(); expect( @@ -216,58 +176,41 @@ describe('StoreHomePageComponent', () => { ).toBe(true); }); - it('shows an empty-state message and hides the paginator when there are no products', async () => { - const catalogServiceStub = { - getProductos: vi.fn(), - }; + it('shows an empty state when CatalogNew returns no groups', async () => { + const catalogServiceStub = { getCatalog: vi.fn(), getFeaturedGroupItems: vi.fn() }; await TestBed.configureTestingModule({ imports: [StoreHomePageComponent], providers: [ - provideActivatedRoute(createResolvedData(createPaginatedResponse([], 1, 1))), - { - provide: CatalogService, - useValue: catalogServiceStub, - }, + provideActivatedRoute({ response: [], error: null }), + { provide: CatalogService, useValue: catalogServiceStub }, ], }).compileComponents(); const fixture = TestBed.createComponent(StoreHomePageComponent); fixture.detectChanges(); - const element = fixture.nativeElement as HTMLElement; - - expect(element.textContent).toContain('No hay productos disponibles en este momento.'); - expect(element.querySelector('app-paginator')).toBeNull(); - expect(catalogServiceStub.getProductos).not.toHaveBeenCalled(); + expect((fixture.nativeElement as HTMLElement).textContent).toContain( + 'No hay productos disponibles en este momento.', + ); }); - it('shows an error message when the route resolver cannot load the catalog', async () => { - const catalogServiceStub = { - getProductos: vi.fn(), - }; + it('shows an error when the catalog resolver fails', async () => { + const catalogServiceStub = { getCatalog: vi.fn(), getFeaturedGroupItems: vi.fn() }; await TestBed.configureTestingModule({ imports: [StoreHomePageComponent], providers: [ - provideActivatedRoute({ - response: null, - error: STORE_HOME_PRODUCTS_ERROR_MESSAGE, - }), - { - provide: CatalogService, - useValue: catalogServiceStub, - }, + provideActivatedRoute({ response: null, error: STORE_HOME_PRODUCTS_ERROR_MESSAGE }), + { provide: CatalogService, useValue: catalogServiceStub }, ], }).compileComponents(); const fixture = TestBed.createComponent(StoreHomePageComponent); fixture.detectChanges(); - const element = fixture.nativeElement as HTMLElement; - - expect(element.textContent).toContain('No pudimos cargar los productos en este momento.'); - expect(element.querySelectorAll('app-product-column-with-image')).toHaveLength(0); - expect(catalogServiceStub.getProductos).not.toHaveBeenCalled(); + expect((fixture.nativeElement as HTMLElement).textContent).toContain( + 'No pudimos cargar los productos en este momento.', + ); }); }); diff --git a/src/app/features/store/pages/store-home-page/store-home-page.component.ts b/src/app/features/store/pages/store-home-page/store-home-page.component.ts index c28779e..61b0e4e 100644 --- a/src/app/features/store/pages/store-home-page/store-home-page.component.ts +++ b/src/app/features/store/pages/store-home-page/store-home-page.component.ts @@ -3,43 +3,29 @@ import { Component, OnDestroy, OnInit, - computed, inject, signal, } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; import { Subscription } from 'rxjs'; -import { ApiPaginatedResponse } from '../../../../core/services/api-paginated-response.interface'; +import { CatalogFeaturedGroup } from '../../../../core/services/catalog/catalog.interface'; import { CatalogService } from '../../../../core/services/catalog/catalog.service'; -import { Product } from '../../../../core/services/catalog/catalog.interface'; -import { ProductColumnWithImageComponent } from '../../../../shared/components/product-column-with-image/product-column-with-image.component'; -import { PaginatorComponent } from '../../../../shared/components/paginator/paginator.component'; -import { StoreSectionComponent } from '../../../../shared/components/store-section/store-section.component'; -import { HeroBannerComponent } from '../../../../shared/components/hero-banner/hero-banner.component'; import { TenantService } from '../../../../core/services/tenant.service'; +import { + ProductListComponent, + ProductListItem, +} from '../../../../shared/components/product-list/product-list.component'; +import { HeroBannerComponent } from '../../../../shared/components/hero-banner/hero-banner.component'; +import { StoreSectionComponent } from '../../../../shared/components/store-section/store-section.component'; import { STORE_HOME_PRODUCTS_ERROR_MESSAGE, - StoreHomeProductsResolvedData, + StoreHomeCatalogResolvedData, } from './store-home-page.resolver'; -interface StoreHomeProductCardViewModel { - id: number; - title: string; - originalPrice: number; - discount: null; - transferPrice: null; - imageUrl: string | null; -} - @Component({ selector: 'app-store-home-page', - imports: [ - StoreSectionComponent, - ProductColumnWithImageComponent, - PaginatorComponent, - HeroBannerComponent, - ], + imports: [StoreSectionComponent, ProductListComponent, HeroBannerComponent], templateUrl: './store-home-page.component.html', styleUrl: './store-home-page.component.scss', changeDetection: ChangeDetectionStrategy.OnPush, @@ -51,120 +37,101 @@ export class StoreHomePageComponent implements OnInit, OnDestroy { private readonly tenantService = inject(TenantService); protected readonly tenant = this.tenantService.tenant; - - private activeRequestId = 0; - private productsRequestSubscription: Subscription | null = null; - protected readonly priorityImageCount = 4; - - protected readonly currentPage = signal(1); - protected readonly products = signal([]); - protected readonly totalPages = signal(0); + protected readonly catalog = signal([]); protected readonly loading = signal(false); + protected readonly loadingGroupIds = signal>(new Set()); protected readonly error = signal(null); - protected readonly productCards = computed(() => - this.products().map((product) => ({ - id: product.id, - title: product.nombre, - originalPrice: this.parseProductPrice(product.precio), - discount: null, - transferPrice: null, - imageUrl: product.images?.[0] ?? null, - })), - ); + private catalogRequestSubscription: Subscription | null = null; + private readonly groupRequestSubscriptions = new Map(); ngOnInit(): void { - const resolvedData = this.route.snapshot.data['productsData'] as - | StoreHomeProductsResolvedData + const resolvedData = this.route.snapshot.data['catalogData'] as + | StoreHomeCatalogResolvedData | undefined; if (resolvedData) { this.applyResolvedData(resolvedData); - return; } - this.loadProducts(1); + this.loadCatalog(); } ngOnDestroy(): void { - this.productsRequestSubscription?.unsubscribe(); + this.catalogRequestSubscription?.unsubscribe(); + this.groupRequestSubscriptions.forEach((subscription) => subscription.unsubscribe()); } - protected onPageChange(page: number): void { - if (page === this.currentPage() || page < 1) { + protected isGroupLoading(groupId: number): boolean { + return this.loadingGroupIds().has(groupId); + } + + protected onPageChange(groupId: number, page: number): void { + const group = this.catalog().find((candidate) => candidate.id === groupId); + + if ( + !group || + page < 1 || + page === group.items.meta.current_page || + this.isGroupLoading(groupId) + ) { return; } - this.loadProducts(page); - } + this.setGroupLoading(groupId, true); + this.groupRequestSubscriptions.get(groupId)?.unsubscribe(); - protected onBuyProduct(productId: number): void { - this.router.navigate(['/producto', productId]); - } - - private loadProducts(page: number): void { - this.activeRequestId += 1; - const requestId = this.activeRequestId; - - this.productsRequestSubscription?.unsubscribe(); - this.loading.set(true); - this.error.set(null); - - this.productsRequestSubscription = this.catalogService.getProductos({ page }).subscribe({ - next: (response) => { - if (requestId !== this.activeRequestId) { - return; - } - - this.applyProductsResponse(response); + const subscription = this.catalogService.getFeaturedGroupItems(groupId, { page }).subscribe({ + next: (items) => { + this.catalog.update((groups) => + groups.map((candidate) => + candidate.id === groupId ? { ...candidate, items } : candidate, + ), + ); + this.error.set(null); }, error: () => { - if (requestId !== this.activeRequestId) { - return; - } + this.error.set(STORE_HOME_PRODUCTS_ERROR_MESSAGE); + this.setGroupLoading(groupId, false); + }, + complete: () => this.setGroupLoading(groupId, false), + }); - this.products.set([]); - this.totalPages.set(0); + this.groupRequestSubscriptions.set(groupId, subscription); + } + + protected onBuyProduct(product: ProductListItem): void { + this.router.navigate(['/producto', product.id]); + } + + private loadCatalog(): void { + this.loading.set(true); + this.error.set(null); + this.catalogRequestSubscription?.unsubscribe(); + + this.catalogRequestSubscription = this.catalogService.getCatalog().subscribe({ + next: (catalog) => this.catalog.set(catalog), + error: () => { + this.catalog.set([]); this.loading.set(false); this.error.set(STORE_HOME_PRODUCTS_ERROR_MESSAGE); }, - complete: () => { - if (requestId !== this.activeRequestId) { - return; - } - - this.loading.set(false); - }, + complete: () => this.loading.set(false), }); } - private applyResolvedData(resolvedData: StoreHomeProductsResolvedData): void { - if (resolvedData.error) { - this.products.set([]); - this.totalPages.set(0); - this.loading.set(false); - this.error.set(resolvedData.error); - - return; - } - - if (resolvedData.response) { - this.applyProductsResponse(resolvedData.response); - this.loading.set(false); - this.error.set(null); - } + private applyResolvedData(resolvedData: StoreHomeCatalogResolvedData): void { + this.catalog.set(resolvedData.response ?? []); + this.loading.set(false); + this.error.set(resolvedData.error); } - private applyProductsResponse(response: ApiPaginatedResponse): void { - this.products.set(response.data ?? []); - this.currentPage.set(response.meta.current_page); - this.totalPages.set(response.meta.last_page); - } - - private parseProductPrice(price: string): number { - const parsedPrice = Number(price); - - return Number.isFinite(parsedPrice) ? parsedPrice : 0; + private setGroupLoading(groupId: number, loading: boolean): void { + this.loadingGroupIds.update((current) => { + const updated = new Set(current); + loading ? updated.add(groupId) : updated.delete(groupId); + return updated; + }); } } diff --git a/src/app/features/store/pages/store-home-page/store-home-page.resolver.ts b/src/app/features/store/pages/store-home-page/store-home-page.resolver.ts index 0d3e524..0571a26 100644 --- a/src/app/features/store/pages/store-home-page/store-home-page.resolver.ts +++ b/src/app/features/store/pages/store-home-page/store-home-page.resolver.ts @@ -2,23 +2,22 @@ import { inject } from '@angular/core'; import { ResolveFn } from '@angular/router'; import { catchError, map, of } from 'rxjs'; -import { ApiPaginatedResponse } from '../../../../core/services/api-paginated-response.interface'; -import { Product } from '../../../../core/services/catalog/catalog.interface'; +import { CatalogFeaturedGroup } from '../../../../core/services/catalog/catalog.interface'; import { CatalogService } from '../../../../core/services/catalog/catalog.service'; export const STORE_HOME_PRODUCTS_ERROR_MESSAGE = 'No pudimos cargar los productos en este momento.'; -export interface StoreHomeProductsResolvedData { - response: ApiPaginatedResponse | null; +export interface StoreHomeCatalogResolvedData { + response: CatalogFeaturedGroup[] | null; error: string | null; } -export const storeHomeProductsResolver: ResolveFn = () => { +export const storeHomeCatalogResolver: ResolveFn = () => { return inject(CatalogService) - .getProductos({ page: 1 }) + .getCatalog() .pipe( map( - (response): StoreHomeProductsResolvedData => ({ + (response): StoreHomeCatalogResolvedData => ({ response, error: null, }), diff --git a/src/app/features/store/store.routes.ts b/src/app/features/store/store.routes.ts index 41b9401..c6013a1 100644 --- a/src/app/features/store/store.routes.ts +++ b/src/app/features/store/store.routes.ts @@ -8,7 +8,7 @@ import { hasMenuGuard } from '../../core/guards/menu.guard'; import { productDetailResolver } from './pages/product-detail-page/product-detail-page.resolver'; import { RegisterPageComponent } from './pages/register-page/register-page.component'; import { StoreHomePageComponent } from './pages/store-home-page/store-home-page.component'; -import { storeHomeProductsResolver } from './pages/store-home-page/store-home-page.resolver'; +import { storeHomeCatalogResolver } from './pages/store-home-page/store-home-page.resolver'; export const routes: Routes = [ { @@ -20,7 +20,7 @@ export const routes: Routes = [ component: StoreHomePageComponent, canActivate: [hasMenuGuard('index')], resolve: { - productsData: storeHomeProductsResolver, + catalogData: storeHomeCatalogResolver, }, }, { diff --git a/src/app/shared/components/product-list/product-list.component.ts b/src/app/shared/components/product-list/product-list.component.ts index af3ed5d..4d10101 100644 --- a/src/app/shared/components/product-list/product-list.component.ts +++ b/src/app/shared/components/product-list/product-list.component.ts @@ -1,6 +1,11 @@ import { ChangeDetectionStrategy, Component, input, output } from '@angular/core'; import { ApiPaginatedResponse } from '../../../core/services/api-paginated-response.interface'; +import { + CatalogFeaturedItem, + CatalogFeaturedItemVariant, + CatalogProductLayout, +} from '../../../core/services/catalog/catalog.interface'; import { PaginatorComponent } from '../paginator/paginator.component'; import { ProductColumnWithImageComponent } from '../product-column-with-image/product-column-with-image.component'; import { @@ -9,23 +14,9 @@ import { } from '../product-row-card/product-row-card.component'; import { ProductVerticalWithCartCardComponent } from '../product-vertical-with-cart-card/product-vertical-with-cart-card.component'; -export type ProductListLayout = 'row' | 'column_with_image' | 'column_with_cart'; - -export interface ProductListVariant { - id: number; - stock_tecnico: number | null; - values: Record; -} - -export interface ProductListItem { - id: number; - nombre: string; - descripcion?: string | null; - precio: number | string; - image?: string | null; - stock_tecnico?: number | null; - variants?: ProductListVariant[]; -} +export type ProductListLayout = CatalogProductLayout; +export type ProductListVariant = CatalogFeaturedItemVariant; +export type ProductListItem = CatalogFeaturedItem; export interface ProductListCartEvent { product: ProductListItem;