CAtegory component making
This commit is contained in:
@@ -62,8 +62,8 @@
|
||||
</div>
|
||||
} @else {
|
||||
<div class="novo-categories-grid">
|
||||
@for (category of topLevelCategories(); track category.categoryID) {
|
||||
<a [routerLink]="['/category', category.categoryID] | langRoute" class="novo-category-card">
|
||||
@for (category of topLevelCategories(); track category.id) {
|
||||
<a [routerLink]="['/category', category.id] | langRoute" class="novo-category-card">
|
||||
<div class="novo-category-image">
|
||||
@if (category.icon) {
|
||||
<img [src]="category.icon" [alt]="categoryName(category)" loading="lazy" />
|
||||
@@ -75,8 +75,8 @@
|
||||
</div>
|
||||
<div class="novo-category-info">
|
||||
<h3>{{ categoryName(category) }}</h3>
|
||||
@if (getItemCount(category.categoryID)) {
|
||||
<p class="novo-category-count">{{ 'home.itemsCount' | translate:{ count: getItemCount(category.categoryID) } }}</p>
|
||||
@if (getItemCount(category.id)) {
|
||||
<p class="novo-category-count">{{ 'home.itemsCount' | translate:{ count: getItemCount(category.id) } }}</p>
|
||||
}
|
||||
</div>
|
||||
</a>
|
||||
@@ -150,14 +150,11 @@
|
||||
</div>
|
||||
} @else {
|
||||
<div class="dexar-categories-grid">
|
||||
@for (category of topLevelCategories(); track category.categoryID) {
|
||||
<a [routerLink]="['/category', category.categoryID] | langRoute"
|
||||
class="dexar-category-card"
|
||||
[class.dexar-category-card--wide]="isWideCategory(category.categoryID)">
|
||||
@for (category of topLevelCategories(); track category.id) {
|
||||
<a [routerLink]="['/category', category.id] | langRoute"
|
||||
class="dexar-category-card">
|
||||
<div class="dexar-category-image">
|
||||
@if (isWideCategory(category.categoryID) && category.wideBanner) {
|
||||
<img [src]="category.wideBanner" [alt]="categoryName(category)" loading="lazy" decoding="async" />
|
||||
} @else if (category.icon) {
|
||||
@if (category.icon) {
|
||||
<img [src]="category.icon" [alt]="categoryName(category)" loading="lazy" decoding="async" />
|
||||
} @else {
|
||||
<div class="dexar-category-fallback">{{ categoryName(category).charAt(0) }}</div>
|
||||
@@ -165,7 +162,7 @@
|
||||
</div>
|
||||
<div class="dexar-category-info">
|
||||
<h3 class="dexar-category-name">{{ categoryName(category) }}</h3>
|
||||
<p class="dexar-category-count">{{ 'home.itemsCount' | translate:{ count: getItemCount(category.categoryID) } }}</p>
|
||||
<p class="dexar-category-count">{{ 'home.itemsCount' | translate:{ count: getItemCount(category.id) } }}</p>
|
||||
</div>
|
||||
</a>
|
||||
}
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import { Component, OnInit, OnDestroy, signal, computed, ChangeDetectionStrategy } from '@angular/core';
|
||||
import { Component, OnInit, signal, computed, ChangeDetectionStrategy } from '@angular/core';
|
||||
import { Router, RouterLink } from '@angular/router';
|
||||
import { LanguageService } from '../../services';
|
||||
import { Category } from '../../models';
|
||||
import { getTranslatedCategoryName } from '../../utils/item.utils';
|
||||
import { ItemsCarouselComponent } from '../../components/items-carousel/items-carousel.component';
|
||||
import { LangRoutePipe } from '../../pipes/lang-route.pipe';
|
||||
import { TranslatePipe } from '../../i18n/translate.pipe';
|
||||
import { UiRuntimeFacade } from '../../facades/runtime/ui-runtime.facade';
|
||||
import { ProductFacade } from '../../facades/platform/product.facade';
|
||||
import { CategoryFacade } from '../../facades/platform/category.facade';
|
||||
import { Category } from '../../core/categories/models/category-domain.model';
|
||||
|
||||
@Component({
|
||||
selector: 'app-home',
|
||||
@@ -16,16 +15,15 @@ import { ProductFacade } from '../../facades/platform/product.facade';
|
||||
styleUrls: ['./home.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class HomeComponent implements OnInit, OnDestroy {
|
||||
export class HomeComponent implements OnInit {
|
||||
constructor(
|
||||
private router: Router,
|
||||
private langService: LanguageService,
|
||||
private readonly uiRuntime: UiRuntimeFacade,
|
||||
private readonly productFacade: ProductFacade
|
||||
private readonly categoryFacade: CategoryFacade
|
||||
) {}
|
||||
|
||||
categories = signal<Category[]>([]);
|
||||
wideCategories = signal<Set<number>>(new Set());
|
||||
loading = signal(true);
|
||||
error = signal<string | null>(null);
|
||||
readonly skeletonSlots = Array.from({ length: 6 });
|
||||
@@ -33,7 +31,6 @@ export class HomeComponent implements OnInit, OnDestroy {
|
||||
// Memoized computed values for performance
|
||||
topLevelCategories = computed(() => {
|
||||
return this.categories()
|
||||
.filter(cat => cat.parentID === 0)
|
||||
.filter(cat => this.isDisplayableTopLevelCategory(cat))
|
||||
.sort((a, b) => (a.priority ?? Infinity) - (b.priority ?? Infinity));
|
||||
});
|
||||
@@ -41,7 +38,7 @@ export class HomeComponent implements OnInit, OnDestroy {
|
||||
// Memoized item count lookup
|
||||
private itemCountMap = computed(() => {
|
||||
const map = new Map<number, number>();
|
||||
this.categories().forEach(cat => map.set(cat.categoryID, cat.itemCount || 0));
|
||||
this.categories().forEach(cat => map.set(cat.id, cat.itemCount || 0));
|
||||
return map;
|
||||
});
|
||||
|
||||
@@ -49,11 +46,9 @@ export class HomeComponent implements OnInit, OnDestroy {
|
||||
private subcategoriesCache = computed(() => {
|
||||
const cache = new Map<number, Category[]>();
|
||||
this.categories().forEach(cat => {
|
||||
if (cat.parentID !== 0 && this.isDisplayableFlatSubcategory(cat)) {
|
||||
if (!cache.has(cat.parentID)) {
|
||||
cache.set(cat.parentID, []);
|
||||
}
|
||||
cache.get(cat.parentID)!.push(cat);
|
||||
const children = cat.children.filter(child => this.isDisplayableFlatSubcategory(child));
|
||||
if (children.length > 0) {
|
||||
cache.set(cat.id, children);
|
||||
}
|
||||
});
|
||||
return cache;
|
||||
@@ -71,21 +66,13 @@ export class HomeComponent implements OnInit, OnDestroy {
|
||||
this.loadCategories();
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.pendingImages.forEach(img => {
|
||||
img.onload = null;
|
||||
img.onerror = null;
|
||||
});
|
||||
this.pendingImages.clear();
|
||||
}
|
||||
|
||||
loadCategories(): void {
|
||||
this.loading.set(true);
|
||||
this.productFacade.getCategories().subscribe({
|
||||
this.error.set(null);
|
||||
this.categoryFacade.getRootCategories().subscribe({
|
||||
next: (categories) => {
|
||||
this.categories.set(categories);
|
||||
this.loading.set(false);
|
||||
this.detectWideImages(categories);
|
||||
},
|
||||
error: (err) => {
|
||||
this.error.set('Failed to load categories');
|
||||
@@ -95,65 +82,35 @@ export class HomeComponent implements OnInit, OnDestroy {
|
||||
});
|
||||
}
|
||||
|
||||
getItemCount(categoryID: number): number {
|
||||
return this.itemCountMap().get(categoryID) || 0;
|
||||
getItemCount(categoryId: number): number {
|
||||
return this.itemCountMap().get(categoryId) || 0;
|
||||
}
|
||||
|
||||
getSubCategories(parentID: number): Category[] {
|
||||
return this.subcategoriesCache().get(parentID) || [];
|
||||
getSubCategories(parentId: number): Category[] {
|
||||
return this.subcategoriesCache().get(parentId) || [];
|
||||
}
|
||||
|
||||
private isDisplayableFlatSubcategory(category: Category): boolean {
|
||||
return category.visible !== false
|
||||
&& ((category.itemCount ?? 0) > 0 || (category.subcategories?.length ?? 0) > 0);
|
||||
&& ((category.itemCount ?? 0) > 0 || category.children.length > 0);
|
||||
}
|
||||
|
||||
private isDisplayableTopLevelCategory(category: Category): boolean {
|
||||
return category.visible !== false
|
||||
&& (
|
||||
(category.itemCount ?? 0) > 0
|
||||
|| (category.categoriesCount ?? 0) > 0
|
||||
|| (category.subcategories?.length ?? 0) > 0
|
||||
|| this.getSubCategories(category.categoryID).length > 0
|
||||
|| category.children.length > 0
|
||||
|| this.getSubCategories(category.id).length > 0
|
||||
);
|
||||
}
|
||||
|
||||
isWideCategory(categoryID: number): boolean {
|
||||
return this.wideCategories().has(categoryID);
|
||||
}
|
||||
|
||||
private pendingImages = new Set<HTMLImageElement>();
|
||||
|
||||
private detectWideImages(categories: Category[]): void {
|
||||
const topLevel = categories.filter(c => c.parentID === 0);
|
||||
topLevel.forEach(cat => {
|
||||
if (!cat.wideBanner) return;
|
||||
|
||||
const img = new Image();
|
||||
this.pendingImages.add(img);
|
||||
img.onload = () => {
|
||||
this.pendingImages.delete(img);
|
||||
const ratio = img.naturalWidth / img.naturalHeight;
|
||||
if (ratio > 2) {
|
||||
this.wideCategories.update(set => {
|
||||
const next = new Set(set);
|
||||
next.add(cat.categoryID);
|
||||
return next;
|
||||
});
|
||||
}
|
||||
};
|
||||
img.onerror = () => this.pendingImages.delete(img);
|
||||
img.src = cat.wideBanner;
|
||||
});
|
||||
}
|
||||
|
||||
navigateToSearch(): void {
|
||||
const lang = this.langService.currentLanguage();
|
||||
this.router.navigate([`/${lang}/search`]);
|
||||
}
|
||||
|
||||
categoryName(cat: Category): string {
|
||||
return getTranslatedCategoryName(cat, this.langService.currentLanguage());
|
||||
return cat.translations[this.langService.currentLanguage()]?.title ?? cat.title;
|
||||
}
|
||||
|
||||
scrollToCatalog(): void {
|
||||
|
||||
Reference in New Issue
Block a user