very first commit
This commit is contained in:
141
src/app/pages/search/search.component.ts
Normal file
141
src/app/pages/search/search.component.ts
Normal file
@@ -0,0 +1,141 @@
|
||||
import { Component, signal, HostListener, OnDestroy, ChangeDetectionStrategy } from '@angular/core';
|
||||
import { CommonModule } 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';
|
||||
|
||||
@Component({
|
||||
selector: 'app-search',
|
||||
standalone: true,
|
||||
imports: [CommonModule, FormsModule, RouterLink],
|
||||
templateUrl: './search.component.html',
|
||||
styleUrls: ['./search.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class SearchComponent implements OnDestroy {
|
||||
searchQuery = '';
|
||||
items = signal<Item[]>([]);
|
||||
loading = signal(false);
|
||||
error = signal<string | null>(null);
|
||||
hasMore = signal(true);
|
||||
totalResults = signal<number>(0);
|
||||
|
||||
private skip = 0;
|
||||
private readonly count = 20;
|
||||
private isLoadingMore = false;
|
||||
private searchSubject = new Subject<string>();
|
||||
private searchSubscription: Subscription;
|
||||
|
||||
constructor(
|
||||
private apiService: ApiService,
|
||||
private cartService: CartService
|
||||
) {
|
||||
this.searchSubscription = this.searchSubject
|
||||
.pipe(
|
||||
debounceTime(300),
|
||||
distinctUntilChanged()
|
||||
)
|
||||
.subscribe(query => {
|
||||
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);
|
||||
}
|
||||
|
||||
getDiscountedPrice(item: Item): number {
|
||||
return item.price * (1 - item.discount / 100);
|
||||
}
|
||||
|
||||
getMainImage(item: Item): string {
|
||||
return item.photos?.[0]?.url || '';
|
||||
}
|
||||
|
||||
// TrackBy function for performance optimization
|
||||
trackByItemId(index: number, item: Item): number {
|
||||
return item.itemID;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user