Files
marketplaces/src/app/pages/home/home.component.ts
2026-02-26 23:09:20 +04:00

136 lines
4.2 KiB
TypeScript

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<Category[]>([]);
wideCategories = signal<Set<number>>(new Set());
loading = signal(true);
error = signal<string | null>(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<number, number>();
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<number, Category[]>();
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);
}
}