2019-03-04 18:28:21 +01:00
|
|
|
import { Component, OnInit, Input } from '@angular/core';
|
2019-04-05 16:15:21 +02:00
|
|
|
import { MatDialog, MatSnackBar } from '@angular/material';
|
2019-03-04 18:28:21 +01:00
|
|
|
|
|
|
|
import { TranslateService } from '@ngx-translate/core';
|
|
|
|
|
2019-04-12 15:11:12 +02:00
|
|
|
import { AssignmentPollDialogComponent } from '../assignment-poll-dialog/assignment-poll-dialog.component';
|
2019-03-04 18:28:21 +01:00
|
|
|
import { AssignmentPollService } from '../../services/assignment-poll.service';
|
|
|
|
import { AssignmentRepositoryService } from 'app/core/repositories/assignments/assignment-repository.service';
|
|
|
|
import { MajorityMethod, CalculablePollKey } from 'app/core/ui-services/poll.service';
|
|
|
|
import { OperatorService } from 'app/core/core-services/operator.service';
|
2019-04-12 15:11:12 +02:00
|
|
|
import { AssignmentPoll } from 'app/shared/models/assignments/assignment-poll';
|
2019-03-04 18:28:21 +01:00
|
|
|
import { PromptService } from 'app/core/ui-services/prompt.service';
|
|
|
|
import { ViewAssignment } from '../../models/view-assignment';
|
2019-04-05 16:15:21 +02:00
|
|
|
import { BaseViewComponent } from 'app/site/base/base-view';
|
|
|
|
import { Title } from '@angular/platform-browser';
|
2019-04-12 15:11:12 +02:00
|
|
|
import { ViewAssignmentPollOption } from '../../models/view-assignment-poll-option';
|
|
|
|
import { ViewAssignmentPoll } from '../../models/view-assignment-poll';
|
2019-03-04 18:28:21 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Component for a single assignment poll. Used in assignment detail view
|
|
|
|
*/
|
|
|
|
@Component({
|
|
|
|
selector: 'os-assignment-poll',
|
|
|
|
templateUrl: './assignment-poll.component.html',
|
|
|
|
styleUrls: ['./assignment-poll.component.scss']
|
|
|
|
})
|
2019-04-05 16:15:21 +02:00
|
|
|
export class AssignmentPollComponent extends BaseViewComponent implements OnInit {
|
2019-03-04 18:28:21 +01:00
|
|
|
/**
|
|
|
|
* The related assignment (used for metainfos, e.g. related user names)
|
|
|
|
*/
|
|
|
|
@Input()
|
|
|
|
public assignment: ViewAssignment;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The poll represented in this component
|
|
|
|
*/
|
|
|
|
@Input()
|
2019-04-12 15:11:12 +02:00
|
|
|
public poll: ViewAssignmentPoll;
|
2019-03-04 18:28:21 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* The selected Majority method to display quorum calculations. Will be
|
|
|
|
* set/changed by the user
|
|
|
|
*/
|
|
|
|
public majorityChoice: MajorityMethod | null;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* permission checks.
|
|
|
|
* TODO stub
|
|
|
|
*
|
|
|
|
* @returns true if the user is permitted to do operations
|
|
|
|
*/
|
|
|
|
public get canManage(): boolean {
|
|
|
|
return this.operator.hasPerms('assignments.can_manage');
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2019-04-05 16:15:21 +02:00
|
|
|
* Gets the voting options
|
|
|
|
*
|
2019-03-04 18:28:21 +01:00
|
|
|
* @returns all used (not undefined) option-independent values that are
|
|
|
|
* used in this poll (e.g.)
|
|
|
|
*/
|
|
|
|
public get pollValues(): CalculablePollKey[] {
|
|
|
|
return this.pollService.pollValues.filter(name => this.poll[name] !== undefined);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2019-04-05 16:15:21 +02:00
|
|
|
* Gets the translated poll method name
|
|
|
|
*
|
2019-03-04 18:28:21 +01:00
|
|
|
* TODO: check/improve text here
|
|
|
|
*
|
|
|
|
* @returns a name for the poll method this poll is set to (which is determined
|
|
|
|
* by the number of candidates and config settings).
|
|
|
|
*/
|
|
|
|
public get pollMethodName(): string {
|
|
|
|
if (!this.poll) {
|
|
|
|
return '';
|
|
|
|
}
|
|
|
|
switch (this.poll.pollmethod) {
|
|
|
|
case 'votes':
|
|
|
|
return this.translate.instant('Vote per Candidate');
|
|
|
|
case 'yna':
|
|
|
|
return this.translate.instant('Yes/No/Abstain per Candidate');
|
|
|
|
case 'yn':
|
|
|
|
return this.translate.instant('Yes/No per Candidate');
|
|
|
|
default:
|
|
|
|
return '';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* constructor. Does nothing
|
|
|
|
*
|
|
|
|
* @param pollService poll related calculations
|
|
|
|
* @param operator permission checks
|
|
|
|
* @param assignmentRepo The repository to the assignments
|
|
|
|
* @param translate Translation service
|
|
|
|
* @param dialog MatDialog for the vote entering dialog
|
|
|
|
* @param promptService Prompts for confirmation dialogs
|
|
|
|
*/
|
|
|
|
public constructor(
|
2019-04-05 16:15:21 +02:00
|
|
|
titleService: Title,
|
|
|
|
matSnackBar: MatSnackBar,
|
2019-03-04 18:28:21 +01:00
|
|
|
public pollService: AssignmentPollService,
|
|
|
|
private operator: OperatorService,
|
|
|
|
private assignmentRepo: AssignmentRepositoryService,
|
|
|
|
public translate: TranslateService,
|
|
|
|
public dialog: MatDialog,
|
|
|
|
private promptService: PromptService
|
2019-04-05 16:15:21 +02:00
|
|
|
) {
|
|
|
|
super(titleService, translate, matSnackBar);
|
|
|
|
}
|
2019-03-04 18:28:21 +01:00
|
|
|
|
|
|
|
/**
|
2019-04-05 16:15:21 +02:00
|
|
|
* Gets the currently selected majority choice option from the repo
|
2019-03-04 18:28:21 +01:00
|
|
|
*/
|
|
|
|
public ngOnInit(): void {
|
|
|
|
this.majorityChoice =
|
|
|
|
this.pollService.majorityMethods.find(method => method.value === this.pollService.defaultMajorityMethod) ||
|
|
|
|
null;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Handler for the 'delete poll' button
|
|
|
|
*
|
|
|
|
* TODO: Some confirmation (advanced logic (e.g. not deleting published?))
|
|
|
|
*/
|
|
|
|
public async onDeletePoll(): Promise<void> {
|
|
|
|
const title = this.translate.instant('Are you sure you want to delete this poll?');
|
|
|
|
if (await this.promptService.open(title, null)) {
|
2019-04-05 16:15:21 +02:00
|
|
|
await this.assignmentRepo.deletePoll(this.poll).then(null, this.raiseError);
|
2019-03-04 18:28:21 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2019-04-05 16:15:21 +02:00
|
|
|
* Print the PDF of this poll with the corresponding options and numbers
|
|
|
|
*
|
2019-03-04 18:28:21 +01:00
|
|
|
* TODO Print the ballots for this poll.
|
|
|
|
*/
|
2019-04-12 15:11:12 +02:00
|
|
|
public printBallot(poll: AssignmentPoll): void {
|
2019-04-05 16:15:21 +02:00
|
|
|
this.raiseError('Not yet implemented');
|
2019-03-04 18:28:21 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Determines whether the candidate has reached the majority needed to pass
|
|
|
|
* the quorum
|
|
|
|
*
|
|
|
|
* @param option
|
|
|
|
* @returns true if the quorum is successfully met
|
|
|
|
*/
|
2019-04-12 15:11:12 +02:00
|
|
|
public quorumReached(option: ViewAssignmentPollOption): boolean {
|
2019-04-05 12:13:34 +02:00
|
|
|
const yesValue = this.poll.pollmethod === 'votes' ? 'Votes' : 'Yes';
|
|
|
|
const amount = option.votes.find(v => v.value === yesValue).weight;
|
2019-03-04 18:28:21 +01:00
|
|
|
const yesQuorum = this.pollService.yesQuorum(this.majorityChoice, this.poll, option);
|
|
|
|
return yesQuorum && amount >= yesQuorum;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Opens the {@link AssignmentPollDialogComponent} dialog and then updates the votes, if the dialog
|
|
|
|
* closes successfully (validation is done there)
|
|
|
|
*/
|
|
|
|
public enterVotes(): void {
|
|
|
|
// TODO deep copy of this.poll (JSON parse is ugly workaround)
|
|
|
|
// or sending just copy of the options
|
|
|
|
const data = {
|
|
|
|
poll: JSON.parse(JSON.stringify(this.poll)),
|
|
|
|
users: this.assignment.candidates // used to get the names of the users
|
|
|
|
};
|
|
|
|
const dialogRef = this.dialog.open(AssignmentPollDialogComponent, {
|
|
|
|
data: data,
|
|
|
|
maxHeight: '90vh',
|
|
|
|
minWidth: '300px',
|
2019-04-05 16:15:21 +02:00
|
|
|
maxWidth: '80vw',
|
2019-03-04 18:28:21 +01:00
|
|
|
disableClose: true
|
|
|
|
});
|
|
|
|
dialogRef.afterClosed().subscribe(result => {
|
|
|
|
if (result) {
|
2019-04-05 16:15:21 +02:00
|
|
|
this.assignmentRepo.updateVotes(result, this.poll).then(null, this.raiseError);
|
2019-03-04 18:28:21 +01:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Updates the majority method for this poll
|
|
|
|
*
|
2019-04-05 16:15:21 +02:00
|
|
|
* @param method the selected majority method
|
2019-03-04 18:28:21 +01:00
|
|
|
*/
|
|
|
|
public setMajority(method: MajorityMethod): void {
|
|
|
|
this.majorityChoice = method;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Toggles the 'published' state
|
|
|
|
*/
|
|
|
|
public togglePublished(): void {
|
|
|
|
this.assignmentRepo.updatePoll({ published: !this.poll.published }, this.poll);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Mark/unmark an option as elected
|
|
|
|
*
|
|
|
|
* @param option
|
|
|
|
*/
|
2019-04-12 15:11:12 +02:00
|
|
|
public toggleElected(option: ViewAssignmentPollOption): void {
|
2019-03-04 18:28:21 +01:00
|
|
|
if (!this.operator.hasPerms('assignments.can_manage')) {
|
|
|
|
return;
|
|
|
|
}
|
2019-04-05 16:15:21 +02:00
|
|
|
|
2019-03-04 18:28:21 +01:00
|
|
|
// TODO additional conditions: assignment not finished?
|
2019-04-12 15:11:12 +02:00
|
|
|
const viewAssignmentRelatedUser = this.assignment.assignmentRelatedUsers.find(
|
|
|
|
user => user.user_id === option.user_id
|
2019-03-04 18:28:21 +01:00
|
|
|
);
|
2019-04-12 15:11:12 +02:00
|
|
|
if (viewAssignmentRelatedUser) {
|
|
|
|
this.assignmentRepo.markElected(viewAssignmentRelatedUser, this.assignment, !option.is_elected);
|
2019-03-04 18:28:21 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|