Skip to content

Commit 6b445bf

Browse files
committed
no message
1 parent 132c3b7 commit 6b445bf

17 files changed

Lines changed: 585 additions & 36 deletions

sprint5-with-bugs/UI/src/app/_services/product.service.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export class ProductService {
2323
.pipe(map(this.extractData));
2424
}
2525

26-
getProductsNew(searchQuery: string, sorting: string, minPrice: string, maxPrice: string, categoryIds: any, brandIds: any, page: any): Observable<Pagination<Product>> {
26+
getProductsNew(searchQuery: string, sorting: string, minPrice: string, maxPrice: string, categoryIds: any, brandIds: any, page: any, ecoFriendlyFilter?: boolean): Observable<Pagination<Product>> {
2727
let params = new HttpParams();
2828
if (searchQuery) {
2929
params = params.set('q', searchQuery);
@@ -40,6 +40,9 @@ export class ProductService {
4040
if (brandIds.length) {
4141
params = params.set('by_brand', brandIds);
4242
}
43+
if (ecoFriendlyFilter) {
44+
params = params.set('eco_friendly', '1');
45+
}
4346
params = params.set('page', page);
4447
return this.httpClient.get(this.apiURL + '/products', {params: params})
4548
.pipe(map(this.extractData));
@@ -76,7 +79,7 @@ export class ProductService {
7679
return this.httpClient.get(this.apiURL + `/products/${id}/related`);
7780
}
7881

79-
getProductsByCategoryAndBrand(categoryIds: any, brandIds: any, sorting: string, slug?: string): Observable<Pagination<Product>> {
82+
getProductsByCategoryAndBrand(categoryIds: any, brandIds: any, sorting: string, slug?: string, ecoFriendlyFilter?: boolean): Observable<Pagination<Product>> {
8083
let params = new HttpParams();
8184
if (categoryIds.length) {
8285
params = params.set('by_category', categoryIds);
@@ -90,6 +93,9 @@ export class ProductService {
9093
if (slug) {
9194
params = params.set('by_category_slug', slug);
9295
}
96+
if (ecoFriendlyFilter) {
97+
params = params.set('eco_friendly', '1');
98+
}
9399
return this.httpClient.get(this.apiURL + '/products', {params: params})
94100
.pipe(map(this.extractData));
95101
}

sprint5-with-bugs/UI/src/app/admin/products-add-edit/products-add-edit.component.html

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
<!-- Copyright (c) 2024-2026 Testsmith. All rights reserved. See LICENSE for details. -->
1+
<!-- Copyright (c) 2024-2026 Testsmith. All rights reserved. -->
2+
<!-- See LICENSE for details. -->
23

34
<div class="container mt-5">
45
<form [formGroup]="form" (ngSubmit)="onSubmit()" autocomplete="off">
@@ -53,8 +54,8 @@ <h2 data-test="page-title">Edit Product</h2>
5354
</div>
5455
@if (submitted && f['stock'].errors) {
5556
<div class="alert alert-danger mt-3 p-2" role="alert">
56-
@if (f['price'].errors['required']) {
57-
<div>Quantity is required</div>
57+
@if (f['stock'].errors['required']) {
58+
<div>Stock is required for non-rental products</div>
5859
}
5960
</div>
6061
}
@@ -109,6 +110,19 @@ <h2 data-test="page-title">Edit Product</h2>
109110
</select>
110111
</div>
111112
</div>
113+
<div class="row mb-3">
114+
<label for="co2_rating" class="col-sm-4 col-form-label">CO₂ Rating</label>
115+
<div class="col-sm-8">
116+
<select class="form-select" formControlName="co2_rating" data-test="co2-rating" id="co2_rating">
117+
<option value="">None</option>
118+
<option value="A">A (Lowest Impact)</option>
119+
<option value="B">B (Low Impact)</option>
120+
<option value="C">C (Moderate Impact)</option>
121+
<option value="D">D (Higher Impact)</option>
122+
<option value="E">E (Highest Impact)</option>
123+
</select>
124+
</div>
125+
</div>
112126
<div class="row mb-3">
113127
<label for="product_image_id" class="col-sm-4 col-form-label">Image</label>
114128
<div class="col-sm-8">

sprint5-with-bugs/UI/src/app/admin/products-add-edit/products-add-edit.component.ts

Lines changed: 58 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -55,15 +55,32 @@ export class ProductsAddEditComponent implements OnInit {
5555
id: ['', []],
5656
name: ['', [Validators.required]],
5757
description: ['', [Validators.required]],
58-
stock: ['', [Validators.required]],
58+
stock: ['', []],
5959
price: ['', [Validators.required]],
6060
brand_id: ['', [Validators.required]],
6161
category_id: ['', [Validators.required]],
6262
product_image_id: ['', [Validators.required]],
6363
is_location_offer: ['', []],
64-
is_rental: ['', []]
64+
is_rental: ['', []],
65+
co2_rating: ['', []]
6566
});
6667

68+
// Add conditional validation for stock based on is_rental
69+
this.form.get('is_rental')?.valueChanges.subscribe(isRental => {
70+
const stockControl = this.form.get('stock');
71+
if (isRental) {
72+
stockControl?.clearValidators();
73+
} else {
74+
stockControl?.setValidators([Validators.required]);
75+
}
76+
stockControl?.updateValueAndValidity();
77+
});
78+
79+
// Set initial stock validation for add mode (non-rental by default)
80+
if (this.isAddMode) {
81+
this.form.get('stock')?.setValidators([Validators.required]);
82+
}
83+
6784
this.brandService.getBrands()
6885
.pipe(first())
6986
.subscribe((brands) => this.brands = brands);
@@ -80,10 +97,33 @@ export class ProductsAddEditComponent implements OnInit {
8097
this.productService.getById(this.id)
8198
.pipe(first())
8299
.subscribe(x => {
83-
this.form.patchValue(x)
100+
this.form.patchValue({
101+
id: x.id,
102+
name: x.name,
103+
description: x.description,
104+
stock: x.stock,
105+
price: x.price,
106+
brand_id: x.brand?.id || x.brand_id,
107+
category_id: x.category?.id || x.category_id,
108+
product_image_id: x.product_image?.id || x.product_image_id,
109+
is_location_offer: x.is_location_offer,
110+
is_rental: x.is_rental,
111+
co2_rating: x.co2_rating
112+
});
113+
114+
this.selectedImageId = x.product_image?.id || x.product_image_id;
84115
this.selectedImage = this.images.find((el: Image) => {
85-
return el?.id == x.product_image_id;
116+
return el?.id == this.selectedImageId;
86117
});
118+
119+
// Set initial stock validation based on is_rental value
120+
const stockControl = this.form.get('stock');
121+
if (x.is_rental) {
122+
stockControl?.clearValidators();
123+
} else {
124+
stockControl?.setValidators([Validators.required]);
125+
}
126+
stockControl?.updateValueAndValidity();
87127
});
88128
}
89129
});
@@ -116,7 +156,13 @@ export class ProductsAddEditComponent implements OnInit {
116156
this.form.get('is_location_offer')?.setValue(false);
117157
}
118158

119-
this.productService.create(this.form.value)
159+
// For rental products, allow stock to be null
160+
const formValue = {...this.form.value};
161+
if (formValue.is_rental && !formValue.stock) {
162+
formValue.stock = null;
163+
}
164+
165+
this.productService.create(formValue)
120166
.pipe(first())
121167
.subscribe({
122168
next: () => {
@@ -131,7 +177,13 @@ export class ProductsAddEditComponent implements OnInit {
131177
}
132178

133179
private updateProduct() {
134-
this.productService.update(this.id, this.form.value)
180+
// For rental products, allow stock to be null
181+
const formValue = {...this.form.value};
182+
if (formValue.is_rental && !formValue.stock) {
183+
formValue.stock = null;
184+
}
185+
186+
this.productService.update(this.id, formValue)
135187
.pipe(first())
136188
.subscribe({
137189
next: () => {

sprint5-with-bugs/UI/src/app/admin/settings/settings.component.html

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
<!-- Copyright (c) 2024-2026 Testsmith. All rights reserved. See LICENSE for details. -->
1+
<!-- Copyright (c) 2024-2026 Testsmith. All rights reserved. -->
2+
<!-- See LICENSE for details. -->
23

34
<div class="container mt-5">
45
<form [formGroup]="form" (ngSubmit)="onSubmit()" autocomplete="off">
@@ -32,6 +33,36 @@ <h2 data-test="page-title">Settings</h2>
3233
</div>
3334
</div>
3435
</div>
36+
<div class="row">
37+
<div class="col-lg-6">
38+
<div class="row mb-4">
39+
<label for="co2Scale" class="col-sm-4 col-form-label">Display CO₂ sustainability features</label>
40+
<div class="col-sm-8">
41+
<input class="form-check-input d-block" formControlName="co2Scale" data-test="co2-scale-toggle" type="checkbox" value=""
42+
id="co2Scale">
43+
<small class="form-text text-muted">
44+
By enabling the "CO₂ sustainability features" option, products will display their CO₂ rating (A-E scale). This setting is enabled by default.
45+
</small>
46+
</div>
47+
</div>
48+
</div>
49+
</div>
50+
@if (isCo2ScaleEnabled()) {
51+
<div class="row">
52+
<div class="col-lg-6">
53+
<div class="row mb-4">
54+
<label for="ecoBadge" class="col-sm-4 col-form-label">Display eco-friendly badge</label>
55+
<div class="col-sm-8">
56+
<input class="form-check-input d-block" formControlName="ecoBadge" data-test="eco-badge-toggle" type="checkbox" value=""
57+
id="ecoBadge">
58+
<small class="form-text text-muted">
59+
By enabling the "display eco-friendly badge" option, products with CO₂ rating A or B will display a green ECO badge. This setting is enabled by default.
60+
</small>
61+
</div>
62+
</div>
63+
</div>
64+
</div>
65+
}
3566
<div class="row">
3667
<div class="col-12">
3768
<div class="d-block">

sprint5-with-bugs/UI/src/app/admin/settings/settings.component.ts

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,20 @@ export class SettingsComponent implements OnInit {
2929
ngOnInit(): void {
3030
this.form = this.formBuilder.group({
3131
endpoint: ['', []],
32-
geolocation: ['', []]
32+
geolocation: ['', []],
33+
co2Scale: ['', []],
34+
ecoBadge: ['', []]
3335
});
3436
this.form.controls['endpoint'].setValue(window.sessionStorage.getItem('PAYMENT_ENDPOINT'));
3537
this.form.controls['geolocation'].setValue(window.sessionStorage.getItem('RETRIEVE_GEOLOCATION'));
38+
39+
// CO₂ scale is enabled by default
40+
const co2ScaleSetting = window.localStorage.getItem('CO2_SCALE_ENABLED');
41+
this.form.controls['co2Scale'].setValue(co2ScaleSetting === null ? true : co2ScaleSetting === 'true');
42+
43+
// Eco badge is enabled by default
44+
const ecoBadgeSetting = window.localStorage.getItem('ECO_BADGE_ENABLED');
45+
this.form.controls['ecoBadge'].setValue(ecoBadgeSetting === null ? true : ecoBadgeSetting === 'true');
3646
}
3747

3848
get f() {
@@ -47,8 +57,25 @@ export class SettingsComponent implements OnInit {
4757
return;
4858
}
4959

50-
window.sessionStorage.setItem('PAYMENT_ENDPOINT', this.form.controls['endpoint'].value);
51-
window.sessionStorage.setItem('RETRIEVE_GEOLOCATION', this.form.controls['geolocation'].value);
60+
const endpoint = this.form.controls['endpoint'].value;
61+
const geolocation = this.form.controls['geolocation'].value;
62+
const co2Scale = this.form.controls['co2Scale'].value;
63+
const ecoBadge = this.form.controls['ecoBadge'].value;
64+
65+
if (endpoint !== '' && endpoint !== null) {
66+
window.sessionStorage.setItem('PAYMENT_ENDPOINT', endpoint);
67+
}
68+
69+
if (geolocation) {
70+
window.sessionStorage.setItem('RETRIEVE_GEOLOCATION', geolocation);
71+
}
72+
73+
// Store CO₂ scale setting (true by default)
74+
window.localStorage.setItem('CO2_SCALE_ENABLED', co2Scale ? 'true' : 'false');
75+
76+
// Store eco badge setting (true by default)
77+
window.localStorage.setItem('ECO_BADGE_ENABLED', ecoBadge ? 'true' : 'false');
78+
5279
this.isUpdated = true;
5380
}
5481

@@ -65,5 +92,11 @@ export class SettingsComponent implements OnInit {
6592
window.sessionStorage.removeItem('RETRIEVE_GEOLOCATION');
6693
window.sessionStorage.removeItem('GEO_LOCATION');
6794
window.sessionStorage.removeItem('cart');
95+
window.localStorage.removeItem('CO2_SCALE_ENABLED');
96+
window.localStorage.removeItem('ECO_BADGE_ENABLED');
97+
}
98+
99+
isCo2ScaleEnabled(): boolean {
100+
return this.form.controls['co2Scale'].value === true;
68101
}
69102
}

sprint5-with-bugs/UI/src/app/app.component.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
import {Component, inject, OnInit} from '@angular/core';
55
import {Spinkit} from "ng-http-loader";
6-
import {faShoppingCart} from '@fortawesome/free-solid-svg-icons';
6+
import {faShoppingCart, faLeaf} from '@fortawesome/free-solid-svg-icons';
77
import {HeaderComponent} from "./header/header.component";
88
import {FaIconLibrary} from "@fortawesome/angular-fontawesome";
99
import {FooterComponent} from "./footer/footer.component";
@@ -29,7 +29,7 @@ export class AppComponent implements OnInit {
2929
title = 'Toolshop';
3030

3131
constructor() {
32-
this.library.addIcons(faShoppingCart);
32+
this.library.addIcons(faShoppingCart, faLeaf);
3333
}
3434

3535
ngOnInit(): void {

sprint5-with-bugs/UI/src/app/models/product.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,6 @@ export interface Product {
2020
category_id?: number;
2121
brand: Brand;
2222
category: Category;
23+
co2_rating?: string;
24+
is_eco_friendly?: boolean;
2325
}

0 commit comments

Comments
 (0)