From 0a03f9be60dcd0418b0a8fbc87adba321ee95041 Mon Sep 17 00:00:00 2001 From: Elyes Date: Thu, 27 Nov 2025 09:49:13 +0100 Subject: [PATCH] feat: implement product configuration and order management with service refactoring and UI updates --- package-lock.json | 25 +------ src/app/app.routes.ts | 2 +- src/app/models/order.interface.ts | 39 +++++++++++ .../productconfigpage/productconfigpage.html | 29 ++++++-- .../productconfigpage/productconfigpage.scss | 22 +++++++ .../productconfigpage/productconfigpage.ts | 66 ++++++++++++++++++- src/app/products.service.ts | 14 ---- src/app/productspage/productspage.html | 9 +-- src/app/productspage/productspage.scss | 13 ++-- src/app/productspage/productspage.ts | 2 +- src/app/services/order.service.ts | 31 +++++++++ src/app/services/products.service.ts | 40 +++++++++++ src/app/userdatapage/userdatapage.html | 39 ++++++++++- src/app/userdatapage/userdatapage.ts | 6 +- 14 files changed, 281 insertions(+), 56 deletions(-) create mode 100644 src/app/models/order.interface.ts delete mode 100644 src/app/products.service.ts create mode 100644 src/app/services/order.service.ts create mode 100644 src/app/services/products.service.ts diff --git a/package-lock.json b/package-lock.json index c049729..06d8a2a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -461,7 +461,6 @@ "resolved": "https://registry.npmjs.org/@angular/common/-/common-21.0.0.tgz", "integrity": "sha512-uFvQDYU5X5nEnI9C4Bkdxcu4aIzNesGLJzmFlnwChVxB4BxIRF0uHL0oRhdkInGTIzPDJPH4nF6B/22c5gDVqA==", "license": "MIT", - "peer": true, "dependencies": { "tslib": "^2.3.0" }, @@ -478,7 +477,6 @@ "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-21.0.0.tgz", "integrity": "sha512-6jCH3UYga5iokj5F40SR4dlwo9ZRMkT8YzHCTijwZuDX9zvugp9jPof092RvIeNsTvCMVfGWuM9yZ1DRUsU/yg==", "license": "MIT", - "peer": true, "dependencies": { "tslib": "^2.3.0" }, @@ -492,7 +490,6 @@ "integrity": "sha512-KTXp+e2UPGyfFew6Wq95ULpHWQ20dhqkAMZ6x6MCYfOe2ccdnGYsAbLLmnWGmSg5BaOI4B0x/1XCFZf/n6WDgA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/core": "7.28.4", "@jridgewell/sourcemap-codec": "^1.4.14", @@ -525,7 +522,6 @@ "resolved": "https://registry.npmjs.org/@angular/core/-/core-21.0.0.tgz", "integrity": "sha512-bqi8fT4csyITeX8vdN5FJDBWx5wuWzdCg4mKSjHd+onVzZLyZ8bcnuAKz4mklgvjvwuXoRYukmclUurLwfq3Rg==", "license": "MIT", - "peer": true, "dependencies": { "tslib": "^2.3.0" }, @@ -570,7 +566,6 @@ "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-21.0.0.tgz", "integrity": "sha512-KQrANla4RBLhcGkwlndqsKzBwVFOWQr1640CfBVjj2oz4M3dW5hyMtXivBACvuwyUhYU/qJbqlDMBXl/OUSudQ==", "license": "MIT", - "peer": true, "dependencies": { "tslib": "^2.3.0" }, @@ -692,7 +687,6 @@ "integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", @@ -1052,7 +1046,6 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": ">=18" }, @@ -1096,7 +1089,6 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": ">=18" } @@ -1813,7 +1805,6 @@ "integrity": "sha512-X7/+dG9SLpSzRkwgG5/xiIzW0oMrV3C0HOa7YHG1WnrLK+vCQHfte4k/T80059YBdei29RBC3s+pSMvPJDU9/A==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@inquirer/checkbox": "^4.3.0", "@inquirer/confirm": "^5.1.19", @@ -3965,8 +3956,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz", "integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@stencil/core": { "version": "4.25.1", @@ -4512,7 +4502,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.8.25", "caniuse-lite": "^1.0.30001754", @@ -4702,7 +4691,6 @@ "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "readdirp": "^4.0.1" }, @@ -5431,7 +5419,6 @@ "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.0", @@ -6192,7 +6179,6 @@ "integrity": "sha512-454TI39PeRDW1LgpyLPyURtB4Zx1tklSr6+OFOipsxGUH1WMTvk6C65JQdrj455+DP2uJ1+veBEHTGFKWVLFoA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@acemir/cssom": "^0.9.23", "@asamuzakjp/dom-selector": "^6.7.4", @@ -6293,7 +6279,6 @@ "integrity": "sha512-ME4Fb83LgEgwNw96RKNvKV4VTLuXfoKudAmm2lP8Kk87KaMK0/Xrx/aAkMWmT8mDb+3MlFDspfbCs7adjRxA2g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "cli-truncate": "^5.0.0", "colorette": "^2.0.20", @@ -7792,7 +7777,6 @@ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", "license": "Apache-2.0", - "peer": true, "dependencies": { "tslib": "^2.1.0" } @@ -8487,8 +8471,7 @@ "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD", - "peer": true + "license": "0BSD" }, "node_modules/tuf-js": { "version": "4.0.0", @@ -8526,7 +8509,6 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -8659,7 +8641,6 @@ "integrity": "sha512-BxAKBWmIbrDgrokdGZH1IgkIk/5mMHDreLDmCJ0qpyJaAteP8NvMhkwr/ZCQNqNH97bw/dANTE9PDzqwJghfMQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", @@ -9219,7 +9200,6 @@ "integrity": "sha512-2Fqty3MM9CDwOVet/jaQalYlbcjATZwPYGcqpiYQqgQ/dLC7GuHdISKgTYIVF/kaishKxLzleKWWfbSDklyIKg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@vitest/expect": "4.0.10", "@vitest/mocker": "4.0.10", @@ -9740,7 +9720,6 @@ "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", "dev": true, "license": "MIT", - "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts index 4efb782..ec07458 100644 --- a/src/app/app.routes.ts +++ b/src/app/app.routes.ts @@ -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 }, ]; diff --git a/src/app/models/order.interface.ts b/src/app/models/order.interface.ts new file mode 100644 index 0000000..720e4f1 --- /dev/null +++ b/src/app/models/order.interface.ts @@ -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'; +} \ No newline at end of file diff --git a/src/app/productconfigpage/productconfigpage.html b/src/app/productconfigpage/productconfigpage.html index 4548414..6dbfc5f 100644 --- a/src/app/productconfigpage/productconfigpage.html +++ b/src/app/productconfigpage/productconfigpage.html @@ -1,6 +1,27 @@
- - - +

