improved 'votes' pollmethod
This commit is contained in:
parent
1de73d5701
commit
bc54a6eb46
@ -6,7 +6,7 @@
|
||||
<div *ngFor="let option of options" class="votes-grid">
|
||||
<div>
|
||||
<span *ngIf="option.user">{{ option.user.getFullName() }}</span>
|
||||
<span *ngIf="!option.user">No user {{ option.candidate_id }}</span>
|
||||
<span *ngIf="!option.user">{{ 'Unknown User' | translate }}</span>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
|
@ -1,48 +1,74 @@
|
||||
<ng-container *ngIf="poll">
|
||||
<ng-container *ngIf="vmanager.canVote(poll)">
|
||||
<span *ngIf="poll.pollmethod === 'votes'"
|
||||
>{{ 'You can distribute' | translate }} {{ poll.votes_amount }} {{ 'votes' | translate }}.</span
|
||||
>
|
||||
<form *ngIf="voteForm" [formGroup]="voteForm" class="voting-grid">
|
||||
<ng-container *ngFor="let option of poll.options">
|
||||
<!-- empty divs to fit the grid -->
|
||||
<div></div><div></div>
|
||||
<div>
|
||||
<span *ngIf="poll.pollmethod === pollMethods.Votes">
|
||||
({{ getVotesCount() }}/{{ poll.votes_amount }} {{ 'Votes' | translate }})
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- candidate votes -->
|
||||
<ng-container *ngFor="let option of poll.options" formGroupName="votes">
|
||||
<div>
|
||||
<span *ngIf="option.user">{{ option.user.getFullName() }}</span>
|
||||
<span *ngIf="!option.user">No user {{ option.candidate_id }}</span>
|
||||
<span *ngIf="!option.user">{{ "Unknown user" | translate }}</span>
|
||||
</div>
|
||||
|
||||
<div class="current-vote">
|
||||
<ng-container *ngIf="currentVotes[option.user_id] !== null">
|
||||
({{ 'Current' | translate }}: {{ getCurrentVoteVerbose(option.user_id) | translate }})
|
||||
<ng-container *ngIf="poll.pollmethod !== pollMethods.Votes && currentVotes[option.user_id]">
|
||||
({{ 'Current' | translate }}: {{ currentVotes[option.user_id] | translate }})
|
||||
</ng-container>
|
||||
<ng-container *ngIf="poll.pollmethod === pollMethods.Votes && currentVotes[option.user_id]">
|
||||
({{ 'Current choice' | translate }})
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
<mat-radio-group
|
||||
name="votes-{{ poll.id }}-{{ option.id }}"
|
||||
[formControlName]="option.id"
|
||||
*ngIf="poll.pollmethod !== 'votes'"
|
||||
>
|
||||
<mat-radio-button value="Y">
|
||||
<mat-radio-button value="Y" (click)="yesButtonClicked($event, option.id.toString())">
|
||||
<span translate>Yes</span>
|
||||
</mat-radio-button>
|
||||
<mat-radio-button value="N">
|
||||
<mat-radio-button value="N" *ngIf="poll.pollmethod !== pollMethods.Votes">
|
||||
<span translate>No</span>
|
||||
</mat-radio-button>
|
||||
<mat-radio-button value="A" *ngIf="poll.pollmethod === 'YNA'">
|
||||
<mat-radio-button value="A" *ngIf="poll.pollmethod === pollMethods.YNA">
|
||||
<span translate>Abstain</span>
|
||||
</mat-radio-button>
|
||||
</mat-radio-group>
|
||||
</ng-container>
|
||||
|
||||
<mat-form-field *ngIf="poll.pollmethod === 'votes'" class="vote-input">
|
||||
<input matInput type="number" min="0" [formControlName]="option.id" />
|
||||
</mat-form-field>
|
||||
<!-- global no/abstain -->
|
||||
<ng-container *ngIf="poll.pollmethod === pollMethods.Votes && (poll.global_no || poll.global_abstain)">
|
||||
<!-- empty div to fit the grid -->
|
||||
<div></div>
|
||||
<div class="current-vote">
|
||||
<ng-container *ngIf="currentVotes.global">
|
||||
({{ 'Current' | translate }}: {{ currentVotes.global | translate }})
|
||||
</ng-container>
|
||||
</div>
|
||||
<mat-radio-group
|
||||
name="votes-{{ poll.id }}-global"
|
||||
formControlName="global"
|
||||
>
|
||||
<mat-radio-button value="N" *ngIf="poll.global_no">
|
||||
<span translate>Global no</span>
|
||||
</mat-radio-button>
|
||||
<mat-radio-button value="A" *ngIf="poll.global_abstain">
|
||||
<span translate>Global abstain</span>
|
||||
</mat-radio-button>
|
||||
</mat-radio-group>
|
||||
</ng-container>
|
||||
</form>
|
||||
<div class="right-align">
|
||||
<div class="right-align" *ngIf="poll.type !== PollType.Named || poll.pollmethod !== pollMethods.Votes">
|
||||
<button
|
||||
mat-button
|
||||
mat-button-default
|
||||
(click)="saveVotes()"
|
||||
[disabled]="!voteForm || voteForm.invalid || voteForm.pristine"
|
||||
[disabled]="isSaveButtonDisabled()"
|
||||
>
|
||||
<span translate>Save</span>
|
||||
</button>
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
import { AbstractControl, FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms';
|
||||
import { MatSnackBar } from '@angular/material';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
|
||||
@ -10,7 +10,7 @@ import { AssignmentPollRepositoryService } from 'app/core/repositories/assignmen
|
||||
import { AssignmentVoteRepositoryService } from 'app/core/repositories/assignments/assignment-vote-repository.service';
|
||||
import { VotingService } from 'app/core/ui-services/voting.service';
|
||||
import { AssignmentPollMethods } from 'app/shared/models/assignments/assignment-poll';
|
||||
import { VoteValueVerbose } from 'app/shared/models/poll/base-vote';
|
||||
import { PollType } from 'app/shared/models/poll/base-poll';
|
||||
import { BasePollVoteComponent } from 'app/site/polls/components/base-poll-vote.component';
|
||||
import { ViewAssignmentPoll } from '../../models/view-assignment-poll';
|
||||
import { ViewAssignmentVote } from '../../models/view-assignment-vote';
|
||||
@ -22,11 +22,12 @@ import { ViewAssignmentVote } from '../../models/view-assignment-vote';
|
||||
})
|
||||
export class AssignmentPollVoteComponent extends BasePollVoteComponent<ViewAssignmentPoll> implements OnInit {
|
||||
public pollMethods = AssignmentPollMethods;
|
||||
public PollType = PollType;
|
||||
|
||||
public voteForm: FormGroup;
|
||||
|
||||
/** holds the currently saved votes */
|
||||
public currentVotes: { [key: number]: string | number | null } = {};
|
||||
public currentVotes: { [key: number]: string | null; global?: string } = {};
|
||||
|
||||
private votes: ViewAssignmentVote[];
|
||||
|
||||
@ -57,30 +58,114 @@ export class AssignmentPollVoteComponent extends BasePollVoteComponent<ViewAssig
|
||||
const filtered = this.votes.filter(
|
||||
vote => vote.option.poll_id === this.poll.id && vote.user_id === this.user.id
|
||||
);
|
||||
this.voteForm = this.formBuilder.group(
|
||||
this.poll.options.reduce((obj, option) => {
|
||||
obj[option.id] = ['', [Validators.required]];
|
||||
return obj;
|
||||
}, {})
|
||||
);
|
||||
for (const option of this.poll.options) {
|
||||
const curr_vote = filtered.find(vote => vote.option.id === option.id);
|
||||
this.currentVotes[option.user_id] = curr_vote
|
||||
? this.poll.pollmethod === AssignmentPollMethods.Votes
|
||||
? curr_vote.weight
|
||||
: curr_vote.value
|
||||
: null;
|
||||
this.voteForm.get(option.id.toString()).setValue(this.currentVotes[option.user_id]);
|
||||
this.voteForm = this.formBuilder.group({
|
||||
votes: this.formBuilder.group(
|
||||
this.poll.options.mapToObject(option => ({ [option.id]: ['', [Validators.required]] }))
|
||||
)
|
||||
});
|
||||
if (
|
||||
this.poll.pollmethod === AssignmentPollMethods.Votes &&
|
||||
(this.poll.global_no || this.poll.global_abstain)
|
||||
) {
|
||||
this.voteForm.addControl('global', new FormControl('', Validators.required));
|
||||
}
|
||||
|
||||
for (const option of this.poll.options) {
|
||||
let curr_vote = filtered.find(vote => vote.option.id === option.id);
|
||||
if (this.poll.pollmethod === AssignmentPollMethods.Votes && curr_vote) {
|
||||
if (curr_vote.value !== 'Y') {
|
||||
this.currentVotes.global = curr_vote.valueVerbose;
|
||||
this.voteForm.controls.global.setValue(curr_vote.value);
|
||||
curr_vote = null;
|
||||
} else {
|
||||
this.currentVotes.global = null;
|
||||
}
|
||||
}
|
||||
this.currentVotes[option.user_id] = curr_vote && curr_vote.valueVerbose;
|
||||
this.voteForm.get(['votes', option.id]).setValue(curr_vote && curr_vote.value);
|
||||
}
|
||||
|
||||
if (this.poll.pollmethod === AssignmentPollMethods.Votes) {
|
||||
this.voteForm.controls.votes.valueChanges.subscribe(value => {
|
||||
if (Object.values(value).some(vote => vote)) {
|
||||
const ctrl = this.voteForm.controls.global;
|
||||
if (ctrl) {
|
||||
ctrl.reset();
|
||||
}
|
||||
this.saveVotesIfNamed();
|
||||
}
|
||||
});
|
||||
|
||||
this.voteForm.controls.global.valueChanges.subscribe(value => {
|
||||
if (value) {
|
||||
this.voteForm.controls.votes.reset();
|
||||
this.saveVotesIfNamed();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private saveVotesIfNamed(): void {
|
||||
if (this.poll.type === PollType.Named && !this.isSaveButtonDisabled()) {
|
||||
this.saveVotes();
|
||||
}
|
||||
}
|
||||
|
||||
public saveVotes(): void {
|
||||
this.pollRepo.vote(this.voteForm.value, this.poll.id).catch(this.raiseError);
|
||||
let values = this.voteForm.value.votes;
|
||||
// convert Y to 1 and null to 0 for votes method
|
||||
if (this.poll.pollmethod === this.pollMethods.Votes) {
|
||||
if (this.voteForm.value.global) {
|
||||
values = JSON.stringify(this.voteForm.value.global);
|
||||
} else {
|
||||
this.poll.options.forEach(option => {
|
||||
values[option.id] = this.voteForm.value.votes[option.id] === 'Y' ? 1 : 0;
|
||||
});
|
||||
}
|
||||
}
|
||||
this.pollRepo.vote(values, this.poll.id).catch(this.raiseError);
|
||||
}
|
||||
|
||||
public getCurrentVoteVerbose(user_id: number): string {
|
||||
const curr_vote = this.currentVotes[user_id];
|
||||
return this.poll.pollmethod === AssignmentPollMethods.Votes ? curr_vote : VoteValueVerbose[curr_vote];
|
||||
public isSaveButtonDisabled(): boolean {
|
||||
return (
|
||||
!this.voteForm ||
|
||||
this.voteForm.pristine ||
|
||||
(this.poll.pollmethod === AssignmentPollMethods.Votes
|
||||
? !this.getAllFormControls().some(control => control.valid)
|
||||
: this.voteForm.invalid)
|
||||
);
|
||||
}
|
||||
|
||||
public getVotesCount(): number {
|
||||
return Object.values(this.voteForm.value.votes).filter(vote => vote).length;
|
||||
}
|
||||
|
||||
private getAllFormControls(): AbstractControl[] {
|
||||
if (this.voteForm) {
|
||||
const votesFormGroup = this.voteForm.controls.votes as FormGroup;
|
||||
return [...Object.values(votesFormGroup.controls), this.voteForm.controls.global];
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
public yesButtonClicked($event: MouseEvent, optionId: string): void {
|
||||
if (this.poll.pollmethod === AssignmentPollMethods.Votes) {
|
||||
// check current value (before click)
|
||||
if (this.voteForm.value.votes[optionId] === 'Y') {
|
||||
// this handler is executed before the mat-radio-button handler, so we have to set a timeout or else the other handler will just set the value again
|
||||
setTimeout(() => {
|
||||
this.voteForm.get(['votes', optionId]).setValue(null);
|
||||
this.voteForm.markAsDirty();
|
||||
this.saveVotesIfNamed();
|
||||
});
|
||||
} else {
|
||||
// check if by clicking this button, the amount of votes would succeed the permitted amount
|
||||
if (this.getVotesCount() >= this.poll.votes_amount) {
|
||||
$event.preventDefault();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -17,9 +17,9 @@
|
||||
</span>
|
||||
</div>
|
||||
<form [formGroup]="contentForm" class="poll-preview--meta-info-form">
|
||||
<ng-container *ngIf="!data || !data.state || data.state === 1">
|
||||
<ng-container *ngIf="!data || !data.state || data.isStateCreated">
|
||||
<mat-form-field *ngIf="pollService.isElectronicVotingEnabled">
|
||||
<mat-select [placeholder]="'Voting type' | translate" formControlName="type" required>
|
||||
<mat-select [placeholder]="PollPropertyVerbose.type | translate" formControlName="type" required>
|
||||
<mat-option *ngFor="let option of pollTypes | keyvalue" [value]="option.key">
|
||||
{{ option.value | translate }}
|
||||
</mat-option>
|
||||
@ -32,12 +32,12 @@
|
||||
[multiple]="true"
|
||||
[showChips]="false"
|
||||
[includeNone]="false"
|
||||
[placeholder]="'Entitled to vote' | translate"
|
||||
[placeholder]="PollPropertyVerbose.groups | translate"
|
||||
[inputListValues]="groupObservable"
|
||||
></os-search-value-selector>
|
||||
</mat-form-field>
|
||||
<mat-form-field *ngIf="pollMethods">
|
||||
<mat-select [placeholder]="'Poll method' | translate" formControlName="pollmethod" required>
|
||||
<mat-select [placeholder]="PollPropertyVerbose.pollmethod | translate" formControlName="pollmethod" required>
|
||||
<mat-option *ngFor="let option of pollMethods | keyvalue" [value]="option.key">
|
||||
{{ option.value }}
|
||||
</mat-option>
|
||||
@ -47,20 +47,28 @@
|
||||
</ng-container>
|
||||
|
||||
<mat-form-field>
|
||||
<mat-select placeholder="{{ '100% base' | translate }}" formControlName="onehundred_percent_base" required>
|
||||
<mat-select placeholder="{{ PollPropertyVerbose.onehundred_percent_base | translate }}" formControlName="onehundred_percent_base" required>
|
||||
<ng-container *ngFor="let option of percentBases | keyvalue">
|
||||
<mat-option *ngIf="isValidPercentBaseWithMethod(option.key)" [value]="option.key">
|
||||
{{ option.value | translate }}
|
||||
</mat-option>
|
||||
<mat-option [value]="option.key">{{ option.value | translate }}</mat-option>
|
||||
</ng-container>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<mat-form-field>
|
||||
<mat-select placeholder="{{ 'Required majority' | translate }}" formControlName="majority_method" required>
|
||||
<mat-select placeholder="{{ PollPropertyVerbose.majority_method | translate }}" formControlName="majority_method" required>
|
||||
<mat-option *ngFor="let option of majorityMethods | keyvalue" [value]="option.key">
|
||||
{{ option.value | translate }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<ng-container *ngIf="!data || !data.state || data.state === 1">
|
||||
<ng-container *ngIf="contentForm.get('pollmethod').value === 'votes'">
|
||||
<mat-form-field>
|
||||
<input type="number" matInput placeholder="{{ PollPropertyVerbose.votes_amount | translate }}" formControlName="votes_amount" min="1" required>
|
||||
</mat-form-field>
|
||||
<mat-checkbox formControlName="global_no">{{ PollPropertyVerbose.global_no | translate }}</mat-checkbox>
|
||||
<mat-checkbox formControlName="global_abstain">{{ PollPropertyVerbose.global_abstain | translate }}</mat-checkbox>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</form>
|
||||
</div>
|
||||
|
@ -8,10 +8,13 @@ import { Observable } from 'rxjs';
|
||||
|
||||
import { GroupRepositoryService } from 'app/core/repositories/users/group-repository.service';
|
||||
import { PercentBase } from 'app/shared/models/poll/base-poll';
|
||||
import { PollType } from 'app/shared/models/poll/base-poll';
|
||||
import { ViewAssignmentPoll } from 'app/site/assignments/models/view-assignment-poll';
|
||||
import { BaseViewComponent } from 'app/site/base/base-view';
|
||||
import {
|
||||
MajorityMethodVerbose,
|
||||
PercentBaseVerbose,
|
||||
PollPropertyVerbose,
|
||||
PollTypeVerbose,
|
||||
ViewBasePoll
|
||||
} from 'app/site/polls/models/view-base-poll';
|
||||
@ -29,6 +32,9 @@ export class PollFormComponent extends BaseViewComponent implements OnInit {
|
||||
*/
|
||||
public contentForm: FormGroup;
|
||||
|
||||
public PollType = PollType;
|
||||
public PollPropertyVerbose = PollPropertyVerbose;
|
||||
|
||||
/**
|
||||
* The different methods for this poll.
|
||||
*/
|
||||
@ -92,6 +98,11 @@ export class PollFormComponent extends BaseViewComponent implements OnInit {
|
||||
public ngOnInit(): void {
|
||||
this.groupObservable = this.groupRepo.getViewModelListObservable();
|
||||
|
||||
const cast = <ViewAssignmentPoll>this.data;
|
||||
if (cast.assignment && !cast.votes_amount) {
|
||||
cast.votes_amount = cast.assignment.open_posts;
|
||||
}
|
||||
|
||||
if (this.data) {
|
||||
Object.keys(this.contentForm.controls).forEach(key => {
|
||||
if (this.data[key]) {
|
||||
@ -118,12 +129,14 @@ export class PollFormComponent extends BaseViewComponent implements OnInit {
|
||||
forbiddenBases = [PercentBase.YN, PercentBase.YNA];
|
||||
}
|
||||
|
||||
this.percentBases = {};
|
||||
const percentBases = {};
|
||||
for (const [key, value] of Object.entries(PercentBaseVerbose)) {
|
||||
if (!forbiddenBases.includes(key)) {
|
||||
this.percentBases[key] = value;
|
||||
percentBases[key] = value;
|
||||
}
|
||||
}
|
||||
this.percentBases = percentBases;
|
||||
// TODO: update selected base
|
||||
});
|
||||
}
|
||||
|
||||
@ -131,10 +144,6 @@ export class PollFormComponent extends BaseViewComponent implements OnInit {
|
||||
return { ...this.data, ...this.contentForm.value };
|
||||
}
|
||||
|
||||
public isValidPercentBaseWithMethod(base: PercentBase): boolean {
|
||||
return !(base === PercentBase.YNA && this.contentForm.get('pollmethod').value === 'YN');
|
||||
}
|
||||
|
||||
/**
|
||||
* This updates the poll-values to get correct data in the view.
|
||||
*
|
||||
@ -147,12 +156,17 @@ export class PollFormComponent extends BaseViewComponent implements OnInit {
|
||||
this.pollService.getVerboseNameForKey(key),
|
||||
this.pollService.getVerboseNameForValue(key, value as string)
|
||||
]);
|
||||
if (data.type === 'named') {
|
||||
if (data.type !== 'analog') {
|
||||
this.pollValues.push([
|
||||
this.pollService.getVerboseNameForKey('groups'),
|
||||
this.groupRepo.getNameForIds(...data.groups_id)
|
||||
this.groupRepo.getNameForIds(...([] || (data && data.groups_id)))
|
||||
]);
|
||||
}
|
||||
if (data.pollmethod === 'votes') {
|
||||
this.pollValues.push([this.pollService.getVerboseNameForKey('votes_amount'), data.votes_amount]);
|
||||
this.pollValues.push([this.pollService.getVerboseNameForKey('global_no'), data.global_no]);
|
||||
this.pollValues.push([this.pollService.getVerboseNameForKey('global_abstain'), data.global_abstain]);
|
||||
}
|
||||
}
|
||||
|
||||
private initContentForm(): void {
|
||||
@ -162,7 +176,10 @@ export class PollFormComponent extends BaseViewComponent implements OnInit {
|
||||
pollmethod: ['', Validators.required],
|
||||
onehundred_percent_base: ['', Validators.required],
|
||||
majority_method: ['', Validators.required],
|
||||
groups_id: [[]]
|
||||
votes_amount: [1, [Validators.required, Validators.min(1)]],
|
||||
groups_id: [],
|
||||
global_no: [],
|
||||
global_abstain: []
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -32,7 +32,10 @@ export const PollPropertyVerbose = {
|
||||
type: 'Poll type',
|
||||
pollmethod: 'Poll method',
|
||||
state: 'State',
|
||||
groups: 'Entitled to vote'
|
||||
groups: 'Entitled to vote',
|
||||
votes_amount: 'Amount of votes',
|
||||
global_no: 'Enable global no',
|
||||
global_abstain: 'Enable global abstain'
|
||||
};
|
||||
|
||||
export const MajorityMethodVerbose = {
|
||||
|
@ -474,10 +474,10 @@ class AssignmentPollViewSet(BasePollViewSet):
|
||||
)
|
||||
amount_sum += amount
|
||||
|
||||
if amount_sum != poll.votes_amount:
|
||||
if amount_sum > poll.votes_amount:
|
||||
raise ValidationError(
|
||||
{
|
||||
"detail": "You have to give exactly {0} votes",
|
||||
"detail": "You can give a maximum of {0} votes",
|
||||
"args": [poll.votes_amount],
|
||||
}
|
||||
)
|
||||
|
Loading…
Reference in New Issue
Block a user