feat: implement product configuration and order management with service refactoring and UI updates

This commit is contained in:
Elyes
2025-11-27 09:49:13 +01:00
parent a740a5b319
commit 0a03f9be60
14 changed files with 281 additions and 56 deletions

View File

@@ -4,5 +4,5 @@ import { Productconfigpage } from './productconfigpage/productconfigpage';
export const routes: Routes = [
{ path: '', component: Productspage },
{ path: 'product/:id', component: Productconfigpage },
{ path: 'products/:id', component: Productconfigpage },
];

View File

@@ -0,0 +1,39 @@
// src/app/models/order.interface.ts
/**
* Definiert die Struktur der gesamten Bestellung.
* Alle Felder sind optional ('?'), da die Bestellung schrittweise befüllt wird.
*/
export interface IOrder {
product?: IProduct;
config?: IProductConfig;
userData?: IUserData;
paymentMethod?: IPaymentMethod;
shippingNumber?: string;
totalPrice?: number;
}
export interface IProduct {
id: number;
name: string;
basePrice: number;
bestChoice: boolean;
img?: string;
}
export interface IProductConfig {
extraBacon: boolean;
saladAmount: 'less' | 'normal' | 'much';
priceDelta: number; // Preisunterschied durch Konfiguration
}
export interface IUserData {
fullName: string;
address: string;
zipCode: string;
city: string;
}
export interface IPaymentMethod {
type: 'twint' | 'visa' | 'mastercard';
}

View File

@@ -1,6 +1,27 @@
<div class="main">
<img [src]="product?.img" alt="" srcset="">
<sdx-numeric-stepper [label]="product?.name" sr-hint="Number of products" max="10"
oninput="console.log('Value:', arguments[0].target.value)">
</sdx-numeric-stepper>
<h1>Configure your <br>{{product?.name}}</h1>
<img [src]="product?.img" alt="burger picture" srcset="">
<div class="options">
<sdx-input-group type="checkbox" inline>
<sdx-input-item value="bacon" #baconCheckbox>Want to add extra bacon?</sdx-input-item>
</sdx-input-group>
<sdx-select
label="How much Salad do you want?"
placeholder="Choose your option…"
#saladSelect> <sdx-select-option value="less">Less (-0.50 CHF)</sdx-select-option>
<sdx-select-option value="normal">Normal</sdx-select-option>
<sdx-select-option value="much">Much (+0.50 CHF)</sdx-select-option>
</sdx-select>
</div>
<div class="price-summary s-heading-s">
Total Price: {{ totalPrice }} </div>
<sdx-button
label="Order"
(click)="addToCart(baconCheckbox.checked, saladSelect.value[0])">
</sdx-button>
</div>

View File

@@ -0,0 +1,22 @@
.main {
display: flex;
padding: 5vw;
flex-direction: column;
align-items: center;
justify-content: center;
img {
width: 350px;
border-radius: 24px;
height: auto;
margin-bottom: 20px;
}
.options {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 20px;
margin-bottom: 20px;
}
}

View File

@@ -1,10 +1,12 @@
import { Component, CUSTOM_ELEMENTS_SCHEMA, inject } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Products } from '../products.service';
import { Products } from '../services/products.service';
import { FormsModule } from '@angular/forms';
import { OrderService } from '../services/order.service';
@Component({
selector: 'app-productconfigpage',
imports: [],
imports: [FormsModule],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
templateUrl: './productconfigpage.html',
styleUrl: './productconfigpage.scss',
@@ -12,7 +14,67 @@ import { Products } from '../products.service';
export class Productconfigpage {
private selectedProduct = inject(ActivatedRoute);
productService = inject(Products);
orderService = inject(OrderService);
productId = this.selectedProduct.snapshot.paramMap.get('id');
product = this.productService.getProductList().find((p) => p.id === Number(this.productId));
// Add index signature so template can safely use options[option.key]
extraBacon: boolean = false;
saladAmount: 'less' | 'normal' | 'much' = 'normal';
console = console;
get priceDelta(): number {
let delta = 0;
// Greift direkt auf this.extraBacon zu
if (this.extraBacon) {
delta += 1.5;
}
// Greift direkt auf this.saladAmount zu
switch (this.saladAmount) {
case 'less':
delta -= 0.5;
break;
case 'much':
delta += 0.5;
break;
}
return delta;
}
// Wandle die Funktion in einen GETTER um (keine Argumente!)
get totalPrice(): number {
// Ruft den Getter this.priceDelta auf
return (this.product?.basePrice || 0) + this.priceDelta;
}
addToCart(isBaconChecked: boolean, selectedSaladAmount: 'less' | 'normal' | 'much') {
// set the variables
this.extraBacon = isBaconChecked;
this.saladAmount = selectedSaladAmount;
// save to localStorage
localStorage.setItem('cart', JSON.stringify({
product: this.product,
config: {
extraBacon: this.extraBacon,
saladAmount: this.saladAmount,
priceDelta: this.priceDelta,
},
totalPrice: this.totalPrice,
}));
this.orderService.updateOrder({
product: this.product,
config: {
extraBacon: this.extraBacon,
saladAmount: this.saladAmount,
priceDelta: this.priceDelta,
},
totalPrice: this.totalPrice,
});
}
}

View File

@@ -1,14 +0,0 @@
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root',
})
export class Products {
getProductList() {
return [
{ id: 1, name: 'Rindburger', price: 14.95, img: '/rindburger.png' },
{ id: 2, name: 'Pouletburger', price: 16.95, img: '/krabsburger.png' },
{ id: 3, name: 'Veggieburger', price: 20.95, img: '/veggieburger.png' },
];
}
}

