Add related products to detail page
This commit is contained in:
@@ -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>
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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 '';
|
||||
|
||||
Reference in New Issue
Block a user