diff --git a/itenium-socks/angular.json b/itenium-socks/angular.json index e7cfa50..f61caf2 100644 --- a/itenium-socks/angular.json +++ b/itenium-socks/angular.json @@ -100,5 +100,8 @@ } } } + }, + "cli": { + "analytics": false } } diff --git a/itenium-socks/package-lock.json b/itenium-socks/package-lock.json index 295c374..a0436b2 100644 --- a/itenium-socks/package-lock.json +++ b/itenium-socks/package-lock.json @@ -18,6 +18,7 @@ "@angular/router": "^18.0.0", "@fortawesome/angular-fontawesome": "^0.15.0", "@fortawesome/fontawesome-free": "^6.5.2", + "ngx-pagination": "^6.0.3", "rxjs": "~7.8.0", "tslib": "^2.3.0", "zone.js": "~0.14.3" @@ -9054,6 +9055,18 @@ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "dev": true }, + "node_modules/ngx-pagination": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/ngx-pagination/-/ngx-pagination-6.0.3.tgz", + "integrity": "sha512-lONjTQ7hFPh1SyhwDrRd5ZwM4NMGQ7bNR6vLrs6mrU0Z8Q1zCcWbf/pvyp4DOlGyd9uyZxRy2wUsSZLeIPjbAw==", + "dependencies": { + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/common": ">=13.0.0", + "@angular/core": ">=13.0.0" + } + }, "node_modules/nice-napi": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/nice-napi/-/nice-napi-1.0.2.tgz", diff --git a/itenium-socks/package.json b/itenium-socks/package.json index 6b3a9d6..9da08d2 100644 --- a/itenium-socks/package.json +++ b/itenium-socks/package.json @@ -20,6 +20,7 @@ "@angular/router": "^18.0.0", "@fortawesome/angular-fontawesome": "^0.15.0", "@fortawesome/fontawesome-free": "^6.5.2", + "ngx-pagination": "^6.0.3", "rxjs": "~7.8.0", "tslib": "^2.3.0", "zone.js": "~0.14.3" diff --git a/itenium-socks/public/images/placeholder.jpg b/itenium-socks/public/images/placeholder.jpg new file mode 100644 index 0000000..9064588 Binary files /dev/null and b/itenium-socks/public/images/placeholder.jpg differ diff --git a/itenium-socks/src/app/home/latest-socks.component.html b/itenium-socks/src/app/home/latest-socks.component.html index 9cd2eb7..6362409 100644 --- a/itenium-socks/src/app/home/latest-socks.component.html +++ b/itenium-socks/src/app/home/latest-socks.component.html @@ -6,22 +6,24 @@

