Cleanups and enhancements

Cleans up and reviews some methods
This commit is contained in:
Sean Engelhardt 2019-04-05 16:15:21 +02:00 committed by Maximilian Krambach
parent 464fb89b53
commit 054f76a5d4
13 changed files with 119 additions and 109 deletions

View File

@ -10,6 +10,7 @@ import { DataSendService } from 'app/core/core-services/data-send.service';
import { DataStoreService } from '../../core-services/data-store.service'; import { DataStoreService } from '../../core-services/data-store.service';
import { HttpService } from 'app/core/core-services/http.service'; import { HttpService } from 'app/core/core-services/http.service';
import { Item } from 'app/shared/models/agenda/item'; import { Item } from 'app/shared/models/agenda/item';
import { Poll } from 'app/shared/models/assignments/poll';
import { Tag } from 'app/shared/models/core/tag'; import { Tag } from 'app/shared/models/core/tag';
import { User } from 'app/shared/models/users/user'; import { User } from 'app/shared/models/users/user';
import { ViewAssignment } from 'app/site/assignments/models/view-assignment'; import { ViewAssignment } from 'app/site/assignments/models/view-assignment';
@ -17,7 +18,6 @@ import { ViewItem } from 'app/site/agenda/models/view-item';
import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service'; import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service';
import { ViewTag } from 'app/site/tags/models/view-tag'; import { ViewTag } from 'app/site/tags/models/view-tag';
import { ViewUser } from 'app/site/users/models/view-user'; import { ViewUser } from 'app/site/users/models/view-user';
import { Poll } from 'app/shared/models/assignments/poll';
/** /**
* Repository Service for Assignments. * Repository Service for Assignments.
@ -28,14 +28,22 @@ import { Poll } from 'app/shared/models/assignments/poll';
providedIn: 'root' providedIn: 'root'
}) })
export class AssignmentRepositoryService extends BaseAgendaContentObjectRepository<ViewAssignment, Assignment> { export class AssignmentRepositoryService extends BaseAgendaContentObjectRepository<ViewAssignment, Assignment> {
private readonly restPath = '/rest/assignments/assignment/';
private readonly restPollPath = '/rest/assignments/poll/';
private readonly candidatureOtherPath = '/candidature_other/';
private readonly candidatureSelfPath = '/candidature_self/';
private readonly createPollPath = '/create_poll/';
private readonly markElectedPath = '/mark_elected/';
/** /**
* Constructor for the Assignment Repository. * Constructor for the Assignment Repository.
* *
* @param DS The DataStore * @param DS DataStore access
* @param mapperService Maps collection strings to classes * @param dataSend Sending data
* @param viewModelStoreService * @param mapperService Map models to object
* @param translate * @param viewModelStoreService Access view models
* @param httpService * @param translate Translate string
* @param httpService make HTTP Requests
*/ */
public constructor( public constructor(
DS: DataStoreService, DS: DataStoreService,
@ -76,55 +84,42 @@ export class AssignmentRepositoryService extends BaseAgendaContentObjectReposito
* Adds another user as a candidate * Adds another user as a candidate
* *
* @param userId User id of a candidate * @param userId User id of a candidate
* @param assignment * @param assignment The assignment to add the candidate to
*/ */
public async addCandidate(userId: number, assignment: ViewAssignment): Promise<void> { public async changeCandidate(userId: number, assignment: ViewAssignment): Promise<void> {
const restPath = `/rest/assignments/assignment/${assignment.id}/candidature_other/`;
const data = { user: userId }; const data = { user: userId };
await this.httpService.post(restPath, data); if (assignment.candidates.some(candidate => candidate.id === userId)) {
} await this.httpService.delete(this.restPath + assignment.id + this.candidatureOtherPath, data);
} else {
/** await this.httpService.post(this.restPath + assignment.id + this.candidatureOtherPath, data);
* Removes an user from the list of candidates for an assignment }
*
* @param user note: AssignmentUser, not a ViewUser
* @param assignment
*/
public async deleteCandidate(user: AssignmentUser, assignment: ViewAssignment): Promise<void> {
const restPath = `/rest/assignments/assignment/${assignment.id}/candidature_other/`;
const data = { user: user.id };
await this.httpService.delete(restPath, data);
} }
/** /**
* Add the operator as candidate to the assignment * Add the operator as candidate to the assignment
* *
* @param assignment * @param assignment The assignment to add the candidate to
*/ */
public async addSelf(assignment: ViewAssignment): Promise<void> { public async addSelf(assignment: ViewAssignment): Promise<void> {
const restPath = `/rest/assignments/assignment/${assignment.id}/candidature_self/`; await this.httpService.post(this.restPath + assignment.id + this.candidatureSelfPath);
await this.httpService.post(restPath);
} }
/** /**
* Removes the current user (operator) from the list of candidates for an assignment * Removes the current user (operator) from the list of candidates for an assignment
* *
* @param assignment * @param assignment The assignment to remove ourself from
*/ */
public async deleteSelf(assignment: ViewAssignment): Promise<void> { public async deleteSelf(assignment: ViewAssignment): Promise<void> {
const restPath = `/rest/assignments/assignment/${assignment.id}/candidature_self/`; await this.httpService.delete(this.restPath + assignment.id + this.candidatureSelfPath);
await this.httpService.delete(restPath);
} }
/** /**
* Creates a new Poll to a given assignment * Creates a new Poll to a given assignment
* *
* @param assignment * @param assignment The assignment to add the poll to
*/ */
public async addPoll(assignment: ViewAssignment): Promise<void> { public async addPoll(assignment: ViewAssignment): Promise<void> {
const restPath = `/rest/assignments/assignment/${assignment.id}/create_poll/`; await this.httpService.post(this.restPath + assignment.id + this.createPollPath);
await this.httpService.post(restPath);
// TODO set phase, too, if phase was 0. Should be done server side?
} }
/** /**
@ -133,8 +128,7 @@ export class AssignmentRepositoryService extends BaseAgendaContentObjectReposito
* @param id id of the poll to delete * @param id id of the poll to delete
*/ */
public async deletePoll(poll: Poll): Promise<void> { public async deletePoll(poll: Poll): Promise<void> {
const restPath = `/rest/assignments/poll/${poll.id}/`; await this.httpService.delete(`${this.restPollPath}${poll.id}/`);
await this.httpService.delete(restPath);
} }
/** /**
@ -143,12 +137,11 @@ export class AssignmentRepositoryService extends BaseAgendaContentObjectReposito
* @param poll the (partial) data to update * @param poll the (partial) data to update
* @param originalPoll the poll to update * @param originalPoll the poll to update
* *
* TODO check if votes is untouched * TODO: check if votes is untouched
*/ */
public async updatePoll(poll: Partial<Poll>, originalPoll: Poll): Promise<void> { public async updatePoll(poll: Partial<Poll>, originalPoll: Poll): Promise<void> {
const restPath = `/rest/assignments/poll/${originalPoll.id}/`;
const data: Poll = Object.assign(originalPoll, poll); const data: Poll = Object.assign(originalPoll, poll);
await this.httpService.patch(restPath, data); await this.httpService.patch(`${this.restPollPath}${originalPoll.id}/`, data);
} }
/** /**
@ -186,8 +179,7 @@ export class AssignmentRepositoryService extends BaseAgendaContentObjectReposito
votesno: null, votesno: null,
votesvalid: poll.votesvalid || null votesvalid: poll.votesvalid || null
}; };
const restPath = `/rest/assignments/poll/${originalPoll.id}/`; await this.httpService.put(`${this.restPollPath}${originalPoll.id}/`, data);
await this.httpService.put(restPath, data);
} }
/** /**
@ -198,12 +190,11 @@ export class AssignmentRepositoryService extends BaseAgendaContentObjectReposito
* @param elected true if the candidate is to be elected, false if unelected * @param elected true if the candidate is to be elected, false if unelected
*/ */
public async markElected(user: AssignmentUser, assignment: ViewAssignment, elected: boolean): Promise<void> { public async markElected(user: AssignmentUser, assignment: ViewAssignment, elected: boolean): Promise<void> {
const restPath = `/rest/assignments/assignment/${assignment.id}/mark_elected/`;
const data = { user: user.user_id }; const data = { user: user.user_id };
if (elected) { if (elected) {
await this.httpService.post(restPath, data); await this.httpService.post(this.restPath + assignment.id + this.markElectedPath, data);
} else { } else {
await this.httpService.delete(restPath, data); await this.httpService.delete(this.restPath + assignment.id + this.markElectedPath, data);
} }
} }

View File

@ -6,7 +6,6 @@ import { _ } from 'app/core/translate/translation-marker';
* The possible keys of a poll object that represent numbers. * The possible keys of a poll object that represent numbers.
* TODO Should be 'key of MotionPoll if type of key is number' * TODO Should be 'key of MotionPoll if type of key is number'
* TODO: normalize MotionPoll model and other poll models * TODO: normalize MotionPoll model and other poll models
* TODO: reuse more motion-poll-service stuff
*/ */
export type CalculablePollKey = 'votesvalid' | 'votesinvalid' | 'votescast' | 'yes' | 'no' | 'abstain'; export type CalculablePollKey = 'votesvalid' | 'votesinvalid' | 'votescast' | 'yes' | 'no' | 'abstain';
@ -102,9 +101,9 @@ export abstract class PollService {
/** /**
* Gets an icon for a Poll Key * Gets an icon for a Poll Key
* *
* @param key * @param key yes, no, abstain or something like that
* @returns a string for material-icons to represent the icon for * @returns a string for material-icons to represent the icon for
* this key(e.g. yes: positiv sign, no: negative sign) * this key(e.g. yes: positive sign, no: negative sign)
*/ */
public getIcon(key: CalculablePollKey): string { public getIcon(key: CalculablePollKey): string {
switch (key) { switch (key) {
@ -128,6 +127,7 @@ export abstract class PollService {
/** /**
* Gets a label for a poll Key * Gets a label for a poll Key
* *
* @param key yes, no, abstain or something like that
* @returns A short descriptive name for the poll keys * @returns A short descriptive name for the poll keys
*/ */
public getLabel(key: CalculablePollKey | PollVoteValue): string { public getLabel(key: CalculablePollKey | PollVoteValue): string {
@ -151,11 +151,11 @@ export abstract class PollService {
/** /**
* retrieve special labels for a poll value * retrieve special labels for a poll value
*
* @param value
* @returns the label for a non-positive value, according to
* {@link specialPollVotes}. Positive values will return as string * {@link specialPollVotes}. Positive values will return as string
* representation of themselves * representation of themselves
*
* @param value check value for special numbers
* @returns the label for a non-positive value, according to
*/ */
public getSpecialLabel(value: number): string { public getSpecialLabel(value: number): string {
if (value >= 0) { if (value >= 0) {
@ -166,10 +166,9 @@ export abstract class PollService {
} }
/** /**
* Get the progressbar class for a decision key * Get the progress bar class for a decision key
*
* @param key
* *
* @param key a calculable poll key (like yes or no)
* @returns a css class designing a progress bar in a color, or an empty string * @returns a css class designing a progress bar in a color, or an empty string
*/ */
public getProgressBarColor(key: CalculablePollKey | PollVoteValue): string { public getProgressBarColor(key: CalculablePollKey | PollVoteValue): string {

View File

@ -32,10 +32,10 @@ export class PollOption extends Deserializer {
} }
}); });
if (input.votes) { if (input.votes) {
input.votes = input.votes.map(v => { input.votes = input.votes.map(vote => {
return { return {
value: v.value, value: vote.value,
weight: parseInt(v.weight, 10) weight: parseInt(vote.weight, 10)
}; };
}); });
} }

View File

@ -33,11 +33,12 @@ export class Poll extends Deserializer {
// cast stringify numbers // cast stringify numbers
if (typeof input === 'object') { if (typeof input === 'object') {
const numberifyKeys = ['id', 'votesvalid', 'votesinvalid', 'votescast', 'assignment_id']; const numberifyKeys = ['id', 'votesvalid', 'votesinvalid', 'votescast', 'assignment_id'];
Object.keys(input).forEach(key => {
for (const key of Object.keys(input)) {
if (numberifyKeys.includes(key) && typeof input[key] === 'string') { if (numberifyKeys.includes(key) && typeof input[key] === 'string') {
input[key] = parseInt(input[key], 10); input[key] = parseInt(input[key], 10);
} }
}); }
} }
super(input); super(input);
} }

View File

@ -1,4 +1,8 @@
<os-head-bar [mainButton]="operator.hasPerms('assignments.can_manage')" (mainEvent)="onPlusButton()" [multiSelectMode]="isMultiSelect"> <os-head-bar
[mainButton]="operator.hasPerms('assignments.can_manage')"
(mainEvent)="onPlusButton()"
[multiSelectMode]="isMultiSelect"
>
<!-- Title --> <!-- Title -->
<div class="title-slot"><h2 translate>Elections</h2></div> <div class="title-slot"><h2 translate>Elections</h2></div>
<!-- Menu --> <!-- Menu -->
@ -91,6 +95,10 @@
<mat-icon>done_all</mat-icon> <mat-icon>done_all</mat-icon>
<span translate>Select all</span> <span translate>Select all</span>
</button> </button>
<button mat-menu-item (click)="deselectAll()">
<mat-icon>clear</mat-icon>
<span translate>Deselect all</span>
</button>
<mat-divider></mat-divider> <mat-divider></mat-divider>
<button <button
mat-menu-item mat-menu-item

View File

@ -15,8 +15,7 @@ import { StorageService } from 'app/core/core-services/storage.service';
import { ViewAssignment } from '../models/view-assignment'; import { ViewAssignment } from '../models/view-assignment';
/** /**
* Listview for the assignments * List view for the assignments
*
*/ */
@Component({ @Component({
selector: 'os-assignment-list', selector: 'os-assignment-list',
@ -26,7 +25,9 @@ import { ViewAssignment } from '../models/view-assignment';
export class AssignmentListComponent extends ListViewBaseComponent<ViewAssignment, Assignment> implements OnInit { export class AssignmentListComponent extends ListViewBaseComponent<ViewAssignment, Assignment> implements OnInit {
/** /**
* Constructor. * Constructor.
*
* @param titleService * @param titleService
* @param storage
* @param translate * @param translate
* @param matSnackBar * @param matSnackBar
* @param repo the repository * @param repo the repository
@ -51,7 +52,7 @@ export class AssignmentListComponent extends ListViewBaseComponent<ViewAssignmen
public operator: OperatorService public operator: OperatorService
) { ) {
super(titleService, translate, matSnackBar, route, storage, filterService, sortService); super(titleService, translate, matSnackBar, route, storage, filterService, sortService);
// activate multiSelect mode for this listview // activate multiSelect mode for this list view
this.canMultiSelect = true; this.canMultiSelect = true;
} }

View File

@ -3,10 +3,10 @@ import { NgModule } from '@angular/core';
import { AssignmentDetailComponent } from './components/assignment-detail/assignment-detail.component'; import { AssignmentDetailComponent } from './components/assignment-detail/assignment-detail.component';
import { AssignmentListComponent } from './assignment-list/assignment-list.component'; import { AssignmentListComponent } from './assignment-list/assignment-list.component';
import { AssignmentsRoutingModule } from './assignments-routing.module';
import { SharedModule } from '../../shared/shared.module';
import { AssignmentPollComponent } from './components/assignment-poll/assignment-poll.component'; import { AssignmentPollComponent } from './components/assignment-poll/assignment-poll.component';
import { AssignmentPollDialogComponent } from './components/assignment-poll/assignment-poll-dialog.component'; import { AssignmentPollDialogComponent } from './components/assignment-poll/assignment-poll-dialog.component';
import { AssignmentsRoutingModule } from './assignments-routing.module';
import { SharedModule } from '../../shared/shared.module';
@NgModule({ @NgModule({
imports: [CommonModule, AssignmentsRoutingModule, SharedModule], imports: [CommonModule, AssignmentsRoutingModule, SharedModule],

View File

@ -1,18 +1,16 @@
import { Router, ActivatedRoute } from '@angular/router'; import { BehaviorSubject } from 'rxjs';
import { Component, OnInit, OnDestroy } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup } from '@angular/forms'; import { FormBuilder, FormGroup } from '@angular/forms';
import { MatSnackBar, MatSelectChange } from '@angular/material'; import { MatSnackBar, MatSelectChange } from '@angular/material';
import { Router, ActivatedRoute } from '@angular/router';
import { Title } from '@angular/platform-browser'; import { Title } from '@angular/platform-browser';
import { BehaviorSubject } from 'rxjs';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { BaseViewComponent } from '../../../base/base-view';
import { Assignment } from 'app/shared/models/assignments/assignment'; import { Assignment } from 'app/shared/models/assignments/assignment';
import { AssignmentPollService } from '../../services/assignment-poll.service'; import { AssignmentPollService } from '../../services/assignment-poll.service';
import { AssignmentRepositoryService } from 'app/core/repositories/assignments/assignment-repository.service'; import { AssignmentRepositoryService } from 'app/core/repositories/assignments/assignment-repository.service';
import { AssignmentUser } from 'app/shared/models/assignments/assignment-user'; import { BaseViewComponent } from 'app/site/base/base-view';
import { ConstantsService } from 'app/core/ui-services/constants.service'; import { ConstantsService } from 'app/core/ui-services/constants.service';
import { ItemRepositoryService } from 'app/core/repositories/agenda/item-repository.service'; import { ItemRepositoryService } from 'app/core/repositories/agenda/item-repository.service';
import { LocalPermissionsService } from 'app/site/motions/services/local-permissions.service'; import { LocalPermissionsService } from 'app/site/motions/services/local-permissions.service';
@ -34,7 +32,7 @@ import { ViewUser } from 'app/site/users/models/view-user';
templateUrl: './assignment-detail.component.html', templateUrl: './assignment-detail.component.html',
styleUrls: ['./assignment-detail.component.scss'] styleUrls: ['./assignment-detail.component.scss']
}) })
export class AssignmentDetailComponent extends BaseViewComponent implements OnInit, OnDestroy { export class AssignmentDetailComponent extends BaseViewComponent implements OnInit {
/** /**
* Determines if the assignment is new * Determines if the assignment is new
*/ */
@ -72,17 +70,17 @@ export class AssignmentDetailComponent extends BaseViewComponent implements OnIn
public assignmentForm: FormGroup; public assignmentForm: FormGroup;
/** /**
* Used in the seacrhValue selector to assign tags * Used in the search Value selector to assign tags
*/ */
public tagsObserver: BehaviorSubject<ViewTag[]>; public tagsObserver: BehaviorSubject<ViewTag[]>;
/** /**
* Used in the seacrhValue selector to assign an agenda item * Used in the search Value selector to assign an agenda item
*/ */
public agendaObserver: BehaviorSubject<ViewItem[]>; public agendaObserver: BehaviorSubject<ViewItem[]>;
/** /**
* Sets the assignment, e.g. via an autoupdate. Reload important things here: * Sets the assignment, e.g. via an auto update. Reload important things here:
* - Poll base values are be recalculated * - Poll base values are be recalculated
* *
* @param assignment the assignment to set * @param assignment the assignment to set
@ -253,7 +251,7 @@ export class AssignmentDetailComponent extends BaseViewComponent implements OnIn
} }
/** /**
* TODO: change/update the assignment form values * Changes/updates the assignment form values
* *
* @param assignment * @param assignment
*/ */
@ -282,7 +280,7 @@ export class AssignmentDetailComponent extends BaseViewComponent implements OnIn
/** /**
* Creates a new Poll * Creates a new Poll
* TODO: directly open poll dialog * TODO: directly open poll dialog?
*/ */
public async createPoll(): Promise<void> { public async createPoll(): Promise<void> {
await this.repo.addPoll(this.assignment).then(null, this.raiseError); await this.repo.addPoll(this.assignment).then(null, this.raiseError);
@ -304,14 +302,12 @@ export class AssignmentDetailComponent extends BaseViewComponent implements OnIn
/** /**
* Adds a user to the list of candidates * Adds a user to the list of candidates
*
* @param user
*/ */
public async addUser(): Promise<void> { public async addUser(): Promise<void> {
const candId = this.candidatesForm.get('candidate').value; const candId = this.candidatesForm.get('candidate').value;
this.candidatesForm.setValue({ candidate: null }); this.candidatesForm.setValue({ candidate: null });
if (candId) { if (candId) {
await this.repo.addCandidate(candId, this.assignment).then(null, this.raiseError); await this.repo.changeCandidate(candId, this.assignment).then(null, this.raiseError);
} }
} }
@ -320,8 +316,8 @@ export class AssignmentDetailComponent extends BaseViewComponent implements OnIn
* *
* @param user Assignment User * @param user Assignment User
*/ */
public async removeUser(user: AssignmentUser): Promise<void> { public async removeUser(user: ViewUser): Promise<void> {
await this.repo.deleteCandidate(user, this.assignment).then(null, this.raiseError); await this.repo.changeCandidate(user.id, this.assignment).then(null, this.raiseError);
} }
/** /**
@ -343,7 +339,7 @@ export class AssignmentDetailComponent extends BaseViewComponent implements OnIn
); );
} else { } else {
this.newAssignment = true; this.newAssignment = true;
// TODO set defaults // TODO set defaults?
this.assignment = new ViewAssignment(new Assignment()); this.assignment = new ViewAssignment(new Assignment());
this.patchForm(this.assignment); this.patchForm(this.assignment);
this.setEditMode(true); this.setEditMode(true);

View File

@ -63,12 +63,11 @@
<input <input
type="number" type="number"
matInput matInput
[value]="getSumValue('votestotal')" [value]="getSumValue('votescast')"
(change)="setSumValue('votestotal', $event.target.value)" (change)="setSumValue('votescast', $event.target.value)"
/> />
<mat-label translate>Total votes</mat-label> <mat-label translate>Total votes</mat-label>
</mat-form-field> </mat-form-field>
</div> </div>
<div class="submit-buttons"> <div class="submit-buttons">
<button mat-button (click)="submit()">{{ 'Save' | translate }}</button> <button mat-button (click)="submit()">{{ 'Save' | translate }}</button>

View File

@ -69,7 +69,7 @@ export class AssignmentPollDialogComponent {
/** /**
* Validates candidates input (every candidate has their options filled in), * Validates candidates input (every candidate has their options filled in),
* submits and closes the dialog if successfull, else displays an error popup. * submits and closes the dialog if successful, else displays an error popup.
* TODO better validation * TODO better validation
*/ */
public submit(): void { public submit(): void {
@ -123,14 +123,14 @@ export class AssignmentPollDialogComponent {
* @param candidate the candidate for whom to update the value * @param candidate the candidate for whom to update the value
* @param newData the new value * @param newData the new value
*/ */
public setValue(value: PollVoteValue, candidate: PollOption, newData: number): void { public setValue(value: PollVoteValue, candidate: PollOption, newData: string): void {
const vote = candidate.votes.find(v => v.value === value); const vote = candidate.votes.find(v => v.value === value);
if (vote) { if (vote) {
vote.weight = +newData; vote.weight = parseInt(newData, 10);
} else { } else {
candidate.votes.push({ candidate.votes.push({
value: value, value: value,
weight: +newData weight: parseInt(newData, 10)
}); });
} }
} }
@ -142,7 +142,7 @@ export class AssignmentPollDialogComponent {
* @param candidate the pollOption * @param candidate the pollOption
* @returns the currently entered number or undefined if no number has been set * @returns the currently entered number or undefined if no number has been set
*/ */
public getValue(value: PollVoteValue, candidate: PollOption): number { public getValue(value: PollVoteValue, candidate: PollOption): number | undefined {
const val = candidate.votes.find(v => v.value === value); const val = candidate.votes.find(v => v.value === value);
return val ? val.weight : undefined; return val ? val.weight : undefined;
} }
@ -151,10 +151,10 @@ export class AssignmentPollDialogComponent {
* Retrieves a per-poll value * Retrieves a per-poll value
* *
* @param value * @param value
* @returns integer or null * @returns integer or undefined
*/ */
public getSumValue(value: summaryPollKeys): number | null { public getSumValue(value: summaryPollKeys): number | undefined {
return this.data.poll[value] || null; return this.data.poll[value] || undefined;
} }
/** /**
@ -164,6 +164,6 @@ export class AssignmentPollDialogComponent {
* @param weight * @param weight
*/ */
public setSumValue(value: summaryPollKeys, weight: string): void { public setSumValue(value: summaryPollKeys, weight: string): void {
this.data.poll[value] = +weight; this.data.poll[value] = parseInt(weight, 10);
} }
} }

View File

@ -1,5 +1,5 @@
import { Component, OnInit, Input } from '@angular/core'; import { Component, OnInit, Input } from '@angular/core';
import { MatDialog } from '@angular/material'; import { MatDialog, MatSnackBar } from '@angular/material';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
@ -12,17 +12,18 @@ import { Poll } from 'app/shared/models/assignments/poll';
import { PollOption } from 'app/shared/models/assignments/poll-option'; import { PollOption } from 'app/shared/models/assignments/poll-option';
import { PromptService } from 'app/core/ui-services/prompt.service'; import { PromptService } from 'app/core/ui-services/prompt.service';
import { ViewAssignment } from '../../models/view-assignment'; import { ViewAssignment } from '../../models/view-assignment';
import { BaseViewComponent } from 'app/site/base/base-view';
import { Title } from '@angular/platform-browser';
/** /**
* Component for a single assignment poll. Used in assignment detail view * Component for a single assignment poll. Used in assignment detail view
* TODO DOCU
*/ */
@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']
}) })
export class AssignmentPollComponent implements OnInit { export class AssignmentPollComponent extends BaseViewComponent implements OnInit {
/** /**
* The related assignment (used for metainfos, e.g. related user names) * The related assignment (used for metainfos, e.g. related user names)
*/ */
@ -52,6 +53,8 @@ export class AssignmentPollComponent implements OnInit {
} }
/** /**
* Gets the voting options
*
* @returns all used (not undefined) option-independent values that are * @returns all used (not undefined) option-independent values that are
* used in this poll (e.g.) * used in this poll (e.g.)
*/ */
@ -60,6 +63,8 @@ export class AssignmentPollComponent implements OnInit {
} }
/** /**
* Gets the translated poll method name
*
* TODO: check/improve text here * TODO: check/improve text here
* *
* @returns a name for the poll method this poll is set to (which is determined * @returns a name for the poll method this poll is set to (which is determined
@ -92,15 +97,20 @@ export class AssignmentPollComponent implements OnInit {
* @param promptService Prompts for confirmation dialogs * @param promptService Prompts for confirmation dialogs
*/ */
public constructor( public constructor(
titleService: Title,
matSnackBar: MatSnackBar,
public pollService: AssignmentPollService, public pollService: AssignmentPollService,
private operator: OperatorService, private operator: OperatorService,
private assignmentRepo: AssignmentRepositoryService, private assignmentRepo: AssignmentRepositoryService,
public translate: TranslateService, public translate: TranslateService,
public dialog: MatDialog, public dialog: MatDialog,
private promptService: PromptService private promptService: PromptService
) {} ) {
super(titleService, translate, matSnackBar);
}
/** /**
* Gets the currently selected majority choice option from the repo
*/ */
public ngOnInit(): void { public ngOnInit(): void {
this.majorityChoice = this.majorityChoice =
@ -116,17 +126,17 @@ export class AssignmentPollComponent implements OnInit {
public async onDeletePoll(): Promise<void> { public async onDeletePoll(): Promise<void> {
const title = this.translate.instant('Are you sure you want to delete this poll?'); const title = this.translate.instant('Are you sure you want to delete this poll?');
if (await this.promptService.open(title, null)) { if (await this.promptService.open(title, null)) {
await this.assignmentRepo.deletePoll(this.poll); await this.assignmentRepo.deletePoll(this.poll).then(null, this.raiseError);
} }
// TODO error handling
} }
/** /**
* Print the PDF of this poll with the corresponding options and numbers
*
* TODO Print the ballots for this poll. * TODO Print the ballots for this poll.
*/ */
public printBallot(poll: Poll): void { public printBallot(poll: Poll): void {
this.promptService.open('TODO', 'TODO'); this.raiseError('Not yet implemented');
// TODO Print ballot not implemented
} }
/** /**
@ -136,7 +146,7 @@ export class AssignmentPollComponent implements OnInit {
* @returns the full_name for the candidate * @returns the full_name for the candidate
*/ */
public getCandidateName(option: PollOption): string { public getCandidateName(option: PollOption): string {
const user = this.assignment.candidates.find(c => c.id === option.candidate_id); const user = this.assignment.candidates.find(candidate => candidate.id === option.candidate_id);
return user ? user.full_name : ''; return user ? user.full_name : '';
// TODO this.assignment.candidates may not contain every candidates' name (if deleted later) // TODO this.assignment.candidates may not contain every candidates' name (if deleted later)
// so we should rather use this.userRepo.getViewModel(option.id).full_name // so we should rather use this.userRepo.getViewModel(option.id).full_name
@ -161,7 +171,6 @@ export class AssignmentPollComponent implements OnInit {
/** /**
* Opens the {@link AssignmentPollDialogComponent} dialog and then updates the votes, if the dialog * Opens the {@link AssignmentPollDialogComponent} dialog and then updates the votes, if the dialog
* closes successfully (validation is done there) * closes successfully (validation is done there)
*
*/ */
public enterVotes(): void { public enterVotes(): void {
// TODO deep copy of this.poll (JSON parse is ugly workaround) // TODO deep copy of this.poll (JSON parse is ugly workaround)
@ -174,13 +183,12 @@ export class AssignmentPollComponent implements OnInit {
data: data, data: data,
maxHeight: '90vh', maxHeight: '90vh',
minWidth: '300px', minWidth: '300px',
maxWidth: '80vh', maxWidth: '80vw',
disableClose: true disableClose: true
}); });
dialogRef.afterClosed().subscribe(result => { dialogRef.afterClosed().subscribe(result => {
if (result) { if (result) {
this.assignmentRepo.updateVotes(result, this.poll); this.assignmentRepo.updateVotes(result, this.poll).then(null, this.raiseError);
// TODO error handling
} }
}); });
} }
@ -188,6 +196,7 @@ export class AssignmentPollComponent implements OnInit {
/** /**
* Updates the majority method for this poll * Updates the majority method for this poll
* *
* @param method the selected majority method
*/ */
public setMajority(method: MajorityMethod): void { public setMajority(method: MajorityMethod): void {
this.majorityChoice = method; this.majorityChoice = method;
@ -209,9 +218,10 @@ export class AssignmentPollComponent implements OnInit {
if (!this.operator.hasPerms('assignments.can_manage')) { if (!this.operator.hasPerms('assignments.can_manage')) {
return; return;
} }
// TODO additional conditions: assignment not finished? // TODO additional conditions: assignment not finished?
const candidate = this.assignment.assignment.assignment_related_users.find( const candidate = this.assignment.assignment.assignment_related_users.find(
u => u.user_id === option.candidate_id user => user.user_id === option.candidate_id
); );
if (candidate) { if (candidate) {
this.assignmentRepo.markElected(candidate, this.assignment, !option.is_elected); this.assignmentRepo.markElected(candidate, this.assignment, !option.is_elected);

View File

@ -69,6 +69,9 @@ export class ViewAssignment extends BaseAgendaViewModel {
public constructor(assignment: Assignment, relatedUser?: ViewUser[], agendaItem?: ViewItem, tags?: ViewTag[]) { public constructor(assignment: Assignment, relatedUser?: ViewUser[], agendaItem?: ViewItem, tags?: ViewTag[]) {
super(Assignment.COLLECTIONSTRING); super(Assignment.COLLECTIONSTRING);
console.log('related user: ', relatedUser);
this._assignment = assignment; this._assignment = assignment;
this._relatedUser = relatedUser; this._relatedUser = relatedUser;
this._agendaItem = agendaItem; this._agendaItem = agendaItem;

View File

@ -16,7 +16,7 @@ export type AssignmentPollMethod = 'yn' | 'yna' | 'votes';
type AssignmentPercentBase = 'YES_NO_ABSTAIN' | 'YES_NO' | 'VALID' | 'CAST' | 'DISABLED'; type AssignmentPercentBase = 'YES_NO_ABSTAIN' | 'YES_NO' | 'VALID' | 'CAST' | 'DISABLED';
/** /**
* Service class for motion polls. * Service class for assignment polls.
*/ */
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
@ -46,6 +46,7 @@ export class AssignmentPollService extends PollService {
/** /**
* Constructor. Subscribes to the configuration values needed * Constructor. Subscribes to the configuration values needed
*
* @param config ConfigService * @param config ConfigService
*/ */
public constructor(config: ConfigService) { public constructor(config: ConfigService) {
@ -59,12 +60,11 @@ export class AssignmentPollService extends PollService {
config config
.get<AssignmentPercentBase>('assignments_poll_100_percent_base') .get<AssignmentPercentBase>('assignments_poll_100_percent_base')
.subscribe(base => (this.percentBase = base)); .subscribe(base => (this.percentBase = base));
// assignments_add_candidates_to_list_of_speakers boolean
} }
/** /**
* Get the base amount for the 100% calculations. Note that some poll methods * Get the base amount for the 100% calculations. Note that some poll methods
* (e.g. yes/no/abstain may have a diffferent percentage base and will return null here) * (e.g. yes/no/abstain may have a different percentage base and will return null here)
* *
* @param poll * @param poll
* @returns The amount of votes indicating the 100% base * @returns The amount of votes indicating the 100% base
@ -120,6 +120,8 @@ export class AssignmentPollService extends PollService {
/** /**
* Check if the option in a poll is abstract (percentages should not be calculated) * Check if the option in a poll is abstract (percentages should not be calculated)
* *
* @param poll
* @param option
* @returns true if the poll has no percentages, the poll option is a special value, * @returns true if the poll has no percentages, the poll option is a special value,
* or if the calculations are disabled in the config * or if the calculations are disabled in the config
*/ */