Merge pull request #4216 from MaximilianKrambach/duration
Estimated duration, and datetimepicker in config
This commit is contained in:
commit
4395b86b9c
@ -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",
|
||||
|
@ -266,4 +266,31 @@ export class AgendaRepositoryService extends BaseRepository<ViewItem, Item> {
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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<number>('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;
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
<div class="filter-count" *ngIf="filterService">
|
||||
<span>{{ displayedCount }} </span><span translate>of</span>
|
||||
<span> {{ filterService.totalCount }}</span>
|
||||
<span *ngIf="extraItemInfo"> · {{ extraItemInfo }}</span>
|
||||
</div>
|
||||
<div class="current-filters" *ngIf="filterService && filterService.activeFilterCount">
|
||||
<div><span translate>Active filters</span>: </div>
|
||||
|
@ -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 {
|
||||
|
@ -49,6 +49,12 @@ export class OsSortFilterBarComponent<V extends BaseViewModel> {
|
||||
@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<string>();
|
||||
/**
|
||||
|
@ -33,7 +33,7 @@ export class Item extends BaseModel<Item> {
|
||||
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;
|
||||
|
@ -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,
|
||||
|
@ -13,7 +13,12 @@
|
||||
</div>
|
||||
</os-head-bar>
|
||||
<mat-drawer-container class="on-transition-fade">
|
||||
<os-sort-filter-bar [filterCount]="filteredCount" [filterService]="filterService" (searchFieldChange)="searchFilter($event)"></os-sort-filter-bar>
|
||||
<os-sort-filter-bar
|
||||
[filterCount]="filteredCount"
|
||||
[extraItemInfo]="getDurationEndString()"
|
||||
[filterService]="filterService"
|
||||
(searchFieldChange)="searchFilter($event)"
|
||||
></os-sort-filter-bar>
|
||||
<mat-table class="os-listview-table on-transition-fade" [dataSource]="dataSource" matSort>
|
||||
<!-- selector column -->
|
||||
<ng-container matColumnDef="selector">
|
||||
@ -28,7 +33,7 @@
|
||||
<mat-header-cell *matHeaderCellDef mat-sort-header>Topic</mat-header-cell>
|
||||
<!-- <mat-cell (click)="onTitleColumn(item)" *matCellDef="let item"> -->
|
||||
<mat-cell (click)="selectItem(item, $event)" *matCellDef="let item">
|
||||
<div [ngStyle]="{'margin-left': item.agendaListLevel * 25 + 'px' }">
|
||||
<div [ngStyle]="{ 'margin-left': item.agendaListLevel * 25 + 'px' }">
|
||||
<span *ngIf="item.closed"> <mat-icon class="done-check">check</mat-icon> </span>
|
||||
<span class="table-view-list-title">{{ item.getListTitle() }}</span>
|
||||
</div>
|
||||
|
@ -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<ViewItem> 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<ViewItem> 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)`;
|
||||
}
|
||||
}
|
||||
|
@ -4,9 +4,6 @@
|
||||
<mat-form-field *ngIf="!isExcludedType(configItem.inputType)">
|
||||
<!-- Decides which input-type to take (i.e) date, select, input) -->
|
||||
<ng-container [ngSwitch]="configItem.inputType">
|
||||
<ng-container *ngSwitchCase="'datetimepicker'">
|
||||
<ng-container *ngTemplateOutlet="date"></ng-container>
|
||||
</ng-container>
|
||||
<ng-container *ngSwitchCase="'choice'">
|
||||
<ng-container *ngTemplateOutlet="select"></ng-container>
|
||||
</ng-container>
|
||||
@ -24,11 +21,6 @@
|
||||
<mat-error *ngIf="error"> {{ error }} </mat-error>
|
||||
|
||||
<!-- templates for exchangeable inputs. Add more here if necessary -->
|
||||
<ng-template #date ngProjectAs="[matInput]">
|
||||
<input matInput formControlName="value" [matDatepicker]="picker" [errorStateMatcher]="matcher" />
|
||||
<mat-datepicker-toggle matPrefix [for]="picker"></mat-datepicker-toggle>
|
||||
<mat-datepicker #picker></mat-datepicker>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #select ngProjectAs="mat-select">
|
||||
<mat-select formControlName="value" [errorStateMatcher]="matcher">
|
||||
@ -64,6 +56,23 @@
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<!-- datetimepicker -->
|
||||
<div *ngIf="configItem.inputType === 'datetimepicker'">
|
||||
<input
|
||||
[owlDateTime]="dt1"
|
||||
[owlDateTimeTrigger]="dt1"
|
||||
(dateTimeChange)="updateTime($event)"
|
||||
[value]="dateValue"
|
||||
/>
|
||||
<owl-date-time #dt1></owl-date-time>
|
||||
<mat-label>{{ configItem.label | translate }}</mat-label>
|
||||
<mat-hint *ngIf="configItem.helpText">{{ configItem.helpText | translate }}</mat-hint>
|
||||
<span matSuffix>
|
||||
<mat-icon pull="right" class="text-success" *ngIf="updateSuccessIcon">check_circle</mat-icon>
|
||||
</span>
|
||||
<mat-error *ngIf="error"> {{ error }} </mat-error>
|
||||
</div>
|
||||
|
||||
<!-- The editor -->
|
||||
<div *ngIf="configItem.inputType === 'markupText'">
|
||||
<h4>{{ configItem.label | translate }}</h4>
|
||||
@ -78,8 +87,7 @@
|
||||
</form>
|
||||
</div>
|
||||
<div class="reset-button">
|
||||
<button mat-icon-button *ngIf="configItem.defaultValue !== undefined" matTooltip="{{ 'Reset' | translate }}"
|
||||
(click)="onResetButton()">
|
||||
<button mat-icon-button *ngIf="hasDefault()" matTooltip="{{ 'Reset' | translate }}" (click)="onResetButton()">
|
||||
<mat-icon>replay</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
|
@ -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<any>
|
||||
) {
|
||||
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(<any>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;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user