import { Component, OnInit, signal, computed, ChangeDetectionStrategy } from '@angular/core'; import { Router, RouterLink } from '@angular/router'; import { ApiService, LanguageService } from '../../services'; import { Category } from '../../models'; import { environment } from '../../../environments/environment'; import { ItemsCarouselComponent } from '../../components/items-carousel/items-carousel.component'; import { LangRoutePipe } from '../../pipes/lang-route.pipe'; import { TranslatePipe } from '../../i18n/translate.pipe'; @Component({ selector: 'app-home', imports: [RouterLink, ItemsCarouselComponent, LangRoutePipe, TranslatePipe], templateUrl: './home.component.html', styleUrls: ['./home.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush }) export class HomeComponent implements OnInit { brandName = environment.brandFullName; isnovo = environment.theme === 'novo'; categories = signal([]); wideCategories = signal>(new Set()); loading = signal(true); error = signal(null); // Memoized computed values for performance topLevelCategories = computed(() => { return this.categories() .filter(cat => cat.parentID === 0) .sort((a, b) => (a.priority ?? Infinity) - (b.priority ?? Infinity)); }); // Memoized item count lookup private itemCountMap = computed(() => { const map = new Map(); this.categories().forEach(cat => map.set(cat.categoryID, cat.itemCount || 0)); return map; }); // Cache subcategories by parent ID private subcategoriesCache = computed(() => { const cache = new Map(); this.categories().forEach(cat => { if (cat.parentID !== 0) { if (!cache.has(cat.parentID)) { cache.set(cat.parentID, []); } cache.get(cat.parentID)!.push(cat); } }); return cache; }); constructor(private apiService: ApiService, private router: Router, private langService: LanguageService) {} ngOnInit(): void { this.loadCategories(); } loadCategories(): void { this.loading.set(true); this.apiService.getCategories().subscribe({ next: (categories) => { this.categories.set(categories); this.loading.set(false); this.detectWideImages(categories); }, error: (err) => { this.error.set('Failed to load categories'); this.loading.set(false); console.error('Error loading categories:', err); } }); } getItemCount(categoryID: number): number { return this.itemCountMap().get(categoryID) || 0; } getSubCategories(parentID: number): Category[] { return this.subcategoriesCache().get(parentID) || []; } isWideCategory(categoryID: number): boolean { return this.wideCategories().has(categoryID); } private detectWideImages(categories: Category[]): void { const topLevel = categories.filter(c => c.parentID === 0); topLevel.forEach(cat => { if (!cat.wideBanner) return; const img = new Image(); img.onload = () => { 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.src = cat.wideBanner; }); } navigateToSearch(): void { const lang = this.langService.currentLanguage(); this.router.navigate([`/${lang}/search`]); } scrollToCatalog(): void { const target = document.getElementById('catalog'); if (!target) return; const targetY = target.getBoundingClientRect().top + window.scrollY; const startY = window.scrollY; const distance = targetY - startY; const duration = 1200; let start: number | null = null; const easeInOutCubic = (t: number) => t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2; const step = (timestamp: number) => { if (!start) start = timestamp; const elapsed = timestamp - start; const progress = Math.min(elapsed / duration, 1); window.scrollTo(0, startY + distance * easeInOutCubic(progress)); if (progress < 1) requestAnimationFrame(step); }; requestAnimationFrame(step); } }