Even more voting refinement
Various additional refinements for a more well rounded voting experience
This commit is contained in:
parent
a05662a0f8
commit
ee4c6aa0bf
@ -1,4 +1,4 @@
|
|||||||
@import '~assets/styles/poll-colors.scss';
|
@import '~assets/styles/poll-styles-common.scss';
|
||||||
|
|
||||||
.result-wrapper {
|
.result-wrapper {
|
||||||
display: grid;
|
display: grid;
|
||||||
@ -21,18 +21,6 @@
|
|||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.yes {
|
|
||||||
color: $votes-yes-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
.no {
|
|
||||||
color: $votes-no-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
.abstain {
|
|
||||||
color: $votes-abstain-color;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.doughnut-chart {
|
.doughnut-chart {
|
||||||
|
@ -77,6 +77,22 @@ export abstract class BasePoll<
|
|||||||
return this.onehundred_percent_base === PercentBase.Cast;
|
return this.onehundred_percent_base === PercentBase.Cast;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public get isAnalog(): boolean {
|
||||||
|
return this.type === PollType.Analog;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get isNamed(): boolean {
|
||||||
|
return this.type === PollType.Named;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get isAnon(): boolean {
|
||||||
|
return this.type === PollType.Pseudoanonymous;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get isEVoting(): boolean {
|
||||||
|
return this.isNamed || this.isAnon;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine if the state is finished or published
|
* Determine if the state is finished or published
|
||||||
*/
|
*/
|
||||||
|
@ -36,12 +36,10 @@ export class PollPercentBasePipe implements PipeTransform {
|
|||||||
totalByBase = this.motionPollService.getPercentBase(poll);
|
totalByBase = this.motionPollService.getPercentBase(poll);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (totalByBase) {
|
if (totalByBase && totalByBase > 0) {
|
||||||
const percentNumber = (value / totalByBase) * 100;
|
const percentNumber = (value / totalByBase) * 100;
|
||||||
if (percentNumber > 0) {
|
const result = percentNumber % 1 === 0 ? percentNumber : percentNumber.toFixed(this.decimalPlaces);
|
||||||
const result = percentNumber % 1 === 0 ? percentNumber : percentNumber.toFixed(this.decimalPlaces);
|
return `(${result} %)`;
|
||||||
return `(${result} %)`;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -263,7 +263,7 @@
|
|||||||
<mat-form-field>
|
<mat-form-field>
|
||||||
<input
|
<input
|
||||||
matInput
|
matInput
|
||||||
placeholder="{{ 'Default comment on the ballot paper' | translate }}"
|
placeholder="{{ 'Hint on voting' | translate }}"
|
||||||
formControlName="default_poll_description"
|
formControlName="default_poll_description"
|
||||||
/>
|
/>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
@ -9,4 +9,21 @@
|
|||||||
border-bottom: 1px solid mat-color($background, focused-button);
|
border-bottom: 1px solid mat-color($background, focused-button);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.openslides-theme .pbl-ngrid-row:hover {
|
||||||
|
background-color: mat-color($background, card);
|
||||||
|
}
|
||||||
|
|
||||||
|
.openslides-theme os-list-view-table os-sort-filter-bar .custom-table-header {
|
||||||
|
&,
|
||||||
|
.action-buttons .input-container input {
|
||||||
|
background: mat-color($background, card);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.openslides-theme .pbl-ngrid-header-cell:first-child {
|
||||||
|
&::after {
|
||||||
|
border-right: 1px solid mat-color($background, focused-button);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,7 @@
|
|||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<th class="voting-option" translate>Candidates</th>
|
<th class="voting-option" translate>Candidates</th>
|
||||||
<th class="result voted-yes">
|
<th class="result yes">
|
||||||
<span *ngIf="!poll.isMethodY" translate>
|
<span *ngIf="!poll.isMethodY" translate>
|
||||||
Yes
|
Yes
|
||||||
</span>
|
</span>
|
||||||
@ -35,8 +35,8 @@
|
|||||||
Votes
|
Votes
|
||||||
</span>
|
</span>
|
||||||
</th>
|
</th>
|
||||||
<th class="result voted-no" translate *ngIf="!poll.isMethodY">No</th>
|
<th class="result no" translate *ngIf="!poll.isMethodY">No</th>
|
||||||
<th class="result voted-abstain" translate *ngIf="poll.isMethodYNA">Abstain</th>
|
<th class="result abstain" translate *ngIf="poll.isMethodYNA">Abstain</th>
|
||||||
</tr>
|
</tr>
|
||||||
<tr *ngFor="let row of getTableData()" [class]="row.class">
|
<tr *ngFor="let row of getTableData()" [class]="row.class">
|
||||||
<td class="voting-option">
|
<td class="voting-option">
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
@import '~assets/styles/variables.scss';
|
|
||||||
@import '~assets/styles/poll-colors.scss';
|
@import '~assets/styles/poll-colors.scss';
|
||||||
|
@import '~assets/styles/poll-styles-common.scss';
|
||||||
|
|
||||||
.assignment-result-wrapper {
|
.assignment-result-wrapper {
|
||||||
.assignment-result-table {
|
.assignment-result-table {
|
||||||
@ -88,30 +88,6 @@
|
|||||||
padding-top: 20px;
|
padding-top: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.voted-yes {
|
|
||||||
color: $votes-yes-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
.voted-no {
|
|
||||||
color: $votes-no-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
.voted-abstain {
|
|
||||||
color: $votes-abstain-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
// theme
|
|
||||||
.openslides-theme .pbl-ngrid-row:hover {
|
|
||||||
background-color: #f9f9f9;
|
|
||||||
}
|
|
||||||
|
|
||||||
.openslides-theme os-list-view-table os-sort-filter-bar .custom-table-header {
|
|
||||||
&,
|
|
||||||
.action-buttons .input-container input {
|
|
||||||
background: white;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.openslides-theme .pbl-ngrid-no-data {
|
.openslides-theme .pbl-ngrid-no-data {
|
||||||
top: 10%;
|
top: 10%;
|
||||||
}
|
}
|
||||||
@ -128,6 +104,5 @@
|
|||||||
top: 0;
|
top: 0;
|
||||||
right: -1px;
|
right: -1px;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
border-right: 1px solid #e0e0e0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { Component, ViewEncapsulation } from '@angular/core';
|
import { Component, ViewEncapsulation } from '@angular/core';
|
||||||
import { MatSnackBar } from '@angular/material';
|
import { MatSnackBar } from '@angular/material';
|
||||||
import { Title } from '@angular/platform-browser';
|
import { Title } from '@angular/platform-browser';
|
||||||
import { ActivatedRoute } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
|
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { PblColumnDefinition } from '@pebula/ngrid';
|
import { PblColumnDefinition } from '@pebula/ngrid';
|
||||||
@ -49,7 +49,8 @@ export class AssignmentPollDetailComponent extends BasePollDetailComponent<ViewA
|
|||||||
pollService: PollService,
|
pollService: PollService,
|
||||||
votesRepo: AssignmentVoteRepositoryService,
|
votesRepo: AssignmentVoteRepositoryService,
|
||||||
private operator: OperatorService,
|
private operator: OperatorService,
|
||||||
private assignmentPollService: AssignmentPollService
|
private assignmentPollService: AssignmentPollService,
|
||||||
|
private router: Router
|
||||||
) {
|
) {
|
||||||
super(title, translate, matSnackbar, repo, route, groupRepo, prompt, pollDialog, pollService, votesRepo);
|
super(title, translate, matSnackbar, repo, route, groupRepo, prompt, pollDialog, pollService, votesRepo);
|
||||||
}
|
}
|
||||||
@ -126,8 +127,7 @@ export class AssignmentPollDetailComponent extends BasePollDetailComponent<ViewA
|
|||||||
}
|
}
|
||||||
|
|
||||||
public getVoteClass(votingResult: VotingResult): string {
|
public getVoteClass(votingResult: VotingResult): string {
|
||||||
const cssPrefix = 'voted-';
|
return votingResult.vote;
|
||||||
return `${cssPrefix}${votingResult.vote}`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public voteFitsMethod(result: VotingResult): boolean {
|
public voteFitsMethod(result: VotingResult): boolean {
|
||||||
@ -143,6 +143,10 @@ export class AssignmentPollDetailComponent extends BasePollDetailComponent<ViewA
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected onDeleted(): void {
|
||||||
|
this.router.navigate(['assignments', this.poll.assignment_id]);
|
||||||
|
}
|
||||||
|
|
||||||
public getTableData(): PollTableData[] {
|
public getTableData(): PollTableData[] {
|
||||||
return this.assignmentPollService.generateTableData(this.poll);
|
return this.assignmentPollService.generateTableData(this.poll);
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
<ng-container *ngIf="poll">
|
<ng-container *ngIf="poll">
|
||||||
<ng-container *ngIf="vmanager.canVote(poll) && !alreadyVoted; else cannotVote">
|
<ng-container *ngIf="vmanager.canVote(poll) && !alreadyVoted; else cannotVote">
|
||||||
<!-- Submit Vote -->
|
<!-- Poll hint -->
|
||||||
<ng-container [ngTemplateOutlet]="sendNow"></ng-container>
|
<p *ngIf="pollHint">
|
||||||
|
<i>{{ pollHint }}</i>
|
||||||
|
</p>
|
||||||
|
|
||||||
<!-- Leftover votes -->
|
<!-- Leftover votes -->
|
||||||
<h4
|
<h4
|
||||||
*ngIf="poll.pollmethod === AssignmentPollMethod.Votes && poll.votes_amount > 1 && !isGlobalOptionSelected()"
|
*ngIf="poll.pollmethod === AssignmentPollMethod.Votes && poll.votes_amount > 1"
|
||||||
>
|
>
|
||||||
{{ 'Votes for this poll' | translate }}: {{ getVotesCount() }}/{{ poll.votes_amount }}
|
{{ 'Votes for this poll' | translate }}: {{ getVotesCount() }}/{{ poll.votes_amount }}
|
||||||
</h4>
|
</h4>
|
||||||
@ -32,10 +34,12 @@
|
|||||||
|
|
||||||
<div *ngFor="let action of voteActions">
|
<div *ngFor="let action of voteActions">
|
||||||
<button
|
<button
|
||||||
|
class="vote-button"
|
||||||
mat-raised-button
|
mat-raised-button
|
||||||
(click)="saveSingleVote(option.id, action.vote)"
|
(click)="saveSingleVote(option.id, action.vote)"
|
||||||
[ngClass]="
|
[ngClass]="
|
||||||
voteRequestData.votes[option.id] === action.vote || voteRequestData.votes[option.id] === 1
|
voteRequestData.votes[option.id] === action.vote ||
|
||||||
|
voteRequestData.votes[option.id] === 1
|
||||||
? action.css
|
? action.css
|
||||||
: ''
|
: ''
|
||||||
"
|
"
|
||||||
@ -57,6 +61,7 @@
|
|||||||
<div class="global-option-grid">
|
<div class="global-option-grid">
|
||||||
<div *ngIf="poll.global_no">
|
<div *ngIf="poll.global_no">
|
||||||
<button
|
<button
|
||||||
|
class="vote-button"
|
||||||
mat-raised-button
|
mat-raised-button
|
||||||
(click)="saveGlobalVote('N')"
|
(click)="saveGlobalVote('N')"
|
||||||
[ngClass]="voteRequestData.global === 'N' ? 'voted-no' : ''"
|
[ngClass]="voteRequestData.global === 'N' ? 'voted-no' : ''"
|
||||||
@ -95,9 +100,13 @@
|
|||||||
|
|
||||||
<ng-template #cannotVote>
|
<ng-template #cannotVote>
|
||||||
<div class="centered-button-wrapper">
|
<div class="centered-button-wrapper">
|
||||||
<os-icon-container icon="check">
|
<div>
|
||||||
{{ 'You already voted on this poll' | translate}}
|
<mat-icon class="vote-submitted">
|
||||||
</os-icon-container>
|
check_circle
|
||||||
|
</mat-icon>
|
||||||
|
<br />
|
||||||
|
<span>{{ 'You already voted on this poll.' | translate }}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
@import '~assets/styles/poll-colors.scss';
|
@import '~assets/styles/poll-colors.scss';
|
||||||
|
@import '~assets/styles/poll-styles-common.scss';
|
||||||
|
|
||||||
%vote-grid-base {
|
%vote-grid-base {
|
||||||
display: grid;
|
display: grid;
|
||||||
@ -42,23 +43,21 @@
|
|||||||
|
|
||||||
.centered-button-wrapper {
|
.centered-button-wrapper {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
text-align: center;
|
||||||
> * {
|
> * {
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.vote-submitted {
|
||||||
|
color: $votes-yes-color;
|
||||||
|
font-size: 200%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Could be some more general component
|
.vote-button {
|
||||||
.voted-yes {
|
min-width: 50px;
|
||||||
background-color: $votes-yes-color;
|
min-height: 50px;
|
||||||
}
|
|
||||||
|
|
||||||
.voted-no {
|
|
||||||
background-color: $votes-no-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
.voted-abstain {
|
|
||||||
background-color: $votes-abstain-color;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.vote-label {
|
.vote-label {
|
||||||
|
@ -62,6 +62,10 @@ export class AssignmentPollVoteComponent extends BasePollVoteComponent<ViewAssig
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public get pollHint(): string {
|
||||||
|
return this.poll.assignment.default_poll_description;
|
||||||
|
}
|
||||||
|
|
||||||
private defineVoteOptions(): void {
|
private defineVoteOptions(): void {
|
||||||
this.voteActions.push({
|
this.voteActions.push({
|
||||||
vote: 'Y',
|
vote: 'Y',
|
||||||
@ -93,7 +97,7 @@ export class AssignmentPollVoteComponent extends BasePollVoteComponent<ViewAssig
|
|||||||
return Object.keys(this.voteRequestData.votes).filter(key => this.voteRequestData.votes[key]).length;
|
return Object.keys(this.voteRequestData.votes).filter(key => this.voteRequestData.votes[key]).length;
|
||||||
}
|
}
|
||||||
|
|
||||||
public isGlobalOptionSelected(): boolean {
|
private isGlobalOptionSelected(): boolean {
|
||||||
return !!this.voteRequestData.global;
|
return !!this.voteRequestData.global;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -145,7 +149,7 @@ export class AssignmentPollVoteComponent extends BasePollVoteComponent<ViewAssig
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.raiseError(
|
this.raiseError(
|
||||||
this.translate.instant('You reached the maximum amount of votes. Deselect somebody first')
|
this.translate.instant('You reached the maximum amount of votes. Deselect somebody first.')
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -165,7 +169,11 @@ export class AssignmentPollVoteComponent extends BasePollVoteComponent<ViewAssig
|
|||||||
|
|
||||||
public saveGlobalVote(globalVote: GlobalVote): void {
|
public saveGlobalVote(globalVote: GlobalVote): void {
|
||||||
this.voteRequestData.votes = {};
|
this.voteRequestData.votes = {};
|
||||||
this.voteRequestData.global = globalVote;
|
if (this.voteRequestData.global && this.voteRequestData.global === globalVote) {
|
||||||
this.submitVote();
|
delete this.voteRequestData.global;
|
||||||
|
} else {
|
||||||
|
this.voteRequestData.global = globalVote;
|
||||||
|
this.submitVote();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -30,10 +30,9 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Change state button -->
|
<!-- Change state button -->
|
||||||
<div *osPerms="'assignments.can_manage'">
|
<div *osPerms="'assignments.can_manage'; and: !hideChangeState">
|
||||||
<button
|
<button
|
||||||
mat-stroked-button
|
mat-stroked-button
|
||||||
*ngIf="!poll.isPublished"
|
|
||||||
[ngClass]="pollStateActions[poll.state].css"
|
[ngClass]="pollStateActions[poll.state].css"
|
||||||
(click)="changeState(poll.nextState)"
|
(click)="changeState(poll.nextState)"
|
||||||
>
|
>
|
||||||
@ -43,6 +42,11 @@
|
|||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Enter Votes Hint -->
|
||||||
|
<div *osPerms="'assignments.can_manage'; and: poll.type === 'analog' && !poll.stateHasVotes">
|
||||||
|
{{ 'Edit to enter votes.' | translate }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Chart -->
|
<!-- Chart -->
|
||||||
@ -70,9 +74,18 @@
|
|||||||
<div *osPerms="'assignments.can_manage'; and: poll && poll.isStarted">
|
<div *osPerms="'assignments.can_manage'; and: poll && poll.isStarted">
|
||||||
<os-poll-progress [poll]="poll"></os-poll-progress>
|
<os-poll-progress [poll]="poll"></os-poll-progress>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- The Vote -->
|
||||||
<os-assignment-poll-vote *ngIf="poll.canBeVotedFor" [poll]="poll"></os-assignment-poll-vote>
|
<os-assignment-poll-vote *ngIf="poll.canBeVotedFor" [poll]="poll"></os-assignment-poll-vote>
|
||||||
|
|
||||||
|
<!-- More-Button -->
|
||||||
<div class="poll-detail-button-wrapper">
|
<div class="poll-detail-button-wrapper">
|
||||||
<a mat-icon-button routerLink="/assignments/polls/{{ poll.id }}" matTooltip="{{ 'More' | translate }}">
|
<a
|
||||||
|
mat-icon-button
|
||||||
|
routerLink="/assignments/polls/{{ poll.id }}"
|
||||||
|
matTooltip="{{ 'More' | translate }}"
|
||||||
|
*ngIf="poll.isPublished"
|
||||||
|
>
|
||||||
<mat-icon class="small-icon">
|
<mat-icon class="small-icon">
|
||||||
visibility
|
visibility
|
||||||
</mat-icon>
|
</mat-icon>
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
@import '~assets/styles/poll-colors.scss';
|
@import '~assets/styles/poll-colors.scss';
|
||||||
|
@import '~assets/styles/poll-styles-common.scss';
|
||||||
|
|
||||||
.assignment-poll-wrapper {
|
.assignment-poll-wrapper {
|
||||||
position: relative;
|
position: relative;
|
||||||
@ -17,16 +18,4 @@
|
|||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.start-poll-button {
|
|
||||||
color: green !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stop-poll-button {
|
|
||||||
color: $poll-stop-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
.publish-poll-button {
|
|
||||||
color: $poll-publish-color;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Component, Input, OnInit, ViewEncapsulation } from '@angular/core';
|
import { Component, Input, OnInit } from '@angular/core';
|
||||||
import { FormBuilder, FormGroup } from '@angular/forms';
|
import { FormBuilder, FormGroup } from '@angular/forms';
|
||||||
import { MatDialog } from '@angular/material/dialog';
|
import { MatDialog } from '@angular/material/dialog';
|
||||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||||
@ -21,8 +21,7 @@ import { ViewAssignmentPoll } from '../../models/view-assignment-poll';
|
|||||||
@Component({
|
@Component({
|
||||||
selector: 'os-assignment-poll',
|
selector: 'os-assignment-poll',
|
||||||
templateUrl: './assignment-poll.component.html',
|
templateUrl: './assignment-poll.component.html',
|
||||||
styleUrls: ['./assignment-poll.component.scss'],
|
styleUrls: ['./assignment-poll.component.scss']
|
||||||
encapsulation: ViewEncapsulation.None
|
|
||||||
})
|
})
|
||||||
export class AssignmentPollComponent extends BasePollComponent<ViewAssignmentPoll> implements OnInit {
|
export class AssignmentPollComponent extends BasePollComponent<ViewAssignmentPoll> implements OnInit {
|
||||||
@Input()
|
@Input()
|
||||||
|
@ -190,7 +190,7 @@ export class AssignmentPollPdfService extends PollPdfService {
|
|||||||
*/
|
*/
|
||||||
private createPollHint(poll: ViewAssignmentPoll): object {
|
private createPollHint(poll: ViewAssignmentPoll): object {
|
||||||
return {
|
return {
|
||||||
text: poll.description || '',
|
text: poll.assignment.default_poll_description || '',
|
||||||
style: 'description'
|
style: 'description'
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -32,6 +32,8 @@ export class AssignmentPollService extends PollService {
|
|||||||
|
|
||||||
public defaultPollMethod: AssignmentPollMethod;
|
public defaultPollMethod: AssignmentPollMethod;
|
||||||
|
|
||||||
|
private sortByVote: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor. Subscribes to the configuration values needed
|
* Constructor. Subscribes to the configuration values needed
|
||||||
* @param config ConfigService
|
* @param config ConfigService
|
||||||
@ -53,6 +55,7 @@ export class AssignmentPollService extends PollService {
|
|||||||
config
|
config
|
||||||
.get<AssignmentPollMethod>(AssignmentPoll.defaultPollMethodConfig)
|
.get<AssignmentPollMethod>(AssignmentPoll.defaultPollMethodConfig)
|
||||||
.subscribe(method => (this.defaultPollMethod = method));
|
.subscribe(method => (this.defaultPollMethod = method));
|
||||||
|
config.get<boolean>('assignment_poll_sort_poll_result_by_votes').subscribe(sort => (this.sortByVote = sort));
|
||||||
}
|
}
|
||||||
|
|
||||||
public getDefaultPollData(contextId?: number): AssignmentPoll {
|
public getDefaultPollData(contextId?: number): AssignmentPoll {
|
||||||
@ -74,38 +77,47 @@ export class AssignmentPollService extends PollService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private getGlobalVoteKeys(poll: ViewAssignmentPoll): VotingResult[] {
|
private getGlobalVoteKeys(poll: ViewAssignmentPoll): VotingResult[] {
|
||||||
|
// debugger;
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
vote: 'amount_global_no',
|
vote: 'amount_global_no',
|
||||||
showPercent: false,
|
showPercent: this.showPercentOfValidOrCast(poll),
|
||||||
hide: poll.amount_global_no === -2 || poll.amount_global_no === 0
|
hide: poll.amount_global_no === -2 || !poll.amount_global_no
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
vote: 'amount_global_abstain',
|
vote: 'amount_global_abstain',
|
||||||
showPercent: false,
|
showPercent: this.showPercentOfValidOrCast(poll),
|
||||||
hide: poll.amount_global_abstain === -2 || poll.amount_global_abstain === 0
|
hide: poll.amount_global_abstain === -2 || !poll.amount_global_abstain
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public generateTableData(poll: ViewAssignmentPoll): PollTableData[] {
|
public generateTableData(poll: ViewAssignmentPoll): PollTableData[] {
|
||||||
const tableData: PollTableData[] = poll.options.map(candidate => ({
|
const tableData: PollTableData[] = poll.options
|
||||||
votingOption: candidate.user.short_name,
|
.sort((a, b) => {
|
||||||
votingOptionSubtitle: candidate.user.getLevelAndNumber(),
|
if (this.sortByVote) {
|
||||||
class: 'user',
|
return b.yes - a.yes;
|
||||||
value: super.getVoteTableKeys(poll).map(
|
} else {
|
||||||
key =>
|
return b.weight - a.weight;
|
||||||
({
|
}
|
||||||
vote: key.vote,
|
})
|
||||||
amount: candidate[key.vote],
|
.map(candidate => ({
|
||||||
icon: key.icon,
|
votingOption: candidate.user.short_name,
|
||||||
hide: key.hide,
|
votingOptionSubtitle: candidate.user.getLevelAndNumber(),
|
||||||
showPercent: key.showPercent
|
class: 'user',
|
||||||
} as VotingResult)
|
value: super.getVoteTableKeys(poll).map(
|
||||||
)
|
key =>
|
||||||
}));
|
({
|
||||||
tableData.push(...this.formatVotingResultToTableData(super.getSumTableKeys(poll), poll));
|
vote: key.vote,
|
||||||
|
amount: candidate[key.vote],
|
||||||
|
icon: key.icon,
|
||||||
|
hide: key.hide,
|
||||||
|
showPercent: key.showPercent
|
||||||
|
} as VotingResult)
|
||||||
|
)
|
||||||
|
}));
|
||||||
tableData.push(...this.formatVotingResultToTableData(this.getGlobalVoteKeys(poll), poll));
|
tableData.push(...this.formatVotingResultToTableData(this.getGlobalVoteKeys(poll), poll));
|
||||||
|
tableData.push(...this.formatVotingResultToTableData(super.getSumTableKeys(poll), poll));
|
||||||
return tableData;
|
return tableData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,8 +31,9 @@
|
|||||||
|
|
||||||
<div *ngIf="!poll.hasVotes || !poll.stateHasVotes">{{ 'No results to show' | translate }}</div>
|
<div *ngIf="!poll.hasVotes || !poll.stateHasVotes">{{ 'No results to show' | translate }}</div>
|
||||||
|
|
||||||
<div *ngIf="poll.stateHasVotes">
|
<div *ngIf="poll.stateHasVotes && (hasPerms() || poll.isPublished)">
|
||||||
<os-motion-poll-detail-content [poll]="poll" [chartData]="chartDataSubject"> </os-motion-poll-detail-content>
|
<os-motion-poll-detail-content [poll]="poll" [chartData]="chartDataSubject">
|
||||||
|
</os-motion-poll-detail-content>
|
||||||
|
|
||||||
<!-- Named table: only show if votes are present -->
|
<!-- Named table: only show if votes are present -->
|
||||||
<div class="named-result-table" *ngIf="poll.type === 'named'">
|
<div class="named-result-table" *ngIf="poll.type === 'named'">
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
@import '~assets/styles/variables.scss';
|
@import '~assets/styles/poll-styles-common.scss';
|
||||||
@import '~assets/styles/poll-colors.scss';
|
|
||||||
|
|
||||||
.poll-content {
|
.poll-content {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
@ -30,18 +29,6 @@
|
|||||||
height: 500px;
|
height: 500px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.voted-yes {
|
|
||||||
color: $votes-yes-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
.voted-no {
|
|
||||||
color: $votes-no-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
.voted-abstain {
|
|
||||||
color: $votes-abstain-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
.openslides-theme .pbl-ngrid-no-data {
|
.openslides-theme .pbl-ngrid-no-data {
|
||||||
top: 10%;
|
top: 10%;
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
|
<div *osPerms="'motions.can_manage_polls'; and: poll && poll.isStarted">
|
||||||
|
<os-poll-progress [poll]="poll"></os-poll-progress>
|
||||||
|
</div>
|
||||||
<ng-container *ngIf="poll && !poll.user_has_voted; else userHasVotes">
|
<ng-container *ngIf="poll && !poll.user_has_voted; else userHasVotes">
|
||||||
<div *osPerms="'motions.can_manage_polls'; and: poll && poll.isStarted">
|
|
||||||
<os-poll-progress [poll]="poll"></os-poll-progress>
|
|
||||||
</div>
|
|
||||||
<div *ngIf="vmanager.canVote(poll)" class="vote-button-grid">
|
<div *ngIf="vmanager.canVote(poll)" class="vote-button-grid">
|
||||||
<!-- Voting -->
|
<!-- Voting -->
|
||||||
<div class="vote-button" *ngFor="let option of voteOptions">
|
<div class="vote-button" *ngFor="let option of voteOptions">
|
||||||
<button
|
<button
|
||||||
mat-raised-button
|
mat-raised-button
|
||||||
(click)="saveVote(option.vote)"
|
(click)="saveVote(option.vote)"
|
||||||
[ngClass]="currentVote && currentVote.value === option.vote ? option.css : ''"
|
[ngClass]="currentVote && currentVote.vote === option.vote ? option.css : ''"
|
||||||
>
|
>
|
||||||
<mat-icon> {{ option.icon }}</mat-icon>
|
<mat-icon> {{ option.icon }}</mat-icon>
|
||||||
</button>
|
</button>
|
||||||
@ -19,8 +19,12 @@
|
|||||||
|
|
||||||
<ng-template #userHasVotes>
|
<ng-template #userHasVotes>
|
||||||
<div class="user-has-voted">
|
<div class="user-has-voted">
|
||||||
<os-icon-container icon="check">
|
<div>
|
||||||
{{ 'You already voted on this poll' | translate }}
|
<mat-icon class="vote-submitted">
|
||||||
</os-icon-container>
|
check_circle
|
||||||
|
</mat-icon>
|
||||||
|
<br />
|
||||||
|
<span>{{ 'You already voted on this poll.' | translate }}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
@import '~assets/styles/poll-colors.scss';
|
@import '~assets/styles/poll-colors.scss';
|
||||||
|
@import '~assets/styles/poll-styles-common.scss';
|
||||||
|
|
||||||
.vote-button-grid {
|
.vote-button-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
@ -19,24 +20,15 @@
|
|||||||
|
|
||||||
.user-has-voted {
|
.user-has-voted {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
text-align: center;
|
||||||
> * {
|
> * {
|
||||||
margin-top: 1em;
|
margin-top: 1em;
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.voted-yes {
|
.vote-submitted {
|
||||||
background-color: $votes-yes-color;
|
color: $votes-yes-color;
|
||||||
color: $vote-active-color;
|
font-size: 200%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.voted-no {
|
|
||||||
background-color: $votes-no-color;
|
|
||||||
color: $vote-active-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
.voted-abstain {
|
|
||||||
background-color: $votes-abstain-color;
|
|
||||||
color: $vote-active-color;
|
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
import { MatSnackBar } from '@angular/material';
|
import { MatSnackBar } from '@angular/material';
|
||||||
import { Title } from '@angular/platform-browser';
|
import { Title } from '@angular/platform-browser';
|
||||||
|
|
||||||
@ -6,20 +6,16 @@ import { TranslateService } from '@ngx-translate/core';
|
|||||||
|
|
||||||
import { OperatorService } from 'app/core/core-services/operator.service';
|
import { OperatorService } from 'app/core/core-services/operator.service';
|
||||||
import { MotionPollRepositoryService } from 'app/core/repositories/motions/motion-poll-repository.service';
|
import { MotionPollRepositoryService } from 'app/core/repositories/motions/motion-poll-repository.service';
|
||||||
import { MotionVoteRepositoryService } from 'app/core/repositories/motions/motion-vote-repository.service';
|
|
||||||
import { PromptService } from 'app/core/ui-services/prompt.service';
|
import { PromptService } from 'app/core/ui-services/prompt.service';
|
||||||
import { VotingService } from 'app/core/ui-services/voting.service';
|
import { VotingService } from 'app/core/ui-services/voting.service';
|
||||||
import { MotionPollMethod } from 'app/shared/models/motions/motion-poll';
|
|
||||||
import { PollType } from 'app/shared/models/poll/base-poll';
|
|
||||||
import { ViewMotionPoll } from 'app/site/motions/models/view-motion-poll';
|
import { ViewMotionPoll } from 'app/site/motions/models/view-motion-poll';
|
||||||
import { ViewMotionVote } from 'app/site/motions/models/view-motion-vote';
|
|
||||||
import { BasePollVoteComponent } from 'app/site/polls/components/base-poll-vote.component';
|
import { BasePollVoteComponent } from 'app/site/polls/components/base-poll-vote.component';
|
||||||
|
|
||||||
interface VoteOption {
|
interface VoteOption {
|
||||||
vote: 'Y' | 'N' | 'A';
|
vote?: 'Y' | 'N' | 'A';
|
||||||
css: string;
|
css?: string;
|
||||||
icon: string;
|
icon?: string;
|
||||||
label: string;
|
label?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@ -27,19 +23,8 @@ interface VoteOption {
|
|||||||
templateUrl: './motion-poll-vote.component.html',
|
templateUrl: './motion-poll-vote.component.html',
|
||||||
styleUrls: ['./motion-poll-vote.component.scss']
|
styleUrls: ['./motion-poll-vote.component.scss']
|
||||||
})
|
})
|
||||||
export class MotionPollVoteComponent extends BasePollVoteComponent<ViewMotionPoll> implements OnInit {
|
export class MotionPollVoteComponent extends BasePollVoteComponent<ViewMotionPoll> {
|
||||||
/**
|
public currentVote: VoteOption = {};
|
||||||
* holds the last saved vote
|
|
||||||
*
|
|
||||||
* TODO: There will be a bug. This has to be reset if the currently observed poll changes it's state back
|
|
||||||
* to started
|
|
||||||
*/
|
|
||||||
public currentVote: ViewMotionVote;
|
|
||||||
|
|
||||||
public MotionPollMethod = MotionPollMethod;
|
|
||||||
|
|
||||||
private votes: ViewMotionVote[];
|
|
||||||
|
|
||||||
public voteOptions: VoteOption[] = [
|
public voteOptions: VoteOption[] = [
|
||||||
{
|
{
|
||||||
vote: 'Y',
|
vote: 'Y',
|
||||||
@ -67,52 +52,23 @@ export class MotionPollVoteComponent extends BasePollVoteComponent<ViewMotionPol
|
|||||||
matSnackbar: MatSnackBar,
|
matSnackbar: MatSnackBar,
|
||||||
vmanager: VotingService,
|
vmanager: VotingService,
|
||||||
operator: OperatorService,
|
operator: OperatorService,
|
||||||
private voteRepo: MotionVoteRepositoryService,
|
|
||||||
private pollRepo: MotionPollRepositoryService,
|
private pollRepo: MotionPollRepositoryService,
|
||||||
private promptService: PromptService
|
private promptService: PromptService
|
||||||
) {
|
) {
|
||||||
super(title, translate, matSnackbar, vmanager, operator);
|
super(title, translate, matSnackbar, vmanager, operator);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ngOnInit(): void {
|
|
||||||
this.subscriptions.push(
|
|
||||||
this.voteRepo.getViewModelListObservable().subscribe(votes => {
|
|
||||||
this.votes = votes;
|
|
||||||
this.updateVotes();
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected updateVotes(): void {
|
|
||||||
if (this.user && this.votes && this.poll) {
|
|
||||||
this.currentVote = null;
|
|
||||||
const filtered = this.votes.filter(
|
|
||||||
vote => vote.option.poll_id === this.poll.id && vote.user_id === this.user.id
|
|
||||||
);
|
|
||||||
if (filtered.length) {
|
|
||||||
if (filtered.length > 1) {
|
|
||||||
// output warning and continue to keep the error case user friendly
|
|
||||||
console.error('A user should never have more than one vote on the same poll.');
|
|
||||||
}
|
|
||||||
this.currentVote = filtered[0];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODO: 'Y' | 'N' | 'A' should refer to some ENUM
|
* TODO: 'Y' | 'N' | 'A' should refer to some ENUM
|
||||||
*/
|
*/
|
||||||
public saveVote(vote: 'Y' | 'N' | 'A'): void {
|
public saveVote(vote: 'Y' | 'N' | 'A'): void {
|
||||||
if (this.poll.type === PollType.Pseudoanonymous) {
|
this.currentVote.vote = vote;
|
||||||
const title = this.translate.instant('Are you sure?');
|
const title = this.translate.instant('Are you sure?');
|
||||||
const content = this.translate.instant('Your decision cannot be changed afterwards');
|
const content = this.translate.instant('Your decision cannot be changed afterwards');
|
||||||
this.promptService.open(title, content).then(confirmed => {
|
this.promptService.open(title, content).then(confirmed => {
|
||||||
if (confirmed) {
|
if (confirmed) {
|
||||||
this.pollRepo.vote(vote, this.poll.id).catch(this.raiseError);
|
this.pollRepo.vote(vote, this.poll.id).catch(this.raiseError);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
this.pollRepo.vote(vote, this.poll.id).catch(this.raiseError);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<mat-card class="motion-poll-wrapper" *ngIf="poll">
|
<mat-card class="motion-poll-wrapper" *ngIf="showPoll">
|
||||||
<!-- Poll Infos -->
|
<!-- Poll Infos -->
|
||||||
<div class="poll-title-wrapper">
|
<div class="poll-title-wrapper">
|
||||||
<!-- Title Area -->
|
<!-- Title Area -->
|
||||||
@ -56,7 +56,7 @@
|
|||||||
|
|
||||||
<!-- Detail link -->
|
<!-- Detail link -->
|
||||||
<div class="poll-detail-button-wrapper">
|
<div class="poll-detail-button-wrapper">
|
||||||
<a mat-icon-button [routerLink]="pollLink" matTooltip="{{ 'More' | translate }}">
|
<a mat-icon-button [routerLink]="pollLink" matTooltip="{{ 'More' | translate }}" *ngIf="poll.isPublished">
|
||||||
<mat-icon class="small-icon">
|
<mat-icon class="small-icon">
|
||||||
visibility
|
visibility
|
||||||
</mat-icon>
|
</mat-icon>
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
@import '~assets/styles/poll-colors.scss';
|
@import '~assets/styles/poll-colors.scss';
|
||||||
|
@import '~assets/styles/poll-styles-common.scss';
|
||||||
|
|
||||||
.poll-link-wrapper {
|
.poll-link-wrapper {
|
||||||
outline: none;
|
outline: none;
|
||||||
@ -46,18 +47,6 @@
|
|||||||
div + div {
|
div + div {
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.yes {
|
|
||||||
color: $votes-yes-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
.no {
|
|
||||||
color: $votes-no-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
.abstain {
|
|
||||||
color: $votes-abstain-color;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -75,18 +64,6 @@
|
|||||||
margin-bottom: auto;
|
margin-bottom: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.start-poll-button {
|
|
||||||
color: green !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stop-poll-button {
|
|
||||||
color: $poll-stop-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
.publish-poll-button {
|
|
||||||
color: $poll-publish-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
.motion-couting-in-progress-hint {
|
.motion-couting-in-progress-hint {
|
||||||
margin-top: 1em;
|
margin-top: 1em;
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
|
@ -7,7 +7,6 @@ import { TranslateService } from '@ngx-translate/core';
|
|||||||
import { MotionPollRepositoryService } from 'app/core/repositories/motions/motion-poll-repository.service';
|
import { MotionPollRepositoryService } from 'app/core/repositories/motions/motion-poll-repository.service';
|
||||||
import { PromptService } from 'app/core/ui-services/prompt.service';
|
import { PromptService } from 'app/core/ui-services/prompt.service';
|
||||||
import { VotingPrivacyWarningComponent } from 'app/shared/components/voting-privacy-warning/voting-privacy-warning.component';
|
import { VotingPrivacyWarningComponent } from 'app/shared/components/voting-privacy-warning/voting-privacy-warning.component';
|
||||||
import { PollType } from 'app/shared/models/poll/base-poll';
|
|
||||||
import { infoDialogSettings } from 'app/shared/utils/dialog-settings';
|
import { infoDialogSettings } from 'app/shared/utils/dialog-settings';
|
||||||
import { ViewMotionPoll } from 'app/site/motions/models/view-motion-poll';
|
import { ViewMotionPoll } from 'app/site/motions/models/view-motion-poll';
|
||||||
import { MotionPollDialogService } from 'app/site/motions/services/motion-poll-dialog.service';
|
import { MotionPollDialogService } from 'app/site/motions/services/motion-poll-dialog.service';
|
||||||
@ -15,6 +14,7 @@ import { MotionPollPdfService } from 'app/site/motions/services/motion-poll-pdf.
|
|||||||
import { MotionPollService } from 'app/site/motions/services/motion-poll.service';
|
import { MotionPollService } from 'app/site/motions/services/motion-poll.service';
|
||||||
import { BasePollComponent } from 'app/site/polls/components/base-poll.component';
|
import { BasePollComponent } from 'app/site/polls/components/base-poll.component';
|
||||||
import { PollService, PollTableData } from 'app/site/polls/services/poll.service';
|
import { PollService, PollTableData } from 'app/site/polls/services/poll.service';
|
||||||
|
import { OperatorService } from 'app/core/core-services/operator.service';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component to show a motion-poll.
|
* Component to show a motion-poll.
|
||||||
@ -48,16 +48,25 @@ export class MotionPollComponent extends BasePollComponent<ViewMotionPoll> {
|
|||||||
return this.motionPollService.showChart(this.poll);
|
return this.motionPollService.showChart(this.poll);
|
||||||
}
|
}
|
||||||
|
|
||||||
public get hideChangeState(): boolean {
|
|
||||||
return this.poll.isPublished || (this.poll.isCreated && this.poll.type === PollType.Analog);
|
|
||||||
}
|
|
||||||
|
|
||||||
public get reducedPollTableData(): PollTableData[] {
|
public get reducedPollTableData(): PollTableData[] {
|
||||||
return this.motionPollService
|
return this.motionPollService
|
||||||
.generateTableData(this.poll)
|
.generateTableData(this.poll)
|
||||||
.filter(data => ['yes', 'no', 'abstain', 'votesinvalid'].includes(data.votingOption));
|
.filter(data => ['yes', 'no', 'abstain', 'votesinvalid'].includes(data.votingOption));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public get showPoll(): boolean {
|
||||||
|
if (this.poll) {
|
||||||
|
if (
|
||||||
|
this.operator.hasPerms('motions.can_manage_polls') ||
|
||||||
|
this.poll.isPublished ||
|
||||||
|
(this.poll.isEVoting && !this.poll.isCreated)
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor.
|
* Constructor.
|
||||||
*
|
*
|
||||||
@ -77,7 +86,8 @@ export class MotionPollComponent extends BasePollComponent<ViewMotionPoll> {
|
|||||||
pollDialog: MotionPollDialogService,
|
pollDialog: MotionPollDialogService,
|
||||||
public pollService: PollService,
|
public pollService: PollService,
|
||||||
private pdfService: MotionPollPdfService,
|
private pdfService: MotionPollPdfService,
|
||||||
private motionPollService: MotionPollService
|
private motionPollService: MotionPollService,
|
||||||
|
private operator: OperatorService
|
||||||
) {
|
) {
|
||||||
super(titleService, matSnackBar, translate, dialog, promptService, pollRepo, pollDialog);
|
super(titleService, matSnackBar, translate, dialog, promptService, pollRepo, pollDialog);
|
||||||
}
|
}
|
||||||
|
@ -42,15 +42,15 @@ export abstract class BasePollDetailComponent<V extends ViewBasePoll> extends Ba
|
|||||||
*/
|
*/
|
||||||
public voteOptionStyle = {
|
public voteOptionStyle = {
|
||||||
Y: {
|
Y: {
|
||||||
css: 'voted-yes',
|
css: 'yes',
|
||||||
icon: 'thumb_up'
|
icon: 'thumb_up'
|
||||||
},
|
},
|
||||||
N: {
|
N: {
|
||||||
css: 'voted-no',
|
css: 'no',
|
||||||
icon: 'thumb_down'
|
icon: 'thumb_down'
|
||||||
},
|
},
|
||||||
A: {
|
A: {
|
||||||
css: 'voted-abstain',
|
css: 'abstain',
|
||||||
icon: 'trip_origin'
|
icon: 'trip_origin'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -151,8 +151,6 @@ export abstract class BasePollDetailComponent<V extends ViewBasePoll> extends Ba
|
|||||||
this.pollDialog.openDialog(viewPoll);
|
this.pollDialog.openDialog(viewPoll);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected onDeleted(): void {}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called after the poll has been loaded. Meant to be overwritten by subclasses who need initial access to the poll
|
* Called after the poll has been loaded. Meant to be overwritten by subclasses who need initial access to the poll
|
||||||
*/
|
*/
|
||||||
@ -166,6 +164,8 @@ export abstract class BasePollDetailComponent<V extends ViewBasePoll> extends Ba
|
|||||||
|
|
||||||
protected abstract hasPerms(): boolean;
|
protected abstract hasPerms(): boolean;
|
||||||
|
|
||||||
|
protected abstract onDeleted(): void;
|
||||||
|
|
||||||
protected get canSeeVotes(): boolean {
|
protected get canSeeVotes(): boolean {
|
||||||
return (this.hasPerms && this.poll.isFinished) || this.poll.isPublished;
|
return (this.hasPerms && this.poll.isFinished) || this.poll.isPublished;
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@ import { BehaviorSubject } from 'rxjs';
|
|||||||
import { BasePollDialogService } from 'app/core/ui-services/base-poll-dialog.service';
|
import { BasePollDialogService } from 'app/core/ui-services/base-poll-dialog.service';
|
||||||
import { PromptService } from 'app/core/ui-services/prompt.service';
|
import { PromptService } from 'app/core/ui-services/prompt.service';
|
||||||
import { ChartData } from 'app/shared/components/charts/charts.component';
|
import { ChartData } from 'app/shared/components/charts/charts.component';
|
||||||
import { PollState } from 'app/shared/models/poll/base-poll';
|
import { PollState, PollType } from 'app/shared/models/poll/base-poll';
|
||||||
import { BaseViewComponent } from 'app/site/base/base-view';
|
import { BaseViewComponent } from 'app/site/base/base-view';
|
||||||
import { BasePollRepositoryService } from '../services/base-poll-repository.service';
|
import { BasePollRepositoryService } from '../services/base-poll-repository.service';
|
||||||
import { ViewBasePoll } from '../models/view-base-poll';
|
import { ViewBasePoll } from '../models/view-base-poll';
|
||||||
@ -33,6 +33,10 @@ export abstract class BasePollComponent<V extends ViewBasePoll> extends BaseView
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
public get hideChangeState(): boolean {
|
||||||
|
return this._poll.isPublished || (this._poll.isCreated && this._poll.type === PollType.Analog);
|
||||||
|
}
|
||||||
|
|
||||||
public constructor(
|
public constructor(
|
||||||
titleService: Title,
|
titleService: Title,
|
||||||
matSnackBar: MatSnackBar,
|
matSnackBar: MatSnackBar,
|
||||||
|
@ -234,23 +234,27 @@ export abstract class PollService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public showPercentOfValidOrCast(poll: PollData | ViewBasePoll): boolean {
|
||||||
|
return poll.onehundred_percent_base === PercentBase.Valid || poll.onehundred_percent_base === PercentBase.Cast;
|
||||||
|
}
|
||||||
|
|
||||||
public getSumTableKeys(poll: PollData | ViewBasePoll): VotingResult[] {
|
public getSumTableKeys(poll: PollData | ViewBasePoll): VotingResult[] {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
vote: 'votesvalid',
|
vote: 'votesvalid',
|
||||||
hide: poll.votesvalid === -2,
|
hide: poll.votesvalid === -2,
|
||||||
showPercent:
|
showPercent: this.showPercentOfValidOrCast(poll)
|
||||||
poll.onehundred_percent_base === PercentBase.Valid ||
|
|
||||||
poll.onehundred_percent_base === PercentBase.Cast
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
vote: 'votesinvalid',
|
vote: 'votesinvalid',
|
||||||
icon: 'not_interested',
|
icon: 'not_interested',
|
||||||
|
// TODO || PollType === analog
|
||||||
hide: poll.votesinvalid === -2,
|
hide: poll.votesinvalid === -2,
|
||||||
showPercent: poll.onehundred_percent_base === PercentBase.Cast
|
showPercent: poll.onehundred_percent_base === PercentBase.Cast
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
vote: 'votescast',
|
vote: 'votescast',
|
||||||
|
// TODO || PollType === analog
|
||||||
hide: poll.votescast === -2,
|
hide: poll.votescast === -2,
|
||||||
showPercent: poll.onehundred_percent_base === PercentBase.Cast
|
showPercent: poll.onehundred_percent_base === PercentBase.Cast
|
||||||
}
|
}
|
||||||
|
@ -44,8 +44,7 @@ export const allSlides: SlideManifest[] = [
|
|||||||
{
|
{
|
||||||
slide: 'motions/motion-poll',
|
slide: 'motions/motion-poll',
|
||||||
path: 'motions/motion-poll',
|
path: 'motions/motion-poll',
|
||||||
loadChildren: () =>
|
loadChildren: () => import('./motions/motion-poll/motion-poll-slide.module').then(m => m.MotionPollSlideModule),
|
||||||
import('./motions/motion-poll/motion-poll-slide.module').then(m => m.MotionPollSlideModule),
|
|
||||||
verboseName: 'Motion Poll',
|
verboseName: 'Motion Poll',
|
||||||
elementIdentifiers: ['name', 'id'],
|
elementIdentifiers: ['name', 'id'],
|
||||||
canBeMappedToModel: true
|
canBeMappedToModel: true
|
||||||
@ -137,7 +136,8 @@ export const allSlides: SlideManifest[] = [
|
|||||||
{
|
{
|
||||||
slide: 'assignments/assignment-poll',
|
slide: 'assignments/assignment-poll',
|
||||||
path: 'assignments/assignment-poll',
|
path: 'assignments/assignment-poll',
|
||||||
loadChildren: () => import('./assignments/assignment-poll/assignment-poll-slide.module').then(m => m.AssignmentPollSlideModule),
|
loadChildren: () =>
|
||||||
|
import('./assignments/assignment-poll/assignment-poll-slide.module').then(m => m.AssignmentPollSlideModule),
|
||||||
verboseName: 'Assignment Poll',
|
verboseName: 'Assignment Poll',
|
||||||
elementIdentifiers: ['name', 'id'],
|
elementIdentifiers: ['name', 'id'],
|
||||||
canBeMappedToModel: true
|
canBeMappedToModel: true
|
||||||
|
@ -5,6 +5,6 @@ $votes-yes-color: #4caf50;
|
|||||||
$votes-no-color: #cc6c5b;
|
$votes-no-color: #cc6c5b;
|
||||||
$votes-abstain-color: #a6a6a6;
|
$votes-abstain-color: #a6a6a6;
|
||||||
$vote-active-color: white;
|
$vote-active-color: white;
|
||||||
$poll-create-color: #4caf50;
|
$poll-start-color: #4caf50;
|
||||||
$poll-stop-color: #ff5252;
|
$poll-stop-color: #ff5252;
|
||||||
$poll-publish-color: #e6b100;
|
$poll-publish-color: #e6b100;
|
||||||
|
40
client/src/assets/styles/poll-styles-common.scss
Normal file
40
client/src/assets/styles/poll-styles-common.scss
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
@import '~assets/styles/poll-colors.scss';
|
||||||
|
|
||||||
|
.yes {
|
||||||
|
color: $votes-yes-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no {
|
||||||
|
color: $votes-no-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.abstain {
|
||||||
|
color: $votes-abstain-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.voted-yes {
|
||||||
|
background-color: $votes-yes-color;
|
||||||
|
color: $vote-active-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.voted-no {
|
||||||
|
background-color: $votes-no-color;
|
||||||
|
color: $vote-active-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.voted-abstain {
|
||||||
|
background-color: $votes-abstain-color;
|
||||||
|
color: $vote-active-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.start-poll-button {
|
||||||
|
color: $poll-start-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stop-poll-button {
|
||||||
|
color: $poll-stop-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.publish-poll-button {
|
||||||
|
color: $poll-publish-color;
|
||||||
|
}
|
@ -65,12 +65,22 @@ def get_config_variables():
|
|||||||
subgroup="Ballot",
|
subgroup="Ballot",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
yield ConfigVariable(
|
||||||
|
name="assignment_poll_sort_poll_result_by_votes",
|
||||||
|
default_value=True,
|
||||||
|
input_type="boolean",
|
||||||
|
label="Sort election results by amount of votes",
|
||||||
|
weight=420,
|
||||||
|
group="Elections",
|
||||||
|
subgroup="Ballot",
|
||||||
|
)
|
||||||
|
|
||||||
yield ConfigVariable(
|
yield ConfigVariable(
|
||||||
name="assignment_poll_add_candidates_to_list_of_speakers",
|
name="assignment_poll_add_candidates_to_list_of_speakers",
|
||||||
default_value=True,
|
default_value=True,
|
||||||
input_type="boolean",
|
input_type="boolean",
|
||||||
label="Put all candidates on the list of speakers",
|
label="Put all candidates on the list of speakers",
|
||||||
weight=420,
|
weight=425,
|
||||||
group="Elections",
|
group="Elections",
|
||||||
subgroup="Ballot",
|
subgroup="Ballot",
|
||||||
)
|
)
|
||||||
|
Loading…
Reference in New Issue
Block a user