From 66a1ac15326fc42cab1a74c682825a66339cf674 Mon Sep 17 00:00:00 2001 From: Maximilian Krambach Date: Mon, 28 Jan 2019 19:23:09 +0100 Subject: [PATCH] estimated duration, and datetimepicker in config --- client/package.json | 1 + .../agenda/agenda-repository.service.ts | 27 ++++++++ .../os-sort-filter-bar.component.html | 1 + .../os-sort-filter-bar.component.scss | 5 +- .../os-sort-filter-bar.component.ts | 6 ++ client/src/app/shared/models/agenda/item.ts | 2 +- client/src/app/shared/shared.module.ts | 7 +- .../agenda-list/agenda-list.component.html | 9 ++- .../agenda-list/agenda-list.component.ts | 22 ++++++- .../config-field/config-field.component.html | 28 +++++--- .../config-field/config-field.component.ts | 66 ++++++++++++++----- client/src/styles.scss | 3 + 12 files changed, 146 insertions(+), 31 deletions(-) diff --git a/client/package.json b/client/package.json index 0f0ecd700..985fcf2a5 100644 --- a/client/package.json +++ b/client/package.json @@ -46,6 +46,7 @@ "css-element-queries": "^1.1.1", "file-saver": "^2.0.0", "material-design-icons": "^3.0.1", + "ng-pick-datetime": "^7.0.0", "ngx-file-drop": "^5.0.2", "ngx-mat-select-search": "^1.5.2", "ngx-papaparse": "^3.0.2", diff --git a/client/src/app/core/repositories/agenda/agenda-repository.service.ts b/client/src/app/core/repositories/agenda/agenda-repository.service.ts index 126dadc2d..d448c6f15 100644 --- a/client/src/app/core/repositories/agenda/agenda-repository.service.ts +++ b/client/src/app/core/repositories/agenda/agenda-repository.service.ts @@ -266,4 +266,31 @@ export class AgendaRepositoryService extends BaseRepository { }) ); } + + /** + * Calculates the estimated end time based on the configured start and the + * sum of durations of all agenda items + * + * @returns a Date object + */ + public calculateEndTime(): Date { + const startTime = this.config.instant('agenda_start_event_date_time'); // a timestamp + const durationTime = this.calculateDuration() * 60 * 1000; // minutes to miliseconds + return new Date(startTime + durationTime); + } + + /** + * get the sum of durations of all agenda items + * + * @returns a numerical value representing item durations (currently minutes) + */ + public calculateDuration(): number { + let duration = 0; + this.getViewModelList().forEach(item => { + if (item.duration) { + duration += item.duration; + } + }); + return duration; + } } diff --git a/client/src/app/shared/components/os-sort-filter-bar/os-sort-filter-bar.component.html b/client/src/app/shared/components/os-sort-filter-bar/os-sort-filter-bar.component.html index 79e01a8f8..6d9a26226 100644 --- a/client/src/app/shared/components/os-sort-filter-bar/os-sort-filter-bar.component.html +++ b/client/src/app/shared/components/os-sort-filter-bar/os-sort-filter-bar.component.html @@ -2,6 +2,7 @@
{{ displayedCount }} of  {{ filterService.totalCount }} +  ยท {{ extraItemInfo }}
Active filters
diff --git a/client/src/app/shared/components/os-sort-filter-bar/os-sort-filter-bar.component.scss b/client/src/app/shared/components/os-sort-filter-bar/os-sort-filter-bar.component.scss index efe2b5aa0..58492d1cb 100644 --- a/client/src/app/shared/components/os-sort-filter-bar/os-sort-filter-bar.component.scss +++ b/client/src/app/shared/components/os-sort-filter-bar/os-sort-filter-bar.component.scss @@ -15,10 +15,13 @@ span.right-with-margin { } .filter-count { - text-align: right; + font-style: italic; margin-right: 10px; margin-left: 10px; min-width: 50px; + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; } .current-filters { diff --git a/client/src/app/shared/components/os-sort-filter-bar/os-sort-filter-bar.component.ts b/client/src/app/shared/components/os-sort-filter-bar/os-sort-filter-bar.component.ts index 9f9be61a6..7436ca98a 100644 --- a/client/src/app/shared/components/os-sort-filter-bar/os-sort-filter-bar.component.ts +++ b/client/src/app/shared/components/os-sort-filter-bar/os-sort-filter-bar.component.ts @@ -49,6 +49,12 @@ export class OsSortFilterBarComponent { @Input() public filterService: any; // TODO a FilterListService extending FilterListService + /** + * optional additional string to show after the item count. This string will not be translated here + */ + @Input() + public extraItemInfo: string; + @Output() public searchFieldChange = new EventEmitter(); /** diff --git a/client/src/app/shared/models/agenda/item.ts b/client/src/app/shared/models/agenda/item.ts index be62b9cf1..fe918a589 100644 --- a/client/src/app/shared/models/agenda/item.ts +++ b/client/src/app/shared/models/agenda/item.ts @@ -33,7 +33,7 @@ export class Item extends BaseModel { public closed: boolean; public type: number; public is_hidden: boolean; - public duration: number; + public duration: number; // minutes public speakers: Speaker[]; public speaker_list_closed: boolean; public content_object: ContentObject; diff --git a/client/src/app/shared/shared.module.ts b/client/src/app/shared/shared.module.ts index 77e1d9b60..c72e6f4a2 100644 --- a/client/src/app/shared/shared.module.ts +++ b/client/src/app/shared/shared.module.ts @@ -2,6 +2,7 @@ import { NgModule } from '@angular/core'; import { RouterModule } from '@angular/router'; import { CommonModule } from '@angular/common'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { OwlDateTimeModule, OwlNativeDateTimeModule } from 'ng-pick-datetime'; // MaterialUI modules import { @@ -130,6 +131,8 @@ import { SlideContainerComponent } from './components/slide-container/slide-cont MatStepperModule, MatTabsModule, MatSliderModule, + OwlDateTimeModule, + OwlNativeDateTimeModule, DragDropModule, OpenSlidesTranslateModule.forChild(), RouterModule, @@ -196,7 +199,9 @@ import { SlideContainerComponent } from './components/slide-container/slide-cont ResizedDirective, MetaTextBlockComponent, ProjectorComponent, - SlideContainerComponent + SlideContainerComponent, + OwlDateTimeModule, + OwlNativeDateTimeModule ], declarations: [ PermsDirective, diff --git a/client/src/app/site/agenda/components/agenda-list/agenda-list.component.html b/client/src/app/site/agenda/components/agenda-list/agenda-list.component.html index 88db7addc..9986e60f7 100644 --- a/client/src/app/site/agenda/components/agenda-list/agenda-list.component.html +++ b/client/src/app/site/agenda/components/agenda-list/agenda-list.component.html @@ -13,7 +13,12 @@
- + @@ -28,7 +33,7 @@ Topic -
+
check {{ item.getListTitle() }}
diff --git a/client/src/app/site/agenda/components/agenda-list/agenda-list.component.ts b/client/src/app/site/agenda/components/agenda-list/agenda-list.component.ts index 0ee6a9d8c..d533dbb04 100644 --- a/client/src/app/site/agenda/components/agenda-list/agenda-list.component.ts +++ b/client/src/app/site/agenda/components/agenda-list/agenda-list.component.ts @@ -1,7 +1,7 @@ import { Component, OnInit } from '@angular/core'; +import { MatSnackBar, MatDialog } from '@angular/material'; import { Router, ActivatedRoute } from '@angular/router'; import { Title } from '@angular/platform-browser'; -import { MatSnackBar, MatDialog } from '@angular/material'; import { TranslateService } from '@ngx-translate/core'; import { AgendaFilterListService } from '../../services/agenda-filter-list.service'; @@ -125,6 +125,8 @@ export class AgendaListComponent extends ListViewBaseComponent impleme if (result) { if (result.durationText) { result.duration = this.durationService.stringToDuration(result.durationText); + } else { + result.duration = 0; } this.repo.update(result, item); } @@ -242,4 +244,22 @@ export class AgendaListComponent extends ListViewBaseComponent impleme const filename = this.translate.instant('Agenda'); this.pdfService.download(this.agendaPdfService.agendaListToDocDef(this.dataSource.filteredData), filename); } + + /** + * Get the calculated end date and time + * + * @returns a readable string with end date and time in the current languages' convention + */ + public getDurationEndString(): string { + const duration = this.repo.calculateDuration(); + if (!duration) { + return ''; + } + const durationString = this.durationService.durationToString(duration); + const endTimeString = this.repo + .calculateEndTime() + .toLocaleTimeString(this.translate.currentLang, { hour: 'numeric', minute: 'numeric' }); + return `${this.translate.instant('Duration')}: ${durationString} (${this.translate.instant('Estimated end')}: + ${endTimeString} h)`; + } } diff --git a/client/src/app/site/config/components/config-field/config-field.component.html b/client/src/app/site/config/components/config-field/config-field.component.html index c7ad43af0..8f0c3af1a 100644 --- a/client/src/app/site/config/components/config-field/config-field.component.html +++ b/client/src/app/site/config/components/config-field/config-field.component.html @@ -4,9 +4,6 @@ - - - @@ -24,11 +21,6 @@ {{ error }} - - - - - @@ -64,6 +56,23 @@
+ +
+ + + {{ configItem.label | translate }} + {{ configItem.helpText | translate }} + + check_circle + + {{ error }} +
+

{{ configItem.label | translate }}

@@ -78,8 +87,7 @@
-
diff --git a/client/src/app/site/config/components/config-field/config-field.component.ts b/client/src/app/site/config/components/config-field/config-field.component.ts index 7877ece84..3fa45780d 100644 --- a/client/src/app/site/config/components/config-field/config-field.component.ts +++ b/client/src/app/site/config/components/config-field/config-field.component.ts @@ -1,14 +1,15 @@ import { Component, OnInit, Input, ChangeDetectorRef, ChangeDetectionStrategy } from '@angular/core'; +import { FormGroup, FormBuilder } from '@angular/forms'; import { Title } from '@angular/platform-browser'; import { distinctUntilChanged } from 'rxjs/operators'; +import { DateTimeAdapter } from 'ng-pick-datetime'; import { TranslateService } from '@ngx-translate/core'; -import { ViewConfig } from '../../models/view-config'; import { BaseComponent } from '../../../../base.component'; -import { FormGroup, FormBuilder } from '@angular/forms'; import { ConfigRepositoryService } from '../../services/config-repository.service'; import { ParentErrorStateMatcher } from 'app/shared/parent-error-state-matcher'; +import { ViewConfig } from '../../models/view-config'; /** * List view for the categories. @@ -24,6 +25,11 @@ import { ParentErrorStateMatcher } from 'app/shared/parent-error-state-matcher'; export class ConfigFieldComponent extends BaseComponent implements OnInit { public configItem: ViewConfig; + /** + * Date representation od the config value, used by the datetimepicker + */ + public dateValue: Date; + /** * Option to show a green check-icon. */ @@ -49,6 +55,8 @@ export class ConfigFieldComponent extends BaseComponent implements OnInit { */ public translatedValue: object; + public rawDate: Date; + /** * The config item for this component. Just accept components with already populated constants-info. */ @@ -79,18 +87,26 @@ export class ConfigFieldComponent extends BaseComponent implements OnInit { public matcher = new ParentErrorStateMatcher(); /** - * The usual component constructor - * @param titleService - * @param translate + * The usual component constructor. datetime pickers will set their locale + * to the current language chosen + * + * @param titleService Title + * @param translate TranslateService + * @param formBuilder FormBuilder + * @param cdRef ChangeDetectorRef + * @param repo ConfigRepositoryService + * @param dateTimeAdapter DateTimeAdapter */ public constructor( protected titleService: Title, protected translate: TranslateService, private formBuilder: FormBuilder, private cdRef: ChangeDetectorRef, - public repo: ConfigRepositoryService + public repo: ConfigRepositoryService, + dateTimeAdapter: DateTimeAdapter ) { super(titleService, translate); + dateTimeAdapter.setLocale(this.translate.currentLang); } /** @@ -100,7 +116,6 @@ export class ConfigFieldComponent extends BaseComponent implements OnInit { this.form = this.formBuilder.group({ value: [''] }); - this.translatedValue = this.configItem.value; if ( this.configItem.inputType === 'string' || @@ -111,7 +126,9 @@ export class ConfigFieldComponent extends BaseComponent implements OnInit { this.translatedValue = this.translate.instant(this.configItem.value); } } - + if (this.configItem.inputType === 'datetimepicker') { + this.dateValue = new Date(this.configItem.value as number); + } this.form.patchValue({ value: this.translatedValue }); @@ -128,6 +145,9 @@ export class ConfigFieldComponent extends BaseComponent implements OnInit { * Trigger an update of the data */ private onChange(value: any): void { + if (this.configItem.inputType === 'datetimepicker') { + this.dateValue = new Date(value as number); + } if (this.debounceTimeout !== null) { clearTimeout(this.debounceTimeout); } @@ -147,15 +167,10 @@ export class ConfigFieldComponent extends BaseComponent implements OnInit { } /** - * Updates the config field. + * Sends an update request for the config item to the server. * @param value The new value to set. */ private update(value: any): void { - // TODO: Fix the Datetimepicker parser and formatter. - if (this.configItem.inputType === 'datetimepicker') { - value = Date.parse(value); - } - this.debounceTimeout = null; this.repo.update({ value: value }, this.configItem).then(() => { this.error = null; @@ -212,7 +227,28 @@ export class ConfigFieldComponent extends BaseComponent implements OnInit { * @returns wheather it should be excluded or not */ public isExcludedType(type: string): boolean { - const excluded = ['boolean', 'markupText', 'text', 'translations']; + const excluded = ['boolean', 'markupText', 'text', 'translations', 'datetimepicker']; return excluded.includes(type); } + + /** + * custom handler for datetime picker updates. Sets the form's value + * to the timestamp of the Date being the event's value + * + * @param event an event-like object with a Date as value property + */ + public updateTime(event: { value: Date }): void { + this.dateValue = event.value; + this.onChange(event.value.valueOf()); + } + + /** + * Determines if a reset buton should be offered. + * TODO: is 'null' a valid default in some cases? + * + * @returns true if any default exists + */ + public hasDefault(): boolean { + return this.configItem.defaultValue !== undefined && this.configItem.defaultValue !== null; + } } diff --git a/client/src/styles.scss b/client/src/styles.scss index e35b5a9a3..94b93ec06 100644 --- a/client/src/styles.scss +++ b/client/src/styles.scss @@ -23,6 +23,9 @@ @import '~angular-tree-component/dist/angular-tree-component.css'; +/** date-time-picker */ +@import "~ng-pick-datetime/assets/style/picker.min.css"; + /** Define the classes to switch between themes */ .openslides-theme { @include angular-material-theme($openslides-theme);