Support nested marketplace categories
This commit is contained in:
@@ -32,8 +32,16 @@
|
||||
</div>
|
||||
<div class="category-info">
|
||||
<h3 class="category-name">{{ sub.name }}</h3>
|
||||
@if (sub.itemCount) {
|
||||
<span class="category-count">{{ sub.itemCount }}</span>
|
||||
<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>
|
||||
@@ -55,6 +63,14 @@
|
||||
</div>
|
||||
<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 ((cat.itemCount ?? 0) > 0) {
|
||||
<span>{{ 'subcategories.productsCount' | translate:{ count: cat.itemCount ?? 0 } }}</span>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
}
|
||||
|
||||
@@ -241,6 +241,34 @@
|
||||
color: #697777;
|
||||
}
|
||||
|
||||
.category-meta {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
min-height: 20px;
|
||||
font-family: "DM Sans", sans-serif;
|
||||
font-size: 0.8rem;
|
||||
color: #697777;
|
||||
|
||||
span {
|
||||
padding: 2px 7px;
|
||||
border-radius: 999px;
|
||||
background: rgba(73, 118, 113, 0.1);
|
||||
color: #3d635f;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
.category-mixed {
|
||||
width: fit-content;
|
||||
padding: 2px 7px;
|
||||
border-radius: 999px;
|
||||
background: #eef1f1;
|
||||
color: #697777;
|
||||
font-family: "DM Sans", sans-serif;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
// Items section within subcategories page
|
||||
.category-items-section {
|
||||
margin-top: 40px;
|
||||
|
||||
@@ -10,6 +10,8 @@ import { TranslateService } from '../../i18n/translate.service';
|
||||
import { getDiscountedPrice, getMainImage, trackByItemId, getBadgeClass, getTranslatedField, getTranslatedCategoryName } from '../../utils/item.utils';
|
||||
import { ProductFacade } from '../../facades/platform/product.facade';
|
||||
|
||||
type CategoryNode = Category | Subcategory;
|
||||
|
||||
@Component({
|
||||
selector: 'app-subcategories',
|
||||
imports: [DecimalPipe, RouterLink, LangRoutePipe, TranslatePipe],
|
||||
@@ -59,12 +61,14 @@ export class SubcategoriesComponent implements OnInit, OnDestroy {
|
||||
this.productFacade.getCategories().subscribe({
|
||||
next: (cats) => {
|
||||
this.categories.set(cats);
|
||||
const parent = cats.find(c => c.categoryID === parentID);
|
||||
this.parentName.set(parent ? getTranslatedCategoryName(parent, this.langService.currentLanguage()) : this.i18n.t('home.categoriesTitle'));
|
||||
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));
|
||||
const visibleNested = nested
|
||||
.filter(s => this.isDisplayableNestedSubcategory(s))
|
||||
.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));
|
||||
@@ -125,6 +129,42 @@ export class SubcategoriesComponent implements OnInit, OnDestroy {
|
||||
);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
hasSubcategories(): boolean {
|
||||
return this.subcategories().length > 0 || this.nestedSubcategories().length > 0;
|
||||
}
|
||||
@@ -144,6 +184,14 @@ export class SubcategoriesComponent implements OnInit, OnDestroy {
|
||||
return sub.id;
|
||||
}
|
||||
|
||||
subcategoryChildCount(subcategory: Subcategory): number {
|
||||
return subcategory.subcategories?.length ?? 0;
|
||||
}
|
||||
|
||||
subcategoryItemCount(subcategory: Subcategory): number {
|
||||
return subcategory.itemCount ?? 0;
|
||||
}
|
||||
|
||||
readonly getDiscountedPrice = getDiscountedPrice;
|
||||
readonly getMainImage = getMainImage;
|
||||
readonly trackByItemId = trackByItemId;
|
||||
|
||||
Reference in New Issue
Block a user