Merge pull request #4625 from MaximilianKrambach/assignmentSlide
Assignment/Assignment poll slide
This commit is contained in:
commit
ddd4588a1c
@ -169,6 +169,7 @@ export abstract class PollService {
|
|||||||
public getSpecialLabel(value: number): string {
|
public getSpecialLabel(value: number): string {
|
||||||
if (value >= 0) {
|
if (value >= 0) {
|
||||||
return value.toString();
|
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);
|
const vote = this.specialPollVotes.find(special => special[0] === value);
|
||||||
return vote ? vote[1] : 'Undocumented special (negative) value';
|
return vote ? vote[1] : 'Undocumented special (negative) value';
|
||||||
|
@ -28,14 +28,22 @@
|
|||||||
<span translate>PDF</span>
|
<span translate>PDF</span>
|
||||||
</button>
|
</button>
|
||||||
<mat-divider></mat-divider>
|
<mat-divider></mat-divider>
|
||||||
<!-- Delete -->
|
|
||||||
</div>
|
</div>
|
||||||
|
<!-- Project -->
|
||||||
|
<os-projector-button
|
||||||
|
[object]="assignment"
|
||||||
|
[menuItem]="true"
|
||||||
|
*osPerms="'core.can_manage_projector'"
|
||||||
|
></os-projector-button>
|
||||||
|
|
||||||
|
<!-- Delete -->
|
||||||
<div *ngIf="assignment && hasPerms('manage')">
|
<div *ngIf="assignment && hasPerms('manage')">
|
||||||
<button mat-menu-item class="red-warning-text" (click)="onDeleteAssignmentButton()">
|
<button mat-menu-item class="red-warning-text" (click)="onDeleteAssignmentButton()">
|
||||||
<mat-icon>delete</mat-icon>
|
<mat-icon>delete</mat-icon>
|
||||||
<span translate>Delete</span>
|
<span translate>Delete</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</mat-menu>
|
</mat-menu>
|
||||||
</os-head-bar>
|
</os-head-bar>
|
||||||
|
|
||||||
|
@ -0,0 +1,4 @@
|
|||||||
|
/** Title */
|
||||||
|
.mat-column-title {
|
||||||
|
padding-left: 10px;
|
||||||
|
}
|
@ -3,17 +3,12 @@ import { MatDialogRef, MAT_DIALOG_DATA, MatSnackBar } from '@angular/material';
|
|||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
|
||||||
import { AssignmentPollOption } from 'app/shared/models/assignments/assignment-poll-option';
|
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 { CalculablePollKey, PollVoteValue } from 'app/core/ui-services/poll.service';
|
||||||
import { UserRepositoryService } from 'app/core/repositories/users/user-repository.service';
|
import { UserRepositoryService } from 'app/core/repositories/users/user-repository.service';
|
||||||
import { ViewAssignmentPoll } from '../../models/view-assignment-poll';
|
import { ViewAssignmentPoll } from '../../models/view-assignment-poll';
|
||||||
import { ViewAssignmentPollOption } from '../../models/view-assignment-poll-option';
|
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.
|
* 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
|
* 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.
|
* List of accepted special non-numerical values.
|
||||||
@ -148,7 +143,7 @@ export class AssignmentPollDialogComponent {
|
|||||||
* @param value
|
* @param value
|
||||||
* @returns integer or undefined
|
* @returns integer or undefined
|
||||||
*/
|
*/
|
||||||
public getSumValue(value: summaryPollKey): number | undefined {
|
public getSumValue(value: SummaryPollKey): number | undefined {
|
||||||
return this.data[value] || undefined;
|
return this.data[value] || undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -158,7 +153,7 @@ export class AssignmentPollDialogComponent {
|
|||||||
* @param value
|
* @param value
|
||||||
* @param weight
|
* @param weight
|
||||||
*/
|
*/
|
||||||
public setSumValue(value: summaryPollKey, weight: string): void {
|
public setSumValue(value: SummaryPollKey, weight: string): void {
|
||||||
this.data[value] = parseFloat(weight);
|
this.data[value] = parseFloat(weight);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,6 +15,11 @@ type AssignmentPollValues = 'auto' | 'votes' | 'yesnoabstain' | 'yesno';
|
|||||||
export type AssignmentPollMethod = 'yn' | 'yna' | 'votes';
|
export type AssignmentPollMethod = 'yn' | 'yna' | 'votes';
|
||||||
export type AssignmentPercentBase = 'YES_NO_ABSTAIN' | 'YES_NO' | 'VALID' | 'CAST' | 'DISABLED';
|
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.
|
* Service class for assignment polls.
|
||||||
*/
|
*/
|
||||||
|
@ -1,3 +1,16 @@
|
|||||||
<div *ngIf="data">
|
<div *ngIf="data">
|
||||||
<h1>TODO</h1>
|
<div class="slidetitle">
|
||||||
|
<h1>{{ data.data.title }}</h1>
|
||||||
|
<h2 translate>Election</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div *ngIf="data.data && data.data.description" [innerHTML]="data.data.description"></div>
|
||||||
|
|
||||||
|
<h3 translate>Candidates</h3>
|
||||||
|
<ul *ngIf="data.data.assignment_related_users && data.data.assignment_related_users.length">
|
||||||
|
<li *ngFor="let candidate of data.data.assignment_related_users">
|
||||||
|
{{ candidate.user }}
|
||||||
|
<mat-icon *ngIf="candidate.elected">star</mat-icon>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
@ -17,12 +17,11 @@ export class AssignmentSlideComponent extends BaseSlideComponent<AssignmentSlide
|
|||||||
@Input()
|
@Input()
|
||||||
public set data(data: SlideData<AssignmentSlideData>) {
|
public set data(data: SlideData<AssignmentSlideData>) {
|
||||||
this._data = data;
|
this._data = data;
|
||||||
console.log('Data: ', data);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public get data(): SlideData<AssignmentSlideData> {
|
public get data(): SlideData<AssignmentSlideData> {
|
||||||
return this._data;
|
return this._data;
|
||||||
}
|
}
|
||||||
// UNTIL HERE
|
|
||||||
|
|
||||||
public constructor() {
|
public constructor() {
|
||||||
super();
|
super();
|
||||||
|
@ -1,4 +1,15 @@
|
|||||||
import { AssignmentPercentBase, AssignmentPollMethod } from 'app/site/assignments/services/assignment-poll.service';
|
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 {
|
export interface PollSlideData {
|
||||||
title: string;
|
title: string;
|
||||||
@ -13,5 +24,6 @@ export interface PollSlideData {
|
|||||||
votesvalid?: string;
|
votesvalid?: string;
|
||||||
votesinvalid?: string;
|
votesinvalid?: string;
|
||||||
votescast?: string;
|
votescast?: string;
|
||||||
|
options?: PollSlideOption[];
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,64 @@
|
|||||||
<div *ngIf="data">
|
<div *ngIf="data">
|
||||||
<h1>TODO</h1>
|
<div class="slidetitle">
|
||||||
|
<h1>{{ data.data.title }}</h1>
|
||||||
|
<h2 translate>Election result</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="spacer-top-10"></div>
|
||||||
|
<div *ngIf="!data.data.poll.published"><span translate>Waiting for results</span><span>...</span></div>
|
||||||
|
<div *ngIf="data.data.poll.published">
|
||||||
|
<div *ngIf="data.data.poll.has_votes" class="result-table">
|
||||||
|
<div class="row">
|
||||||
|
<div class="option-name heading">
|
||||||
|
<h3 translate>Candidates</h3>
|
||||||
|
</div>
|
||||||
|
<div class="option-percents heading">
|
||||||
|
<h3 translate>Votes</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div *ngFor="let option of data.data.poll.options">
|
||||||
|
<div class="row">
|
||||||
|
<div class="option-name">
|
||||||
|
<span class="bold">{{ option.user }}</span>
|
||||||
|
<mat-icon *ngIf="option.is_elected">star</mat-icon>
|
||||||
|
</div>
|
||||||
|
<div class="option-percents">
|
||||||
|
<div *ngFor="let vote of option.votes" class="bold">
|
||||||
|
<span *ngIf="vote.value !== 'Votes'">{{ vote.value | translate }}:</span>
|
||||||
|
<span>
|
||||||
|
{{ labelValue(vote.weight) | translate }}
|
||||||
|
</span>
|
||||||
|
<span *ngIf="vote.percent">
|
||||||
|
({{ vote.percent }})
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div *ngIf="data.data.poll.votesvalid !== null" class="row">
|
||||||
|
<div class="option-name grey">
|
||||||
|
<span translate>Valid votes</span>
|
||||||
|
</div>
|
||||||
|
<div class="option-percents grey">
|
||||||
|
{{ labelValue(data.data.poll.votesvalid) | translate }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div *ngIf="data.data.poll.votesinvalid !== null" class="row">
|
||||||
|
<div class="option-name grey">
|
||||||
|
<span translate>Invalid votes</span>
|
||||||
|
</div>
|
||||||
|
<div class="option-percents grey">
|
||||||
|
{{ labelValue(data.data.poll.votesinvalid) | translate }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div *ngIf="data.data.poll.votescast !== null" class="row">
|
||||||
|
<div class="option-name grey">
|
||||||
|
<span translate>Total votes cast</span>
|
||||||
|
</div>
|
||||||
|
<div class="option-percents grey">
|
||||||
|
{{ labelValue(data.data.poll.votescast) | translate }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -0,0 +1,31 @@
|
|||||||
|
.row {
|
||||||
|
border-top: 1px solid #ddd;
|
||||||
|
display: table;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.heading {
|
||||||
|
text-transform: uppercase;
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-name {
|
||||||
|
display: table-cell;
|
||||||
|
padding: 5px;
|
||||||
|
vertical-align: middle;
|
||||||
|
width: 70%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-percents {
|
||||||
|
display: table-cell;
|
||||||
|
padding: 5px;
|
||||||
|
width: 30%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grey {
|
||||||
|
background-color: #ddd !important;
|
||||||
|
color: rgba(0, 0, 0, 0.87) !important;
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
import { Component, Input } from '@angular/core';
|
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 { BaseSlideComponent } from 'app/slides/base-slide-component';
|
||||||
import { PollSlideData } from './poll-slide-data';
|
import { PollSlideData } from './poll-slide-data';
|
||||||
import { SlideData } from 'app/core/core-services/projector-data.service';
|
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']
|
styleUrls: ['./poll-slide.component.scss']
|
||||||
})
|
})
|
||||||
export class PollSlideComponent extends BaseSlideComponent<PollSlideData> {
|
export class PollSlideComponent extends BaseSlideComponent<PollSlideData> {
|
||||||
// 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<PollSlideData>;
|
private _data: SlideData<PollSlideData>;
|
||||||
|
|
||||||
|
public pollValues: SummaryPollKey[] = ['votesno', 'votesabstain', 'votesvalid', 'votesinvalid', 'votescast'];
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
public set data(data: SlideData<PollSlideData>) {
|
public set data(data: SlideData<PollSlideData>) {
|
||||||
this._data = data;
|
this._data = data;
|
||||||
console.log('Data: ', data);
|
this.setPercents();
|
||||||
}
|
}
|
||||||
|
|
||||||
public get data(): SlideData<PollSlideData> {
|
public get data(): SlideData<PollSlideData> {
|
||||||
return this._data;
|
return this._data;
|
||||||
}
|
}
|
||||||
// UNTIL HERE
|
|
||||||
|
|
||||||
public constructor() {
|
public constructor(private pollService: AssignmentPollService) {
|
||||||
super();
|
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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
#sidebox {
|
#sidebox {
|
||||||
width: 260px;
|
width: 260px;
|
||||||
right: 0;
|
right: 0;
|
||||||
margin-top: 95px;
|
margin-top: 94px;
|
||||||
background: #d3d3d3;
|
background: #d3d3d3;
|
||||||
border-radius: 7px 0 0 7px;
|
border-radius: 7px 0 0 7px;
|
||||||
padding: 3px 7px 10px 10px;
|
padding: 3px 7px 10px 10px;
|
||||||
|
@ -22,18 +22,19 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.slidetitle {
|
.slidetitle {
|
||||||
border-bottom: 5px solid #d3d3d3;
|
border-bottom: 4px solid #d3d3d3;
|
||||||
margin-bottom: 40px;
|
margin-bottom: 40px;
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
padding-bottom: 0;
|
padding-bottom: 0;
|
||||||
|
margin-top: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
color: #9a9898;
|
color: #9a9898;
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
margin-bottom: 5px;
|
margin-bottom: 2px;
|
||||||
font-size: 28px;
|
font-size: 28px;
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
display: block;
|
display: block;
|
||||||
@ -48,4 +49,8 @@
|
|||||||
hr {
|
hr {
|
||||||
margin: 10px 0;
|
margin: 10px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bold {
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -87,7 +87,7 @@ async def poll_slide(
|
|||||||
|
|
||||||
poll_data["options"] = [
|
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"],
|
"is_elected": option["is_elected"],
|
||||||
"votes": option["votes"],
|
"votes": option["votes"],
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user