2026-01-18 18:57:06 +04:00
|
|
|
|
import { Component, OnInit, signal, computed, ChangeDetectionStrategy } from '@angular/core';
|
|
|
|
|
|
import { CommonModule } from '@angular/common';
|
2026-02-14 00:45:17 +04:00
|
|
|
|
import { Router, RouterLink } from '@angular/router';
|
2026-02-14 01:28:08 +04:00
|
|
|
|
import { forkJoin } from 'rxjs';
|
2026-01-18 18:57:06 +04:00
|
|
|
|
import { ApiService } from '../../services';
|
|
|
|
|
|
import { Category } from '../../models';
|
|
|
|
|
|
import { environment } from '../../../environments/environment';
|
|
|
|
|
|
import { ItemsCarouselComponent } from '../../components/items-carousel/items-carousel.component';
|
|
|
|
|
|
|
|
|
|
|
|
@Component({
|
|
|
|
|
|
selector: 'app-home',
|
|
|
|
|
|
standalone: true,
|
|
|
|
|
|
imports: [CommonModule, RouterLink, ItemsCarouselComponent],
|
|
|
|
|
|
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[]>([]);
|
2026-02-14 01:28:08 +04:00
|
|
|
|
itemCounts = signal<Map<number, number>>(new Map());
|
2026-01-18 18:57:06 +04:00
|
|
|
|
loading = signal(true);
|
|
|
|
|
|
error = signal<string | null>(null);
|
|
|
|
|
|
|
|
|
|
|
|
// Memoized computed values for performance
|
|
|
|
|
|
topLevelCategories = computed(() => {
|
|
|
|
|
|
return this.categories().filter(cat => cat.parentID === 0);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 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;
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2026-02-14 00:45:17 +04:00
|
|
|
|
constructor(private apiService: ApiService, private router: Router) {}
|
2026-01-18 18:57:06 +04:00
|
|
|
|
|
|
|
|
|
|
ngOnInit(): void {
|
|
|
|
|
|
this.loadCategories();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
loadCategories(): void {
|
|
|
|
|
|
this.loading.set(true);
|
|
|
|
|
|
this.apiService.getCategories().subscribe({
|
|
|
|
|
|
next: (categories) => {
|
|
|
|
|
|
this.categories.set(categories);
|
|
|
|
|
|
this.loading.set(false);
|
2026-02-14 01:28:08 +04:00
|
|
|
|
this.loadItemCounts();
|
2026-01-18 18:57:06 +04:00
|
|
|
|
},
|
|
|
|
|
|
error: (err) => {
|
|
|
|
|
|
this.error.set('Failed to load categories');
|
|
|
|
|
|
this.loading.set(false);
|
|
|
|
|
|
console.error('Error loading categories:', err);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-14 01:28:08 +04:00
|
|
|
|
private loadItemCounts(): void {
|
|
|
|
|
|
const topLevel = this.topLevelCategories();
|
|
|
|
|
|
if (topLevel.length === 0) return;
|
|
|
|
|
|
|
|
|
|
|
|
const requests: Record<string, ReturnType<ApiService['getCategoryItems']>> = {};
|
|
|
|
|
|
topLevel.forEach(cat => {
|
|
|
|
|
|
requests[cat.categoryID.toString()] = this.apiService.getCategoryItems(cat.categoryID, 1000);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
forkJoin(requests).subscribe({
|
|
|
|
|
|
next: (results) => {
|
|
|
|
|
|
const counts = new Map<number, number>();
|
|
|
|
|
|
Object.entries(results).forEach(([id, items]) => {
|
|
|
|
|
|
counts.set(Number(id), items.length);
|
|
|
|
|
|
});
|
|
|
|
|
|
this.itemCounts.set(counts);
|
|
|
|
|
|
},
|
|
|
|
|
|
error: (err) => console.error('Error loading item counts:', err)
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
getItemCount(categoryID: number): number {
|
|
|
|
|
|
return this.itemCounts().get(categoryID) || 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-18 18:57:06 +04:00
|
|
|
|
getTopLevelCategories(): Category[] {
|
|
|
|
|
|
return this.topLevelCategories();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
getSubCategories(parentID: number): Category[] {
|
|
|
|
|
|
return this.subcategoriesCache().get(parentID) || [];
|
|
|
|
|
|
}
|
2026-02-14 00:45:17 +04:00
|
|
|
|
|
|
|
|
|
|
navigateToSearch(): void {
|
|
|
|
|
|
this.router.navigate(['/search']);
|
|
|
|
|
|
}
|
2026-02-14 01:28:08 +04:00
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
}
|
2026-01-18 18:57:06 +04:00
|
|
|
|
}
|