Files
marketplaces/src/app/pages/item-detail/item-detail.component.ts

242 lines
7.8 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, LanguageService, SeoService } from '../../services';
import { Item, DescriptionField } 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, getAllImages, getStockStatus, getBadgeClass, getTranslatedField } 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,
private languageService: LanguageService
) {}
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);
}
// BackOffice integration helpers
getItemName(): string {
const currentItem = this.item();
if (!currentItem) return '';
const lang = this.languageService.currentLanguage();
return getTranslatedField(currentItem, 'name', lang);
}
getSimpleDescription(): string {
const currentItem = this.item();
if (!currentItem) return '';
const lang = this.languageService.currentLanguage();
return getTranslatedField(currentItem, 'simpleDescription', lang);
}
hasDescriptionFields(): boolean {
const currentItem = this.item();
return !!(currentItem?.descriptionFields && currentItem.descriptionFields.length > 0);
}
getTranslatedDescriptionFields(): DescriptionField[] {
const currentItem = this.item();
if (!currentItem) return [];
const lang = this.languageService.currentLanguage();
const translation = currentItem.translations?.[lang];
if (translation?.description && translation.description.length > 0) {
return translation.description;
}
return currentItem.descriptionFields || [];
}
getStockClass(): string {
const currentItem = this.item();
if (!currentItem) return 'high';
return getStockStatus(currentItem);
}
getStockLabel(): string {
const status = this.getStockClass();
switch (status) {
case 'high': return 'В наличии';
case 'medium': return 'Заканчивается';
case 'low': return 'Последние штуки';
case 'out': return 'Нет в наличии';
default: return 'В наличии';
}
}
readonly getBadgeClass = getBadgeClass;
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);
}
});
}
}