From 1b1499a660475c714d67395d922d9f783f34b4b3 Mon Sep 17 00:00:00 2001 From: Maximilian Krambach Date: Thu, 25 Apr 2019 15:34:42 +0200 Subject: [PATCH] Assignment slides --- .../src/app/core/ui-services/poll.service.ts | 1 + .../assignment-detail.component.html | 10 ++- .../assignment-list.component.html | 4 +- .../assignment-list.component.scss | 4 + .../assignment-poll-dialog.component.ts | 13 +-- .../services/assignment-poll.service.ts | 5 ++ .../assignment-slide.component.html | 15 +++- .../assignment/assignment-slide.component.ts | 3 +- .../assignments/poll/poll-slide-data.ts | 12 +++ .../poll/poll-slide.component.html | 59 +++++++++++++- .../poll/poll-slide.component.scss | 20 +++++ .../assignments/poll/poll-slide.component.ts | 79 +++++++++++++++++-- openslides/assignments/projector.py | 2 +- 13 files changed, 204 insertions(+), 23 deletions(-) diff --git a/client/src/app/core/ui-services/poll.service.ts b/client/src/app/core/ui-services/poll.service.ts index 23f4e8bb4..09e522c51 100644 --- a/client/src/app/core/ui-services/poll.service.ts +++ b/client/src/app/core/ui-services/poll.service.ts @@ -169,6 +169,7 @@ export abstract class PollService { public getSpecialLabel(value: number): string { if (value >= 0) { return value.toString(); + // TODO: toLocaleString(lang); but translateService is not usable here, thus lang is not well defined } const vote = this.specialPollVotes.find(special => special[0] === value); return vote ? vote[1] : 'Undocumented special (negative) value'; diff --git a/client/src/app/site/assignments/components/assignment-detail/assignment-detail.component.html b/client/src/app/site/assignments/components/assignment-detail/assignment-detail.component.html index f40ff7eb8..318a3fa98 100644 --- a/client/src/app/site/assignments/components/assignment-detail/assignment-detail.component.html +++ b/client/src/app/site/assignments/components/assignment-detail/assignment-detail.component.html @@ -28,14 +28,22 @@ PDF - + + + +
+ diff --git a/client/src/app/site/assignments/components/assignment-list/assignment-list.component.html b/client/src/app/site/assignments/components/assignment-list/assignment-list.component.html index 549422ef5..e1c4e374f 100644 --- a/client/src/app/site/assignments/components/assignment-list/assignment-list.component.html +++ b/client/src/app/site/assignments/components/assignment-list/assignment-list.component.html @@ -36,8 +36,8 @@ - - + + Projector diff --git a/client/src/app/site/assignments/components/assignment-list/assignment-list.component.scss b/client/src/app/site/assignments/components/assignment-list/assignment-list.component.scss index e69de29bb..5bd6af3bf 100644 --- a/client/src/app/site/assignments/components/assignment-list/assignment-list.component.scss +++ b/client/src/app/site/assignments/components/assignment-list/assignment-list.component.scss @@ -0,0 +1,4 @@ +/** Title */ +.mat-column-title { + padding-left: 10px; +} diff --git a/client/src/app/site/assignments/components/assignment-poll-dialog/assignment-poll-dialog.component.ts b/client/src/app/site/assignments/components/assignment-poll-dialog/assignment-poll-dialog.component.ts index ecfaae768..967e5a3e1 100644 --- a/client/src/app/site/assignments/components/assignment-poll-dialog/assignment-poll-dialog.component.ts +++ b/client/src/app/site/assignments/components/assignment-poll-dialog/assignment-poll-dialog.component.ts @@ -3,17 +3,12 @@ import { MatDialogRef, MAT_DIALOG_DATA, MatSnackBar } from '@angular/material'; import { TranslateService } from '@ngx-translate/core'; import { AssignmentPollOption } from 'app/shared/models/assignments/assignment-poll-option'; -import { AssignmentPollService } from '../../services/assignment-poll.service'; +import { AssignmentPollService, SummaryPollKey } from '../../services/assignment-poll.service'; import { CalculablePollKey, PollVoteValue } from 'app/core/ui-services/poll.service'; import { UserRepositoryService } from 'app/core/repositories/users/user-repository.service'; import { ViewAssignmentPoll } from '../../models/view-assignment-poll'; import { ViewAssignmentPollOption } from '../../models/view-assignment-poll-option'; -/** - * Vote entries included once for summary (e.g. total votes cast) - */ -type summaryPollKey = 'votescast' | 'votesvalid' | 'votesinvalid'; - /** * A dialog for updating the values of an assignment-related poll. */ @@ -26,7 +21,7 @@ export class AssignmentPollDialogComponent { /** * The summary values that will have fields in the dialog */ - public sumValues: summaryPollKey[] = ['votesvalid', 'votesinvalid', 'votescast']; + public sumValues: SummaryPollKey[] = ['votesvalid', 'votesinvalid', 'votescast']; /** * List of accepted special non-numerical values. @@ -148,7 +143,7 @@ export class AssignmentPollDialogComponent { * @param value * @returns integer or undefined */ - public getSumValue(value: summaryPollKey): number | undefined { + public getSumValue(value: SummaryPollKey): number | undefined { return this.data[value] || undefined; } @@ -158,7 +153,7 @@ export class AssignmentPollDialogComponent { * @param value * @param weight */ - public setSumValue(value: summaryPollKey, weight: string): void { + public setSumValue(value: SummaryPollKey, weight: string): void { this.data[value] = parseFloat(weight); } diff --git a/client/src/app/site/assignments/services/assignment-poll.service.ts b/client/src/app/site/assignments/services/assignment-poll.service.ts index 44f1ee992..71b41cffa 100644 --- a/client/src/app/site/assignments/services/assignment-poll.service.ts +++ b/client/src/app/site/assignments/services/assignment-poll.service.ts @@ -15,6 +15,11 @@ type AssignmentPollValues = 'auto' | 'votes' | 'yesnoabstain' | 'yesno'; export type AssignmentPollMethod = 'yn' | 'yna' | 'votes'; export type AssignmentPercentBase = 'YES_NO_ABSTAIN' | 'YES_NO' | 'VALID' | 'CAST' | 'DISABLED'; +/** + * Vote entries included once for summary (e.g. total votes cast) + */ +export type SummaryPollKey = 'votescast' | 'votesvalid' | 'votesinvalid' | 'votesno' | 'votesabstain'; + /** * Service class for assignment polls. */ diff --git a/client/src/app/slides/assignments/assignment/assignment-slide.component.html b/client/src/app/slides/assignments/assignment/assignment-slide.component.html index edc11e288..5c66fea3f 100644 --- a/client/src/app/slides/assignments/assignment/assignment-slide.component.html +++ b/client/src/app/slides/assignments/assignment/assignment-slide.component.html @@ -1,3 +1,16 @@
-

TODO

+
+

{{ data.data.title }}

+
+ +
+ + +

Candidates

+
+
+ {{ candidate.user }} + {{ candidate.elected ? 'star' : 'star_border' }} +
+
diff --git a/client/src/app/slides/assignments/assignment/assignment-slide.component.ts b/client/src/app/slides/assignments/assignment/assignment-slide.component.ts index 1a4d84fd1..2b3eb096a 100644 --- a/client/src/app/slides/assignments/assignment/assignment-slide.component.ts +++ b/client/src/app/slides/assignments/assignment/assignment-slide.component.ts @@ -17,12 +17,11 @@ export class AssignmentSlideComponent extends BaseSlideComponent) { this._data = data; - console.log('Data: ', data); } + public get data(): SlideData { return this._data; } - // UNTIL HERE public constructor() { super(); diff --git a/client/src/app/slides/assignments/poll/poll-slide-data.ts b/client/src/app/slides/assignments/poll/poll-slide-data.ts index e72af43b1..532389dd3 100644 --- a/client/src/app/slides/assignments/poll/poll-slide-data.ts +++ b/client/src/app/slides/assignments/poll/poll-slide-data.ts @@ -1,4 +1,15 @@ import { AssignmentPercentBase, AssignmentPollMethod } from 'app/site/assignments/services/assignment-poll.service'; +import { PollVoteValue } from 'app/core/ui-services/poll.service'; + +export interface PollSlideOption { + user: string; + is_elected: boolean; + votes: { + weight: PollVoteValue; + value: string; + percent?: string; + }[]; +} export interface PollSlideData { title: string; @@ -13,5 +24,6 @@ export interface PollSlideData { votesvalid?: string; votesinvalid?: string; votescast?: string; + options?: PollSlideOption[]; }; } diff --git a/client/src/app/slides/assignments/poll/poll-slide.component.html b/client/src/app/slides/assignments/poll/poll-slide.component.html index edc11e288..1d54257d7 100644 --- a/client/src/app/slides/assignments/poll/poll-slide.component.html +++ b/client/src/app/slides/assignments/poll/poll-slide.component.html @@ -1,3 +1,60 @@
-

TODO

+

{{ data.data.title }}

+
+
Waiting for results...
+
+
+
+
+

Candidates

+
+
+

Votes

+
+
+
+
+
+ star + {{ option.user }} +
+
+
+ {{ vote.value | translate }}: + + {{ labelValue(vote.weight) | translate }} + + + ({{ vote.percent }}) + +
+
+
+
+
+
+ Valid votes +
+
+ {{ labelValue(data.data.poll.votesvalid) | translate }} +
+
+
+
+ Invalid votes +
+
+ {{ labelValue(data.data.poll.votesinvalid) | translate }} +
+
+
+
+ Total votes cast +
+
+ {{ labelValue(data.data.poll.votescast) | translate }} +
+
+
+
diff --git a/client/src/app/slides/assignments/poll/poll-slide.component.scss b/client/src/app/slides/assignments/poll/poll-slide.component.scss index e69de29bb..04eb64c42 100644 --- a/client/src/app/slides/assignments/poll/poll-slide.component.scss +++ b/client/src/app/slides/assignments/poll/poll-slide.component.scss @@ -0,0 +1,20 @@ +.row { + display: table; + width: 100%; + .option-name { + display: table-cell; + padding: 5px; + border: 1px solid grey; + vertical-align: middle; + width: 70%; + .bold { + font-weight: bold; + } + } + .option-percents { + display: table-cell; + padding: 5px; + border: 1px solid grey; + width: 30%; + } +} diff --git a/client/src/app/slides/assignments/poll/poll-slide.component.ts b/client/src/app/slides/assignments/poll/poll-slide.component.ts index f1bbaaf9e..4ad071ee0 100644 --- a/client/src/app/slides/assignments/poll/poll-slide.component.ts +++ b/client/src/app/slides/assignments/poll/poll-slide.component.ts @@ -1,5 +1,6 @@ import { Component, Input } from '@angular/core'; +import { AssignmentPollService, SummaryPollKey } from 'app/site/assignments/services/assignment-poll.service'; import { BaseSlideComponent } from 'app/slides/base-slide-component'; import { PollSlideData } from './poll-slide-data'; import { SlideData } from 'app/core/core-services/projector-data.service'; @@ -10,21 +11,87 @@ import { SlideData } from 'app/core/core-services/projector-data.service'; styleUrls: ['./poll-slide.component.scss'] }) export class PollSlideComponent extends BaseSlideComponent { - // TODO: Remove the following block, if not needed. - // This is just for debugging to get a console statement with all recieved - // data from the server private _data: SlideData; + + public pollValues: SummaryPollKey[] = ['votesno', 'votesabstain', 'votesvalid', 'votesinvalid', 'votescast']; + @Input() public set data(data: SlideData) { this._data = data; - console.log('Data: ', data); + this.setPercents(); } + public get data(): SlideData { return this._data; } - // UNTIL HERE - public constructor() { + public constructor(private pollService: AssignmentPollService) { super(); } + + public getVoteString(rawValue: string): string { + const num = parseFloat(rawValue); + if (!isNaN(num)) { + return this.pollService.getSpecialLabel(num); + } + return '-'; + } + + private setPercents(): void { + if ( + this.data.data.assignments_poll_100_percent_base === 'DISABLED' || + !this.data.data.poll.has_votes || + !this.data.data.poll.options.length + ) { + return; + } + for (const option of this.data.data.poll.options) { + for (const vote of option.votes) { + const voteweight = parseFloat(vote.weight); + if (isNaN(voteweight) || voteweight < 0) { + return; + } + let base: number; + switch (this.data.data.assignments_poll_100_percent_base) { + case 'CAST': + base = this.data.data.poll.votescast ? parseFloat(this.data.data.poll.votescast) : 0; + break; + case 'VALID': + base = this.data.data.poll.votesvalid ? parseFloat(this.data.data.poll.votesvalid) : 0; + break; + case 'YES_NO': + case 'YES_NO_ABSTAIN': + const yesOption = option.votes.find(v => v.value === 'Yes'); + const yes = yesOption ? parseFloat(yesOption.weight) : -1; + const noOption = option.votes.find(v => v.value === 'No'); + const no = noOption ? parseFloat(noOption.weight) : -1; + const absOption = option.votes.find(v => v.value === 'Abstain'); + const abs = absOption ? parseFloat(absOption.weight) : -1; + if (this.data.data.assignments_poll_100_percent_base === 'YES_NO_ABSTAIN') { + base = yes >= 0 && no >= 0 && abs >= 0 ? yes + no + abs : 0; + } else { + if (vote.value !== 'Abstain') { + base = yes >= 0 && no >= 0 ? yes + no : 0; + } + } + break; + default: + break; + } + if (base) { + vote.percent = `${Math.round(((parseFloat(vote.weight) * 100) / base) * 100) / 100}%`; + } + } + } + } + + /** + * Converts a number-like string to a simpler, more readable version + * + * @param input a server-sent string representing a numerical value + * @returns either the special label or a cleaned-up number of the inpu string + */ + public labelValue(input: string): string { + return this.pollService.getSpecialLabel(parseFloat(input)); + } } diff --git a/openslides/assignments/projector.py b/openslides/assignments/projector.py index 1fb2e4d06..581166f25 100644 --- a/openslides/assignments/projector.py +++ b/openslides/assignments/projector.py @@ -87,7 +87,7 @@ async def poll_slide( poll_data["options"] = [ { - "user": await get_user_name(all_data, option["user_id"]), + "user": await get_user_name(all_data, option["candidate_id"]), "is_elected": option["is_elected"], "votes": option["votes"], }