Merge pull request #5705 from tsiegleauq/show-poll-result-in-ap

Show latest meaningful poll results in autopilot
This commit is contained in:
Emanuel Schütze 2020-12-08 16:17:49 +01:00 committed by GitHub
commit 9ddb3a9179
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 373 additions and 232 deletions

View File

@ -1,5 +1,5 @@
<div *ngIf="poll">
<table class="assignment-result-table">
<table class="assignment-result-table" *ngIf="hasResults && canSeeResults">
<tbody>
<tr>
<th class="voting-option">{{ 'Candidates' | translate }}</th>
@ -51,4 +51,18 @@
</tr>
</tbody>
</table>
<!-- No results yet -->
<div *ngIf="!hasResults">
<i>
{{ 'No results yet.' | translate }}
</i>
</div>
<!-- Has results, but user cannot see -->
<div *ngIf="hasResults && !canSeeResults">
<i>
{{ 'Counting of votes is in progress ...' | translate }}
</i>
</div>
</div>

View File

@ -1,6 +1,12 @@
import { Component, Input } from '@angular/core';
import { Title } from '@angular/platform-browser';
import { TranslateService } from '@ngx-translate/core';
import { BaseComponent } from 'app/base.component';
import { OperatorService } from 'app/core/core-services/operator.service';
import { AssignmentPollMethod } from 'app/shared/models/assignments/assignment-poll';
import { PollState } from 'app/shared/models/poll/base-poll';
import { ViewAssignmentPoll } from 'app/site/assignments/models/view-assignment-poll';
import { AssignmentPollService } from 'app/site/assignments/modules/assignment-poll/services/assignment-poll.service';
import { PollData, PollTableData, VotingResult } from 'app/site/polls/services/poll.service';
@ -10,16 +16,18 @@ import { PollData, PollTableData, VotingResult } from 'app/site/polls/services/p
templateUrl: './assignment-poll-detail-content.component.html',
styleUrls: ['./assignment-poll-detail-content.component.scss']
})
export class AssignmentPollDetailContentComponent {
export class AssignmentPollDetailContentComponent extends BaseComponent {
@Input()
public poll: ViewAssignmentPoll | PollData;
public constructor(private pollService: AssignmentPollService) {}
private get method(): string {
return this.poll.pollmethod;
}
private get state(): PollState {
return this.poll.state;
}
public get showYHeader(): boolean {
return this.isMethodY || this.isMethodYN || this.isMethodYNA;
}
@ -44,10 +52,35 @@ export class AssignmentPollDetailContentComponent {
return this.method === AssignmentPollMethod.YNA;
}
public get isFinished(): boolean {
return this.state === PollState.Finished;
}
public get isPublished(): boolean {
return this.state === PollState.Published;
}
public get tableData(): PollTableData[] {
return this.pollService.generateTableData(this.poll);
}
public get hasResults(): boolean {
return this.isFinished || this.isPublished;
}
public get canSeeResults(): boolean {
return this.operator.hasPerms(this.permission.assignmentsCanManage) || this.isPublished;
}
public constructor(
titleService: Title,
translateService: TranslateService,
private pollService: AssignmentPollService,
private operator: OperatorService
) {
super(titleService, translateService);
}
public getVoteClass(votingResult: VotingResult): string {
const votingClass = votingResult.vote;
if (this.isMethodN && votingClass === 'no') {

View File

@ -74,7 +74,9 @@ export class ChartsComponent extends BaseViewComponentDirective {
@Input()
public set data(inputData: ChartData) {
this.progressInputData(inputData);
if (inputData) {
this.progressInputData(inputData);
}
}
/**
@ -85,6 +87,9 @@ export class ChartsComponent extends BaseViewComponentDirective {
return {
responsive: true,
maintainAspectRatio: false,
animation: {
duration: 0
},
tooltips: {
enabled: false
},
@ -96,6 +101,9 @@ export class ChartsComponent extends BaseViewComponentDirective {
return {
responsive: true,
maintainAspectRatio: false,
animation: {
duration: 0
},
tooltips: {
enabled: false
},

View File

@ -1,39 +1,55 @@
<div class="result-wrapper" *ngIf="hasVotes">
<!-- result table -->
<table class="result-table">
<tbody>
<tr>
<th></th>
<th colspan="2">{{ 'Votes' | translate }}</th>
</tr>
<tr *ngFor="let row of getTableData()" [class]="row.votingOption">
<!-- YNA/Valid etc -->
<td>
<os-icon-container *ngIf="row.value[0].icon" [icon]="row.value[0].icon" [size]="iconSize">
{{ row.votingOption | pollKeyVerbose | translate }}
</os-icon-container>
<span *ngIf="!row.value[0].icon">
{{ row.votingOption | pollKeyVerbose | translate }}
</span>
</td>
<div class="result-wrapper" *ngIf="poll">
<ng-container *ngIf="hasResults && canSeeResults">
<!-- result table -->
<table class="result-table">
<tbody>
<tr>
<th></th>
<th colspan="2">{{ 'Votes' | translate }}</th>
</tr>
<tr *ngFor="let row of tableData; trackBy: trackByIndex" [class]="row.votingOption">
<!-- YNA/Valid etc -->
<td>
<os-icon-container *ngIf="row.value[0].icon" [icon]="row.value[0].icon" [size]="iconSize">
{{ row.votingOption | pollKeyVerbose | translate }}
</os-icon-container>
<span *ngIf="!row.value[0].icon">
{{ row.votingOption | pollKeyVerbose | translate }}
</span>
</td>
<!-- Percent numbers -->
<td class="result-cell-definition">
<span *ngIf="row.value[0].showPercent">
{{ row.value[0].amount | pollPercentBase: poll:'motion' }}
</span>
</td>
<!-- Percent numbers -->
<td class="result-cell-definition">
<span *ngIf="row.value[0].showPercent">
{{ row.value[0].amount | pollPercentBase: poll:'motion' }}
</span>
</td>
<!-- Voices -->
<td class="result-cell-definition">
{{ row.value[0].amount | parsePollNumber }}
</td>
</tr>
</tbody>
</table>
<!-- Voices -->
<td class="result-cell-definition">
{{ row.value[0].amount | parsePollNumber }}
</td>
</tr>
</tbody>
</table>
<!-- Chart -->
<div class="doughnut-chart" *ngIf="showChart">
<os-charts type="doughnut" [data]="chartData | async"></os-charts>
<!-- Chart -->
<div class="doughnut-chart">
<os-charts type="doughnut" [data]="chartData"></os-charts>
</div>
</ng-container>
<!-- No results yet -->
<div *ngIf="!hasResults">
<i>
{{ 'No results yet.' | translate }}
</i>
</div>
<!-- Has results, but user cannot see -->
<div *ngIf="hasResults && !canSeeResults">
<i>
{{ 'Counting of votes is in progress ...' | translate }}
</i>
</div>
</div>

View File

@ -1,7 +1,11 @@
import { Component, Input, OnInit } from '@angular/core';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input } from '@angular/core';
import { Title } from '@angular/platform-browser';
import { BehaviorSubject } from 'rxjs';
import { TranslateService } from '@ngx-translate/core';
import { BaseComponent } from 'app/base.component';
import { OperatorService } from 'app/core/core-services/operator.service';
import { PollState } from 'app/shared/models/poll/base-poll';
import { ViewMotionPoll } from 'app/site/motions/models/view-motion-poll';
import { MotionPollService } from 'app/site/motions/services/motion-poll.service';
import { PollData, PollTableData } from 'app/site/polls/services/poll.service';
@ -10,31 +14,65 @@ import { ChartData } from '../charts/charts.component';
@Component({
selector: 'os-motion-poll-detail-content',
templateUrl: './motion-poll-detail-content.component.html',
styleUrls: ['./motion-poll-detail-content.component.scss']
styleUrls: ['./motion-poll-detail-content.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class MotionPollDetailContentComponent implements OnInit {
@Input()
public poll: ViewMotionPoll | PollData;
export class MotionPollDetailContentComponent extends BaseComponent {
private _poll: ViewMotionPoll | PollData;
public chartData: ChartData;
public tableData: PollTableData[];
@Input()
public chartData: BehaviorSubject<ChartData>;
public set poll(pollData: ViewMotionPoll | PollData) {
this._poll = pollData;
this.setTableData();
this.setChartData();
this.cd.markForCheck();
}
public get poll(): ViewMotionPoll | PollData {
return this._poll;
}
@Input()
public iconSize: 'large' | 'gigantic' = 'large';
public get hasVotes(): boolean {
return this.poll && !!this.poll.options;
private get state(): PollState {
return this.poll.state;
}
public constructor(private motionPollService: MotionPollService) {}
public ngOnInit(): void {}
public getTableData(): PollTableData[] {
return this.motionPollService.generateTableData(this.poll);
public get hasResults(): boolean {
return this.isFinished || this.isPublished;
}
public get showChart(): boolean {
return this.motionPollService.showChart(this.poll) && this.chartData && !!this.chartData.value;
public get isFinished(): boolean {
return this.state === PollState.Finished;
}
public get isPublished(): boolean {
return this.state === PollState.Published;
}
public get canSeeResults(): boolean {
return this.operator.hasPerms(this.permission.motionsCanManagePolls) || this.isPublished;
}
public constructor(
titleService: Title,
translate: TranslateService,
private pollService: MotionPollService,
private cd: ChangeDetectorRef,
private operator: OperatorService
) {
super(titleService, translate);
}
private setTableData(): void {
this.tableData = this.pollService.generateTableData(this.poll);
}
private setChartData(): void {
this.chartData = this.pollService.generateChartData(this.poll);
}
}

View File

@ -28,22 +28,17 @@
</span>
</div>
<div class="assignment-result-wrapper" *ngIf="poll && poll.stateHasVotes">
<div class="assignment-result-wrapper" *ngIf="poll">
<!-- Result Table -->
<os-assignment-poll-detail-content [poll]="poll"></os-assignment-poll-detail-content>
<!-- Result Chart -->
<div class="chart-wrapper">
<os-charts
class="assignment-result-chart"
*ngIf="chartDataSubject.value"
[labels]="candidatesLabels"
[data]="chartDataSubject | async"
></os-charts>
<div class="chart-wrapper" *ngIf="showResults && poll.stateHasVotes">
<os-charts class="assignment-result-chart" [labels]="candidatesLabels" [data]="chartData"></os-charts>
</div>
<!-- Single Votes Table -->
<div class="named-result-table" *ngIf="poll.type === 'named'">
<div class="named-result-table" *ngIf="showResults && poll.stateHasVotes && poll.type === 'named'">
<h3>{{ 'Single votes' | translate }}</h3>
<os-list-view-table
class="single-votes-table"

View File

@ -1,4 +1,4 @@
import { Component, ViewEncapsulation } from '@angular/core';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ViewEncapsulation } from '@angular/core';
import { MatSnackBar } from '@angular/material/snack-bar';
import { Title } from '@angular/platform-browser';
import { ActivatedRoute, Router } from '@angular/router';
@ -12,6 +12,7 @@ import { AssignmentVoteRepositoryService } from 'app/core/repositories/assignmen
import { GroupRepositoryService } from 'app/core/repositories/users/group-repository.service';
import { ConfigService } from 'app/core/ui-services/config.service';
import { PromptService } from 'app/core/ui-services/prompt.service';
import { ChartData } from 'app/shared/components/charts/charts.component';
import { VoteValue } from 'app/shared/models/poll/base-vote';
import { ViewAssignmentPoll } from 'app/site/assignments/models/view-assignment-poll';
import { BasePollDetailComponentDirective } from 'app/site/polls/components/base-poll-detail.component';
@ -22,6 +23,7 @@ import { AssignmentPollService } from '../../services/assignment-poll.service';
selector: 'os-assignment-poll-detail',
templateUrl: './assignment-poll-detail.component.html',
styleUrls: ['./assignment-poll-detail.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.None
})
export class AssignmentPollDetailComponent extends BasePollDetailComponentDirective<
@ -38,6 +40,14 @@ export class AssignmentPollDetailComponent extends BasePollDetailComponentDirect
public isVoteWeightActive: boolean;
public get showResults(): boolean {
return this.hasPerms() || this.poll.isPublished;
}
public get chartData(): ChartData {
return this.pollService.generateChartData(this.poll);
}
public constructor(
title: Title,
translate: TranslateService,
@ -51,7 +61,8 @@ export class AssignmentPollDetailComponent extends BasePollDetailComponentDirect
protected pollService: AssignmentPollService,
votesRepo: AssignmentVoteRepositoryService,
protected operator: OperatorService,
private router: Router
private router: Router,
protected cd: ChangeDetectorRef
) {
super(
title,
@ -64,7 +75,8 @@ export class AssignmentPollDetailComponent extends BasePollDetailComponentDirect
pollDialog,
pollService,
votesRepo,
operator
operator,
cd
);
configService
.get<boolean>('users_activate_vote_weight')

View File

@ -12,9 +12,7 @@
<div class="italic spacer-bottom-20">
<span *osPerms="'assignments.can_manage'; and: poll.type === 'pseudoanonymous'">
<button mat-icon-button color="warn" (click)="openVotingWarning()">
<mat-icon>
warning
</mat-icon>
<mat-icon> warning </mat-icon>
</button>
</span>
<span *ngIf="poll.type !== 'analog'"> {{ poll.typeVerbose | translate }} &middot; </span>
@ -64,19 +62,8 @@
<!-- Chart / Table -->
<div *ngIf="poll.stateHasVotes" class="poll-result-wrapper">
<div *osPerms="'assignments.can_manage'; or: poll.isPublished">
<os-assignment-poll-detail-content
routerLink="/assignments/polls/{{ poll.id }}"
[poll]="poll"
></os-assignment-poll-detail-content>
</div>
<!-- Cannot see unpublished -->
<div *osPerms="'assignments.can_manage'; complement: true">
<span *ngIf="poll.isFinished">
{{ 'Counting of votes is in progress ...' | translate }}
</span>
</div>
<os-assignment-poll-detail-content routerLink="/assignments/polls/{{ poll.id }}" [poll]="poll">
</os-assignment-poll-detail-content>
</div>
<!-- Poll progress bar -->
@ -95,9 +82,7 @@
matTooltip="{{ 'More' | translate }}"
*ngIf="poll.isPublished"
>
<mat-icon class="small-icon">
visibility
</mat-icon>
<mat-icon class="small-icon"> visibility </mat-icon>
</a>
</div>
</div>

View File

@ -1,4 +1,4 @@
import { Component, OnInit, ViewChild } from '@angular/core';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit, ViewChild } from '@angular/core';
import { MatSnackBar } from '@angular/material/snack-bar';
import { Title } from '@angular/platform-browser';
@ -90,7 +90,8 @@ export class CinemaComponent extends BaseViewComponentDirective implements OnIni
private projectorService: ProjectorService,
private projectorRepo: ProjectorRepositoryService,
private closService: CurrentListOfSpeakersService,
private listOfSpeakersRepo: ListOfSpeakersRepositoryService
private listOfSpeakersRepo: ListOfSpeakersRepositoryService,
private cd: ChangeDetectorRef
) {
super(title, translate, snackBar);
}

View File

@ -1,13 +1,32 @@
<mat-card class="os-card" *ngFor="let poll of polls; trackBy: identifyPoll">
<p class="subtitle-text">
<a [routerLink]="getPollDetailLink(poll)" [state]="{ back: 'true' }">{{ getPollVoteTitle(poll) }}</a>
</p>
<ng-container
*ngFor="let poll of polls; trackBy: identifyPoll"
[ngTemplateOutlet]="pollArea"
[ngTemplateOutletContext]="{ poll: poll, last: false }"
></ng-container>
<div *ngIf="poll.pollClassType === 'motion'">
<os-motion-poll-vote [poll]="poll" *ngIf="poll.canBeVotedFor()"></os-motion-poll-vote>
</div>
<ng-container
*ngIf="lastPublishedPoll && !hasProjectedModelOpenPolls"
[ngTemplateOutlet]="pollArea"
[ngTemplateOutletContext]="{ poll: lastPublishedPoll, last: true }"
></ng-container>
<div *ngIf="poll.pollClassType === 'assignment'">
<os-assignment-poll-vote [poll]="poll" *ngIf="poll.canBeVotedFor()"></os-assignment-poll-vote>
</div>
</mat-card>
<ng-template #pollArea let-poll="poll" let-last="last">
<mat-card class="os-card">
<p class="subtitle-text">
<a [routerLink]="getPollDetailLink(poll)" [state]="{ back: 'true' }">{{ getPollVoteTitle(poll) }}</a>
</p>
<div *ngIf="poll.pollClassType === 'motion'">
<os-motion-poll-vote [poll]="poll" *ngIf="poll.canBeVotedFor() && !last"></os-motion-poll-vote>
<os-motion-poll-detail-content [poll]="lastPublishedPoll" *ngIf="last"></os-motion-poll-detail-content>
</div>
<div *ngIf="poll.pollClassType === 'assignment'">
<os-assignment-poll-vote [poll]="poll" *ngIf="poll.canBeVotedFor() && !last"></os-assignment-poll-vote>
<os-assignment-poll-detail-content
[poll]="lastPublishedPoll"
*ngIf="last"
></os-assignment-poll-detail-content>
</div>
</mat-card>
</ng-template>

View File

@ -1,25 +1,52 @@
import { Component, Input, OnInit } from '@angular/core';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnInit } from '@angular/core';
import { MatSnackBar } from '@angular/material/snack-bar';
import { Title } from '@angular/platform-browser';
import { TranslateService } from '@ngx-translate/core';
import { map } from 'rxjs/operators';
import { ViewAssignment } from 'app/site/assignments/models/view-assignment';
import { ViewAssignmentPoll } from 'app/site/assignments/models/view-assignment-poll';
import { BaseViewComponentDirective } from 'app/site/base/base-view';
import { BaseViewModel } from 'app/site/base/base-view-model';
import { ViewMotion } from 'app/site/motions/models/view-motion';
import { ViewMotionPoll } from 'app/site/motions/models/view-motion-poll';
import { ViewBasePoll } from 'app/site/polls/models/view-base-poll';
import { PollListObservableService } from 'app/site/polls/services/poll-list-observable.service';
@Component({
selector: 'os-poll-collection',
templateUrl: './poll-collection.component.html',
styleUrls: ['./poll-collection.component.scss']
styleUrls: ['./poll-collection.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class PollCollectionComponent extends BaseViewComponentDirective implements OnInit {
public polls: ViewBasePoll[];
public lastPublishedPoll: ViewBasePoll;
private _currentProjection: BaseViewModel<any>;
public get currentProjection(): BaseViewModel<any> {
return this._currentProjection;
}
/**
* CLEANUP: This function belongs to "HasViewPolls"/ ViewModelWithPolls
*/
public get hasProjectedModelOpenPolls(): boolean {
if (this.currentProjection instanceof ViewMotion || this.currentProjection instanceof ViewAssignment) {
const currPolls: ViewMotionPoll[] | ViewAssignmentPoll[] = this.currentProjection.polls;
return currPolls.some((p: ViewMotionPoll | ViewAssignmentPoll) => p.isStarted);
}
return false;
}
@Input()
private currentProjection: BaseViewModel<any>;
public set currentProjection(viewModel: BaseViewModel<any>) {
this._currentProjection = viewModel;
this.updateLastPublished();
}
private get showExtendedTitle(): boolean {
const areAllPollsSameModel = this.polls.every(
@ -27,7 +54,7 @@ export class PollCollectionComponent extends BaseViewComponentDirective implemen
);
if (this.currentProjection && areAllPollsSameModel) {
return this.polls[0].getContentObject() !== this.currentProjection;
return this.polls[0]?.getContentObject() !== this.currentProjection;
} else {
return !areAllPollsSameModel;
}
@ -37,7 +64,8 @@ export class PollCollectionComponent extends BaseViewComponentDirective implemen
title: Title,
translate: TranslateService,
snackBar: MatSnackBar,
private pollService: PollListObservableService
private pollService: PollListObservableService,
private cd: ChangeDetectorRef
) {
super(title, translate, snackBar);
}
@ -46,9 +74,17 @@ export class PollCollectionComponent extends BaseViewComponentDirective implemen
this.subscriptions.push(
this.pollService
.getViewModelListObservable()
.pipe(map(polls => polls.filter(poll => poll.canBeVotedFor())))
.pipe(
map(polls => {
return polls.filter(poll => {
return poll.canBeVotedFor();
});
})
)
.subscribe(polls => {
this.polls = polls;
this.cd.markForCheck();
this.updateLastPublished();
})
);
}
@ -57,6 +93,10 @@ export class PollCollectionComponent extends BaseViewComponentDirective implemen
return poll.id;
}
public getPollDetailLink(poll: ViewBasePoll): string {
return poll.parentLink;
}
public getPollVoteTitle(poll: ViewBasePoll): string {
const contentObject = poll.getContentObject();
const listTitle = contentObject.getListTitle();
@ -70,7 +110,35 @@ export class PollCollectionComponent extends BaseViewComponentDirective implemen
}
}
public getPollDetailLink(poll: ViewBasePoll): string {
return poll.parentLink;
/**
* Helper function to detect new latest published polls and set them.
*/
private updateLastPublished(): void {
const lastPublished = this.getLastfinshedPoll(this.currentProjection);
if (lastPublished !== this.lastPublishedPoll) {
this.lastPublishedPoll = lastPublished;
this.cd.markForCheck();
}
}
/**
* CLEANUP: This function belongs to "HasViewPolls"/ ViewModelWithPolls
* *class* (is an interface right now)
*
* @param viewModel
*/
private getLastfinshedPoll(viewModel: BaseViewModel): ViewBasePoll {
if (viewModel instanceof ViewMotion || viewModel instanceof ViewAssignment) {
let currPolls: ViewMotionPoll[] | ViewAssignmentPoll[] = viewModel.polls;
/**
* Although it should, since the union type could use `.filter
* without any problem, without an any cast it will not work
*/
currPolls = (currPolls as any[])
.filter((p: ViewMotionPoll | ViewAssignmentPoll) => p.stateHasVotes)
.reverse();
return currPolls[0];
}
return null;
}
}

View File

@ -11,6 +11,7 @@ import { ProjectorElementBuildDeskriptor } from 'app/site/base/projectable';
import { Searchable } from 'app/site/base/searchable';
import { SlideOptions } from 'app/site/base/slide-options';
import { ViewMediafile } from 'app/site/mediafiles/models/view-mediafile';
import { HasViewPolls } from 'app/site/polls/models/has-view-polls';
import { ViewTag } from 'app/site/tags/models/view-tag';
import { ViewUser } from 'app/site/users/models/view-user';
import { AmendmentType } from '../motions.constants';
@ -355,7 +356,7 @@ export class ViewMotion
}
}
interface TIMotionRelations {
interface TIMotionRelations extends HasViewPolls<ViewMotionPoll> {
category?: ViewCategory;
submitters: ViewSubmitter[];
supporters?: ViewUser[];
@ -369,7 +370,6 @@ interface TIMotionRelations {
amendments?: ViewMotion[];
changeRecommendations?: ViewMotionChangeRecommendation[];
diffLines?: DiffLinesInParagraph[];
polls: ViewMotionPoll[];
}
export interface ViewMotion extends MotionWithoutNestedModels, TIMotionRelations {}

View File

@ -29,68 +29,63 @@
</span>
</div>
<p *ngIf="!poll.hasVotes || !poll.stateHasVotes">{{ 'No results yet.' | translate }}</p>
<os-motion-poll-detail-content [poll]="poll"></os-motion-poll-detail-content>
<div *ngIf="poll.stateHasVotes && (hasPerms() || poll.isPublished)">
<os-motion-poll-detail-content [poll]="poll" [chartData]="chartDataSubject">
</os-motion-poll-detail-content>
<!-- Named table: only show if votes are present -->
<div class="named-result-table" *ngIf="showResults && poll.stateHasVotes && poll.type === 'named'">
<h2>{{ 'Single votes' | translate }}</h2>
<os-list-view-table
*ngIf="votesDataObservable"
class="single-votes-table"
[listObservable]="votesDataObservable"
[columns]="columnDefinition"
[filterProps]="filterProps"
[allowProjector]="false"
[fullScreen]="true"
[vScrollFixed]="-1"
listStorageKey="motion-poll-vote"
>
<!-- Header -->
<div *pblNgridHeaderCellDef="'*'; col as col">
{{ col.label | translate }}
</div>
<!-- Named table: only show if votes are present -->
<div class="named-result-table" *ngIf="poll.type === 'named'">
<h2>{{ 'Single votes' | translate }}</h2>
<os-list-view-table
*ngIf="votesDataObservable"
class="single-votes-table"
[listObservable]="votesDataObservable"
[columns]="columnDefinition"
[filterProps]="filterProps"
[allowProjector]="false"
[fullScreen]="true"
[vScrollFixed]="-1"
listStorageKey="motion-poll-vote"
>
<!-- Header -->
<div *pblNgridHeaderCellDef="'*'; col as col">
{{ col.label | translate }}
</div>
<!-- Content -->
<div *pblNgridCellDef="'user'; row as vote">
<div *ngIf="vote.user">
{{ vote.user.getShortName() }}
<!-- Content -->
<div *pblNgridCellDef="'user'; row as vote">
<div *ngIf="vote.user">
{{ vote.user.getShortName() }}
<div class="user-subtitle">
<!-- Level and number -->
<div *ngIf="vote.user.getLevelAndNumber()">
{{ vote.user.getLevelAndNumber() }}
</div>
<div class="user-subtitle">
<!-- Level and number -->
<div *ngIf="vote.user.getLevelAndNumber()">
{{ vote.user.getLevelAndNumber() }}
</div>
<!-- Vote weight -->
<div *ngIf="isVoteWeightActive">
{{ 'Vote weight' | translate }}: {{ vote.user.vote_weight }}
</div>
<!-- Vote weight -->
<div *ngIf="isVoteWeightActive">
{{ 'Vote weight' | translate }}: {{ vote.user.vote_weight }}
</div>
<!-- Delegation -->
<div *ngIf="userHasVoteDelegation(vote.user)">
<span>
({{ 'represented by' | translate }}
{{ getUsersVoteDelegation(vote.user).getShortName().trim() }})
</span>
</div>
<!-- Delegation -->
<div *ngIf="userHasVoteDelegation(vote.user)">
<span>
({{ 'represented by' | translate }}
{{ getUsersVoteDelegation(vote.user).getShortName().trim() }})
</span>
</div>
</div>
<div *ngIf="!vote.user">{{ 'Anonymous' | translate }}</div>
</div>
<div *pblNgridCellDef="'vote'; row as vote" class="vote-cell">
<div class="vote-cell-icon-container" [ngClass]="voteOptionStyle[vote.value].css">
<mat-icon>{{ voteOptionStyle[vote.value].icon }}</mat-icon>
</div>
<div>{{ vote.valueVerbose | translate }}</div>
</div>
</os-list-view-table>
<div *ngIf="!votesDataObservable">
{{ 'The individual votes were anonymized.' | translate }}
<div *ngIf="!vote.user">{{ 'Anonymous' | translate }}</div>
</div>
<div *pblNgridCellDef="'vote'; row as vote" class="vote-cell">
<div class="vote-cell-icon-container" [ngClass]="voteOptionStyle[vote.value].css">
<mat-icon>{{ voteOptionStyle[vote.value].icon }}</mat-icon>
</div>
<div>{{ vote.valueVerbose | translate }}</div>
</div>
</os-list-view-table>
<div *ngIf="!votesDataObservable">
{{ 'The individual votes were anonymized.' | translate }}
</div>
</div>

View File

@ -1,4 +1,4 @@
import { ChangeDetectionStrategy, Component, ViewEncapsulation } from '@angular/core';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ViewEncapsulation } from '@angular/core';
import { MatSnackBar } from '@angular/material/snack-bar';
import { Title } from '@angular/platform-browser';
import { ActivatedRoute, Router } from '@angular/router';
@ -44,6 +44,10 @@ export class MotionPollDetailComponent extends BasePollDetailComponentDirective<
public isVoteWeightActive: boolean;
public get showResults(): boolean {
return this.hasPerms() || this.poll.isPublished;
}
public constructor(
title: Title,
translate: TranslateService,
@ -57,7 +61,8 @@ export class MotionPollDetailComponent extends BasePollDetailComponentDirective<
votesRepo: MotionVoteRepositoryService,
configService: ConfigService,
protected operator: OperatorService,
private router: Router
private router: Router,
protected cd: ChangeDetectorRef
) {
super(
title,
@ -70,7 +75,8 @@ export class MotionPollDetailComponent extends BasePollDetailComponentDirective<
pollDialog,
pollService,
votesRepo,
operator
operator,
cd
);
configService
.get<boolean>('users_activate_vote_weight')

View File

@ -23,9 +23,7 @@
<div class="italic spacer-bottom-20">
<span *osPerms="'motions.can_manage_polls'; and: poll.type === 'pseudoanonymous'">
<button mat-icon-button color="warn" (click)="openVotingWarning()">
<mat-icon>
warning
</mat-icon>
<mat-icon>warning</mat-icon>
</button>
</span>
@ -42,7 +40,12 @@
<!-- Change state button -->
<div *osPerms="'motions.can_manage_polls'; and: !hideChangeState">
<button mat-stroked-button [ngClass]="pollStateActions[poll.state].css" (click)="changeState(poll.nextState)" [disabled]="stateChangePending">
<button
mat-stroked-button
[ngClass]="pollStateActions[poll.state].css"
(click)="changeState(poll.nextState)"
[disabled]="stateChangePending"
>
<mat-icon> {{ pollStateActions[poll.state].icon }}</mat-icon>
<span class="next-state-label">
<ng-container *ngIf="!stateChangePending">
@ -66,9 +69,7 @@
<!-- Detail link -->
<div class="poll-detail-button-wrapper">
<a mat-icon-button [routerLink]="pollLink" matTooltip="{{ 'More' | translate }}" *ngIf="poll.isPublished">
<mat-icon class="small-icon">
visibility
</mat-icon>
<mat-icon class="small-icon">visibility</mat-icon>
</a>
</div>
</mat-card>
@ -82,34 +83,7 @@
</ng-template>
<ng-template #viewTemplate>
<!-- Result Chart and legend -->
<div class="poll-chart-wrapper" *osPerms="'motions.can_manage_polls'; or: poll.isPublished">
<div class="vote-legend" [routerLink]="pollLink">
<!-- any (click)-binding in an *ngFor-loop of dynamic length will break the view on iOS.
Therefore, we have to provide the trackBy-function here.
I suppose there would be debug output, but i-devices are not giving any.
The error-handling-service (PR #5131) might be helpful here. Nothing easy to find. -->
<div *ngFor="let row of reducedPollTableData; trackBy: trackByIndex" [class]="row.votingOption">
<os-icon-container [icon]="row.value[0].icon" size="large">
{{ row.value[0].amount | parsePollNumber }}
<span *ngIf="row.value[0].showPercent">
{{ row.value[0].amount | pollPercentBase: poll:'motion' }}
</span>
</os-icon-container>
</div>
</div>
<div class="doughnut-chart" [routerLink]="pollLink">
<os-charts *ngIf="showChart" type="doughnut" [data]="chartDataSubject | async"></os-charts>
</div>
</div>
<!-- In Progress hint -->
<div class="motion-couting-in-progress-hint" *osPerms="'motions.can_manage_polls'; complement: true">
<span *ngIf="poll.isFinished">
{{ 'Counting of votes is in progress ...' | translate }}
</span>
</div>
<os-motion-poll-detail-content [poll]="poll" [routerLink]="pollLink"></os-motion-poll-detail-content>
</ng-template>
<ng-template #emptyTemplate>

View File

@ -15,7 +15,6 @@ import { MotionPollDialogService } from 'app/site/motions/services/motion-poll-d
import { MotionPollPdfService } from 'app/site/motions/services/motion-poll-pdf.service';
import { MotionPollService } from 'app/site/motions/services/motion-poll.service';
import { BasePollComponent } from 'app/site/polls/components/base-poll.component';
import { PollTableData } from 'app/site/polls/services/poll.service';
/**
* Component to show a motion-poll.
@ -29,8 +28,6 @@ export class MotionPollComponent extends BasePollComponent<ViewMotionPoll, Motio
@Input()
public set poll(value: ViewMotionPoll) {
this.initPoll(value);
const chartData = this.pollService.generateChartData(value);
this.chartDataSubject.next(chartData);
}
public get poll(): ViewMotionPoll {
@ -41,16 +38,6 @@ export class MotionPollComponent extends BasePollComponent<ViewMotionPoll, Motio
return `/motions/polls/${this.poll.id}`;
}
public get showChart(): boolean {
return this.pollService.showChart(this.poll);
}
public get reducedPollTableData(): PollTableData[] {
return this.pollService
.generateTableData(this.poll)
.filter(data => ['yes', 'no', 'abstain', 'votesinvalid'].includes(data.votingOption));
}
public get showPoll(): boolean {
if (this.poll) {
if (

View File

@ -1,4 +1,4 @@
import { Directive, OnInit } from '@angular/core';
import { ChangeDetectorRef, Directive, OnInit } from '@angular/core';
import { MatSnackBar } from '@angular/material/snack-bar';
import { Title } from '@angular/platform-browser';
import { ActivatedRoute } from '@angular/router';
@ -70,11 +70,6 @@ export abstract class BasePollDetailComponentDirective<V extends ViewBasePoll, S
*/
public labels: Label[] = [];
/**
* Subject, that holds the data for the chart.
*/
public chartDataSubject: BehaviorSubject<ChartData> = new BehaviorSubject(null);
// The observable for the votes-per-user table
public votesDataObservable: Observable<BaseVoteData[]>;
@ -106,7 +101,8 @@ export abstract class BasePollDetailComponentDirective<V extends ViewBasePoll, S
protected pollDialog: BasePollDialogService<V, S>,
protected pollService: S,
protected votesRepo: BaseRepository<ViewBaseVote, BaseVote, object>,
protected operator: OperatorService
protected operator: OperatorService,
protected cd: ChangeDetectorRef
) {
super(title, translate, matSnackbar);
this.setup();
@ -187,14 +183,6 @@ export abstract class BasePollDetailComponentDirective<V extends ViewBasePoll, S
*/
protected abstract createVotesData(): void;
/**
* Initializes data for the shown chart.
* Could be overwritten to implement custom chart data.
*/
protected initChartData(): void {
this.chartDataSubject.next(this.pollService.generateChartData(this.poll));
}
/**
* Helper-function to search for this poll and display data or create a new one.
*/
@ -206,8 +194,8 @@ export abstract class BasePollDetailComponentDirective<V extends ViewBasePoll, S
if (poll) {
this.poll = poll;
this.createVotesData();
this.initChartData();
this.optionsLoaded.resolve();
this.cd.markForCheck();
}
})
);

View File

@ -1,5 +1,5 @@
import { ViewBasePoll } from './view-base-poll';
export interface HasViewPolls<T extends ViewBasePoll> {
polls: T[];
polls?: T[];
}

View File

@ -10,6 +10,7 @@ import {
MajorityMethod,
PercentBase,
PollColor,
PollState,
PollType,
VOTE_UNDOCUMENTED
} from 'app/shared/models/poll/base-poll';
@ -104,6 +105,7 @@ export const PollMajorityMethod: CalculableMajorityMethod[] = [
export interface PollData {
pollmethod: string;
type: string;
state: PollState;
onehundred_percent_base: string;
options: PollDataOption[];
votesvalid: number;

View File

@ -7,6 +7,6 @@
<h2 class="poll-title">{{ data.data.poll.title }}</h2>
</div>
<div *ngIf="data.data.poll.state === PollState.Published">
<os-motion-poll-detail-content [poll]="pollData" [chartData]="chartDataSubject" iconSize="gigantic"></os-motion-poll-detail-content>
<os-motion-poll-detail-content [poll]="pollData" iconSize="gigantic"></os-motion-poll-detail-content>
</div>
</ng-container>