Add basic result page

This commit is contained in:
Patrick Gebhardt 2020-06-14 20:34:51 +02:00
parent a9eab54674
commit 56818c4bff
17 changed files with 453 additions and 53 deletions

View File

@ -1,11 +1,13 @@
import {NgModule} from '@angular/core'; import {NgModule} from '@angular/core';
import { Routes, RouterModule } from '@angular/router'; import {RouterModule, Routes} from '@angular/router';
import {HomeComponent} from './containers/home/home.component'; import {HomeComponent} from './containers/home/home.component';
import {NotfoundComponent} from './containers/notfound/notfound.component'; import {NotfoundComponent} from './containers/notfound/notfound.component';
import {SearchComponent} from './containers/search/search.component';
const routes: Routes = [ const routes: Routes = [
{path: 'home', component: HomeComponent}, {path: 'home', component: HomeComponent},
{path: 'search', component: SearchComponent},
{path: '', redirectTo: 'home', pathMatch: 'full'}, {path: '', redirectTo: 'home', pathMatch: 'full'},
{path: '**', component: NotfoundComponent} {path: '**', component: NotfoundComponent}
]; ];

View File

@ -14,12 +14,17 @@ import {MatFormFieldModule} from '@angular/material/form-field';
import {MatInputModule} from '@angular/material/input'; import {MatInputModule} from '@angular/material/input';
import {MatSelectModule} from '@angular/material/select'; import {MatSelectModule} from '@angular/material/select';
import {NotfoundComponent} from './containers/notfound/notfound.component'; import {NotfoundComponent} from './containers/notfound/notfound.component';
import {SearchComponent} from './containers/search/search.component';
import {SearchInputComponent} from './components/search-input/search-input.component';
import {MatProgressSpinnerModule} from '@angular/material/progress-spinner';
@NgModule({ @NgModule({
declarations: [ declarations: [
AppComponent, AppComponent,
HomeComponent, HomeComponent,
NotfoundComponent NotfoundComponent,
SearchComponent,
SearchInputComponent
], ],
imports: [ imports: [
BrowserModule, BrowserModule,
@ -32,7 +37,8 @@ import { NotfoundComponent } from './containers/notfound/notfound.component';
MatCardModule, MatCardModule,
MatFormFieldModule, MatFormFieldModule,
MatInputModule, MatInputModule,
MatSelectModule MatSelectModule,
MatProgressSpinnerModule
], ],
providers: [], providers: [],
bootstrap: [AppComponent] bootstrap: [AppComponent]

View File

@ -0,0 +1,28 @@
<mat-card class="search-container">
<mat-form-field appearance="outline">
<mat-label>Search</mat-label>
<input #textSearch matInput>
<button (click)="textSearch.value=''" *ngIf="textSearch.value.length>0" mat-icon-button matSuffix>
<mat-icon>clear</mat-icon>
</button>
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>Start</mat-label>
<input matInput type="date">
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>End</mat-label>
<input matInput type="date">
</mat-form-field>
<mat-form-field>
<mat-label>Price</mat-label>
<mat-select value="Budget">
<mat-option value="Budget">Budget</mat-option>
<mat-option value="Mid-Range">Mid-Range</mat-option>
<mat-option value="Luxury">Luxury</mat-option>
</mat-select>
</mat-form-field>
<button (click)="onSearch()" color="primary" mat-flat-button>Search
<mat-icon matSuffix>search</mat-icon>
</button>
</mat-card>

View File

@ -0,0 +1,4 @@
.search-container {
display: flex;
flex-direction: column;
}

View File

@ -0,0 +1,25 @@
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
import {SearchInputComponent} from './search-input.component';
describe('SearchInputComponent', () => {
let component: SearchInputComponent;
let fixture: ComponentFixture<SearchInputComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [SearchInputComponent]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(SearchInputComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,28 @@
import {Component, OnInit} from '@angular/core';
import {Router} from '@angular/router';
import {Query} from '../../interfaces/search-request.interface';
import {objToBase64} from '../../utils/base64conversion';
@Component({
selector: 'app-search-input',
templateUrl: './search-input.component.html',
styleUrls: ['./search-input.component.scss']
})
export class SearchInputComponent implements OnInit {
constructor(private router: Router) {
}
ngOnInit() {
}
async onSearch() {
const query: Query = {
from: Date.now(),
to: Date.now(),
};
await this.router.navigate(['/search'], {queryParams: {q: objToBase64(query)}});
}
}

View File

@ -1,28 +1 @@
<mat-card class="search-container"> <app-search-input></app-search-input>
<mat-form-field appearance="outline">
<mat-label>Search</mat-label>
<input #textSearch matInput>
<button *ngIf="textSearch.value.length>0" mat-icon-button matSuffix (click)="textSearch.value=''">
<mat-icon>clear</mat-icon>
</button>
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>Start</mat-label>
<input matInput type="date">
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>End</mat-label>
<input matInput type="date">
</mat-form-field>
<mat-form-field>
<mat-label>Price</mat-label>
<mat-select value="Budget">
<mat-option value="Budget">Budget</mat-option>
<mat-option value="Mid-Range">Mid-Range</mat-option>
<mat-option value="Luxury">Luxury</mat-option>
</mat-select>
</mat-form-field>
<button mat-flat-button color="primary">Search
<mat-icon matSuffix>search</mat-icon>
</button>
</mat-card>

View File

@ -2,8 +2,3 @@
margin: 0; margin: 0;
box-sizing: border-box; box-sizing: border-box;
} }
.search-container {
display: flex;
flex-direction: column;
}

View File

@ -0,0 +1,12 @@
<app-search-input></app-search-input>
<mat-card *ngIf="results">
<h2 matCardTitle>Suchergebnisse ({{results.length}}):</h2>
<ul>
<li *ngFor="let result of results">{{result.name}} ({{result.score}})</li>
</ul>
</mat-card>
<div *ngIf="!results" class="spinner">
<mat-spinner></mat-spinner>
</div>

View File

@ -0,0 +1,17 @@
:host {
display: flex;
flex-direction: column;
height: 100%;
> * {
margin-bottom: 1rem;
}
}
.spinner {
flex: 1 1 auto;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
}

View File

@ -0,0 +1,25 @@
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
import {SearchComponent} from './search.component';
describe('SearchComponent', () => {
let component: SearchComponent;
let fixture: ComponentFixture<SearchComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [SearchComponent]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(SearchComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,30 @@
import {Component, OnInit} from '@angular/core';
import {ActivatedRoute} from '@angular/router';
import {Result} from '../../interfaces/result.interface';
import {MOCK_RESULT} from '../../mock/mock-data';
@Component({
selector: 'app-search',
templateUrl: './search.component.html',
styleUrls: ['./search.component.scss']
})
export class SearchComponent implements OnInit {
queryString: string;
results: Result[];
constructor(private route: ActivatedRoute) {
}
ngOnInit() {
this.route.queryParams.subscribe(params => {
this.queryString = params.q;
});
// Mock results
setTimeout(() => {
this.results = MOCK_RESULT;
}, 1000);
}
}

View File

@ -1,15 +1,14 @@
import {SearchParam} from './search-request.interface';
/** Represents the structure of one search result. */ /** Represents the structure of one search result. */
export interface Result { export interface Result {
region_id: string; region_id: number;
region_name: string; name: string;
country_id: number;
score: number; score: number;
scores: Score[]; scores: Score[];
} }
export interface Score { export interface Score {
type: SearchParam; type: string;
value: number; value: number;
score: number; score: number;
} }

View File

@ -10,7 +10,7 @@ export enum SearchParam {
FOOD = 'food' FOOD = 'food'
} }
export interface SearchParams { export interface Query {
from: number; from: number;
to: number; to: number;
price?: number[]; price?: number[];

View File

@ -0,0 +1,242 @@
import {Result} from '../interfaces/result.interface';
export const MOCK_RESULT: Result[] = [
{
region_id: 24,
country_id: 20,
name: 'Kuala Lumpur',
scores: [
{
type: 'temperature_mean',
value: 28.1,
score: 10
},
{
type: 'raindays',
value: 13.8,
score: 7.5
}
],
score: 8.75
},
{
region_id: 10,
country_id: 7,
name: 'Shanghai',
scores: [
{
type: 'temperature_mean',
value: 24.8,
score: 6.3
},
{
type: 'raindays',
value: 8.6,
score: 9.4
},
{
type: 'sunhours',
value: 151.2,
score: 2.2
}
],
score: 5.97
},
{
region_id: 3,
country_id: 2,
name: 'Sydney',
scores: [
{
type: 'temperature_mean',
value: 16.8,
score: 0
},
{
type: 'raindays',
value: 6.4,
score: 8.2
},
{
type: 'sunhours',
value: 245.8,
score: 9.5
}
],
score: 5.9
},
{
region_id: 9,
country_id: 7,
name: 'Peking',
scores: [
{
type: 'temperature_mean',
value: 21.1,
score: 4.2
},
{
type: 'raindays',
value: 5.4,
score: 7.1
},
{
type: 'sunhours',
value: 199,
score: 6
}
],
score: 5.77
},
{
region_id: 7,
country_id: 5,
name: 'Toronto',
scores: [
{
type: 'temperature_mean',
value: 16.8,
score: 1.2
},
{
type: 'raindays',
value: 8.4,
score: 9.3
},
{
type: 'sunhours',
value: 206.4,
score: 6.2
}
],
score: 5.57
},
{
region_id: 2,
country_id: 2,
name: 'Melbourne',
scores: [
{
type: 'temperature_mean',
value: 12.3,
score: 0
},
{
type: 'raindays',
value: 8.3,
score: 10
},
{
type: 'sunhours',
value: 187.4,
score: 5.1
}
],
score: 5.03
},
{
region_id: 12,
country_id: 9,
name: 'Kairo',
scores: [
{
type: 'temperature_mean',
value: 27.9,
score: 8.9
},
{
type: 'raindays',
value: 0,
score: 1.1
}
],
score: 5
},
{
region_id: 13,
country_id: 10,
name: 'London',
scores: [
{
type: 'temperature_mean',
value: 16,
score: 0
},
{
type: 'raindays',
value: 8.6,
score: 9.6
},
{
type: 'sunhours',
value: 142.4,
score: 1.9
}
],
score: 3.83
},
{
region_id: 6,
country_id: 4,
name: 'Sao Paolo',
scores: [
{
type: 'temperature_mean',
value: 19.6,
score: 0.8
},
{
type: 'raindays',
value: 4.6,
score: 6
}
],
score: 3.4
},
{
region_id: 11,
country_id: 8,
name: 'Bogota',
scores: [
{
type: 'temperature_mean',
value: 13.7,
score: 0
},
{
type: 'raindays',
value: 9.1,
score: 9.8
},
{
type: 'sunhours',
value: 121.2,
score: 0.1
}
],
score: 3.3
},
{
region_id: 18,
country_id: 15,
name: 'Reykjavik',
scores: [
{
type: 'temperature_mean',
value: 8.6,
score: 0
},
{
type: 'raindays',
value: 13.5,
score: 7.4
},
{
type: 'sunhours',
value: 135.1,
score: 1.7
}
],
score: 3.03
}
];

View File

@ -2,7 +2,6 @@ import {Injectable} from '@angular/core';
import {HttpClient, HttpParams} from '@angular/common/http'; import {HttpClient, HttpParams} from '@angular/common/http';
import {Preset} from '../interfaces/preset.interface'; import {Preset} from '../interfaces/preset.interface';
import {Result} from '../interfaces/result.interface'; import {Result} from '../interfaces/result.interface';
import {SearchParams} from '../interfaces/search-request.interface';
import {Region} from '../interfaces/region.interface'; import {Region} from '../interfaces/region.interface';
@Injectable({ @Injectable({
@ -15,9 +14,9 @@ export class DataService {
constructor(private http: HttpClient) { constructor(private http: HttpClient) {
} }
public searchRegions(searchParams: SearchParams): Promise<Result[]> { public searchRegions(query: string): Promise<Result[]> {
const params = new HttpParams(); const params = new HttpParams();
params.append('q', btoa(JSON.stringify(searchParams))); params.append('q', query);
return this.http.get<Result[]>(this.API_URL + 'search', {params}).toPromise(); return this.http.get<Result[]>(this.API_URL + 'search', {params}).toPromise();
} }

View File

@ -0,0 +1,15 @@
/**
* Encodes an object as base64 string.
* @param obj The object to encode
*/
export function objToBase64(obj: object): string {
return btoa(JSON.stringify(obj));
}
/**
* Decodes a base64 encoded object.
* @param base64 Encoded object
*/
export function base64ToObj(base64: string): object {
return JSON.parse(atob(base64));
}