140 lines
4.3 KiB
TypeScript
140 lines
4.3 KiB
TypeScript
import { Component, signal, HostListener, OnDestroy, ChangeDetectionStrategy, inject } 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';
|
|
import { LangRoutePipe } from '../../pipes/lang-route.pipe';
|
|
import { TranslatePipe } from '../../i18n/translate.pipe';
|
|
import { TranslateService } from '../../i18n/translate.service';
|
|
|
|
@Component({
|
|
selector: 'app-search',
|
|
imports: [DecimalPipe, FormsModule, RouterLink, LangRoutePipe, TranslatePipe],
|
|
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;
|
|
private i18n = inject(TranslateService);
|
|
|
|
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();
|
|
if (this.scrollTimeout) clearTimeout(this.scrollTimeout);
|
|
}
|
|
|
|
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.i18n.t('home.errorTitle'));
|
|
this.loading.set(false);
|
|
this.isLoadingMore = false;
|
|
console.error('Error searching items:', err);
|
|
}
|
|
});
|
|
}
|
|
|
|
private scrollTimeout?: ReturnType<typeof setTimeout>;
|
|
|
|
@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;
|
|
}
|