-
- + }
diff --git a/itenium-socks/src/app/home/latest-socks.component.ts b/itenium-socks/src/app/home/latest-socks.component.ts index 9b03950..f817894 100644 --- a/itenium-socks/src/app/home/latest-socks.component.ts +++ b/itenium-socks/src/app/home/latest-socks.component.ts @@ -2,13 +2,14 @@ import { Component, OnInit } from '@angular/core'; import { SocksService } from '../socks/socks.service'; import { Observable } from 'rxjs'; import { Sock } from '../socks/sock.model'; -import { AsyncPipe, NgFor } from '@angular/common'; +import { AsyncPipe } from '@angular/common'; import { RouterLink } from '@angular/router'; +import { PricePipe } from '../price.pipe'; @Component({ selector: 'app-latest-socks', standalone: true, - imports: [NgFor, AsyncPipe, RouterLink], + imports: [AsyncPipe, RouterLink, PricePipe], templateUrl: './latest-socks.component.html' }) export class LatestSocksComponent implements OnInit { diff --git a/itenium-socks/src/app/price.pipe.ts b/itenium-socks/src/app/price.pipe.ts new file mode 100644 index 0000000..43e56c4 --- /dev/null +++ b/itenium-socks/src/app/price.pipe.ts @@ -0,0 +1,17 @@ +import { Pipe, PipeTransform } from '@angular/core'; + +@Pipe({ + name: 'price', + standalone: true +}) +export class PricePipe implements PipeTransform { + + transform(value: number): string { + if (value) { + return `€${value.toFixed(2)}`; + } else { + return '€0.00'; + } + } + +} diff --git a/itenium-socks/src/app/socks/shop.component.html b/itenium-socks/src/app/socks/shop.component.html index a0139e2..9c89adf 100644 --- a/itenium-socks/src/app/socks/shop.component.html +++ b/itenium-socks/src/app/socks/shop.component.html @@ -5,16 +5,33 @@

Our Socks

+
+ + + +
-
+ +
+ + +
diff --git a/itenium-socks/src/app/socks/shop.component.ts b/itenium-socks/src/app/socks/shop.component.ts index 33c897b..7424621 100644 --- a/itenium-socks/src/app/socks/shop.component.ts +++ b/itenium-socks/src/app/socks/shop.component.ts @@ -1,21 +1,83 @@ import { Component } from '@angular/core'; import { SocksService } from './socks.service'; -import { Observable } from 'rxjs'; +import { Observable,BehaviorSubject, combineLatest } from 'rxjs'; import { Sock } from './sock.model'; import { AsyncPipe, NgFor } from '@angular/common'; +import { NgxPaginationModule } from 'ngx-pagination'; +import { map } from 'rxjs/operators'; +import { PricePipe } from '../price.pipe'; @Component({ selector: 'app-shop', standalone: true, - imports: [NgFor, AsyncPipe], + imports: [NgFor, AsyncPipe, NgxPaginationModule, PricePipe], templateUrl: './shop.component.html' }) export class ShopComponent { socks$!: Observable; + filteredSocks$!: Observable; + + public nameFilterSubject = new BehaviorSubject(''); + public colorFilterSubject = new BehaviorSubject(''); + public sortBySubject = new BehaviorSubject('name'); + public currentPageSubject = new BehaviorSubject(1); + public pageSize = 10; + + placeholderImageUrl = 'images/placeholder.jpg'; constructor(private socksService: SocksService) {} ngOnInit(): void { this.socks$ = this.socksService.get(); + + this.filteredSocks$ = combineLatest([ + this.socks$, + this.nameFilterSubject.asObservable(), + this.colorFilterSubject.asObservable(), + this.sortBySubject.asObservable(), + this.currentPageSubject.asObservable() + ]).pipe( + map(([socks, nameFilter, colorFilter, sortBy, currentPage]) => { + let filtered = socks.filter(sock => + sock.name.toLowerCase().includes(nameFilter.toLowerCase()) && + sock.variant.toLowerCase().includes(colorFilter.toLowerCase()) + ); + + filtered = filtered.sort((a, b) => { + if (sortBy === 'name') { + return a.name.localeCompare(b.name); + } else if (sortBy === 'price') { + return a.price - b.price; + } + return 0; + }); + + const startIndex = (currentPage - 1) * this.pageSize; + return filtered.slice(startIndex, startIndex + this.pageSize); + }) + ); + } + + onFilterName(event: Event) { + const name = (event.target as HTMLInputElement).value; + this.nameFilterSubject.next(name); + } + + onFilterColor(event: Event) { + const color = (event.target as HTMLInputElement).value; + this.colorFilterSubject.next(color); + } + + onSortBy(event: Event) { + const sortBy = (event.target as HTMLInputElement).value; + this.sortBySubject.next(sortBy); + } + + onPageChange(page: number) { + this.currentPageSubject.next(page); + } + + onImageError(event: Event) { + (event.target as HTMLImageElement).src = this.placeholderImageUrl; } } diff --git a/itenium-socks/src/app/socks/sock-reviews.component.html b/itenium-socks/src/app/socks/sock-reviews.component.html index 696aab3..774dafd 100644 --- a/itenium-socks/src/app/socks/sock-reviews.component.html +++ b/itenium-socks/src/app/socks/sock-reviews.component.html @@ -17,7 +17,7 @@
{{ review.socksId }}
-
On {{ review.added }} by {{ review.email }}
+
On {{ review.added | timeAgo }} by {{ review.email }}
diff --git a/itenium-socks/src/app/socks/sock-reviews.component.ts b/itenium-socks/src/app/socks/sock-reviews.component.ts index fe89cb9..63722ca 100644 --- a/itenium-socks/src/app/socks/sock-reviews.component.ts +++ b/itenium-socks/src/app/socks/sock-reviews.component.ts @@ -3,11 +3,12 @@ import { Component } from '@angular/core'; import { Observable } from 'rxjs'; import { SocksService } from './socks.service'; import { Review } from './sock.model'; +import { TimeAgoPipe } from '../time-ago.pipe'; @Component({ selector: 'app-sock-reviews', standalone: true, - imports: [NgFor, AsyncPipe], + imports: [NgFor, AsyncPipe, TimeAgoPipe], templateUrl: './sock-reviews.component.html' }) export class SockReviewsComponent { diff --git a/itenium-socks/src/app/time-ago.pipe.ts b/itenium-socks/src/app/time-ago.pipe.ts new file mode 100644 index 0000000..1d1e3b9 --- /dev/null +++ b/itenium-socks/src/app/time-ago.pipe.ts @@ -0,0 +1,32 @@ +import { Pipe, PipeTransform } from '@angular/core'; + +@Pipe({ + name: 'timeAgo', + standalone: true +}) +export class TimeAgoPipe implements PipeTransform { + + transform(value: number | Date | string): string { + if (!value) return 'N/A'; + + const time = new Date(value).getTime(); + const now = new Date().getTime(); + const difference = Math.floor((now - time) / 1000); + + if (difference < 30) { + return 'Just now'; + } else if (difference < 60) { + return 'A few seconds ago'; + } else if (difference < 3600) { + const minutes = Math.floor(difference / 60); + return `${minutes} minute${minutes > 1 ? 's' : ''} ago`; + } else if (difference < 86400) { + const hours = Math.floor(difference / 3600); + return `${hours} hour${hours > 1 ? 's' : ''} ago`; + } else { + const days = Math.floor(difference / 86400); + return `${days} day${days > 1 ? 's' : ''} ago`; + } + } + +}