Merge remote-tracking branch 'origin' into back-office-integration

This commit is contained in:
sdarbinyan
2026-02-28 16:13:14 +04:00
217 changed files with 10170 additions and 5789 deletions

View File

@@ -2,7 +2,7 @@
@if (error()) {
<div class="error">
<p>{{ error() }}</p>
<button (click)="resetAndLoad()">Попробовать снова</button>
<button (click)="resetAndLoad()">{{ 'category.retry' | translate }}</button>
</div>
}
@@ -10,7 +10,7 @@
<div class="items-grid">
@for (item of items(); track trackByItemId($index, item)) {
<div class="item-card">
<a [routerLink]="['/item', item.itemID]" class="item-link">
<a [routerLink]="['/item', item.itemID] | langRoute" class="item-link">
<div class="item-image">
<img [src]="getMainImage(item)" [alt]="item.name" loading="lazy" decoding="async" width="300" height="300" />
@if (item.discount > 0) {
@@ -53,7 +53,7 @@
</a>
<button class="add-to-cart-btn" (click)="addToCart(item.itemID, $event)">
В корзину
{{ 'category.addToCart' | translate }}
</button>
</div>
}
@@ -62,13 +62,13 @@
@if (loading() && items().length > 0) {
<div class="loading-more">
<div class="spinner"></div>
<p>Загрузка...</p>
<p>{{ 'category.loadingMore' | translate }}</p>
</div>
}
@if (!hasMore() && items().length > 0) {
<div class="no-more">
<p>Все товары загружены</p>
<p>{{ 'category.allLoaded' | translate }}</p>
</div>
}
@@ -82,16 +82,16 @@
<path d="M10 14H14" stroke="#d3dad9" stroke-width="1.5" stroke-linecap="round"/>
</svg>
</div>
<h3>Упс! Здесь пока пусто</h3>
<p>В этой категории ещё нет товаров, но скоро они появятся</p>
<a routerLink="/" class="no-items-btn">На главную</a>
<h3>{{ 'category.emptyTitle' | translate }}</h3>
<p>{{ 'category.emptyDesc' | translate }}</p>
<a [routerLink]="'/' | langRoute" class="no-items-btn">{{ 'category.goHome' | translate }}</a>
</div>
}
@if (loading() && items().length === 0) {
<div class="loading-initial">
<div class="spinner"></div>
<p>Загрузка товаров...</p>
<p>{{ 'category.loading' | translate }}</p>
</div>
}
}

View File

