Merge pull request #4594 from FinnStutzenstein/modelStructureForAssignments
Background structure for assignments
This commit is contained in:
commit
259afa7f88
@ -87,6 +87,7 @@ matrix:
|
|||||||
install:
|
install:
|
||||||
- npm install
|
- npm install
|
||||||
script:
|
script:
|
||||||
|
- npm list --depth=0 || cat --help
|
||||||
- npm run prettify-check
|
- npm run prettify-check
|
||||||
|
|
||||||
- language: node_js
|
- language: node_js
|
||||||
|
@ -79,7 +79,7 @@
|
|||||||
"karma-jasmine": "~2.0.1",
|
"karma-jasmine": "~2.0.1",
|
||||||
"karma-jasmine-html-reporter": "^1.4.0",
|
"karma-jasmine-html-reporter": "^1.4.0",
|
||||||
"npm-run-all": "^4.1.5",
|
"npm-run-all": "^4.1.5",
|
||||||
"prettier": "^1.16.4",
|
"prettier": "^1.17.0",
|
||||||
"protractor": "^5.4.2",
|
"protractor": "^5.4.2",
|
||||||
"source-map-explorer": "^1.7.0",
|
"source-map-explorer": "^1.7.0",
|
||||||
"terser": "3.16.1",
|
"terser": "3.16.1",
|
||||||
|
@ -3,14 +3,14 @@ import { Injectable } from '@angular/core';
|
|||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
|
||||||
import { Assignment } from 'app/shared/models/assignments/assignment';
|
import { Assignment } from 'app/shared/models/assignments/assignment';
|
||||||
import { AssignmentUser } from 'app/shared/models/assignments/assignment-user';
|
import { AssignmentRelatedUser } from 'app/shared/models/assignments/assignment-related-user';
|
||||||
import { BaseAgendaContentObjectRepository } from '../base-agenda-content-object-repository';
|
import { BaseAgendaContentObjectRepository } from '../base-agenda-content-object-repository';
|
||||||
import { CollectionStringMapperService } from '../../core-services/collectionStringMapper.service';
|
import { CollectionStringMapperService } from '../../core-services/collectionStringMapper.service';
|
||||||
import { DataSendService } from 'app/core/core-services/data-send.service';
|
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 { AssignmentPoll } from 'app/shared/models/assignments/assignment-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';
|
||||||
@ -18,6 +18,9 @@ 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 { ViewAssignmentRelatedUser } from 'app/site/assignments/models/view-assignment-related-user';
|
||||||
|
import { ViewAssignmentPoll } from 'app/site/assignments/models/view-assignment-poll';
|
||||||
|
import { ViewAssignmentPollOption } from 'app/site/assignments/models/view-assignment-poll-option';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Repository Service for Assignments.
|
* Repository Service for Assignments.
|
||||||
@ -69,17 +72,43 @@ export class AssignmentRepositoryService extends BaseAgendaContentObjectReposito
|
|||||||
};
|
};
|
||||||
|
|
||||||
public createViewModel(assignment: Assignment): ViewAssignment {
|
public createViewModel(assignment: Assignment): ViewAssignment {
|
||||||
const relatedUser = this.viewModelStoreService.getMany(ViewUser, assignment.candidates_id);
|
|
||||||
const agendaItem = this.viewModelStoreService.get(ViewItem, assignment.agenda_item_id);
|
const agendaItem = this.viewModelStoreService.get(ViewItem, assignment.agenda_item_id);
|
||||||
const tags = this.viewModelStoreService.getMany(ViewTag, assignment.tags_id);
|
const tags = this.viewModelStoreService.getMany(ViewTag, assignment.tags_id);
|
||||||
|
const assignmentRelatedUsers = this.createViewAssignmentRelatedUsers(assignment.assignment_related_users);
|
||||||
|
const assignmentPolls = this.createViewAssignmentPolls(assignment.polls);
|
||||||
|
|
||||||
const viewAssignment = new ViewAssignment(assignment, relatedUser, agendaItem, tags);
|
const viewAssignment = new ViewAssignment(
|
||||||
|
assignment,
|
||||||
|
assignmentRelatedUsers,
|
||||||
|
assignmentPolls,
|
||||||
|
agendaItem,
|
||||||
|
tags
|
||||||
|
);
|
||||||
viewAssignment.getVerboseName = this.getVerboseName;
|
viewAssignment.getVerboseName = this.getVerboseName;
|
||||||
viewAssignment.getAgendaTitle = () => this.getAgendaTitle(viewAssignment);
|
viewAssignment.getAgendaTitle = () => this.getAgendaTitle(viewAssignment);
|
||||||
viewAssignment.getAgendaTitleWithType = () => this.getAgendaTitleWithType(viewAssignment);
|
viewAssignment.getAgendaTitleWithType = () => this.getAgendaTitleWithType(viewAssignment);
|
||||||
return viewAssignment;
|
return viewAssignment;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private createViewAssignmentRelatedUsers(
|
||||||
|
assignmentRelatedUsers: AssignmentRelatedUser[]
|
||||||
|
): ViewAssignmentRelatedUser[] {
|
||||||
|
return assignmentRelatedUsers.map(aru => {
|
||||||
|
const user = this.viewModelStoreService.get(ViewUser, aru.user_id);
|
||||||
|
return new ViewAssignmentRelatedUser(aru, user);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private createViewAssignmentPolls(assignmentPolls: AssignmentPoll[]): ViewAssignmentPoll[] {
|
||||||
|
return assignmentPolls.map(poll => {
|
||||||
|
const options = poll.options.map(option => {
|
||||||
|
const user = this.viewModelStoreService.get(ViewUser, option.candidate_id);
|
||||||
|
return new ViewAssignmentPollOption(option, user);
|
||||||
|
});
|
||||||
|
return new ViewAssignmentPoll(poll, options);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds another user as a candidate
|
* Adds another user as a candidate
|
||||||
*
|
*
|
||||||
@ -127,7 +156,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: ViewAssignmentPoll): Promise<void> {
|
||||||
await this.httpService.delete(`${this.restPollPath}${poll.id}/`);
|
await this.httpService.delete(`${this.restPollPath}${poll.id}/`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -139,8 +168,8 @@ export class AssignmentRepositoryService extends BaseAgendaContentObjectReposito
|
|||||||
*
|
*
|
||||||
* 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<AssignmentPoll>, originalPoll: ViewAssignmentPoll): Promise<void> {
|
||||||
const data: Poll = Object.assign(originalPoll, poll);
|
const data: AssignmentPoll = Object.assign(originalPoll.poll, poll);
|
||||||
await this.httpService.patch(`${this.restPollPath}${originalPoll.id}/`, data);
|
await this.httpService.patch(`${this.restPollPath}${originalPoll.id}/`, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -151,7 +180,7 @@ export class AssignmentRepositoryService extends BaseAgendaContentObjectReposito
|
|||||||
* @param poll the updated Poll
|
* @param poll the updated Poll
|
||||||
* @param originalPoll the original poll
|
* @param originalPoll the original poll
|
||||||
*/
|
*/
|
||||||
public async updateVotes(poll: Partial<Poll>, originalPoll: Poll): Promise<void> {
|
public async updateVotes(poll: Partial<AssignmentPoll>, originalPoll: ViewAssignmentPoll): Promise<void> {
|
||||||
poll.options.sort((a, b) => a.weight - b.weight);
|
poll.options.sort((a, b) => a.weight - b.weight);
|
||||||
const votes = poll.options.map(option => {
|
const votes = poll.options.map(option => {
|
||||||
switch (poll.pollmethod) {
|
switch (poll.pollmethod) {
|
||||||
@ -185,12 +214,16 @@ export class AssignmentRepositoryService extends BaseAgendaContentObjectReposito
|
|||||||
/**
|
/**
|
||||||
* change the 'elected' state of an election candidate
|
* change the 'elected' state of an election candidate
|
||||||
*
|
*
|
||||||
* @param user
|
* @param assignmentRelatedUser
|
||||||
* @param assignment
|
* @param assignment
|
||||||
* @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(
|
||||||
const data = { user: user.user_id };
|
assignmentRelatedUser: ViewAssignmentRelatedUser,
|
||||||
|
assignment: ViewAssignment,
|
||||||
|
elected: boolean
|
||||||
|
): Promise<void> {
|
||||||
|
const data = { user: assignmentRelatedUser.user_id };
|
||||||
if (elected) {
|
if (elected) {
|
||||||
await this.httpService.post(this.restPath + assignment.id + this.markElectedPath, data);
|
await this.httpService.post(this.restPath + assignment.id + this.markElectedPath, data);
|
||||||
} else {
|
} else {
|
||||||
|
@ -7,12 +7,12 @@ import { PollVoteValue } from 'app/core/ui-services/poll.service';
|
|||||||
* part of the 'polls-options'-array in poll
|
* part of the 'polls-options'-array in poll
|
||||||
* @ignore
|
* @ignore
|
||||||
*/
|
*/
|
||||||
export class PollOption extends Deserializer {
|
export class AssignmentPollOption extends Deserializer {
|
||||||
public id: number; // The AssignmentUser id of the candidate
|
public id: number; // The AssignmentUser id of the candidate
|
||||||
public candidate_id: number; // the User id of the candidate
|
public candidate_id: number; // the User id of the candidate
|
||||||
public is_elected: boolean;
|
public is_elected: boolean;
|
||||||
public votes: {
|
public votes: {
|
||||||
weight: number; // TODO arrives as string?
|
weight: number; // represented as a string because it's a decimal field
|
||||||
value: PollVoteValue;
|
value: PollVoteValue;
|
||||||
}[];
|
}[];
|
||||||
public poll_id: number;
|
public poll_id: number;
|
||||||
@ -24,21 +24,12 @@ export class PollOption extends Deserializer {
|
|||||||
* @param input
|
* @param input
|
||||||
*/
|
*/
|
||||||
public constructor(input?: any) {
|
public constructor(input?: any) {
|
||||||
// cast stringify numbers
|
if (input && input.votes) {
|
||||||
if (typeof input === 'object') {
|
input.votes.forEach(vote => {
|
||||||
Object.keys(input).forEach(key => {
|
if (vote.weight) {
|
||||||
if (typeof input[key] === 'string') {
|
vote.weight = parseFloat(vote.weight);
|
||||||
input[key] = parseInt(input[key], 10);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (input.votes) {
|
|
||||||
input.votes = input.votes.map(vote => {
|
|
||||||
return {
|
|
||||||
value: vote.value,
|
|
||||||
weight: parseInt(vote.weight, 10)
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
super(input);
|
super(input);
|
||||||
}
|
}
|
@ -1,44 +1,37 @@
|
|||||||
import { AssignmentPollMethod } from 'app/site/assignments/services/assignment-poll.service';
|
import { AssignmentPollMethod } from 'app/site/assignments/services/assignment-poll.service';
|
||||||
import { Deserializer } from '../base/deserializer';
|
import { Deserializer } from '../base/deserializer';
|
||||||
import { PollOption } from './poll-option';
|
import { AssignmentPollOption } from './assignment-poll-option';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Content of the 'polls' property of assignments
|
* Content of the 'polls' property of assignments
|
||||||
* @ignore
|
* @ignore
|
||||||
*/
|
*/
|
||||||
export class Poll extends Deserializer {
|
export class AssignmentPoll extends Deserializer {
|
||||||
|
private static DECIMAL_FIELDS = ['votesvalid', 'votesinvalid', 'votescast'];
|
||||||
|
|
||||||
public id: number;
|
public id: number;
|
||||||
public pollmethod: AssignmentPollMethod;
|
public pollmethod: AssignmentPollMethod;
|
||||||
public description: string;
|
public description: string;
|
||||||
public published: boolean;
|
public published: boolean;
|
||||||
public options: PollOption[];
|
public options: AssignmentPollOption[];
|
||||||
public votesvalid: number;
|
public votesvalid: number;
|
||||||
public votesinvalid: number;
|
public votesinvalid: number;
|
||||||
public votescast: number;
|
public votescast: number;
|
||||||
public has_votes: boolean;
|
public has_votes: boolean;
|
||||||
public assignment_id: number;
|
public assignment_id: number;
|
||||||
|
|
||||||
/**
|
|
||||||
* (temporary?) storing the base values for percentage calculations,
|
|
||||||
* to avoid recalculating pollBases too often
|
|
||||||
* (the calculation iterates through all pollOptions in some use cases)
|
|
||||||
*/
|
|
||||||
public pollBase: number;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Needs to be completely optional because assignment has (yet) the optional parameter 'polls'
|
* Needs to be completely optional because assignment has (yet) the optional parameter 'polls'
|
||||||
* @param input
|
* @param input
|
||||||
*/
|
*/
|
||||||
public constructor(input?: any) {
|
public constructor(input?: any) {
|
||||||
// cast stringify numbers
|
// cast stringify numbers
|
||||||
if (typeof input === 'object') {
|
if (input) {
|
||||||
const numberifyKeys = ['id', 'votesvalid', 'votesinvalid', 'votescast', 'assignment_id'];
|
AssignmentPoll.DECIMAL_FIELDS.forEach(field => {
|
||||||
|
if (input[field] && typeof input[field] === 'string') {
|
||||||
for (const key of Object.keys(input)) {
|
input[field] = parseFloat(input[field]);
|
||||||
if (numberifyKeys.includes(key) && typeof input[key] === 'string') {
|
|
||||||
input[key] = parseInt(input[key], 10);
|
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
super(input);
|
super(input);
|
||||||
}
|
}
|
||||||
@ -47,7 +40,7 @@ export class Poll extends Deserializer {
|
|||||||
Object.assign(this, input);
|
Object.assign(this, input);
|
||||||
this.options = [];
|
this.options = [];
|
||||||
if (input.options instanceof Array) {
|
if (input.options instanceof Array) {
|
||||||
this.options = input.options.map(pollOptionData => new PollOption(pollOptionData));
|
this.options = input.options.map(pollOptionData => new AssignmentPollOption(pollOptionData));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
/**
|
||||||
|
* Content of the 'assignment_related_users' property.
|
||||||
|
*/
|
||||||
|
export interface AssignmentRelatedUser {
|
||||||
|
id: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* id of the user this assignment user relates to
|
||||||
|
*/
|
||||||
|
user_id: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current 'elected' state
|
||||||
|
*/
|
||||||
|
elected: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* id of the related assignment
|
||||||
|
*/
|
||||||
|
assignment_id: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A weight to determine the position in the list of candidates
|
||||||
|
* (determined by the server)
|
||||||
|
*/
|
||||||
|
weight: number;
|
||||||
|
}
|
@ -1,41 +0,0 @@
|
|||||||
import { Deserializer } from '../base/deserializer';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Content of the 'assignment_related_users' property.
|
|
||||||
* Note that this differs from a ViewUser (e.g. different id)
|
|
||||||
* @ignore
|
|
||||||
*/
|
|
||||||
export class AssignmentUser extends Deserializer {
|
|
||||||
public id: number;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* id of the user this assignment user relates to
|
|
||||||
*/
|
|
||||||
public user_id: number;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The current 'elected' state
|
|
||||||
*/
|
|
||||||
public elected: boolean;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* id of the related assignment
|
|
||||||
*/
|
|
||||||
public assignment_id: number;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A weight to determine the position in the list of candidates
|
|
||||||
* (determined by the server)
|
|
||||||
*/
|
|
||||||
public weight: number;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor. Needs to be completely optional because assignment has
|
|
||||||
* (yet) the optional parameter 'assignment_related_users'
|
|
||||||
*
|
|
||||||
* @param input
|
|
||||||
*/
|
|
||||||
public constructor(input?: any) {
|
|
||||||
super(input);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,6 +1,6 @@
|
|||||||
import { AssignmentUser } from './assignment-user';
|
|
||||||
import { Poll } from './poll';
|
|
||||||
import { BaseModel } from '../base/base-model';
|
import { BaseModel } from '../base/base-model';
|
||||||
|
import { AssignmentRelatedUser } from './assignment-related-user';
|
||||||
|
import { AssignmentPoll } from './assignment-poll';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Representation of an assignment.
|
* Representation of an assignment.
|
||||||
@ -8,14 +8,15 @@ import { BaseModel } from '../base/base-model';
|
|||||||
*/
|
*/
|
||||||
export class Assignment extends BaseModel<Assignment> {
|
export class Assignment extends BaseModel<Assignment> {
|
||||||
public static COLLECTIONSTRING = 'assignments/assignment';
|
public static COLLECTIONSTRING = 'assignments/assignment';
|
||||||
|
|
||||||
public id: number;
|
public id: number;
|
||||||
public title: string;
|
public title: string;
|
||||||
public description: string;
|
public description: string;
|
||||||
public open_posts: number;
|
public open_posts: number;
|
||||||
public phase: number; // see Openslides constants
|
public phase: number; // see Openslides constants
|
||||||
public assignment_related_users: AssignmentUser[];
|
public assignment_related_users: AssignmentRelatedUser[];
|
||||||
public poll_description_default: number;
|
public poll_description_default: number;
|
||||||
public polls: Poll[];
|
public polls: AssignmentPoll[];
|
||||||
public agenda_item_id: number;
|
public agenda_item_id: number;
|
||||||
public tags_id: number[];
|
public tags_id: number[];
|
||||||
|
|
||||||
@ -25,25 +26,18 @@ export class Assignment extends BaseModel<Assignment> {
|
|||||||
|
|
||||||
public get candidates_id(): number[] {
|
public get candidates_id(): number[] {
|
||||||
return this.assignment_related_users
|
return this.assignment_related_users
|
||||||
.sort((a: AssignmentUser, b: AssignmentUser) => {
|
.sort((a: AssignmentRelatedUser, b: AssignmentRelatedUser) => {
|
||||||
return a.weight - b.weight;
|
return a.weight - b.weight;
|
||||||
})
|
})
|
||||||
.map((candidate: AssignmentUser) => candidate.user_id);
|
.map((candidate: AssignmentRelatedUser) => candidate.user_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public deserialize(input: any): void {
|
public deserialize(input: any): void {
|
||||||
Object.assign(this, input);
|
Object.assign(this, input);
|
||||||
|
|
||||||
this.assignment_related_users = [];
|
|
||||||
if (input.assignment_related_users instanceof Array) {
|
|
||||||
this.assignment_related_users = input.assignment_related_users.map(
|
|
||||||
assignmentUserData => new AssignmentUser(assignmentUserData)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.polls = [];
|
this.polls = [];
|
||||||
if (input.polls instanceof Array) {
|
if (input.polls instanceof Array) {
|
||||||
this.polls = input.polls.map(pollData => new Poll(pollData));
|
this.polls = input.polls.map(pollData => new AssignmentPoll(pollData));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
31
client/src/app/shared/pipes/precision.pipe.ts
Normal file
31
client/src/app/shared/pipes/precision.pipe.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import { Pipe, PipeTransform } from '@angular/core';
|
||||||
|
import { DecimalPipe } from '@angular/common';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formats floats to have a defined precision.
|
||||||
|
*
|
||||||
|
* In this default application, the precision is 0, so no decimal places. Plugins
|
||||||
|
* may override this pipe to enable more precise votes.
|
||||||
|
*/
|
||||||
|
@Pipe({
|
||||||
|
name: 'precisionPipe'
|
||||||
|
})
|
||||||
|
export class PrecisionPipe implements PipeTransform {
|
||||||
|
protected precision: number;
|
||||||
|
|
||||||
|
public constructor(private decimalPipe: DecimalPipe) {
|
||||||
|
this.precision = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public transform(value: number, precision?: number): string {
|
||||||
|
if (!precision) {
|
||||||
|
precision = this.precision;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Min 1 place before the comma and exactly precision places after.
|
||||||
|
*/
|
||||||
|
const digitsInfo = `1.${precision}-${precision}`;
|
||||||
|
return this.decimalPipe.transform(value, digitsInfo);
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { RouterModule } from '@angular/router';
|
import { RouterModule } from '@angular/router';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule, DecimalPipe } from '@angular/common';
|
||||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||||
import { OwlDateTimeModule, OwlNativeDateTimeModule } from 'ng-pick-datetime';
|
import { OwlDateTimeModule, OwlNativeDateTimeModule } from 'ng-pick-datetime';
|
||||||
|
|
||||||
@ -81,6 +81,7 @@ import { ProjectorComponent } from './components/projector/projector.component';
|
|||||||
import { SlideContainerComponent } from './components/slide-container/slide-container.component';
|
import { SlideContainerComponent } from './components/slide-container/slide-container.component';
|
||||||
import { CountdownTimeComponent } from './components/contdown-time/countdown-time.component';
|
import { CountdownTimeComponent } from './components/contdown-time/countdown-time.component';
|
||||||
import { MediaUploadContentComponent } from './components/media-upload-content/media-upload-content.component';
|
import { MediaUploadContentComponent } from './components/media-upload-content/media-upload-content.component';
|
||||||
|
import { PrecisionPipe } from './pipes/precision.pipe';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Share Module for all "dumb" components and pipes.
|
* Share Module for all "dumb" components and pipes.
|
||||||
@ -201,7 +202,8 @@ import { MediaUploadContentComponent } from './components/media-upload-content/m
|
|||||||
OwlDateTimeModule,
|
OwlDateTimeModule,
|
||||||
OwlNativeDateTimeModule,
|
OwlNativeDateTimeModule,
|
||||||
CountdownTimeComponent,
|
CountdownTimeComponent,
|
||||||
MediaUploadContentComponent
|
MediaUploadContentComponent,
|
||||||
|
PrecisionPipe
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
PermsDirective,
|
PermsDirective,
|
||||||
@ -228,7 +230,8 @@ import { MediaUploadContentComponent } from './components/media-upload-content/m
|
|||||||
ProjectorComponent,
|
ProjectorComponent,
|
||||||
SlideContainerComponent,
|
SlideContainerComponent,
|
||||||
CountdownTimeComponent,
|
CountdownTimeComponent,
|
||||||
MediaUploadContentComponent
|
MediaUploadContentComponent,
|
||||||
|
PrecisionPipe
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
{ provide: DateAdapter, useClass: OpenSlidesDateAdapter },
|
{ provide: DateAdapter, useClass: OpenSlidesDateAdapter },
|
||||||
@ -236,7 +239,8 @@ import { MediaUploadContentComponent } from './components/media-upload-content/m
|
|||||||
SortingListComponent,
|
SortingListComponent,
|
||||||
SortingTreeComponent,
|
SortingTreeComponent,
|
||||||
OsSortFilterBarComponent,
|
OsSortFilterBarComponent,
|
||||||
OsSortBottomSheetComponent
|
OsSortBottomSheetComponent,
|
||||||
|
DecimalPipe
|
||||||
],
|
],
|
||||||
entryComponents: [OsSortBottomSheetComponent, C4DialogComponent]
|
entryComponents: [OsSortBottomSheetComponent, C4DialogComponent]
|
||||||
})
|
})
|
||||||
|
@ -2,7 +2,7 @@ import { NgModule } from '@angular/core';
|
|||||||
import { Routes, RouterModule } from '@angular/router';
|
import { Routes, RouterModule } from '@angular/router';
|
||||||
|
|
||||||
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 './components/assignment-list/assignment-list.component';
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{ path: '', component: AssignmentListComponent, pathMatch: 'full' },
|
{ path: '', component: AssignmentListComponent, pathMatch: 'full' },
|
||||||
|
@ -2,9 +2,9 @@ import { CommonModule } from '@angular/common';
|
|||||||
import { NgModule } from '@angular/core';
|
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 './components/assignment-list/assignment-list.component';
|
||||||
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-dialog/assignment-poll-dialog.component';
|
||||||
import { AssignmentsRoutingModule } from './assignments-routing.module';
|
import { AssignmentsRoutingModule } from './assignments-routing.module';
|
||||||
import { SharedModule } from '../../shared/shared.module';
|
import { SharedModule } from '../../shared/shared.module';
|
||||||
|
|
||||||
|
@ -9,8 +9,7 @@
|
|||||||
<!-- Title -->
|
<!-- Title -->
|
||||||
<div class="title-slot">
|
<div class="title-slot">
|
||||||
<h2 *ngIf="assignment && !newAssignment">
|
<h2 *ngIf="assignment && !newAssignment">
|
||||||
<span translate>Election</span>
|
<span *ngIf="!editAssignment">{{ assignment.getTitle() }}</span>
|
||||||
<span> </span> <span *ngIf="!editAssignment">{{ assignment.title }}</span>
|
|
||||||
<span *ngIf="editAssignment">{{ assignmentForm.get('title').value }}</span>
|
<span *ngIf="editAssignment">{{ assignmentForm.get('title').value }}</span>
|
||||||
</h2>
|
</h2>
|
||||||
<h2 *ngIf="newAssignment" translate>New election</h2>
|
<h2 *ngIf="newAssignment" translate>New election</h2>
|
||||||
@ -47,7 +46,7 @@
|
|||||||
<!-- Title -->
|
<!-- Title -->
|
||||||
<div class="title on-transition-fade" *ngIf="assignment && !editAssignment">
|
<div class="title on-transition-fade" *ngIf="assignment && !editAssignment">
|
||||||
<div class="title-line">
|
<div class="title-line">
|
||||||
<h1>{{ assignment.title }}</h1>
|
<h1>{{ assignment.getTitle() }}</h1>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ng-container *ngIf="vp.isMobile; then mobileView; else desktopView"></ng-container>
|
<ng-container *ngIf="vp.isMobile; then mobileView; else desktopView"></ng-container>
|
||||||
@ -131,7 +130,7 @@
|
|||||||
<div *ngIf="assignment && assignment.candidates">
|
<div *ngIf="assignment && assignment.candidates">
|
||||||
<!-- TODO: Sorting -->
|
<!-- TODO: Sorting -->
|
||||||
<div *ngFor="let candidate of assignment.candidates">
|
<div *ngFor="let candidate of assignment.candidates">
|
||||||
<span>{{ candidate.username }}</span>
|
<span>{{ candidate.full_name }}</span>
|
||||||
<button mat-button *ngIf="hasPerms('addOthers')" (click)="removeUser(candidate)">Remove</button>
|
<button mat-button *ngIf="hasPerms('addOthers')" (click)="removeUser(candidate)">Remove</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import { BehaviorSubject } from 'rxjs';
|
|
||||||
import { Component, OnInit } 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';
|
||||||
@ -6,6 +5,7 @@ import { Router, ActivatedRoute } from '@angular/router';
|
|||||||
import { Title } from '@angular/platform-browser';
|
import { Title } from '@angular/platform-browser';
|
||||||
|
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
import { BehaviorSubject } from 'rxjs';
|
||||||
|
|
||||||
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';
|
||||||
@ -15,7 +15,7 @@ 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';
|
||||||
import { OperatorService } from 'app/core/core-services/operator.service';
|
import { OperatorService } from 'app/core/core-services/operator.service';
|
||||||
import { Poll } from 'app/shared/models/assignments/poll';
|
import { AssignmentPoll } from 'app/shared/models/assignments/assignment-poll';
|
||||||
import { TagRepositoryService } from 'app/core/repositories/tags/tag-repository.service';
|
import { TagRepositoryService } from 'app/core/repositories/tags/tag-repository.service';
|
||||||
import { UserRepositoryService } from 'app/core/repositories/users/user-repository.service';
|
import { UserRepositoryService } from 'app/core/repositories/users/user-repository.service';
|
||||||
import { ViewAssignment, AssignmentPhase } from '../../models/view-assignment';
|
import { ViewAssignment, AssignmentPhase } from '../../models/view-assignment';
|
||||||
@ -23,6 +23,7 @@ import { ViewItem } from 'app/site/agenda/models/view-item';
|
|||||||
import { ViewportService } from 'app/core/ui-services/viewport.service';
|
import { ViewportService } from 'app/core/ui-services/viewport.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 { PromptService } from 'app/core/ui-services/prompt.service';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component for the assignment detail view
|
* Component for the assignment detail view
|
||||||
@ -87,7 +88,7 @@ export class AssignmentDetailComponent extends BaseViewComponent implements OnIn
|
|||||||
*/
|
*/
|
||||||
public set assignment(assignment: ViewAssignment) {
|
public set assignment(assignment: ViewAssignment) {
|
||||||
this._assignment = assignment;
|
this._assignment = assignment;
|
||||||
if (this.assignment.polls && this.assignment.polls.length) {
|
if (this.assignment.polls.length) {
|
||||||
this.assignment.polls.forEach(poll => {
|
this.assignment.polls.forEach(poll => {
|
||||||
poll.pollBase = this.pollService.getBaseAmount(poll);
|
poll.pollBase = this.pollService.getBaseAmount(poll);
|
||||||
});
|
});
|
||||||
@ -148,6 +149,7 @@ export class AssignmentDetailComponent extends BaseViewComponent implements OnIn
|
|||||||
* @param pollService
|
* @param pollService
|
||||||
* @param agendaRepo
|
* @param agendaRepo
|
||||||
* @param tagRepo
|
* @param tagRepo
|
||||||
|
* @param promptService
|
||||||
*/
|
*/
|
||||||
public constructor(
|
public constructor(
|
||||||
title: Title,
|
title: Title,
|
||||||
@ -164,7 +166,8 @@ export class AssignmentDetailComponent extends BaseViewComponent implements OnIn
|
|||||||
private constants: ConstantsService,
|
private constants: ConstantsService,
|
||||||
public pollService: AssignmentPollService,
|
public pollService: AssignmentPollService,
|
||||||
private agendaRepo: ItemRepositoryService,
|
private agendaRepo: ItemRepositoryService,
|
||||||
private tagRepo: TagRepositoryService
|
private tagRepo: TagRepositoryService,
|
||||||
|
private promptService: PromptService
|
||||||
) {
|
) {
|
||||||
super(title, translate, matSnackBar);
|
super(title, translate, matSnackBar);
|
||||||
/* Server side constants for phases */
|
/* Server side constants for phases */
|
||||||
@ -342,7 +345,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);
|
||||||
}
|
}
|
||||||
@ -350,9 +353,14 @@ export class AssignmentDetailComponent extends BaseViewComponent implements OnIn
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Handler for deleting the assignment
|
* Handler for deleting the assignment
|
||||||
* TODO: navigating to assignment overview on delete
|
|
||||||
*/
|
*/
|
||||||
public onDeleteAssignmentButton(): void {}
|
public async onDeleteAssignmentButton(): Promise<void> {
|
||||||
|
const title = this.translate.instant('Are you sure you want to delete this election?');
|
||||||
|
if (await this.promptService.open(title, this.assignment.getTitle())) {
|
||||||
|
await this.repo.delete(this.assignment);
|
||||||
|
this.router.navigate(['../assignments/']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handler for actions to be done on change of displayed poll
|
* Handler for actions to be done on change of displayed poll
|
||||||
@ -420,7 +428,7 @@ export class AssignmentDetailComponent extends BaseViewComponent implements OnIn
|
|||||||
* Assemble a meaningful label for the poll
|
* Assemble a meaningful label for the poll
|
||||||
* TODO (currently e.g. 'Ballot 10 (unublished)')
|
* TODO (currently e.g. 'Ballot 10 (unublished)')
|
||||||
*/
|
*/
|
||||||
public getPollLabel(poll: Poll, index: number): string {
|
public getPollLabel(poll: AssignmentPoll, index: number): string {
|
||||||
const pubState = poll.published ? this.translate.instant('published') : this.translate.instant('unpublished');
|
const pubState = poll.published ? this.translate.instant('published') : this.translate.instant('unpublished');
|
||||||
const title = this.translate.instant('Ballot');
|
const title = this.translate.instant('Ballot');
|
||||||
return `${title} ${index + 1} (${pubState})`;
|
return `${title} ${index + 1} (${pubState})`;
|
||||||
|
@ -38,7 +38,7 @@
|
|||||||
<!-- name column -->
|
<!-- name column -->
|
||||||
<ng-container matColumnDef="title">
|
<ng-container matColumnDef="title">
|
||||||
<mat-header-cell *matHeaderCellDef mat-sort-header>Title</mat-header-cell>
|
<mat-header-cell *matHeaderCellDef mat-sort-header>Title</mat-header-cell>
|
||||||
<mat-cell *matCellDef="let assignment">{{ assignment.getTitle() }}</mat-cell>
|
<mat-cell *matCellDef="let assignment">{{ assignment.getListTitle() }}</mat-cell>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<!-- pahse column-->
|
<!-- pahse column-->
|
||||||
<ng-container matColumnDef="phase">
|
<ng-container matColumnDef="phase">
|
@ -1,7 +1,7 @@
|
|||||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
import { AssignmentListComponent } from './assignment-list.component';
|
import { AssignmentListComponent } from './assignment-list.component';
|
||||||
import { E2EImportsModule } from '../../../../e2e-imports.module';
|
import { E2EImportsModule } from '../../../../../e2e-imports.module';
|
||||||
|
|
||||||
describe('AssignmentListComponent', () => {
|
describe('AssignmentListComponent', () => {
|
||||||
let component: AssignmentListComponent;
|
let component: AssignmentListComponent;
|
@ -1,18 +1,19 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
import { MatSnackBar } from '@angular/material';
|
import { MatSnackBar } from '@angular/material';
|
||||||
import { Router, ActivatedRoute } from '@angular/router';
|
import { Router, ActivatedRoute } from '@angular/router';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
|
||||||
import { Title } from '@angular/platform-browser';
|
import { Title } from '@angular/platform-browser';
|
||||||
|
|
||||||
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
|
||||||
import { Assignment } from 'app/shared/models/assignments/assignment';
|
import { Assignment } from 'app/shared/models/assignments/assignment';
|
||||||
import { AssignmentFilterListService } from '../services/assignment-filter.service';
|
import { AssignmentFilterListService } from '../../services/assignment-filter.service';
|
||||||
import { AssignmentSortListService } from '../services/assignment-sort-list.service';
|
import { AssignmentSortListService } from '../../services/assignment-sort-list.service';
|
||||||
import { AssignmentRepositoryService } from 'app/core/repositories/assignments/assignment-repository.service';
|
import { AssignmentRepositoryService } from 'app/core/repositories/assignments/assignment-repository.service';
|
||||||
import { ListViewBaseComponent } from '../../base/list-view-base';
|
import { ListViewBaseComponent } from 'app/site/base/list-view-base';
|
||||||
import { OperatorService } from 'app/core/core-services/operator.service';
|
import { OperatorService } from 'app/core/core-services/operator.service';
|
||||||
import { PromptService } from 'app/core/ui-services/prompt.service';
|
import { PromptService } from 'app/core/ui-services/prompt.service';
|
||||||
import { StorageService } from 'app/core/core-services/storage.service';
|
import { StorageService } from 'app/core/core-services/storage.service';
|
||||||
import { ViewAssignment } from '../models/view-assignment';
|
import { ViewAssignment } from '../../models/view-assignment';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List view for the assignments
|
* List view for the assignments
|
||||||
@ -97,7 +98,7 @@ export class AssignmentListComponent extends ListViewBaseComponent<ViewAssignmen
|
|||||||
*/
|
*/
|
||||||
public async deleteSelected(): Promise<void> {
|
public async deleteSelected(): Promise<void> {
|
||||||
const title = this.translate.instant('Are you sure you want to delete all selected elections?');
|
const title = this.translate.instant('Are you sure you want to delete all selected elections?');
|
||||||
if (await this.promptService.open(title, null)) {
|
if (await this.promptService.open(title, '')) {
|
||||||
for (const assignment of this.selectedRows) {
|
for (const assignment of this.selectedRows) {
|
||||||
await this.repo.delete(assignment);
|
await this.repo.delete(assignment);
|
||||||
}
|
}
|
@ -4,9 +4,9 @@ import { TranslateService } from '@ngx-translate/core';
|
|||||||
|
|
||||||
import { AssignmentPollService } from '../../services/assignment-poll.service';
|
import { AssignmentPollService } from '../../services/assignment-poll.service';
|
||||||
import { CalculablePollKey, PollVoteValue } from 'app/core/ui-services/poll.service';
|
import { CalculablePollKey, PollVoteValue } from 'app/core/ui-services/poll.service';
|
||||||
import { Poll } from 'app/shared/models/assignments/poll';
|
import { AssignmentPoll } from 'app/shared/models/assignments/assignment-poll';
|
||||||
import { PollOption } from 'app/shared/models/assignments/poll-option';
|
|
||||||
import { ViewUser } from 'app/site/users/models/view-user';
|
import { ViewUser } from 'app/site/users/models/view-user';
|
||||||
|
import { AssignmentPollOption } from 'app/shared/models/assignments/assignment-poll-option';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Vote entries included once for summary (e.g. total votes cast)
|
* Vote entries included once for summary (e.g. total votes cast)
|
||||||
@ -40,7 +40,7 @@ export class AssignmentPollDialogComponent {
|
|||||||
*/
|
*/
|
||||||
public constructor(
|
public constructor(
|
||||||
public dialogRef: MatDialogRef<AssignmentPollDialogComponent>,
|
public dialogRef: MatDialogRef<AssignmentPollDialogComponent>,
|
||||||
@Inject(MAT_DIALOG_DATA) public data: { poll: Poll; users: ViewUser[] },
|
@Inject(MAT_DIALOG_DATA) public data: { poll: AssignmentPoll; users: ViewUser[] },
|
||||||
private matSnackBar: MatSnackBar,
|
private matSnackBar: MatSnackBar,
|
||||||
private translate: TranslateService,
|
private translate: TranslateService,
|
||||||
private pollService: AssignmentPollService
|
private pollService: AssignmentPollService
|
||||||
@ -123,7 +123,7 @@ 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: string): void {
|
public setValue(value: PollVoteValue, candidate: AssignmentPollOption, 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 = parseInt(newData, 10);
|
vote.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 | undefined {
|
public getValue(value: PollVoteValue, candidate: AssignmentPollOption): 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;
|
||||||
}
|
}
|
@ -64,7 +64,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<!-- candidate Name -->
|
<!-- candidate Name -->
|
||||||
<div>
|
<div>
|
||||||
{{ getCandidateName(option) }}
|
{{ option.user.full_name }}
|
||||||
</div>
|
</div>
|
||||||
<!-- Votes -->
|
<!-- Votes -->
|
||||||
<div *ngIf="poll.published && poll.has_votes">
|
<div *ngIf="poll.published && poll.has_votes">
|
||||||
@ -111,6 +111,7 @@
|
|||||||
<span>{{ key | translate }}</span>:
|
<span>{{ key | translate }}</span>:
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
|
{{ poll[key] | precisionPipe }}
|
||||||
{{ pollService.getSpecialLabel(poll[key]) }}
|
{{ pollService.getSpecialLabel(poll[key]) }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -3,17 +3,18 @@ import { MatDialog, MatSnackBar } from '@angular/material';
|
|||||||
|
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
|
||||||
import { AssignmentPollDialogComponent } from './assignment-poll-dialog.component';
|
import { AssignmentPollDialogComponent } from '../assignment-poll-dialog/assignment-poll-dialog.component';
|
||||||
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 { MajorityMethod, CalculablePollKey } from 'app/core/ui-services/poll.service';
|
import { MajorityMethod, CalculablePollKey } from 'app/core/ui-services/poll.service';
|
||||||
import { OperatorService } from 'app/core/core-services/operator.service';
|
import { OperatorService } from 'app/core/core-services/operator.service';
|
||||||
import { Poll } from 'app/shared/models/assignments/poll';
|
import { AssignmentPoll } from 'app/shared/models/assignments/assignment-poll';
|
||||||
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 { BaseViewComponent } from 'app/site/base/base-view';
|
||||||
import { Title } from '@angular/platform-browser';
|
import { Title } from '@angular/platform-browser';
|
||||||
|
import { ViewAssignmentPollOption } from '../../models/view-assignment-poll-option';
|
||||||
|
import { ViewAssignmentPoll } from '../../models/view-assignment-poll';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component for a single assignment poll. Used in assignment detail view
|
* Component for a single assignment poll. Used in assignment detail view
|
||||||
@ -34,7 +35,7 @@ export class AssignmentPollComponent extends BaseViewComponent implements OnInit
|
|||||||
* The poll represented in this component
|
* The poll represented in this component
|
||||||
*/
|
*/
|
||||||
@Input()
|
@Input()
|
||||||
public poll: Poll;
|
public poll: ViewAssignmentPoll;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The selected Majority method to display quorum calculations. Will be
|
* The selected Majority method to display quorum calculations. Will be
|
||||||
@ -135,25 +136,10 @@ export class AssignmentPollComponent extends BaseViewComponent implements OnInit
|
|||||||
*
|
*
|
||||||
* TODO Print the ballots for this poll.
|
* TODO Print the ballots for this poll.
|
||||||
*/
|
*/
|
||||||
public printBallot(poll: Poll): void {
|
public printBallot(poll: AssignmentPoll): void {
|
||||||
this.raiseError('Not yet implemented');
|
this.raiseError('Not yet implemented');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches the name for a candidate from the assignment
|
|
||||||
*
|
|
||||||
* @param option Any poll option
|
|
||||||
* @returns the full_name for the candidate
|
|
||||||
*/
|
|
||||||
public getCandidateName(option: PollOption): string {
|
|
||||||
const user = this.assignment.candidates.find(candidate => candidate.id === option.candidate_id);
|
|
||||||
return user ? user.full_name : '';
|
|
||||||
// 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
|
|
||||||
// TODO is this name always available?
|
|
||||||
// TODO error handling
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determines whether the candidate has reached the majority needed to pass
|
* Determines whether the candidate has reached the majority needed to pass
|
||||||
* the quorum
|
* the quorum
|
||||||
@ -161,7 +147,7 @@ export class AssignmentPollComponent extends BaseViewComponent implements OnInit
|
|||||||
* @param option
|
* @param option
|
||||||
* @returns true if the quorum is successfully met
|
* @returns true if the quorum is successfully met
|
||||||
*/
|
*/
|
||||||
public quorumReached(option: PollOption): boolean {
|
public quorumReached(option: ViewAssignmentPollOption): boolean {
|
||||||
const yesValue = this.poll.pollmethod === 'votes' ? 'Votes' : 'Yes';
|
const yesValue = this.poll.pollmethod === 'votes' ? 'Votes' : 'Yes';
|
||||||
const amount = option.votes.find(v => v.value === yesValue).weight;
|
const amount = option.votes.find(v => v.value === yesValue).weight;
|
||||||
const yesQuorum = this.pollService.yesQuorum(this.majorityChoice, this.poll, option);
|
const yesQuorum = this.pollService.yesQuorum(this.majorityChoice, this.poll, option);
|
||||||
@ -214,17 +200,17 @@ export class AssignmentPollComponent extends BaseViewComponent implements OnInit
|
|||||||
*
|
*
|
||||||
* @param option
|
* @param option
|
||||||
*/
|
*/
|
||||||
public toggleElected(option: PollOption): void {
|
public toggleElected(option: ViewAssignmentPollOption): void {
|
||||||
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 viewAssignmentRelatedUser = this.assignment.assignmentRelatedUsers.find(
|
||||||
user => user.user_id === option.candidate_id
|
user => user.user_id === option.user_id
|
||||||
);
|
);
|
||||||
if (candidate) {
|
if (viewAssignmentRelatedUser) {
|
||||||
this.assignmentRepo.markElected(candidate, this.assignment, !option.is_elected);
|
this.assignmentRepo.markElected(viewAssignmentRelatedUser, this.assignment, !option.is_elected);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,63 @@
|
|||||||
|
import { ViewUser } from 'app/site/users/models/view-user';
|
||||||
|
import { BaseViewModel } from 'app/site/base/base-view-model';
|
||||||
|
import { Updateable } from 'app/site/base/updateable';
|
||||||
|
import { Identifiable } from 'app/shared/models/base/identifiable';
|
||||||
|
import { PollVoteValue } from 'app/core/ui-services/poll.service';
|
||||||
|
import { AssignmentPollOption } from 'app/shared/models/assignments/assignment-poll-option';
|
||||||
|
|
||||||
|
export class ViewAssignmentPollOption implements Identifiable, Updateable {
|
||||||
|
private _assignmentPollOption: AssignmentPollOption;
|
||||||
|
private _user: ViewUser; // This is the "candidate". We'll stay consistent wich user here...
|
||||||
|
|
||||||
|
public get option(): AssignmentPollOption {
|
||||||
|
return this._assignmentPollOption;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Note: "User" instead of "candidate" to be consistent.
|
||||||
|
*/
|
||||||
|
public get user(): ViewUser | null {
|
||||||
|
return this._user;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get id(): number {
|
||||||
|
return this.option.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Note: "User" instead of "candidate" to be consistent.
|
||||||
|
*/
|
||||||
|
public get user_id(): number {
|
||||||
|
return this.option.candidate_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get is_elected(): boolean {
|
||||||
|
return this.option.is_elected;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get votes(): {
|
||||||
|
weight: number;
|
||||||
|
value: PollVoteValue;
|
||||||
|
}[] {
|
||||||
|
return this.option.votes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get poll_id(): number {
|
||||||
|
return this.option.poll_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get weight(): number {
|
||||||
|
return this.option.weight;
|
||||||
|
}
|
||||||
|
|
||||||
|
public constructor(assignmentPollOption: AssignmentPollOption, user?: ViewUser) {
|
||||||
|
this._assignmentPollOption = assignmentPollOption;
|
||||||
|
this._user = user;
|
||||||
|
}
|
||||||
|
|
||||||
|
public updateDependencies(update: BaseViewModel): void {
|
||||||
|
if (update instanceof ViewUser && update.id === this.user_id) {
|
||||||
|
this._user = update;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,71 @@
|
|||||||
|
import { BaseViewModel } from 'app/site/base/base-view-model';
|
||||||
|
import { Updateable } from 'app/site/base/updateable';
|
||||||
|
import { Identifiable } from 'app/shared/models/base/identifiable';
|
||||||
|
import { AssignmentPoll } from 'app/shared/models/assignments/assignment-poll';
|
||||||
|
import { AssignmentPollMethod } from '../services/assignment-poll.service';
|
||||||
|
import { ViewAssignmentPollOption } from './view-assignment-poll-option';
|
||||||
|
|
||||||
|
export class ViewAssignmentPoll implements Identifiable, Updateable {
|
||||||
|
private _assignmentPoll: AssignmentPoll;
|
||||||
|
private _assignmentPollOptions: ViewAssignmentPollOption[];
|
||||||
|
|
||||||
|
public get poll(): AssignmentPoll {
|
||||||
|
return this._assignmentPoll;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get options(): ViewAssignmentPollOption[] {
|
||||||
|
return this._assignmentPollOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get id(): number {
|
||||||
|
return this.poll.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get pollmethod(): AssignmentPollMethod {
|
||||||
|
return this.poll.pollmethod;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get description(): string {
|
||||||
|
return this.poll.description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get published(): boolean {
|
||||||
|
return this.poll.published;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get votesvalid(): number {
|
||||||
|
return this.poll.votesvalid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get votesinvalid(): number {
|
||||||
|
return this.poll.votesinvalid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get votescast(): number {
|
||||||
|
return this.poll.votescast;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get has_votes(): boolean {
|
||||||
|
return this.poll.has_votes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get assignment_id(): number {
|
||||||
|
return this.poll.assignment_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* storing the base values for percentage calculations,
|
||||||
|
* to avoid recalculating pollBases too often
|
||||||
|
* (the calculation iterates through all pollOptions in some use cases)
|
||||||
|
*/
|
||||||
|
public pollBase: number;
|
||||||
|
|
||||||
|
public constructor(assignmentPoll: AssignmentPoll, assignmentPollOptions: ViewAssignmentPollOption[]) {
|
||||||
|
this._assignmentPoll = assignmentPoll;
|
||||||
|
this._assignmentPollOptions = assignmentPollOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public updateDependencies(update: BaseViewModel): void {
|
||||||
|
this.options.forEach(option => option.updateDependencies(update));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,49 @@
|
|||||||
|
import { AssignmentRelatedUser } from 'app/shared/models/assignments/assignment-related-user';
|
||||||
|
import { ViewUser } from 'app/site/users/models/view-user';
|
||||||
|
import { BaseViewModel } from 'app/site/base/base-view-model';
|
||||||
|
import { Updateable } from 'app/site/base/updateable';
|
||||||
|
import { Identifiable } from 'app/shared/models/base/identifiable';
|
||||||
|
|
||||||
|
export class ViewAssignmentRelatedUser implements Updateable, Identifiable {
|
||||||
|
private _assignmentRelatedUser: AssignmentRelatedUser;
|
||||||
|
private _user?: ViewUser;
|
||||||
|
|
||||||
|
public get assignmentRelatedUser(): AssignmentRelatedUser {
|
||||||
|
return this._assignmentRelatedUser;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get user(): ViewUser {
|
||||||
|
return this._user;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get id(): number {
|
||||||
|
return this.assignmentRelatedUser.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get user_id(): number {
|
||||||
|
return this.assignmentRelatedUser.user_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get assignment_id(): number {
|
||||||
|
return this.assignmentRelatedUser.assignment_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get elected(): boolean {
|
||||||
|
return this.assignmentRelatedUser.elected;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get weight(): number {
|
||||||
|
return this.assignmentRelatedUser.weight;
|
||||||
|
}
|
||||||
|
|
||||||
|
public constructor(assignmentRelatedUser: AssignmentRelatedUser, user?: ViewUser) {
|
||||||
|
this._assignmentRelatedUser = assignmentRelatedUser;
|
||||||
|
this._user = user;
|
||||||
|
}
|
||||||
|
|
||||||
|
public updateDependencies(update: BaseViewModel): void {
|
||||||
|
if (update instanceof ViewUser && update.id === this.user_id) {
|
||||||
|
this._user = update;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -6,7 +6,8 @@ import { ViewUser } from 'app/site/users/models/view-user';
|
|||||||
import { ViewItem } from 'app/site/agenda/models/view-item';
|
import { ViewItem } from 'app/site/agenda/models/view-item';
|
||||||
import { ViewTag } from 'app/site/tags/models/view-tag';
|
import { ViewTag } from 'app/site/tags/models/view-tag';
|
||||||
import { BaseViewModel } from 'app/site/base/base-view-model';
|
import { BaseViewModel } from 'app/site/base/base-view-model';
|
||||||
import { Poll } from 'app/shared/models/assignments/poll';
|
import { ViewAssignmentRelatedUser } from './view-assignment-related-user';
|
||||||
|
import { ViewAssignmentPoll } from './view-assignment-poll';
|
||||||
|
|
||||||
export interface AssignmentPhase {
|
export interface AssignmentPhase {
|
||||||
value: number;
|
value: number;
|
||||||
@ -17,9 +18,10 @@ export class ViewAssignment extends BaseAgendaViewModel {
|
|||||||
public static COLLECTIONSTRING = Assignment.COLLECTIONSTRING;
|
public static COLLECTIONSTRING = Assignment.COLLECTIONSTRING;
|
||||||
|
|
||||||
private _assignment: Assignment;
|
private _assignment: Assignment;
|
||||||
private _relatedUser: ViewUser[];
|
private _assignmentRelatedUsers: ViewAssignmentRelatedUser[];
|
||||||
private _agendaItem: ViewItem;
|
private _assignmentPolls: ViewAssignmentPoll[];
|
||||||
private _tags: ViewTag[];
|
private _agendaItem?: ViewItem;
|
||||||
|
private _tags?: ViewTag[];
|
||||||
|
|
||||||
public get id(): number {
|
public get id(): number {
|
||||||
return this._assignment ? this._assignment.id : null;
|
return this._assignment ? this._assignment.id : null;
|
||||||
@ -29,35 +31,39 @@ export class ViewAssignment extends BaseAgendaViewModel {
|
|||||||
return this._assignment;
|
return this._assignment;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public get polls(): ViewAssignmentPoll[] {
|
||||||
|
return this._assignmentPolls;
|
||||||
|
}
|
||||||
|
|
||||||
public get title(): string {
|
public get title(): string {
|
||||||
return this.assignment.title;
|
return this.assignment.title;
|
||||||
}
|
}
|
||||||
|
|
||||||
public get candidates(): ViewUser[] {
|
public get candidates(): ViewUser[] {
|
||||||
return this._relatedUser;
|
return this._assignmentRelatedUsers.map(aru => aru.user);
|
||||||
}
|
}
|
||||||
|
|
||||||
public get agendaItem(): ViewItem {
|
public get assignmentRelatedUsers(): ViewAssignmentRelatedUser[] {
|
||||||
|
return this._assignmentRelatedUsers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get agendaItem(): ViewItem | null {
|
||||||
return this._agendaItem;
|
return this._agendaItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
public get tags(): ViewTag[] {
|
public get tags(): ViewTag[] {
|
||||||
return this._tags;
|
return this._tags || [];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* unknown where the identifier to the phase is get
|
* unknown where the identifier to the phase is get
|
||||||
*/
|
*/
|
||||||
public get phase(): number {
|
public get phase(): number {
|
||||||
return this.assignment ? this.assignment.phase : null;
|
return this.assignment.phase;
|
||||||
}
|
}
|
||||||
|
|
||||||
public get candidateAmount(): number {
|
public get candidateAmount(): number {
|
||||||
return this.candidates ? this.candidates.length : 0;
|
return this._assignmentRelatedUsers ? this._assignmentRelatedUsers.length : 0;
|
||||||
}
|
|
||||||
|
|
||||||
public get polls(): Poll[] {
|
|
||||||
return this.assignment ? this.assignment.polls : []; // TODO check
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -67,18 +73,37 @@ export class ViewAssignment extends BaseAgendaViewModel {
|
|||||||
public getAgendaTitle;
|
public getAgendaTitle;
|
||||||
public getAgendaTitleWithType;
|
public getAgendaTitleWithType;
|
||||||
|
|
||||||
public constructor(assignment: Assignment, relatedUser?: ViewUser[], agendaItem?: ViewItem, tags?: ViewTag[]) {
|
public constructor(
|
||||||
|
assignment: Assignment,
|
||||||
|
assignmentRelatedUsers: ViewAssignmentRelatedUser[],
|
||||||
|
assignmentPolls: ViewAssignmentPoll[],
|
||||||
|
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._assignmentRelatedUsers = assignmentRelatedUsers;
|
||||||
|
this._assignmentPolls = assignmentPolls;
|
||||||
this._agendaItem = agendaItem;
|
this._agendaItem = agendaItem;
|
||||||
this._tags = tags;
|
this._tags = tags;
|
||||||
}
|
}
|
||||||
|
|
||||||
public updateDependencies(update: BaseViewModel): void {}
|
public updateDependencies(update: BaseViewModel): void {
|
||||||
|
if (update instanceof ViewItem && update.id === this.assignment.agenda_item_id) {
|
||||||
|
this._agendaItem = update;
|
||||||
|
} else if (update instanceof ViewTag && this.assignment.tags_id.includes(update.id)) {
|
||||||
|
const tagIndex = this._tags.findIndex(_tag => _tag.id === update.id);
|
||||||
|
if (tagIndex < 0) {
|
||||||
|
this._tags.push(update);
|
||||||
|
} else {
|
||||||
|
this._tags[tagIndex] = update;
|
||||||
|
}
|
||||||
|
} else if (update instanceof ViewUser) {
|
||||||
|
this.assignmentRelatedUsers.forEach(aru => aru.updateDependencies(update));
|
||||||
|
this.polls.forEach(poll => poll.updateDependencies(update));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public getAgendaItem(): ViewItem {
|
public getAgendaItem(): ViewItem {
|
||||||
return this.agendaItem;
|
return this.agendaItem;
|
||||||
|
@ -8,8 +8,8 @@ import {
|
|||||||
CalculablePollKey,
|
CalculablePollKey,
|
||||||
PollVoteValue
|
PollVoteValue
|
||||||
} from 'app/core/ui-services/poll.service';
|
} from 'app/core/ui-services/poll.service';
|
||||||
import { Poll } from 'app/shared/models/assignments/poll';
|
import { ViewAssignmentPollOption } from '../models/view-assignment-poll-option';
|
||||||
import { PollOption } from 'app/shared/models/assignments/poll-option';
|
import { ViewAssignmentPoll } from '../models/view-assignment-poll';
|
||||||
|
|
||||||
type AssignmentPollValues = 'auto' | 'votes' | 'yesnoabstain' | 'yesno';
|
type AssignmentPollValues = 'auto' | 'votes' | 'yesnoabstain' | 'yesno';
|
||||||
export type AssignmentPollMethod = 'yn' | 'yna' | 'votes';
|
export type AssignmentPollMethod = 'yn' | 'yna' | 'votes';
|
||||||
@ -69,15 +69,15 @@ export class AssignmentPollService extends PollService {
|
|||||||
* @param poll
|
* @param poll
|
||||||
* @returns The amount of votes indicating the 100% base
|
* @returns The amount of votes indicating the 100% base
|
||||||
*/
|
*/
|
||||||
public getBaseAmount(poll: Poll): number | null {
|
public getBaseAmount(poll: ViewAssignmentPoll): number | null {
|
||||||
switch (this.percentBase) {
|
switch (this.percentBase) {
|
||||||
case 'DISABLED':
|
case 'DISABLED':
|
||||||
return null;
|
return null;
|
||||||
case 'YES_NO':
|
case 'YES_NO':
|
||||||
case 'YES_NO_ABSTAIN':
|
case 'YES_NO_ABSTAIN':
|
||||||
if (poll.pollmethod === 'votes') {
|
if (poll.pollmethod === 'votes') {
|
||||||
const yes = poll.options.map(cand => {
|
const yes = poll.options.map(option => {
|
||||||
const yesValue = cand.votes.find(v => v.value === 'Yes');
|
const yesValue = option.votes.find(v => v.value === 'Yes');
|
||||||
return yesValue ? yesValue.weight : -99;
|
return yesValue ? yesValue.weight : -99;
|
||||||
});
|
});
|
||||||
if (Math.min(...yes) < 0) {
|
if (Math.min(...yes) < 0) {
|
||||||
@ -105,7 +105,7 @@ export class AssignmentPollService extends PollService {
|
|||||||
* @param value
|
* @param value
|
||||||
* @returns a percentage number with two digits, null if the value cannot be calculated
|
* @returns a percentage number with two digits, null if the value cannot be calculated
|
||||||
*/
|
*/
|
||||||
public getPercent(poll: Poll, option: PollOption, value: PollVoteValue): number | null {
|
public getPercent(poll: ViewAssignmentPoll, option: ViewAssignmentPollOption, value: PollVoteValue): number | null {
|
||||||
const base = poll.pollmethod === 'votes' ? poll.pollBase : this.getOptionBaseAmount(poll, option);
|
const base = poll.pollmethod === 'votes' ? poll.pollBase : this.getOptionBaseAmount(poll, option);
|
||||||
if (!base) {
|
if (!base) {
|
||||||
return null;
|
return null;
|
||||||
@ -125,7 +125,7 @@ export class AssignmentPollService extends PollService {
|
|||||||
* @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
|
||||||
*/
|
*/
|
||||||
public isAbstractOption(poll: Poll, option: PollOption): boolean {
|
public isAbstractOption(poll: ViewAssignmentPoll, option: ViewAssignmentPollOption): boolean {
|
||||||
if (!option.votes || !option.votes.length) {
|
if (!option.votes || !option.votes.length) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -146,7 +146,7 @@ export class AssignmentPollService extends PollService {
|
|||||||
* TODO: Yes, No, etc. in an option will always return true.
|
* TODO: Yes, No, etc. in an option will always return true.
|
||||||
* Use {@link isAbstractOption} for these
|
* Use {@link isAbstractOption} for these
|
||||||
*/
|
*/
|
||||||
public isAbstractValue(poll: Poll, value: CalculablePollKey): boolean {
|
public isAbstractValue(poll: ViewAssignmentPoll, value: CalculablePollKey): boolean {
|
||||||
if (!poll.pollBase || !this.pollValues.includes(value)) {
|
if (!poll.pollBase || !this.pollValues.includes(value)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -163,7 +163,7 @@ export class AssignmentPollService extends PollService {
|
|||||||
*
|
*
|
||||||
* @returns an positive integer to be used as percentage base, or null
|
* @returns an positive integer to be used as percentage base, or null
|
||||||
*/
|
*/
|
||||||
private getOptionBaseAmount(poll: Poll, option: PollOption): number | null {
|
private getOptionBaseAmount(poll: ViewAssignmentPoll, option: ViewAssignmentPollOption): number | null {
|
||||||
if (poll.pollmethod === 'votes') {
|
if (poll.pollmethod === 'votes') {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -193,7 +193,11 @@ export class AssignmentPollService extends PollService {
|
|||||||
* @param option
|
* @param option
|
||||||
* @returns a positive integer number; may return null if quorum is not calculable
|
* @returns a positive integer number; may return null if quorum is not calculable
|
||||||
*/
|
*/
|
||||||
public yesQuorum(method: MajorityMethod, poll: Poll, option: PollOption): number | null {
|
public yesQuorum(
|
||||||
|
method: MajorityMethod,
|
||||||
|
poll: ViewAssignmentPoll,
|
||||||
|
option: ViewAssignmentPollOption
|
||||||
|
): number | null {
|
||||||
const baseAmount = poll.pollmethod === 'votes' ? poll.pollBase : this.getOptionBaseAmount(poll, option);
|
const baseAmount = poll.pollmethod === 'votes' ? poll.pollBase : this.getOptionBaseAmount(poll, option);
|
||||||
return method.calc(baseAmount);
|
return method.calc(baseAmount);
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ import { Displayable } from './displayable';
|
|||||||
import { Identifiable } from '../../shared/models/base/identifiable';
|
import { Identifiable } from '../../shared/models/base/identifiable';
|
||||||
import { Collection } from 'app/shared/models/base/collection';
|
import { Collection } from 'app/shared/models/base/collection';
|
||||||
import { BaseModel } from 'app/shared/models/base/base-model';
|
import { BaseModel } from 'app/shared/models/base/base-model';
|
||||||
|
import { Updateable } from './updateable';
|
||||||
|
|
||||||
export interface ViewModelConstructor<T extends BaseViewModel> {
|
export interface ViewModelConstructor<T extends BaseViewModel> {
|
||||||
COLLECTIONSTRING: string;
|
COLLECTIONSTRING: string;
|
||||||
@ -11,7 +12,7 @@ export interface ViewModelConstructor<T extends BaseViewModel> {
|
|||||||
/**
|
/**
|
||||||
* Base class for view models. alls view models should have titles.
|
* Base class for view models. alls view models should have titles.
|
||||||
*/
|
*/
|
||||||
export abstract class BaseViewModel implements Displayable, Identifiable, Collection {
|
export abstract class BaseViewModel implements Displayable, Identifiable, Collection, Updateable {
|
||||||
/**
|
/**
|
||||||
* Force children to have an id.
|
* Force children to have an id.
|
||||||
*/
|
*/
|
||||||
|
5
client/src/app/site/base/updateable.ts
Normal file
5
client/src/app/site/base/updateable.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import { BaseViewModel } from './base-view-model';
|
||||||
|
|
||||||
|
export interface Updateable {
|
||||||
|
updateDependencies(update: BaseViewModel): void;
|
||||||
|
}
|
@ -707,13 +707,13 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit,
|
|||||||
this.updateWorkflowIdForCreateForm();
|
this.updateWorkflowIdForCreateForm();
|
||||||
|
|
||||||
const component = this;
|
const component = this;
|
||||||
this.highlightedLineMatcher = new class implements ErrorStateMatcher {
|
this.highlightedLineMatcher = new (class implements ErrorStateMatcher {
|
||||||
public isErrorState(control: FormControl): boolean {
|
public isErrorState(control: FormControl): boolean {
|
||||||
const value: string = control && control.value ? control.value + '' : '';
|
const value: string = control && control.value ? control.value + '' : '';
|
||||||
const maxLineNumber = component.repo.getLastLineNumber(component.motion, component.lineLength);
|
const maxLineNumber = component.repo.getLastLineNumber(component.motion, component.lineLength);
|
||||||
return value.match(/[^\d]/) !== null || parseInt(value, 10) >= maxLineNumber;
|
return value.match(/[^\d]/) !== null || parseInt(value, 10) >= maxLineNumber;
|
||||||
}
|
}
|
||||||
}();
|
})();
|
||||||
|
|
||||||
// create the search motion form
|
// create the search motion form
|
||||||
this.recommendationExtensionForm = this.formBuilder.group({
|
this.recommendationExtensionForm = this.formBuilder.group({
|
||||||
|
Loading…
Reference in New Issue
Block a user