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

190 lines
6.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { Component, OnInit, OnDestroy, signal, ChangeDetectionStrategy, inject } from '@angular/core';
import { DecimalPipe } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { ActivatedRoute, RouterLink } from '@angular/router';
import { ApiService, CartService, TelegramService, SeoService } from '../../services';
import { Item } from '../../models';
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
import { Subscription } from 'rxjs';
import { environment } from '../../../environments/environment';
import { SecurityContext } from '@angular/core';
import { getDiscountedPrice } 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-item-detail',
imports: [DecimalPipe, RouterLink, FormsModule, LangRoutePipe, TranslatePipe],
templateUrl: './item-detail.component.html',
styleUrls: ['./item-detail.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ItemDetailComponent implements OnInit, OnDestroy {
item = signal<Item | null>(null);
selectedPhotoIndex = signal(0);
loading = signal(true);
error = signal<string | null>(null);
isnovo = environment.theme === 'novo';
newReview = {
rating: 0,
comment: '',
anonymous: false
};
reviewSubmitStatus = signal<'idle' | 'loading' | 'success' | 'error'>('idle');
private routeSubscription?: Subscription;
private reviewResetTimeout?: ReturnType<typeof setTimeout>;
private reviewErrorTimeout?: ReturnType<typeof setTimeout>;
private reloadTimeout?: ReturnType<typeof setTimeout>;
private seoService = inject(SeoService);
private i18n = inject(TranslateService);
constructor(
private route: ActivatedRoute,
private apiService: ApiService,
private cartService: CartService,
private telegramService: TelegramService,
private sanitizer: DomSanitizer
) {}
ngOnInit(): void {
this.routeSubscription = this.route.params.subscribe(params => {
const id = parseInt(params['id'], 10);
this.loadItem(id);
});
}
ngOnDestroy(): void {
this.routeSubscription?.unsubscribe();
if (this.reviewResetTimeout) clearTimeout(this.reviewResetTimeout);
if (this.reviewErrorTimeout) clearTimeout(this.reviewErrorTimeout);
if (this.reloadTimeout) clearTimeout(this.reloadTimeout);
this.seoService.resetToDefaults();
}
loadItem(itemID: number): void {
this.loading.set(true);
this.apiService.getItem(itemID).subscribe({
next: (item) => {
this.item.set(item);
this.seoService.setItemMeta(item);
this.loading.set(false);
},
error: (err) => {
this.error.set('Failed to load item');
this.loading.set(false);
console.error('Error loading item:', err);
}
});
}
selectPhoto(index: number): void {
this.selectedPhotoIndex.set(index);
}
addToCart(): void {
const currentItem = this.item();
if (currentItem) {
this.cartService.addItem(currentItem.itemID);
}
}
getDiscountedPrice(): number {
const currentItem = this.item();
if (!currentItem) return 0;
return getDiscountedPrice(currentItem);
}
getSafeHtml(html: string): SafeHtml {
return this.sanitizer.sanitize(SecurityContext.HTML, html) || '';
}
getRatingStars(rating: number): string {
const fullStars = Math.floor(rating);
const hasHalfStar = rating % 1 >= 0.5;
let stars = '★'.repeat(fullStars);
if (hasHalfStar) stars += '☆';
return stars;
}
formatDate(timestamp: string): string {
const date = new Date(timestamp);
const now = new Date();
const diffMs = now.getTime() - date.getTime();
const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
if (diffDays === 0) return this.i18n.t('itemDetail.today');
if (diffDays === 1) return this.i18n.t('itemDetail.yesterday');
if (diffDays < 7) return `${diffDays} ${this.i18n.t('itemDetail.daysAgo')}`;
if (diffDays < 30) return `${Math.floor(diffDays / 7)} ${this.i18n.t('itemDetail.weeksAgo')}`;
return date.toLocaleDateString('ru-RU', {
day: 'numeric',
month: 'long',
year: 'numeric'
});
}
setRating(rating: number): void {
this.newReview.rating = rating;
}
getUserDisplayName(): string | null {
if (!this.telegramService.isTelegramApp()) {
return this.i18n.t('itemDetail.defaultUser');
}
return this.telegramService.getDisplayName();
}
submitReview(): void {
if (!this.newReview.rating || !this.newReview.comment.trim()) {
return;
}
const currentItem = this.item();
if (!currentItem) return;
this.reviewSubmitStatus.set('loading');
const reviewData = {
itemID: currentItem.itemID,
rating: this.newReview.rating,
comment: this.newReview.comment.trim(),
username: this.newReview.anonymous ? null : this.getUserDisplayName(),
userId: this.telegramService.getUserId(),
timestamp: new Date().toISOString()
};
this.apiService.submitReview(reviewData).subscribe({
next: (response) => {
this.reviewSubmitStatus.set('success');
this.newReview = { rating: 0, comment: '', anonymous: false };
// Сброс состояния через 3 секунды
this.reviewResetTimeout = setTimeout(() => {
this.reviewSubmitStatus.set('idle');
}, 3000);
// Перезагрузить данные товара после отправки отзыва
this.reloadTimeout = setTimeout(() => {
this.loadItem(currentItem.itemID);
}, 500);
},
error: (err) => {
console.error('Error submitting review:', err);
this.reviewSubmitStatus.set('error');
// Сброс состояния об ошибке через 5 секунд
this.reviewErrorTimeout = setTimeout(() => {
this.reviewSubmitStatus.set('idle');
}, 5000);
}
});
}
}