diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 1446cf0..ee849bd 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -5338,6 +5338,11 @@ "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==", "dev": true }, + "hammerjs": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/hammerjs/-/hammerjs-2.0.8.tgz", + "integrity": "sha1-BO93hiz/K7edMPdpIJWTAiK/YPE=" + }, "handle-thing": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", diff --git a/frontend/package.json b/frontend/package.json index 99df01f..cfe4a1e 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -22,6 +22,7 @@ "@angular/platform-browser-dynamic": "~8.2.14", "@angular/router": "~8.2.14", "@ngx-translate/core": "^12.1.2", + "hammerjs": "^2.0.8", "ngx-device-detector": "^1.4.5", "rxjs": "~6.4.0", "tslib": "^1.10.0", diff --git a/frontend/src/app/app.module.ts b/frontend/src/app/app.module.ts index da36f83..f4069d1 100644 --- a/frontend/src/app/app.module.ts +++ b/frontend/src/app/app.module.ts @@ -22,10 +22,15 @@ import {TranslateModule, TranslateService} from '@ngx-translate/core'; import * as enLang from '../assets/i18n/en.json'; import {HttpClientModule} from '@angular/common/http'; import { + MatBadgeModule, MatButtonToggleModule, MatCheckboxModule, MatDialogModule, MatDividerModule, + MatRadioModule, + MatSliderModule, + MatSlideToggleModule, + MatStepperModule, MatTabsModule, MatTooltipModule } from '@angular/material'; @@ -41,6 +46,7 @@ import {ShareButtonComponent} from './components/share-button/share-button.compo import {ShareDialogComponent} from './dialogs/share-dialog/share-dialog.component'; import {TeamComponent} from './containers/team/team.component'; import {DeviceDetectorModule} from 'ngx-device-detector'; +import {ToggleSliderComponent} from './components/toggle-slider/toggle-slider.component'; @NgModule({ @@ -59,7 +65,8 @@ import {DeviceDetectorModule} from 'ngx-device-detector'; BookmarkListComponent, ShareButtonComponent, ShareDialogComponent, - TeamComponent + TeamComponent, + ToggleSliderComponent ], imports: [ BrowserModule, @@ -83,7 +90,12 @@ import {DeviceDetectorModule} from 'ngx-device-detector'; MatTooltipModule, MatDialogModule, DeviceDetectorModule, - MatTabsModule + MatTabsModule, + MatBadgeModule, + MatStepperModule, + MatRadioModule, + MatSlideToggleModule, + MatSliderModule ], providers: [], bootstrap: [AppComponent], diff --git a/frontend/src/app/components/result/result.component.html b/frontend/src/app/components/result/result.component.html index fef4628..775f78b 100644 --- a/frontend/src/app/components/result/result.component.html +++ b/frontend/src/app/components/result/result.component.html @@ -14,7 +14,7 @@
- {{PROPERTY_VIS_DEF[score.type].icon}} + {{PROPERTY_VIS_DEF[score.type] ? PROPERTY_VIS_DEF[score.type].icon : 'bar_chart'}} {{score.type|translate}}:
@@ -25,7 +25,12 @@
- {{PROPERTY_VIS_DEF[score.type].unit}} + {{PROPERTY_VIS_DEF[score.type] ? PROPERTY_VIS_DEF[score.type].unit : ''}} +
+ + +
+ ({{score.score}})
diff --git a/frontend/src/app/components/search-input/search-input.component.html b/frontend/src/app/components/search-input/search-input.component.html index b596e30..ba983a0 100644 --- a/frontend/src/app/components/search-input/search-input.component.html +++ b/frontend/src/app/components/search-input/search-input.component.html @@ -1,42 +1,106 @@ -
-

When is your trip?

- - Start - - - - End - - -
-
-

What would you prefer?

-
- {{key|translate}}:
- - - {{preset.tag_label|translate}} - - -
-
+ Search -
-

Whats most important to you?