Configure your
{{product?.name}}

+ burger picture + +
+ + + Want to add extra bacon? + + + Less (-0.50 CHF) + Normal + Much (+0.50 CHF) + +
+ +
+ Total Price: {{ totalPrice }}
+ + +
\ No newline at end of file diff --git a/src/app/productconfigpage/productconfigpage.scss b/src/app/productconfigpage/productconfigpage.scss index e69de29..75cebca 100644 --- a/src/app/productconfigpage/productconfigpage.scss +++ b/src/app/productconfigpage/productconfigpage.scss @@ -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; + } +} \ No newline at end of file diff --git a/src/app/productconfigpage/productconfigpage.ts b/src/app/productconfigpage/productconfigpage.ts index bc6e6d9..accc1fd 100644 --- a/src/app/productconfigpage/productconfigpage.ts +++ b/src/app/productconfigpage/productconfigpage.ts @@ -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, + }); + } } diff --git a/src/app/products.service.ts b/src/app/products.service.ts deleted file mode 100644 index 0fb373a..0000000 --- a/src/app/products.service.ts +++ /dev/null @@ -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' }, - ]; - } -} diff --git a/src/app/productspage/productspage.html b/src/app/productspage/productspage.html index dab13b6..eca04f7 100644 --- a/src/app/productspage/productspage.html +++ b/src/app/productspage/productspage.html @@ -5,9 +5,10 @@
@for (item of productList; track $index) { - -

{{item.price}}

- + + - } + }
\ No newline at end of file diff --git a/src/app/productspage/productspage.scss b/src/app/productspage/productspage.scss index a09504c..6c6d9fb 100644 --- a/src/app/productspage/productspage.scss +++ b/src/app/productspage/productspage.scss @@ -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; + } } diff --git a/src/app/productspage/productspage.ts b/src/app/productspage/productspage.ts index fc43c51..3b1cf14 100644 --- a/src/app/productspage/productspage.ts +++ b/src/app/productspage/productspage.ts @@ -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', diff --git a/src/app/services/order.service.ts b/src/app/services/order.service.ts new file mode 100644 index 0000000..2db6649 --- /dev/null +++ b/src/app/services/order.service.ts @@ -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): 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; + } +} diff --git a/src/app/services/products.service.ts b/src/app/services/products.service.ts new file mode 100644 index 0000000..9b4cf61 --- /dev/null +++ b/src/app/services/products.service.ts @@ -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; + }) + } + +} diff --git a/src/app/userdatapage/userdatapage.html b/src/app/userdatapage/userdatapage.html index a050e70..0051067 100644 --- a/src/app/userdatapage/userdatapage.html +++ b/src/app/userdatapage/userdatapage.html @@ -1 +1,38 @@ -

userdatapage works!

+ + + + + + + +

We will be happy to put you through to one of our employees now.

+ +

+ + +

+ + + + + + + + +
+
\ No newline at end of file diff --git a/src/app/userdatapage/userdatapage.ts b/src/app/userdatapage/userdatapage.ts index 6872c15..7f76501 100644 --- a/src/app/userdatapage/userdatapage.ts +++ b/src/app/userdatapage/userdatapage.ts @@ -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', })