From ad1e75832561c9d3e940a0b677496b548c8462ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20M=C3=BCller?= Date: Mon, 7 Dec 2020 21:37:12 +0100 Subject: [PATCH 1/6] BETTERZON-42: Refactoring components and creating product detail component --- Frontend/src/app/app.module.ts | 12 +++++---- Frontend/src/app/app.routing.ts | 6 ++--- .../footer/footer.component.css | 0 .../footer/footer.component.html | 0 .../footer/footer.component.spec.ts | 0 .../footer/footer.component.ts | 0 .../landingpage/landingpage.component.css | 0 .../landingpage/landingpage.component.html | 0 .../landingpage/landingpage.component.spec.ts | 0 .../landingpage/landingpage.component.ts | 0 .../product-detail-page.component.css | 0 .../product-detail-page.component.html | 0 .../product-detail-page.component.spec.ts | 0 .../product-detail-page.component.ts | 0 .../product-details.component.css | 0 .../product-details.component.html | 1 + .../product-details.component.spec.ts | 25 +++++++++++++++++++ .../product-details.component.ts | 15 +++++++++++ .../product-list/product-list.component.css | 0 .../product-list/product-list.component.html | 0 .../product-list.component.spec.ts | 0 .../product-list/product-list.component.ts | 4 +-- Frontend/src/app/models/price.ts | 7 ++++++ .../app/{ => services}/api.service.spec.ts | 0 .../src/app/{ => services}/api.service.ts | 13 +++++++++- 25 files changed, 72 insertions(+), 11 deletions(-) rename Frontend/src/app/{ => components}/footer/footer.component.css (100%) rename Frontend/src/app/{ => components}/footer/footer.component.html (100%) rename Frontend/src/app/{ => components}/footer/footer.component.spec.ts (100%) rename Frontend/src/app/{ => components}/footer/footer.component.ts (100%) rename Frontend/src/app/{ => components}/landingpage/landingpage.component.css (100%) rename Frontend/src/app/{ => components}/landingpage/landingpage.component.html (100%) rename Frontend/src/app/{ => components}/landingpage/landingpage.component.spec.ts (100%) rename Frontend/src/app/{ => components}/landingpage/landingpage.component.ts (100%) rename Frontend/src/app/{ => components}/product-detail-page/product-detail-page.component.css (100%) rename Frontend/src/app/{ => components}/product-detail-page/product-detail-page.component.html (100%) rename Frontend/src/app/{ => components}/product-detail-page/product-detail-page.component.spec.ts (100%) rename Frontend/src/app/{ => components}/product-detail-page/product-detail-page.component.ts (100%) create mode 100644 Frontend/src/app/components/product-details/product-details.component.css create mode 100644 Frontend/src/app/components/product-details/product-details.component.html create mode 100644 Frontend/src/app/components/product-details/product-details.component.spec.ts create mode 100644 Frontend/src/app/components/product-details/product-details.component.ts rename Frontend/src/app/{ => components}/product-list/product-list.component.css (100%) rename Frontend/src/app/{ => components}/product-list/product-list.component.html (100%) rename Frontend/src/app/{ => components}/product-list/product-list.component.spec.ts (100%) rename Frontend/src/app/{ => components}/product-list/product-list.component.ts (90%) create mode 100644 Frontend/src/app/models/price.ts rename Frontend/src/app/{ => services}/api.service.spec.ts (100%) rename Frontend/src/app/{ => services}/api.service.ts (62%) diff --git a/Frontend/src/app/app.module.ts b/Frontend/src/app/app.module.ts index 83b30d8..9acb4c6 100644 --- a/Frontend/src/app/app.module.ts +++ b/Frontend/src/app/app.module.ts @@ -4,10 +4,11 @@ import {NgModule} from '@angular/core'; import {AppComponent} from './app.component'; import {AppRouting} from './app.routing'; -import {ProductListComponent} from './product-list/product-list.component'; -import { LandingpageComponent } from './landingpage/landingpage.component'; -import { ProductDetailPageComponent } from './product-detail-page/product-detail-page.component'; -import { FooterComponent } from './footer/footer.component'; +import {ProductListComponent} from './components/product-list/product-list.component'; +import { LandingpageComponent } from './components/landingpage/landingpage.component'; +import { ProductDetailPageComponent } from './components/product-detail-page/product-detail-page.component'; +import { FooterComponent } from './components/footer/footer.component'; +import { ProductDetailsComponent } from './components/product-details/product-details.component'; @NgModule({ declarations: [ @@ -15,7 +16,8 @@ import { FooterComponent } from './footer/footer.component'; ProductListComponent, LandingpageComponent, ProductDetailPageComponent, - FooterComponent + FooterComponent, + ProductDetailsComponent ], imports: [ BrowserModule, diff --git a/Frontend/src/app/app.routing.ts b/Frontend/src/app/app.routing.ts index f34925f..9f175a5 100644 --- a/Frontend/src/app/app.routing.ts +++ b/Frontend/src/app/app.routing.ts @@ -2,9 +2,9 @@ import {NgModule} from '@angular/core'; import {CommonModule} from '@angular/common'; import {RouterModule, Routes} from '@angular/router'; import {AppComponent} from './app.component'; -import {ProductListComponent} from './product-list/product-list.component'; -import {LandingpageComponent} from './landingpage/landingpage.component'; -import {ProductDetailPageComponent} from './product-detail-page/product-detail-page.component'; +import {ProductListComponent} from './components/product-list/product-list.component'; +import {LandingpageComponent} from './components/landingpage/landingpage.component'; +import {ProductDetailPageComponent} from './components/product-detail-page/product-detail-page.component'; const routes: Routes = [ {path: '', component: LandingpageComponent}, diff --git a/Frontend/src/app/footer/footer.component.css b/Frontend/src/app/components/footer/footer.component.css similarity index 100% rename from Frontend/src/app/footer/footer.component.css rename to Frontend/src/app/components/footer/footer.component.css diff --git a/Frontend/src/app/footer/footer.component.html b/Frontend/src/app/components/footer/footer.component.html similarity index 100% rename from Frontend/src/app/footer/footer.component.html rename to Frontend/src/app/components/footer/footer.component.html diff --git a/Frontend/src/app/footer/footer.component.spec.ts b/Frontend/src/app/components/footer/footer.component.spec.ts similarity index 100% rename from Frontend/src/app/footer/footer.component.spec.ts rename to Frontend/src/app/components/footer/footer.component.spec.ts diff --git a/Frontend/src/app/footer/footer.component.ts b/Frontend/src/app/components/footer/footer.component.ts similarity index 100% rename from Frontend/src/app/footer/footer.component.ts rename to Frontend/src/app/components/footer/footer.component.ts diff --git a/Frontend/src/app/landingpage/landingpage.component.css b/Frontend/src/app/components/landingpage/landingpage.component.css similarity index 100% rename from Frontend/src/app/landingpage/landingpage.component.css rename to Frontend/src/app/components/landingpage/landingpage.component.css diff --git a/Frontend/src/app/landingpage/landingpage.component.html b/Frontend/src/app/components/landingpage/landingpage.component.html similarity index 100% rename from Frontend/src/app/landingpage/landingpage.component.html rename to Frontend/src/app/components/landingpage/landingpage.component.html diff --git a/Frontend/src/app/landingpage/landingpage.component.spec.ts b/Frontend/src/app/components/landingpage/landingpage.component.spec.ts similarity index 100% rename from Frontend/src/app/landingpage/landingpage.component.spec.ts rename to Frontend/src/app/components/landingpage/landingpage.component.spec.ts diff --git a/Frontend/src/app/landingpage/landingpage.component.ts b/Frontend/src/app/components/landingpage/landingpage.component.ts similarity index 100% rename from Frontend/src/app/landingpage/landingpage.component.ts rename to Frontend/src/app/components/landingpage/landingpage.component.ts diff --git a/Frontend/src/app/product-detail-page/product-detail-page.component.css b/Frontend/src/app/components/product-detail-page/product-detail-page.component.css similarity index 100% rename from Frontend/src/app/product-detail-page/product-detail-page.component.css rename to Frontend/src/app/components/product-detail-page/product-detail-page.component.css diff --git a/Frontend/src/app/product-detail-page/product-detail-page.component.html b/Frontend/src/app/components/product-detail-page/product-detail-page.component.html similarity index 100% rename from Frontend/src/app/product-detail-page/product-detail-page.component.html rename to Frontend/src/app/components/product-detail-page/product-detail-page.component.html diff --git a/Frontend/src/app/product-detail-page/product-detail-page.component.spec.ts b/Frontend/src/app/components/product-detail-page/product-detail-page.component.spec.ts similarity index 100% rename from Frontend/src/app/product-detail-page/product-detail-page.component.spec.ts rename to Frontend/src/app/components/product-detail-page/product-detail-page.component.spec.ts diff --git a/Frontend/src/app/product-detail-page/product-detail-page.component.ts b/Frontend/src/app/components/product-detail-page/product-detail-page.component.ts similarity index 100% rename from Frontend/src/app/product-detail-page/product-detail-page.component.ts rename to Frontend/src/app/components/product-detail-page/product-detail-page.component.ts diff --git a/Frontend/src/app/components/product-details/product-details.component.css b/Frontend/src/app/components/product-details/product-details.component.css new file mode 100644 index 0000000..e69de29 diff --git a/Frontend/src/app/components/product-details/product-details.component.html b/Frontend/src/app/components/product-details/product-details.component.html new file mode 100644 index 0000000..b8bfedf --- /dev/null +++ b/Frontend/src/app/components/product-details/product-details.component.html @@ -0,0 +1 @@ +

product-details works!

diff --git a/Frontend/src/app/components/product-details/product-details.component.spec.ts b/Frontend/src/app/components/product-details/product-details.component.spec.ts new file mode 100644 index 0000000..4181ef9 --- /dev/null +++ b/Frontend/src/app/components/product-details/product-details.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ProductDetailsComponent } from './product-details.component'; + +describe('ProductDetailsComponent', () => { + let component: ProductDetailsComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ ProductDetailsComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(ProductDetailsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/Frontend/src/app/components/product-details/product-details.component.ts b/Frontend/src/app/components/product-details/product-details.component.ts new file mode 100644 index 0000000..d04a454 --- /dev/null +++ b/Frontend/src/app/components/product-details/product-details.component.ts @@ -0,0 +1,15 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'app-product-details', + templateUrl: './product-details.component.html', + styleUrls: ['./product-details.component.css'] +}) +export class ProductDetailsComponent implements OnInit { + + constructor() { } + + ngOnInit(): void { + } + +} diff --git a/Frontend/src/app/product-list/product-list.component.css b/Frontend/src/app/components/product-list/product-list.component.css similarity index 100% rename from Frontend/src/app/product-list/product-list.component.css rename to Frontend/src/app/components/product-list/product-list.component.css diff --git a/Frontend/src/app/product-list/product-list.component.html b/Frontend/src/app/components/product-list/product-list.component.html similarity index 100% rename from Frontend/src/app/product-list/product-list.component.html rename to Frontend/src/app/components/product-list/product-list.component.html diff --git a/Frontend/src/app/product-list/product-list.component.spec.ts b/Frontend/src/app/components/product-list/product-list.component.spec.ts similarity index 100% rename from Frontend/src/app/product-list/product-list.component.spec.ts rename to Frontend/src/app/components/product-list/product-list.component.spec.ts diff --git a/Frontend/src/app/product-list/product-list.component.ts b/Frontend/src/app/components/product-list/product-list.component.ts similarity index 90% rename from Frontend/src/app/product-list/product-list.component.ts rename to Frontend/src/app/components/product-list/product-list.component.ts index 58e9100..70f774f 100644 --- a/Frontend/src/app/product-list/product-list.component.ts +++ b/Frontend/src/app/components/product-list/product-list.component.ts @@ -1,6 +1,6 @@ import {Component, Input, OnInit} from '@angular/core'; -import {ApiService} from '../api.service'; -import {Product} from '../models/product'; +import {ApiService} from '../../services/api.service'; +import {Product} from '../../models/product'; import {Router} from '@angular/router'; @Component({ diff --git a/Frontend/src/app/models/price.ts b/Frontend/src/app/models/price.ts new file mode 100644 index 0000000..49030e8 --- /dev/null +++ b/Frontend/src/app/models/price.ts @@ -0,0 +1,7 @@ +export interface Price { + price_id: number; + product_id: number; + vendor_id: number; + price_in_cents: number; + timestamp: Date; +} diff --git a/Frontend/src/app/api.service.spec.ts b/Frontend/src/app/services/api.service.spec.ts similarity index 100% rename from Frontend/src/app/api.service.spec.ts rename to Frontend/src/app/services/api.service.spec.ts diff --git a/Frontend/src/app/api.service.ts b/Frontend/src/app/services/api.service.ts similarity index 62% rename from Frontend/src/app/api.service.ts rename to Frontend/src/app/services/api.service.ts index e029888..7487633 100644 --- a/Frontend/src/app/api.service.ts +++ b/Frontend/src/app/services/api.service.ts @@ -1,7 +1,8 @@ import {Injectable} from '@angular/core'; import {HttpClient, HttpHeaders} from '@angular/common/http'; import process from 'process'; -import {Product} from './models/product'; +import {Product} from '../models/product'; +import {Price} from '../models/price'; import {Observable, of} from 'rxjs'; @Injectable({ @@ -24,4 +25,14 @@ export class ApiService { process.stderr.write(`ERROR received from ${this.apiUrl}: ${exception}\n`); } } + + getPrices(): Observable { + try { + const prices = this.http.get((this.apiUrl + '/prices')); + console.log(prices); + return prices; + } catch (exception) { + process.stderr.write(`ERROR received from ${this.apiUrl}: ${exception}\n`); + } + } } From 76545e56ce308e994a0f01aee85280a9db747d60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20M=C3=BCller?= Date: Tue, 8 Dec 2020 22:24:40 +0100 Subject: [PATCH 2/6] BETTERZON-42: Adding main components to PDP --- .../product-detail-page/product-detail-page.component.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Frontend/src/app/components/product-detail-page/product-detail-page.component.html b/Frontend/src/app/components/product-detail-page/product-detail-page.component.html index b57e35b..f55bc46 100644 --- a/Frontend/src/app/components/product-detail-page/product-detail-page.component.html +++ b/Frontend/src/app/components/product-detail-page/product-detail-page.component.html @@ -1 +1,2 @@ -

product-detail-page works!

+ + From acdc9a4e92588190e2dc430feaea1e786b949610 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20M=C3=BCller?= Date: Wed, 9 Dec 2020 11:00:49 +0100 Subject: [PATCH 3/6] BETTERZON-42: Functionality and design of various components for the search product UC --- Frontend/src/app/app.module.ts | 8 +++- Frontend/src/app/app.routing.ts | 4 +- .../components/header/header.component.css | 42 +++++++++++++++++++ .../components/header/header.component.html | 15 ++++++- .../app/components/header/header.component.ts | 32 ++++++++++---- .../newest-prices-list.component.css | 7 ++++ .../newest-prices-list.component.html | 4 +- .../product-details.component.css | 10 +++-- .../product-details.component.html | 1 - .../product-list/product-list.component.html | 4 +- .../product-list/product-list.component.ts | 36 +++++++++++++--- .../landingpage/landingpage.component.css | 5 +++ .../landingpage/landingpage.component.html | 4 +- .../page-not-found-page.component.css | 0 .../page-not-found-page.component.html | 2 + .../page-not-found-page.component.spec.ts | 25 +++++++++++ .../page-not-found-page.component.ts | 15 +++++++ .../product-detail-page.component.css | 3 ++ .../product-detail-page.component.html | 5 ++- .../product-search-page.component.css | 5 +++ .../product-search-page.component.html | 5 ++- Frontend/src/app/services/api.service.ts | 11 ++++- Frontend/src/assets/images/Betterzon.svg | 1 + Frontend/src/styles.css | 4 ++ 24 files changed, 219 insertions(+), 29 deletions(-) create mode 100644 Frontend/src/app/pages/page-not-found-page/page-not-found-page.component.css create mode 100644 Frontend/src/app/pages/page-not-found-page/page-not-found-page.component.html create mode 100644 Frontend/src/app/pages/page-not-found-page/page-not-found-page.component.spec.ts create mode 100644 Frontend/src/app/pages/page-not-found-page/page-not-found-page.component.ts create mode 100644 Frontend/src/assets/images/Betterzon.svg diff --git a/Frontend/src/app/app.module.ts b/Frontend/src/app/app.module.ts index fd204c0..01415bc 100644 --- a/Frontend/src/app/app.module.ts +++ b/Frontend/src/app/app.module.ts @@ -13,6 +13,8 @@ import {NgApexchartsModule} from 'ng-apexcharts'; import { ProductSearchPageComponent } from './pages/product-search-page/product-search-page.component'; import { HeaderComponent } from './components/header/header.component'; import { NewestPricesListComponent } from './components/newest-prices-list/newest-prices-list.component'; +import {FormsModule} from '@angular/forms'; +import { PageNotFoundPageComponent } from './pages/page-not-found-page/page-not-found-page.component'; @NgModule({ declarations: [ @@ -24,13 +26,15 @@ import { NewestPricesListComponent } from './components/newest-prices-list/newes ProductDetailsComponent, ProductSearchPageComponent, HeaderComponent, - NewestPricesListComponent + NewestPricesListComponent, + PageNotFoundPageComponent ], imports: [ BrowserModule, AppRouting, HttpClientModule, - NgApexchartsModule + NgApexchartsModule, + FormsModule ], providers: [], bootstrap: [AppComponent] diff --git a/Frontend/src/app/app.routing.ts b/Frontend/src/app/app.routing.ts index e76c894..efb2a36 100644 --- a/Frontend/src/app/app.routing.ts +++ b/Frontend/src/app/app.routing.ts @@ -6,11 +6,13 @@ import {ProductListComponent} from './components/product-list/product-list.compo import {LandingpageComponent} from './pages/landingpage/landingpage.component'; import {ProductDetailPageComponent} from './pages/product-detail-page/product-detail-page.component'; import {ProductSearchPageComponent} from './pages/product-search-page/product-search-page.component'; +import {PageNotFoundPageComponent} from './pages/page-not-found-page/page-not-found-page.component'; const routes: Routes = [ {path: '', component: LandingpageComponent}, {path: 'search', component: ProductSearchPageComponent}, - {path: 'product/:id', component: ProductDetailPageComponent} + {path: 'product/:id', component: ProductDetailPageComponent}, + {path: '**', component: PageNotFoundPageComponent} ]; @NgModule({ diff --git a/Frontend/src/app/components/header/header.component.css b/Frontend/src/app/components/header/header.component.css index e69de29..e3147c8 100644 --- a/Frontend/src/app/components/header/header.component.css +++ b/Frontend/src/app/components/header/header.component.css @@ -0,0 +1,42 @@ +.header { + width: auto; + background-color: dimgrey; + color: white; + text-align: center; + padding: .25em; +} + +#headerContent { + display: flex; +} + +.logo { + position: relative; + margin-left: 50%; +} + +.searchBox { + position: relative; + margin: auto; + margin-left: 25%; +} + +.searchBox input { + width: 100%; + padding: .25em; + display: inline-block; + border: 1px solid #ccc; + border-radius: 4px; + box-sizing: border-box; +} + +.profileIcon { + position: relative; + margin: auto; + margin-left: 10%; +} + +.icon-3d { + padding: 10px; + color: #fff; +} diff --git a/Frontend/src/app/components/header/header.component.html b/Frontend/src/app/components/header/header.component.html index 4f5a95d..38bc6fa 100644 --- a/Frontend/src/app/components/header/header.component.html +++ b/Frontend/src/app/components/header/header.component.html @@ -1 +1,14 @@ -

header works!

+ +
+
+ + +
+ Profile +
+
+
diff --git a/Frontend/src/app/components/header/header.component.ts b/Frontend/src/app/components/header/header.component.ts index a093fe7..3a1d6a4 100644 --- a/Frontend/src/app/components/header/header.component.ts +++ b/Frontend/src/app/components/header/header.component.ts @@ -1,15 +1,33 @@ -import { Component, OnInit } from '@angular/core'; +import {Component, OnInit} from '@angular/core'; +import {Router} from '@angular/router'; @Component({ - selector: 'app-header', - templateUrl: './header.component.html', - styleUrls: ['./header.component.css'] + selector: 'app-header', + templateUrl: './header.component.html', + styleUrls: ['./header.component.css'] }) export class HeaderComponent implements OnInit { + searchInput: string; - constructor() { } + constructor( + private router: Router + ) { + } - ngOnInit(): void { - } + ngOnInit(): void { + } + + clickedLogo(): void { + this.router.navigate([('/')]); + } + + startedSearch(): void { + this.redirectTo('/search', {queryParams: {q: this.searchInput}}); + } + + redirectTo(uri: string, queryParams: object): void { + this.router.navigateByUrl('/', {skipLocationChange: true}).then(() => + this.router.navigate([uri], queryParams)); + } } diff --git a/Frontend/src/app/components/newest-prices-list/newest-prices-list.component.css b/Frontend/src/app/components/newest-prices-list/newest-prices-list.component.css index e69de29..0eacd0e 100644 --- a/Frontend/src/app/components/newest-prices-list/newest-prices-list.component.css +++ b/Frontend/src/app/components/newest-prices-list/newest-prices-list.component.css @@ -0,0 +1,7 @@ +.priceList { + max-width: 50%; + margin: auto; + margin: auto; + align-content: center; + text-align: center; +} diff --git a/Frontend/src/app/components/newest-prices-list/newest-prices-list.component.html b/Frontend/src/app/components/newest-prices-list/newest-prices-list.component.html index 780c261..c62745c 100644 --- a/Frontend/src/app/components/newest-prices-list/newest-prices-list.component.html +++ b/Frontend/src/app/components/newest-prices-list/newest-prices-list.component.html @@ -1 +1,3 @@ -

newest-prices-list works!

+
+ PriceList +
diff --git a/Frontend/src/app/components/product-details/product-details.component.css b/Frontend/src/app/components/product-details/product-details.component.css index 7f1ac6b..55239cd 100644 --- a/Frontend/src/app/components/product-details/product-details.component.css +++ b/Frontend/src/app/components/product-details/product-details.component.css @@ -4,11 +4,11 @@ margin: auto; margin-bottom: .5em; display: grid; - grid-template-columns: 20% 50% 30%; + grid-template-columns: 20% 25% 25% 30%; grid-template-areas: - 'image title priceChart' - 'image description priceChart' - 'image priceAlarm bestPrice'; + 'image title title priceChart' + 'image description description priceChart' + 'image priceAlarm bestPrice blank'; } /* Image div */ @@ -54,6 +54,7 @@ grid-area: priceAlarm; border-style: solid; border-color: dimgrey; + border-radius: 5px; padding: .25em; margin: auto; } @@ -63,6 +64,7 @@ grid-area: bestPrice; border-style: solid; border-color: dimgrey; + border-radius: 5px; padding: .25em; margin: auto; } diff --git a/Frontend/src/app/components/product-details/product-details.component.html b/Frontend/src/app/components/product-details/product-details.component.html index 3e268b8..f4c7667 100644 --- a/Frontend/src/app/components/product-details/product-details.component.html +++ b/Frontend/src/app/components/product-details/product-details.component.html @@ -1,4 +1,3 @@ -
diff --git a/Frontend/src/app/components/product-list/product-list.component.html b/Frontend/src/app/components/product-list/product-list.component.html index c35c5b1..6f9a8e5 100644 --- a/Frontend/src/app/components/product-list/product-list.component.html +++ b/Frontend/src/app/components/product-list/product-list.component.html @@ -1,4 +1,6 @@ - +
+ No Products found! +
diff --git a/Frontend/src/app/components/product-list/product-list.component.ts b/Frontend/src/app/components/product-list/product-list.component.ts index b7a87cb..94bab8a 100644 --- a/Frontend/src/app/components/product-list/product-list.component.ts +++ b/Frontend/src/app/components/product-list/product-list.component.ts @@ -1,7 +1,7 @@ import {Component, Input, OnInit} from '@angular/core'; import {ApiService} from '../../services/api.service'; import {Product} from '../../models/product'; -import {Router} from '@angular/router'; +import {ActivatedRoute, Router} from '@angular/router'; @Component({ selector: 'app-product-list', @@ -9,33 +9,57 @@ import {Router} from '@angular/router'; styleUrls: ['./product-list.component.css'] }) export class ProductListComponent implements OnInit { - products: Product[]; + products: Product[] = []; @Input() numberOfProducts: number; @Input() showProductPicture: boolean; - type: string; + @Input() searchQuery: string; + @Input() type: string; constructor( private apiService: ApiService, - private router: Router + private router: Router, + private route: ActivatedRoute ) { } ngOnInit(): void { - this.getProducts(); + this.loadParams(); + } + loadParams(): void { if (!this.numberOfProducts) { this.numberOfProducts = 10; } if (!this.showProductPicture) { this.showProductPicture = false; } - this.type = 'PLP'; + if (!this.searchQuery) { + this.searchQuery = ''; + } + if (!this.type) { + this.type = ''; + } + + switch (this.type) { + case 'search': { + this.getSearchedProducts(); + break; + } + default: { + this.getProducts(); + break; + } + } } getProducts(): void { this.apiService.getProducts().subscribe(products => this.products = products); } + getSearchedProducts(): void { + this.apiService.getProductsByQuery(this.searchQuery).subscribe(products => this.products = products); + } + clickedProduct(product: Product): void { this.router.navigate([('/product/' + product.product_id)]); } diff --git a/Frontend/src/app/pages/landingpage/landingpage.component.css b/Frontend/src/app/pages/landingpage/landingpage.component.css index e69de29..653b875 100644 --- a/Frontend/src/app/pages/landingpage/landingpage.component.css +++ b/Frontend/src/app/pages/landingpage/landingpage.component.css @@ -0,0 +1,5 @@ +#mainComponents { + margin: 5em; + margin-top: .5em; + margin-bottom: .5em; +} diff --git a/Frontend/src/app/pages/landingpage/landingpage.component.html b/Frontend/src/app/pages/landingpage/landingpage.component.html index 810a156..e724bd0 100644 --- a/Frontend/src/app/pages/landingpage/landingpage.component.html +++ b/Frontend/src/app/pages/landingpage/landingpage.component.html @@ -1,3 +1,5 @@ - +
+ +
diff --git a/Frontend/src/app/pages/page-not-found-page/page-not-found-page.component.css b/Frontend/src/app/pages/page-not-found-page/page-not-found-page.component.css new file mode 100644 index 0000000..e69de29 diff --git a/Frontend/src/app/pages/page-not-found-page/page-not-found-page.component.html b/Frontend/src/app/pages/page-not-found-page/page-not-found-page.component.html new file mode 100644 index 0000000..8e032b3 --- /dev/null +++ b/Frontend/src/app/pages/page-not-found-page/page-not-found-page.component.html @@ -0,0 +1,2 @@ +

404

+

Page not found!

diff --git a/Frontend/src/app/pages/page-not-found-page/page-not-found-page.component.spec.ts b/Frontend/src/app/pages/page-not-found-page/page-not-found-page.component.spec.ts new file mode 100644 index 0000000..67193bb --- /dev/null +++ b/Frontend/src/app/pages/page-not-found-page/page-not-found-page.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { PageNotFoundPageComponent } from './page-not-found-page.component'; + +describe('PageNotFoundPageComponent', () => { + let component: PageNotFoundPageComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ PageNotFoundPageComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(PageNotFoundPageComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/Frontend/src/app/pages/page-not-found-page/page-not-found-page.component.ts b/Frontend/src/app/pages/page-not-found-page/page-not-found-page.component.ts new file mode 100644 index 0000000..9de002b --- /dev/null +++ b/Frontend/src/app/pages/page-not-found-page/page-not-found-page.component.ts @@ -0,0 +1,15 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'app-page-not-found-page', + templateUrl: './page-not-found-page.component.html', + styleUrls: ['./page-not-found-page.component.css'] +}) +export class PageNotFoundPageComponent implements OnInit { + + constructor() { } + + ngOnInit(): void { + } + +} diff --git a/Frontend/src/app/pages/product-detail-page/product-detail-page.component.css b/Frontend/src/app/pages/product-detail-page/product-detail-page.component.css index e69de29..e4095ed 100644 --- a/Frontend/src/app/pages/product-detail-page/product-detail-page.component.css +++ b/Frontend/src/app/pages/product-detail-page/product-detail-page.component.css @@ -0,0 +1,3 @@ +#mainComponents { + padding: 5em; +} diff --git a/Frontend/src/app/pages/product-detail-page/product-detail-page.component.html b/Frontend/src/app/pages/product-detail-page/product-detail-page.component.html index 1b9ce73..e919a51 100644 --- a/Frontend/src/app/pages/product-detail-page/product-detail-page.component.html +++ b/Frontend/src/app/pages/product-detail-page/product-detail-page.component.html @@ -1,3 +1,6 @@ - +
+ + +
diff --git a/Frontend/src/app/pages/product-search-page/product-search-page.component.css b/Frontend/src/app/pages/product-search-page/product-search-page.component.css index e69de29..653b875 100644 --- a/Frontend/src/app/pages/product-search-page/product-search-page.component.css +++ b/Frontend/src/app/pages/product-search-page/product-search-page.component.css @@ -0,0 +1,5 @@ +#mainComponents { + margin: 5em; + margin-top: .5em; + margin-bottom: .5em; +} diff --git a/Frontend/src/app/pages/product-search-page/product-search-page.component.html b/Frontend/src/app/pages/product-search-page/product-search-page.component.html index 810a156..6bd08c7 100644 --- a/Frontend/src/app/pages/product-search-page/product-search-page.component.html +++ b/Frontend/src/app/pages/product-search-page/product-search-page.component.html @@ -1,3 +1,6 @@ - +
+ +
diff --git a/Frontend/src/app/services/api.service.ts b/Frontend/src/app/services/api.service.ts index bb54404..c53ff9e 100644 --- a/Frontend/src/app/services/api.service.ts +++ b/Frontend/src/app/services/api.service.ts @@ -19,17 +19,24 @@ export class ApiService { getProduct(id): Observable { try { const prod = this.http.get((this.apiUrl + '/products/' + id)); - console.log(prod); return prod; } catch (exception) { process.stderr.write(`ERROR received from ${this.apiUrl}: ${exception}\n`); } } + getProductsByQuery(query): Observable { + try { + const prods = this.http.get((this.apiUrl + '/products/search/' + query)); + return prods; + } catch (exception) { + process.stderr.write(`ERROR received from ${this.apiUrl}: ${exception}\n`); + } + } + getProducts(): Observable { try { const prods = this.http.get((this.apiUrl + '/products')); - console.log(prods); return prods; } catch (exception) { process.stderr.write(`ERROR received from ${this.apiUrl}: ${exception}\n`); diff --git a/Frontend/src/assets/images/Betterzon.svg b/Frontend/src/assets/images/Betterzon.svg new file mode 100644 index 0000000..406b3fa --- /dev/null +++ b/Frontend/src/assets/images/Betterzon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Frontend/src/styles.css b/Frontend/src/styles.css index 90d4ee0..e6cbeed 100644 --- a/Frontend/src/styles.css +++ b/Frontend/src/styles.css @@ -1 +1,5 @@ /* You can add global styles to this file, and also import other style files */ +body { + margin: 0; + font-family: sans-serif; +} From 6338060b7825ea8d497a534416d6388223280dd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20M=C3=BCller?= Date: Wed, 9 Dec 2020 17:26:56 +0100 Subject: [PATCH 4/6] BETTERZON-38: Adjusted prices API --- Backend/src/models/prices/prices.router.ts | 35 ++++----- Backend/src/models/prices/prices.service.ts | 80 +++++++++++++++++++++ 2 files changed, 94 insertions(+), 21 deletions(-) diff --git a/Backend/src/models/prices/prices.router.ts b/Backend/src/models/prices/prices.router.ts index 976cc9f..9672efa 100644 --- a/Backend/src/models/prices/prices.router.ts +++ b/Backend/src/models/prices/prices.router.ts @@ -23,7 +23,20 @@ export const pricesRouter = express.Router(); pricesRouter.get('/', async (req: Request, res: Response) => { try { - const prices: Prices = await PriceService.findAll(); + let prices: Prices = []; + const product = req.query.product; + const vendor = req.query.vendor; + const type = req.query.type; + + if (product) { + if (vendor) { + prices = await PriceService.findByVendor( product, vendor, type); + } else { + prices = await PriceService.findByType( product, type); + } + } else { + prices = await PriceService.findAll(); + } res.status(200).send(prices); } catch (e) { @@ -50,26 +63,6 @@ pricesRouter.get('/:id', async (req: Request, res: Response) => { } }); -// GET items/:name - -pricesRouter.get('/products/:id', async (req: Request, res: Response) => { - const id: number = parseInt(req.params.id, 10); - - if (!id) { - res.status(400).send('Missing parameters.'); - return; - } - - try { - const prices: Prices = await PriceService.findByProduct(id); - - res.status(200).send(prices); - } catch (e) { - res.status(404).send(e.message); - } -}); - - // POST items/ // pricesRouter.post('/', async (req: Request, res: Response) => { diff --git a/Backend/src/models/prices/prices.service.ts b/Backend/src/models/prices/prices.service.ts index 81915e7..14846fe 100644 --- a/Backend/src/models/prices/prices.service.ts +++ b/Backend/src/models/prices/prices.service.ts @@ -106,6 +106,86 @@ export const findByProduct = async (product: number): Promise => { return priceRows; }; +export const findByType = async (product: string, type: string): Promise => { + let conn; + let priceRows = []; + try { + conn = await pool.getConnection(); + let rows = []; + if (type === 'newest') { + // Used to get the newest price for this product per vendor + rows = await conn.query(('WITH summary AS ( ' + + 'SELECT p.product_id, ' + + 'p.vendor_id, ' + + 'p.price_in_cents, ' + + 'p.timestamp, ' + + 'ROW_NUMBER() OVER( ' + + 'PARTITION BY p.vendor_id ' + + 'ORDER BY p.timestamp DESC) AS rk ' + + 'FROM prices p ' + + 'WHERE product_id = ?) ' + + 'SELECT s.* ' + + 'FROM summary s ' + + 'WHERE s.rk = 1 '), product); + } else if (type === 'lowest') { + // Used to get the lowest prices for this product over a period of time + rows = await conn.query('SELECT price_id, product_id, vendor_id, MIN(price_in_cents) as price_in_cents, timestamp FROM prices WHERE product_id = ? GROUP BY DAY(timestamp) ORDER BY timestamp', product); + } else { + // If no type is given, return all prices for this product + rows = await conn.query('SELECT price_id, product_id, vendor_id, price_in_cents, timestamp FROM prices WHERE product_id = ?', product); + } + + for (let row in rows) { + if (row !== 'meta') { + priceRows.push(rows[row]); + } + } + + } catch (err) { + throw err; + } finally { + if (conn) { + conn.end(); + } + } + + return priceRows; +}; + +export const findByVendor = async (product: string, vendor: string, type: string): Promise => { + let conn; + let priceRows = []; + try { + conn = await pool.getConnection(); + let rows = []; + if (type === 'newest') { + // Used to get the newest price for this product and vendor + rows = await conn.query('SELECT price_id, product_id, vendor_id, price_in_cents, timestamp FROM prices WHERE product_id = ? AND vendor_id = ? ORDER BY timestamp DESC LIMIT 1', [product, vendor]); + } else if (type === 'lowest') { + // Used to get the lowest prices for this product and vendor in all time + rows = await conn.query('SELECT price_id, product_id, vendor_id, MIN(price_in_cents) as price_in_cents, timestamp FROM prices WHERE product_id = ? AND vendor_id = ? LIMIT 1', [product, vendor]); + } else { + // If no type is given, return all prices for this product and vendor + rows = await conn.query('SELECT price_id, product_id, vendor_id, price_in_cents, timestamp FROM prices WHERE product_id = ? AND vendor_id = ?', [product, vendor]); + } + + for (let row in rows) { + if (row !== 'meta') { + priceRows.push(rows[row]); + } + } + + } catch (err) { + throw err; + } finally { + if (conn) { + conn.end(); + } + } + + return priceRows; +}; + // export const create = async (newItem: Product): Promise => { // let conn; // try { From 31423c630a620e09221fc32dd0efb11b1ea2ad29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20M=C3=BCller?= Date: Wed, 9 Dec 2020 20:35:08 +0100 Subject: [PATCH 5/6] BETTERZON-25: Finishing prototype of UC --- Backend/src/models/prices/prices.service.ts | 6 +- Frontend/src/app/app.module.ts | 16 ++--- .../components/header/header.component.html | 2 +- .../app/components/header/header.component.ts | 6 +- .../newest-prices-list.component.css | 28 +++++++-- .../newest-prices-list.component.html | 13 +++- .../newest-prices-list.component.ts | 44 +++++++++++--- .../product-details.component.css | 19 +++++- .../product-details.component.html | 15 +++-- .../product-details.component.ts | 59 ++++++++++++++++++- .../product-list/product-list.component.css | 2 +- Frontend/src/app/models/vendor.ts | 10 ++++ .../landingpage/landingpage.component.css | 45 ++++++++++++++ .../landingpage/landingpage.component.html | 17 +++++- .../landingpage/landingpage.component.ts | 28 ++++++--- .../product-detail-page.component.html | 4 +- .../product-search-page.component.html | 2 +- Frontend/src/app/services/api.service.ts | 50 +++++++++++++++- 18 files changed, 315 insertions(+), 51 deletions(-) create mode 100644 Frontend/src/app/models/vendor.ts diff --git a/Backend/src/models/prices/prices.service.ts b/Backend/src/models/prices/prices.service.ts index 14846fe..b0a8038 100644 --- a/Backend/src/models/prices/prices.service.ts +++ b/Backend/src/models/prices/prices.service.ts @@ -123,16 +123,16 @@ export const findByType = async (product: string, type: string): Promise 'PARTITION BY p.vendor_id ' + 'ORDER BY p.timestamp DESC) AS rk ' + 'FROM prices p ' + - 'WHERE product_id = ?) ' + + 'WHERE product_id = ? AND vendor_id != 1) ' + 'SELECT s.* ' + 'FROM summary s ' + 'WHERE s.rk = 1 '), product); } else if (type === 'lowest') { // Used to get the lowest prices for this product over a period of time - rows = await conn.query('SELECT price_id, product_id, vendor_id, MIN(price_in_cents) as price_in_cents, timestamp FROM prices WHERE product_id = ? GROUP BY DAY(timestamp) ORDER BY timestamp', product); + rows = await conn.query('SELECT price_id, product_id, vendor_id, MIN(price_in_cents) as price_in_cents, timestamp FROM prices WHERE product_id = ? AND vendor_id != 1 GROUP BY DAY(timestamp) ORDER BY timestamp', product); } else { // If no type is given, return all prices for this product - rows = await conn.query('SELECT price_id, product_id, vendor_id, price_in_cents, timestamp FROM prices WHERE product_id = ?', product); + rows = await conn.query('SELECT price_id, product_id, vendor_id, price_in_cents, timestamp FROM prices WHERE product_id = ? AND vendor_id != 1', product); } for (let row in rows) { diff --git a/Frontend/src/app/app.module.ts b/Frontend/src/app/app.module.ts index 01415bc..eef8e23 100644 --- a/Frontend/src/app/app.module.ts +++ b/Frontend/src/app/app.module.ts @@ -5,16 +5,16 @@ import {NgModule} from '@angular/core'; import {AppComponent} from './app.component'; import {AppRouting} from './app.routing'; import {ProductListComponent} from './components/product-list/product-list.component'; -import { LandingpageComponent } from './pages/landingpage/landingpage.component'; -import { ProductDetailPageComponent } from './pages/product-detail-page/product-detail-page.component'; -import { FooterComponent } from './components/footer/footer.component'; -import { ProductDetailsComponent } from './components/product-details/product-details.component'; +import {LandingpageComponent} from './pages/landingpage/landingpage.component'; +import {ProductDetailPageComponent} from './pages/product-detail-page/product-detail-page.component'; +import {FooterComponent} from './components/footer/footer.component'; +import {ProductDetailsComponent} from './components/product-details/product-details.component'; import {NgApexchartsModule} from 'ng-apexcharts'; -import { ProductSearchPageComponent } from './pages/product-search-page/product-search-page.component'; -import { HeaderComponent } from './components/header/header.component'; -import { NewestPricesListComponent } from './components/newest-prices-list/newest-prices-list.component'; +import {ProductSearchPageComponent} from './pages/product-search-page/product-search-page.component'; +import {HeaderComponent} from './components/header/header.component'; +import {NewestPricesListComponent} from './components/newest-prices-list/newest-prices-list.component'; import {FormsModule} from '@angular/forms'; -import { PageNotFoundPageComponent } from './pages/page-not-found-page/page-not-found-page.component'; +import {PageNotFoundPageComponent} from './pages/page-not-found-page/page-not-found-page.component'; @NgModule({ declarations: [ diff --git a/Frontend/src/app/components/header/header.component.html b/Frontend/src/app/components/header/header.component.html index 38bc6fa..f99e61f 100644 --- a/Frontend/src/app/components/header/header.component.html +++ b/Frontend/src/app/components/header/header.component.html @@ -5,7 +5,7 @@ Betterzon Logo
Profile diff --git a/Frontend/src/app/components/header/header.component.ts b/Frontend/src/app/components/header/header.component.ts index 3a1d6a4..ee76e9f 100644 --- a/Frontend/src/app/components/header/header.component.ts +++ b/Frontend/src/app/components/header/header.component.ts @@ -1,4 +1,4 @@ -import {Component, OnInit} from '@angular/core'; +import {Component, Input, OnInit} from '@angular/core'; import {Router} from '@angular/router'; @Component({ @@ -8,6 +8,7 @@ import {Router} from '@angular/router'; }) export class HeaderComponent implements OnInit { searchInput: string; + @Input() showSearch: boolean; constructor( private router: Router @@ -15,6 +16,9 @@ export class HeaderComponent implements OnInit { } ngOnInit(): void { + if (!this.showSearch) { + this.showSearch = false; + } } clickedLogo(): void { diff --git a/Frontend/src/app/components/newest-prices-list/newest-prices-list.component.css b/Frontend/src/app/components/newest-prices-list/newest-prices-list.component.css index 0eacd0e..761167b 100644 --- a/Frontend/src/app/components/newest-prices-list/newest-prices-list.component.css +++ b/Frontend/src/app/components/newest-prices-list/newest-prices-list.component.css @@ -1,7 +1,25 @@ .priceList { - max-width: 50%; - margin: auto; - margin: auto; - align-content: center; - text-align: center; + margin: 2em; + position: relative; +} + +.priceList table { + position: relative; + margin: auto; + font-family: Arial, Helvetica, sans-serif; + border-collapse: collapse; + width: 80%; +} + +.priceList table td, .priceList table th { + border: 1px solid #ddd; + padding: 8px; +} + +.priceList table tr:nth-child(even) { + background-color: #f2f2f2; +} + +.priceList table tr:hover { + background-color: #ddd; } diff --git a/Frontend/src/app/components/newest-prices-list/newest-prices-list.component.html b/Frontend/src/app/components/newest-prices-list/newest-prices-list.component.html index c62745c..0c8de11 100644 --- a/Frontend/src/app/components/newest-prices-list/newest-prices-list.component.html +++ b/Frontend/src/app/components/newest-prices-list/newest-prices-list.component.html @@ -1,3 +1,14 @@
- PriceList + + + + + + + + + + + +
VendorCurrent priceVisit
{{vendorMap[price.vendor_id]?.name}}{{price.price_in_cents / 100}}€Visit Shop
diff --git a/Frontend/src/app/components/newest-prices-list/newest-prices-list.component.ts b/Frontend/src/app/components/newest-prices-list/newest-prices-list.component.ts index 42b67a6..6c490b8 100644 --- a/Frontend/src/app/components/newest-prices-list/newest-prices-list.component.ts +++ b/Frontend/src/app/components/newest-prices-list/newest-prices-list.component.ts @@ -1,15 +1,45 @@ -import { Component, OnInit } from '@angular/core'; +import {Component, Input, OnInit} from '@angular/core'; +import {Price} from '../../models/price'; +import {Vendor} from '../../models/vendor'; +import {ApiService} from '../../services/api.service'; @Component({ - selector: 'app-newest-prices-list', - templateUrl: './newest-prices-list.component.html', - styleUrls: ['./newest-prices-list.component.css'] + selector: 'app-newest-prices-list', + templateUrl: './newest-prices-list.component.html', + styleUrls: ['./newest-prices-list.component.css'] }) export class NewestPricesListComponent implements OnInit { + @Input() productId: number; + prices: Price[] = []; + vendors: Vendor[] = []; + vendorMap = {}; - constructor() { } + constructor( + private apiService: ApiService + ) { + } - ngOnInit(): void { - } + ngOnInit(): void { + this.getVendors(); + this.getPrices(); + } + + getPrices(): void { + // Lowest prices + this.apiService.getCurrentPricePerVendor(this.productId).subscribe( + prices => { + this.prices = prices; + }); + } + + getVendors(): void { + this.apiService.getVendors().subscribe(vendors => { + this.vendors = vendors; + + this.vendors.forEach(vendor => { + this.vendorMap[vendor.vendor_id] = vendor; + }); + }); + } } diff --git a/Frontend/src/app/components/product-details/product-details.component.css b/Frontend/src/app/components/product-details/product-details.component.css index 55239cd..42dfa5b 100644 --- a/Frontend/src/app/components/product-details/product-details.component.css +++ b/Frontend/src/app/components/product-details/product-details.component.css @@ -21,7 +21,7 @@ .productImage { max-width: 300px; max-height: 300px; - display:block; + display: block; margin: auto; position: absolute; top: 0; @@ -57,14 +57,27 @@ border-radius: 5px; padding: .25em; margin: auto; + font-size: 1.5em; } -/* Best price div */ -.bestPrice { +/* Best price container div */ +.bestPriceContainer { grid-area: bestPrice; +} + +/* best price div */ +.bestPrice { border-style: solid; border-color: dimgrey; border-radius: 5px; padding: .25em; margin: auto; + text-align: center; + font-size: 1.5em; +} + +/* amazon price div */ +.amazonPrice { + margin-top: .5em; + text-align: center; } diff --git a/Frontend/src/app/components/product-details/product-details.component.html b/Frontend/src/app/components/product-details/product-details.component.html index f4c7667..f392f00 100644 --- a/Frontend/src/app/components/product-details/product-details.component.html +++ b/Frontend/src/app/components/product-details/product-details.component.html @@ -3,7 +3,7 @@
- {{product.name}} + {{product?.name}}
@@ -17,13 +17,20 @@
- {{product.short_description}} + {{product?.short_description}}
Set Price Alarm
-
- 5€ +
+
+ Best price: {{currentlyLowestPrice?.price_in_cents / 100}}€ at + vendor {{vendorMap[currentlyLowestPrice.vendor_id]?.name}}! +
+
+ Amazon-price: {{currentAmazonPrice?.price_in_cents / 100}}€ (+{{getAmazonPriceDifference()}}%) +
diff --git a/Frontend/src/app/components/product-details/product-details.component.ts b/Frontend/src/app/components/product-details/product-details.component.ts index 6537665..fa9e87d 100644 --- a/Frontend/src/app/components/product-details/product-details.component.ts +++ b/Frontend/src/app/components/product-details/product-details.component.ts @@ -8,6 +8,8 @@ import { ApexXAxis, ApexTitleSubtitle, ApexStroke } from 'ng-apexcharts'; +import {Price} from '../../models/price'; +import {Vendor} from '../../models/vendor'; export type ChartOptions = { series: ApexAxisChartSeries; @@ -25,6 +27,11 @@ export type ChartOptions = { export class ProductDetailsComponent implements OnInit { @Input() productId: number; product: Product; + lowestPrices: Price[]; + currentlyLowestPrice: Price; + currentAmazonPrice: Price; + vendors: Vendor[] = []; + vendorMap = {}; @ViewChild('chart') chart: ChartComponent; public chartOptions: ChartOptions; @@ -35,19 +42,53 @@ export class ProductDetailsComponent implements OnInit { ngOnInit(): void { this.getProduct(); - this.getChartData(); + this.getVendors(); + this.getPrices(); } getProduct(): void { this.apiService.getProduct(this.productId).subscribe(product => this.product = product); } + getPrices(): void { + // Lowest prices + this.apiService.getLowestPrices(this.productId).subscribe( + prices => { + this.lowestPrices = prices; + this.currentlyLowestPrice = prices[prices.length - 1]; + + // Update charts + this.getChartData(); + }); + + // Amazon price + this.apiService.getAmazonPrice(this.productId).subscribe(price => { + this.currentAmazonPrice = price[0]; + }); + } + + getVendors(): void { + this.apiService.getVendors().subscribe(vendors => { + this.vendors = vendors; + this.vendors.forEach(vendor => { + this.vendorMap[vendor.vendor_id] = vendor; + }); + }); + } + getChartData(): void { + const prices = []; + const categs = []; + this.lowestPrices?.forEach(price => { + prices.push(price.price_in_cents / 100); + categs.push(new Date(price.timestamp).toDateString()); + }); + this.chartOptions = { series: [ { name: 'Lowest Price', - data: [1061.20, 1060, 1070, 1040, 1061.20, 1061, 1100, 1070, 1061.20] + data: prices } ], chart: { @@ -58,7 +99,7 @@ export class ProductDetailsComponent implements OnInit { text: 'Lowest price' }, xaxis: { - categories: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep'] + categories: categs }, stroke: { curve: 'stepline' @@ -66,4 +107,16 @@ export class ProductDetailsComponent implements OnInit { }; } + getAmazonPriceDifference(): number { + const amazonPrice = this.currentAmazonPrice?.price_in_cents; + const lowestPrice = this.currentlyLowestPrice?.price_in_cents; + + const percentage = amazonPrice / lowestPrice; + + if (percentage < 1) { + return -Math.round(percentage); + } else { + return +Math.round(percentage); + } + } } diff --git a/Frontend/src/app/components/product-list/product-list.component.css b/Frontend/src/app/components/product-list/product-list.component.css index 814df74..d80c1f5 100644 --- a/Frontend/src/app/components/product-list/product-list.component.css +++ b/Frontend/src/app/components/product-list/product-list.component.css @@ -24,7 +24,7 @@ .productImage { max-width: 50px; max-height: 50px; - display:block; + display: block; margin: auto; position: absolute; top: 0; diff --git a/Frontend/src/app/models/vendor.ts b/Frontend/src/app/models/vendor.ts new file mode 100644 index 0000000..51afc49 --- /dev/null +++ b/Frontend/src/app/models/vendor.ts @@ -0,0 +1,10 @@ +export interface Vendor { + vendor_id: number; + name: string; + streetname: string; + zip_code: string; + city: string; + country_code: string; + phone: string; + website: string; +} diff --git a/Frontend/src/app/pages/landingpage/landingpage.component.css b/Frontend/src/app/pages/landingpage/landingpage.component.css index 653b875..cac058e 100644 --- a/Frontend/src/app/pages/landingpage/landingpage.component.css +++ b/Frontend/src/app/pages/landingpage/landingpage.component.css @@ -3,3 +3,48 @@ margin-top: .5em; margin-bottom: .5em; } + +#productListsContainer { + display: grid; + grid-template-areas: + 'search search' + 'popularSearches bestDeals'; + grid-template-columns: 50% 50%; +} + +#searchContainer { + position: relative; + grid-area: search; + height: 10em; +} + +#searchContainer input { + position: relative; + font-size: 1.5em; + padding: .25em; + display: block; + border: 1px solid #ccc; + border-radius: 4px; + box-sizing: border-box; + margin: auto; + -ms-transform: translateY(50%); + transform: translateY(2.5em); +} + +#popularSearchesList { + grid-area: popularSearches; + padding: .5em; +} + +#popularSearchesList h2 { + text-align: center; +} + +#bestDealsList { + grid-area: bestDeals; + padding: .5em; +} + +#bestDealsList h2 { + text-align: center; +} diff --git a/Frontend/src/app/pages/landingpage/landingpage.component.html b/Frontend/src/app/pages/landingpage/landingpage.component.html index e724bd0..9cc4252 100644 --- a/Frontend/src/app/pages/landingpage/landingpage.component.html +++ b/Frontend/src/app/pages/landingpage/landingpage.component.html @@ -1,5 +1,18 @@ - +
- +
+ +
+
+
+

Popular Searches

+ +
+
+

Best Deals

+ +
+
diff --git a/Frontend/src/app/pages/landingpage/landingpage.component.ts b/Frontend/src/app/pages/landingpage/landingpage.component.ts index ad70dcb..d62ca82 100644 --- a/Frontend/src/app/pages/landingpage/landingpage.component.ts +++ b/Frontend/src/app/pages/landingpage/landingpage.component.ts @@ -1,15 +1,29 @@ -import { Component, OnInit } from '@angular/core'; +import {Component, OnInit} from '@angular/core'; +import {Router} from '@angular/router'; @Component({ - selector: 'app-landingpage', - templateUrl: './landingpage.component.html', - styleUrls: ['./landingpage.component.css'] + selector: 'app-landingpage', + templateUrl: './landingpage.component.html', + styleUrls: ['./landingpage.component.css'] }) export class LandingpageComponent implements OnInit { + searchInput: string; - constructor() { } + constructor( + private router: Router + ) { + } - ngOnInit(): void { - } + ngOnInit(): void { + } + + startedSearch(): void { + this.redirectTo('/search', {queryParams: {q: this.searchInput}}); + } + + redirectTo(uri: string, queryParams: object): void { + this.router.navigateByUrl('/', {skipLocationChange: true}).then(() => + this.router.navigate([uri], queryParams)); + } } diff --git a/Frontend/src/app/pages/product-detail-page/product-detail-page.component.html b/Frontend/src/app/pages/product-detail-page/product-detail-page.component.html index e919a51..a0b0822 100644 --- a/Frontend/src/app/pages/product-detail-page/product-detail-page.component.html +++ b/Frontend/src/app/pages/product-detail-page/product-detail-page.component.html @@ -1,6 +1,6 @@ - +
- +
diff --git a/Frontend/src/app/pages/product-search-page/product-search-page.component.html b/Frontend/src/app/pages/product-search-page/product-search-page.component.html index 6bd08c7..793cf38 100644 --- a/Frontend/src/app/pages/product-search-page/product-search-page.component.html +++ b/Frontend/src/app/pages/product-search-page/product-search-page.component.html @@ -1,4 +1,4 @@ - +
diff --git a/Frontend/src/app/services/api.service.ts b/Frontend/src/app/services/api.service.ts index c53ff9e..ec91051 100644 --- a/Frontend/src/app/services/api.service.ts +++ b/Frontend/src/app/services/api.service.ts @@ -1,9 +1,10 @@ import {Injectable} from '@angular/core'; -import {HttpClient, HttpHeaders} from '@angular/common/http'; +import {HttpClient, HttpHeaders, HttpParams} from '@angular/common/http'; import process from 'process'; import {Product} from '../models/product'; import {Price} from '../models/price'; import {Observable, of} from 'rxjs'; +import {Vendor} from '../models/vendor'; @Injectable({ providedIn: 'root' @@ -46,10 +47,55 @@ export class ApiService { getPrices(): Observable { try { const prices = this.http.get((this.apiUrl + '/prices')); - console.log(prices); return prices; } catch (exception) { process.stderr.write(`ERROR received from ${this.apiUrl}: ${exception}\n`); } } + + getLowestPrices(productId): Observable { + try { + let params = new HttpParams(); + params = params.append('product', productId); + params = params.append('type', 'lowest'); + const prices = this.http.get((this.apiUrl + '/prices'), {params}); + return prices; + } catch (exception) { + process.stderr.write(`ERROR received from ${this.apiUrl}: ${exception}\n`); + } + } + + getAmazonPrice(productId): Observable { + try { + let params = new HttpParams(); + params = params.append('product', productId); + params = params.append('vendor', '1'); + params = params.append('type', 'newest'); + const price = this.http.get((this.apiUrl + '/prices'), {params}); + return price; + } catch (exception) { + process.stderr.write(`ERROR received from ${this.apiUrl}: ${exception}\n`); + } + } + + getCurrentPricePerVendor(productId): Observable { + try { + let params = new HttpParams(); + params = params.append('product', productId); + params = params.append('type', 'newest'); + const prices = this.http.get((this.apiUrl + '/prices'), {params}); + return prices; + } catch (exception) { + process.stderr.write(`ERROR received from ${this.apiUrl}: ${exception}\n`); + } + } + + getVendors(): Observable { + try { + const vendors = this.http.get((this.apiUrl + '/vendors')); + return vendors; + } catch (exception) { + process.stderr.write(`ERROR received from ${this.apiUrl}: ${exception}\n`); + } + } } From ab277b3311fa552400606ed1125b8af49e764276 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20M=C3=BCller?= Date: Wed, 9 Dec 2020 21:33:37 +0100 Subject: [PATCH 6/6] BETTERZON-42: Fixing amazon price difference percentage --- .../product-details/product-details.component.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/Frontend/src/app/components/product-details/product-details.component.ts b/Frontend/src/app/components/product-details/product-details.component.ts index fa9e87d..9c18988 100644 --- a/Frontend/src/app/components/product-details/product-details.component.ts +++ b/Frontend/src/app/components/product-details/product-details.component.ts @@ -111,12 +111,8 @@ export class ProductDetailsComponent implements OnInit { const amazonPrice = this.currentAmazonPrice?.price_in_cents; const lowestPrice = this.currentlyLowestPrice?.price_in_cents; - const percentage = amazonPrice / lowestPrice; + const percentage = ((amazonPrice / lowestPrice) - 1) * 100; - if (percentage < 1) { - return -Math.round(percentage); - } else { - return +Math.round(percentage); - } + return Math.round(percentage); } }