@@ -5,10 +5,12 @@ import { ApiService, CartService } from '../../services';
import { Item } from '../../models';
import { Subscription } from 'rxjs';
import { getDiscountedPrice, getMainImage, trackByItemId, getBadgeClass } from '../../utils/item.utils';
import { LangRoutePipe } from '../../pipes/lang-route.pipe';
import { TranslatePipe } from '../../i18n/translate.pipe';
@Component({
selector: 'app-category',
imports: [DecimalPipe, RouterLink],
imports: [DecimalPipe, RouterLink, LangRoutePipe, TranslatePipe],
templateUrl: './category.component.html',
styleUrls: ['./category.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
@@ -24,6 +26,7 @@ export class CategoryComponent implements OnInit, OnDestroy {
private readonly count = 20;
private isLoadingMore = false;
private routeSubscription?: Subscription;
private scrollTimeout?: ReturnType<typeof setTimeout>;
constructor(
private route: ActivatedRoute,
@@ -41,6 +44,7 @@ export class CategoryComponent implements OnInit, OnDestroy {
ngOnDestroy(): void {
this.routeSubscription?.unsubscribe();
if (this.scrollTimeout) clearTimeout(this.scrollTimeout);
}
resetAndLoad(): void {
@@ -80,8 +84,6 @@ export class CategoryComponent implements OnInit, OnDestroy {
});
}
private scrollTimeout: any;
@HostListener('window:scroll')
onScroll(): void {
if (this.scrollTimeout) clearTimeout(this.scrollTimeout);

View File

@@ -2,14 +2,14 @@
@if (loading()) {
<div class="loading">
<div class="spinner"></div>
<p>Загрузка подкатегорий...</p>
<p>{{ 'subcategories.loading' | translate }}</p>
</div>
}
@if (error()) {
<div class="error">
<p>{{ error() }}</p>
<button (click)="ngOnInit()">Попробовать снова</button>
<button (click)="ngOnInit()">{{ 'subcategories.retry' | translate }}</button>
</div>
}
@@ -21,7 +21,7 @@
@if (subcategories().length > 0) {
<div class="categories-grid">
@for (cat of subcategories(); track trackByCategoryId($index, cat)) {
<a [routerLink]="['/category', cat.categoryID]" class="category-card">
<a [routerLink]="['/category', cat.categoryID] | langRoute" class="category-card">
<div class="category-image">
@if (cat.icon) {
<img [src]="cat.icon" [alt]="cat.name" loading="lazy" decoding="async" />
@@ -45,9 +45,9 @@
<rect x="14" y="14" width="7" height="7" rx="1.5" stroke="#d3dad9" stroke-width="1.5"/>
</svg>
</div>
<h3>Упс! Подкатегорий пока нет</h3>
<p>В этом разделе ещё нет подкатегорий, но скоро они появятся</p>
<a routerLink="/" class="no-subcats-btn">На главную</a>
<h3>{{ 'subcategories.emptyTitle' | translate }}</h3>
<p>{{ 'subcategories.emptyDesc' | translate }}</p>
<a [routerLink]="'/' | langRoute" class="no-subcats-btn">{{ 'subcategories.goHome' | translate }}</a>
</div>
}
}

View File

@@ -1,35 +1,48 @@
import { Component, OnInit, signal, ChangeDetectionStrategy } from '@angular/core';
import { Component, OnInit, OnDestroy, signal, ChangeDetectionStrategy, inject } from '@angular/core';
import { ActivatedRoute, Router, RouterLink } from '@angular/router';
import { ApiService } from '../../services';
import { ApiService, LanguageService } from '../../services';
import { Category } from '../../models';
import { Subscription } from 'rxjs';
import { LangRoutePipe } from '../../pipes/lang-route.pipe';
import { TranslatePipe } from '../../i18n/translate.pipe';
import { TranslateService } from '../../i18n/translate.service';
@Component({
selector: 'app-subcategories',
imports: [RouterLink],
imports: [RouterLink, LangRoutePipe, TranslatePipe],
templateUrl: './subcategories.component.html',
styleUrls: ['./subcategories.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class SubcategoriesComponent implements OnInit {
export class SubcategoriesComponent implements OnInit, OnDestroy {
categories = signal<Category[]>([]);
subcategories = signal<Category[]>([]);
loading = signal(true);
error = signal<string | null>(null);
private i18n = inject(TranslateService);
parentName = signal<string>('');
private routeSubscription?: Subscription;
constructor(
private route: ActivatedRoute,
private router: Router,
private apiService: ApiService
private apiService: ApiService,
private langService: LanguageService
) {}
ngOnInit(): void {
this.route.params.subscribe(params => {
this.routeSubscription = this.route.params.subscribe(params => {
const id = parseInt(params['id'], 10);
this.loadForParent(id);
});
}
ngOnDestroy(): void {
this.routeSubscription?.unsubscribe();
}
private loadForParent(parentID: number): void {
this.loading.set(true);
this.apiService.getCategories().subscribe({
@@ -37,11 +50,12 @@ export class SubcategoriesComponent implements OnInit {
this.categories.set(cats);
const subs = cats.filter(c => c.parentID === parentID);
const parent = cats.find(c => c.categoryID === parentID);
this.parentName.set(parent ? parent.name : 'Категория');
this.parentName.set(parent ? parent.name : this.i18n.t('home.categoriesTitle'));
if (!subs || subs.length === 0) {
// No subcategories: redirect to items list for this category
this.router.navigate(['/category', parentID, 'items'], { replaceUrl: true });
const lang = this.langService.currentLanguage();
this.router.navigate([`/${lang}/category`, parentID, 'items'], { replaceUrl: true });
} else {
this.subcategories.set(subs);
}