View File

@@ -5,9 +5,10 @@
<div class="products">
@for (item of productList; track $index) {
<sdx-card [label]="item.name" label-aria-level="3" [imageSrc]="item.img" image-alt="burger">
<p>{{item.price}}</p>
<sdx-button icon-name="icon-shopping-trolley" label="Jetzt bestellen"></sdx-button>
<sdx-card [label]="item.name + ' - CHF ' + item.basePrice"
[background]="item.bestChoice ? 'blue' : 'white'" object-fit="cover" label-aria-level="3"
[imageSrc]="item.img" image-alt="burger" href-label="Order now!" [href]="'/products/' + item.id">
</sdx-card>
}
}
</div>

View File

@@ -2,15 +2,16 @@
display: flex;
flex-direction: row;
justify-content: space-between;
padding: 10px 20px;
border-bottom: #333 2px solid;
img {
height: 40px;
height: 60px;
}
h1 {
margin: 0;
font-size: 24px;
font-size: 32px;
color: #333;
border-bottom: #333 2px solid;
}
}
@@ -24,6 +25,10 @@
sdx-card {
height: auto;
object-fit: cover;
width: 350px;
display: flex;
flex-direction: column;
justify-content: space-between;
}
}

View File

@@ -1,5 +1,5 @@
import { Component, inject, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { Products } from '../products.service';
import { Products } from '../services/products.service';
@Component({
selector: 'app-productspage',

View File

@@ -0,0 +1,31 @@
import { Injectable } from '@angular/core';
import { IOrder } from '../models/order.interface';
@Injectable({
providedIn: 'root',
})
export class OrderService {
private currentOrder: IOrder = {};
public getOrder(): IOrder {
return this.currentOrder;
}
public updateOrder(partialOrder: Partial<IOrder>): void {
this.currentOrder = { ...this.currentOrder, ...partialOrder };
}
public placeOrder(): string {
// Logic to send order
const finalOrder = this.getOrder();
const shippingID = "SC-" + Math.floor(Math.random() * 1000000).toString().padStart(6, '0');
this.updateOrder({ shippingNumber: shippingID });
console.log('Order placed:', this.getOrder());
return shippingID;
}
}

View File

@@ -0,0 +1,40 @@
import { Injectable } from '@angular/core';
import { IProduct } from '../models/order.interface';
@Injectable({
providedIn: 'root',
})
export class Products {
public getProductList(): IProduct[] {
return [
{
id: 1,
name: 'Classic Burger',
basePrice: 8.5,
bestChoice: false,
img: 'rindburger.png',
},
{
id: 2,
name: 'Mr.Krabs Burger',
basePrice: 9.5,
bestChoice: true,
img: 'krabsburger.png',
},
{
id: 3,
name: 'Veggie Burger',
basePrice: 10.0,
bestChoice: false,
img: 'veggieburger.png',
},
];
}
getProductById(id: number) {
return this.getProductList().find((product) => {
return product.id === id;
})
}
}

View File

@@ -1 +1,38 @@
<p>userdatapage works!</p>
<sdx-dialog
label="Call back now"
type="closable-modal"
id="contact-modal" icon-name="icon-call-centre"
display-change-callback="
/* Fügt Fokus-Logik hinzu, wenn nötig */
if (arguments[0] === 'open') document.querySelector('#contact-input').doFocus();
if (arguments[0] === 'closing') document.querySelector('#contact-opener').doFocus();">
<sdx-dialog-toggle>
<sdx-button id="contact-opener" label="Rückruf anfordern"></sdx-button>
</sdx-dialog-toggle>
<sdx-dialog-content>
<p>We will be happy to put you through to one of our employees now.</p>
<p class="margin-bottom-4">
<sdx-input
id="contact-input"
label="How can we contact you by phone?"
#phoneNumber>
</sdx-input>
</p>
<sdx-button-group>
<sdx-button
label="Call back now"
(click)="saveContactData(phoneNumber.value)">
</sdx-button>
<sdx-button
label="Cancel"
theme="secondary"
onclick="document.getElementById('contact-modal').close()">
</sdx-button>
</sdx-button-group>
</sdx-dialog-content>
</sdx-dialog>

View File

@@ -1,8 +1,10 @@
import { Component } from '@angular/core';
import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { FormsModule } from '@angular/forms';
@Component({
selector: 'app-userdatapage',
imports: [],
imports: [FormsModule],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
templateUrl: './userdatapage.html',
styleUrl: './userdatapage.scss',
})