Support nested marketplace categories
This commit is contained in:
@@ -150,6 +150,9 @@ export const en: Translations = {
|
|||||||
emptyDesc: 'There are no subcategories in this section yet, but they will appear soon',
|
emptyDesc: 'There are no subcategories in this section yet, but they will appear soon',
|
||||||
goHome: 'Go home',
|
goHome: 'Go home',
|
||||||
itemsInCategory: 'Items in this category',
|
itemsInCategory: 'Items in this category',
|
||||||
|
childrenCount: '{{count}} categories',
|
||||||
|
productsCount: '{{count}} products',
|
||||||
|
includesProducts: 'Includes products',
|
||||||
},
|
},
|
||||||
itemDetail: {
|
itemDetail: {
|
||||||
loading: 'Loading...',
|
loading: 'Loading...',
|
||||||
|
|||||||
@@ -150,6 +150,9 @@ export const hy: Translations = {
|
|||||||
emptyDesc: 'Այս բաժնում դեռ ենթակատեգորիաներ չկան',
|
emptyDesc: 'Այս բաժնում դեռ ենթակատեգորիաներ չկան',
|
||||||
goHome: 'Գլխավոր',
|
goHome: 'Գլխավոր',
|
||||||
itemsInCategory: 'Ապրանքներ այս կատեգորիայում',
|
itemsInCategory: 'Ապրանքներ այս կատեգորիայում',
|
||||||
|
childrenCount: '{{count}} կատեգորիա',
|
||||||
|
productsCount: '{{count}} ապրանք',
|
||||||
|
includesProducts: 'Կան ապրանքներ',
|
||||||
},
|
},
|
||||||
itemDetail: {
|
itemDetail: {
|
||||||
loading: 'Բեռնում...',
|
loading: 'Բեռնում...',
|
||||||
|
|||||||
@@ -150,6 +150,9 @@ export const ru: Translations = {
|
|||||||
emptyDesc: 'В этом разделе ещё нет подкатегорий, но скоро они появятся',
|
emptyDesc: 'В этом разделе ещё нет подкатегорий, но скоро они появятся',
|
||||||
goHome: 'На главную',
|
goHome: 'На главную',
|
||||||
itemsInCategory: 'Товары в этой категории',
|
itemsInCategory: 'Товары в этой категории',
|
||||||
|
childrenCount: '{{count}} категорий',
|
||||||
|
productsCount: '{{count}} товаров',
|
||||||
|
includesProducts: 'Есть товары',
|
||||||
},
|
},
|
||||||
itemDetail: {
|
itemDetail: {
|
||||||
loading: 'Загрузка...',
|
loading: 'Загрузка...',
|
||||||
|
|||||||
@@ -148,6 +148,9 @@ export interface Translations {
|
|||||||
emptyDesc: string;
|
emptyDesc: string;
|
||||||
goHome: string;
|
goHome: string;
|
||||||
itemsInCategory: string;
|
itemsInCategory: string;
|
||||||
|
childrenCount: string;
|
||||||
|
productsCount: string;
|
||||||
|
includesProducts: string;
|
||||||
};
|
};
|
||||||
itemDetail: {
|
itemDetail: {
|
||||||
loading: string;
|
loading: string;
|
||||||
|
|||||||
@@ -32,8 +32,16 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="category-info">
|
<div class="category-info">
|
||||||
<h3 class="category-name">{{ sub.name }}</h3>
|
<h3 class="category-name">{{ sub.name }}</h3>
|
||||||
@if (sub.itemCount) {
|
<div class="category-meta">
|
||||||
<span class="category-count">{{ sub.itemCount }}</span>
|
@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>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
@@ -55,6 +63,14 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="category-info">
|
<div class="category-info">
|
||||||
<h3 class="category-name">{{ categoryName(cat) }}</h3>
|
<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>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -241,6 +241,34 @@
|
|||||||
color: #697777;
|
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
|
// Items section within subcategories page
|
||||||
.category-items-section {
|
.category-items-section {
|
||||||
margin-top: 40px;
|
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 { getDiscountedPrice, getMainImage, trackByItemId, getBadgeClass, getTranslatedField, getTranslatedCategoryName } from '../../utils/item.utils';
|
||||||
import { ProductFacade } from '../../facades/platform/product.facade';
|
import { ProductFacade } from '../../facades/platform/product.facade';
|
||||||
|
|
||||||
|
type CategoryNode = Category | Subcategory;
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-subcategories',
|
selector: 'app-subcategories',
|
||||||
imports: [DecimalPipe, RouterLink, LangRoutePipe, TranslatePipe],
|
imports: [DecimalPipe, RouterLink, LangRoutePipe, TranslatePipe],
|
||||||
@@ -59,12 +61,14 @@ export class SubcategoriesComponent implements OnInit, OnDestroy {
|
|||||||
this.productFacade.getCategories().subscribe({
|
this.productFacade.getCategories().subscribe({
|
||||||
next: (cats) => {
|
next: (cats) => {
|
||||||
this.categories.set(cats);
|
this.categories.set(cats);
|
||||||
const parent = cats.find(c => c.categoryID === parentID);
|
const parent = this.findCategoryNode(cats, parentID);
|
||||||
this.parentName.set(parent ? getTranslatedCategoryName(parent, this.langService.currentLanguage()) : this.i18n.t('home.categoriesTitle'));
|
this.parentName.set(parent ? this.nodeName(parent) : this.i18n.t('home.categoriesTitle'));
|
||||||
|
|
||||||
// Check for nested subcategories from API response (backOffice format)
|
// Check for nested subcategories from API response (backOffice format)
|
||||||
const nested = parent?.subcategories || [];
|
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
|
// Also check flat legacy subcategories
|
||||||
const flatSubs = cats.filter(c => c.parentID === parentID && this.isDisplayableFlatSubcategory(c));
|
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 {
|
hasSubcategories(): boolean {
|
||||||
return this.subcategories().length > 0 || this.nestedSubcategories().length > 0;
|
return this.subcategories().length > 0 || this.nestedSubcategories().length > 0;
|
||||||
}
|
}
|
||||||
@@ -144,6 +184,14 @@ export class SubcategoriesComponent implements OnInit, OnDestroy {
|
|||||||
return sub.id;
|
return sub.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
subcategoryChildCount(subcategory: Subcategory): number {
|
||||||
|
return subcategory.subcategories?.length ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
subcategoryItemCount(subcategory: Subcategory): number {
|
||||||
|
return subcategory.itemCount ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
readonly getDiscountedPrice = getDiscountedPrice;
|
readonly getDiscountedPrice = getDiscountedPrice;
|
||||||
readonly getMainImage = getMainImage;
|
readonly getMainImage = getMainImage;
|
||||||
readonly trackByItemId = trackByItemId;
|
readonly trackByItemId = trackByItemId;
|
||||||
|
|||||||
Reference in New Issue
Block a user