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"
|
||||
[ngClass]="(banner.type === 'history' ? 'history-mode-indicator' : '') + ' ' + (banner.class ? banner.class : '')"
|
||||
[ngSwitch]="banner.type"
|
||||
|
@ -4,12 +4,13 @@
|
||||
line-height: 20px;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
background-color: blue;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
text-decoration: none;
|
||||
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">
|
||||
<canvas
|
||||
*ngIf="type === 'bar' || type === 'line' || type === 'horizontalBar'"
|
||||
baseChart
|
||||
[datasets]="chartData"
|
||||
[labels]="labels"
|
||||
[legend]="showLegend"
|
||||
[options]="chartOptions"
|
||||
[chartType]="type"
|
||||
(chartClick)="select.emit($event)"
|
||||
(chartHover)="hover.emit($event)"
|
||||
>
|
||||
</canvas>
|
||||
<canvas
|
||||
*ngIf="type === 'pie' || type === 'doughnut'"
|
||||
baseChart
|
||||
[data]="circleData"
|
||||
[labels]="circleLabels"
|
||||
[colors]="circleColors"
|
||||
[chartType]="type"
|
||||
[legend]="showLegend"
|
||||
></canvas>
|
||||
<ng-container *ngIf="chartData.length || circleData.length">
|
||||
<canvas
|
||||
*ngIf="type === 'bar' || type === 'stackedBar' || type === 'horizontalBar' || type === 'line'"
|
||||
baseChart
|
||||
[datasets]="chartData"
|
||||
[labels]="labels"
|
||||
[legend]="showLegend"
|
||||
[options]="chartOptions"
|
||||
[chartType]="type"
|
||||
(chartClick)="select.emit($event)"
|
||||
(chartHover)="hover.emit($event)"
|
||||
>
|
||||
</canvas>
|
||||
<canvas
|
||||
*ngIf="type === 'pie' || type === 'doughnut'"
|
||||
baseChart
|
||||
[data]="circleData"
|
||||
[labels]="circleLabels"
|
||||
[colors]="circleColors"
|
||||
[chartType]="type"
|
||||
[legend]="showLegend"
|
||||
></canvas>
|
||||
</ng-container>
|
||||
</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 { Title } from '@angular/platform-browser';
|
||||
|
||||
@ -12,7 +12,7 @@ import { BaseViewComponent } from 'app/site/base/base-view';
|
||||
/**
|
||||
* 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.
|
||||
@ -30,6 +30,8 @@ export interface ChartDate {
|
||||
label: string;
|
||||
backgroundColor?: string;
|
||||
hoverBackgroundColor?: string;
|
||||
barThickness?: number;
|
||||
maxBarThickness?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -37,6 +39,8 @@ export interface ChartDate {
|
||||
*/
|
||||
export type ChartData = ChartDate[];
|
||||
|
||||
export type ChartLegendSize = 'small' | 'middle';
|
||||
|
||||
/**
|
||||
* Wrapper for the chart-library.
|
||||
*
|
||||
@ -45,7 +49,8 @@ export type ChartData = ChartDate[];
|
||||
@Component({
|
||||
selector: 'os-charts',
|
||||
templateUrl: './charts.component.html',
|
||||
styleUrls: ['./charts.component.scss']
|
||||
styleUrls: ['./charts.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class ChartsComponent extends BaseViewComponent {
|
||||
/**
|
||||
@ -57,15 +62,22 @@ export class ChartsComponent extends BaseViewComponent {
|
||||
public set data(dataObservable: Observable<ChartData>) {
|
||||
this.subscriptions.push(
|
||||
dataObservable.subscribe(data => {
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
data = data.flatMap((date: ChartDate) => ({ ...date, data: date.data.filter(value => value >= 0) }));
|
||||
this.chartData = data;
|
||||
this.circleData = data.flatMap((date: ChartDate) => date.data);
|
||||
this.circleLabels = data.map(date => date.label);
|
||||
this.circleColors = [
|
||||
const circleColors = [
|
||||
{
|
||||
backgroundColor: data.map(date => date.backgroundColor),
|
||||
hoverBackgroundColor: data.map(date => date.hoverBackgroundColor)
|
||||
backgroundColor: data.map(date => date.backgroundColor).filter(color => !!color),
|
||||
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()
|
||||
public set type(type: ChartType) {
|
||||
if (type === 'horizontalBar') {
|
||||
this.setupHorizontalBar();
|
||||
}
|
||||
this._type = type;
|
||||
this.checkChartType(type);
|
||||
this.cd.detectChanges();
|
||||
}
|
||||
|
||||
public get type(): ChartType {
|
||||
return this._type;
|
||||
}
|
||||
|
||||
@Input()
|
||||
public set chartLegendSize(size: ChartLegendSize) {
|
||||
this._chartLegendSize = size;
|
||||
this.setupChartLegendSize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether to show the legend.
|
||||
*/
|
||||
@ -147,11 +163,12 @@ export class ChartsComponent extends BaseViewComponent {
|
||||
responsive: true,
|
||||
legend: {
|
||||
position: 'top',
|
||||
labels: {
|
||||
fontSize: 14
|
||||
}
|
||||
labels: {}
|
||||
},
|
||||
scales: {
|
||||
xAxes: [{ ticks: { beginAtZero: true } }],
|
||||
yAxes: [{ ticks: { beginAtZero: true } }]
|
||||
},
|
||||
scales: { xAxes: [{}], yAxes: [{ ticks: { beginAtZero: true } }] },
|
||||
plugins: {
|
||||
datalabels: {
|
||||
anchor: 'end',
|
||||
@ -165,6 +182,8 @@ export class ChartsComponent extends BaseViewComponent {
|
||||
*/
|
||||
private _type: ChartType = 'bar';
|
||||
|
||||
private _chartLegendSize: ChartLegendSize = 'middle';
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
@ -173,17 +192,63 @@ export class ChartsComponent extends BaseViewComponent {
|
||||
* @param matSnackbar
|
||||
* @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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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, {
|
||||
xAxes: [{ 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
|
||||
if (formResult && formResult.userId) {
|
||||
this.addUser(formResult.userId);
|
||||
this.candidatesForm.setValue({ userId: null });
|
||||
}
|
||||
})
|
||||
);
|
||||
|
@ -1,7 +1,7 @@
|
||||
<os-head-bar
|
||||
[goBack]="true"
|
||||
[nav]="false"
|
||||
[hasMainButton]="poll && (poll.state === 2 || poll.state === 3)"
|
||||
[hasMainButton]="poll ? poll.type === 'analog' && (poll.state === 2 || poll.state === 3) : false"
|
||||
[mainButtonIcon]="'edit'"
|
||||
[mainActionTooltip]="'Edit' | translate"
|
||||
(mainEvent)="openDialog()"
|
||||
@ -23,7 +23,7 @@
|
||||
|
||||
<!-- Detailview for poll -->
|
||||
<ng-template #viewTemplate>
|
||||
<ng-container *ngIf="poll">
|
||||
<ng-container *ngIf="isReady">
|
||||
<h1>{{ poll.title }}</h1>
|
||||
<mat-divider></mat-divider>
|
||||
<os-breadcrumb [breadcrumbs]="breadcrumbs" [breadcrumbStyle]="'>'"></os-breadcrumb>
|
||||
@ -42,23 +42,62 @@
|
||||
<div *ngIf="poll.state === 3 || poll.state === 4">
|
||||
<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 -->
|
||||
<div></div>
|
||||
<!-- header (the assignment related users) -->
|
||||
<ng-container *ngFor="let option of poll.options">
|
||||
<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>
|
||||
<!-- rows -->
|
||||
<ng-container *ngFor="let obj of votesByUser | keyvalue">
|
||||
<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">
|
||||
<div>{{ obj.value.votes[option.user_id]}}</div>
|
||||
<div>{{ obj.value.votes[option.user_id] }}</div>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</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>
|
||||
</ng-container>
|
||||
</ng-template>
|
||||
|
@ -5,9 +5,11 @@ import { ActivatedRoute } from '@angular/router';
|
||||
|
||||
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 { GroupRepositoryService } from 'app/core/repositories/users/group-repository.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 { BasePollDetailComponent } from 'app/site/polls/components/base-poll-detail.component';
|
||||
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']
|
||||
})
|
||||
export class AssignmentPollDetailComponent extends BasePollDetailComponent<ViewAssignmentPoll> {
|
||||
public isReady = false;
|
||||
|
||||
public candidatesLabels: string[] = [];
|
||||
|
||||
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(
|
||||
title: Title,
|
||||
translate: TranslateService,
|
||||
@ -31,7 +49,8 @@ export class AssignmentPollDetailComponent extends BasePollDetailComponent<ViewA
|
||||
route: ActivatedRoute,
|
||||
groupRepo: GroupRepositoryService,
|
||||
prompt: PromptService,
|
||||
pollDialog: AssignmentPollDialogService
|
||||
pollDialog: AssignmentPollDialogService,
|
||||
private operator: OperatorService
|
||||
) {
|
||||
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);
|
||||
this.votesByUser = votes;
|
||||
}, 1000);
|
||||
this.candidatesLabels = this.poll.initChartLabels();
|
||||
this.isReady = true;
|
||||
});
|
||||
}
|
||||
|
||||
protected hasPerms(): boolean {
|
||||
return this.operator.hasPerms('assignments.can_manage');
|
||||
}
|
||||
}
|
||||
|
@ -1,16 +1,18 @@
|
||||
<ng-container *ngIf="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">
|
||||
<ng-container *ngFor="let option of poll.options">
|
||||
<div>
|
||||
<span *ngIf="option.user">{{ option.user.getFullName() }}</span>
|
||||
<span *ngIf="!option.user">No user {{ option.candidate_id }}</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="current-vote">
|
||||
<ng-container *ngIf="currentVotes[option.user_id] !== null">
|
||||
({{ "Current" | translate }}: {{ getCurrentVoteVerbose(option.user_id) | translate }})
|
||||
({{ 'Current' | translate }}: {{ getCurrentVoteVerbose(option.user_id) | translate }})
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
@ -31,12 +33,17 @@
|
||||
</mat-radio-group>
|
||||
|
||||
<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>
|
||||
</ng-container>
|
||||
</form>
|
||||
<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>
|
||||
</button>
|
||||
</div>
|
||||
@ -44,4 +51,4 @@
|
||||
<ng-container *ngIf="!vmanager.canVote(poll)">
|
||||
<span>{{ vmanager.getVotePermissionErrorVerbose(poll) | translate }}</span>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
|
@ -3,7 +3,7 @@
|
||||
<!-- Buttons -->
|
||||
<button
|
||||
mat-icon-button
|
||||
*osPerms="'assignments.can_manage'; 'core.can_manage_projector'"
|
||||
*osPerms="'assignments.can_manage'; "core.can_manage_projector""
|
||||
[matMenuTriggerFor]="pollItemMenu"
|
||||
(click)="$event.stopPropagation()"
|
||||
>
|
||||
@ -15,17 +15,6 @@
|
||||
<mat-icon>edit</mat-icon>
|
||||
<span translate>Edit</span>
|
||||
</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 *osPerms="'core.can_manage_projector'">
|
||||
<os-projector-button menuItem="true" [object]="poll"></os-projector-button>
|
||||
@ -40,200 +29,38 @@
|
||||
</mat-menu>
|
||||
</div>
|
||||
|
||||
<div class="poll-properties">
|
||||
<!-- <mat-chip *ngIf="pollService.isElectronicVotingEnabled">{{ poll.typeVerbose }}</mat-chip> -->
|
||||
<mat-chip
|
||||
class="poll-state active"
|
||||
[matMenuTriggerFor]="triggerMenu"
|
||||
[ngClass]="poll.stateVerbose.toLowerCase()"
|
||||
>
|
||||
{{ poll.stateVerbose }}
|
||||
</mat-chip>
|
||||
<!-- <mat-chip
|
||||
class="poll-state active"
|
||||
*ngIf="poll.state !== 2"
|
||||
[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>
|
||||
<div>
|
||||
<div>
|
||||
<div class="poll-properties">
|
||||
<!-- <mat-chip *ngIf="pollService.isElectronicVotingEnabled">{{ poll.typeVerbose }}</mat-chip> -->
|
||||
<mat-chip
|
||||
class="poll-state active"
|
||||
[matMenuTriggerFor]="triggerMenu"
|
||||
[ngClass]="poll.stateVerbose.toLowerCase()"
|
||||
>
|
||||
{{ poll.stateVerbose }}
|
||||
</mat-chip>
|
||||
</div>
|
||||
|
||||
<h3>
|
||||
<a routerLink="/assignments/polls/{{ poll.id }}">
|
||||
{{ poll.title }}
|
||||
</a>
|
||||
</h3>
|
||||
<h3>
|
||||
<a routerLink="/assignments/polls/{{ poll.id }}">
|
||||
{{ poll.title }}
|
||||
</a>
|
||||
</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>
|
||||
<!-- <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>
|
||||
|
||||
<ng-template #resultsTemplate>
|
||||
|
||||
</ng-template>
|
||||
<ng-template #resultsTemplate> </ng-template>
|
||||
|
||||
<mat-menu #triggerMenu="matMenu">
|
||||
<ng-container *ngIf="poll">
|
||||
<button
|
||||
mat-menu-item
|
||||
(click)="changeState(state.value)"
|
||||
*ngFor="let state of poll.nextStates | keyvalue"
|
||||
>
|
||||
<button mat-menu-item (click)="changeState(state.value)" *ngFor="let state of poll.nextStates | keyvalue">
|
||||
<span translate>{{ state.key }}</span>
|
||||
</button>
|
||||
</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 { MatDialog } from '@angular/material/dialog';
|
||||
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 { AssignmentPollRepositoryService } from 'app/core/repositories/assignments/assignment-poll-repository.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 { PollService } from 'app/site/polls/services/poll.service';
|
||||
import { AssignmentPollDialogService } from '../../services/assignment-poll-dialog.service';
|
||||
import { ViewAssignmentOption } from '../../models/view-assignment-option';
|
||||
import { ViewAssignmentPoll } from '../../models/view-assignment-poll';
|
||||
@ -24,6 +27,27 @@ import { ViewAssignmentPoll } from '../../models/view-assignment-poll';
|
||||
encapsulation: ViewEncapsulation.None
|
||||
})
|
||||
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
|
||||
*/
|
||||
@ -58,6 +82,7 @@ export class AssignmentPollComponent extends BasePollComponent<ViewAssignmentPol
|
||||
promptService: PromptService,
|
||||
repo: AssignmentPollRepositoryService,
|
||||
pollDialog: AssignmentPollDialogService,
|
||||
public pollService: PollService,
|
||||
private operator: OperatorService,
|
||||
private formBuilder: FormBuilder
|
||||
) {
|
||||
|
@ -1,5 +1,8 @@
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
|
||||
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 { ViewBasePoll } from 'app/site/polls/models/view-base-poll';
|
||||
import { ViewAssignment } from './view-assignment';
|
||||
@ -19,8 +22,13 @@ export class ViewAssignmentPoll extends ViewBasePoll<AssignmentPoll> implements
|
||||
public static COLLECTIONSTRING = AssignmentPoll.COLLECTIONSTRING;
|
||||
protected _collectionString = AssignmentPoll.COLLECTIONSTRING;
|
||||
|
||||
public readonly tableChartData: Map<string, BehaviorSubject<ChartData>> = new Map();
|
||||
public readonly pollClassType: 'assignment' | 'motion' = 'assignment';
|
||||
|
||||
public get pollmethodVerbose(): string {
|
||||
return AssignmentPollMethodsVerbose[this.pollmethod];
|
||||
}
|
||||
|
||||
public getSlide(): ProjectorElementBuildDeskriptor {
|
||||
// TODO: update to new voting system?
|
||||
return {
|
||||
@ -36,13 +44,43 @@ export class ViewAssignmentPoll extends ViewBasePoll<AssignmentPoll> implements
|
||||
};
|
||||
}
|
||||
|
||||
public get pollmethodVerbose(): string {
|
||||
return AssignmentPollMethodsVerbose[this.pollmethod];
|
||||
public initChartLabels(): string[] {
|
||||
return this.options.map(candidate => candidate.user.full_name);
|
||||
}
|
||||
|
||||
// TODO
|
||||
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';
|
||||
|
||||
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 {
|
||||
const fields = ['yes', 'no'];
|
||||
if (this.pollmethod === MotionPollMethods.YNA) {
|
||||
@ -27,18 +41,11 @@ export class ViewMotionPoll extends ViewBasePoll<MotionPoll> implements MotionPo
|
||||
}
|
||||
const data: ChartData = fields.map(key => ({
|
||||
label: key.toUpperCase(),
|
||||
data: [this.options[0][key]],
|
||||
data: this.options.map(option => option[key]),
|
||||
backgroundColor: PollColor[key],
|
||||
hoverBackgroundColor: PollColor[key]
|
||||
}));
|
||||
|
||||
data.push({
|
||||
label: 'Votes invalid',
|
||||
data: [this.votesinvalid],
|
||||
backgroundColor: PollColor.votesinvalid,
|
||||
hoverBackgroundColor: PollColor.votesinvalid
|
||||
});
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
|
@ -459,17 +459,13 @@
|
||||
|
||||
<!-- motion polls -->
|
||||
<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)">
|
||||
<button mat-button (click)="openDialog()">
|
||||
<mat-icon class="main-nav-color">poll</mat-icon>
|
||||
<span translate>New poll</span>
|
||||
</button>
|
||||
</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>
|
||||
</ng-template>
|
||||
|
@ -1,13 +1,13 @@
|
||||
<os-head-bar
|
||||
[goBack]="true"
|
||||
[nav]="false"
|
||||
[hasMainButton]="poll && (poll.state === 2 || poll.state === 3)"
|
||||
[hasMainButton]="poll ? poll.state === 2 || poll.state === 3 : false"
|
||||
[mainButtonIcon]="'edit'"
|
||||
[mainActionTooltip]="'Edit' | translate"
|
||||
(mainEvent)="openDialog()"
|
||||
>
|
||||
<div class="title-slot">
|
||||
<h2 *ngIf="!!poll">{{ poll.title }}</h2>
|
||||
<h2 *ngIf="!!poll">{{ motionTitle }}</h2>
|
||||
</div>
|
||||
|
||||
<div class="menu-slot" *osPerms="'agenda.can_manage'; or: 'agenda.can_see_list_of_speakers'">
|
||||
@ -27,38 +27,55 @@
|
||||
<h1>{{ poll.title }}</h1>
|
||||
<mat-divider></mat-divider>
|
||||
<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>{{ 'Current state' | translate }}: {{ poll.stateVerbose | translate }}</div>
|
||||
<div *ngIf="poll.groups && poll.type && poll.type !== 'analog'">
|
||||
{{ '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>{{ '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>
|
||||
|
||||
<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-template>
|
||||
|
||||
|
@ -2,8 +2,13 @@
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
.result-title {
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
.chart-wrapper {
|
||||
display: flex;
|
||||
padding: 16px;
|
||||
text-align: center;
|
||||
justify-content: space-around;
|
||||
align-items: center;
|
||||
* {
|
||||
|
@ -1,23 +1,40 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { MatSnackBar } from '@angular/material';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
|
||||
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 { MotionRepositoryService } from 'app/core/repositories/motions/motion-repository.service';
|
||||
import { GroupRepositoryService } from 'app/core/repositories/users/group-repository.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 { MotionPollDialogService } from 'app/site/motions/services/motion-poll-dialog.service';
|
||||
import { BasePollDetailComponent } from 'app/site/polls/components/base-poll-detail.component';
|
||||
// import { MotionRepositoryService } from 'app/core/repositories/motions/motion-repository.service';
|
||||
|
||||
@Component({
|
||||
selector: 'os-motion-poll-detail',
|
||||
templateUrl: './motion-poll-detail.component.html',
|
||||
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(
|
||||
title: Title,
|
||||
translate: TranslateService,
|
||||
@ -26,8 +43,27 @@ export class MotionPollDetailComponent extends BasePollDetailComponent<ViewMotio
|
||||
route: ActivatedRoute,
|
||||
groupRepo: GroupRepositoryService,
|
||||
prompt: PromptService,
|
||||
pollDialog: MotionPollDialogService
|
||||
pollDialog: MotionPollDialogService,
|
||||
private operator: OperatorService,
|
||||
private router: Router,
|
||||
private motionRepo: MotionRepositoryService
|
||||
) {
|
||||
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"
|
||||
></os-check-input>
|
||||
<os-check-input
|
||||
*ngIf="pollForm.contentForm.get('pollmethod').value === motionPollMethods.YNA"
|
||||
[placeholder]="'Abstain' | translate"
|
||||
[checkboxValue]="-1"
|
||||
inputType="number"
|
||||
@ -46,10 +45,7 @@
|
||||
</div>
|
||||
<mat-divider></mat-divider>
|
||||
<div class="spacer-top-20">
|
||||
<mat-checkbox
|
||||
[(ngModel)]="publishImmediately"
|
||||
(change)="publishStateChanged($event.checked)"
|
||||
>
|
||||
<mat-checkbox [(ngModel)]="publishImmediately" (change)="publishStateChanged($event.checked)">
|
||||
<span translate>Publish immediately</span>
|
||||
</mat-checkbox>
|
||||
<mat-error *ngIf="!dialogVoteForm.valid" translate>
|
||||
|
@ -5,7 +5,6 @@ import { Title } from '@angular/platform-browser';
|
||||
|
||||
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 { MotionPollMethodsVerbose } from 'app/site/motions/models/view-motion-poll';
|
||||
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']
|
||||
})
|
||||
export class MotionPollDialogComponent extends BasePollDialogComponent {
|
||||
public motionPollMethods = MotionPollMethodsVerbose;
|
||||
public motionPollMethods = { YNA: MotionPollMethodsVerbose.YNA };
|
||||
|
||||
@ViewChild('pollForm', { static: false })
|
||||
protected pollForm: PollFormComponent;
|
||||
@ -38,13 +37,14 @@ export class MotionPollDialogComponent extends BasePollDialogComponent {
|
||||
const update: any = {
|
||||
Y: data.options[0].yes,
|
||||
N: data.options[0].no,
|
||||
A: data.options[0].abstain,
|
||||
votesvalid: data.votesvalid,
|
||||
votesinvalid: data.votesinvalid,
|
||||
votescast: data.votescast
|
||||
};
|
||||
if (data.pollmethod === 'YNA') {
|
||||
update.A = data.options[0].abstain;
|
||||
}
|
||||
// if (data.pollmethod === 'YNA') {
|
||||
// update.A = data.options[0].abstain;
|
||||
// }
|
||||
|
||||
if (this.dialogVoteForm) {
|
||||
const result = this.undoReplaceEmptyValues(update);
|
||||
@ -59,13 +59,14 @@ export class MotionPollDialogComponent extends BasePollDialogComponent {
|
||||
this.dialogVoteForm = this.fb.group({
|
||||
Y: ['', [Validators.min(-2)]],
|
||||
N: ['', [Validators.min(-2)]],
|
||||
A: ['', [Validators.min(-2)]],
|
||||
votesvalid: ['', [Validators.min(-2)]],
|
||||
votesinvalid: ['', [Validators.min(-2)]],
|
||||
votescast: ['', [Validators.min(-2)]]
|
||||
});
|
||||
if (this.pollData.pollmethod === MotionPollMethods.YNA) {
|
||||
this.dialogVoteForm.addControl('A', this.fb.control('', [Validators.min(-2)]));
|
||||
}
|
||||
// if (this.pollData.pollmethod === MotionPollMethods.YNA) {
|
||||
// this.dialogVoteForm.addControl('A', this.fb.control('', [Validators.min(-2)]));
|
||||
// }
|
||||
if (this.pollData.poll) {
|
||||
this.updateDialogVoteForm(this.pollData);
|
||||
}
|
||||
|
@ -31,8 +31,13 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="poll-chart-wrapper" *ngIf="poll">
|
||||
<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 *ngIf="poll.type === 'analog' || poll.state === 3 || poll.state === 4" (click)="openPoll()">
|
||||
<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'">
|
||||
<os-motion-poll-vote [poll]="poll"></os-motion-poll-vote>
|
||||
</ng-container>
|
||||
@ -45,7 +50,7 @@
|
||||
<mat-icon>close</mat-icon>
|
||||
: {{ voteNo }}
|
||||
</div>
|
||||
<div class="doughnut-chart">
|
||||
<div *ngIf="showChart" class="doughnut-chart">
|
||||
<os-charts [type]="'doughnut'" [data]="chartDataSubject" [showLegend]="false"> </os-charts>
|
||||
</div>
|
||||
<div class="chart-wrapper-right">
|
||||
@ -62,11 +67,7 @@
|
||||
|
||||
<mat-menu #triggerMenu="matMenu">
|
||||
<ng-container *ngIf="poll">
|
||||
<button
|
||||
mat-menu-item
|
||||
(click)="changeState(state.value)"
|
||||
*ngFor="let state of poll.nextStates | keyvalue"
|
||||
>
|
||||
<button mat-menu-item (click)="changeState(state.value)" *ngFor="let state of poll.nextStates | keyvalue">
|
||||
<span translate>{{ state.key }}</span>
|
||||
</button>
|
||||
</ng-container>
|
||||
|
@ -1,10 +1,12 @@
|
||||
import { Component, Input } from '@angular/core';
|
||||
import { MatDialog, MatSnackBar } from '@angular/material';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
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 { PromptService } from 'app/core/ui-services/prompt.service';
|
||||
import { ChartData } from 'app/shared/components/charts/charts.component';
|
||||
@ -28,7 +30,7 @@ export class MotionPollComponent extends BasePollComponent<ViewMotionPoll> {
|
||||
*/
|
||||
@Input()
|
||||
public set poll(value: ViewMotionPoll) {
|
||||
this._poll = value;
|
||||
this.initPoll(value);
|
||||
|
||||
const chartData = this.poll.generateChartData();
|
||||
for (const data of chartData) {
|
||||
@ -54,17 +56,33 @@ export class MotionPollComponent extends BasePollComponent<ViewMotionPoll> {
|
||||
/**
|
||||
* 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`.
|
||||
*/
|
||||
public voteNo = 0;
|
||||
public set voteNo(n: number | string) {
|
||||
this._voteNo = n;
|
||||
}
|
||||
|
||||
/**
|
||||
* The motion-poll.
|
||||
*/
|
||||
private _poll: ViewMotionPoll;
|
||||
public get voteNo(): number | string {
|
||||
return this.verboseForNumber(this._voteNo as number);
|
||||
}
|
||||
|
||||
public get showChart(): boolean {
|
||||
return this._voteYes >= 0 && this._voteNo >= 0;
|
||||
}
|
||||
|
||||
private _voteNo: number | string = 0;
|
||||
|
||||
private _voteYes: number | string = 0;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
@ -81,10 +99,30 @@ export class MotionPollComponent extends BasePollComponent<ViewMotionPoll> {
|
||||
translate: TranslateService,
|
||||
dialog: MatDialog,
|
||||
promptService: PromptService,
|
||||
public repo: MotionPollRepositoryService,
|
||||
public pollRepo: MotionPollRepositoryService,
|
||||
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;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -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 { PromptService } from 'app/core/ui-services/prompt.service';
|
||||
import { Breadcrumb } from 'app/shared/components/breadcrumb/breadcrumb.component';
|
||||
import { ChartData } from 'app/shared/components/charts/charts.component';
|
||||
import { PollState } from 'app/shared/models/poll/base-poll';
|
||||
import { ChartData, ChartType } from 'app/shared/components/charts/charts.component';
|
||||
import { PollState, PollType } from 'app/shared/models/poll/base-poll';
|
||||
import { BaseViewComponent } from 'app/site/base/base-view';
|
||||
import { ViewGroup } from 'app/site/users/models/view-group';
|
||||
import { BasePollRepositoryService } from '../services/base-poll-repository.service';
|
||||
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.
|
||||
*/
|
||||
@ -42,7 +42,8 @@ export class BasePollDetailComponent<V extends ViewBasePoll> extends BaseViewCom
|
||||
/**
|
||||
* 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).
|
||||
@ -99,7 +100,7 @@ export class BasePollDetailComponent<V extends ViewBasePoll> extends BaseViewCom
|
||||
const text = 'Do you really want to delete the selected poll?';
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
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.
|
||||
*/
|
||||
private checkData(): void {
|
||||
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.updateBreadcrumbs();
|
||||
this.checkData();
|
||||
this.labels = this.createChartLabels();
|
||||
this.onPollLoaded();
|
||||
}
|
||||
})
|
||||
@ -157,23 +168,18 @@ export class BasePollDetailComponent<V extends ViewBasePoll> extends BaseViewCom
|
||||
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.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
private actionWrapper(action: Promise<any>): any {
|
||||
action.then(() => this.checkData()).catch(this.raiseError);
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to create the labels for the chart.
|
||||
*
|
||||
* @returns An array of `Label`.
|
||||
*/
|
||||
private createChartLabels(): Label[] {
|
||||
return ['Number of votes'];
|
||||
private actionWrapper(action: Promise<any>, callback?: () => any): any {
|
||||
action
|
||||
.then(() => {
|
||||
this.checkData();
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
})
|
||||
.catch(this.raiseError);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -220,11 +224,14 @@ export class BasePollDetailComponent<V extends ViewBasePoll> extends BaseViewCom
|
||||
if (!this.poll) {
|
||||
return null;
|
||||
}
|
||||
if (!this.hasPerms()) {
|
||||
return null;
|
||||
}
|
||||
switch (this.poll.state) {
|
||||
case PollState.Created:
|
||||
return state === 2 ? () => this.changeState() : null;
|
||||
case PollState.Started:
|
||||
return null;
|
||||
return this.poll.type !== PollType.Analog && state === 3 ? () => this.changeState() : null;
|
||||
case PollState.Finished:
|
||||
if (state === 1) {
|
||||
return () => this.resetState();
|
||||
|
@ -1,23 +1,28 @@
|
||||
import { Input } from '@angular/core';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
|
||||
import { BasePollDialogService } from 'app/core/ui-services/base-poll-dialog.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 { BaseViewComponent } from 'app/site/base/base-view';
|
||||
import { BasePollRepositoryService } from '../services/base-poll-repository.service';
|
||||
import { ViewBasePoll } from '../models/view-base-poll';
|
||||
|
||||
export class BasePollComponent<V extends ViewBasePoll> extends BaseViewComponent {
|
||||
/**
|
||||
* The poll represented in this component
|
||||
*/
|
||||
@Input()
|
||||
public poll: V;
|
||||
export abstract class BasePollComponent<V extends ViewBasePoll> extends BaseViewComponent {
|
||||
// /**
|
||||
// * The poll represented in this component
|
||||
// */
|
||||
// @Input()
|
||||
// public abstract set poll(model: V);
|
||||
|
||||
public chartDataSubject: BehaviorSubject<ChartData> = new BehaviorSubject([]);
|
||||
|
||||
protected _poll: V;
|
||||
|
||||
public constructor(
|
||||
titleService: Title,
|
||||
@ -25,14 +30,14 @@ export class BasePollComponent<V extends ViewBasePoll> extends BaseViewComponent
|
||||
public translate: TranslateService,
|
||||
public dialog: MatDialog,
|
||||
protected promptService: PromptService,
|
||||
public repo: BasePollRepositoryService,
|
||||
protected repo: BasePollRepositoryService,
|
||||
protected pollDialog: BasePollDialogService<V>
|
||||
) {
|
||||
super(titleService, translate, matSnackBar);
|
||||
}
|
||||
|
||||
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> {
|
||||
const title = this.translate.instant('Are you sure you want to delete this poll?');
|
||||
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
|
||||
*/
|
||||
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>
|
||||
|
||||
<mat-form-field>
|
||||
<mat-select
|
||||
placeholder="{{ '100% base' | translate }}"
|
||||
formControlName="onehundred_percent_base"
|
||||
required
|
||||
>
|
||||
<mat-select placeholder="{{ '100% base' | translate }}" formControlName="onehundred_percent_base" required>
|
||||
<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 }}
|
||||
</mat-option>
|
||||
</ng-container>
|
||||
@ -68,4 +64,3 @@
|
||||
</mat-form-field>
|
||||
</form>
|
||||
</div>
|
||||
|
@ -63,6 +63,12 @@ export class PollFormComponent extends BaseViewComponent implements OnInit {
|
||||
*/
|
||||
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,
|
||||
* injects the poll itself
|
||||
@ -73,18 +79,10 @@ export class PollFormComponent extends BaseViewComponent implements OnInit {
|
||||
snackbar: MatSnackBar,
|
||||
private fb: FormBuilder,
|
||||
private groupRepo: GroupRepositoryService,
|
||||
private pollService: PollService
|
||||
public pollService: PollService
|
||||
) {
|
||||
super(title, translate, snackbar);
|
||||
|
||||
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: [[]]
|
||||
});
|
||||
this.initContentForm();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -133,6 +131,10 @@ export class PollFormComponent extends BaseViewComponent implements OnInit {
|
||||
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.
|
||||
*
|
||||
@ -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> {
|
||||
private _tableData: {}[] = [];
|
||||
|
||||
public get tableData(): {}[] {
|
||||
if (!this._tableData.length) {
|
||||
this._tableData = this.generateTableData();
|
||||
}
|
||||
return this._tableData;
|
||||
}
|
||||
|
||||
public get poll(): M {
|
||||
return this._model;
|
||||
}
|
||||
@ -101,7 +110,14 @@ export abstract class ViewBasePoll<M extends BasePoll<M, any> = any> extends Bas
|
||||
|
||||
public abstract getSlide(): ProjectorElementBuildDeskriptor;
|
||||
|
||||
/**
|
||||
* Initializes labels for a chart.
|
||||
*/
|
||||
public abstract initChartLabels(): string[];
|
||||
|
||||
public abstract generateChartData(): ChartData;
|
||||
|
||||
public abstract generateTableData(): {}[];
|
||||
}
|
||||
|
||||
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/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/shared/components/banner/banner.component.scss-theme.scss';
|
||||
|
||||
/** fonts */
|
||||
@import './assets/styles/fonts.scss';
|
||||
@ -54,6 +55,7 @@ $narrow-spacing: (
|
||||
@include os-config-field-style($theme);
|
||||
@include os-amendment-create-wizard-style($theme);
|
||||
@include os-motion-detail-diff-style($theme);
|
||||
@include os-banner-style($theme);
|
||||
}
|
||||
|
||||
/** Load projector specific SCSS values */
|
||||
|
Loading…
Reference in New Issue
Block a user