Add related products to detail page

This commit is contained in:
sdarbinyan
2026-07-05 01:07:53 +04:00
parent d7d73c2a10
commit b676cec4e9
7 changed files with 80 additions and 1 deletions

View File

@@ -294,6 +294,23 @@
}
</div>
</div>
@if (relatedProducts().length > 0) {
<section class="related-products" aria-labelledby="novo-related-products-title">
<h2 id="novo-related-products-title">{{ 'itemDetail.relatedProducts' | translate }}</h2>
<div class="related-products-grid">
@for (related of relatedProducts(); track related.itemID) {
<app-product-card
[item]="related"
[title]="relatedItemName(related)"
[addToCartLabel]="'itemDetail.addToCart' | translate"
appearance="compact"
(addToCart)="addRelatedToCart($event.itemID, $event.event)"
/>
}
</div>
</section>
}
}
</div>
} @else {
@@ -634,6 +651,23 @@
</div>
</div>
}
@if (relatedProducts().length > 0) {
<section class="related-products" aria-labelledby="dx-related-products-title">
<h2 id="dx-related-products-title">{{ 'itemDetail.relatedProducts' | translate }}</h2>
<div class="related-products-grid">
@for (related of relatedProducts(); track related.itemID) {
<app-product-card
[item]="related"
[title]="relatedItemName(related)"
[addToCartLabel]="'itemDetail.addToCart' | translate"
appearance="compact"
(addToCart)="addRelatedToCart($event.itemID, $event.event)"
/>
}
</div>
</section>
}
}
</div>
}

View File

@@ -65,6 +65,23 @@ $dx-card-bg: #f5f3f9;
margin-bottom: 48px;
}
.related-products {
margin-top: 48px;
h2 {
margin: 0 0 20px;
color: $dx-dark;
font-size: 1.5rem;
font-weight: 800;
}
}
.related-products-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
gap: 24px;
}
// Gallery: thumbnails left + main photo right
.dx-gallery {
display: flex;

View File

@@ -14,10 +14,11 @@ import { TranslatePipe } from '../../i18n/translate.pipe';
import { TranslateService } from '../../i18n/translate.service';
import { UiRuntimeFacade } from '../../facades/runtime/ui-runtime.facade';
import { ProductFacade } from '../../facades/platform/product.facade';
import { ProductCardComponent } from '../../components/product-card/product-card.component';
@Component({
selector: 'app-item-detail',
imports: [DecimalPipe, RouterLink, FormsModule, LangRoutePipe, TranslatePipe],
imports: [DecimalPipe, RouterLink, FormsModule, LangRoutePipe, TranslatePipe, ProductCardComponent],
templateUrl: './item-detail.component.html',
styleUrls: ['./item-detail.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
@@ -25,6 +26,7 @@ import { ProductFacade } from '../../facades/platform/product.facade';
export class ItemDetailComponent implements OnInit, OnDestroy {
item = signal<Item | null>(null);
selectedPhotoIndex = signal(0);
relatedProducts = signal<Item[]>([]);
loading = signal(true);
error = signal<string | null>(null);
private readonly uiRuntime = inject(UiRuntimeFacade);
@@ -139,6 +141,7 @@ export class ItemDetailComponent implements OnInit, OnDestroy {
this.item.set(item);
this.initVariantSelection(item);
this.seoService.setItemMeta(item);
this.loadRelatedProducts(item);
this.loading.set(false);
},
error: (err) => {
@@ -163,6 +166,17 @@ export class ItemDetailComponent implements OnInit, OnDestroy {
}
}
private loadRelatedProducts(item: Item): void {
this.productFacade.getRelatedProducts({
productID: item.itemID,
categoryID: item.categoryID,
count: 8
}).subscribe({
next: (result) => this.relatedProducts.set(result.items),
error: () => this.relatedProducts.set([])
});
}
selectColour(colour: string): void {
this.selectedColour.set(colour);
// If current size is not available for the new colour, reset to first available
@@ -192,6 +206,12 @@ export class ItemDetailComponent implements OnInit, OnDestroy {
}
}
addRelatedToCart(itemID: number, event: Event): void {
event.preventDefault();
event.stopPropagation();
this.cartService.addItem(itemID);
}
getDiscountedPrice(): number {
const currentItem = this.item();
if (!currentItem) return 0;
@@ -221,6 +241,10 @@ export class ItemDetailComponent implements OnInit, OnDestroy {
return getTranslatedField(currentItem, 'name', lang);
}
relatedItemName(item: Item): string {
return getTranslatedField(item, 'name', this.languageService.currentLanguage());
}
getSimpleDescription(): string {
const currentItem = this.item();
if (!currentItem) return '';