import { Component, signal, HostListener, OnDestroy, ChangeDetectionStrategy } from '@angular/core'; import { DecimalPipe } from '@angular/common'; import { FormsModule } from '@angular/forms'; import { RouterLink } from '@angular/router'; import { ApiService, CartService } from '../../services'; import { Item } from '../../models'; import { Subject, Subscription } from 'rxjs'; import { debounceTime, distinctUntilChanged } from 'rxjs/operators'; import { getDiscountedPrice, getMainImage, trackByItemId } from '../../utils/item.utils'; @Component({ selector: 'app-search', imports: [DecimalPipe, FormsModule, RouterLink], templateUrl: './search.component.html', styleUrls: ['./search.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush }) export class SearchComponent implements OnDestroy { searchQuery = ''; items = signal([]); loading = signal(false); error = signal(null); hasMore = signal(true); totalResults = signal(0); private skip = 0; private readonly count = 20; private isLoadingMore = false; private searchSubject = new Subject(); private searchSubscription: Subscription; constructor( private apiService: ApiService, private cartService: CartService ) { this.searchSubscription = this.searchSubject .pipe( debounceTime(300), distinctUntilChanged() ) .subscribe(query => { if (query.trim().length >= 3 || query.trim().length === 0) { this.performSearch(query); } }); } ngOnDestroy(): void { this.searchSubscription.unsubscribe(); this.searchSubject.complete(); } onSearchInput(query: string): void { this.searchQuery = query; this.searchSubject.next(query); } performSearch(query: string): void { if (!query.trim()) { this.items.set([]); this.hasMore.set(true); this.totalResults.set(0); return; } this.items.set([]); this.skip = 0; this.hasMore.set(true); this.totalResults.set(0); this.loadResults(); } loadResults(): void { if (this.isLoadingMore || !this.hasMore() || !this.searchQuery.trim()) return; this.loading.set(true); this.isLoadingMore = true; this.apiService.searchItems(this.searchQuery, this.count, this.skip).subscribe({ next: (response) => { // Update total results (only on first load) if (this.skip === 0) { this.totalResults.set(response.total); } // Handle empty results if (!response.items || response.items.length === 0) { this.hasMore.set(false); } else { // Check if there are more items to load if (response.items.length < this.count || this.skip + response.items.length >= response.total) { this.hasMore.set(false); } this.items.update(current => [...current, ...response.items]); this.skip += response.items.length; } this.loading.set(false); this.isLoadingMore = false; }, error: (err) => { this.error.set('Ошибка при поиске товаров'); this.loading.set(false); this.isLoadingMore = false; console.error('Error searching items:', err); } }); } private scrollTimeout: any; @HostListener('window:scroll') onScroll(): void { if (this.scrollTimeout) clearTimeout(this.scrollTimeout); this.scrollTimeout = setTimeout(() => { const scrollPosition = window.innerHeight + window.scrollY; const bottomPosition = document.documentElement.scrollHeight - 500; if (scrollPosition >= bottomPosition && !this.loading() && this.hasMore()) { this.loadResults(); } }, 100); } addToCart(itemID: number, event: Event): void { event.preventDefault(); event.stopPropagation(); this.cartService.addItem(itemID); } readonly getDiscountedPrice = getDiscountedPrice; readonly getMainImage = getMainImage; readonly trackByItemId = trackByItemId; }