Add basic result page
This commit is contained in:
parent
a9eab54674
commit
56818c4bff
@ -1,11 +1,13 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { Routes, RouterModule } from '@angular/router';
|
||||
import {NgModule} from '@angular/core';
|
||||
import {RouterModule, Routes} from '@angular/router';
|
||||
import {HomeComponent} from './containers/home/home.component';
|
||||
import {NotfoundComponent} from './containers/notfound/notfound.component';
|
||||
import {SearchComponent} from './containers/search/search.component';
|
||||
|
||||
|
||||
const routes: Routes = [
|
||||
{path: 'home', component: HomeComponent},
|
||||
{path: 'search', component: SearchComponent},
|
||||
{path: '', redirectTo: 'home', pathMatch: 'full'},
|
||||
{path: '**', component: NotfoundComponent}
|
||||
];
|
||||
|
||||
@ -1,25 +1,30 @@
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { NgModule } from '@angular/core';
|
||||
import {BrowserModule} from '@angular/platform-browser';
|
||||
import {NgModule} from '@angular/core';
|
||||
|
||||
import { AppRoutingModule } from './app-routing.module';
|
||||
import { AppComponent } from './app.component';
|
||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import {AppRoutingModule} from './app-routing.module';
|
||||
import {AppComponent} from './app.component';
|
||||
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
|
||||
import {MatToolbarModule} from '@angular/material/toolbar';
|
||||
import {MatIconModule} from '@angular/material/icon';
|
||||
import {MatButtonModule} from '@angular/material/button';
|
||||
import {MatSidenavModule} from '@angular/material/sidenav';
|
||||
import { HomeComponent } from './containers/home/home.component';
|
||||
import {HomeComponent} from './containers/home/home.component';
|
||||
import {MatCardModule} from '@angular/material/card';
|
||||
import {MatFormFieldModule} from '@angular/material/form-field';
|
||||
import {MatInputModule} from '@angular/material/input';
|
||||
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({
|
||||
declarations: [
|
||||
AppComponent,
|
||||
HomeComponent,
|
||||
NotfoundComponent
|
||||
NotfoundComponent,
|
||||
SearchComponent,
|
||||
SearchInputComponent
|
||||
],
|
||||
imports: [
|
||||
BrowserModule,
|
||||
@ -32,7 +37,8 @@ import { NotfoundComponent } from './containers/notfound/notfound.component';
|
||||
MatCardModule,
|
||||
MatFormFieldModule,
|
||||
MatInputModule,
|
||||
MatSelectModule
|
||||
MatSelectModule,
|
||||
MatProgressSpinnerModule
|
||||
],
|
||||
providers: [],
|
||||
bootstrap: [AppComponent]
|
||||
|
||||
@ -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>
|
||||
@ -0,0 +1,4 @@
|
||||
.search-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
@ -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();
|
||||
});
|
||||
});
|
||||
@ -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)}});
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,28 +1 @@
|
||||
<mat-card class="search-container">
|
||||
<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>
|
||||
<app-search-input></app-search-input>
|
||||
|
||||
@ -2,8 +2,3 @@
|
||||
margin: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.search-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
12
frontend/src/app/containers/search/search.component.html
Normal file
12
frontend/src/app/containers/search/search.component.html
Normal 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>
|
||||
17
frontend/src/app/containers/search/search.component.scss
Normal file
17
frontend/src/app/containers/search/search.component.scss
Normal 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;
|
||||
}
|
||||
25
frontend/src/app/containers/search/search.component.spec.ts
Normal file
25
frontend/src/app/containers/search/search.component.spec.ts
Normal 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();
|
||||
});
|
||||
});
|
||||
30
frontend/src/app/containers/search/search.component.ts
Normal file
30
frontend/src/app/containers/search/search.component.ts
Normal 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);
|
||||
|
||||
}
|
||||
}
|
||||
@ -1,15 +1,14 @@
|
||||
import {SearchParam} from './search-request.interface';
|
||||
|
||||
/** Represents the structure of one search result. */
|
||||
export interface Result {
|
||||
region_id: string;
|
||||
region_name: string;
|
||||
region_id: number;
|
||||
name: string;
|
||||
country_id: number;
|
||||
score: number;
|
||||
scores: Score[];
|
||||
}
|
||||
|
||||
export interface Score {
|
||||
type: SearchParam;
|
||||
type: string;
|
||||
value: number;
|
||||
score: number;
|
||||
}
|
||||
|
||||
@ -10,7 +10,7 @@ export enum SearchParam {
|
||||
FOOD = 'food'
|
||||
}
|
||||
|
||||
export interface SearchParams {
|
||||
export interface Query {
|
||||
from: number;
|
||||
to: number;
|
||||
price?: number[];
|
||||
|
||||
242
frontend/src/app/mock/mock-data.ts
Normal file
242
frontend/src/app/mock/mock-data.ts
Normal 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
|
||||
}
|
||||
];
|
||||
@ -2,7 +2,6 @@ import {Injectable} from '@angular/core';
|
||||
import {HttpClient, HttpParams} from '@angular/common/http';
|
||||
import {Preset} from '../interfaces/preset.interface';
|
||||
import {Result} from '../interfaces/result.interface';
|
||||
import {SearchParams} from '../interfaces/search-request.interface';
|
||||
import {Region} from '../interfaces/region.interface';
|
||||
|
||||
@Injectable({
|
||||
@ -15,9 +14,9 @@ export class DataService {
|
||||
constructor(private http: HttpClient) {
|
||||
}
|
||||
|
||||
public searchRegions(searchParams: SearchParams): Promise<Result[]> {
|
||||
public searchRegions(query: string): Promise<Result[]> {
|
||||
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();
|
||||
}
|
||||
|
||||
15
frontend/src/app/utils/base64conversion.ts
Normal file
15
frontend/src/app/utils/base64conversion.ts
Normal 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));
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user