CAtegory component making
This commit is contained in:
@@ -20,7 +20,7 @@ import { ProductCardComponent } from '../../components/product-card/product-card
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class CategoryComponent implements OnInit, OnDestroy {
|
||||
categoryID = signal<number>(0);
|
||||
categoryId = signal<number>(0);
|
||||
items = signal<Item[]>([]);
|
||||
loading = signal(false);
|
||||
error = signal<string | null>(null);
|
||||
@@ -42,7 +42,7 @@ export class CategoryComponent implements OnInit, OnDestroy {
|
||||
ngOnInit(): void {
|
||||
this.routeSubscription = this.route.params.subscribe(params => {
|
||||
const id = parseInt(params['id'], 10);
|
||||
this.categoryID.set(id);
|
||||
this.categoryId.set(id);
|
||||
this.resetAndLoad();
|
||||
});
|
||||
}
|
||||
@@ -65,7 +65,7 @@ export class CategoryComponent implements OnInit, OnDestroy {
|
||||
this.loading.set(true);
|
||||
this.isLoadingMore = true;
|
||||
|
||||
this.productFacade.getProductsByCategory(this.categoryID(), { count: this.count, skip: this.skip }).subscribe({
|
||||
this.productFacade.getProductsByCategory(this.categoryId(), { count: this.count, skip: this.skip }).subscribe({
|
||||
next: (result) => {
|
||||
const newItems = result.items;
|
||||
// Handle null or empty response
|
||||
|
||||
@@ -18,42 +18,10 @@
|
||||
<h2>{{ parentName() }}</h2>
|
||||
</header>
|
||||
|
||||
<!-- Nested subcategories from API (backOffice format with hasItems) -->
|
||||
@if (nestedSubcategories().length > 0) {
|
||||
<div class="categories-grid">
|
||||
@for (sub of nestedSubcategories(); track trackBySubId($index, sub)) {
|
||||
<a [routerLink]="['/category', sub.id] | langRoute" class="category-card">
|
||||
<div class="category-image">
|
||||
@if (sub.img) {
|
||||
<img [src]="sub.img" [alt]="sub.name" loading="lazy" decoding="async" />
|
||||
} @else {
|
||||
<div class="category-fallback">{{ sub.name.charAt(0) }}</div>
|
||||
}
|
||||
</div>
|
||||
<div class="category-info">
|
||||
<h3 class="category-name">{{ sub.name }}</h3>
|
||||
<div class="category-meta">
|
||||
@if (subcategoryChildCount(sub) > 0) {
|
||||
<span>{{ 'subcategories.childrenCount' | translate:{ count: subcategoryChildCount(sub) } }}</span>
|
||||
}
|
||||
@if (subcategoryItemCount(sub) > 0) {
|
||||
<span>{{ 'subcategories.productsCount' | translate:{ count: subcategoryItemCount(sub) } }}</span>
|
||||
}
|
||||
</div>
|
||||
@if (sub.hasItems && subcategoryChildCount(sub) > 0) {
|
||||
<span class="category-mixed">{{ 'subcategories.includesProducts' | translate }}</span>
|
||||
}
|
||||
</div>
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- Legacy flat subcategories -->
|
||||
@if (subcategories().length > 0) {
|
||||
<div class="categories-grid">
|
||||
@for (cat of subcategories(); track trackByCategoryId($index, cat)) {
|
||||
<a [routerLink]="['/category', cat.categoryID] | langRoute" class="category-card">
|
||||
<a [routerLink]="['/category', cat.id] | langRoute" class="category-card">
|
||||
<div class="category-image">
|
||||
@if (cat.icon) {
|
||||
<img [src]="cat.icon" [alt]="categoryName(cat)" loading="lazy" decoding="async" />
|
||||
@@ -64,13 +32,16 @@
|
||||
<div class="category-info">
|
||||
<h3 class="category-name">{{ categoryName(cat) }}</h3>
|
||||
<div class="category-meta">
|
||||
@if ((cat.categoriesCount ?? 0) > 0) {
|
||||
<span>{{ 'subcategories.childrenCount' | translate:{ count: cat.categoriesCount ?? 0 } }}</span>
|
||||
@if (subcategoryChildCount(cat) > 0) {
|
||||
<span>{{ 'subcategories.childrenCount' | translate:{ count: subcategoryChildCount(cat) } }}</span>
|
||||
}
|
||||
@if ((cat.itemCount ?? 0) > 0) {
|
||||
<span>{{ 'subcategories.productsCount' | translate:{ count: cat.itemCount ?? 0 } }}</span>
|
||||
@if (subcategoryItemCount(cat) > 0) {
|
||||
<span>{{ 'subcategories.productsCount' | translate:{ count: subcategoryItemCount(cat) } }}</span>
|
||||
}
|
||||
</div>
|
||||
@if (subcategoryItemCount(cat) > 0 && subcategoryChildCount(cat) > 0) {
|
||||
<span class="category-mixed">{{ 'subcategories.includesProducts' | translate }}</span>
|
||||
}
|
||||
</div>
|
||||
</a>
|
||||
}
|
||||
|
||||
@@ -2,15 +2,15 @@ import { Component, OnInit, OnDestroy, signal, ChangeDetectionStrategy, inject }
|
||||
import { DecimalPipe } from '@angular/common';
|
||||
import { ActivatedRoute, Router, RouterLink } from '@angular/router';
|
||||
import { CartService, LanguageService } from '../../services';
|
||||
import { Category, Item, Subcategory } from '../../models';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { Item } from '../../models';
|
||||
import { combineLatest, Subscription } from 'rxjs';
|
||||
import { LangRoutePipe } from '../../pipes/lang-route.pipe';
|
||||
import { TranslatePipe } from '../../i18n/translate.pipe';
|
||||
import { TranslateService } from '../../i18n/translate.service';
|
||||
import { getDiscountedPrice, getMainImage, trackByItemId, getBadgeClass, getTranslatedField, getTranslatedCategoryName } from '../../utils/item.utils';
|
||||
import { getDiscountedPrice, getMainImage, trackByItemId, getBadgeClass, getTranslatedField } from '../../utils/item.utils';
|
||||
import { ProductFacade } from '../../facades/platform/product.facade';
|
||||
|
||||
type CategoryNode = Category | Subcategory;
|
||||
import { CategoryFacade } from '../../facades/platform/category.facade';
|
||||
import { Category } from '../../core/categories/models/category-domain.model';
|
||||
|
||||
@Component({
|
||||
selector: 'app-subcategories',
|
||||
@@ -20,10 +20,7 @@ type CategoryNode = Category | Subcategory;
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class SubcategoriesComponent implements OnInit, OnDestroy {
|
||||
categories = signal<Category[]>([]);
|
||||
subcategories = signal<Category[]>([]);
|
||||
/** Nested subcategories from API with hasItems support */
|
||||
nestedSubcategories = signal<Subcategory[]>([]);
|
||||
/** Items belonging directly to this category (when hasItems is true) */
|
||||
categoryItems = signal<Item[]>([]);
|
||||
loading = signal(true);
|
||||
@@ -37,6 +34,7 @@ export class SubcategoriesComponent implements OnInit, OnDestroy {
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
private router: Router,
|
||||
private categoryFacade: CategoryFacade,
|
||||
private productFacade: ProductFacade,
|
||||
private langService: LanguageService,
|
||||
private cartService: CartService
|
||||
@@ -53,44 +51,29 @@ export class SubcategoriesComponent implements OnInit, OnDestroy {
|
||||
this.routeSubscription?.unsubscribe();
|
||||
}
|
||||
|
||||
private loadForParent(parentID: number): void {
|
||||
private loadForParent(parentId: number): void {
|
||||
this.loading.set(true);
|
||||
this.categoryItems.set([]);
|
||||
this.nestedSubcategories.set([]);
|
||||
this.subcategories.set([]);
|
||||
this.error.set(null);
|
||||
this.categoryFacade.selectCategory(parentId);
|
||||
|
||||
this.productFacade.getCategories().subscribe({
|
||||
next: (cats) => {
|
||||
this.categories.set(cats);
|
||||
const parent = this.findCategoryNode(cats, parentID);
|
||||
this.parentName.set(parent ? this.nodeName(parent) : this.i18n.t('home.categoriesTitle'));
|
||||
|
||||
// Check for nested subcategories from API response (backOffice format)
|
||||
const nested = parent?.subcategories || [];
|
||||
const visibleNested = nested
|
||||
.filter(s => this.isDisplayableNestedSubcategory(s))
|
||||
combineLatest([
|
||||
this.categoryFacade.getCategoryById(parentId),
|
||||
this.categoryFacade.getChildren(parentId),
|
||||
]).subscribe({
|
||||
next: ([parent, children]) => {
|
||||
this.parentName.set(parent ? this.categoryName(parent) : this.i18n.t('home.categoriesTitle'));
|
||||
const visibleChildren = children
|
||||
.filter(category => this.isDisplayableCategory(category))
|
||||
.sort((a, b) => (a.priority ?? 0) - (b.priority ?? 0));
|
||||
|
||||
// Also check flat legacy subcategories
|
||||
const flatSubs = cats.filter(c => c.parentID === parentID && this.isDisplayableFlatSubcategory(c));
|
||||
|
||||
if (visibleNested.length > 0) {
|
||||
// Use nested subcategories from API
|
||||
this.nestedSubcategories.set(visibleNested);
|
||||
this.subcategories.set([]);
|
||||
|
||||
// If this category itself has items, load them too
|
||||
this.loadCategoryItems(parentID);
|
||||
} else if (flatSubs.length > 0) {
|
||||
// Legacy flat subcategories
|
||||
this.subcategories.set(flatSubs);
|
||||
this.nestedSubcategories.set([]);
|
||||
|
||||
// Also load items for this category in case it has direct items
|
||||
this.loadCategoryItems(parentID);
|
||||
if (visibleChildren.length > 0) {
|
||||
this.subcategories.set(visibleChildren);
|
||||
this.loadCategoryItems(parentId);
|
||||
} else {
|
||||
// No subcategories: redirect to items list for this category
|
||||
const lang = this.langService.currentLanguage();
|
||||
this.router.navigate([`/${lang}/category`, parentID, 'items'], { replaceUrl: true });
|
||||
this.router.navigate([`/${lang}/category`, parentId, 'items'], { replaceUrl: true });
|
||||
}
|
||||
|
||||
this.loading.set(false);
|
||||
@@ -104,8 +87,8 @@ export class SubcategoriesComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
/** Load items that belong directly to this category */
|
||||
private loadCategoryItems(categoryID: number): void {
|
||||
this.productFacade.getProductsByCategory(categoryID, { count: 50, skip: 0 }).subscribe({
|
||||
private loadCategoryItems(categoryId: number): void {
|
||||
this.productFacade.getProductsByCategory(categoryId, { count: 50, skip: 0 }).subscribe({
|
||||
next: (result) => {
|
||||
this.categoryItems.set(result.items);
|
||||
},
|
||||
@@ -115,58 +98,13 @@ export class SubcategoriesComponent implements OnInit, OnDestroy {
|
||||
});
|
||||
}
|
||||
|
||||
private isDisplayableFlatSubcategory(category: Category): boolean {
|
||||
private isDisplayableCategory(category: Category): boolean {
|
||||
return category.visible !== false
|
||||
&& ((category.itemCount ?? 0) > 0 || (category.subcategories?.length ?? 0) > 0);
|
||||
}
|
||||
|
||||
private isDisplayableNestedSubcategory(subcategory: Subcategory): boolean {
|
||||
return subcategory.visible !== false
|
||||
&& (
|
||||
(subcategory.itemCount ?? 0) > 0
|
||||
|| subcategory.hasItems === true
|
||||
|| (subcategory.subcategories?.length ?? 0) > 0
|
||||
);
|
||||
}
|
||||
|
||||
private findCategoryNode(categories: Category[], categoryID: number): CategoryNode | undefined {
|
||||
for (const category of categories) {
|
||||
if (category.categoryID === categoryID || Number(category.id) === categoryID) {
|
||||
return category;
|
||||
}
|
||||
|
||||
const child = this.findSubcategoryNode(category.subcategories ?? [], categoryID);
|
||||
if (child) {
|
||||
return child;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private findSubcategoryNode(subcategories: Subcategory[], categoryID: number): Subcategory | undefined {
|
||||
for (const subcategory of subcategories) {
|
||||
if (Number(subcategory.id) === categoryID || Number(subcategory.categoryId) === categoryID) {
|
||||
return subcategory;
|
||||
}
|
||||
|
||||
const child = this.findSubcategoryNode(subcategory.subcategories ?? [], categoryID);
|
||||
if (child) {
|
||||
return child;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private nodeName(node: CategoryNode): string {
|
||||
return 'categoryID' in node
|
||||
? getTranslatedCategoryName(node, this.langService.currentLanguage())
|
||||
: node.name;
|
||||
&& ((category.itemCount ?? 0) > 0 || category.children.length > 0);
|
||||
}
|
||||
|
||||
hasSubcategories(): boolean {
|
||||
return this.subcategories().length > 0 || this.nestedSubcategories().length > 0;
|
||||
return this.subcategories().length > 0;
|
||||
}
|
||||
|
||||
addToCart(itemID: number, event: Event): void {
|
||||
@@ -177,18 +115,14 @@ export class SubcategoriesComponent implements OnInit, OnDestroy {
|
||||
|
||||
// TrackBy function for performance optimization
|
||||
trackByCategoryId(_index: number, category: Category): number {
|
||||
return category.categoryID;
|
||||
return category.id;
|
||||
}
|
||||
|
||||
trackBySubId(_index: number, sub: Subcategory): string {
|
||||
return sub.id;
|
||||
subcategoryChildCount(subcategory: Category): number {
|
||||
return subcategory.children.length;
|
||||
}
|
||||
|
||||
subcategoryChildCount(subcategory: Subcategory): number {
|
||||
return subcategory.subcategories?.length ?? 0;
|
||||
}
|
||||
|
||||
subcategoryItemCount(subcategory: Subcategory): number {
|
||||
subcategoryItemCount(subcategory: Category): number {
|
||||
return subcategory.itemCount ?? 0;
|
||||
}
|
||||
|
||||
@@ -199,5 +133,5 @@ export class SubcategoriesComponent implements OnInit, OnDestroy {
|
||||
|
||||
itemName(item: Item): string { return getTranslatedField(item, 'name', this.langService.currentLanguage()); }
|
||||
|
||||
categoryName(cat: Category): string { return getTranslatedCategoryName(cat, this.langService.currentLanguage()); }
|
||||
categoryName(cat: Category): string { return cat.translations[this.langService.currentLanguage()]?.title ?? cat.title; }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user