Polls for motions and assignments
- Adds charts to assignments - Creates base-classes for polls
This commit is contained in:
parent
96aa3b0084
commit
fff1f15b6c
@ -1,4 +1,5 @@
|
|||||||
<div *ngFor="let banner of banners"
|
<div
|
||||||
|
*ngFor="let banner of banners"
|
||||||
class="banner"
|
class="banner"
|
||||||
[ngClass]="(banner.type === 'history' ? 'history-mode-indicator' : '') + ' ' + (banner.class ? banner.class : '')"
|
[ngClass]="(banner.type === 'history' ? 'history-mode-indicator' : '') + ' ' + (banner.class ? banner.class : '')"
|
||||||
[ngSwitch]="banner.type"
|
[ngSwitch]="banner.type"
|
||||||
|
@ -4,12 +4,13 @@
|
|||||||
line-height: 20px;
|
line-height: 20px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
background-color: blue;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|
||||||
a {
|
a {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,11 @@
|
|||||||
|
@import '~@angular/material/theming';
|
||||||
|
|
||||||
|
/** Custom component theme. Only lives in a specific scope */
|
||||||
|
@mixin os-banner-style($theme) {
|
||||||
|
$primary: map-get($theme, primary);
|
||||||
|
|
||||||
|
/** style for the offline-banner */
|
||||||
|
.banner {
|
||||||
|
background: mat-color($primary, 900);
|
||||||
|
}
|
||||||
|
}
|
@ -1,23 +1,25 @@
|
|||||||
<div class="charts-wrapper">
|
<div class="charts-wrapper">
|
||||||
<canvas
|
<ng-container *ngIf="chartData.length || circleData.length">
|
||||||
*ngIf="type === 'bar' || type === 'line' || type === 'horizontalBar'"
|
<canvas
|
||||||
baseChart
|
*ngIf="type === 'bar' || type === 'stackedBar' || type === 'horizontalBar' || type === 'line'"
|
||||||
[datasets]="chartData"
|
baseChart
|
||||||
[labels]="labels"
|
[datasets]="chartData"
|
||||||
[legend]="showLegend"
|
[labels]="labels"
|
||||||
[options]="chartOptions"
|
[legend]="showLegend"
|
||||||
[chartType]="type"
|
[options]="chartOptions"
|
||||||
(chartClick)="select.emit($event)"
|
[chartType]="type"
|
||||||
(chartHover)="hover.emit($event)"
|
(chartClick)="select.emit($event)"
|
||||||
>
|
(chartHover)="hover.emit($event)"
|
||||||
</canvas>
|
>
|
||||||
<canvas
|
</canvas>
|
||||||
*ngIf="type === 'pie' || type === 'doughnut'"
|
<canvas
|
||||||
baseChart
|
*ngIf="type === 'pie' || type === 'doughnut'"
|
||||||
[data]="circleData"
|
baseChart
|
||||||
[labels]="circleLabels"
|
[data]="circleData"
|
||||||
[colors]="circleColors"
|
[labels]="circleLabels"
|
||||||
[chartType]="type"
|
[colors]="circleColors"
|
||||||
[legend]="showLegend"
|
[chartType]="type"
|
||||||
></canvas>
|
[legend]="showLegend"
|
||||||
|
></canvas>
|
||||||
|
</ng-container>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, Output } from '@angular/core';
|
||||||
import { MatSnackBar } from '@angular/material';
|
import { MatSnackBar } from '@angular/material';
|
||||||
import { Title } from '@angular/platform-browser';
|
import { Title } from '@angular/platform-browser';
|
||||||
|
|
||||||
@ -12,7 +12,7 @@ import { BaseViewComponent } from 'app/site/base/base-view';
|
|||||||
/**
|
/**
|
||||||
* The different supported chart-types.
|
* The different supported chart-types.
|
||||||
*/
|
*/
|
||||||
export type ChartType = 'line' | 'bar' | 'pie' | 'doughnut' | 'horizontalBar';
|
export type ChartType = 'line' | 'bar' | 'pie' | 'doughnut' | 'horizontalBar' | 'stackedBar';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Describes the events the chart is fired, when hovering or clicking on it.
|
* Describes the events the chart is fired, when hovering or clicking on it.
|
||||||
@ -30,6 +30,8 @@ export interface ChartDate {
|
|||||||
label: string;
|
label: string;
|
||||||
backgroundColor?: string;
|
backgroundColor?: string;
|
||||||
hoverBackgroundColor?: string;
|
hoverBackgroundColor?: string;
|
||||||
|
barThickness?: number;
|
||||||
|
maxBarThickness?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -37,6 +39,8 @@ export interface ChartDate {
|
|||||||
*/
|
*/
|
||||||
export type ChartData = ChartDate[];
|
export type ChartData = ChartDate[];
|
||||||
|
|
||||||
|
export type ChartLegendSize = 'small' | 'middle';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wrapper for the chart-library.
|
* Wrapper for the chart-library.
|
||||||
*
|
*
|
||||||
@ -45,7 +49,8 @@ export type ChartData = ChartDate[];
|
|||||||
@Component({
|
@Component({
|
||||||
selector: 'os-charts',
|
selector: 'os-charts',
|
||||||
templateUrl: './charts.component.html',
|
templateUrl: './charts.component.html',
|
||||||
styleUrls: ['./charts.component.scss']
|
styleUrls: ['./charts.component.scss'],
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush
|
||||||
})
|
})
|
||||||
export class ChartsComponent extends BaseViewComponent {
|
export class ChartsComponent extends BaseViewComponent {
|
||||||
/**
|
/**
|
||||||
@ -57,15 +62,22 @@ export class ChartsComponent extends BaseViewComponent {
|
|||||||
public set data(dataObservable: Observable<ChartData>) {
|
public set data(dataObservable: Observable<ChartData>) {
|
||||||
this.subscriptions.push(
|
this.subscriptions.push(
|
||||||
dataObservable.subscribe(data => {
|
dataObservable.subscribe(data => {
|
||||||
|
if (!data) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
data = data.flatMap((date: ChartDate) => ({ ...date, data: date.data.filter(value => value >= 0) }));
|
||||||
this.chartData = data;
|
this.chartData = data;
|
||||||
this.circleData = data.flatMap((date: ChartDate) => date.data);
|
this.circleData = data.flatMap((date: ChartDate) => date.data);
|
||||||
this.circleLabels = data.map(date => date.label);
|
this.circleLabels = data.map(date => date.label);
|
||||||
this.circleColors = [
|
const circleColors = [
|
||||||
{
|
{
|
||||||
backgroundColor: data.map(date => date.backgroundColor),
|
backgroundColor: data.map(date => date.backgroundColor).filter(color => !!color),
|
||||||
hoverBackgroundColor: data.map(date => date.hoverBackgroundColor)
|
hoverBackgroundColor: data.map(date => date.hoverBackgroundColor).filter(color => !!color)
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
this.circleColors = !!circleColors[0].backgroundColor.length ? circleColors : null;
|
||||||
|
this.checkChartType();
|
||||||
|
this.cd.detectChanges();
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -75,16 +87,20 @@ export class ChartsComponent extends BaseViewComponent {
|
|||||||
*/
|
*/
|
||||||
@Input()
|
@Input()
|
||||||
public set type(type: ChartType) {
|
public set type(type: ChartType) {
|
||||||
if (type === 'horizontalBar') {
|
this.checkChartType(type);
|
||||||
this.setupHorizontalBar();
|
this.cd.detectChanges();
|
||||||
}
|
|
||||||
this._type = type;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public get type(): ChartType {
|
public get type(): ChartType {
|
||||||
return this._type;
|
return this._type;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
public set chartLegendSize(size: ChartLegendSize) {
|
||||||
|
this._chartLegendSize = size;
|
||||||
|
this.setupChartLegendSize();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether to show the legend.
|
* Whether to show the legend.
|
||||||
*/
|
*/
|
||||||
@ -147,11 +163,12 @@ export class ChartsComponent extends BaseViewComponent {
|
|||||||
responsive: true,
|
responsive: true,
|
||||||
legend: {
|
legend: {
|
||||||
position: 'top',
|
position: 'top',
|
||||||
labels: {
|
labels: {}
|
||||||
fontSize: 14
|
},
|
||||||
}
|
scales: {
|
||||||
|
xAxes: [{ ticks: { beginAtZero: true } }],
|
||||||
|
yAxes: [{ ticks: { beginAtZero: true } }]
|
||||||
},
|
},
|
||||||
scales: { xAxes: [{}], yAxes: [{ ticks: { beginAtZero: true } }] },
|
|
||||||
plugins: {
|
plugins: {
|
||||||
datalabels: {
|
datalabels: {
|
||||||
anchor: 'end',
|
anchor: 'end',
|
||||||
@ -165,6 +182,8 @@ export class ChartsComponent extends BaseViewComponent {
|
|||||||
*/
|
*/
|
||||||
private _type: ChartType = 'bar';
|
private _type: ChartType = 'bar';
|
||||||
|
|
||||||
|
private _chartLegendSize: ChartLegendSize = 'middle';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor.
|
* Constructor.
|
||||||
*
|
*
|
||||||
@ -173,17 +192,63 @@ export class ChartsComponent extends BaseViewComponent {
|
|||||||
* @param matSnackbar
|
* @param matSnackbar
|
||||||
* @param cd
|
* @param cd
|
||||||
*/
|
*/
|
||||||
public constructor(title: Title, protected translate: TranslateService, matSnackbar: MatSnackBar) {
|
public constructor(
|
||||||
|
title: Title,
|
||||||
|
protected translate: TranslateService,
|
||||||
|
matSnackbar: MatSnackBar,
|
||||||
|
private cd: ChangeDetectorRef
|
||||||
|
) {
|
||||||
super(title, translate, matSnackbar);
|
super(title, translate, matSnackbar);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Changes the chart-options, if the `horizontalBar` is used.
|
* Changes the chart-options, if the `stackedBar` is used.
|
||||||
*/
|
*/
|
||||||
private setupHorizontalBar(): void {
|
private setupStackedBar(): void {
|
||||||
this.chartOptions.scales = Object.assign(this.chartOptions.scales, {
|
this.chartOptions.scales = Object.assign(this.chartOptions.scales, {
|
||||||
xAxes: [{ stacked: true }],
|
xAxes: [{ stacked: true }],
|
||||||
yAxes: [{ stacked: true }]
|
yAxes: [{ stacked: true }]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private setupBar(): void {
|
||||||
|
if (!this.chartData.every(date => date.barThickness && date.maxBarThickness)) {
|
||||||
|
this.chartData = this.chartData.map(chartDate => ({
|
||||||
|
...chartDate,
|
||||||
|
barThickness: 20,
|
||||||
|
maxBarThickness: 48
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private setupChartLegendSize(): void {
|
||||||
|
switch (this._chartLegendSize) {
|
||||||
|
case 'small':
|
||||||
|
this.chartOptions.legend.labels = Object.assign(this.chartOptions.legend.labels, {
|
||||||
|
fontSize: 10,
|
||||||
|
boxWidth: 20
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case 'middle':
|
||||||
|
this.chartOptions.legend.labels = {
|
||||||
|
fontSize: 14,
|
||||||
|
boxWidth: 40
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
this.cd.detectChanges();
|
||||||
|
}
|
||||||
|
|
||||||
|
private checkChartType(chartType?: ChartType): void {
|
||||||
|
let type = chartType || this._type;
|
||||||
|
if (type === 'stackedBar') {
|
||||||
|
this.setupStackedBar();
|
||||||
|
this.setupBar();
|
||||||
|
type = 'horizontalBar';
|
||||||
|
}
|
||||||
|
// if (type === 'bar' || type === 'horizontalBar') {
|
||||||
|
// this.setupBar();
|
||||||
|
// }
|
||||||
|
this._type = type;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -374,6 +374,7 @@ export class AssignmentDetailComponent extends BaseViewComponent implements OnIn
|
|||||||
// resetting a form triggers a form.next(null) - check if data is present
|
// resetting a form triggers a form.next(null) - check if data is present
|
||||||
if (formResult && formResult.userId) {
|
if (formResult && formResult.userId) {
|
||||||
this.addUser(formResult.userId);
|
this.addUser(formResult.userId);
|
||||||
|
this.candidatesForm.setValue({ userId: null });
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<os-head-bar
|
<os-head-bar
|
||||||
[goBack]="true"
|
[goBack]="true"
|
||||||
[nav]="false"
|
[nav]="false"
|
||||||
[hasMainButton]="poll && (poll.state === 2 || poll.state === 3)"
|
[hasMainButton]="poll ? poll.type === 'analog' && (poll.state === 2 || poll.state === 3) : false"
|
||||||
[mainButtonIcon]="'edit'"
|
[mainButtonIcon]="'edit'"
|
||||||
[mainActionTooltip]="'Edit' | translate"
|
[mainActionTooltip]="'Edit' | translate"
|
||||||
(mainEvent)="openDialog()"
|
(mainEvent)="openDialog()"
|
||||||
@ -23,7 +23,7 @@
|
|||||||
|
|
||||||
<!-- Detailview for poll -->
|
<!-- Detailview for poll -->
|
||||||
<ng-template #viewTemplate>
|
<ng-template #viewTemplate>
|
||||||
<ng-container *ngIf="poll">
|
<ng-container *ngIf="isReady">
|
||||||
<h1>{{ poll.title }}</h1>
|
<h1>{{ poll.title }}</h1>
|
||||||
<mat-divider></mat-divider>
|
<mat-divider></mat-divider>
|
||||||
<os-breadcrumb [breadcrumbs]="breadcrumbs" [breadcrumbStyle]="'>'"></os-breadcrumb>
|
<os-breadcrumb [breadcrumbs]="breadcrumbs" [breadcrumbStyle]="'>'"></os-breadcrumb>
|
||||||
@ -42,23 +42,62 @@
|
|||||||
<div *ngIf="poll.state === 3 || poll.state === 4">
|
<div *ngIf="poll.state === 3 || poll.state === 4">
|
||||||
<h2 translate>Result</h2>
|
<h2 translate>Result</h2>
|
||||||
|
|
||||||
<div *ngIf="poll.type === 'named'" style="display: grid; grid-template-columns: auto repeat({{ poll.options.length }}, max-content);">
|
<div
|
||||||
|
*ngIf="poll.type === 'named'"
|
||||||
|
style="display: grid; grid-template-columns: auto repeat({{ poll.options.length }}, max-content);"
|
||||||
|
>
|
||||||
<!-- top left cell is empty -->
|
<!-- top left cell is empty -->
|
||||||
<div></div>
|
<div></div>
|
||||||
<!-- header (the assignment related users) -->
|
<!-- header (the assignment related users) -->
|
||||||
<ng-container *ngFor="let option of poll.options">
|
<ng-container *ngFor="let option of poll.options">
|
||||||
<div *ngIf="option.user">{{ option.user.full_name }}</div>
|
<div *ngIf="option.user">{{ option.user.full_name }}</div>
|
||||||
<div *ngIf="!option.user">{{ "Unknown user" | translate}}</div>
|
<div *ngIf="!option.user">{{ 'Unknown user' | translate }}</div>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<!-- rows -->
|
<!-- rows -->
|
||||||
<ng-container *ngFor="let obj of votesByUser | keyvalue">
|
<ng-container *ngFor="let obj of votesByUser | keyvalue">
|
||||||
<div *ngIf="obj.value.user">{{ obj.value.user.full_name }}</div>
|
<div *ngIf="obj.value.user">{{ obj.value.user.full_name }}</div>
|
||||||
<div *ngIf="!obj.value.user">{{ "Unknown user" | translate}}</div>
|
<div *ngIf="!obj.value.user">{{ 'Unknown user' | translate }}</div>
|
||||||
<ng-container *ngFor="let option of poll.options">
|
<ng-container *ngFor="let option of poll.options">
|
||||||
<div>{{ obj.value.votes[option.user_id]}}</div>
|
<div>{{ obj.value.votes[option.user_id] }}</div>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="chart-wrapper"></div>
|
||||||
|
<mat-table [dataSource]="poll.tableData">
|
||||||
|
<ng-container matColumnDef="user" sticky>
|
||||||
|
<mat-header-cell *matHeaderCellDef>{{ 'Candidates' | translate }}</mat-header-cell>
|
||||||
|
<mat-cell *matCellDef="let row">{{ row.user }}</mat-cell>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container matColumnDef="yes">
|
||||||
|
<mat-header-cell *matHeaderCellDef>{{ 'Yes' | translate }}</mat-header-cell>
|
||||||
|
<mat-cell *matCellDef="let row">{{ row.yes }}</mat-cell>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container matColumnDef="no">
|
||||||
|
<mat-header-cell *matHeaderCellDef>{{ 'No' | translate }}</mat-header-cell>
|
||||||
|
<mat-cell *matCellDef="let row">{{ row.no }}</mat-cell>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container matColumnDef="abstain">
|
||||||
|
<mat-header-cell *matHeaderCellDef>{{ 'Abstain' | translate }}</mat-header-cell>
|
||||||
|
<mat-cell *matCellDef="let row">{{ row.abstain }}</mat-cell>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container matColumnDef="quorum">
|
||||||
|
<mat-header-cell *matHeaderCellDef>{{ 'Quorum' | translate }}</mat-header-cell>
|
||||||
|
<mat-cell *matCellDef="let row"></mat-cell>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<mat-header-row *matHeaderRowDef="columnDefinition"></mat-header-row>
|
||||||
|
<mat-row *matRowDef="let row; columns: columnDefinition"></mat-row>
|
||||||
|
</mat-table>
|
||||||
|
<os-charts
|
||||||
|
*ngIf="chartDataSubject.value"
|
||||||
|
[type]="chartType"
|
||||||
|
[labels]="candidatesLabels"
|
||||||
|
[showLegend]="true"
|
||||||
|
[data]="chartDataSubject"
|
||||||
|
></os-charts>
|
||||||
</div>
|
</div>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
@ -5,9 +5,11 @@ import { ActivatedRoute } from '@angular/router';
|
|||||||
|
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
|
||||||
|
import { OperatorService } from 'app/core/core-services/operator.service';
|
||||||
import { AssignmentPollRepositoryService } from 'app/core/repositories/assignments/assignment-poll-repository.service';
|
import { AssignmentPollRepositoryService } from 'app/core/repositories/assignments/assignment-poll-repository.service';
|
||||||
import { GroupRepositoryService } from 'app/core/repositories/users/group-repository.service';
|
import { GroupRepositoryService } from 'app/core/repositories/users/group-repository.service';
|
||||||
import { PromptService } from 'app/core/ui-services/prompt.service';
|
import { PromptService } from 'app/core/ui-services/prompt.service';
|
||||||
|
import { ChartType } from 'app/shared/components/charts/charts.component';
|
||||||
import { AssignmentPollMethods } from 'app/shared/models/assignments/assignment-poll';
|
import { AssignmentPollMethods } from 'app/shared/models/assignments/assignment-poll';
|
||||||
import { BasePollDetailComponent } from 'app/site/polls/components/base-poll-detail.component';
|
import { BasePollDetailComponent } from 'app/site/polls/components/base-poll-detail.component';
|
||||||
import { ViewUser } from 'app/site/users/models/view-user';
|
import { ViewUser } from 'app/site/users/models/view-user';
|
||||||
@ -21,8 +23,24 @@ import { ViewAssignmentVote } from '../../models/view-assignment-vote';
|
|||||||
styleUrls: ['./assignment-poll-detail.component.scss']
|
styleUrls: ['./assignment-poll-detail.component.scss']
|
||||||
})
|
})
|
||||||
export class AssignmentPollDetailComponent extends BasePollDetailComponent<ViewAssignmentPoll> {
|
export class AssignmentPollDetailComponent extends BasePollDetailComponent<ViewAssignmentPoll> {
|
||||||
|
public isReady = false;
|
||||||
|
|
||||||
|
public candidatesLabels: string[] = [];
|
||||||
|
|
||||||
public votesByUser: { [key: number]: { user: ViewUser; votes: { [key: number]: ViewAssignmentVote } } };
|
public votesByUser: { [key: number]: { user: ViewUser; votes: { [key: number]: ViewAssignmentVote } } };
|
||||||
|
|
||||||
|
public get chartType(): ChartType {
|
||||||
|
return 'horizontalBar';
|
||||||
|
}
|
||||||
|
|
||||||
|
public get columnDefinition(): string[] {
|
||||||
|
const columns = ['user', 'yes', 'no', 'quorum'];
|
||||||
|
if ((<ViewAssignmentPoll>this.poll).pollmethod === AssignmentPollMethods.YNA) {
|
||||||
|
columns.splice(3, 0, 'abstain');
|
||||||
|
}
|
||||||
|
return columns;
|
||||||
|
}
|
||||||
|
|
||||||
public constructor(
|
public constructor(
|
||||||
title: Title,
|
title: Title,
|
||||||
translate: TranslateService,
|
translate: TranslateService,
|
||||||
@ -31,7 +49,8 @@ export class AssignmentPollDetailComponent extends BasePollDetailComponent<ViewA
|
|||||||
route: ActivatedRoute,
|
route: ActivatedRoute,
|
||||||
groupRepo: GroupRepositoryService,
|
groupRepo: GroupRepositoryService,
|
||||||
prompt: PromptService,
|
prompt: PromptService,
|
||||||
pollDialog: AssignmentPollDialogService
|
pollDialog: AssignmentPollDialogService,
|
||||||
|
private operator: OperatorService
|
||||||
) {
|
) {
|
||||||
super(title, translate, matSnackbar, repo, route, groupRepo, prompt, pollDialog);
|
super(title, translate, matSnackbar, repo, route, groupRepo, prompt, pollDialog);
|
||||||
}
|
}
|
||||||
@ -54,6 +73,12 @@ export class AssignmentPollDetailComponent extends BasePollDetailComponent<ViewA
|
|||||||
}
|
}
|
||||||
console.log(votes, this.poll, this.poll.options);
|
console.log(votes, this.poll, this.poll.options);
|
||||||
this.votesByUser = votes;
|
this.votesByUser = votes;
|
||||||
}, 1000);
|
this.candidatesLabels = this.poll.initChartLabels();
|
||||||
|
this.isReady = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected hasPerms(): boolean {
|
||||||
|
return this.operator.hasPerms('assignments.can_manage');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
<ng-container *ngIf="poll">
|
<ng-container *ngIf="poll">
|
||||||
<ng-container *ngIf="vmanager.canVote(poll)">
|
<ng-container *ngIf="vmanager.canVote(poll)">
|
||||||
<span *ngIf="poll.pollmethod === 'votes'">{{ "You can distribute" | translate }} {{ poll.votes_amount }} {{ "votes" | translate }}.</span>
|
<span *ngIf="poll.pollmethod === 'votes'"
|
||||||
|
>{{ 'You can distribute' | translate }} {{ poll.votes_amount }} {{ 'votes' | translate }}.</span
|
||||||
|
>
|
||||||
<form *ngIf="voteForm" [formGroup]="voteForm" class="voting-grid">
|
<form *ngIf="voteForm" [formGroup]="voteForm" class="voting-grid">
|
||||||
<ng-container *ngFor="let option of poll.options">
|
<ng-container *ngFor="let option of poll.options">
|
||||||
<div>
|
<div>
|
||||||
@ -10,7 +12,7 @@
|
|||||||
|
|
||||||
<div class="current-vote">
|
<div class="current-vote">
|
||||||
<ng-container *ngIf="currentVotes[option.user_id] !== null">
|
<ng-container *ngIf="currentVotes[option.user_id] !== null">
|
||||||
({{ "Current" | translate }}: {{ getCurrentVoteVerbose(option.user_id) | translate }})
|
({{ 'Current' | translate }}: {{ getCurrentVoteVerbose(option.user_id) | translate }})
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -31,12 +33,17 @@
|
|||||||
</mat-radio-group>
|
</mat-radio-group>
|
||||||
|
|
||||||
<mat-form-field *ngIf="poll.pollmethod === 'votes'" class="vote-input">
|
<mat-form-field *ngIf="poll.pollmethod === 'votes'" class="vote-input">
|
||||||
<input matInput type="number" min="0" [formControlName]="option.id">
|
<input matInput type="number" min="0" [formControlName]="option.id" />
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</form>
|
</form>
|
||||||
<div class="right-align">
|
<div class="right-align">
|
||||||
<button mat-button mat-button-default (click)="saveVotes()" [disabled]="!voteForm || voteForm.invalid || voteForm.pristine">
|
<button
|
||||||
|
mat-button
|
||||||
|
mat-button-default
|
||||||
|
(click)="saveVotes()"
|
||||||
|
[disabled]="!voteForm || voteForm.invalid || voteForm.pristine"
|
||||||
|
>
|
||||||
<span translate>Save</span>
|
<span translate>Save</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
<!-- Buttons -->
|
<!-- Buttons -->
|
||||||
<button
|
<button
|
||||||
mat-icon-button
|
mat-icon-button
|
||||||
*osPerms="'assignments.can_manage'; 'core.can_manage_projector'"
|
*osPerms="'assignments.can_manage'; "core.can_manage_projector""
|
||||||
[matMenuTriggerFor]="pollItemMenu"
|
[matMenuTriggerFor]="pollItemMenu"
|
||||||
(click)="$event.stopPropagation()"
|
(click)="$event.stopPropagation()"
|
||||||
>
|
>
|
||||||
@ -15,17 +15,6 @@
|
|||||||
<mat-icon>edit</mat-icon>
|
<mat-icon>edit</mat-icon>
|
||||||
<span translate>Edit</span>
|
<span translate>Edit</span>
|
||||||
</button>
|
</button>
|
||||||
<!-- <button mat-menu-item (click)="printBallot()">
|
|
||||||
<mat-icon>local_printshop</mat-icon>
|
|
||||||
<span translate>Print ballot paper</span>
|
|
||||||
</button>
|
|
||||||
<button mat-menu-item (click)="togglePublished()">
|
|
||||||
<mat-icon>
|
|
||||||
{{ poll.published ? 'visibility_off' : 'visibility' }}
|
|
||||||
</mat-icon>
|
|
||||||
<span *ngIf="!poll.published" translate>Publish</span>
|
|
||||||
<span *ngIf="poll.published" translate>Unpublish</span>
|
|
||||||
</button> -->
|
|
||||||
</div>
|
</div>
|
||||||
<div *osPerms="'core.can_manage_projector'">
|
<div *osPerms="'core.can_manage_projector'">
|
||||||
<os-projector-button menuItem="true" [object]="poll"></os-projector-button>
|
<os-projector-button menuItem="true" [object]="poll"></os-projector-button>
|
||||||
@ -40,200 +29,38 @@
|
|||||||
</mat-menu>
|
</mat-menu>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="poll-properties">
|
<div>
|
||||||
<!-- <mat-chip *ngIf="pollService.isElectronicVotingEnabled">{{ poll.typeVerbose }}</mat-chip> -->
|
<div>
|
||||||
<mat-chip
|
<div class="poll-properties">
|
||||||
class="poll-state active"
|
<!-- <mat-chip *ngIf="pollService.isElectronicVotingEnabled">{{ poll.typeVerbose }}</mat-chip> -->
|
||||||
[matMenuTriggerFor]="triggerMenu"
|
<mat-chip
|
||||||
[ngClass]="poll.stateVerbose.toLowerCase()"
|
class="poll-state active"
|
||||||
>
|
[matMenuTriggerFor]="triggerMenu"
|
||||||
{{ poll.stateVerbose }}
|
[ngClass]="poll.stateVerbose.toLowerCase()"
|
||||||
</mat-chip>
|
>
|
||||||
<!-- <mat-chip
|
{{ poll.stateVerbose }}
|
||||||
class="poll-state active"
|
</mat-chip>
|
||||||
*ngIf="poll.state !== 2"
|
</div>
|
||||||
[matMenuTriggerFor]="triggerMenu"
|
|
||||||
[ngClass]="poll.stateVerbose.toLowerCase()"
|
|
||||||
>
|
|
||||||
{{ poll.stateVerbose }}
|
|
||||||
</mat-chip> -->
|
|
||||||
<!-- <mat-chip class="poll-state" *ngIf="poll.state === 2" [ngClass]="poll.stateVerbose.toLowerCase()">
|
|
||||||
{{ poll.stateVerbose }}
|
|
||||||
</mat-chip> -->
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h3>
|
<h3>
|
||||||
<a routerLink="/assignments/polls/{{ poll.id }}">
|
<a routerLink="/assignments/polls/{{ poll.id }}">
|
||||||
{{ poll.title }}
|
{{ poll.title }}
|
||||||
</a>
|
</a>
|
||||||
</h3>
|
</h3>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<os-charts [type]="chartType" [labels]="candidatesLabels" [data]="chartDataSubject"></os-charts>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<os-assignment-poll-vote *ngIf="poll.canBeVotedFor" [poll]="poll"></os-assignment-poll-vote>
|
<os-assignment-poll-vote *ngIf="poll.canBeVotedFor" [poll]="poll"></os-assignment-poll-vote>
|
||||||
<!-- <ng-container *ngIf="poll.state === pollStates.STATE_PUBLISHED" [ngTemplateOutlet]="resultsTemplate"></ng-container> -->
|
<!-- <ng-container *ngIf="poll.state === pollStates.STATE_PUBLISHED" [ngTemplateOutlet]="resultsTemplate"></ng-container> -->
|
||||||
|
|
||||||
<div class="poll-main-content" *ngIf="false && poll.options">
|
|
||||||
<div *ngIf="canSee">
|
|
||||||
<div class="poll-grid">
|
|
||||||
<div></div>
|
|
||||||
<div><span class="table-view-list-title" translate>Candidates</span></div>
|
|
||||||
<div><span class="table-view-list-title" translate>Votes</span></div>
|
|
||||||
<div *ngIf="pollService.majorityMethods && majorityChoice && canManage">
|
|
||||||
<div>
|
|
||||||
<span class="table-view-list-title" translate>Quorum</span>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<!-- manager majority chip (menu trigger) -->
|
|
||||||
<mat-basic-chip [matMenuTriggerFor]="majorityMenu" class="grey" disableRipple>
|
|
||||||
{{ majorityChoice.display_name | translate }}
|
|
||||||
</mat-basic-chip>
|
|
||||||
<!-- menu for selecting quorum choices -->
|
|
||||||
<mat-menu #majorityMenu="matMenu">
|
|
||||||
<button
|
|
||||||
mat-menu-item
|
|
||||||
*ngFor="let method of pollService.majorityMethods"
|
|
||||||
(click)="setMajority(method)"
|
|
||||||
>
|
|
||||||
<mat-icon *ngIf="method.value === majorityChoice.value">
|
|
||||||
check
|
|
||||||
</mat-icon>
|
|
||||||
{{ method.display_name | translate }}
|
|
||||||
</button>
|
|
||||||
</mat-menu>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div *ngFor="let option of poll.options" class="poll-grid poll-border">
|
|
||||||
<div>
|
|
||||||
<div>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
mat-icon-button
|
|
||||||
(click)="toggleElected(option)"
|
|
||||||
[disabled]="!canManage || poll.assignment.isFinished"
|
|
||||||
disableRipple
|
|
||||||
>
|
|
||||||
<mat-icon
|
|
||||||
*ngIf="option.is_elected"
|
|
||||||
class="top-aligned green-text"
|
|
||||||
matTooltip="{{ 'Elected' | translate }}"
|
|
||||||
>check_box</mat-icon
|
|
||||||
>
|
|
||||||
<mat-icon
|
|
||||||
*ngIf="!option.is_elected && canManage && !poll.assignment.isFinished"
|
|
||||||
class="top-aligned primary"
|
|
||||||
matTooltip="{{ 'Mark as elected' | translate }}"
|
|
||||||
>
|
|
||||||
check_box_outline_blank</mat-icon
|
|
||||||
>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- candidate Name -->
|
|
||||||
<div class="candidate-name">
|
|
||||||
{{ option.user.full_name }}
|
|
||||||
</div>
|
|
||||||
<!-- Votes -->
|
|
||||||
<div>
|
|
||||||
<div *ngIf="poll.has_votes">
|
|
||||||
<div *ngFor="let vote of option.votes" class="spacer-bottom-10">
|
|
||||||
<div class="poll-progress">
|
|
||||||
<span *ngIf="vote.value !== 'Votes'"
|
|
||||||
>{{ pollService.getLabel(vote.value) | translate }}:</span
|
|
||||||
>
|
|
||||||
{{ pollService.getSpecialLabel(vote.weight) | translate }}
|
|
||||||
<span *ngIf="!pollService.isAbstractOption(poll, option, vote.value)"
|
|
||||||
>({{ pollService.getPercent(poll, option, vote.value) }}%)</span
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
*ngIf="!pollService.isAbstractOption(poll, option, vote.value)"
|
|
||||||
class="poll-progress-bar"
|
|
||||||
>
|
|
||||||
<mat-progress-bar
|
|
||||||
mode="determinate"
|
|
||||||
[value]="pollService.getPercent(poll, option, vote.value)"
|
|
||||||
[ngClass]="pollService.getProgressBarColor(vote.value)"
|
|
||||||
>
|
|
||||||
</mat-progress-bar>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div *ngIf="canManage">
|
|
||||||
<div
|
|
||||||
*ngIf="
|
|
||||||
poll.has_votes &&
|
|
||||||
majorityChoice &&
|
|
||||||
majorityChoice.value !== 'disabled' &&
|
|
||||||
!pollService.isAbstractOption(poll, option)
|
|
||||||
"
|
|
||||||
class="poll-quorum"
|
|
||||||
>
|
|
||||||
<span>{{ pollService.yesQuorum(majorityChoice, poll, option) }}</span>
|
|
||||||
<span
|
|
||||||
[ngClass]="quorumReached(option) ? 'green-text' : 'red-warning-text'"
|
|
||||||
matTooltip="{{ getQuorumReachedString(option) }}"
|
|
||||||
>
|
|
||||||
<mat-icon *ngIf="quorumReached(option)">{{ pollService.getIcon('yes') }}</mat-icon>
|
|
||||||
<mat-icon *ngIf="!quorumReached(option)">{{ pollService.getIcon('no') }}</mat-icon>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- summary -->
|
|
||||||
<div>
|
|
||||||
<div *ngFor="let key of pollValues" class="poll-grid">
|
|
||||||
<div></div>
|
|
||||||
<div class="candidate-name">
|
|
||||||
<span>{{ pollService.getLabel(key) | translate }}</span
|
|
||||||
>:
|
|
||||||
</div>
|
|
||||||
<div *ngIf="poll[key]">
|
|
||||||
{{ pollService.getSpecialLabel(poll[key]) | translate }}
|
|
||||||
<span *ngIf="!pollService.isAbstractValue(poll, key)">
|
|
||||||
({{ pollService.getValuePercent(poll, key) }} %)
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div *ngIf="!pollData">
|
|
||||||
<h4 translate>Candidates</h4>
|
|
||||||
<div *ngFor="let option of poll.options">
|
|
||||||
<span class="accent" *ngIf="option.user">{{ option.user.getFullName() }}</span>
|
|
||||||
<span *ngIf="!option.user">No user {{ option.candidate_id }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Election Method -->
|
|
||||||
<!-- <div *ngIf="canManage" class="spacer-bottom-10">
|
|
||||||
<h4 translate>Election method</h4>
|
|
||||||
<span>{{ pollMethodName | translate }}</span>
|
|
||||||
</div> -->
|
|
||||||
|
|
||||||
<!-- Poll paper hint -->
|
|
||||||
<!-- <div *ngIf="canManage" class="hint-form" [formGroup]="descriptionForm">
|
|
||||||
<mat-form-field class="wide">
|
|
||||||
<mat-label translate>Hint for ballot paper</mat-label>
|
|
||||||
<input matInput formControlName="description" />
|
|
||||||
</mat-form-field>
|
|
||||||
<button mat-icon-button [disabled]="!dirtyDescription" (click)="onEditDescriptionButton()">
|
|
||||||
<mat-icon inline>check</mat-icon>
|
|
||||||
</button>
|
|
||||||
</div> -->
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ng-template #resultsTemplate>
|
<ng-template #resultsTemplate> </ng-template>
|
||||||
|
|
||||||
</ng-template>
|
|
||||||
|
|
||||||
<mat-menu #triggerMenu="matMenu">
|
<mat-menu #triggerMenu="matMenu">
|
||||||
<ng-container *ngIf="poll">
|
<ng-container *ngIf="poll">
|
||||||
<button
|
<button mat-menu-item (click)="changeState(state.value)" *ngFor="let state of poll.nextStates | keyvalue">
|
||||||
mat-menu-item
|
|
||||||
(click)="changeState(state.value)"
|
|
||||||
*ngFor="let state of poll.nextStates | keyvalue"
|
|
||||||
>
|
|
||||||
<span translate>{{ state.key }}</span>
|
<span translate>{{ state.key }}</span>
|
||||||
</button>
|
</button>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Component, OnInit, ViewEncapsulation } from '@angular/core';
|
import { Component, Input, OnInit, ViewEncapsulation } from '@angular/core';
|
||||||
import { FormBuilder, FormGroup } from '@angular/forms';
|
import { FormBuilder, FormGroup } from '@angular/forms';
|
||||||
import { MatDialog } from '@angular/material/dialog';
|
import { MatDialog } from '@angular/material/dialog';
|
||||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||||
@ -9,7 +9,10 @@ import { TranslateService } from '@ngx-translate/core';
|
|||||||
import { OperatorService } from 'app/core/core-services/operator.service';
|
import { OperatorService } from 'app/core/core-services/operator.service';
|
||||||
import { AssignmentPollRepositoryService } from 'app/core/repositories/assignments/assignment-poll-repository.service';
|
import { AssignmentPollRepositoryService } from 'app/core/repositories/assignments/assignment-poll-repository.service';
|
||||||
import { PromptService } from 'app/core/ui-services/prompt.service';
|
import { PromptService } from 'app/core/ui-services/prompt.service';
|
||||||
|
import { ChartType } from 'app/shared/components/charts/charts.component';
|
||||||
|
import { AssignmentPollMethods } from 'app/shared/models/assignments/assignment-poll';
|
||||||
import { BasePollComponent } from 'app/site/polls/components/base-poll.component';
|
import { BasePollComponent } from 'app/site/polls/components/base-poll.component';
|
||||||
|
import { PollService } from 'app/site/polls/services/poll.service';
|
||||||
import { AssignmentPollDialogService } from '../../services/assignment-poll-dialog.service';
|
import { AssignmentPollDialogService } from '../../services/assignment-poll-dialog.service';
|
||||||
import { ViewAssignmentOption } from '../../models/view-assignment-option';
|
import { ViewAssignmentOption } from '../../models/view-assignment-option';
|
||||||
import { ViewAssignmentPoll } from '../../models/view-assignment-poll';
|
import { ViewAssignmentPoll } from '../../models/view-assignment-poll';
|
||||||
@ -24,6 +27,27 @@ import { ViewAssignmentPoll } from '../../models/view-assignment-poll';
|
|||||||
encapsulation: ViewEncapsulation.None
|
encapsulation: ViewEncapsulation.None
|
||||||
})
|
})
|
||||||
export class AssignmentPollComponent extends BasePollComponent<ViewAssignmentPoll> implements OnInit {
|
export class AssignmentPollComponent extends BasePollComponent<ViewAssignmentPoll> implements OnInit {
|
||||||
|
@Input()
|
||||||
|
public set poll(value: ViewAssignmentPoll) {
|
||||||
|
this.initPoll(value);
|
||||||
|
this.candidatesLabels = value.initChartLabels();
|
||||||
|
const chartData =
|
||||||
|
value.pollmethod === AssignmentPollMethods.Votes
|
||||||
|
? value.generateCircleChartData()
|
||||||
|
: value.generateChartData();
|
||||||
|
this.chartDataSubject.next(chartData);
|
||||||
|
}
|
||||||
|
|
||||||
|
public get poll(): ViewAssignmentPoll {
|
||||||
|
return this._poll;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get chartType(): ChartType {
|
||||||
|
return this.poll && this.poll.pollmethod === AssignmentPollMethods.Votes ? 'doughnut' : 'horizontalBar';
|
||||||
|
}
|
||||||
|
|
||||||
|
public candidatesLabels: string[] = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Form for updating the poll's description
|
* Form for updating the poll's description
|
||||||
*/
|
*/
|
||||||
@ -58,6 +82,7 @@ export class AssignmentPollComponent extends BasePollComponent<ViewAssignmentPol
|
|||||||
promptService: PromptService,
|
promptService: PromptService,
|
||||||
repo: AssignmentPollRepositoryService,
|
repo: AssignmentPollRepositoryService,
|
||||||
pollDialog: AssignmentPollDialogService,
|
pollDialog: AssignmentPollDialogService,
|
||||||
|
public pollService: PollService,
|
||||||
private operator: OperatorService,
|
private operator: OperatorService,
|
||||||
private formBuilder: FormBuilder
|
private formBuilder: FormBuilder
|
||||||
) {
|
) {
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
|
import { BehaviorSubject } from 'rxjs';
|
||||||
|
|
||||||
import { ChartData } from 'app/shared/components/charts/charts.component';
|
import { ChartData } from 'app/shared/components/charts/charts.component';
|
||||||
import { AssignmentPoll } from 'app/shared/models/assignments/assignment-poll';
|
import { AssignmentPoll, AssignmentPollMethods } from 'app/shared/models/assignments/assignment-poll';
|
||||||
|
import { PollColor } from 'app/shared/models/poll/base-poll';
|
||||||
import { ProjectorElementBuildDeskriptor } from 'app/site/base/projectable';
|
import { ProjectorElementBuildDeskriptor } from 'app/site/base/projectable';
|
||||||
import { ViewBasePoll } from 'app/site/polls/models/view-base-poll';
|
import { ViewBasePoll } from 'app/site/polls/models/view-base-poll';
|
||||||
import { ViewAssignment } from './view-assignment';
|
import { ViewAssignment } from './view-assignment';
|
||||||
@ -19,8 +22,13 @@ export class ViewAssignmentPoll extends ViewBasePoll<AssignmentPoll> implements
|
|||||||
public static COLLECTIONSTRING = AssignmentPoll.COLLECTIONSTRING;
|
public static COLLECTIONSTRING = AssignmentPoll.COLLECTIONSTRING;
|
||||||
protected _collectionString = AssignmentPoll.COLLECTIONSTRING;
|
protected _collectionString = AssignmentPoll.COLLECTIONSTRING;
|
||||||
|
|
||||||
|
public readonly tableChartData: Map<string, BehaviorSubject<ChartData>> = new Map();
|
||||||
public readonly pollClassType: 'assignment' | 'motion' = 'assignment';
|
public readonly pollClassType: 'assignment' | 'motion' = 'assignment';
|
||||||
|
|
||||||
|
public get pollmethodVerbose(): string {
|
||||||
|
return AssignmentPollMethodsVerbose[this.pollmethod];
|
||||||
|
}
|
||||||
|
|
||||||
public getSlide(): ProjectorElementBuildDeskriptor {
|
public getSlide(): ProjectorElementBuildDeskriptor {
|
||||||
// TODO: update to new voting system?
|
// TODO: update to new voting system?
|
||||||
return {
|
return {
|
||||||
@ -36,13 +44,43 @@ export class ViewAssignmentPoll extends ViewBasePoll<AssignmentPoll> implements
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public get pollmethodVerbose(): string {
|
public initChartLabels(): string[] {
|
||||||
return AssignmentPollMethodsVerbose[this.pollmethod];
|
return this.options.map(candidate => candidate.user.full_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO
|
|
||||||
public generateChartData(): ChartData {
|
public generateChartData(): ChartData {
|
||||||
return [];
|
const fields = ['yes', 'no'];
|
||||||
|
if (this.pollmethod === AssignmentPollMethods.YNA) {
|
||||||
|
fields.push('abstain');
|
||||||
|
}
|
||||||
|
const data: ChartData = fields.map(key => ({
|
||||||
|
label: key.toUpperCase(),
|
||||||
|
data: this.options.map(vote => vote[key]),
|
||||||
|
backgroundColor: PollColor[key],
|
||||||
|
hoverBackgroundColor: PollColor[key]
|
||||||
|
}));
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public generateCircleChartData(): ChartData {
|
||||||
|
const data: ChartData = this.options.map(candidate => ({
|
||||||
|
label: candidate.user.getFullName(),
|
||||||
|
data: [candidate.yes]
|
||||||
|
}));
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public generateTableData(): {}[] {
|
||||||
|
const data = this.options
|
||||||
|
.map(candidate => ({
|
||||||
|
yes: candidate.yes,
|
||||||
|
no: candidate.no,
|
||||||
|
abstain: candidate.abstain,
|
||||||
|
user: candidate.user.full_name
|
||||||
|
}))
|
||||||
|
.sort((a, b) => b.yes - a.yes);
|
||||||
|
|
||||||
|
return data;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,6 +20,20 @@ export class ViewMotionPoll extends ViewBasePoll<MotionPoll> implements MotionPo
|
|||||||
|
|
||||||
public readonly pollClassType: 'assignment' | 'motion' = 'motion';
|
public readonly pollClassType: 'assignment' | 'motion' = 'motion';
|
||||||
|
|
||||||
|
private tableKeys = ['yes', 'no', 'abstain'];
|
||||||
|
private voteKeys = ['votesvalid', 'votesinvalid', 'votescast'];
|
||||||
|
|
||||||
|
public initChartLabels(): string[] {
|
||||||
|
return ['Votes'];
|
||||||
|
}
|
||||||
|
|
||||||
|
public generateTableData(): {}[] {
|
||||||
|
let tableData = this.options.flatMap(vote => this.tableKeys.map(key => ({ key: key, value: vote[key] })));
|
||||||
|
tableData.push(...this.voteKeys.map(key => ({ key: key, value: this[key] })));
|
||||||
|
tableData = tableData.map(entry => (entry.value >= 0 ? entry : { key: entry.key, value: null }));
|
||||||
|
return tableData;
|
||||||
|
}
|
||||||
|
|
||||||
public generateChartData(): ChartData {
|
public generateChartData(): ChartData {
|
||||||
const fields = ['yes', 'no'];
|
const fields = ['yes', 'no'];
|
||||||
if (this.pollmethod === MotionPollMethods.YNA) {
|
if (this.pollmethod === MotionPollMethods.YNA) {
|
||||||
@ -27,18 +41,11 @@ export class ViewMotionPoll extends ViewBasePoll<MotionPoll> implements MotionPo
|
|||||||
}
|
}
|
||||||
const data: ChartData = fields.map(key => ({
|
const data: ChartData = fields.map(key => ({
|
||||||
label: key.toUpperCase(),
|
label: key.toUpperCase(),
|
||||||
data: [this.options[0][key]],
|
data: this.options.map(option => option[key]),
|
||||||
backgroundColor: PollColor[key],
|
backgroundColor: PollColor[key],
|
||||||
hoverBackgroundColor: PollColor[key]
|
hoverBackgroundColor: PollColor[key]
|
||||||
}));
|
}));
|
||||||
|
|
||||||
data.push({
|
|
||||||
label: 'Votes invalid',
|
|
||||||
data: [this.votesinvalid],
|
|
||||||
backgroundColor: PollColor.votesinvalid,
|
|
||||||
hoverBackgroundColor: PollColor.votesinvalid
|
|
||||||
});
|
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -459,17 +459,13 @@
|
|||||||
|
|
||||||
<!-- 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.polls; trackBy: trackByIndex" [poll]="poll"></os-motion-poll>
|
||||||
<div class="mat-card create-poll-button" *ngIf="perms.isAllowed('createpoll', motion)">
|
<div class="mat-card create-poll-button" *ngIf="perms.isAllowed('createpoll', motion)">
|
||||||
<button mat-button (click)="openDialog()">
|
<button mat-button (click)="openDialog()">
|
||||||
<mat-icon class="main-nav-color">poll</mat-icon>
|
<mat-icon class="main-nav-color">poll</mat-icon>
|
||||||
<span translate>New poll</span>
|
<span translate>New poll</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<os-motion-poll
|
|
||||||
*ngFor="let poll of motion.polls; trackBy: trackByIndex"
|
|
||||||
[poll]="poll"
|
|
||||||
></os-motion-poll>
|
|
||||||
<!-- <os-motion-poll-manager [motion]="motion"></os-motion-poll-manager> -->
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
<os-head-bar
|
<os-head-bar
|
||||||
[goBack]="true"
|
[goBack]="true"
|
||||||
[nav]="false"
|
[nav]="false"
|
||||||
[hasMainButton]="poll && (poll.state === 2 || poll.state === 3)"
|
[hasMainButton]="poll ? poll.state === 2 || poll.state === 3 : false"
|
||||||
[mainButtonIcon]="'edit'"
|
[mainButtonIcon]="'edit'"
|
||||||
[mainActionTooltip]="'Edit' | translate"
|
[mainActionTooltip]="'Edit' | translate"
|
||||||
(mainEvent)="openDialog()"
|
(mainEvent)="openDialog()"
|
||||||
>
|
>
|
||||||
<div class="title-slot">
|
<div class="title-slot">
|
||||||
<h2 *ngIf="!!poll">{{ poll.title }}</h2>
|
<h2 *ngIf="!!poll">{{ motionTitle }}</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="menu-slot" *osPerms="'agenda.can_manage'; or: 'agenda.can_see_list_of_speakers'">
|
<div class="menu-slot" *osPerms="'agenda.can_manage'; or: 'agenda.can_see_list_of_speakers'">
|
||||||
@ -27,38 +27,55 @@
|
|||||||
<h1>{{ poll.title }}</h1>
|
<h1>{{ poll.title }}</h1>
|
||||||
<mat-divider></mat-divider>
|
<mat-divider></mat-divider>
|
||||||
<os-breadcrumb [breadcrumbs]="breadcrumbs" [breadcrumbStyle]="'>'"></os-breadcrumb>
|
<os-breadcrumb [breadcrumbs]="breadcrumbs" [breadcrumbStyle]="'>'"></os-breadcrumb>
|
||||||
|
|
||||||
|
<div *ngIf="poll.state === 3 || poll.state === 4">
|
||||||
|
<h2 translate>Result</h2>
|
||||||
|
<os-charts
|
||||||
|
*ngIf="chartDataSubject.value"
|
||||||
|
[type]="chartType"
|
||||||
|
[showLegend]="true"
|
||||||
|
[data]="chartDataSubject"
|
||||||
|
></os-charts>
|
||||||
|
<div
|
||||||
|
*ngIf="poll.type === 'named'"
|
||||||
|
style="display: grid; grid-template-columns: max-content auto;grid-column-gap: 20px;"
|
||||||
|
>
|
||||||
|
<ng-container *ngFor="let vote of poll.options[0].votes">
|
||||||
|
<div *ngIf="vote.user">{{ vote.user.full_name }}</div>
|
||||||
|
<div *ngIf="!vote.user">{{ 'Unknown user' | translate }}</div>
|
||||||
|
<div>{{ vote.valueVerbose }}</div>
|
||||||
|
</ng-container>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<mat-table [dataSource]="poll.tableData">
|
||||||
|
<ng-container matColumnDef="key" sticky>
|
||||||
|
<mat-header-cell *matHeaderCellDef></mat-header-cell>
|
||||||
|
<mat-cell *matCellDef="let row">{{ row.key }}</mat-cell>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container matColumnDef="value" sticky>
|
||||||
|
<mat-header-cell *matHeaderCellDef>Votes</mat-header-cell>
|
||||||
|
<mat-cell *matCellDef="let row">{{ row.value }}</mat-cell>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<mat-header-row *matHeaderRowDef="columnDefinition"></mat-header-row>
|
||||||
|
<mat-row *matRowDef="let row; columns: columnDefinition"></mat-row>
|
||||||
|
</mat-table>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="poll-content">
|
<div class="poll-content">
|
||||||
<div>{{ 'Current state' | translate }}: {{ poll.stateVerbose | translate }}</div>
|
<div>{{ 'Current state' | translate }}: {{ poll.stateVerbose | translate }}</div>
|
||||||
<div *ngIf="poll.groups && poll.type && poll.type !== 'analog'">
|
<div *ngIf="poll.groups && poll.type && poll.type !== 'analog'">
|
||||||
{{ 'Groups' | translate }}:
|
{{ 'Groups' | translate }}:
|
||||||
<span *ngFor="let group of poll.groups">{{ group.getTitle() | translate }}</span>
|
<span *ngFor="let group of poll.groups; let i = index">
|
||||||
|
{{ group.getTitle() | translate }}
|
||||||
|
<span *ngIf="i < poll.groups.length - 1">, </span>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div>{{ 'Poll type' | translate }}: {{ poll.typeVerbose | translate }}</div>
|
<div>{{ 'Poll type' | translate }}: {{ poll.typeVerbose | translate }}</div>
|
||||||
<div>{{ 'Poll method' | translate }}: {{ poll.pollmethodVerbose | translate }}</div>
|
<div>{{ 'Poll method' | translate }}: {{ poll.pollmethodVerbose | translate }}</div>
|
||||||
<div>{{ 'Majority method' | translate }}: {{ poll.majorityMethodVerbose | translate }}</div>
|
<div>{{ 'Majority method' | translate }}: {{ poll.majorityMethodVerbose | translate }}</div>
|
||||||
<div>{{ '100% base' | translate }}: {{ poll.percentBaseVerbose | translate }}</div>
|
<div>{{ '100% base' | translate }}: {{ poll.percentBaseVerbose | translate }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div *ngIf="poll.state === 3 || poll.state === 4">
|
|
||||||
<h2 translate>Result</h2>
|
|
||||||
<!-- <div class="chart-wrapper">
|
|
||||||
<mat-form-field>
|
|
||||||
<mat-select [(ngModel)]="chartType" [placeholder]="'Display as' | translate">
|
|
||||||
<mat-option [value]="'horizontalBar'">{{ 'Bar' | translate }}</mat-option>
|
|
||||||
<mat-option [value]="'doughnut'">{{ 'Circle' | translate }}</mat-option>
|
|
||||||
</mat-select>
|
|
||||||
</mat-form-field>
|
|
||||||
</div>
|
|
||||||
<os-charts [type]="chartType" [labels]="labels" [showLegend]="true" [data]="chartDataSubject"></os-charts> -->
|
|
||||||
|
|
||||||
<div *ngIf="poll.type === 'named'" style="display: grid; grid-template-columns: max-content auto;grid-column-gap: 20px;">
|
|
||||||
<ng-container *ngFor="let vote of poll.options[0].votes">
|
|
||||||
<div *ngIf="vote.user">{{ vote.user.full_name }}</div>
|
|
||||||
<div *ngIf="!vote.user">{{ "Unknown user" | translate}}</div>
|
|
||||||
<div>{{ vote.valueVerbose }}</div>
|
|
||||||
</ng-container>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
||||||
|
@ -2,8 +2,13 @@
|
|||||||
padding-top: 10px;
|
padding-top: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.result-title {
|
||||||
|
border-bottom: 1px solid rgba(0, 0, 0, 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
.chart-wrapper {
|
.chart-wrapper {
|
||||||
display: flex;
|
padding: 16px;
|
||||||
|
text-align: center;
|
||||||
justify-content: space-around;
|
justify-content: space-around;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
* {
|
* {
|
||||||
|
@ -1,23 +1,40 @@
|
|||||||
import { Component } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
import { MatSnackBar } from '@angular/material';
|
import { MatSnackBar } from '@angular/material';
|
||||||
import { Title } from '@angular/platform-browser';
|
import { Title } from '@angular/platform-browser';
|
||||||
import { ActivatedRoute } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
|
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
|
||||||
|
import { OperatorService } from 'app/core/core-services/operator.service';
|
||||||
import { MotionPollRepositoryService } from 'app/core/repositories/motions/motion-poll-repository.service';
|
import { MotionPollRepositoryService } from 'app/core/repositories/motions/motion-poll-repository.service';
|
||||||
|
import { MotionRepositoryService } from 'app/core/repositories/motions/motion-repository.service';
|
||||||
import { GroupRepositoryService } from 'app/core/repositories/users/group-repository.service';
|
import { GroupRepositoryService } from 'app/core/repositories/users/group-repository.service';
|
||||||
import { PromptService } from 'app/core/ui-services/prompt.service';
|
import { PromptService } from 'app/core/ui-services/prompt.service';
|
||||||
|
import { ChartType } from 'app/shared/components/charts/charts.component';
|
||||||
import { ViewMotionPoll } from 'app/site/motions/models/view-motion-poll';
|
import { ViewMotionPoll } from 'app/site/motions/models/view-motion-poll';
|
||||||
import { MotionPollDialogService } from 'app/site/motions/services/motion-poll-dialog.service';
|
import { MotionPollDialogService } from 'app/site/motions/services/motion-poll-dialog.service';
|
||||||
import { BasePollDetailComponent } from 'app/site/polls/components/base-poll-detail.component';
|
import { BasePollDetailComponent } from 'app/site/polls/components/base-poll-detail.component';
|
||||||
|
// import { MotionRepositoryService } from 'app/core/repositories/motions/motion-repository.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'os-motion-poll-detail',
|
selector: 'os-motion-poll-detail',
|
||||||
templateUrl: './motion-poll-detail.component.html',
|
templateUrl: './motion-poll-detail.component.html',
|
||||||
styleUrls: ['./motion-poll-detail.component.scss']
|
styleUrls: ['./motion-poll-detail.component.scss']
|
||||||
})
|
})
|
||||||
export class MotionPollDetailComponent extends BasePollDetailComponent<ViewMotionPoll> {
|
export class MotionPollDetailComponent extends BasePollDetailComponent<ViewMotionPoll> implements OnInit {
|
||||||
|
public motionTitle = '';
|
||||||
|
public columnDefinition = ['key', 'value'];
|
||||||
|
|
||||||
|
public set chartType(type: ChartType) {
|
||||||
|
this._chartType = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get chartType(): ChartType {
|
||||||
|
return this._chartType;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _chartType: ChartType = 'doughnut';
|
||||||
|
|
||||||
public constructor(
|
public constructor(
|
||||||
title: Title,
|
title: Title,
|
||||||
translate: TranslateService,
|
translate: TranslateService,
|
||||||
@ -26,8 +43,27 @@ export class MotionPollDetailComponent extends BasePollDetailComponent<ViewMotio
|
|||||||
route: ActivatedRoute,
|
route: ActivatedRoute,
|
||||||
groupRepo: GroupRepositoryService,
|
groupRepo: GroupRepositoryService,
|
||||||
prompt: PromptService,
|
prompt: PromptService,
|
||||||
pollDialog: MotionPollDialogService
|
pollDialog: MotionPollDialogService,
|
||||||
|
private operator: OperatorService,
|
||||||
|
private router: Router,
|
||||||
|
private motionRepo: MotionRepositoryService
|
||||||
) {
|
) {
|
||||||
super(title, translate, matSnackbar, repo, route, groupRepo, prompt, pollDialog);
|
super(title, translate, matSnackbar, repo, route, groupRepo, prompt, pollDialog);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected onPollLoaded(): void {
|
||||||
|
this.motionTitle = this.motionRepo.getViewModel((<ViewMotionPoll>this.poll).motion_id).getTitle();
|
||||||
|
}
|
||||||
|
|
||||||
|
public openDialog(): void {
|
||||||
|
this.pollDialog.openDialog(this.poll);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected onDeleted(): void {
|
||||||
|
this.router.navigate(['motions', (<ViewMotionPoll>this.poll).motion_id]);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected hasPerms(): boolean {
|
||||||
|
return this.operator.hasPerms('motions.can_manage_polls');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,6 @@
|
|||||||
formControlName="N"
|
formControlName="N"
|
||||||
></os-check-input>
|
></os-check-input>
|
||||||
<os-check-input
|
<os-check-input
|
||||||
*ngIf="pollForm.contentForm.get('pollmethod').value === motionPollMethods.YNA"
|
|
||||||
[placeholder]="'Abstain' | translate"
|
[placeholder]="'Abstain' | translate"
|
||||||
[checkboxValue]="-1"
|
[checkboxValue]="-1"
|
||||||
inputType="number"
|
inputType="number"
|
||||||
@ -46,10 +45,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<mat-divider></mat-divider>
|
<mat-divider></mat-divider>
|
||||||
<div class="spacer-top-20">
|
<div class="spacer-top-20">
|
||||||
<mat-checkbox
|
<mat-checkbox [(ngModel)]="publishImmediately" (change)="publishStateChanged($event.checked)">
|
||||||
[(ngModel)]="publishImmediately"
|
|
||||||
(change)="publishStateChanged($event.checked)"
|
|
||||||
>
|
|
||||||
<span translate>Publish immediately</span>
|
<span translate>Publish immediately</span>
|
||||||
</mat-checkbox>
|
</mat-checkbox>
|
||||||
<mat-error *ngIf="!dialogVoteForm.valid" translate>
|
<mat-error *ngIf="!dialogVoteForm.valid" translate>
|
||||||
|
@ -5,7 +5,6 @@ import { Title } from '@angular/platform-browser';
|
|||||||
|
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
|
||||||
import { MotionPollMethods } from 'app/shared/models/motions/motion-poll';
|
|
||||||
import { ViewMotionPoll } from 'app/site/motions/models/view-motion-poll';
|
import { ViewMotionPoll } from 'app/site/motions/models/view-motion-poll';
|
||||||
import { MotionPollMethodsVerbose } from 'app/site/motions/models/view-motion-poll';
|
import { MotionPollMethodsVerbose } from 'app/site/motions/models/view-motion-poll';
|
||||||
import { BasePollDialogComponent } from 'app/site/polls/components/base-poll-dialog.component';
|
import { BasePollDialogComponent } from 'app/site/polls/components/base-poll-dialog.component';
|
||||||
@ -17,7 +16,7 @@ import { PollFormComponent } from 'app/site/polls/components/poll-form/poll-form
|
|||||||
styleUrls: ['./motion-poll-dialog.component.scss']
|
styleUrls: ['./motion-poll-dialog.component.scss']
|
||||||
})
|
})
|
||||||
export class MotionPollDialogComponent extends BasePollDialogComponent {
|
export class MotionPollDialogComponent extends BasePollDialogComponent {
|
||||||
public motionPollMethods = MotionPollMethodsVerbose;
|
public motionPollMethods = { YNA: MotionPollMethodsVerbose.YNA };
|
||||||
|
|
||||||
@ViewChild('pollForm', { static: false })
|
@ViewChild('pollForm', { static: false })
|
||||||
protected pollForm: PollFormComponent;
|
protected pollForm: PollFormComponent;
|
||||||
@ -38,13 +37,14 @@ export class MotionPollDialogComponent extends BasePollDialogComponent {
|
|||||||
const update: any = {
|
const update: any = {
|
||||||
Y: data.options[0].yes,
|
Y: data.options[0].yes,
|
||||||
N: data.options[0].no,
|
N: data.options[0].no,
|
||||||
|
A: data.options[0].abstain,
|
||||||
votesvalid: data.votesvalid,
|
votesvalid: data.votesvalid,
|
||||||
votesinvalid: data.votesinvalid,
|
votesinvalid: data.votesinvalid,
|
||||||
votescast: data.votescast
|
votescast: data.votescast
|
||||||
};
|
};
|
||||||
if (data.pollmethod === 'YNA') {
|
// if (data.pollmethod === 'YNA') {
|
||||||
update.A = data.options[0].abstain;
|
// update.A = data.options[0].abstain;
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (this.dialogVoteForm) {
|
if (this.dialogVoteForm) {
|
||||||
const result = this.undoReplaceEmptyValues(update);
|
const result = this.undoReplaceEmptyValues(update);
|
||||||
@ -59,13 +59,14 @@ export class MotionPollDialogComponent extends BasePollDialogComponent {
|
|||||||
this.dialogVoteForm = this.fb.group({
|
this.dialogVoteForm = this.fb.group({
|
||||||
Y: ['', [Validators.min(-2)]],
|
Y: ['', [Validators.min(-2)]],
|
||||||
N: ['', [Validators.min(-2)]],
|
N: ['', [Validators.min(-2)]],
|
||||||
|
A: ['', [Validators.min(-2)]],
|
||||||
votesvalid: ['', [Validators.min(-2)]],
|
votesvalid: ['', [Validators.min(-2)]],
|
||||||
votesinvalid: ['', [Validators.min(-2)]],
|
votesinvalid: ['', [Validators.min(-2)]],
|
||||||
votescast: ['', [Validators.min(-2)]]
|
votescast: ['', [Validators.min(-2)]]
|
||||||
});
|
});
|
||||||
if (this.pollData.pollmethod === MotionPollMethods.YNA) {
|
// if (this.pollData.pollmethod === MotionPollMethods.YNA) {
|
||||||
this.dialogVoteForm.addControl('A', this.fb.control('', [Validators.min(-2)]));
|
// this.dialogVoteForm.addControl('A', this.fb.control('', [Validators.min(-2)]));
|
||||||
}
|
// }
|
||||||
if (this.pollData.poll) {
|
if (this.pollData.poll) {
|
||||||
this.updateDialogVoteForm(this.pollData);
|
this.updateDialogVoteForm(this.pollData);
|
||||||
}
|
}
|
||||||
|
@ -31,8 +31,13 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="poll-chart-wrapper" *ngIf="poll">
|
<div class="poll-chart-wrapper" *ngIf="poll">
|
||||||
<ng-container *ngIf="poll.state === 3 || poll.state === 4" [ngTemplateOutlet]="viewTemplate"></ng-container>
|
<div *ngIf="poll.type === 'analog' || poll.state === 3 || poll.state === 4" (click)="openPoll()">
|
||||||
<ng-container *ngIf="(poll.state === 1 || poll.state === 2) && poll.type === 'analog'" [ngTemplateOutlet]="emptyTemplate"></ng-container>
|
<ng-container *ngIf="poll.state === 3 || poll.state === 4" [ngTemplateOutlet]="viewTemplate"></ng-container>
|
||||||
|
<ng-container
|
||||||
|
*ngIf="(poll.state === 1 || poll.state === 2) && poll.type === 'analog'"
|
||||||
|
[ngTemplateOutlet]="emptyTemplate"
|
||||||
|
></ng-container>
|
||||||
|
</div>
|
||||||
<ng-container *ngIf="(poll.state === 1 || poll.state === 2) && poll.type !== 'analog'">
|
<ng-container *ngIf="(poll.state === 1 || poll.state === 2) && poll.type !== 'analog'">
|
||||||
<os-motion-poll-vote [poll]="poll"></os-motion-poll-vote>
|
<os-motion-poll-vote [poll]="poll"></os-motion-poll-vote>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
@ -45,7 +50,7 @@
|
|||||||
<mat-icon>close</mat-icon>
|
<mat-icon>close</mat-icon>
|
||||||
: {{ voteNo }}
|
: {{ voteNo }}
|
||||||
</div>
|
</div>
|
||||||
<div class="doughnut-chart">
|
<div *ngIf="showChart" class="doughnut-chart">
|
||||||
<os-charts [type]="'doughnut'" [data]="chartDataSubject" [showLegend]="false"> </os-charts>
|
<os-charts [type]="'doughnut'" [data]="chartDataSubject" [showLegend]="false"> </os-charts>
|
||||||
</div>
|
</div>
|
||||||
<div class="chart-wrapper-right">
|
<div class="chart-wrapper-right">
|
||||||
@ -62,11 +67,7 @@
|
|||||||
|
|
||||||
<mat-menu #triggerMenu="matMenu">
|
<mat-menu #triggerMenu="matMenu">
|
||||||
<ng-container *ngIf="poll">
|
<ng-container *ngIf="poll">
|
||||||
<button
|
<button mat-menu-item (click)="changeState(state.value)" *ngFor="let state of poll.nextStates | keyvalue">
|
||||||
mat-menu-item
|
|
||||||
(click)="changeState(state.value)"
|
|
||||||
*ngFor="let state of poll.nextStates | keyvalue"
|
|
||||||
>
|
|
||||||
<span translate>{{ state.key }}</span>
|
<span translate>{{ state.key }}</span>
|
||||||
</button>
|
</button>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
import { Component, Input } from '@angular/core';
|
import { Component, Input } from '@angular/core';
|
||||||
import { MatDialog, MatSnackBar } from '@angular/material';
|
import { MatDialog, MatSnackBar } from '@angular/material';
|
||||||
import { Title } from '@angular/platform-browser';
|
import { Title } from '@angular/platform-browser';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { BehaviorSubject } from 'rxjs';
|
import { BehaviorSubject } from 'rxjs';
|
||||||
|
|
||||||
|
import { OperatorService } from 'app/core/core-services/operator.service';
|
||||||
import { MotionPollRepositoryService } from 'app/core/repositories/motions/motion-poll-repository.service';
|
import { MotionPollRepositoryService } from 'app/core/repositories/motions/motion-poll-repository.service';
|
||||||
import { PromptService } from 'app/core/ui-services/prompt.service';
|
import { PromptService } from 'app/core/ui-services/prompt.service';
|
||||||
import { ChartData } from 'app/shared/components/charts/charts.component';
|
import { ChartData } from 'app/shared/components/charts/charts.component';
|
||||||
@ -28,7 +30,7 @@ export class MotionPollComponent extends BasePollComponent<ViewMotionPoll> {
|
|||||||
*/
|
*/
|
||||||
@Input()
|
@Input()
|
||||||
public set poll(value: ViewMotionPoll) {
|
public set poll(value: ViewMotionPoll) {
|
||||||
this._poll = value;
|
this.initPoll(value);
|
||||||
|
|
||||||
const chartData = this.poll.generateChartData();
|
const chartData = this.poll.generateChartData();
|
||||||
for (const data of chartData) {
|
for (const data of chartData) {
|
||||||
@ -54,17 +56,33 @@ export class MotionPollComponent extends BasePollComponent<ViewMotionPoll> {
|
|||||||
/**
|
/**
|
||||||
* Number of votes for `Yes`.
|
* Number of votes for `Yes`.
|
||||||
*/
|
*/
|
||||||
public voteYes = 0;
|
// public voteYes = 0;
|
||||||
|
public set voteYes(n: number | string) {
|
||||||
|
this._voteYes = n;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get voteYes(): number | string {
|
||||||
|
return this.verboseForNumber(this._voteYes as number);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Number of votes for `No`.
|
* Number of votes for `No`.
|
||||||
*/
|
*/
|
||||||
public voteNo = 0;
|
public set voteNo(n: number | string) {
|
||||||
|
this._voteNo = n;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
public get voteNo(): number | string {
|
||||||
* The motion-poll.
|
return this.verboseForNumber(this._voteNo as number);
|
||||||
*/
|
}
|
||||||
private _poll: ViewMotionPoll;
|
|
||||||
|
public get showChart(): boolean {
|
||||||
|
return this._voteYes >= 0 && this._voteNo >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _voteNo: number | string = 0;
|
||||||
|
|
||||||
|
private _voteYes: number | string = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor.
|
* Constructor.
|
||||||
@ -81,10 +99,30 @@ export class MotionPollComponent extends BasePollComponent<ViewMotionPoll> {
|
|||||||
translate: TranslateService,
|
translate: TranslateService,
|
||||||
dialog: MatDialog,
|
dialog: MatDialog,
|
||||||
promptService: PromptService,
|
promptService: PromptService,
|
||||||
public repo: MotionPollRepositoryService,
|
public pollRepo: MotionPollRepositoryService,
|
||||||
pollDialog: MotionPollDialogService,
|
pollDialog: MotionPollDialogService,
|
||||||
public pollService: PollService
|
public pollService: PollService,
|
||||||
|
private router: Router,
|
||||||
|
private operator: OperatorService
|
||||||
) {
|
) {
|
||||||
super(titleService, matSnackBar, translate, dialog, promptService, repo, pollDialog);
|
super(titleService, matSnackBar, translate, dialog, promptService, pollRepo, pollDialog);
|
||||||
|
}
|
||||||
|
|
||||||
|
public openPoll(): void {
|
||||||
|
if (this.operator.hasPerms('motions.can_manage_polls')) {
|
||||||
|
this.router.navigate(['motions', 'polls', this.poll.id]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private verboseForNumber(input: number): number | string {
|
||||||
|
input = Math.trunc(input);
|
||||||
|
switch (input) {
|
||||||
|
case -1:
|
||||||
|
return 'Majority';
|
||||||
|
case -2:
|
||||||
|
return 'Not documented';
|
||||||
|
default:
|
||||||
|
return input;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -52,7 +52,7 @@ export class MotionPollService extends PollService {
|
|||||||
const length = this.pollRepo.getViewModelList().filter(item => item.motion_id === poll.motion_id).length;
|
const length = this.pollRepo.getViewModelList().filter(item => item.motion_id === poll.motion_id).length;
|
||||||
|
|
||||||
poll.title = !length ? this.translate.instant('Vote') : `${this.translate.instant('Vote')} (${length + 1})`;
|
poll.title = !length ? this.translate.instant('Vote') : `${this.translate.instant('Vote')} (${length + 1})`;
|
||||||
poll.pollmethod = MotionPollMethods.YN;
|
poll.pollmethod = MotionPollMethods.YNA;
|
||||||
poll.motion_id = poll.motion_id;
|
poll.motion_id = poll.motion_id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,14 +11,14 @@ import { GroupRepositoryService } from 'app/core/repositories/users/group-reposi
|
|||||||
import { BasePollDialogService } from 'app/core/ui-services/base-poll-dialog.service';
|
import { BasePollDialogService } from 'app/core/ui-services/base-poll-dialog.service';
|
||||||
import { PromptService } from 'app/core/ui-services/prompt.service';
|
import { PromptService } from 'app/core/ui-services/prompt.service';
|
||||||
import { Breadcrumb } from 'app/shared/components/breadcrumb/breadcrumb.component';
|
import { Breadcrumb } from 'app/shared/components/breadcrumb/breadcrumb.component';
|
||||||
import { ChartData } from 'app/shared/components/charts/charts.component';
|
import { ChartData, ChartType } from 'app/shared/components/charts/charts.component';
|
||||||
import { PollState } from 'app/shared/models/poll/base-poll';
|
import { PollState, PollType } from 'app/shared/models/poll/base-poll';
|
||||||
import { BaseViewComponent } from 'app/site/base/base-view';
|
import { BaseViewComponent } from 'app/site/base/base-view';
|
||||||
import { ViewGroup } from 'app/site/users/models/view-group';
|
import { ViewGroup } from 'app/site/users/models/view-group';
|
||||||
import { BasePollRepositoryService } from '../services/base-poll-repository.service';
|
import { BasePollRepositoryService } from '../services/base-poll-repository.service';
|
||||||
import { ViewBasePoll } from '../models/view-base-poll';
|
import { ViewBasePoll } from '../models/view-base-poll';
|
||||||
|
|
||||||
export class BasePollDetailComponent<V extends ViewBasePoll> extends BaseViewComponent implements OnInit {
|
export abstract class BasePollDetailComponent<V extends ViewBasePoll> extends BaseViewComponent implements OnInit {
|
||||||
/**
|
/**
|
||||||
* All the groups of users.
|
* All the groups of users.
|
||||||
*/
|
*/
|
||||||
@ -42,7 +42,8 @@ export class BasePollDetailComponent<V extends ViewBasePoll> extends BaseViewCom
|
|||||||
/**
|
/**
|
||||||
* Sets the type of the shown chart, if votes are entered.
|
* Sets the type of the shown chart, if votes are entered.
|
||||||
*/
|
*/
|
||||||
public chartType = 'horizontalBar';
|
// public chartType = 'horizontalBar';
|
||||||
|
public abstract get chartType(): ChartType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The different labels for the votes (used for chart).
|
* The different labels for the votes (used for chart).
|
||||||
@ -99,7 +100,7 @@ export class BasePollDetailComponent<V extends ViewBasePoll> extends BaseViewCom
|
|||||||
const text = 'Do you really want to delete the selected poll?';
|
const text = 'Do you really want to delete the selected poll?';
|
||||||
|
|
||||||
if (await this.promptDialog.open(title, text)) {
|
if (await this.promptDialog.open(title, text)) {
|
||||||
await this.repo.delete(this.poll);
|
this.repo.delete(this.poll).then(() => this.onDeleted());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -121,12 +122,23 @@ export class BasePollDetailComponent<V extends ViewBasePoll> extends BaseViewCom
|
|||||||
this.chartDataSubject.next(this.poll.generateChartData());
|
this.chartDataSubject.next(this.poll.generateChartData());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected onDeleted(): void {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called after the poll has been loaded. Meant to be overwritten by subclasses who need initial access to the poll
|
||||||
|
*/
|
||||||
|
protected onPollLoaded(): void {}
|
||||||
|
|
||||||
|
protected onStateChanged(): void {}
|
||||||
|
|
||||||
|
protected abstract hasPerms(): boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This checks, if the poll has votes.
|
* This checks, if the poll has votes.
|
||||||
*/
|
*/
|
||||||
private checkData(): void {
|
private checkData(): void {
|
||||||
if (this.poll.state === 3 || this.poll.state === 4) {
|
if (this.poll.state === 3 || this.poll.state === 4) {
|
||||||
// this.chartDataSubject.next(this.poll.generateChartData());
|
setTimeout(() => this.chartDataSubject.next(this.poll.generateChartData()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -142,7 +154,6 @@ export class BasePollDetailComponent<V extends ViewBasePoll> extends BaseViewCom
|
|||||||
this.poll = poll;
|
this.poll = poll;
|
||||||
this.updateBreadcrumbs();
|
this.updateBreadcrumbs();
|
||||||
this.checkData();
|
this.checkData();
|
||||||
this.labels = this.createChartLabels();
|
|
||||||
this.onPollLoaded();
|
this.onPollLoaded();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -157,23 +168,18 @@ export class BasePollDetailComponent<V extends ViewBasePoll> extends BaseViewCom
|
|||||||
this.pollDialog.openDialog(this.poll);
|
this.pollDialog.openDialog(this.poll);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Called after the poll has been loaded. Meant to be overwritten by subclasses who need initial access to the poll
|
|
||||||
*/
|
|
||||||
public onPollLoaded(): void {}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Action for the different breadcrumbs.
|
* Action for the different breadcrumbs.
|
||||||
*/
|
*/
|
||||||
private async changeState(): Promise<void> {
|
private async changeState(): Promise<void> {
|
||||||
this.actionWrapper(this.repo.changePollState(this.poll));
|
this.actionWrapper(this.repo.changePollState(this.poll), this.onStateChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resets the state of a motion-poll.
|
* Resets the state of a motion-poll.
|
||||||
*/
|
*/
|
||||||
private async resetState(): Promise<void> {
|
private async resetState(): Promise<void> {
|
||||||
this.actionWrapper(this.repo.resetPoll(this.poll));
|
this.actionWrapper(this.repo.resetPoll(this.poll), this.onStateChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -183,17 +189,15 @@ export class BasePollDetailComponent<V extends ViewBasePoll> extends BaseViewCom
|
|||||||
*
|
*
|
||||||
* @returns Any promise-like.
|
* @returns Any promise-like.
|
||||||
*/
|
*/
|
||||||
private actionWrapper(action: Promise<any>): any {
|
private actionWrapper(action: Promise<any>, callback?: () => any): any {
|
||||||
action.then(() => this.checkData()).catch(this.raiseError);
|
action
|
||||||
}
|
.then(() => {
|
||||||
|
this.checkData();
|
||||||
/**
|
if (callback) {
|
||||||
* Function to create the labels for the chart.
|
callback();
|
||||||
*
|
}
|
||||||
* @returns An array of `Label`.
|
})
|
||||||
*/
|
.catch(this.raiseError);
|
||||||
private createChartLabels(): Label[] {
|
|
||||||
return ['Number of votes'];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -220,11 +224,14 @@ export class BasePollDetailComponent<V extends ViewBasePoll> extends BaseViewCom
|
|||||||
if (!this.poll) {
|
if (!this.poll) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
if (!this.hasPerms()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
switch (this.poll.state) {
|
switch (this.poll.state) {
|
||||||
case PollState.Created:
|
case PollState.Created:
|
||||||
return state === 2 ? () => this.changeState() : null;
|
return state === 2 ? () => this.changeState() : null;
|
||||||
case PollState.Started:
|
case PollState.Started:
|
||||||
return null;
|
return this.poll.type !== PollType.Analog && state === 3 ? () => this.changeState() : null;
|
||||||
case PollState.Finished:
|
case PollState.Finished:
|
||||||
if (state === 1) {
|
if (state === 1) {
|
||||||
return () => this.resetState();
|
return () => this.resetState();
|
||||||
|
@ -1,23 +1,28 @@
|
|||||||
import { Input } from '@angular/core';
|
|
||||||
import { MatDialog } from '@angular/material/dialog';
|
import { MatDialog } from '@angular/material/dialog';
|
||||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||||
import { Title } from '@angular/platform-browser';
|
import { Title } from '@angular/platform-browser';
|
||||||
|
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
import { BehaviorSubject } from 'rxjs';
|
||||||
|
|
||||||
import { BasePollDialogService } from 'app/core/ui-services/base-poll-dialog.service';
|
import { BasePollDialogService } from 'app/core/ui-services/base-poll-dialog.service';
|
||||||
import { PromptService } from 'app/core/ui-services/prompt.service';
|
import { PromptService } from 'app/core/ui-services/prompt.service';
|
||||||
|
import { ChartData } from 'app/shared/components/charts/charts.component';
|
||||||
import { PollState } from 'app/shared/models/poll/base-poll';
|
import { PollState } from 'app/shared/models/poll/base-poll';
|
||||||
import { BaseViewComponent } from 'app/site/base/base-view';
|
import { BaseViewComponent } from 'app/site/base/base-view';
|
||||||
import { BasePollRepositoryService } from '../services/base-poll-repository.service';
|
import { BasePollRepositoryService } from '../services/base-poll-repository.service';
|
||||||
import { ViewBasePoll } from '../models/view-base-poll';
|
import { ViewBasePoll } from '../models/view-base-poll';
|
||||||
|
|
||||||
export class BasePollComponent<V extends ViewBasePoll> extends BaseViewComponent {
|
export abstract class BasePollComponent<V extends ViewBasePoll> extends BaseViewComponent {
|
||||||
/**
|
// /**
|
||||||
* The poll represented in this component
|
// * The poll represented in this component
|
||||||
*/
|
// */
|
||||||
@Input()
|
// @Input()
|
||||||
public poll: V;
|
// public abstract set poll(model: V);
|
||||||
|
|
||||||
|
public chartDataSubject: BehaviorSubject<ChartData> = new BehaviorSubject([]);
|
||||||
|
|
||||||
|
protected _poll: V;
|
||||||
|
|
||||||
public constructor(
|
public constructor(
|
||||||
titleService: Title,
|
titleService: Title,
|
||||||
@ -25,14 +30,14 @@ export class BasePollComponent<V extends ViewBasePoll> extends BaseViewComponent
|
|||||||
public translate: TranslateService,
|
public translate: TranslateService,
|
||||||
public dialog: MatDialog,
|
public dialog: MatDialog,
|
||||||
protected promptService: PromptService,
|
protected promptService: PromptService,
|
||||||
public repo: BasePollRepositoryService,
|
protected repo: BasePollRepositoryService,
|
||||||
protected pollDialog: BasePollDialogService<V>
|
protected pollDialog: BasePollDialogService<V>
|
||||||
) {
|
) {
|
||||||
super(titleService, translate, matSnackBar);
|
super(titleService, translate, matSnackBar);
|
||||||
}
|
}
|
||||||
|
|
||||||
public changeState(key: PollState): void {
|
public changeState(key: PollState): void {
|
||||||
key === PollState.Created ? this.repo.resetPoll(this.poll) : this.repo.changePollState(this.poll);
|
key === PollState.Created ? this.repo.resetPoll(this._poll) : this.repo.changePollState(this._poll);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -41,7 +46,7 @@ export class BasePollComponent<V extends ViewBasePoll> extends BaseViewComponent
|
|||||||
public async onDeletePoll(): Promise<void> {
|
public async onDeletePoll(): Promise<void> {
|
||||||
const title = this.translate.instant('Are you sure you want to delete this poll?');
|
const title = this.translate.instant('Are you sure you want to delete this poll?');
|
||||||
if (await this.promptService.open(title)) {
|
if (await this.promptService.open(title)) {
|
||||||
await this.repo.delete(this.poll).catch(this.raiseError);
|
await this.repo.delete(this._poll).catch(this.raiseError);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,6 +54,13 @@ export class BasePollComponent<V extends ViewBasePoll> extends BaseViewComponent
|
|||||||
* Edits the poll
|
* Edits the poll
|
||||||
*/
|
*/
|
||||||
public openDialog(): void {
|
public openDialog(): void {
|
||||||
this.pollDialog.openDialog(this.poll);
|
this.pollDialog.openDialog(this._poll);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Forces to initialize the poll.
|
||||||
|
*/
|
||||||
|
protected initPoll(model: V): void {
|
||||||
|
this._poll = model;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -47,13 +47,9 @@
|
|||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<mat-form-field>
|
<mat-form-field>
|
||||||
<mat-select
|
<mat-select placeholder="{{ '100% base' | translate }}" formControlName="onehundred_percent_base" required>
|
||||||
placeholder="{{ '100% base' | translate }}"
|
|
||||||
formControlName="onehundred_percent_base"
|
|
||||||
required
|
|
||||||
>
|
|
||||||
<ng-container *ngFor="let option of percentBases | keyvalue">
|
<ng-container *ngFor="let option of percentBases | keyvalue">
|
||||||
<mat-option [value]="option.key">
|
<mat-option *ngIf="isValidPercentBaseWithMethod(option.key)" [value]="option.key">
|
||||||
{{ option.value | translate }}
|
{{ option.value | translate }}
|
||||||
</mat-option>
|
</mat-option>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
@ -68,4 +64,3 @@
|
|||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -63,6 +63,12 @@ export class PollFormComponent extends BaseViewComponent implements OnInit {
|
|||||||
*/
|
*/
|
||||||
public pollValues: [string, unknown][] = [];
|
public pollValues: [string, unknown][] = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Model for the checkbox.
|
||||||
|
* If true, the given poll will immediately be published.
|
||||||
|
*/
|
||||||
|
public publishImmediately = true;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor. Retrieves necessary metadata from the pollService,
|
* Constructor. Retrieves necessary metadata from the pollService,
|
||||||
* injects the poll itself
|
* injects the poll itself
|
||||||
@ -73,18 +79,10 @@ export class PollFormComponent extends BaseViewComponent implements OnInit {
|
|||||||
snackbar: MatSnackBar,
|
snackbar: MatSnackBar,
|
||||||
private fb: FormBuilder,
|
private fb: FormBuilder,
|
||||||
private groupRepo: GroupRepositoryService,
|
private groupRepo: GroupRepositoryService,
|
||||||
private pollService: PollService
|
public pollService: PollService
|
||||||
) {
|
) {
|
||||||
super(title, translate, snackbar);
|
super(title, translate, snackbar);
|
||||||
|
this.initContentForm();
|
||||||
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: [[]]
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -133,6 +131,10 @@ export class PollFormComponent extends BaseViewComponent implements OnInit {
|
|||||||
return { ...this.data, ...this.contentForm.value };
|
return { ...this.data, ...this.contentForm.value };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public isValidPercentBaseWithMethod(base: PercentBase): boolean {
|
||||||
|
return !(base === PercentBase.YNA && this.contentForm.get('pollmethod').value === 'YN');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This updates the poll-values to get correct data in the view.
|
* This updates the poll-values to get correct data in the view.
|
||||||
*
|
*
|
||||||
@ -152,4 +154,15 @@ export class PollFormComponent extends BaseViewComponent implements OnInit {
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private initContentForm(): 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: [[]]
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -53,6 +53,15 @@ export const PercentBaseVerbose = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export abstract class ViewBasePoll<M extends BasePoll<M, any> = any> extends BaseProjectableViewModel<M> {
|
export abstract class ViewBasePoll<M extends BasePoll<M, any> = any> extends BaseProjectableViewModel<M> {
|
||||||
|
private _tableData: {}[] = [];
|
||||||
|
|
||||||
|
public get tableData(): {}[] {
|
||||||
|
if (!this._tableData.length) {
|
||||||
|
this._tableData = this.generateTableData();
|
||||||
|
}
|
||||||
|
return this._tableData;
|
||||||
|
}
|
||||||
|
|
||||||
public get poll(): M {
|
public get poll(): M {
|
||||||
return this._model;
|
return this._model;
|
||||||
}
|
}
|
||||||
@ -101,7 +110,14 @@ export abstract class ViewBasePoll<M extends BasePoll<M, any> = any> extends Bas
|
|||||||
|
|
||||||
public abstract getSlide(): ProjectorElementBuildDeskriptor;
|
public abstract getSlide(): ProjectorElementBuildDeskriptor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes labels for a chart.
|
||||||
|
*/
|
||||||
|
public abstract initChartLabels(): string[];
|
||||||
|
|
||||||
public abstract generateChartData(): ChartData;
|
public abstract generateChartData(): ChartData;
|
||||||
|
|
||||||
|
public abstract generateTableData(): {}[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ViewBasePoll<M extends BasePoll<M, any> = any> extends BasePoll<M, any> {
|
export interface ViewBasePoll<M extends BasePoll<M, any> = any> extends BasePoll<M, any> {
|
||||||
|
@ -27,6 +27,7 @@
|
|||||||
@import './app/site/config/components/config-field/config-field.component.scss-theme.scss';
|
@import './app/site/config/components/config-field/config-field.component.scss-theme.scss';
|
||||||
@import './app/site/motions/modules/motion-detail/components/amendment-create-wizard/amendment-create-wizard.components.scss-theme.scss';
|
@import './app/site/motions/modules/motion-detail/components/amendment-create-wizard/amendment-create-wizard.components.scss-theme.scss';
|
||||||
@import './app/site/motions/modules/motion-detail/components/motion-detail-diff/motion-detail-diff.component.scss-theme.scss';
|
@import './app/site/motions/modules/motion-detail/components/motion-detail-diff/motion-detail-diff.component.scss-theme.scss';
|
||||||
|
@import './app/shared/components/banner/banner.component.scss-theme.scss';
|
||||||
|
|
||||||
/** fonts */
|
/** fonts */
|
||||||
@import './assets/styles/fonts.scss';
|
@import './assets/styles/fonts.scss';
|
||||||
@ -54,6 +55,7 @@ $narrow-spacing: (
|
|||||||
@include os-config-field-style($theme);
|
@include os-config-field-style($theme);
|
||||||
@include os-amendment-create-wizard-style($theme);
|
@include os-amendment-create-wizard-style($theme);
|
||||||
@include os-motion-detail-diff-style($theme);
|
@include os-motion-detail-diff-style($theme);
|
||||||
|
@include os-banner-style($theme);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Load projector specific SCSS values */
|
/** Load projector specific SCSS values */
|
||||||
|
Loading…
Reference in New Issue
Block a user