Initial polling
This commit is contained in:
parent
1b761d31c0
commit
8d77c0495b
@ -21,15 +21,16 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Parent item -->
|
<!-- Parent item -->
|
||||||
<div *ngIf="itemObserver.value.length > 0">
|
<div *ngIf="itemObserver.value.length > 0" [formGroup]="form">
|
||||||
|
<mat-form-field>
|
||||||
<os-search-value-selector
|
<os-search-value-selector
|
||||||
ngDefaultControl
|
formControlName="agenda_parent_id"
|
||||||
[formControl]="form.get('agenda_parent_id')"
|
|
||||||
[multiple]="false"
|
[multiple]="false"
|
||||||
[includeNone]="true"
|
[includeNone]="true"
|
||||||
listname="{{ 'Parent agenda item' | translate }}"
|
placeholder="{{ 'Parent agenda item' | translate }}"
|
||||||
[inputListValues]="itemObserver"
|
[inputListValues]="itemObserver"
|
||||||
></os-search-value-selector>
|
></os-search-value-selector>
|
||||||
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
<div class="attachment-container" *ngIf="controlName">
|
<div class="attachment-container" *ngIf="contentForm">
|
||||||
|
<mat-form-field>
|
||||||
<os-search-value-selector
|
<os-search-value-selector
|
||||||
class="selector"
|
class="selector"
|
||||||
ngDefaultControl
|
|
||||||
[multiple]="true"
|
[multiple]="true"
|
||||||
listname="{{ 'Attachments' | translate }}"
|
placeholder="{{ 'Attachments' | translate }}"
|
||||||
[formControl]="controlName"
|
[formControl]="contentForm"
|
||||||
[inputListValues]="mediaFileList"
|
[inputListValues]="mediaFileList"
|
||||||
></os-search-value-selector>
|
></os-search-value-selector>
|
||||||
|
</mat-form-field>
|
||||||
<button type="button" mat-icon-button (click)="openUploadDialog(uploadDialog)" *osPerms="'mediafiles.can_manage'">
|
<button type="button" mat-icon-button (click)="openUploadDialog(uploadDialog)" *osPerms="'mediafiles.can_manage'">
|
||||||
<mat-icon>cloud_upload</mat-icon>
|
<mat-icon>cloud_upload</mat-icon>
|
||||||
</button>
|
</button>
|
||||||
|
@ -1,44 +1,68 @@
|
|||||||
import { Component, EventEmitter, Input, OnInit, Output, TemplateRef } from '@angular/core';
|
import { FocusMonitor } from '@angular/cdk/a11y';
|
||||||
import { ControlValueAccessor, FormControl } from '@angular/forms';
|
import {
|
||||||
import { MatDialog } from '@angular/material';
|
ChangeDetectionStrategy,
|
||||||
|
Component,
|
||||||
|
ElementRef,
|
||||||
|
EventEmitter,
|
||||||
|
OnInit,
|
||||||
|
Optional,
|
||||||
|
Output,
|
||||||
|
Self,
|
||||||
|
TemplateRef
|
||||||
|
} from '@angular/core';
|
||||||
|
import { FormBuilder, NgControl } from '@angular/forms';
|
||||||
|
import { MatDialog, MatFormFieldControl } from '@angular/material';
|
||||||
|
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { map } from 'rxjs/operators';
|
import { map } from 'rxjs/operators';
|
||||||
|
|
||||||
import { MediafileRepositoryService } from 'app/core/repositories/mediafiles/mediafile-repository.service';
|
import { MediafileRepositoryService } from 'app/core/repositories/mediafiles/mediafile-repository.service';
|
||||||
|
import { BaseFormControlComponent } from 'app/shared/models/base/base-form-control';
|
||||||
import { mediumDialogSettings } from 'app/shared/utils/dialog-settings';
|
import { mediumDialogSettings } from 'app/shared/utils/dialog-settings';
|
||||||
import { ViewMediafile } from 'app/site/mediafiles/models/view-mediafile';
|
import { ViewMediafile } from 'app/site/mediafiles/models/view-mediafile';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'os-attachment-control',
|
selector: 'os-attachment-control',
|
||||||
templateUrl: './attachment-control.component.html',
|
templateUrl: './attachment-control.component.html',
|
||||||
styleUrls: ['./attachment-control.component.scss']
|
styleUrls: ['./attachment-control.component.scss'],
|
||||||
|
providers: [{ provide: MatFormFieldControl, useExisting: AttachmentControlComponent }],
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush
|
||||||
})
|
})
|
||||||
export class AttachmentControlComponent implements OnInit, ControlValueAccessor {
|
export class AttachmentControlComponent extends BaseFormControlComponent<ViewMediafile[]> implements OnInit {
|
||||||
/**
|
/**
|
||||||
* Output for an error handler
|
* Output for an error handler
|
||||||
*/
|
*/
|
||||||
@Output()
|
@Output()
|
||||||
public errorHandler: EventEmitter<string> = new EventEmitter();
|
public errorHandler: EventEmitter<string> = new EventEmitter();
|
||||||
|
|
||||||
/**
|
|
||||||
* The form-control name to access the value for the form-control
|
|
||||||
*/
|
|
||||||
@Input()
|
|
||||||
public controlName: FormControl;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The file list that is necessary for the `SearchValueSelector`
|
* The file list that is necessary for the `SearchValueSelector`
|
||||||
*/
|
*/
|
||||||
public mediaFileList: Observable<ViewMediafile[]>;
|
public mediaFileList: Observable<ViewMediafile[]>;
|
||||||
|
|
||||||
|
public get empty(): boolean {
|
||||||
|
return !this.contentForm.value.length;
|
||||||
|
}
|
||||||
|
public get controlType(): string {
|
||||||
|
return 'attachment-control';
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default constructor
|
* Default constructor
|
||||||
*
|
*
|
||||||
* @param dialogService Reference to the `MatDialog`
|
* @param dialogService Reference to the `MatDialog`
|
||||||
* @param mediaService Reference for the `MediaFileRepositoryService`
|
* @param mediaService Reference for the `MediaFileRepositoryService`
|
||||||
*/
|
*/
|
||||||
public constructor(private dialogService: MatDialog, private mediaService: MediafileRepositoryService) {}
|
public constructor(
|
||||||
|
fb: FormBuilder,
|
||||||
|
fm: FocusMonitor,
|
||||||
|
element: ElementRef<HTMLElement>,
|
||||||
|
@Optional() @Self() public ngControl: NgControl,
|
||||||
|
private dialogService: MatDialog,
|
||||||
|
private mediaService: MediafileRepositoryService
|
||||||
|
) {
|
||||||
|
super(fb, fm, element, ngControl);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* On init method
|
* On init method
|
||||||
@ -64,12 +88,10 @@ export class AttachmentControlComponent implements OnInit, ControlValueAccessor
|
|||||||
* @param fileIDs a list with the ids of the uploaded files
|
* @param fileIDs a list with the ids of the uploaded files
|
||||||
*/
|
*/
|
||||||
public uploadSuccess(fileIDs: number[]): void {
|
public uploadSuccess(fileIDs: number[]): void {
|
||||||
if (this.controlName) {
|
const newValues = [...this.contentForm.value, ...fileIDs];
|
||||||
const newValues = [...this.controlName.value, ...fileIDs];
|
this.updateForm(newValues);
|
||||||
this.controlName.setValue(newValues);
|
|
||||||
this.dialogService.closeAll();
|
this.dialogService.closeAll();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Function to emit an occurring error.
|
* Function to emit an occurring error.
|
||||||
@ -80,29 +102,13 @@ export class AttachmentControlComponent implements OnInit, ControlValueAccessor
|
|||||||
this.errorHandler.emit(error);
|
this.errorHandler.emit(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public onContainerClick(event: MouseEvent): void {
|
||||||
* Function to write a new value to the form.
|
// TODO: implement
|
||||||
* Satisfy the interface.
|
|
||||||
*
|
|
||||||
* @param value The new value for this form.
|
|
||||||
*/
|
|
||||||
public writeValue(value: any): void {
|
|
||||||
if (value && this.controlName) {
|
|
||||||
this.controlName.setValue(value);
|
|
||||||
}
|
}
|
||||||
|
protected initializeForm(): void {
|
||||||
|
this.contentForm = this.fb.control([]);
|
||||||
|
}
|
||||||
|
protected updateForm(value: ViewMediafile[]): void {
|
||||||
|
this.contentForm.setValue(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Function executed when the control's value changed.
|
|
||||||
*
|
|
||||||
* @param fn the function that is executed.
|
|
||||||
*/
|
|
||||||
public registerOnChange(fn: any): void {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* To satisfy the interface
|
|
||||||
*
|
|
||||||
* @param fn the registered callback function for onBlur-events.
|
|
||||||
*/
|
|
||||||
public registerOnTouched(fn: any): void {}
|
|
||||||
}
|
}
|
||||||
|
@ -38,14 +38,14 @@
|
|||||||
(keydown)="keyDownFunction($event)"
|
(keydown)="keyDownFunction($event)"
|
||||||
/>
|
/>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
<mat-form-field *ngIf="searchList">
|
||||||
<os-search-value-selector
|
<os-search-value-selector
|
||||||
*ngIf="searchList"
|
formControlName="list"
|
||||||
ngDefaultControl
|
|
||||||
[formControl]="extensionFieldForm.get('list')"
|
|
||||||
[fullWidth]="true"
|
[fullWidth]="true"
|
||||||
[inputListValues]="searchList"
|
[inputListValues]="searchList"
|
||||||
[listname]="searchListLabel"
|
[placeholder]="searchListLabel"
|
||||||
></os-search-value-selector>
|
></os-search-value-selector>
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
<button mat-button (click)="changeEditMode(true)">{{ 'Save' | translate }}</button>
|
<button mat-button (click)="changeEditMode(true)">{{ 'Save' | translate }}</button>
|
||||||
<button mat-button (click)="changeEditMode()">{{ 'Cancel' | translate }}</button>
|
<button mat-button (click)="changeEditMode()">{{ 'Cancel' | translate }}</button>
|
||||||
|
@ -13,16 +13,17 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Directory selector, if no external directory is provided -->
|
<!-- Directory selector, if no external directory is provided -->
|
||||||
<div *ngIf="showDirectorySelector">
|
<div *ngIf="showDirectorySelector" [formGroup]="directorySelectionForm">
|
||||||
|
<mat-form-field>
|
||||||
<os-search-value-selector
|
<os-search-value-selector
|
||||||
ngDefaultControl
|
formControlName="parent_id"
|
||||||
[formControl]="directorySelectionForm.get('parent_id')"
|
|
||||||
[multiple]="false"
|
[multiple]="false"
|
||||||
[includeNone]="true"
|
[includeNone]="true"
|
||||||
[noneTitle]="'Base folder'"
|
[noneTitle]="'Base folder'"
|
||||||
listname="{{ 'Parent directory' | translate }}"
|
placeholder="{{ 'Parent directory' | translate }}"
|
||||||
[inputListValues]="directoryBehaviorSubject"
|
[inputListValues]="directoryBehaviorSubject"
|
||||||
></os-search-value-selector>
|
></os-search-value-selector>
|
||||||
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
@ -69,14 +70,15 @@
|
|||||||
<!-- Access groups -->
|
<!-- Access groups -->
|
||||||
<ng-container matColumnDef="access_groups">
|
<ng-container matColumnDef="access_groups">
|
||||||
<th mat-header-cell *matHeaderCellDef><span translate>Access groups</span></th>
|
<th mat-header-cell *matHeaderCellDef><span translate>Access groups</span></th>
|
||||||
<td mat-cell *matCellDef="let file">
|
<td mat-cell *matCellDef="let file" [formGroup]="file.form">
|
||||||
|
<mat-form-field>
|
||||||
<os-search-value-selector
|
<os-search-value-selector
|
||||||
ngDefaultControl
|
formControlName="access_groups_id"
|
||||||
[formControl]="file.form.get('access_groups_id')"
|
|
||||||
[multiple]="true"
|
[multiple]="true"
|
||||||
listname="{{ 'Access groups' | translate }}"
|
placeholder="{{ 'Access groups' | translate }}"
|
||||||
[inputListValues]="groupsBehaviorSubject"
|
[inputListValues]="groupsBehaviorSubject"
|
||||||
></os-search-value-selector>
|
></os-search-value-selector>
|
||||||
|
</mat-form-field>
|
||||||
</td>
|
</td>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
|
@ -90,7 +90,8 @@ export class MediaUploadContentComponent implements OnInit {
|
|||||||
|
|
||||||
public get selectedDirectoryId(): number | null {
|
public get selectedDirectoryId(): number | null {
|
||||||
if (this.showDirectorySelector) {
|
if (this.showDirectorySelector) {
|
||||||
return this.directorySelectionForm.controls.parent_id.value;
|
const parent = this.directorySelectionForm.controls.parent_id;
|
||||||
|
return !parent.value || typeof parent.value !== 'number' ? null : parent.value;
|
||||||
} else {
|
} else {
|
||||||
return this.directoryId;
|
return this.directoryId;
|
||||||
}
|
}
|
||||||
@ -110,7 +111,7 @@ export class MediaUploadContentComponent implements OnInit {
|
|||||||
this.directoryBehaviorSubject = this.repo.getDirectoryBehaviorSubject();
|
this.directoryBehaviorSubject = this.repo.getDirectoryBehaviorSubject();
|
||||||
this.groupsBehaviorSubject = this.groupRepo.getViewModelListBehaviorSubject();
|
this.groupsBehaviorSubject = this.groupRepo.getViewModelListBehaviorSubject();
|
||||||
this.directorySelectionForm = this.formBuilder.group({
|
this.directorySelectionForm = this.formBuilder.group({
|
||||||
parent_id: []
|
parent_id: null
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,19 +1,12 @@
|
|||||||
<mat-form-field [style.display]="fullWidth ? 'block' : 'inline-block'">
|
<mat-select [formControl]="contentForm" [multiple]="multiple">
|
||||||
<mat-select
|
<ngx-mat-select-search [formControl]="searchValue"></ngx-mat-select-search>
|
||||||
[formControl]="formControl"
|
<ng-container *ngIf="!multiple && includeNone">
|
||||||
placeholder="{{ listname | translate }}"
|
|
||||||
[multiple]="multiple"
|
|
||||||
#thisSelector
|
|
||||||
>
|
|
||||||
<ngx-mat-select-search ngModel (ngModelChange)="onSearch($event)"></ngx-mat-select-search>
|
|
||||||
<div *ngIf="!multiple && includeNone">
|
|
||||||
<mat-option [value]="null">
|
<mat-option [value]="null">
|
||||||
{{ noneTitle | translate }}
|
{{ noneTitle | translate }}
|
||||||
</mat-option>
|
</mat-option>
|
||||||
<mat-divider></mat-divider>
|
<mat-divider></mat-divider>
|
||||||
</div>
|
</ng-container>
|
||||||
<mat-option *ngFor="let selectedItem of getFilteredItems()" [value]="selectedItem.id">
|
<mat-option *ngFor="let selectedItem of getFilteredItems()" [value]="selectedItem.id">
|
||||||
{{ selectedItem.getTitle() | translate }}
|
{{ selectedItem.getTitle() | translate }}
|
||||||
</mat-option>
|
</mat-option>
|
||||||
</mat-select>
|
</mat-select>
|
||||||
</mat-form-field>
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { Component, ViewChild } from '@angular/core';
|
import { Component, ViewChild } from '@angular/core';
|
||||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
import { FormBuilder, FormControl } from '@angular/forms';
|
import { FormBuilder } from '@angular/forms';
|
||||||
|
|
||||||
import { BehaviorSubject } from 'rxjs';
|
import { BehaviorSubject } from 'rxjs';
|
||||||
|
|
||||||
@ -43,10 +43,8 @@ describe('SearchValueSelectorComponent', () => {
|
|||||||
hostComponent.searchValueSelectorComponent.inputListValues = subject;
|
hostComponent.searchValueSelectorComponent.inputListValues = subject;
|
||||||
|
|
||||||
const formBuilder: FormBuilder = TestBed.get(FormBuilder);
|
const formBuilder: FormBuilder = TestBed.get(FormBuilder);
|
||||||
const formGroup = formBuilder.group({
|
const formControl = formBuilder.control([]);
|
||||||
testArray: []
|
hostComponent.searchValueSelectorComponent.contentForm = formControl;
|
||||||
});
|
|
||||||
hostComponent.searchValueSelectorComponent.formControl = <FormControl>formGroup.get('testArray');
|
|
||||||
|
|
||||||
hostFixture.detectChanges();
|
hostFixture.detectChanges();
|
||||||
expect(hostComponent.searchValueSelectorComponent).toBeTruthy();
|
expect(hostComponent.searchValueSelectorComponent).toBeTruthy();
|
||||||
|
@ -1,31 +1,38 @@
|
|||||||
import { ChangeDetectionStrategy, Component, Input, OnDestroy, ViewChild } from '@angular/core';
|
import { FocusMonitor } from '@angular/cdk/a11y';
|
||||||
import { FormControl } from '@angular/forms';
|
import {
|
||||||
import { MatSelect } from '@angular/material';
|
ChangeDetectionStrategy,
|
||||||
|
ChangeDetectorRef,
|
||||||
|
Component,
|
||||||
|
ElementRef,
|
||||||
|
Input,
|
||||||
|
Optional,
|
||||||
|
Self
|
||||||
|
} from '@angular/core';
|
||||||
|
import { FormBuilder, FormControl, NgControl } from '@angular/forms';
|
||||||
|
import { MatFormFieldControl } from '@angular/material';
|
||||||
|
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { Observable, Subscription } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { auditTime } from 'rxjs/operators';
|
import { auditTime } from 'rxjs/operators';
|
||||||
|
|
||||||
|
import { BaseFormControlComponent } from 'app/shared/models/base/base-form-control';
|
||||||
import { Selectable } from '../selectable';
|
import { Selectable } from '../selectable';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reusable Searchable Value Selector
|
* Searchable Value Selector
|
||||||
*
|
*
|
||||||
* Use `multiple="true"`, `[InputListValues]=myValues`,`[formControl]="myformcontrol"` and `placeholder={{listname}}` to pass the Values and Listname
|
* Use `multiple="true"`, `[inputListValues]=myValues`,`formControlName="myformcontrol"` and `placeholder={{listname}}` to pass the Values and Listname
|
||||||
*
|
*
|
||||||
* ## Examples:
|
* ## Examples:
|
||||||
*
|
*
|
||||||
* ### Usage of the selector:
|
* ### Usage of the selector:
|
||||||
*
|
*
|
||||||
* ngDefaultControl: https://stackoverflow.com/a/39053470
|
|
||||||
*
|
|
||||||
* ```html
|
* ```html
|
||||||
* <os-search-value-selector
|
* <os-search-value-selector
|
||||||
* ngDefaultControl
|
|
||||||
* [multiple]="true"
|
* [multiple]="true"
|
||||||
* placeholder="Placeholder"
|
* placeholder="Placeholder"
|
||||||
* [InputListValues]="myListValues"
|
* [inputListValues]="myListValues"
|
||||||
* [formControl]="myformcontrol">
|
* formControlName="myformcontrol">
|
||||||
* </os-search-value-selector>
|
* </os-search-value-selector>
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
@ -35,24 +42,10 @@ import { Selectable } from '../selectable';
|
|||||||
selector: 'os-search-value-selector',
|
selector: 'os-search-value-selector',
|
||||||
templateUrl: './search-value-selector.component.html',
|
templateUrl: './search-value-selector.component.html',
|
||||||
styleUrls: ['./search-value-selector.component.scss'],
|
styleUrls: ['./search-value-selector.component.scss'],
|
||||||
|
providers: [{ provide: MatFormFieldControl, useExisting: SearchValueSelectorComponent }],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush
|
changeDetection: ChangeDetectionStrategy.OnPush
|
||||||
})
|
})
|
||||||
export class SearchValueSelectorComponent implements OnDestroy {
|
export class SearchValueSelectorComponent extends BaseFormControlComponent<Selectable[]> {
|
||||||
/**
|
|
||||||
* Saves the current subscription to _inputListSubject.
|
|
||||||
*/
|
|
||||||
private _inputListSubscription: Subscription = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Value of the search input
|
|
||||||
*/
|
|
||||||
private searchValue = '';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* All items
|
|
||||||
*/
|
|
||||||
private selectableItems: Selectable[];
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decide if this should be a single or multi-select-field
|
* Decide if this should be a single or multi-select-field
|
||||||
*/
|
*/
|
||||||
@ -83,55 +76,41 @@ export class SearchValueSelectorComponent implements OnDestroy {
|
|||||||
if (!value) {
|
if (!value) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
this.subscriptions.push(
|
||||||
if (Array.isArray(value)) {
|
value.pipe(auditTime(10)).subscribe(items => {
|
||||||
this.selectableItems = value;
|
|
||||||
} else {
|
|
||||||
// unsubscribe to old subscription.
|
|
||||||
if (this._inputListSubscription) {
|
|
||||||
this._inputListSubscription.unsubscribe();
|
|
||||||
}
|
|
||||||
this._inputListSubscription = value.pipe(auditTime(10)).subscribe(items => {
|
|
||||||
this.selectableItems = items;
|
this.selectableItems = items;
|
||||||
if (this.formControl) {
|
if (this.contentForm) {
|
||||||
!!items && items.length > 0
|
this.disabled = !items || (!!items && !items.length);
|
||||||
? this.formControl.enable({ emitEvent: false })
|
|
||||||
: this.formControl.disable({ emitEvent: false });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public searchValue: FormControl;
|
||||||
* Placeholder of the List
|
|
||||||
*/
|
public get empty(): boolean {
|
||||||
@Input()
|
return Array.isArray(this.contentForm.value) ? !this.contentForm.value.length : !this.contentForm.value;
|
||||||
public listname: string;
|
}
|
||||||
|
|
||||||
|
public controlType = 'search-value-selector';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Name of the Form
|
* All items
|
||||||
*/
|
*/
|
||||||
@Input()
|
private selectableItems: Selectable[];
|
||||||
public formControl: FormControl;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The MultiSelect Component
|
|
||||||
*/
|
|
||||||
@ViewChild('thisSelector', { static: true })
|
|
||||||
public thisSelector: MatSelect;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Empty constructor
|
* Empty constructor
|
||||||
*/
|
*/
|
||||||
public constructor(protected translate: TranslateService) {}
|
public constructor(
|
||||||
|
protected translate: TranslateService,
|
||||||
/**
|
cd: ChangeDetectorRef,
|
||||||
* Unsubscribe on destroing.
|
fb: FormBuilder,
|
||||||
*/
|
@Optional() @Self() public ngControl: NgControl,
|
||||||
public ngOnDestroy(): void {
|
fm: FocusMonitor,
|
||||||
if (this._inputListSubscription) {
|
element: ElementRef<HTMLElement>
|
||||||
this._inputListSubscription.unsubscribe();
|
) {
|
||||||
}
|
super(fb, fm, element, ngControl);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -141,29 +120,42 @@ export class SearchValueSelectorComponent implements OnDestroy {
|
|||||||
*/
|
*/
|
||||||
public getFilteredItems(): Selectable[] {
|
public getFilteredItems(): Selectable[] {
|
||||||
if (this.selectableItems) {
|
if (this.selectableItems) {
|
||||||
|
const searchValue: string = this.searchValue.value.toLowerCase();
|
||||||
return this.selectableItems.filter(item => {
|
return this.selectableItems.filter(item => {
|
||||||
const idString = '' + item.id;
|
const idString = '' + item.id;
|
||||||
const foundId =
|
const foundId =
|
||||||
idString
|
idString
|
||||||
.trim()
|
.trim()
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
.indexOf(this.searchValue) !== -1;
|
.indexOf(searchValue) !== -1;
|
||||||
|
|
||||||
if (foundId) {
|
if (foundId) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
const searchableString = this.translate.instant(item.getTitle()).toLowerCase();
|
|
||||||
return searchableString.indexOf(this.searchValue) > -1;
|
return (
|
||||||
|
item
|
||||||
|
.toString()
|
||||||
|
.toLowerCase()
|
||||||
|
.indexOf(searchValue) > -1
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public onContainerClick(event: MouseEvent): void {
|
||||||
* Function to set the search value.
|
if ((event.target as Element).tagName.toLowerCase() !== 'select') {
|
||||||
*
|
// this.element.nativeElement.querySelector('select').focus();
|
||||||
* @param searchValue the new value the user is searching for.
|
}
|
||||||
*/
|
}
|
||||||
public onSearch(searchValue: string): void {
|
|
||||||
this.searchValue = searchValue.toLowerCase();
|
protected initializeForm(): void {
|
||||||
|
this.contentForm = this.fb.control([]);
|
||||||
|
this.searchValue = this.fb.control('');
|
||||||
|
}
|
||||||
|
|
||||||
|
protected updateForm(value: Selectable[] | null): void {
|
||||||
|
const nextValue = value;
|
||||||
|
this.contentForm.setValue(nextValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
161
client/src/app/shared/models/base/base-form-control.ts
Normal file
161
client/src/app/shared/models/base/base-form-control.ts
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
import { FocusMonitor } from '@angular/cdk/a11y';
|
||||||
|
import { coerceBooleanProperty } from '@angular/cdk/coercion';
|
||||||
|
import { ElementRef, HostBinding, Input, OnDestroy, Optional, Self } from '@angular/core';
|
||||||
|
import { ControlValueAccessor, FormBuilder, FormControl, FormGroup, NgControl } from '@angular/forms';
|
||||||
|
import { MatFormFieldControl } from '@angular/material';
|
||||||
|
|
||||||
|
import { Subject, Subscription } from 'rxjs';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abstract class to implement some simple logic and provide the subclass as a controllable form-control in `MatFormField`.
|
||||||
|
*
|
||||||
|
* Please remember to prepare the `providers` in the `@Component`-decorator. Something like:
|
||||||
|
*
|
||||||
|
* ```ts
|
||||||
|
* @Component({
|
||||||
|
* selector: ...,
|
||||||
|
* templateUrl: ...,
|
||||||
|
* styleUrls: [...],
|
||||||
|
* providers: [{ provide: MatFormFieldControl, useExisting: <TheComponent>}]
|
||||||
|
* })
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export abstract class BaseFormControlComponent<T> extends MatFormFieldControl<T>
|
||||||
|
implements OnDestroy, ControlValueAccessor {
|
||||||
|
public static nextId = 0;
|
||||||
|
|
||||||
|
@HostBinding() public id = `base-form-control-${BaseFormControlComponent.nextId++}`;
|
||||||
|
|
||||||
|
@HostBinding('class.floating') public get shouldLabelFloat(): boolean {
|
||||||
|
return this.focused || !this.empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
@HostBinding('attr.aria-describedby') public describedBy = '';
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
public set value(value: T | null) {
|
||||||
|
this.updateForm(value);
|
||||||
|
this.stateChanges.next();
|
||||||
|
}
|
||||||
|
|
||||||
|
public get value(): T | null {
|
||||||
|
return this.contentForm.value || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
public set placeholder(placeholder: string) {
|
||||||
|
this._placeholder = placeholder;
|
||||||
|
this.stateChanges.next();
|
||||||
|
}
|
||||||
|
|
||||||
|
public get placeholder(): string {
|
||||||
|
return this._placeholder;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
public set required(required: boolean) {
|
||||||
|
this._required = coerceBooleanProperty(required);
|
||||||
|
this.stateChanges.next();
|
||||||
|
}
|
||||||
|
|
||||||
|
public get required(): boolean {
|
||||||
|
return this._required;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
public set disabled(disable: boolean) {
|
||||||
|
this._disabled = coerceBooleanProperty(disable);
|
||||||
|
this._disabled ? this.contentForm.disable() : this.contentForm.enable();
|
||||||
|
this.stateChanges.next();
|
||||||
|
}
|
||||||
|
|
||||||
|
public get disabled(): boolean {
|
||||||
|
return this._disabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract get empty(): boolean;
|
||||||
|
|
||||||
|
public abstract get controlType(): string;
|
||||||
|
|
||||||
|
public contentForm: FormControl | FormGroup;
|
||||||
|
|
||||||
|
public stateChanges = new Subject<void>();
|
||||||
|
|
||||||
|
public errorState = false;
|
||||||
|
|
||||||
|
public focused = false;
|
||||||
|
|
||||||
|
private _placeholder: string;
|
||||||
|
|
||||||
|
private _required = false;
|
||||||
|
|
||||||
|
private _disabled = false;
|
||||||
|
|
||||||
|
protected subscriptions: Subscription[] = [];
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
protected fb: FormBuilder,
|
||||||
|
protected fm: FocusMonitor,
|
||||||
|
protected element: ElementRef<HTMLElement>,
|
||||||
|
@Optional() @Self() public ngControl: NgControl
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.initializeForm();
|
||||||
|
|
||||||
|
if (this.ngControl !== null) {
|
||||||
|
this.ngControl.valueAccessor = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.subscriptions.push(
|
||||||
|
fm.monitor(element.nativeElement, true).subscribe(origin => {
|
||||||
|
this.focused = !!origin;
|
||||||
|
this.stateChanges.next();
|
||||||
|
}),
|
||||||
|
this.contentForm.valueChanges.subscribe(nextValue => this.push(nextValue))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ngOnDestroy(): void {
|
||||||
|
for (const subscription of this.subscriptions) {
|
||||||
|
subscription.unsubscribe();
|
||||||
|
}
|
||||||
|
this.subscriptions = [];
|
||||||
|
|
||||||
|
this.fm.stopMonitoring(this.element.nativeElement);
|
||||||
|
|
||||||
|
this.stateChanges.complete();
|
||||||
|
}
|
||||||
|
|
||||||
|
public writeValue(value: T): void {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
public registerOnChange(fn: any): void {
|
||||||
|
this._onChange = fn;
|
||||||
|
}
|
||||||
|
public registerOnTouched(fn: any): void {
|
||||||
|
this._onTouched = fn;
|
||||||
|
}
|
||||||
|
public setDisabledState?(isDisabled: boolean): void {
|
||||||
|
this.disabled = isDisabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public setDescribedByIds(ids: string[]): void {
|
||||||
|
this.describedBy = ids.join(' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract onContainerClick(event: MouseEvent): void;
|
||||||
|
|
||||||
|
protected _onChange = (value: T) => {};
|
||||||
|
|
||||||
|
protected _onTouched = (value: T) => {};
|
||||||
|
|
||||||
|
protected abstract initializeForm(): void;
|
||||||
|
|
||||||
|
protected abstract updateForm(value: T | null): void;
|
||||||
|
|
||||||
|
protected push(value: T): void {
|
||||||
|
this._onChange(value);
|
||||||
|
this._onTouched(value);
|
||||||
|
}
|
||||||
|
}
|
@ -1,14 +1,18 @@
|
|||||||
import { BasePoll, BasePollWithoutNestedModels } from '../poll/base-poll';
|
import { BasePoll, BasePollWithoutNestedModels } from '../poll/base-poll';
|
||||||
import { MotionOption } from './motion-option';
|
import { MotionOption } from './motion-option';
|
||||||
|
|
||||||
export enum MotionPollmethods {
|
export enum MotionPollMethods {
|
||||||
'YN' = 'YN',
|
YN = 'YN',
|
||||||
'YNA' = 'YNA'
|
YNA = 'YNA'
|
||||||
}
|
}
|
||||||
|
export const MotionPollMethodsVerbose = {
|
||||||
|
YN: 'Yes/No',
|
||||||
|
YNA: 'Yes/No/Abstain'
|
||||||
|
};
|
||||||
|
|
||||||
export interface MotionPollWithoutNestedModels extends BasePollWithoutNestedModels {
|
export interface MotionPollWithoutNestedModels extends BasePollWithoutNestedModels {
|
||||||
motion_id: number;
|
motion_id: number;
|
||||||
pollmethod: MotionPollmethods;
|
pollmethod: MotionPollMethods;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -22,5 +26,9 @@ export class MotionPoll extends BasePoll<MotionPoll, MotionOption> {
|
|||||||
public constructor(input?: any) {
|
public constructor(input?: any) {
|
||||||
super(MotionPoll.COLLECTIONSTRING, input);
|
super(MotionPoll.COLLECTIONSTRING, input);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public get pollmethodVerbose(): string {
|
||||||
|
return MotionPollMethodsVerbose[this.pollmethod];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
export interface MotionPoll extends MotionPollWithoutNestedModels {}
|
export interface MotionPoll extends MotionPollWithoutNestedModels {}
|
||||||
|
@ -8,12 +8,55 @@ export enum PollState {
|
|||||||
Published
|
Published
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const PollStateVerbose = {
|
||||||
|
1: 'Created',
|
||||||
|
2: 'Started',
|
||||||
|
3: 'Finished',
|
||||||
|
4: 'Published'
|
||||||
|
};
|
||||||
|
|
||||||
export enum PollType {
|
export enum PollType {
|
||||||
Analog = 'analog',
|
Analog = 'analog',
|
||||||
Named = 'named',
|
Named = 'named',
|
||||||
Pseudoanonymous = 'pseudoanonymous'
|
Pseudoanonymous = 'pseudoanonymous'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const PollTypeVerbose = {
|
||||||
|
analog: 'Analog',
|
||||||
|
named: 'Named',
|
||||||
|
pseudoanonymous: 'Pseudoanonymous'
|
||||||
|
};
|
||||||
|
|
||||||
|
export enum PercentBase {
|
||||||
|
YN = 'YN',
|
||||||
|
YNA = 'YNA',
|
||||||
|
Valid = 'valid',
|
||||||
|
Cast = 'cast',
|
||||||
|
Disabled = 'disabled'
|
||||||
|
}
|
||||||
|
|
||||||
|
export const PercentBaseVerbose = {
|
||||||
|
YN: 'Yes/No',
|
||||||
|
YNA: 'Yes/No/Abstain',
|
||||||
|
valid: 'Valid votes',
|
||||||
|
cast: 'Casted votes',
|
||||||
|
disabled: 'Disabled'
|
||||||
|
};
|
||||||
|
|
||||||
|
export enum MajorityMethod {
|
||||||
|
Simple = 'simple',
|
||||||
|
TwoThirds = 'two_thirds',
|
||||||
|
ThreeQuarters = 'three_quarters',
|
||||||
|
Disabled = 'disabled'
|
||||||
|
}
|
||||||
|
|
||||||
|
export const MajorityMethodVerbose = {
|
||||||
|
simple: 'Simple',
|
||||||
|
two_thirds: 'Two Thirds',
|
||||||
|
three_quarters: 'Three Quarters',
|
||||||
|
disabled: 'Disabled'
|
||||||
|
};
|
||||||
|
|
||||||
export interface BasePollWithoutNestedModels {
|
export interface BasePollWithoutNestedModels {
|
||||||
state: PollState;
|
state: PollState;
|
||||||
type: PollType;
|
type: PollType;
|
||||||
@ -23,6 +66,8 @@ export interface BasePollWithoutNestedModels {
|
|||||||
votescast: number;
|
votescast: number;
|
||||||
groups_id: number[];
|
groups_id: number[];
|
||||||
voted_id: number[];
|
voted_id: number[];
|
||||||
|
majority_method: MajorityMethod;
|
||||||
|
onehundred_percent_base: PercentBase;
|
||||||
}
|
}
|
||||||
|
|
||||||
export abstract class BasePoll<T, O extends BaseOption<any>> extends BaseDecimalModel<T> {
|
export abstract class BasePoll<T, O extends BaseOption<any>> extends BaseDecimalModel<T> {
|
||||||
@ -31,5 +76,21 @@ export abstract class BasePoll<T, O extends BaseOption<any>> extends BaseDecimal
|
|||||||
protected getDecimalFields(): (keyof BasePoll<T, O>)[] {
|
protected getDecimalFields(): (keyof BasePoll<T, O>)[] {
|
||||||
return ['votesvalid', 'votesinvalid', 'votescast'];
|
return ['votesvalid', 'votesinvalid', 'votescast'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public get stateVerbose(): string {
|
||||||
|
return PollStateVerbose[this.state];
|
||||||
|
}
|
||||||
|
|
||||||
|
public get typeVerbose(): string {
|
||||||
|
return PollTypeVerbose[this.type];
|
||||||
|
}
|
||||||
|
|
||||||
|
public get majorityMethodVerbose(): string {
|
||||||
|
return MajorityMethodVerbose[this.majority_method];
|
||||||
|
}
|
||||||
|
|
||||||
|
public get percentBaseVerbose(): string {
|
||||||
|
return PercentBaseVerbose[this.onehundred_percent_base];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
export interface BasePoll<T, O extends BaseOption<any>> extends BasePollWithoutNestedModels {}
|
export interface BasePoll<T, O extends BaseOption<any>> extends BasePollWithoutNestedModels {}
|
||||||
|
@ -1,4 +1,10 @@
|
|||||||
<os-head-bar [nav]="false" [goBack]="true" [editMode]="isSortMode" (cancelEditEvent)="onCancelSorting()" (saveEvent)="onSaveSorting()">
|
<os-head-bar
|
||||||
|
[nav]="false"
|
||||||
|
[goBack]="true"
|
||||||
|
[editMode]="isSortMode"
|
||||||
|
(cancelEditEvent)="onCancelSorting()"
|
||||||
|
(saveEvent)="onSaveSorting()"
|
||||||
|
>
|
||||||
<!-- Title -->
|
<!-- Title -->
|
||||||
<div class="title-slot">
|
<div class="title-slot">
|
||||||
<h2>
|
<h2>
|
||||||
@ -8,7 +14,15 @@
|
|||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
<div class="menu-slot" *osPerms="['agenda.can_manage_list_of_speakers', 'core.can_manage_projector']">
|
<div class="menu-slot" *osPerms="['agenda.can_manage_list_of_speakers', 'core.can_manage_projector']">
|
||||||
<button type="button" mat-icon-button matTooltip="{{ 'Re-add last speaker' | translate }}" (click)="readdLastSpeaker()" [disabled]="!finishedSpeakers || !finishedSpeakers.length"><mat-icon>undo</mat-icon></button>
|
<button
|
||||||
|
type="button"
|
||||||
|
mat-icon-button
|
||||||
|
matTooltip="{{ 'Re-add last speaker' | translate }}"
|
||||||
|
(click)="readdLastSpeaker()"
|
||||||
|
[disabled]="!finishedSpeakers || !finishedSpeakers.length"
|
||||||
|
>
|
||||||
|
<mat-icon>undo</mat-icon>
|
||||||
|
</button>
|
||||||
<button type="button" mat-icon-button [matMenuTriggerFor]="speakerMenu"><mat-icon>more_vert</mat-icon></button>
|
<button type="button" mat-icon-button [matMenuTriggerFor]="speakerMenu"><mat-icon>more_vert</mat-icon></button>
|
||||||
</div>
|
</div>
|
||||||
</os-head-bar>
|
</os-head-bar>
|
||||||
@ -77,12 +91,7 @@
|
|||||||
|
|
||||||
<!-- Waiting speakers -->
|
<!-- Waiting speakers -->
|
||||||
<div class="waiting-list" *ngIf="speakers && speakers.length > 0">
|
<div class="waiting-list" *ngIf="speakers && speakers.length > 0">
|
||||||
<os-sorting-list
|
<os-sorting-list [input]="speakers" [live]="!isSortMode" [count]="true" [enable]="opCanManage() && isSortMode">
|
||||||
[input]="speakers"
|
|
||||||
[live]="!isSortMode"
|
|
||||||
[count]="true"
|
|
||||||
[enable]="opCanManage() && isSortMode"
|
|
||||||
>
|
|
||||||
<!-- implicit speaker references into the component using ng-template slot -->
|
<!-- implicit speaker references into the component using ng-template slot -->
|
||||||
<ng-template let-speaker>
|
<ng-template let-speaker>
|
||||||
<span *osPerms="'agenda.can_manage_list_of_speakers'">
|
<span *osPerms="'agenda.can_manage_list_of_speakers'">
|
||||||
@ -124,13 +133,14 @@
|
|||||||
<!-- Search for speakers -->
|
<!-- Search for speakers -->
|
||||||
<div *osPerms="'agenda.can_manage_list_of_speakers'">
|
<div *osPerms="'agenda.can_manage_list_of_speakers'">
|
||||||
<form *ngIf="filteredUsers && filteredUsers.value.length > 0" [formGroup]="addSpeakerForm">
|
<form *ngIf="filteredUsers && filteredUsers.value.length > 0" [formGroup]="addSpeakerForm">
|
||||||
|
<mat-form-field>
|
||||||
<os-search-value-selector
|
<os-search-value-selector
|
||||||
class="search-users"
|
class="search-users"
|
||||||
ngDefaultControl
|
formControlName="user_id"
|
||||||
[formControl]="addSpeakerForm.get('user_id')"
|
placeholder="{{ 'Select or search new speaker ...' | translate }}"
|
||||||
listname="{{ 'Select or search new speaker ...' | translate }}"
|
|
||||||
[inputListValues]="filteredUsers"
|
[inputListValues]="filteredUsers"
|
||||||
></os-search-value-selector>
|
></os-search-value-selector>
|
||||||
|
</mat-form-field>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -145,10 +145,7 @@
|
|||||||
*ngIf="assignment && assignment.polls && assignment.polls.length"
|
*ngIf="assignment && assignment.polls && assignment.polls.length"
|
||||||
>
|
>
|
||||||
<!-- TODO avoid animation/switching on update -->
|
<!-- TODO avoid animation/switching on update -->
|
||||||
<mat-tab
|
<mat-tab *ngFor="let poll of assignment.polls; let i = index; trackBy: trackByIndex" [label]="poll.title">
|
||||||
*ngFor="let poll of assignment.polls; let i = index; trackBy: trackByIndex"
|
|
||||||
[label]="poll.title"
|
|
||||||
>
|
|
||||||
<os-assignment-poll [assignment]="assignment" [poll]="poll"> </os-assignment-poll>
|
<os-assignment-poll [assignment]="assignment" [poll]="poll"> </os-assignment-poll>
|
||||||
</mat-tab>
|
</mat-tab>
|
||||||
</mat-tab-group>
|
</mat-tab-group>
|
||||||
@ -194,14 +191,15 @@
|
|||||||
*ngIf="hasPerms('addOthers') && filteredCandidates && filteredCandidates.value.length > 0"
|
*ngIf="hasPerms('addOthers') && filteredCandidates && filteredCandidates.value.length > 0"
|
||||||
[formGroup]="candidatesForm"
|
[formGroup]="candidatesForm"
|
||||||
>
|
>
|
||||||
|
<mat-form-field>
|
||||||
<os-search-value-selector
|
<os-search-value-selector
|
||||||
class="search-bar"
|
class="search-bar"
|
||||||
ngDefaultControl
|
formControlName="userId"
|
||||||
[formControl]="candidatesForm.get('userId')"
|
|
||||||
[multiple]="false"
|
[multiple]="false"
|
||||||
listname="{{ 'Select a new candidate' | translate }}"
|
placeholder="{{ 'Select a new candidate' | translate }}"
|
||||||
[inputListValues]="filteredCandidates"
|
[inputListValues]="filteredCandidates"
|
||||||
></os-search-value-selector>
|
></os-search-value-selector>
|
||||||
|
</mat-form-field>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -238,11 +236,7 @@
|
|||||||
<div>
|
<div>
|
||||||
<!-- title -->
|
<!-- title -->
|
||||||
<mat-form-field class="full-width">
|
<mat-form-field class="full-width">
|
||||||
<input
|
<input matInput placeholder="{{ 'Title' | translate }}" formControlName="title" />
|
||||||
matInput
|
|
||||||
placeholder="{{ 'Title' | translate }}"
|
|
||||||
formControlName="title"
|
|
||||||
/>
|
|
||||||
<mat-error>{{ 'The title is required' | translate }}</mat-error>
|
<mat-error>{{ 'The title is required' | translate }}</mat-error>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
@ -256,22 +250,20 @@
|
|||||||
></editor>
|
></editor>
|
||||||
|
|
||||||
<!-- searchValueSelector: tags -->
|
<!-- searchValueSelector: tags -->
|
||||||
<div class="content-field" *ngIf="tagsAvailable">
|
<div class="content-field" *ngIf="tagsAvailable" [formGroup]="assignmentForm">
|
||||||
|
<mat-form-field>
|
||||||
<os-search-value-selector
|
<os-search-value-selector
|
||||||
ngDefaultControl
|
formControlName="tags_id"
|
||||||
[formControl]="assignmentForm.get('tags_id')"
|
|
||||||
[multiple]="true"
|
[multiple]="true"
|
||||||
[includeNone]="true"
|
[includeNone]="true"
|
||||||
listname="{{ 'Tags' | translate }}"
|
placeholder="{{ 'Tags' | translate }}"
|
||||||
[inputListValues]="tagsObserver"
|
[inputListValues]="tagsObserver"
|
||||||
></os-search-value-selector>
|
></os-search-value-selector>
|
||||||
</div>
|
</mat-form-field>
|
||||||
|
|
||||||
<!-- Attachments -->
|
|
||||||
<div class="content-field">
|
|
||||||
<os-attachment-control
|
<os-attachment-control
|
||||||
|
formControlName="attachments_id"
|
||||||
(errorHandler)="raiseError($event)"
|
(errorHandler)="raiseError($event)"
|
||||||
[controlName]="assignmentForm.get('attachments_id')"
|
|
||||||
></os-attachment-control>
|
></os-attachment-control>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -13,15 +13,16 @@
|
|||||||
|
|
||||||
<div class="custom-table-header">
|
<div class="custom-table-header">
|
||||||
<div>
|
<div>
|
||||||
<span>
|
<span [formGroup]="modelSelectForm">
|
||||||
|
<mat-form-field>
|
||||||
<os-search-value-selector
|
<os-search-value-selector
|
||||||
ngDefaultControl
|
formControlName="model"
|
||||||
[formControl]="modelSelectForm.get('model')"
|
|
||||||
[multiple]="false"
|
[multiple]="false"
|
||||||
[includeNone]="false"
|
[includeNone]="false"
|
||||||
listname="{{ 'Motion' | translate }}"
|
placeholder="{{ 'Motion' | translate }}"
|
||||||
[inputListValues]="collectionObserver"
|
[inputListValues]="collectionObserver"
|
||||||
></os-search-value-selector>
|
></os-search-value-selector>
|
||||||
|
</mat-form-field>
|
||||||
</span>
|
</span>
|
||||||
<span class="spacer-left-20">
|
<span class="spacer-left-20">
|
||||||
<button mat-button (click)="refresh()" *ngIf="currentModelId">
|
<button mat-button (click)="refresh()" *ngIf="currentModelId">
|
||||||
|
@ -269,13 +269,14 @@
|
|||||||
<mat-error *ngIf="fileEditForm.invalid" translate>Required</mat-error>
|
<mat-error *ngIf="fileEditForm.invalid" translate>Required</mat-error>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
|
||||||
|
<mat-form-field>
|
||||||
<os-search-value-selector
|
<os-search-value-selector
|
||||||
ngDefaultControl
|
formControlName="access_groups_id"
|
||||||
[formControl]="fileEditForm.get('access_groups_id')"
|
|
||||||
[multiple]="true"
|
[multiple]="true"
|
||||||
listname="{{ 'Access groups' | translate }}"
|
placeholder="{{ 'Access groups' | translate }}"
|
||||||
[inputListValues]="groupsBehaviorSubject"
|
[inputListValues]="groupsBehaviorSubject"
|
||||||
></os-search-value-selector>
|
></os-search-value-selector>
|
||||||
|
</mat-form-field>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div mat-dialog-actions>
|
<div mat-dialog-actions>
|
||||||
@ -301,16 +302,17 @@
|
|||||||
<form class="edit-file-form" [formGroup]="newDirectoryForm">
|
<form class="edit-file-form" [formGroup]="newDirectoryForm">
|
||||||
<p translate>Please enter a name for the new directory:</p>
|
<p translate>Please enter a name for the new directory:</p>
|
||||||
<mat-form-field>
|
<mat-form-field>
|
||||||
<input matInput osAutofocus formControlName="title" required />
|
<input matInput osAutofocus formControlName="title" placeholder="{{ 'Title' | translate }}" required />
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
|
||||||
|
<mat-form-field>
|
||||||
<os-search-value-selector
|
<os-search-value-selector
|
||||||
ngDefaultControl
|
formControlName="access_groups_id"
|
||||||
[formControl]="newDirectoryForm.get('access_groups_id')"
|
|
||||||
[multiple]="true"
|
[multiple]="true"
|
||||||
listname="{{ 'Access groups' | translate }}"
|
placeholder="{{ 'Access groups' | translate }}"
|
||||||
[inputListValues]="groupsBehaviorSubject"
|
[inputListValues]="groupsBehaviorSubject"
|
||||||
></os-search-value-selector>
|
></os-search-value-selector>
|
||||||
|
</mat-form-field>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div mat-dialog-actions>
|
<div mat-dialog-actions>
|
||||||
@ -328,16 +330,17 @@
|
|||||||
<h1 mat-dialog-title>
|
<h1 mat-dialog-title>
|
||||||
<span translate>Move into directory</span>
|
<span translate>Move into directory</span>
|
||||||
</h1>
|
</h1>
|
||||||
<div class="os-form-card-mobile" mat-dialog-content>
|
<div class="os-form-card-mobile" [formGroup]="moveForm" mat-dialog-content>
|
||||||
<p translate>Please select the directory:</p>
|
<p translate>Please select the directory:</p>
|
||||||
|
<mat-form-field>
|
||||||
<os-search-value-selector
|
<os-search-value-selector
|
||||||
ngDefaultControl
|
formControlName="directory_id"
|
||||||
[formControl]="moveForm.get('directory_id')"
|
|
||||||
[includeNone]="true"
|
[includeNone]="true"
|
||||||
[noneTitle]="'Base folder'"
|
[noneTitle]="'Base folder'"
|
||||||
listname="{{ 'Parent directory' | translate }}"
|
placeholder="{{ 'Parent directory' | translate }}"
|
||||||
[inputListValues]="filteredDirectoryBehaviorSubject"
|
[inputListValues]="filteredDirectoryBehaviorSubject"
|
||||||
></os-search-value-selector>
|
></os-search-value-selector>
|
||||||
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
<div mat-dialog-actions>
|
<div mat-dialog-actions>
|
||||||
<button type="submit" mat-button color="primary" [mat-dialog-close]="true">
|
<button type="submit" mat-button color="primary" [mat-dialog-close]="true">
|
||||||
|
@ -18,17 +18,16 @@ export class ViewMotionPoll extends BaseProjectableViewModel<MotionPoll> impleme
|
|||||||
}
|
}
|
||||||
|
|
||||||
public getSlide(): ProjectorElementBuildDeskriptor {
|
public getSlide(): ProjectorElementBuildDeskriptor {
|
||||||
/*return {
|
return {
|
||||||
getBasicProjectorElement: options => ({
|
getBasicProjectorElement: options => ({
|
||||||
name: Motion.COLLECTIONSTRING,
|
name: MotionPoll.COLLECTIONSTRING,
|
||||||
id: this.id,
|
id: this.id,
|
||||||
getIdentifiers: () => ['name', 'id']
|
getIdentifiers: () => ['name', 'id']
|
||||||
}),
|
}),
|
||||||
slideOptions: [],
|
slideOptions: [],
|
||||||
projectionDefaultName: 'motions',
|
projectionDefaultName: 'motion-poll',
|
||||||
getDialogTitle: this.getTitle
|
getDialogTitle: this.getTitle
|
||||||
};*/
|
};
|
||||||
throw new Error('TODO');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,22 +87,24 @@
|
|||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
|
<mat-form-field>
|
||||||
<os-search-value-selector
|
<os-search-value-selector
|
||||||
ngDefaultControl
|
formControlName="read_groups_id"
|
||||||
[formControl]="commentFieldForm.get('read_groups_id')"
|
|
||||||
[multiple]="true"
|
[multiple]="true"
|
||||||
listname="Groups with read permissions"
|
placeholder="Groups with read permissions"
|
||||||
[inputListValues]="groups"
|
[inputListValues]="groups"
|
||||||
></os-search-value-selector>
|
></os-search-value-selector>
|
||||||
|
</mat-form-field>
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
|
<mat-form-field>
|
||||||
<os-search-value-selector
|
<os-search-value-selector
|
||||||
ngDefaultControl
|
formControlName="write_groups_id"
|
||||||
[formControl]="commentFieldForm.get('write_groups_id')"
|
|
||||||
[multiple]="true"
|
[multiple]="true"
|
||||||
listname="Groups with write permissions"
|
placeholder="Groups with write permissions"
|
||||||
[inputListValues]="groups"
|
[inputListValues]="groups"
|
||||||
></os-search-value-selector>
|
></os-search-value-selector>
|
||||||
|
</mat-form-field>
|
||||||
</p>
|
</p>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
@ -34,13 +34,14 @@
|
|||||||
</os-sorting-list>
|
</os-sorting-list>
|
||||||
|
|
||||||
<form *ngIf="users && users.value.length > 0" [formGroup]="addSubmitterForm">
|
<form *ngIf="users && users.value.length > 0" [formGroup]="addSubmitterForm">
|
||||||
|
<mat-form-field>
|
||||||
<os-search-value-selector
|
<os-search-value-selector
|
||||||
class="search-users"
|
class="search-users"
|
||||||
ngDefaultControl
|
formControlName="userId"
|
||||||
[formControl]="addSubmitterForm.get('userId')"
|
placeholder="{{ 'Select or search new submitter ...' | translate }}"
|
||||||
listname="{{ 'Select or search new submitter ...' | translate }}"
|
|
||||||
[inputListValues]="users"
|
[inputListValues]="users"
|
||||||
></os-search-value-selector>
|
></os-search-value-selector>
|
||||||
|
</mat-form-field>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
|
@ -459,14 +459,15 @@
|
|||||||
|
|
||||||
<!-- motion polls -->
|
<!-- motion polls -->
|
||||||
<div *ngIf="!editMotion" class="spacer-top-20 spacer-bottom-20">
|
<div *ngIf="!editMotion" class="spacer-top-20 spacer-bottom-20">
|
||||||
<os-motion-poll *ngFor="let poll of motion.motion.polls; let i = index" [rawPoll]="poll" [pollIndex]="i">
|
|
||||||
</os-motion-poll>
|
|
||||||
<div class="create-poll-button" *ngIf="perms.isAllowed('createpoll', motion)">
|
<div class="create-poll-button" *ngIf="perms.isAllowed('createpoll', motion)">
|
||||||
<button mat-button (click)="createPoll()">
|
<button mat-button (click)="createPoll()">
|
||||||
<mat-icon class="main-nav-color">poll</mat-icon>
|
<mat-icon class="main-nav-color">poll</mat-icon>
|
||||||
<span translate>New vote</span>
|
<span translate>New vote</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
<mat-accordion>
|
||||||
|
<os-motion-poll-preview *ngFor="let poll of motion.polls" [poll]="poll"></os-motion-poll-preview>
|
||||||
|
</mat-accordion>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
@ -601,15 +602,16 @@
|
|||||||
|
|
||||||
<!-- Submitter -->
|
<!-- Submitter -->
|
||||||
<div *ngIf="newMotion" class="content-field">
|
<div *ngIf="newMotion" class="content-field">
|
||||||
<div *ngIf="perms.isAllowed('change_metadata', motion)">
|
<ng-container *ngIf="perms.isAllowed('change_metadata', motion)">
|
||||||
|
<mat-form-field>
|
||||||
<os-search-value-selector
|
<os-search-value-selector
|
||||||
ngDefaultControl
|
formControlName="submitters_id"
|
||||||
[formControl]="contentForm.get('submitters_id')"
|
|
||||||
[multiple]="true"
|
[multiple]="true"
|
||||||
listname="{{ 'Submitters' | translate }}"
|
placeholder="{{ 'Submitters' | translate }}"
|
||||||
[inputListValues]="submitterObserver"
|
[inputListValues]="submitterObserver"
|
||||||
></os-search-value-selector>
|
></os-search-value-selector>
|
||||||
</div>
|
</mat-form-field>
|
||||||
|
</ng-container>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-id-title">
|
<div class="form-id-title">
|
||||||
@ -783,13 +785,14 @@
|
|||||||
|
|
||||||
<!-- Category form -->
|
<!-- Category form -->
|
||||||
<div class="content-field" *ngIf="newMotion && categoryObserver.value.length > 0">
|
<div class="content-field" *ngIf="newMotion && categoryObserver.value.length > 0">
|
||||||
|
<mat-form-field>
|
||||||
<os-search-value-selector
|
<os-search-value-selector
|
||||||
ngDefaultControl
|
formControlName="category_id"
|
||||||
[formControl]="contentForm.get('category_id')"
|
|
||||||
[includeNone]="true"
|
[includeNone]="true"
|
||||||
listname="{{ 'Category' | translate }}"
|
placeholder="{{ 'Category' | translate }}"
|
||||||
[inputListValues]="categoryObserver"
|
[inputListValues]="categoryObserver"
|
||||||
></os-search-value-selector>
|
></os-search-value-selector>
|
||||||
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="extra-data">
|
<div class="extra-data">
|
||||||
@ -805,8 +808,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<div *osPerms="'motions.can_manage'; and: editMotion">
|
<div *osPerms="'motions.can_manage'; and: editMotion">
|
||||||
<os-attachment-control
|
<os-attachment-control
|
||||||
|
formControlName="attachments_id"
|
||||||
(errorHandler)="showUploadError($event)"
|
(errorHandler)="showUploadError($event)"
|
||||||
[controlName]="contentForm.get('attachments_id')"
|
|
||||||
></os-attachment-control>
|
></os-attachment-control>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -818,25 +821,27 @@
|
|||||||
<!-- Supporter form -->
|
<!-- Supporter form -->
|
||||||
<div class="content-field" *ngIf="editMotion && minSupporters">
|
<div class="content-field" *ngIf="editMotion && minSupporters">
|
||||||
<div *ngIf="perms.isAllowed('change_metadata', motion)">
|
<div *ngIf="perms.isAllowed('change_metadata', motion)">
|
||||||
|
<mat-form-field>
|
||||||
<os-search-value-selector
|
<os-search-value-selector
|
||||||
ngDefaultControl
|
formControlName="supporters_id"
|
||||||
[formControl]="contentForm.get('supporters_id')"
|
|
||||||
[multiple]="true"
|
[multiple]="true"
|
||||||
listname="{{ 'Supporters' | translate }}"
|
placeholder="{{ 'Supporters' | translate }}"
|
||||||
[inputListValues]="supporterObserver"
|
[inputListValues]="supporterObserver"
|
||||||
></os-search-value-selector>
|
></os-search-value-selector>
|
||||||
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Workflow -->
|
<!-- Workflow -->
|
||||||
<div class="content-field" *ngIf="editMotion && workflowObserver.value.length > 1">
|
<div class="content-field" *ngIf="editMotion && workflowObserver.value.length > 1">
|
||||||
<div *ngIf="perms.isAllowed('change_metadata', motion)">
|
<div *ngIf="perms.isAllowed('change_metadata', motion)">
|
||||||
|
<mat-form-field>
|
||||||
<os-search-value-selector
|
<os-search-value-selector
|
||||||
ngDefaultControl
|
formControlName="workflow_id"
|
||||||
[formControl]="contentForm.get('workflow_id')"
|
placeholder="{{ 'Workflow' | translate }}"
|
||||||
listname="{{ 'Workflow' | translate }}"
|
|
||||||
[inputListValues]="workflowObserver"
|
[inputListValues]="workflowObserver"
|
||||||
></os-search-value-selector>
|
></os-search-value-selector>
|
||||||
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@ import { MotionCommentsComponent } from '../motion-comments/motion-comments.comp
|
|||||||
import { MotionDetailDiffComponent } from '../motion-detail-diff/motion-detail-diff.component';
|
import { MotionDetailDiffComponent } from '../motion-detail-diff/motion-detail-diff.component';
|
||||||
import { MotionDetailOriginalChangeRecommendationsComponent } from '../motion-detail-original-change-recommendations/motion-detail-original-change-recommendations.component';
|
import { MotionDetailOriginalChangeRecommendationsComponent } from '../motion-detail-original-change-recommendations/motion-detail-original-change-recommendations.component';
|
||||||
import { MotionDetailComponent } from './motion-detail.component';
|
import { MotionDetailComponent } from './motion-detail.component';
|
||||||
|
import { MotionPollPreviewComponent } from '../motion-poll/motion-poll-preview/motion-poll-preview.component';
|
||||||
import { MotionPollComponent } from '../motion-poll/motion-poll.component';
|
import { MotionPollComponent } from '../motion-poll/motion-poll.component';
|
||||||
import { PersonalNoteComponent } from '../personal-note/personal-note.component';
|
import { PersonalNoteComponent } from '../personal-note/personal-note.component';
|
||||||
|
|
||||||
@ -24,7 +25,8 @@ describe('MotionDetailComponent', () => {
|
|||||||
ManageSubmittersComponent,
|
ManageSubmittersComponent,
|
||||||
MotionPollComponent,
|
MotionPollComponent,
|
||||||
MotionDetailOriginalChangeRecommendationsComponent,
|
MotionDetailOriginalChangeRecommendationsComponent,
|
||||||
MotionDetailDiffComponent
|
MotionDetailDiffComponent,
|
||||||
|
MotionPollPreviewComponent
|
||||||
]
|
]
|
||||||
}).compileComponents();
|
}).compileComponents();
|
||||||
}));
|
}));
|
||||||
|
@ -1382,9 +1382,7 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit,
|
|||||||
* Handler for creating a poll
|
* Handler for creating a poll
|
||||||
*/
|
*/
|
||||||
public createPoll(): void {
|
public createPoll(): void {
|
||||||
// TODO
|
this.router.navigate(['motions', 'polls', 'new'], { queryParams: { parent: this.motion.id || null } });
|
||||||
// this.repo.createPoll(<any>{}).catch(this.raiseError);
|
|
||||||
throw new Error('TODO');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -0,0 +1,56 @@
|
|||||||
|
<mat-expansion-panel>
|
||||||
|
<mat-expansion-panel-header *ngIf="poll">{{ poll.title }}</mat-expansion-panel-header>
|
||||||
|
<ng-template matExpansionPanelContent>
|
||||||
|
<div class="poll-content">
|
||||||
|
<div>{{ 'Current state' | translate }}: {{ poll.stateVerbose }}</div>
|
||||||
|
<div *ngIf="poll.groups">{{ 'Groups' | translate }}: {{ poll.groups }}</div>
|
||||||
|
<div>{{ 'Method' | translate }}: {{ poll.pollmethodVerbose }}</div>
|
||||||
|
<div>{{ 'Type' | translate }}: {{ poll.typeVerbose }}</div>
|
||||||
|
</div>
|
||||||
|
<mat-divider></mat-divider>
|
||||||
|
<div class="poll-footer">
|
||||||
|
<button *ngIf="poll.type === pollTypes.Analog" mat-icon-button (click)="enterAnalogVotes()">
|
||||||
|
<mat-icon class="small-icon" matTooltip="{{ 'Enter votes' | translate }}">check</mat-icon><!-- TODO: other icon-->
|
||||||
|
</button>
|
||||||
|
<button mat-icon-button (click)="openPoll()">
|
||||||
|
<mat-icon class="small-icon" matTooltip="{{ 'View' | translate }}">pageview</mat-icon>
|
||||||
|
</button>
|
||||||
|
<button mat-icon-button (click)="editPoll()">
|
||||||
|
<mat-icon class="small-icon" matTooltip="{{ 'Edit' | translate }}">edit</mat-icon>
|
||||||
|
</button>
|
||||||
|
<button mat-icon-button (click)="deletePoll()">
|
||||||
|
<mat-icon class="small-icon" matTooltip="{{ 'Delete' | translate }}">delete</mat-icon>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
</mat-expansion-panel>
|
||||||
|
|
||||||
|
<!-- Template for dialog for entering votes -->
|
||||||
|
<ng-template #enterVotesDialog>
|
||||||
|
<h1 mat-dialog-title translate>Enter votes</h1>
|
||||||
|
<div class="os-form-card-mobile" mat-dialog-content>
|
||||||
|
<form [formGroup]="statuteParagraphForm" (keydown)="onKeyDown($event)">
|
||||||
|
<p>
|
||||||
|
<mat-form-field>
|
||||||
|
<input formControlName="title" matInput placeholder="{{ 'Title' | translate }}" required />
|
||||||
|
<mat-hint *ngIf="!statuteParagraphForm.controls.title.valid">
|
||||||
|
<span translate>Required</span>
|
||||||
|
</mat-hint>
|
||||||
|
</mat-form-field>
|
||||||
|
</p>
|
||||||
|
<span>
|
||||||
|
<!-- The HTML Editor -->
|
||||||
|
<h4 translate>Statute paragraph</h4>
|
||||||
|
<editor formControlName="text" [init]="tinyMceSettings"></editor>
|
||||||
|
</span>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div mat-dialog-actions>
|
||||||
|
<button mat-button [mat-dialog-close]="true" [disabled]="!statuteParagraphForm.valid">
|
||||||
|
<span translate>Save</span>
|
||||||
|
</button>
|
||||||
|
<button mat-button [mat-dialog-close]="false">
|
||||||
|
<span translate>Cancel</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
@ -0,0 +1,7 @@
|
|||||||
|
.poll-content {
|
||||||
|
padding-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.poll-footer {
|
||||||
|
text-align: end;
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { E2EImportsModule } from 'e2e-imports.module';
|
||||||
|
|
||||||
|
import { MotionPollPreviewComponent } from './motion-poll-preview.component';
|
||||||
|
|
||||||
|
describe('MotionPollPreviewComponent', () => {
|
||||||
|
let component: MotionPollPreviewComponent;
|
||||||
|
let fixture: ComponentFixture<MotionPollPreviewComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [E2EImportsModule],
|
||||||
|
declarations: [MotionPollPreviewComponent]
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(MotionPollPreviewComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,56 @@
|
|||||||
|
import { Component, Input } from '@angular/core';
|
||||||
|
import { MatSnackBar } from '@angular/material';
|
||||||
|
import { Title } from '@angular/platform-browser';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
|
||||||
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
|
||||||
|
import { MotionPollRepositoryService } from 'app/core/repositories/motions/motion-poll-repository.service';
|
||||||
|
import { PromptService } from 'app/core/ui-services/prompt.service';
|
||||||
|
import { PollType } from 'app/shared/models/poll/base-poll';
|
||||||
|
import { BaseViewComponent } from 'app/site/base/base-view';
|
||||||
|
import { ViewMotionPoll } from 'app/site/motions/models/view-motion-poll';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'os-motion-poll-preview',
|
||||||
|
templateUrl: './motion-poll-preview.component.html',
|
||||||
|
styleUrls: ['./motion-poll-preview.component.scss']
|
||||||
|
})
|
||||||
|
export class MotionPollPreviewComponent extends BaseViewComponent {
|
||||||
|
@Input()
|
||||||
|
public poll: ViewMotionPoll;
|
||||||
|
|
||||||
|
public pollTypes = PollType;
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
title: Title,
|
||||||
|
protected translate: TranslateService,
|
||||||
|
matSnackbar: MatSnackBar,
|
||||||
|
private repo: MotionPollRepositoryService,
|
||||||
|
private promptDialog: PromptService,
|
||||||
|
private router: Router
|
||||||
|
) {
|
||||||
|
super(title, translate, matSnackbar);
|
||||||
|
}
|
||||||
|
|
||||||
|
public openPoll(): void {
|
||||||
|
this.router.navigate(['motions', 'polls', this.poll.id]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public editPoll(): void {
|
||||||
|
this.router.navigate(['motions', 'polls', this.poll.id], { queryParams: { edit: true } });
|
||||||
|
}
|
||||||
|
|
||||||
|
public async deletePoll(): Promise<void> {
|
||||||
|
const title = 'Delete poll';
|
||||||
|
const text = 'Do you really want to delete the selected poll?';
|
||||||
|
|
||||||
|
if (await this.promptDialog.open(title, text)) {
|
||||||
|
await this.repo.delete(this.poll);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enterAnalogVotes(): void {
|
||||||
|
throw new Error('TODO');
|
||||||
|
}
|
||||||
|
}
|
@ -11,6 +11,7 @@ import { MotionDetailOriginalChangeRecommendationsComponent } from './components
|
|||||||
import { MotionDetailRoutingModule } from './motion-detail-routing.module';
|
import { MotionDetailRoutingModule } from './motion-detail-routing.module';
|
||||||
import { MotionDetailComponent } from './components/motion-detail/motion-detail.component';
|
import { MotionDetailComponent } from './components/motion-detail/motion-detail.component';
|
||||||
import { MotionPollDialogComponent } from './components/motion-poll/motion-poll-dialog.component';
|
import { MotionPollDialogComponent } from './components/motion-poll/motion-poll-dialog.component';
|
||||||
|
import { MotionPollPreviewComponent } from './components/motion-poll/motion-poll-preview/motion-poll-preview.component';
|
||||||
import { MotionPollComponent } from './components/motion-poll/motion-poll.component';
|
import { MotionPollComponent } from './components/motion-poll/motion-poll.component';
|
||||||
import { MotionTitleChangeRecommendationDialogComponent } from './components/motion-title-change-recommendation-dialog/motion-title-change-recommendation-dialog.component';
|
import { MotionTitleChangeRecommendationDialogComponent } from './components/motion-title-change-recommendation-dialog/motion-title-change-recommendation-dialog.component';
|
||||||
import { PersonalNoteComponent } from './components/personal-note/personal-note.component';
|
import { PersonalNoteComponent } from './components/personal-note/personal-note.component';
|
||||||
@ -24,6 +25,7 @@ import { PersonalNoteComponent } from './components/personal-note/personal-note.
|
|||||||
PersonalNoteComponent,
|
PersonalNoteComponent,
|
||||||
ManageSubmittersComponent,
|
ManageSubmittersComponent,
|
||||||
MotionPollComponent,
|
MotionPollComponent,
|
||||||
|
MotionPollPreviewComponent,
|
||||||
MotionPollDialogComponent,
|
MotionPollDialogComponent,
|
||||||
MotionDetailDiffComponent,
|
MotionDetailDiffComponent,
|
||||||
MotionDetailOriginalChangeRecommendationsComponent,
|
MotionDetailOriginalChangeRecommendationsComponent,
|
||||||
|
@ -0,0 +1,92 @@
|
|||||||
|
<os-head-bar
|
||||||
|
[goBack]="true"
|
||||||
|
[nav]="false"
|
||||||
|
[hasMainButton]="true"
|
||||||
|
[editMode]="isEditingPoll"
|
||||||
|
[mainButtonIcon]="'edit'"
|
||||||
|
[mainActionTooltip]="'Edit' | translate"
|
||||||
|
[isSaveButtonEnabled]="contentForm.valid"
|
||||||
|
(mainEvent)="editPoll()"
|
||||||
|
(saveEvent)="savePoll()"
|
||||||
|
(cancelEditEvent)="backToView()"
|
||||||
|
>
|
||||||
|
<div class="title-slot">
|
||||||
|
<h2 *ngIf="isNewPoll" translate>New vote</h2>
|
||||||
|
<h2 *ngIf="!isNewPoll && !!poll">{{ poll.title }}</h2>
|
||||||
|
</div>
|
||||||
|
</os-head-bar>
|
||||||
|
|
||||||
|
<mat-card [ngClass]="isEditingPoll ? 'os-form-card' : 'os-card'">
|
||||||
|
<ng-container *ngIf="!isEditingPoll" [ngTemplateOutlet]="viewTemplate"></ng-container>
|
||||||
|
<ng-container *ngIf="isEditingPoll" [ngTemplateOutlet]="formTemplate"></ng-container>
|
||||||
|
</mat-card>
|
||||||
|
|
||||||
|
<ng-template #viewTemplate>
|
||||||
|
<ng-container *ngIf="poll">
|
||||||
|
<h1>{{ poll.title }}</h1>
|
||||||
|
<mat-divider></mat-divider>
|
||||||
|
<div class="poll-content">
|
||||||
|
<div>{{ 'Current state' | translate }}: {{ poll.stateVerbose | translate }}</div>
|
||||||
|
<div *ngIf="poll.groups">
|
||||||
|
{{ 'Groups' | translate }}:
|
||||||
|
<span *ngFor="let group of poll.groups">{{ group.getTitle() | translate }}</span>
|
||||||
|
</div>
|
||||||
|
<div>{{ 'Poll type' | translate }}: {{ poll.typeVerbose | translate }}</div>
|
||||||
|
<div>{{ 'Poll method' | translate }}: {{ poll.pollmethodVerbose | translate }}</div>
|
||||||
|
<div>{{ 'Majority method' | translate }}: {{ poll.majorityMethodVerbose | translate }}</div>
|
||||||
|
<div>{{ '100% base' | translate }}: {{ poll.percentBaseVerbose | translate }}</div>
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
<ng-template #formTemplate>
|
||||||
|
<form [formGroup]="contentForm">
|
||||||
|
<mat-form-field>
|
||||||
|
<input matInput formControlName="title" [placeholder]="'Title' | translate" required />
|
||||||
|
<mat-error translate>A title is required</mat-error>
|
||||||
|
</mat-form-field>
|
||||||
|
<mat-form-field>
|
||||||
|
<mat-select [placeholder]="'Poll type' | translate" formControlName="type" required>
|
||||||
|
<mat-option *ngFor="let option of pollTypes | keyvalue" [value]="option.key">{{
|
||||||
|
option.value | translate
|
||||||
|
}}</mat-option>
|
||||||
|
</mat-select>
|
||||||
|
<mat-error translate>This field is required</mat-error>
|
||||||
|
</mat-form-field>
|
||||||
|
<mat-form-field *ngIf="contentForm.controls.type.value && contentForm.controls.type.value != 'analog'">
|
||||||
|
<os-search-value-selector
|
||||||
|
formControlName="groups_id"
|
||||||
|
[multiple]="true"
|
||||||
|
[includeNone]="false"
|
||||||
|
[placeholder]="'Entitled to vote' | translate"
|
||||||
|
[inputListValues]="groupObservable"
|
||||||
|
></os-search-value-selector>
|
||||||
|
</mat-form-field>
|
||||||
|
<mat-form-field>
|
||||||
|
<mat-select [placeholder]="'Poll method' | translate" formControlName="pollmethod" required>
|
||||||
|
<mat-option *ngFor="let option of pollMethods | keyvalue" [value]="option.key">{{
|
||||||
|
option.value
|
||||||
|
}}</mat-option>
|
||||||
|
</mat-select>
|
||||||
|
<mat-error translate>This field is required</mat-error>
|
||||||
|
</mat-form-field>
|
||||||
|
<mat-form-field>
|
||||||
|
<mat-select
|
||||||
|
placeholder="{{ '100% base' | translate }}"
|
||||||
|
formControlName="onehundred_percent_base"
|
||||||
|
required
|
||||||
|
>
|
||||||
|
<mat-option *ngFor="let option of percentBases | keyvalue" [value]="option.key">{{
|
||||||
|
option.value | translate
|
||||||
|
}}</mat-option>
|
||||||
|
</mat-select>
|
||||||
|
</mat-form-field>
|
||||||
|
<mat-form-field>
|
||||||
|
<mat-select placeholder="{{ 'Majority' | translate }}" formControlName="majority_method" required>
|
||||||
|
<mat-option *ngFor="let option of majorityMethods | keyvalue" [value]="option.key">{{
|
||||||
|
option.value | translate
|
||||||
|
}}</mat-option>
|
||||||
|
</mat-select>
|
||||||
|
</mat-form-field>
|
||||||
|
</form>
|
||||||
|
</ng-template>
|
@ -0,0 +1,3 @@
|
|||||||
|
.poll-content {
|
||||||
|
padding-top: 10px;
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { E2EImportsModule } from 'e2e-imports.module';
|
||||||
|
|
||||||
|
import { MotionPollDetailComponent } from './motion-poll-detail.component';
|
||||||
|
|
||||||
|
describe('MotionPollDetailComponent', () => {
|
||||||
|
let component: MotionPollDetailComponent;
|
||||||
|
let fixture: ComponentFixture<MotionPollDetailComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [E2EImportsModule],
|
||||||
|
declarations: [MotionPollDetailComponent]
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(MotionPollDetailComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,152 @@
|
|||||||
|
import { Location } from '@angular/common';
|
||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||||
|
import { MatSnackBar } from '@angular/material';
|
||||||
|
import { Title } from '@angular/platform-browser';
|
||||||
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
|
|
||||||
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
|
||||||
|
import { MotionPollRepositoryService } from 'app/core/repositories/motions/motion-poll-repository.service';
|
||||||
|
import { GroupRepositoryService } from 'app/core/repositories/users/group-repository.service';
|
||||||
|
import { MotionPoll, MotionPollMethodsVerbose } from 'app/shared/models/motions/motion-poll';
|
||||||
|
import {
|
||||||
|
MajorityMethodVerbose,
|
||||||
|
PercentBaseVerbose,
|
||||||
|
PollStateVerbose,
|
||||||
|
PollTypeVerbose
|
||||||
|
} from 'app/shared/models/poll/base-poll';
|
||||||
|
import { BaseViewComponent } from 'app/site/base/base-view';
|
||||||
|
import { ViewMotionPoll } from 'app/site/motions/models/view-motion-poll';
|
||||||
|
import { ViewGroup } from 'app/site/users/models/view-group';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'os-motion-poll-detail',
|
||||||
|
templateUrl: './motion-poll-detail.component.html',
|
||||||
|
styleUrls: ['./motion-poll-detail.component.scss']
|
||||||
|
})
|
||||||
|
export class MotionPollDetailComponent extends BaseViewComponent implements OnInit {
|
||||||
|
private pollId: number;
|
||||||
|
|
||||||
|
public pollStates = PollStateVerbose;
|
||||||
|
public pollMethods = MotionPollMethodsVerbose;
|
||||||
|
public pollTypes = PollTypeVerbose;
|
||||||
|
public percentBases = PercentBaseVerbose;
|
||||||
|
public majorityMethods = MajorityMethodVerbose;
|
||||||
|
|
||||||
|
public userGroups: ViewGroup[] = [];
|
||||||
|
|
||||||
|
public groupObservable: Observable<ViewGroup[]> = null;
|
||||||
|
|
||||||
|
public isNewPoll = false;
|
||||||
|
|
||||||
|
public poll: ViewMotionPoll = null;
|
||||||
|
|
||||||
|
public motionId: number;
|
||||||
|
|
||||||
|
public isEditingPoll = false;
|
||||||
|
|
||||||
|
public contentForm: FormGroup;
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
title: Title,
|
||||||
|
protected translate: TranslateService,
|
||||||
|
matSnackbar: MatSnackBar,
|
||||||
|
private repo: MotionPollRepositoryService,
|
||||||
|
private route: ActivatedRoute,
|
||||||
|
private router: Router,
|
||||||
|
private fb: FormBuilder,
|
||||||
|
private groupRepo: GroupRepositoryService,
|
||||||
|
private location: Location
|
||||||
|
) {
|
||||||
|
super(title, translate, matSnackbar);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ngOnInit(): void {
|
||||||
|
this.findComponentById();
|
||||||
|
this.createPoll();
|
||||||
|
|
||||||
|
this.groupObservable = this.groupRepo.getViewModelListObservable();
|
||||||
|
this.subscriptions.push(
|
||||||
|
this.groupRepo.getViewModelListObservable().subscribe(groups => (this.userGroups = groups))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public savePoll(): void {
|
||||||
|
const pollValues = this.contentForm.value;
|
||||||
|
const poll: MotionPoll = this.isNewPoll ? new MotionPoll() : this.poll.poll;
|
||||||
|
Object.keys(pollValues).forEach(key => (poll[key] = pollValues[key]));
|
||||||
|
if (this.isNewPoll) {
|
||||||
|
poll.motion_id = this.motionId;
|
||||||
|
this.repo.create(poll).then(success => {
|
||||||
|
if (success && success.id) {
|
||||||
|
this.pollId = success.id;
|
||||||
|
this.router.navigate(['motions', 'polls', this.pollId]);
|
||||||
|
}
|
||||||
|
}, this.raiseError);
|
||||||
|
} else {
|
||||||
|
this.repo.update(pollValues, this.poll).then(() => (this.isEditingPoll = false), this.raiseError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public editPoll(): void {
|
||||||
|
this.isEditingPoll = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public backToView(): void {
|
||||||
|
if (this.pollId) {
|
||||||
|
this.isEditingPoll = false;
|
||||||
|
} else {
|
||||||
|
// TODO
|
||||||
|
this.location.back();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private findComponentById(): void {
|
||||||
|
const params = this.route.snapshot.params;
|
||||||
|
const queryParams = this.route.snapshot.queryParams;
|
||||||
|
if (params && params.id) {
|
||||||
|
this.pollId = +params.id;
|
||||||
|
this.subscriptions.push(
|
||||||
|
this.repo.getViewModelObservable(this.pollId).subscribe(poll => {
|
||||||
|
if (poll) {
|
||||||
|
this.poll = poll;
|
||||||
|
this.updateForm();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
this.isNewPoll = true;
|
||||||
|
this.isEditingPoll = true;
|
||||||
|
if (queryParams && queryParams.parent) {
|
||||||
|
this.motionId = +queryParams.parent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (queryParams && queryParams.edit) {
|
||||||
|
this.isEditingPoll = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private createPoll(): void {
|
||||||
|
this.contentForm = this.fb.group({
|
||||||
|
title: ['', Validators.required],
|
||||||
|
type: ['', Validators.required],
|
||||||
|
pollmethod: ['', Validators.required],
|
||||||
|
onehundred_percent_base: ['', Validators.required],
|
||||||
|
majority_method: ['', Validators.required],
|
||||||
|
groups_id: [[]]
|
||||||
|
});
|
||||||
|
if (this.poll) {
|
||||||
|
this.updateForm();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateForm(): void {
|
||||||
|
if (this.contentForm) {
|
||||||
|
Object.keys(this.contentForm.controls).forEach(key => {
|
||||||
|
this.contentForm.get(key).setValue(this.poll[key]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,36 @@
|
|||||||
|
<os-head-bar>
|
||||||
|
<div class="title-slot" translate>Motions poll list</div>
|
||||||
|
|
||||||
|
<!-- Menu -->
|
||||||
|
<div class="menu-slot">
|
||||||
|
<button type="button" mat-icon-button [matMenuTriggerFor]="pollMenu"><mat-icon>more_vert</mat-icon></button>
|
||||||
|
</div>
|
||||||
|
</os-head-bar>
|
||||||
|
|
||||||
|
<os-list-view-table
|
||||||
|
[repo]="repo"
|
||||||
|
[vScrollFixed]="64"
|
||||||
|
[columns]="tableColumnDefinition"
|
||||||
|
[listStorageKey]="'motion-polls'"
|
||||||
|
>
|
||||||
|
<div *pblNgridCellDef="'title'; row as poll; rowContext as context" class="cell-slot fill">
|
||||||
|
<a
|
||||||
|
class="detail-link"
|
||||||
|
(click)="saveScrollIndex('motion-polls', rowContext.identity)"
|
||||||
|
[routerLink]="poll.id"
|
||||||
|
*ngIf="!isMultiSelect"
|
||||||
|
></a>
|
||||||
|
<span>{{ poll.title }}</span>
|
||||||
|
</div>
|
||||||
|
<div *pblNgridCellDef="'state'; row as poll; rowContext as context" class="cell-slot fill">
|
||||||
|
<span>{{ poll.stateVerbose }}</span>
|
||||||
|
</div>
|
||||||
|
</os-list-view-table>
|
||||||
|
|
||||||
|
<mat-menu #pollMenu="matMenu">
|
||||||
|
<!-- Settings -->
|
||||||
|
<button mat-menu-item *osPerms="'core.can_manage_config'" routerLink="/settings/polls">
|
||||||
|
<mat-icon>settings</mat-icon>
|
||||||
|
<span translate>Settings</span>
|
||||||
|
</button>
|
||||||
|
</mat-menu>
|
@ -0,0 +1,27 @@
|
|||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { E2EImportsModule } from 'e2e-imports.module';
|
||||||
|
|
||||||
|
import { MotionPollListComponent } from './motion-poll-list.component';
|
||||||
|
|
||||||
|
describe('MotionPollListComponent', () => {
|
||||||
|
let component: MotionPollListComponent;
|
||||||
|
let fixture: ComponentFixture<MotionPollListComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [E2EImportsModule],
|
||||||
|
declarations: [MotionPollListComponent]
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(MotionPollListComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,45 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { MatSnackBar } from '@angular/material';
|
||||||
|
import { Title } from '@angular/platform-browser';
|
||||||
|
|
||||||
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
import { PblColumnDefinition } from '@pebula/ngrid';
|
||||||
|
|
||||||
|
import { StorageService } from 'app/core/core-services/storage.service';
|
||||||
|
import { MotionPollRepositoryService } from 'app/core/repositories/motions/motion-poll-repository.service';
|
||||||
|
import { BaseListViewComponent } from 'app/site/base/base-list-view';
|
||||||
|
import { ViewMotionPoll } from 'app/site/motions/models/view-motion-poll';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'os-motion-poll-list',
|
||||||
|
templateUrl: './motion-poll-list.component.html',
|
||||||
|
styleUrls: ['./motion-poll-list.component.scss']
|
||||||
|
})
|
||||||
|
export class MotionPollListComponent extends BaseListViewComponent<ViewMotionPoll> implements OnInit {
|
||||||
|
public tableColumnDefinition: PblColumnDefinition[] = [
|
||||||
|
{
|
||||||
|
prop: 'title',
|
||||||
|
width: 'auto'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'state',
|
||||||
|
width: 'auto'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
public polls: ViewMotionPoll[] = [];
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
title: Title,
|
||||||
|
protected translate: TranslateService,
|
||||||
|
matSnackbar: MatSnackBar,
|
||||||
|
storage: StorageService,
|
||||||
|
public repo: MotionPollRepositoryService
|
||||||
|
) {
|
||||||
|
super(title, translate, matSnackbar, storage);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ngOnInit(): void {
|
||||||
|
this.subscriptions.push(this.repo.getViewModelListObservable().subscribe(polls => (this.polls = polls)));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { RouterModule, Routes } from '@angular/router';
|
||||||
|
|
||||||
|
import { MotionPollDetailComponent } from './motion-poll-detail/motion-poll-detail.component';
|
||||||
|
import { MotionPollListComponent } from './motion-poll-list/motion-poll-list.component';
|
||||||
|
|
||||||
|
const routes: Routes = [
|
||||||
|
{ path: '', component: MotionPollListComponent, pathMatch: 'full' },
|
||||||
|
{ path: 'new', component: MotionPollDetailComponent },
|
||||||
|
{ path: ':id', component: MotionPollDetailComponent }
|
||||||
|
];
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [RouterModule.forChild(routes)],
|
||||||
|
exports: [RouterModule]
|
||||||
|
})
|
||||||
|
export class MotionPollRoutingModule {}
|
@ -0,0 +1,13 @@
|
|||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
|
||||||
|
import { SharedModule } from 'app/shared/shared.module';
|
||||||
|
import { MotionPollDetailComponent } from './motion-poll-detail/motion-poll-detail.component';
|
||||||
|
import { MotionPollListComponent } from './motion-poll-list/motion-poll-list.component';
|
||||||
|
import { MotionPollRoutingModule } from './motion-poll-routing.module';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [MotionPollDetailComponent, MotionPollListComponent],
|
||||||
|
imports: [CommonModule, SharedModule, MotionPollRoutingModule]
|
||||||
|
})
|
||||||
|
export class MotionPollModule {}
|
@ -62,6 +62,11 @@ const routes: Routes = [
|
|||||||
loadChildren: () => import('./modules/amendment-list/amendment-list.module').then(m => m.AmendmentListModule),
|
loadChildren: () => import('./modules/amendment-list/amendment-list.module').then(m => m.AmendmentListModule),
|
||||||
data: { basePerm: 'motions.can_see' }
|
data: { basePerm: 'motions.can_see' }
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'polls',
|
||||||
|
loadChildren: () => import('./modules/motion-poll/motion-poll.module').then(m => m.MotionPollModule),
|
||||||
|
data: { basePerm: 'motions.can_manage_polls' }
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: ':id',
|
path: ':id',
|
||||||
loadChildren: () => import('./modules/motion-detail/motion-detail.module').then(m => m.MotionDetailModule),
|
loadChildren: () => import('./modules/motion-detail/motion-detail.module').then(m => m.MotionDetailModule),
|
||||||
|
@ -70,7 +70,7 @@
|
|||||||
|
|
||||||
<!-- Attachments -->
|
<!-- Attachments -->
|
||||||
<os-attachment-control
|
<os-attachment-control
|
||||||
[controlName]="topicForm.get('attachments_id')"
|
formControlName="attachments_id"
|
||||||
(errorHandler)="raiseError($event)"
|
(errorHandler)="raiseError($event)"
|
||||||
></os-attachment-control>
|
></os-attachment-control>
|
||||||
|
|
||||||
@ -88,13 +88,14 @@
|
|||||||
|
|
||||||
<!-- Parent item -->
|
<!-- Parent item -->
|
||||||
<div>
|
<div>
|
||||||
|
<mat-form-field>
|
||||||
<os-search-value-selector
|
<os-search-value-selector
|
||||||
ngDefaultControl
|
formControlName="agenda_parent_id"
|
||||||
[formControl]="topicForm.get('agenda_parent_id')"
|
|
||||||
[includeNone]="true"
|
[includeNone]="true"
|
||||||
listname="{{ 'Parent agenda item' | translate }}"
|
placeholder="{{ 'Parent agenda item' | translate }}"
|
||||||
[inputListValues]="itemObserver"
|
[inputListValues]="itemObserver"
|
||||||
></os-search-value-selector>
|
></os-search-value-selector>
|
||||||
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
@ -75,22 +75,12 @@
|
|||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
<!-- First name -->
|
<!-- First name -->
|
||||||
<mat-form-field class="form37 distance force-min-with">
|
<mat-form-field class="form37 distance force-min-with">
|
||||||
<input
|
<input type="text" matInput placeholder="{{ 'Given name' | translate }}" formControlName="first_name" />
|
||||||
type="text"
|
|
||||||
matInput
|
|
||||||
placeholder="{{ 'Given name' | translate }}"
|
|
||||||
formControlName="first_name"
|
|
||||||
/>
|
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
|
||||||
<!-- Last name -->
|
<!-- Last name -->
|
||||||
<mat-form-field class="form37 force-min-with">
|
<mat-form-field class="form37 force-min-with">
|
||||||
<input
|
<input type="text" matInput placeholder="{{ 'Surname' | translate }}" formControlName="last_name" />
|
||||||
type="text"
|
|
||||||
matInput
|
|
||||||
placeholder="{{ 'Surname' | translate }}"
|
|
||||||
formControlName="last_name"
|
|
||||||
/>
|
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -142,13 +132,14 @@
|
|||||||
|
|
||||||
<div>
|
<div>
|
||||||
<!-- Groups -->
|
<!-- Groups -->
|
||||||
|
<mat-form-field>
|
||||||
<os-search-value-selector
|
<os-search-value-selector
|
||||||
ngDefaultControl
|
formControlName="groups_id"
|
||||||
[formControl]="personalInfoForm.get('groups_id')"
|
|
||||||
[multiple]="true"
|
[multiple]="true"
|
||||||
listname="{{ 'Groups' | translate }}"
|
placeholder="{{ 'Groups' | translate }}"
|
||||||
[inputListValues]="groups"
|
[inputListValues]="groups"
|
||||||
></os-search-value-selector>
|
></os-search-value-selector>
|
||||||
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div *ngIf="isAllowed('manage')">
|
<div *ngIf="isAllowed('manage')">
|
||||||
@ -184,23 +175,14 @@
|
|||||||
<div *ngIf="isAllowed('seePersonal')">
|
<div *ngIf="isAllowed('seePersonal')">
|
||||||
<!-- username -->
|
<!-- username -->
|
||||||
<mat-form-field>
|
<mat-form-field>
|
||||||
<input
|
<input type="text" matInput placeholder="{{ 'Username' | translate }}" formControlName="username" />
|
||||||
type="text"
|
|
||||||
matInput
|
|
||||||
placeholder="{{ 'Username' | translate }}"
|
|
||||||
formControlName="username"
|
|
||||||
/>
|
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div *ngIf="isAllowed('seeExtra')">
|
<div *ngIf="isAllowed('seeExtra')">
|
||||||
<!-- Comment -->
|
<!-- Comment -->
|
||||||
<mat-form-field>
|
<mat-form-field>
|
||||||
<input
|
<input matInput placeholder="{{ 'Comment' | translate }}" formControlName="comment" />
|
||||||
matInput
|
|
||||||
placeholder="{{ 'Comment' | translate }}"
|
|
||||||
formControlName="comment"
|
|
||||||
/>
|
|
||||||
<mat-hint translate>Only for internal notes.</mat-hint>
|
<mat-hint translate>Only for internal notes.</mat-hint>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
@ -245,7 +227,9 @@
|
|||||||
<span class="state-icons">
|
<span class="state-icons">
|
||||||
<span>{{ user.short_name }}</span>
|
<span>{{ user.short_name }}</span>
|
||||||
<mat-icon *ngIf="user.is_present" matTooltip="{{ 'Is present' | translate }}">check_box</mat-icon>
|
<mat-icon *ngIf="user.is_present" matTooltip="{{ 'Is present' | translate }}">check_box</mat-icon>
|
||||||
<mat-icon *ngIf="user.is_committee" matTooltip="{{ 'Is committee' | translate }}">account_balance</mat-icon>
|
<mat-icon *ngIf="user.is_committee" matTooltip="{{ 'Is committee' | translate }}"
|
||||||
|
>account_balance</mat-icon
|
||||||
|
>
|
||||||
<mat-icon *ngIf="!user.is_active && isAllowed('seeExtra')" matTooltip="{{ 'Inactive' | translate }}"
|
<mat-icon *ngIf="!user.is_active && isAllowed('seeExtra')" matTooltip="{{ 'Inactive' | translate }}"
|
||||||
>block</mat-icon
|
>block</mat-icon
|
||||||
>
|
>
|
||||||
|
@ -136,6 +136,15 @@
|
|||||||
right: 0;
|
right: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
color: mat-color($foreground, icon);
|
||||||
|
}
|
||||||
|
|
||||||
|
.small-icon {
|
||||||
|
@extend .icon;
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
/** Custom themes for NGrid. Could be an own file if it gets more */
|
/** Custom themes for NGrid. Could be an own file if it gets more */
|
||||||
.pbl-ngrid-container {
|
.pbl-ngrid-container {
|
||||||
background: mat-color($background, card);
|
background: mat-color($background, card);
|
||||||
|
Loading…
Reference in New Issue
Block a user