-
- {{preset.tag_label|translate}} -
-
+ - + +
+ Search in description + +
+ + + +
+ Climate + + +
+ +
+ Fincancial + +
+ + + +
+ + +
diff --git a/frontend/src/app/components/search-input/search-input.component.scss b/frontend/src/app/components/search-input/search-input.component.scss index af4bcc8..817a977 100644 --- a/frontend/src/app/components/search-input/search-input.component.scss +++ b/frontend/src/app/components/search-input/search-input.component.scss @@ -2,27 +2,72 @@ display: flex; flex-direction: column; - > .group { - flex: 0 0 auto; - display: flex; - flex-direction: column; - box-sizing: border-box; - } - > .search-btn { margin-top: 1rem; } } .sub-group { - margin-bottom: 0.5rem; + margin-bottom: 2rem; + display: flex; + flex-direction: column; - > .label { + .label { font-weight: bold; } + + mat-radio-group { + display: flex; + flex-direction: column; + } + + mat-radio-button { + margin: 0 1rem 0.5rem 0; + width: 40%; + } +} + +.group { + display: flex; + flex-direction: column; + margin: 1rem 0; + + > .title { + font-size: 1.2rem; + font-weight: bold; + margin-bottom: 0.5rem; + } + + > .content { + margin: 0 2.5rem; + + .text-input { + min-width: 14rem; + } + } +} + +.horizontal { + display: flex; + flex-direction: row; + align-items: center; + + &.space { + > * { + margin-right: 1rem; + } + } } .vertical { display: flex; flex-direction: column; } + +.vertical-wrap { + display: flex; + flex-direction: row; + flex-wrap: wrap; + justify-content: space-around; +} + diff --git a/frontend/src/app/components/search-input/search-input.component.ts b/frontend/src/app/components/search-input/search-input.component.ts index 12a1202..e27d705 100644 --- a/frontend/src/app/components/search-input/search-input.component.ts +++ b/frontend/src/app/components/search-input/search-input.component.ts @@ -14,6 +14,9 @@ import {SearchService} from '../../services/search.service'; }) export class SearchInputComponent implements OnInit { + + selectedTab = 0; + presets: Preset[]; singlePresets: Preset[]; multiPresets: Map; @@ -21,9 +24,18 @@ export class SearchInputComponent implements OnInit { from: string; to: string; + + // Guided Search singlePresetSelection = {}; multiPresetSelection = {}; + // Advanced Search + textFilter = ''; + fullText = false; + temperatureMeanMax: number; + precipitation: number; + accommodation: number; + readonly today = this.from = formatDate(new Date(), 'yyyy-MM-dd', 'en-GB'); constructor(private router: Router, private ps: PresetService, private ss: SearchService) { @@ -43,41 +55,13 @@ export class SearchInputComponent implements OnInit { this.multiPresets = this.ps.multiPresets; this.multiPresetsKeys = [...this.multiPresets.keys()]; - const prevInput = this.ss.loadSearchInput(); - - if (prevInput) { - this.from = prevInput.from; - this.to = prevInput.to; - this.singlePresetSelection = prevInput.singlePresetSelection; - this.multiPresetSelection = prevInput.multiPresetSelection; - } + this.loadSearch(); } - async onSearch() { - const query: Query = { - from: new Date(this.from).getTime(), - to: new Date(this.to).getTime(), - }; - - for (const preset of this.singlePresets) { - if (this.singlePresetSelection[preset.preset_id]) { - query[preset.parameter] = preset.value; - } - } - - for (const key of this.multiPresetsKeys) { - if (this.multiPresetSelection[key]) { - query[key] = this.presets.find(preset => preset.preset_id === this.multiPresetSelection[key]).value; - } - } - - this.ss.saveSearchInput({ - from: this.from, - to: this.to, - singlePresetSelection: this.singlePresetSelection, - multiPresetSelection: this.multiPresetSelection - }); + async onSearch(isAdvanced: boolean) { + this.saveSearch(isAdvanced); + const query = isAdvanced ? this.getQueryFromAdvanced() : this.getQueryFromGuided(); await this.router.navigate(['/search'], {queryParams: {q: objToBase64(query)}}); } @@ -107,4 +91,76 @@ export class SearchInputComponent implements OnInit { this.to = formatDate(newToDate, 'yyyy-MM-dd', 'en-GB'); } } + + private getQueryFromGuided(): Query { + const query: Query = { + from: new Date(this.from).getTime(), + to: new Date(this.to).getTime(), + }; + + for (const preset of this.singlePresets) { + if (this.singlePresetSelection[preset.preset_id]) { + query[preset.parameter] = preset.value; + } + } + + for (const key of this.multiPresetsKeys) { + if (this.multiPresetSelection[key]) { + query[key] = this.presets.find(preset => preset.preset_id === this.multiPresetSelection[key]).value; + } + } + + return query; + } + + private getQueryFromAdvanced(): Query { + const query: Query = { + from: new Date(this.from).getTime(), + to: new Date(this.to).getTime(), + }; + + if (this.textFilter.length > 0) { + query.fulltext = this.fullText; + query.textfilter = this.textFilter; + } + + query.temperature_mean_max = this.temperatureMeanMax ? [this.temperatureMeanMax, this.temperatureMeanMax] : undefined; + query.precipitation = this.precipitation ? [this.precipitation, this.precipitation] : undefined; + query.accommodation_costs = this.accommodation ? [this.accommodation, this.accommodation] : undefined; + + return query; + } + + private saveSearch(isAdvanced: boolean) { + this.ss.saveSearchInput({ + wasAdvanced: isAdvanced, + from: this.from, + to: this.to, + singlePresetSelection: this.singlePresetSelection, + multiPresetSelection: this.multiPresetSelection, + fullText: this.fullText, + textFiler: this.textFilter, + tempMeanMax: this.temperatureMeanMax, + precipitation: this.precipitation, + accommodation: this.accommodation, + }); + } + + private loadSearch() { + const prevInput = this.ss.loadSearchInput(); + + if (prevInput) { + this.from = prevInput.from; + this.to = prevInput.to; + this.singlePresetSelection = prevInput.singlePresetSelection; + this.multiPresetSelection = prevInput.multiPresetSelection; + this.textFilter = prevInput.textFiler; + this.fullText = prevInput.fullText; + this.selectedTab = prevInput.wasAdvanced ? 1 : 0; + this.temperatureMeanMax = prevInput.tempMeanMax; + this.precipitation = prevInput.precipitation; + this.accommodation = prevInput.accommodation; + } + } + } diff --git a/frontend/src/app/components/toggle-slider/toggle-slider.component.html b/frontend/src/app/components/toggle-slider/toggle-slider.component.html new file mode 100644 index 0000000..20527b8 --- /dev/null +++ b/frontend/src/app/components/toggle-slider/toggle-slider.component.html @@ -0,0 +1,11 @@ +{{label}} + + diff --git a/frontend/src/app/components/toggle-slider/toggle-slider.component.scss b/frontend/src/app/components/toggle-slider/toggle-slider.component.scss new file mode 100644 index 0000000..839782e --- /dev/null +++ b/frontend/src/app/components/toggle-slider/toggle-slider.component.scss @@ -0,0 +1,24 @@ +:host { + display: flex; + flex-direction: row; + align-items: center; +} + +span { + width: 33%; + min-width: 8rem; + margin-right: 0.5rem; + overflow-x: hidden; + text-overflow: ellipsis; + white-space: nowrap; + +} + +mat-slide-toggle { + flex: 0 1 auto; + margin-right: 0.5rem; +} + +mat-slider { + flex: 1 1 auto; +} diff --git a/frontend/src/app/components/toggle-slider/toggle-slider.component.spec.ts b/frontend/src/app/components/toggle-slider/toggle-slider.component.spec.ts new file mode 100644 index 0000000..085e7f7 --- /dev/null +++ b/frontend/src/app/components/toggle-slider/toggle-slider.component.spec.ts @@ -0,0 +1,25 @@ +import {async, ComponentFixture, TestBed} from '@angular/core/testing'; + +import {ToggleSliderComponent} from './toggle-slider.component'; + +describe('ToggleSliderComponent', () => { + let component: ToggleSliderComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ToggleSliderComponent] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ToggleSliderComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/frontend/src/app/components/toggle-slider/toggle-slider.component.ts b/frontend/src/app/components/toggle-slider/toggle-slider.component.ts new file mode 100644 index 0000000..5a86204 --- /dev/null +++ b/frontend/src/app/components/toggle-slider/toggle-slider.component.ts @@ -0,0 +1,42 @@ +import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core'; +import {MatSlideToggleChange} from '@angular/material'; + +@Component({ + selector: 'app-toggle-slider', + templateUrl: './toggle-slider.component.html', + styleUrls: ['./toggle-slider.component.scss'] +}) +export class ToggleSliderComponent implements OnInit { + + enabled = false; + rawValue: number; + @Output() modelChange: EventEmitter = new EventEmitter(); + @Input() min = 0; + @Input() max = 100; + @Input() step = 1; + @Input() label: string; + + get value(): number { + return this.rawValue; + } + + set value(value: number) { + this.rawValue = value; + this.modelChange.emit(value); + } + + @Input() + set model(value: number) { + this.rawValue = value; + this.enabled = value !== undefined; + } + + ngOnInit() { + } + + onSlideToggleChange(event: MatSlideToggleChange) { + if (event.checked === false) { + this.modelChange.emit(undefined); + } + } +} diff --git a/frontend/src/app/containers/search/search.component.html b/frontend/src/app/containers/search/search.component.html index 90af0e8..f1c3247 100644 --- a/frontend/src/app/containers/search/search.component.html +++ b/frontend/src/app/containers/search/search.component.html @@ -9,6 +9,10 @@ +
+ error + No match found! +
diff --git a/frontend/src/app/containers/search/search.component.scss b/frontend/src/app/containers/search/search.component.scss index 86a7e39..097ba9d 100644 --- a/frontend/src/app/containers/search/search.component.scss +++ b/frontend/src/app/containers/search/search.component.scss @@ -15,6 +15,21 @@ > app-result { margin-bottom: 3rem; } + +} + +.note { + flex: 1 1 auto; + align-self: center; + font-size: 1.2rem; + margin: 1.5rem; + display: flex; + flex-direction: row; + align-items: center; + + mat-icon { + margin-right: 0.5rem; + } } .spinner { diff --git a/frontend/src/app/services/search.service.ts b/frontend/src/app/services/search.service.ts index 72bba81..a4d0f6f 100644 --- a/frontend/src/app/services/search.service.ts +++ b/frontend/src/app/services/search.service.ts @@ -3,10 +3,16 @@ import {Result} from '../interfaces/result.interface'; import {DataService} from './data.service'; export interface SearchInput { + wasAdvanced: boolean; from: string; to: string; singlePresetSelection: object; multiPresetSelection: object; + textFiler: string; + fullText: boolean; + tempMeanMax: number; + precipitation: number; + accommodation: number; } @Injectable({ diff --git a/frontend/src/main.ts b/frontend/src/main.ts index c7b673c..5e54bee 100644 --- a/frontend/src/main.ts +++ b/frontend/src/main.ts @@ -1,8 +1,10 @@ -import { enableProdMode } from '@angular/core'; -import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; +import {enableProdMode} from '@angular/core'; +import {platformBrowserDynamic} from '@angular/platform-browser-dynamic'; -import { AppModule } from './app/app.module'; -import { environment } from './environments/environment'; +import {AppModule} from './app/app.module'; +import {environment} from './environments/environment'; + +import 'hammerjs'; if (environment.production) { enableProdMode(); diff --git a/frontend/src/theme.scss b/frontend/src/theme.scss index 0856b36..e728b8e 100644 --- a/frontend/src/theme.scss +++ b/frontend/src/theme.scss @@ -55,20 +55,20 @@ $travopti-green: ( A400: #006400, A700: #004d00, contrast: ( - 50: $dark-primary-text, - 100: $dark-primary-text, - 200: $dark-primary-text, - 300: $dark-primary-text, - 400: $dark-primary-text, - 500: $dark-primary-text, + 50: $light-primary-text, + 100: $light-primary-text, + 200: $light-primary-text, + 300: $light-primary-text, + 400: $light-primary-text, + 500: $light-primary-text, 600: $light-primary-text, 700: $light-primary-text, 800: $light-primary-text, 900: $light-primary-text, - A100: $dark-primary-text, - A200: $dark-primary-text, - A400: $dark-primary-text, - A700: $dark-primary-text, + A100: $light-primary-text, + A200: $light-primary-text, + A400: $light-primary-text, + A700: $light-primary-text, ) );