More voting UI improvements
For Motion poll: - Overworked how motion poll chart displays the legend - Added the vote counter to the motion detail - Added a progress bar to the vote counter - Fixed some perm errors with the chart - Show a "Singe Votes" link as button for published named polls - Replace the edit-button with a dot-menu - Having project, Edit, PDF and Delete For Motion Poll detail: - enhance search panel - Remove the breadcrumbs - Remove the vote counter - Enhanced the single-vote grid, table and filter bar - Enhance how the poll state enum was checkend For the Motion Poll Create/Update Form: - Remove the selection of poll-methode (whenever possible) - only show "publish imediately" during creation
This commit is contained in:
parent
682db96b7c
commit
84a39ccb62
@ -95,7 +95,6 @@ export class AppComponent {
|
|||||||
translate.use(translate.getLangs().includes(browserLang) ? browserLang : 'en');
|
translate.use(translate.getLangs().includes(browserLang) ? browserLang : 'en');
|
||||||
|
|
||||||
// change default JS functions
|
// change default JS functions
|
||||||
this.overloadArrayToString();
|
|
||||||
this.overloadArrayFunctions();
|
this.overloadArrayFunctions();
|
||||||
this.overloadModulo();
|
this.overloadModulo();
|
||||||
|
|
||||||
@ -118,7 +117,7 @@ export class AppComponent {
|
|||||||
*
|
*
|
||||||
* TODO: Should be renamed
|
* TODO: Should be renamed
|
||||||
*/
|
*/
|
||||||
private overloadArrayToString(): void {
|
private overloadArrayFunctions(): void {
|
||||||
Object.defineProperty(Array.prototype, 'toString', {
|
Object.defineProperty(Array.prototype, 'toString', {
|
||||||
value: function(): string {
|
value: function(): string {
|
||||||
let string = '';
|
let string = '';
|
||||||
@ -139,12 +138,7 @@ export class AppComponent {
|
|||||||
},
|
},
|
||||||
enumerable: false
|
enumerable: false
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds an implementation of flatMap and intersect.
|
|
||||||
*/
|
|
||||||
private overloadFlatMap(): void {
|
|
||||||
Object.defineProperty(Array.prototype, 'flatMap', {
|
Object.defineProperty(Array.prototype, 'flatMap', {
|
||||||
value: function(o: any): any[] {
|
value: function(o: any): any[] {
|
||||||
const concatFunction = (x: any, y: any[]) => x.concat(y);
|
const concatFunction = (x: any, y: any[]) => x.concat(y);
|
||||||
@ -154,10 +148,11 @@ export class AppComponent {
|
|||||||
enumerable: false
|
enumerable: false
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// intersect
|
||||||
Object.defineProperty(Array.prototype, 'intersect', {
|
Object.defineProperty(Array.prototype, 'intersect', {
|
||||||
value: function<T>(other: T[]): T[] {
|
value: function<T>(other: T[]): T[] {
|
||||||
let a = this,
|
let a = this;
|
||||||
b = other;
|
let b = other;
|
||||||
// indexOf to loop over shorter
|
// indexOf to loop over shorter
|
||||||
if (b.length > a.length) {
|
if (b.length > a.length) {
|
||||||
[a, b] = [b, a];
|
[a, b] = [b, a];
|
||||||
@ -167,6 +162,7 @@ export class AppComponent {
|
|||||||
enumerable: false
|
enumerable: false
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// mapToObject
|
||||||
Object.defineProperty(Array.prototype, 'mapToObject', {
|
Object.defineProperty(Array.prototype, 'mapToObject', {
|
||||||
value: function<T>(f: (item: T) => { [key: string]: any }): { [key: string]: any } {
|
value: function<T>(f: (item: T) => { [key: string]: any }): { [key: string]: any } {
|
||||||
return this.reduce((aggr, item) => {
|
return this.reduce((aggr, item) => {
|
||||||
|
@ -71,6 +71,7 @@ export abstract class PollPdfService {
|
|||||||
* @returns the amount of ballots, depending on the config settings
|
* @returns the amount of ballots, depending on the config settings
|
||||||
*/
|
*/
|
||||||
protected getBallotCount(): number {
|
protected getBallotCount(): number {
|
||||||
|
// TODO: seems to be broken
|
||||||
switch (this.ballotCountSelection) {
|
switch (this.ballotCountSelection) {
|
||||||
case 'NUMBER_OF_ALL_PARTICIPANTS':
|
case 'NUMBER_OF_ALL_PARTICIPANTS':
|
||||||
return this.userRepo.getViewModelList().length;
|
return this.userRepo.getViewModelList().length;
|
||||||
|
@ -52,6 +52,22 @@ export abstract class BasePoll<T = any, O extends BaseOption<any> = any> extends
|
|||||||
public onehundred_percent_base: PercentBase;
|
public onehundred_percent_base: PercentBase;
|
||||||
public user_has_voted: boolean;
|
public user_has_voted: boolean;
|
||||||
|
|
||||||
|
public get isStateCreated(): boolean {
|
||||||
|
return this.state === PollState.Created;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get isStateStarted(): boolean {
|
||||||
|
return this.state === PollState.Started;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get isStateFinished(): boolean {
|
||||||
|
return this.state === PollState.Finished;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get isStatePublished(): boolean {
|
||||||
|
return this.state === PollState.Published;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine if the state is finished or published
|
* Determine if the state is finished or published
|
||||||
*/
|
*/
|
||||||
|
@ -9,9 +9,9 @@ export const VoteValueVerbose = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const GeneralValueVerbose = {
|
export const GeneralValueVerbose = {
|
||||||
votesvalid: 'Votes valid',
|
votesvalid: 'Valid votes',
|
||||||
votesinvalid: 'Votes invalid',
|
votesinvalid: 'Invalid votes',
|
||||||
votescast: 'Votes cast',
|
votescast: 'Total votes cast',
|
||||||
votesno: 'Votes No',
|
votesno: 'Votes No',
|
||||||
votesabstain: 'Votes abstain'
|
votesabstain: 'Votes abstain'
|
||||||
};
|
};
|
||||||
|
20
client/src/app/shared/pipes/parse-poll-number.pipe.spec.ts
Normal file
20
client/src/app/shared/pipes/parse-poll-number.pipe.spec.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { inject, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { TranslateModule, TranslateService } from '@ngx-translate/core';
|
||||||
|
|
||||||
|
import { ParsePollNumberPipe } from './parse-poll-number.pipe';
|
||||||
|
|
||||||
|
describe('ParsePollNumberPipe', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [TranslateModule.forRoot()],
|
||||||
|
declarations: [ParsePollNumberPipe]
|
||||||
|
});
|
||||||
|
TestBed.compileComponents();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('create an instance', inject([TranslateService], (translate: TranslateService) => {
|
||||||
|
const pipe = new ParsePollNumberPipe(translate);
|
||||||
|
expect(pipe).toBeTruthy();
|
||||||
|
}));
|
||||||
|
});
|
22
client/src/app/shared/pipes/parse-poll-number.pipe.ts
Normal file
22
client/src/app/shared/pipes/parse-poll-number.pipe.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { Pipe, PipeTransform } from '@angular/core';
|
||||||
|
|
||||||
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
|
||||||
|
@Pipe({
|
||||||
|
name: 'parsePollNumber'
|
||||||
|
})
|
||||||
|
export class ParsePollNumberPipe implements PipeTransform {
|
||||||
|
public constructor(private translate: TranslateService) {}
|
||||||
|
|
||||||
|
public transform(value: number): number | string {
|
||||||
|
const input = Math.trunc(value);
|
||||||
|
switch (input) {
|
||||||
|
case -1:
|
||||||
|
return this.translate.instant('majority');
|
||||||
|
case -2:
|
||||||
|
return this.translate.instant('undocumented');
|
||||||
|
default:
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
8
client/src/app/shared/pipes/reverse.pipe.spec.ts
Normal file
8
client/src/app/shared/pipes/reverse.pipe.spec.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { ReversePipe } from './reverse.pipe';
|
||||||
|
|
||||||
|
describe('ReversePipe', () => {
|
||||||
|
it('create an instance', () => {
|
||||||
|
const pipe = new ReversePipe();
|
||||||
|
expect(pipe).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
20
client/src/app/shared/pipes/reverse.pipe.ts
Normal file
20
client/src/app/shared/pipes/reverse.pipe.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { Pipe, PipeTransform } from '@angular/core';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invert the order of arrays in templates
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```html
|
||||||
|
* <li *ngFor="let user of users | reverse">
|
||||||
|
* {{ user.name }} has the id: {{ user.id }}
|
||||||
|
* </li>
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
@Pipe({
|
||||||
|
name: 'reverse'
|
||||||
|
})
|
||||||
|
export class ReversePipe implements PipeTransform {
|
||||||
|
public transform(value: any[]): any[] {
|
||||||
|
return value.slice().reverse();
|
||||||
|
}
|
||||||
|
}
|
@ -119,6 +119,8 @@ import { BasePollDialogComponent } from 'app/site/polls/components/base-poll-dia
|
|||||||
import { PollFormComponent } from 'app/site/polls/components/poll-form/poll-form.component';
|
import { PollFormComponent } from 'app/site/polls/components/poll-form/poll-form.component';
|
||||||
import { MotionPollDialogComponent } from 'app/site/motions/modules/motion-poll/motion-poll-dialog/motion-poll-dialog.component';
|
import { MotionPollDialogComponent } from 'app/site/motions/modules/motion-poll/motion-poll-dialog/motion-poll-dialog.component';
|
||||||
import { AssignmentPollDialogComponent } from 'app/site/assignments/components/assignment-poll-dialog/assignment-poll-dialog.component';
|
import { AssignmentPollDialogComponent } from 'app/site/assignments/components/assignment-poll-dialog/assignment-poll-dialog.component';
|
||||||
|
import { ParsePollNumberPipe } from './pipes/parse-poll-number.pipe';
|
||||||
|
import { ReversePipe } from './pipes/reverse.pipe';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Share Module for all "dumb" components and pipes.
|
* Share Module for all "dumb" components and pipes.
|
||||||
@ -279,7 +281,9 @@ import { AssignmentPollDialogComponent } from 'app/site/assignments/components/a
|
|||||||
BannerComponent,
|
BannerComponent,
|
||||||
PollFormComponent,
|
PollFormComponent,
|
||||||
MotionPollDialogComponent,
|
MotionPollDialogComponent,
|
||||||
AssignmentPollDialogComponent
|
AssignmentPollDialogComponent,
|
||||||
|
ParsePollNumberPipe,
|
||||||
|
ReversePipe
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
PermsDirective,
|
PermsDirective,
|
||||||
@ -333,7 +337,9 @@ import { AssignmentPollDialogComponent } from 'app/site/assignments/components/a
|
|||||||
BannerComponent,
|
BannerComponent,
|
||||||
PollFormComponent,
|
PollFormComponent,
|
||||||
MotionPollDialogComponent,
|
MotionPollDialogComponent,
|
||||||
AssignmentPollDialogComponent
|
AssignmentPollDialogComponent,
|
||||||
|
ParsePollNumberPipe,
|
||||||
|
ReversePipe
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
{
|
{
|
||||||
@ -349,7 +355,9 @@ import { AssignmentPollDialogComponent } from 'app/site/assignments/components/a
|
|||||||
DecimalPipe,
|
DecimalPipe,
|
||||||
ProgressSnackBarComponent,
|
ProgressSnackBarComponent,
|
||||||
TrustPipe,
|
TrustPipe,
|
||||||
LocalizedDatePipe
|
LocalizedDatePipe,
|
||||||
|
ParsePollNumberPipe,
|
||||||
|
ReversePipe
|
||||||
],
|
],
|
||||||
entryComponents: [
|
entryComponents: [
|
||||||
SortBottomSheetComponent,
|
SortBottomSheetComponent,
|
||||||
|
@ -65,20 +65,12 @@
|
|||||||
<div *ngIf="!editAssignment">
|
<div *ngIf="!editAssignment">
|
||||||
<!-- assignment meta infos-->
|
<!-- assignment meta infos-->
|
||||||
<ng-container [ngTemplateOutlet]="metaInfoTemplate"></ng-container>
|
<ng-container [ngTemplateOutlet]="metaInfoTemplate"></ng-container>
|
||||||
<!-- votable polls -->
|
|
||||||
<ng-container *ngIf="assignment">
|
|
||||||
<ng-container *ngFor="let poll of assignment.polls; trackBy: trackByIndex">
|
|
||||||
<mat-card class="os-card" *ngIf="poll.canBeVotedFor">
|
|
||||||
<os-assignment-poll [poll]="poll"> </os-assignment-poll>
|
|
||||||
</mat-card>
|
|
||||||
</ng-container>
|
|
||||||
</ng-container>
|
|
||||||
<!-- candidates list -->
|
<!-- candidates list -->
|
||||||
<ng-container [ngTemplateOutlet]="candidatesTemplate"></ng-container>
|
<ng-container [ngTemplateOutlet]="candidatesTemplate"></ng-container>
|
||||||
<!-- closed polls -->
|
<!-- closed polls -->
|
||||||
<ng-container *ngIf="assignment">
|
<ng-container *ngIf="assignment">
|
||||||
<ng-container *ngFor="let poll of assignment.polls; trackBy: trackByIndex">
|
<ng-container *ngFor="let poll of assignment.polls | reverse; trackBy: trackByIndex">
|
||||||
<mat-card class="os-card" *ngIf="!poll.canBeVotedFor">
|
<mat-card class="os-card">
|
||||||
<os-assignment-poll [poll]="poll"> </os-assignment-poll>
|
<os-assignment-poll [poll]="poll"> </os-assignment-poll>
|
||||||
</mat-card>
|
</mat-card>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
@ -135,13 +127,17 @@
|
|||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
||||||
<ng-template #candidatesTemplate>
|
<ng-template #candidatesTemplate>
|
||||||
<mat-card class="os-card">
|
<mat-card class="os-card" *ngIf="assignment && !assignment.isFinished">
|
||||||
<ng-container *ngIf="assignment && !assignment.isFinished">
|
<ng-container>
|
||||||
<h3 translate>Candidates</h3>
|
<h3 translate>Candidates</h3>
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
class="candidates-list"
|
class="candidates-list"
|
||||||
*ngIf="assignment && assignment.assignment_related_users && assignment.assignment_related_users.length > 0"
|
*ngIf="
|
||||||
|
assignment &&
|
||||||
|
assignment.assignment_related_users &&
|
||||||
|
assignment.assignment_related_users.length > 0
|
||||||
|
"
|
||||||
>
|
>
|
||||||
<os-sorting-list
|
<os-sorting-list
|
||||||
[input]="assignment.assignment_related_users"
|
[input]="assignment.assignment_related_users"
|
||||||
|
@ -33,16 +33,18 @@
|
|||||||
{{ 'Groups' | translate }}:
|
{{ 'Groups' | translate }}:
|
||||||
<span *ngFor="let group of poll.groups">{{ group.getTitle() | translate }}</span>
|
<span *ngFor="let group of poll.groups">{{ group.getTitle() | translate }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div>{{ 'Poll type' | translate }}: {{ poll.typeVerbose | translate }}</div>
|
<div>{{ 'Voting type' | translate }}: {{ poll.typeVerbose | translate }}</div>
|
||||||
<div>{{ 'Poll method' | translate }}: {{ poll.pollmethodVerbose | translate }}</div>
|
<div>{{ 'Election method' | translate }}: {{ poll.pollmethodVerbose | translate }}</div>
|
||||||
<div>{{ 'Majority method' | translate }}: {{ poll.majorityMethodVerbose | translate }}</div>
|
<div>{{ 'Required majority' | translate }}: {{ poll.majorityMethodVerbose | translate }}</div>
|
||||||
<div>{{ '100% base' | translate }}: {{ poll.percentBaseVerbose | translate }}</div>
|
<div>{{ '100% base' | translate }}: {{ poll.percentBaseVerbose | translate }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- TODO Enum -->
|
||||||
<div *ngIf="poll.state === 2">
|
<div *ngIf="poll.state === 2">
|
||||||
<os-poll-progress [poll]="poll"></os-poll-progress>
|
<os-poll-progress [poll]="poll"></os-poll-progress>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div *ngIf="poll.state === 3 || poll.state === 4">
|
<div *ngIf="poll.stateHasVotes">
|
||||||
<h2 translate>Result</h2>
|
<h2 translate>Result</h2>
|
||||||
|
|
||||||
<div class="chart-wrapper"></div>
|
<div class="chart-wrapper"></div>
|
||||||
|
@ -8,14 +8,14 @@
|
|||||||
<span *ngIf="option.user">{{ option.user.getFullName() }}</span>
|
<span *ngIf="option.user">{{ option.user.getFullName() }}</span>
|
||||||
<span *ngIf="!option.user">No user {{ option.candidate_id }}</span>
|
<span *ngIf="!option.user">No user {{ option.candidate_id }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<div *ngFor="let value of analogPollValues" [formGroupName]="option.user_id">
|
<div *ngFor="let value of analogPollValues" [formGroupName]="option.user_id">
|
||||||
<os-check-input
|
<os-check-input
|
||||||
[placeholder]="voteValueVerbose[value] | translate"
|
[placeholder]="voteValueVerbose[value] | translate"
|
||||||
[checkboxValue]="-1"
|
[checkboxValue]="-1"
|
||||||
inputType="number"
|
inputType="number"
|
||||||
[checkboxLabel]="'Majority' | translate"
|
[checkboxLabel]="'majority' | translate"
|
||||||
[formControlName]="value"
|
[formControlName]="value"
|
||||||
></os-check-input>
|
></os-check-input>
|
||||||
</div>
|
</div>
|
||||||
@ -28,7 +28,7 @@
|
|||||||
[placeholder]="generalValueVerbose[value] | translate"
|
[placeholder]="generalValueVerbose[value] | translate"
|
||||||
[checkboxValue]="-1"
|
[checkboxValue]="-1"
|
||||||
inputType="number"
|
inputType="number"
|
||||||
[checkboxLabel]="'Majority' | translate"
|
[checkboxLabel]="'majority' | translate"
|
||||||
[formControlName]="value"
|
[formControlName]="value"
|
||||||
></os-check-input>
|
></os-check-input>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,4 +1,10 @@
|
|||||||
<div class="assignment-poll-wrapper" *ngIf="poll">
|
<div class="assignment-poll-wrapper" *ngIf="poll">
|
||||||
|
|
||||||
|
<div class="">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div class="poll-menu">
|
<div class="poll-menu">
|
||||||
<!-- Buttons -->
|
<!-- Buttons -->
|
||||||
<button
|
<button
|
||||||
@ -9,30 +15,16 @@
|
|||||||
>
|
>
|
||||||
<mat-icon>more_horiz</mat-icon>
|
<mat-icon>more_horiz</mat-icon>
|
||||||
</button>
|
</button>
|
||||||
<mat-menu #pollItemMenu="matMenu">
|
|
||||||
<div *osPerms="'assignments.can_manage'">
|
|
||||||
<button mat-menu-item (click)="openDialog()">
|
|
||||||
<mat-icon>edit</mat-icon>
|
|
||||||
<span translate>Edit</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div *osPerms="'core.can_manage_projector'">
|
|
||||||
<os-projector-button menuItem="true" [object]="poll"></os-projector-button>
|
|
||||||
</div>
|
|
||||||
<div *osPerms="'assignments.can_manage'">
|
|
||||||
<mat-divider></mat-divider>
|
|
||||||
<button mat-menu-item class="red-warning-text" (click)="onDeletePoll()">
|
|
||||||
<mat-icon>delete</mat-icon>
|
|
||||||
<span translate>Delete</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</mat-menu>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<div>
|
<div>
|
||||||
|
<h3>
|
||||||
|
<a routerLink="/assignments/polls/{{ poll.id }}">
|
||||||
|
{{ poll.title }}
|
||||||
|
</a>
|
||||||
|
</h3>
|
||||||
<div class="poll-properties">
|
<div class="poll-properties">
|
||||||
<!-- <mat-chip *ngIf="pollService.isElectronicVotingEnabled">{{ poll.typeVerbose }}</mat-chip> -->
|
|
||||||
<mat-chip
|
<mat-chip
|
||||||
class="poll-state active"
|
class="poll-state active"
|
||||||
[matMenuTriggerFor]="triggerMenu"
|
[matMenuTriggerFor]="triggerMenu"
|
||||||
@ -41,23 +33,14 @@
|
|||||||
{{ poll.stateVerbose }}
|
{{ poll.stateVerbose }}
|
||||||
</mat-chip>
|
</mat-chip>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h3>
|
|
||||||
<a routerLink="/assignments/polls/{{ poll.id }}">
|
|
||||||
{{ poll.title }}
|
|
||||||
</a>
|
|
||||||
</h3>
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div *ngIf="poll.stateHasVotes">
|
||||||
<os-charts [type]="chartType" [labels]="candidatesLabels" [data]="chartDataSubject"></os-charts>
|
<os-charts [type]="chartType" [labels]="candidatesLabels" [data]="chartDataSubject"></os-charts>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<os-assignment-poll-vote *ngIf="poll.canBeVotedFor" [poll]="poll"></os-assignment-poll-vote>
|
<os-assignment-poll-vote *ngIf="poll.canBeVotedFor" [poll]="poll"></os-assignment-poll-vote>
|
||||||
<!-- <ng-container *ngIf="poll.state === pollStates.STATE_PUBLISHED" [ngTemplateOutlet]="resultsTemplate"></ng-container> -->
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ng-template #resultsTemplate> </ng-template>
|
|
||||||
|
|
||||||
<mat-menu #triggerMenu="matMenu">
|
<mat-menu #triggerMenu="matMenu">
|
||||||
<ng-container *ngIf="poll">
|
<ng-container *ngIf="poll">
|
||||||
<button mat-menu-item (click)="changeState(state.value)" *ngFor="let state of poll.nextStates | keyvalue">
|
<button mat-menu-item (click)="changeState(state.value)" *ngFor="let state of poll.nextStates | keyvalue">
|
||||||
@ -65,3 +48,22 @@
|
|||||||
</button>
|
</button>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</mat-menu>
|
</mat-menu>
|
||||||
|
|
||||||
|
<mat-menu #pollItemMenu="matMenu">
|
||||||
|
<div *osPerms="'assignments.can_manage'">
|
||||||
|
<button mat-menu-item (click)="openDialog()">
|
||||||
|
<mat-icon>edit</mat-icon>
|
||||||
|
<span translate>Edit</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div *osPerms="'core.can_manage_projector'">
|
||||||
|
<os-projector-button menuItem="true" [object]="poll"></os-projector-button>
|
||||||
|
</div>
|
||||||
|
<div *osPerms="'assignments.can_manage'">
|
||||||
|
<mat-divider></mat-divider>
|
||||||
|
<button mat-menu-item class="red-warning-text" (click)="onDeletePoll()">
|
||||||
|
<mat-icon>delete</mat-icon>
|
||||||
|
<span translate>Delete</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</mat-menu>
|
||||||
|
@ -3,33 +3,10 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
padding: 0 15px;
|
padding: 0 15px;
|
||||||
|
|
||||||
.poll-main-content {
|
.poll-menu {
|
||||||
padding-top: 10px;
|
position: absolute;
|
||||||
}
|
top: 0;
|
||||||
|
right: 0;
|
||||||
.poll-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-gap: 5px;
|
|
||||||
padding: 5px;
|
|
||||||
grid-template-columns: 30px auto 250px 150px;
|
|
||||||
.candidate-name {
|
|
||||||
word-wrap: break-word;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.right-align {
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vote-input {
|
|
||||||
.mat-form-field-wrapper {
|
|
||||||
// padding-bottom: 0;
|
|
||||||
|
|
||||||
.mat-form-field-infix {
|
|
||||||
width: 60px;
|
|
||||||
border-top: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.poll-properties {
|
.poll-properties {
|
||||||
@ -62,33 +39,4 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.poll-menu {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.poll-quorum {
|
|
||||||
text-align: right;
|
|
||||||
margin-right: 10px;
|
|
||||||
mat-icon {
|
|
||||||
vertical-align: middle;
|
|
||||||
font-size: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.top-aligned {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wide {
|
|
||||||
width: 90%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hint-form {
|
|
||||||
margin-top: 20px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -49,7 +49,7 @@ export class AssignmentPollService extends PollService {
|
|||||||
const length = this.pollRepo.getViewModelList().filter(item => item.assignment_id === poll.assignment_id)
|
const length = this.pollRepo.getViewModelList().filter(item => item.assignment_id === poll.assignment_id)
|
||||||
.length;
|
.length;
|
||||||
|
|
||||||
poll.title = !length ? this.translate.instant('Vote') : `${this.translate.instant('Vote')} (${length + 1})`;
|
poll.title = !length ? this.translate.instant('Ballot') : `${this.translate.instant('Ballot')} (${length + 1})`;
|
||||||
poll.pollmethod = AssignmentPollMethods.YN;
|
poll.pollmethod = AssignmentPollMethods.YN;
|
||||||
poll.assignment_id = poll.assignment_id;
|
poll.assignment_id = poll.assignment_id;
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,7 @@
|
|||||||
<mat-icon *ngSwitchCase="'General'">home</mat-icon>
|
<mat-icon *ngSwitchCase="'General'">home</mat-icon>
|
||||||
<mat-icon *ngSwitchCase="'Agenda'">today</mat-icon>
|
<mat-icon *ngSwitchCase="'Agenda'">today</mat-icon>
|
||||||
<mat-icon *ngSwitchCase="'Motions'">assignment</mat-icon>
|
<mat-icon *ngSwitchCase="'Motions'">assignment</mat-icon>
|
||||||
|
<mat-icon *ngSwitchCase="'Voting'">pie_chart</mat-icon>
|
||||||
<mat-icon *ngSwitchCase="'Elections'">how_to_vote</mat-icon>
|
<mat-icon *ngSwitchCase="'Elections'">how_to_vote</mat-icon>
|
||||||
<mat-icon *ngSwitchCase="'Participants'">groups</mat-icon>
|
<mat-icon *ngSwitchCase="'Participants'">groups</mat-icon>
|
||||||
<mat-icon *ngSwitchCase="'Custom translations'">language</mat-icon>
|
<mat-icon *ngSwitchCase="'Custom translations'">language</mat-icon>
|
||||||
|
@ -463,7 +463,7 @@
|
|||||||
<div class="mat-card create-poll-button" *ngIf="perms.isAllowed('createpoll', motion)">
|
<div class="mat-card create-poll-button" *ngIf="perms.isAllowed('createpoll', motion)">
|
||||||
<button mat-button (click)="openDialog()">
|
<button mat-button (click)="openDialog()">
|
||||||
<mat-icon class="main-nav-color">poll</mat-icon>
|
<mat-icon class="main-nav-color">poll</mat-icon>
|
||||||
<span translate>New poll</span>
|
<span translate>New vote</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
<os-head-bar [goBack]="true" [nav]="false">
|
<os-head-bar [goBack]="true" [nav]="false">
|
||||||
<div class="title-slot">
|
<div class="title-slot">
|
||||||
<h2 *ngIf="poll">{{ 'Motion' | translate }} {{ poll.motion.id }}</h2>
|
<h2 *ngIf="poll">{{ 'Motion' | translate }} {{ poll.motion.identifierOrTitle }}</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="menu-slot" *osPerms="'agenda.can_manage'; or: 'agenda.can_see_list_of_speakers'">
|
<div class="menu-slot" *osPerms="'motions.can_manage_polls'">
|
||||||
<button type="button" mat-icon-button [matMenuTriggerFor]="pollDetailMenu">
|
<button type="button" mat-icon-button [matMenuTriggerFor]="pollDetailMenu">
|
||||||
<mat-icon>more_vert</mat-icon>
|
<mat-icon>more_vert</mat-icon>
|
||||||
</button>
|
</button>
|
||||||
@ -18,14 +18,13 @@
|
|||||||
<ng-template #viewTemplate>
|
<ng-template #viewTemplate>
|
||||||
<ng-container *ngIf="poll">
|
<ng-container *ngIf="poll">
|
||||||
<h1>{{ poll.title }}</h1>
|
<h1>{{ poll.title }}</h1>
|
||||||
<span *ngIf="poll.type !== 'analog'">{{ 'Polly type' | translate }}: {{ poll.type | translate }}</span>
|
<span *ngIf="poll.type !== 'analog'">{{ poll.typeVerbose | translate }}</span>
|
||||||
<os-breadcrumb [breadcrumbs]="breadcrumbs"></os-breadcrumb>
|
|
||||||
|
<div *ngIf="!poll.hasVotes || !poll.stateHasVotes">{{ 'No results to show' | translate }}</div>
|
||||||
|
|
||||||
<div *ngIf="poll.stateHasVotes">
|
<div *ngIf="poll.stateHasVotes">
|
||||||
<h2 translate>Result</h2>
|
<h2 translate>Result</h2>
|
||||||
|
|
||||||
<div *ngIf="!poll.hasVotes">{{ 'No results to show' | translate }}</div>
|
|
||||||
|
|
||||||
<div class="result-wrapper" *ngIf="poll.hasVotes">
|
<div class="result-wrapper" *ngIf="poll.hasVotes">
|
||||||
<!-- Chart -->
|
<!-- Chart -->
|
||||||
<os-charts
|
<os-charts
|
||||||
@ -52,25 +51,28 @@
|
|||||||
</mat-table>
|
</mat-table>
|
||||||
|
|
||||||
<!-- Named table: only show if votes are present -->
|
<!-- Named table: only show if votes are present -->
|
||||||
<ng-container *ngIf="poll.type === 'named' && votesDataSource.data">
|
<div class="named-result-table" *ngIf="poll.type === 'named' && votesDataSource.data">
|
||||||
<input matInput [(ngModel)]="votesDataSource.filter" placeholder="Filter"/>
|
<h3>{{ 'Single votes' | translate }}</h3>
|
||||||
|
<mat-form-field>
|
||||||
|
<input matInput [(ngModel)]="votesDataSource.filter" placeholder="Filter" />
|
||||||
|
</mat-form-field>
|
||||||
<mat-table [dataSource]="votesDataSource">
|
<mat-table [dataSource]="votesDataSource">
|
||||||
<ng-container matColumnDef="key" sticky>
|
<ng-container matColumnDef="key" sticky>
|
||||||
<mat-header-cell *matHeaderCellDef>{{ "User" | translate }}</mat-header-cell>
|
<mat-header-cell *matHeaderCellDef>{{ 'User' | translate }}</mat-header-cell>
|
||||||
<mat-cell *matCellDef="let vote">
|
<mat-cell *matCellDef="let vote">
|
||||||
<div *ngIf="vote.user">{{ vote.user.getFullName() }}</div>
|
<div *ngIf="vote.user">{{ vote.user.getFullName() }}</div>
|
||||||
<div *ngIf="!vote.user">{{ 'Unknown user' | translate }}</div>
|
<div *ngIf="!vote.user">{{ 'Unknown user' | translate }}</div>
|
||||||
</mat-cell>
|
</mat-cell>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<ng-container matColumnDef="value" sticky>
|
<ng-container matColumnDef="value" sticky>
|
||||||
<mat-header-cell *matHeaderCellDef>{{ "Vote" | translate }}</mat-header-cell>
|
<mat-header-cell *matHeaderCellDef>{{ 'Vote' | translate }}</mat-header-cell>
|
||||||
<mat-cell *matCellDef="let vote">{{ vote.valueVerbose }}</mat-cell>
|
<mat-cell *matCellDef="let vote">{{ vote.valueVerbose }}</mat-cell>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<mat-header-row *matHeaderRowDef="columnDefinition"></mat-header-row>
|
<mat-header-row *matHeaderRowDef="columnDefinition"></mat-header-row>
|
||||||
<mat-row *matRowDef="let vote; columns: columnDefinition"></mat-row>
|
<mat-row *matRowDef="let vote; columns: columnDefinition"></mat-row>
|
||||||
</mat-table>
|
</mat-table>
|
||||||
</ng-container>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -82,29 +84,29 @@
|
|||||||
{{ group.getTitle() | translate }}<span *ngIf="i < poll.groups.length - 1">, </span>
|
{{ group.getTitle() | translate }}<span *ngIf="i < poll.groups.length - 1">, </span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div>{{ 'Majority method' | translate }}: {{ poll.majorityMethodVerbose | translate }}</div>
|
<div>{{ 'Required majority' | translate }}: {{ poll.majorityMethodVerbose | translate }}</div>
|
||||||
<div>{{ '100% base' | translate }}: {{ poll.percentBaseVerbose | translate }}</div>
|
<div>{{ '100% base' | translate }}: {{ poll.percentBaseVerbose | translate }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div *ngIf="poll.state === 2">
|
|
||||||
<os-poll-progress [poll]="poll"></os-poll-progress>
|
|
||||||
</div>
|
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
||||||
<!-- More Menu -->
|
<!-- More Menu -->
|
||||||
<mat-menu #pollDetailMenu="matMenu">
|
<mat-menu #pollDetailMenu="matMenu">
|
||||||
<os-projector-button [menuItem]="true" [object]="poll" *osPerms="'core.can_manage_projector'"></os-projector-button>
|
<os-projector-button [menuItem]="true" [object]="poll" *osPerms="'core.can_manage_projector'"></os-projector-button>
|
||||||
<button mat-menu-item (click)="openDialog()">
|
<button *osPerms="'motions.can_manage_polls'" mat-menu-item (click)="openDialog()">
|
||||||
<mat-icon>edit</mat-icon>
|
<mat-icon>edit</mat-icon>
|
||||||
<span translate>Edit</span>
|
<span translate>Edit</span>
|
||||||
</button>
|
</button>
|
||||||
<button mat-menu-item *ngIf="poll && poll.type === 'named'" (click)="pseudoanonymizePoll()">
|
<button
|
||||||
<mat-icon>polymer</mat-icon>
|
mat-menu-item
|
||||||
<span translate>Pseudoanonymize</span>
|
*osPerms="'motions.can_manage_polls'; and: poll && poll.type === 'named'"
|
||||||
|
(click)="pseudoanonymizePoll()"
|
||||||
|
>
|
||||||
|
<mat-icon>warning</mat-icon>
|
||||||
|
<span translate>Anonymize votes</span>
|
||||||
</button>
|
</button>
|
||||||
<mat-divider></mat-divider>
|
<mat-divider></mat-divider>
|
||||||
<button mat-menu-item (click)="deletePoll()">
|
<button *osPerms="'motions.can_manage_polls'" mat-menu-item (click)="deletePoll()">
|
||||||
<mat-icon color="warn">delete</mat-icon>
|
<mat-icon color="warn">delete</mat-icon>
|
||||||
<span translate>Delete</span>
|
<span translate>Delete</span>
|
||||||
</button>
|
</button>
|
||||||
|
@ -35,4 +35,8 @@
|
|||||||
|
|
||||||
.named-result-table {
|
.named-result-table {
|
||||||
grid-area: names;
|
grid-area: names;
|
||||||
|
.mat-form-field {
|
||||||
|
font-size: 14px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,41 +6,43 @@
|
|||||||
[placeholder]="'Yes' | translate"
|
[placeholder]="'Yes' | translate"
|
||||||
[checkboxValue]="-1"
|
[checkboxValue]="-1"
|
||||||
inputType="number"
|
inputType="number"
|
||||||
[checkboxLabel]="'Majority' | translate"
|
[checkboxLabel]="'majority' | translate"
|
||||||
formControlName="Y"
|
formControlName="Y"
|
||||||
></os-check-input>
|
></os-check-input>
|
||||||
<os-check-input
|
<os-check-input
|
||||||
[placeholder]="'No' | translate"
|
[placeholder]="'No' | translate"
|
||||||
[checkboxValue]="-1"
|
[checkboxValue]="-1"
|
||||||
inputType="number"
|
inputType="number"
|
||||||
[checkboxLabel]="'Majority' | translate"
|
[checkboxLabel]="'majority' | translate"
|
||||||
formControlName="N"
|
formControlName="N"
|
||||||
></os-check-input>
|
></os-check-input>
|
||||||
<os-check-input
|
<os-check-input
|
||||||
[placeholder]="'Abstain' | translate"
|
[placeholder]="'Abstain' | translate"
|
||||||
[checkboxValue]="-1"
|
[checkboxValue]="-1"
|
||||||
inputType="number"
|
inputType="number"
|
||||||
[checkboxLabel]="'Majority' | translate"
|
[checkboxLabel]="'majority' | translate"
|
||||||
formControlName="A"
|
formControlName="A"
|
||||||
></os-check-input>
|
></os-check-input>
|
||||||
<os-check-input
|
<os-check-input
|
||||||
[placeholder]="'Votes valid' | translate"
|
[placeholder]="'Valid votes' | translate"
|
||||||
inputType="number"
|
inputType="number"
|
||||||
formControlName="votesvalid"
|
formControlName="votesvalid"
|
||||||
></os-check-input>
|
></os-check-input>
|
||||||
<os-check-input
|
<os-check-input
|
||||||
[placeholder]="'Votes invalid' | translate"
|
[placeholder]="'Invalid votes' | translate"
|
||||||
inputType="number"
|
inputType="number"
|
||||||
formControlName="votesinvalid"
|
formControlName="votesinvalid"
|
||||||
></os-check-input>
|
></os-check-input>
|
||||||
<os-check-input
|
<os-check-input
|
||||||
[placeholder]="'Votes cast' | translate"
|
[placeholder]="'Total votes cast' | translate"
|
||||||
inputType="number"
|
inputType="number"
|
||||||
formControlName="votescast"
|
formControlName="votescast"
|
||||||
></os-check-input>
|
></os-check-input>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
|
||||||
|
<!-- Publish immediately button. Only show for new polls -->
|
||||||
|
<div *ngIf="!pollData.state">
|
||||||
<mat-checkbox [(ngModel)]="publishImmediately" (change)="publishStateChanged($event.checked)">
|
<mat-checkbox [(ngModel)]="publishImmediately" (change)="publishStateChanged($event.checked)">
|
||||||
<span translate>Publish immediately</span>
|
<span translate>Publish immediately</span>
|
||||||
</mat-checkbox>
|
</mat-checkbox>
|
||||||
|
@ -42,9 +42,6 @@ export class MotionPollDialogComponent extends BasePollDialogComponent {
|
|||||||
votesinvalid: data.votesinvalid,
|
votesinvalid: data.votesinvalid,
|
||||||
votescast: data.votescast
|
votescast: data.votescast
|
||||||
};
|
};
|
||||||
// if (data.pollmethod === 'YNA') {
|
|
||||||
// update.A = data.options[0].abstain;
|
|
||||||
// }
|
|
||||||
|
|
||||||
if (this.dialogVoteForm) {
|
if (this.dialogVoteForm) {
|
||||||
const result = this.undoReplaceEmptyValues(update);
|
const result = this.undoReplaceEmptyValues(update);
|
||||||
@ -64,9 +61,7 @@ export class MotionPollDialogComponent extends BasePollDialogComponent {
|
|||||||
votesinvalid: ['', [Validators.min(-2)]],
|
votesinvalid: ['', [Validators.min(-2)]],
|
||||||
votescast: ['', [Validators.min(-2)]]
|
votescast: ['', [Validators.min(-2)]]
|
||||||
});
|
});
|
||||||
// if (this.pollData.pollmethod === MotionPollMethods.YNA) {
|
|
||||||
// this.dialogVoteForm.addControl('A', this.fb.control('', [Validators.min(-2)]));
|
|
||||||
// }
|
|
||||||
if (this.pollData.poll) {
|
if (this.pollData.poll) {
|
||||||
this.updateDialogVoteForm(this.pollData);
|
this.updateDialogVoteForm(this.pollData);
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
<ng-container *ngIf="poll">
|
<ng-container *ngIf="poll">
|
||||||
|
<div *osPerms="'motions.can_manage_polls';and:poll.isStateStarted">
|
||||||
|
<os-poll-progress [poll]="poll"></os-poll-progress>
|
||||||
|
</div>
|
||||||
<ng-container *ngIf="vmanager.canVote(poll)">
|
<ng-container *ngIf="vmanager.canVote(poll)">
|
||||||
|
|
||||||
<!-- Voting -->
|
<!-- Voting -->
|
||||||
<p *ngFor="let option of voteOptions">
|
<p *ngFor="let option of voteOptions">
|
||||||
<button
|
<button
|
||||||
|
@ -1,16 +1,15 @@
|
|||||||
/**
|
@import '~assets/styles/poll-colors.scss';
|
||||||
* These colors should be extracted from some global CSS Constants file
|
|
||||||
*/
|
|
||||||
.voted-yes {
|
.voted-yes {
|
||||||
background-color: #9fd773;
|
background-color: $votes-yes-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
.voted-no {
|
.voted-no {
|
||||||
background-color: #cc6c5b;
|
background-color: $votes-no-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
.voted-abstain {
|
.voted-abstain {
|
||||||
background-color: #a6a6a6;
|
background-color: $votes-abstain-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vote-label {
|
.vote-label {
|
||||||
|
@ -1,23 +1,23 @@
|
|||||||
<div class="poll-preview-wrapper">
|
<div class="poll-preview-wrapper" *ngIf="poll && showPoll()">
|
||||||
<!-- Poll Infos -->
|
<!-- Poll Infos -->
|
||||||
<div class="poll-title-wrapper" *ngIf="poll">
|
<div class="poll-title-wrapper">
|
||||||
<!-- Title -->
|
<!-- Title -->
|
||||||
<a class="poll-title" routerLink="/motions/polls/{{ poll.id }}">
|
<a class="poll-title" [routerLink]="pollLink">
|
||||||
{{ poll.title }}
|
{{ poll.title }}
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<!-- Edit button -->
|
<!-- Dot Menu -->
|
||||||
<span class="poll-title-actions" *osPerms="'motions.can_manage_polls'">
|
<span class="poll-title-actions" *osPerms="'motions.can_manage_polls'">
|
||||||
<button mat-icon-button (click)="openDialog()">
|
<button mat-icon-button [matMenuTriggerFor]="pollDetailMenu">
|
||||||
<mat-icon class="small-icon">edit</mat-icon>
|
<mat-icon class="small-icon">more_horiz</mat-icon>
|
||||||
</button>
|
</button>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<!-- State chip -->
|
<!-- State chip -->
|
||||||
<div class="poll-properties" *osPerms="'motions.can_manage_polls'">
|
<div class="poll-properties" *osPerms="'motions.can_manage_polls'">
|
||||||
<span *ngIf="pollService.isElectronicVotingEnabled && poll.typeVerbose !== 'Analog'">
|
<div *ngIf="pollService.isElectronicVotingEnabled && poll.type !== 'analog'">
|
||||||
{{ 'Poll type' | translate }}: {{ poll.typeVerbose | translate }}
|
{{ poll.typeVerbose | translate }}
|
||||||
</span>
|
</div>
|
||||||
|
|
||||||
<mat-chip
|
<mat-chip
|
||||||
disableRipple
|
disableRipple
|
||||||
@ -37,35 +37,72 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ng-template #votingResult>
|
<ng-template #votingResult>
|
||||||
<div (click)="openPoll()">
|
<div [routerLink]="pollLink">
|
||||||
<ng-container [ngTemplateOutlet]="poll.hasVotes && poll.stateHasVotes ? viewTemplate : emptyTemplate"></ng-container>
|
<ng-container
|
||||||
|
[ngTemplateOutlet]="poll.hasVotes && poll.stateHasVotes ? viewTemplate : emptyTemplate"
|
||||||
|
></ng-container>
|
||||||
</div>
|
</div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
||||||
<ng-template #viewTemplate>
|
<ng-template #viewTemplate>
|
||||||
<div class="poll-chart-wrapper">
|
<div class="poll-chart-wrapper">
|
||||||
<div class="votes-yes">
|
<!-- empty helper div to center the grid wrapper -->
|
||||||
<os-icon-container icon="check" size="large">
|
<div></div>
|
||||||
{{ voteYes }}
|
<div class="doughnut-chart">
|
||||||
</os-icon-container>
|
<os-charts *ngIf="showChart" [type]="'doughnut'" [data]="chartDataSubject" [showLegend]="false"> </os-charts>
|
||||||
</div>
|
</div>
|
||||||
<div *ngIf="showChart" class="doughnut-chart">
|
<div class="vote-legend">
|
||||||
<os-charts [type]="'doughnut'" [data]="chartDataSubject" [showLegend]="false"> </os-charts>
|
<div class="votes-yes" *ngIf="isVoteDocumented(voteYes)">
|
||||||
</div>
|
<os-icon-container icon="thumb_up" size="large">
|
||||||
<div class="votes-no">
|
{{ voteYes | parsePollNumber }}
|
||||||
<os-icon-container icon="close" size="large">
|
</os-icon-container>
|
||||||
{{ voteNo }}
|
</div>
|
||||||
</os-icon-container>
|
<div class="votes-no" *ngIf="isVoteDocumented(voteNo)">
|
||||||
|
<os-icon-container icon="thumb_down" size="large">
|
||||||
|
{{ voteNo | parsePollNumber }}
|
||||||
|
</os-icon-container>
|
||||||
|
</div>
|
||||||
|
<div class="votes-abstain" *ngIf="isVoteDocumented(voteAbstain)">
|
||||||
|
<os-icon-container icon="trip_origin" size="large">
|
||||||
|
{{ voteAbstain | parsePollNumber }}
|
||||||
|
</os-icon-container>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="poll-detail-button-wrapper" *ngIf="poll.type !== 'analog'">
|
||||||
|
<button mat-button [routerLink]="pollLink">
|
||||||
|
{{ 'Single Votes' | translate }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
||||||
<ng-template #emptyTemplate>
|
<ng-template #emptyTemplate>
|
||||||
<div>
|
<div *osPerms="'motions.can_manage_polls'">
|
||||||
An empty poll - you have to enter votes.
|
{{ 'An empty poll - you have to enter votes.' | translate }}
|
||||||
</div>
|
</div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
||||||
|
<!-- More Menu -->
|
||||||
|
<mat-menu #pollDetailMenu="matMenu">
|
||||||
|
<os-projector-button [menuItem]="true" [object]="poll" *osPerms="'core.can_manage_projector'"></os-projector-button>
|
||||||
|
<button *osPerms="'motions.can_manage_polls'" mat-menu-item (click)="openDialog()">
|
||||||
|
<mat-icon>edit</mat-icon>
|
||||||
|
<span translate>Edit</span>
|
||||||
|
</button>
|
||||||
|
<button mat-menu-item (click)="downloadPdf()">
|
||||||
|
<mat-icon>picture_as_pdf</mat-icon>
|
||||||
|
<span translate>PDF</span>
|
||||||
|
</button>
|
||||||
|
<div *osPerms="'motions.can_manage_polls'">
|
||||||
|
<mat-divider></mat-divider>
|
||||||
|
<button mat-menu-item (click)="deletePoll()">
|
||||||
|
<mat-icon color="warn">delete</mat-icon>
|
||||||
|
<span translate>Delete</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</mat-menu>
|
||||||
|
|
||||||
|
<!-- Select state menu -->
|
||||||
<mat-menu #triggerMenu="matMenu">
|
<mat-menu #triggerMenu="matMenu">
|
||||||
<ng-container *ngIf="poll">
|
<ng-container *ngIf="poll">
|
||||||
<button mat-menu-item (click)="changeState(state.value)" *ngFor="let state of poll.nextStates | keyvalue">
|
<button mat-menu-item (click)="changeState(state.value)" *ngFor="let state of poll.nextStates | keyvalue">
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
@import '~assets/styles/poll-colors.scss';
|
||||||
|
|
||||||
.poll-preview-wrapper {
|
.poll-preview-wrapper {
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
background: white;
|
background: white;
|
||||||
@ -6,6 +8,7 @@
|
|||||||
.poll-title {
|
.poll-title {
|
||||||
color: black;
|
color: black;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
.poll-title-actions {
|
.poll-title-actions {
|
||||||
@ -48,20 +51,41 @@
|
|||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: auto minmax(50px, 20%) auto;
|
grid-template-columns: auto minmax(50px, 20%) auto;
|
||||||
|
|
||||||
.votes-no {
|
.doughnut-chart {
|
||||||
color: #cc6c5b;
|
margin-top: auto;
|
||||||
margin: auto 0;
|
margin-bottom: auto;
|
||||||
width: fit-content;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.votes-yes {
|
.vote-legend {
|
||||||
color: #9fc773;
|
margin: auto 10px;
|
||||||
margin: auto 0 auto auto;
|
|
||||||
width: fit-content;
|
div + div {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.votes-yes {
|
||||||
|
color: $votes-yes-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.votes-no {
|
||||||
|
color: $votes-no-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.votes-abstain {
|
||||||
|
color: $votes-abstain-color;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.poll-detail-button-wrapper {
|
||||||
|
display: flex;
|
||||||
|
margin: auto 0;
|
||||||
|
> button {
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.poll-preview-meta-info {
|
.poll-preview-meta-info {
|
||||||
span {
|
span {
|
||||||
padding: 0 5px;
|
padding: 0 5px;
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { Component, Input } from '@angular/core';
|
import { Component, Input } from '@angular/core';
|
||||||
import { MatDialog, MatSnackBar } from '@angular/material';
|
import { MatDialog, MatSnackBar } from '@angular/material';
|
||||||
import { Title } from '@angular/platform-browser';
|
import { Title } from '@angular/platform-browser';
|
||||||
import { Router } from '@angular/router';
|
|
||||||
|
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { BehaviorSubject } from 'rxjs';
|
import { BehaviorSubject } from 'rxjs';
|
||||||
@ -12,6 +11,7 @@ import { PromptService } from 'app/core/ui-services/prompt.service';
|
|||||||
import { ChartData } from 'app/shared/components/charts/charts.component';
|
import { ChartData } from 'app/shared/components/charts/charts.component';
|
||||||
import { ViewMotionPoll } from 'app/site/motions/models/view-motion-poll';
|
import { ViewMotionPoll } from 'app/site/motions/models/view-motion-poll';
|
||||||
import { MotionPollDialogService } from 'app/site/motions/services/motion-poll-dialog.service';
|
import { MotionPollDialogService } from 'app/site/motions/services/motion-poll-dialog.service';
|
||||||
|
import { MotionPollPdfService } from 'app/site/motions/services/motion-poll-pdf.service';
|
||||||
import { BasePollComponent } from 'app/site/polls/components/base-poll.component';
|
import { BasePollComponent } from 'app/site/polls/components/base-poll.component';
|
||||||
import { PollService } from 'app/site/polls/services/poll.service';
|
import { PollService } from 'app/site/polls/services/poll.service';
|
||||||
|
|
||||||
@ -40,6 +40,9 @@ export class MotionPollComponent extends BasePollComponent<ViewMotionPoll> {
|
|||||||
if (data.label === 'NO') {
|
if (data.label === 'NO') {
|
||||||
this.voteNo = data.data[0];
|
this.voteNo = data.data[0];
|
||||||
}
|
}
|
||||||
|
if (data.label === 'ABSTAIN') {
|
||||||
|
this.voteAbstain = data.data[0];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
this.chartDataSubject.next(chartData);
|
this.chartDataSubject.next(chartData);
|
||||||
}
|
}
|
||||||
@ -48,6 +51,10 @@ export class MotionPollComponent extends BasePollComponent<ViewMotionPoll> {
|
|||||||
return this._poll;
|
return this._poll;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public get pollLink(): string {
|
||||||
|
return `/motions/polls/${this.poll.id}`;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Subject to holding the data needed for the chart.
|
* Subject to holding the data needed for the chart.
|
||||||
*/
|
*/
|
||||||
@ -56,32 +63,45 @@ export class MotionPollComponent extends BasePollComponent<ViewMotionPoll> {
|
|||||||
/**
|
/**
|
||||||
* Number of votes for `Yes`.
|
* Number of votes for `Yes`.
|
||||||
*/
|
*/
|
||||||
public set voteYes(n: number | string) {
|
public set voteYes(n: number) {
|
||||||
this._voteYes = n;
|
this._voteYes = n;
|
||||||
}
|
}
|
||||||
|
|
||||||
public get voteYes(): number | string {
|
public get voteYes(): number {
|
||||||
return this.verboseForNumber(this._voteYes as number);
|
return this._voteYes;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Number of votes for `No`.
|
* Number of votes for `No`.
|
||||||
*/
|
*/
|
||||||
public set voteNo(n: number | string) {
|
public set voteNo(n: number) {
|
||||||
this._voteNo = n;
|
this._voteNo = n;
|
||||||
}
|
}
|
||||||
|
|
||||||
public get voteNo(): number | string {
|
public get voteNo(): number {
|
||||||
return this.verboseForNumber(this._voteNo as number);
|
return this._voteNo;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Number of votes for `Abstain`.
|
||||||
|
*/
|
||||||
|
public set voteAbstain(n: number) {
|
||||||
|
this._voteAbstain = n;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get voteAbstain(): number {
|
||||||
|
return this._voteAbstain;
|
||||||
}
|
}
|
||||||
|
|
||||||
public get showChart(): boolean {
|
public get showChart(): boolean {
|
||||||
return this._voteYes >= 0 && this._voteNo >= 0;
|
return this._voteYes >= 0 && this._voteNo >= 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _voteNo: number | string = 0;
|
private _voteNo: number;
|
||||||
|
|
||||||
private _voteYes: number | string = 0;
|
private _voteYes: number;
|
||||||
|
|
||||||
|
private _voteAbstain: number;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor.
|
* Constructor.
|
||||||
@ -101,27 +121,35 @@ export class MotionPollComponent extends BasePollComponent<ViewMotionPoll> {
|
|||||||
public pollRepo: MotionPollRepositoryService,
|
public pollRepo: MotionPollRepositoryService,
|
||||||
pollDialog: MotionPollDialogService,
|
pollDialog: MotionPollDialogService,
|
||||||
public pollService: PollService,
|
public pollService: PollService,
|
||||||
private router: Router,
|
private operator: OperatorService,
|
||||||
private operator: OperatorService
|
private pdfService: MotionPollPdfService
|
||||||
) {
|
) {
|
||||||
super(titleService, matSnackBar, translate, dialog, promptService, pollRepo, pollDialog);
|
super(titleService, matSnackBar, translate, dialog, promptService, pollRepo, pollDialog);
|
||||||
}
|
}
|
||||||
|
|
||||||
public openPoll(): void {
|
public showPoll(): boolean {
|
||||||
if (this.operator.hasPerms('motions.can_manage_polls')) {
|
return (
|
||||||
this.router.navigate(['motions', 'polls', this.poll.id]);
|
this.operator.hasPerms('motions.can_manage_polls') ||
|
||||||
|
this.poll.isStatePublished ||
|
||||||
|
(this.poll.type !== 'analog' && this.poll.isStateStarted)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public downloadPdf(): void {
|
||||||
|
console.log('picture_as_pdf');
|
||||||
|
this.pdfService.printBallots(this.poll);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async deletePoll(): Promise<void> {
|
||||||
|
const title = 'Delete poll';
|
||||||
|
const text = 'Do you really want to delete the selected poll?';
|
||||||
|
|
||||||
|
if (await this.promptService.open(title, text)) {
|
||||||
|
this.repo.delete(this.poll).catch(this.raiseError);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private verboseForNumber(input: number): number | string {
|
public isVoteDocumented(vote: number): boolean {
|
||||||
input = Math.trunc(input);
|
return vote !== null && vote !== undefined && vote !== -2;
|
||||||
switch (input) {
|
|
||||||
case -1:
|
|
||||||
return 'Majority';
|
|
||||||
case -2:
|
|
||||||
return 'Not documented';
|
|
||||||
default:
|
|
||||||
return input;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -65,7 +65,7 @@ const routes: Routes = [
|
|||||||
{
|
{
|
||||||
path: 'polls',
|
path: 'polls',
|
||||||
loadChildren: () => import('./modules/motion-poll/motion-poll.module').then(m => m.MotionPollModule),
|
loadChildren: () => import('./modules/motion-poll/motion-poll.module').then(m => m.MotionPollModule),
|
||||||
data: { basePerm: 'motions.can_manage_polls' }
|
data: { basePerm: 'motions.can_see' }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: ':id',
|
path: ':id',
|
||||||
|
@ -17,7 +17,7 @@ type BallotCountChoices = 'NUMBER_OF_DELEGATES' | 'NUMBER_OF_ALL_PARTICIPANTS' |
|
|||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* ```ts
|
* ```ts
|
||||||
* this.MotionPollPdfService.printBallos(this.poll);
|
* this.MotionPollPdfService.printBallots(this.poll);
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
@Injectable({
|
@Injectable({
|
||||||
|
@ -75,7 +75,7 @@ export abstract class BasePollDetailComponent<V extends ViewBasePoll> extends Ba
|
|||||||
* @param fb
|
* @param fb
|
||||||
* @param groupRepo
|
* @param groupRepo
|
||||||
* @param location
|
* @param location
|
||||||
* @param promptDialog
|
* @param promptService
|
||||||
* @param dialog
|
* @param dialog
|
||||||
*/
|
*/
|
||||||
public constructor(
|
public constructor(
|
||||||
@ -85,7 +85,7 @@ export abstract class BasePollDetailComponent<V extends ViewBasePoll> extends Ba
|
|||||||
protected repo: BasePollRepositoryService,
|
protected repo: BasePollRepositoryService,
|
||||||
protected route: ActivatedRoute,
|
protected route: ActivatedRoute,
|
||||||
protected groupRepo: GroupRepositoryService,
|
protected groupRepo: GroupRepositoryService,
|
||||||
protected promptDialog: PromptService,
|
protected promptService: PromptService,
|
||||||
protected pollDialog: BasePollDialogService<V>
|
protected pollDialog: BasePollDialogService<V>
|
||||||
) {
|
) {
|
||||||
super(title, translate, matSnackbar);
|
super(title, translate, matSnackbar);
|
||||||
@ -108,16 +108,16 @@ export abstract class BasePollDetailComponent<V extends ViewBasePoll> extends Ba
|
|||||||
const title = 'Delete poll';
|
const title = 'Delete poll';
|
||||||
const text = 'Do you really want to delete the selected poll?';
|
const text = 'Do you really want to delete the selected poll?';
|
||||||
|
|
||||||
if (await this.promptDialog.open(title, text)) {
|
if (await this.promptService.open(title, text)) {
|
||||||
this.repo.delete(this.poll).then(() => this.onDeleted(), this.raiseError);
|
this.repo.delete(this.poll).then(() => this.onDeleted(), this.raiseError);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async pseudoanonymizePoll(): Promise<void> {
|
public async pseudoanonymizePoll(): Promise<void> {
|
||||||
const title = 'Pseudoanonymize poll';
|
const title = 'Anonymize single votes';
|
||||||
const text = 'Do you really want to pseudoanonymize the selected poll?';
|
const text = 'Do you really want to anonymize all votes? This cannot be undone.';
|
||||||
|
|
||||||
if (await this.promptDialog.open(title, text)) {
|
if (await this.promptService.open(title, text)) {
|
||||||
this.repo.pseudoanonymize(this.poll).then(() => this.onPollLoaded(), this.raiseError); // votes have changed, but not the poll, so the components have to be informed about the update
|
this.repo.pseudoanonymize(this.poll).then(() => this.onPollLoaded(), this.raiseError); // votes have changed, but not the poll, so the components have to be informed about the update
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
<form [formGroup]="contentForm" class="poll-preview--meta-info-form">
|
<form [formGroup]="contentForm" class="poll-preview--meta-info-form">
|
||||||
<ng-container *ngIf="!data || !data.state || data.state === 1">
|
<ng-container *ngIf="!data || !data.state || data.state === 1">
|
||||||
<mat-form-field *ngIf="pollService.isElectronicVotingEnabled">
|
<mat-form-field *ngIf="pollService.isElectronicVotingEnabled">
|
||||||
<mat-select [placeholder]="'Poll type' | translate" formControlName="type" required>
|
<mat-select [placeholder]="'Voting type' | translate" formControlName="type" required>
|
||||||
<mat-option *ngFor="let option of pollTypes | keyvalue" [value]="option.key">
|
<mat-option *ngFor="let option of pollTypes | keyvalue" [value]="option.key">
|
||||||
{{ option.value | translate }}
|
{{ option.value | translate }}
|
||||||
</mat-option>
|
</mat-option>
|
||||||
@ -56,7 +56,7 @@
|
|||||||
</mat-select>
|
</mat-select>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
<mat-form-field>
|
<mat-form-field>
|
||||||
<mat-select placeholder="{{ 'Majority' | translate }}" formControlName="majority_method" required>
|
<mat-select placeholder="{{ 'Required majority' | translate }}" formControlName="majority_method" required>
|
||||||
<mat-option *ngFor="let option of majorityMethods | keyvalue" [value]="option.key">
|
<mat-option *ngFor="let option of majorityMethods | keyvalue" [value]="option.key">
|
||||||
{{ option.value | translate }}
|
{{ option.value | translate }}
|
||||||
</mat-option>
|
</mat-option>
|
||||||
|
@ -1 +1,3 @@
|
|||||||
<span>{{ this.poll.voted_id.length }} von {{ this.max }} haben abgestimmt.</span>
|
<span>{{ poll.voted_id.length }} von {{ max }} haben abgestimmt.</span>
|
||||||
|
|
||||||
|
<mat-progress-bar class="voting-progress-bar" [value]="valueInPercent"></mat-progress-bar>
|
||||||
|
@ -29,6 +29,10 @@ export class PollProgressComponent extends BaseViewComponent implements OnInit {
|
|||||||
super(title, translate, snackbar);
|
super(title, translate, snackbar);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public get valueInPercent(): number {
|
||||||
|
return (this.poll.voted_id.length / this.max) * 100;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* OnInit.
|
* OnInit.
|
||||||
* Sets the observable for groups.
|
* Sets the observable for groups.
|
||||||
|
@ -22,9 +22,9 @@ export const PollStateVerbose = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const PollTypeVerbose = {
|
export const PollTypeVerbose = {
|
||||||
analog: 'Analog',
|
analog: 'Analog voting',
|
||||||
named: 'Named',
|
named: 'Named voting',
|
||||||
pseudoanonymous: 'Pseudoanonymous'
|
pseudoanonymous: 'Pseudoanonymous voting'
|
||||||
};
|
};
|
||||||
|
|
||||||
export const PollPropertyVerbose = {
|
export const PollPropertyVerbose = {
|
||||||
@ -37,9 +37,9 @@ export const PollPropertyVerbose = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const MajorityMethodVerbose = {
|
export const MajorityMethodVerbose = {
|
||||||
simple: 'Simple',
|
simple: 'Simple majority',
|
||||||
two_thirds: 'Two Thirds',
|
two_thirds: 'Two-thirds majority',
|
||||||
three_quarters: 'Three Quarters',
|
three_quarters: 'Three-quarters majority',
|
||||||
disabled: 'Disabled'
|
disabled: 'Disabled'
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -48,7 +48,7 @@ export const PercentBaseVerbose = {
|
|||||||
YNA: 'Yes/No/Abstain',
|
YNA: 'Yes/No/Abstain',
|
||||||
votes: 'All votes',
|
votes: 'All votes',
|
||||||
valid: 'Valid votes',
|
valid: 'Valid votes',
|
||||||
cast: 'Cast votes',
|
cast: 'Total votes cast',
|
||||||
disabled: 'Disabled'
|
disabled: 'Disabled'
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -104,6 +104,7 @@ export abstract class ViewBasePoll<M extends BasePoll<M, any> = any> extends Bas
|
|||||||
}
|
}
|
||||||
return states;
|
return states;
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract readonly pollClassType: 'motion' | 'assignment';
|
public abstract readonly pollClassType: 'motion' | 'assignment';
|
||||||
|
|
||||||
public canBeVotedFor: () => boolean;
|
public canBeVotedFor: () => boolean;
|
||||||
|
6
client/src/assets/styles/poll-colors.scss
Normal file
6
client/src/assets/styles/poll-colors.scss
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
/**
|
||||||
|
* Define the colors used for yes, no and abstain
|
||||||
|
*/
|
||||||
|
$votes-yes-color: #9fd773;
|
||||||
|
$votes-no-color: #cc6c5b;
|
||||||
|
$votes-abstain-color: #a6a6a6;
|
@ -9,7 +9,7 @@ def get_config_variables():
|
|||||||
They are grouped in 'Ballot and ballot papers' and 'PDF'. The generator has
|
They are grouped in 'Ballot and ballot papers' and 'PDF'. The generator has
|
||||||
to be evaluated during app loading (see apps.py).
|
to be evaluated during app loading (see apps.py).
|
||||||
"""
|
"""
|
||||||
# Polls
|
# Voting
|
||||||
yield ConfigVariable(
|
yield ConfigVariable(
|
||||||
name="assignment_poll_default_100_percent_base",
|
name="assignment_poll_default_100_percent_base",
|
||||||
default_value="YNA",
|
default_value="YNA",
|
||||||
@ -20,7 +20,7 @@ def get_config_variables():
|
|||||||
for base in AssignmentPoll.PERCENT_BASES
|
for base in AssignmentPoll.PERCENT_BASES
|
||||||
),
|
),
|
||||||
weight=400,
|
weight=400,
|
||||||
group="Polls",
|
group="Voting",
|
||||||
subgroup="Elections",
|
subgroup="Elections",
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -35,7 +35,8 @@ def get_config_variables():
|
|||||||
label="Required majority",
|
label="Required majority",
|
||||||
help_text="Default method to check whether a candidate has reached the required majority.",
|
help_text="Default method to check whether a candidate has reached the required majority.",
|
||||||
weight=405,
|
weight=405,
|
||||||
group="Polls",
|
hidden=True,
|
||||||
|
group="Voting",
|
||||||
subgroup="Elections",
|
subgroup="Elections",
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -45,7 +46,7 @@ def get_config_variables():
|
|||||||
input_type="boolean",
|
input_type="boolean",
|
||||||
label="Put all candidates on the list of speakers",
|
label="Put all candidates on the list of speakers",
|
||||||
weight=410,
|
weight=410,
|
||||||
group="Polls",
|
group="Voting",
|
||||||
subgroup="Elections",
|
subgroup="Elections",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -117,7 +117,7 @@ def set_correct_state(apps, schema_editor):
|
|||||||
AssignmentPoll = apps.get_model("assignments", "AssignmentPoll")
|
AssignmentPoll = apps.get_model("assignments", "AssignmentPoll")
|
||||||
AssignmentVote = apps.get_model("assignments", "AssignmentVote")
|
AssignmentVote = apps.get_model("assignments", "AssignmentVote")
|
||||||
for poll in AssignmentPoll.objects.all():
|
for poll in AssignmentPoll.objects.all():
|
||||||
# Polls, that are published (old field) but have no votes, will be
|
# Voting, that are published (old field) but have no votes, will be
|
||||||
# left at the created state...
|
# left at the created state...
|
||||||
if AssignmentVote.objects.filter(option__poll__pk=poll.pk).exists():
|
if AssignmentVote.objects.filter(option__poll__pk=poll.pk).exists():
|
||||||
if poll.published:
|
if poll.published:
|
||||||
|
@ -6,7 +6,7 @@ from django.db import migrations, models
|
|||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
("core", "0026_remove_history_restricted"),
|
("core", "0025_projector_color"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
@ -37,7 +37,7 @@ def calculate_aspect_ratios(apps, schema_editor):
|
|||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
("core", "0027_projector_size_1"),
|
("core", "0026_projector_size_1"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
@ -6,7 +6,7 @@ from django.db import migrations
|
|||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
("core", "0028_projector_size_2"),
|
("core", "0027_projector_size_2"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
@ -5,6 +5,6 @@ from django.db import migrations
|
|||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [("core", "0025_projector_color")]
|
dependencies = [("core", "0028_projector_size_3")]
|
||||||
|
|
||||||
operations = [migrations.RemoveField(model_name="history", name="restricted")]
|
operations = [migrations.RemoveField(model_name="history", name="restricted")]
|
@ -388,18 +388,18 @@ def get_config_variables():
|
|||||||
subgroup="PDF export",
|
subgroup="PDF export",
|
||||||
)
|
)
|
||||||
|
|
||||||
# Polls
|
# Voting
|
||||||
yield ConfigVariable(
|
yield ConfigVariable(
|
||||||
name="motion_poll_default_100_percent_base",
|
name="motion_poll_default_100_percent_base",
|
||||||
default_value="YNA",
|
default_value="YNA",
|
||||||
input_type="choice",
|
input_type="choice",
|
||||||
label="The 100-%-base of an election result consists of",
|
label="The 100 % base of a voting result consists of",
|
||||||
choices=tuple(
|
choices=tuple(
|
||||||
{"value": base[0], "display_name": base[1]}
|
{"value": base[0], "display_name": base[1]}
|
||||||
for base in MotionPoll.PERCENT_BASES
|
for base in MotionPoll.PERCENT_BASES
|
||||||
),
|
),
|
||||||
weight=420,
|
weight=420,
|
||||||
group="Polls",
|
group="Voting",
|
||||||
subgroup="Motions",
|
subgroup="Motions",
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -412,8 +412,9 @@ def get_config_variables():
|
|||||||
for method in MotionPoll.MAJORITY_METHODS
|
for method in MotionPoll.MAJORITY_METHODS
|
||||||
),
|
),
|
||||||
label="Required majority",
|
label="Required majority",
|
||||||
help_text="Default method to check whether a candidate has reached the required majority.",
|
help_text="Default method to check whether a motion has reached the required majority.",
|
||||||
weight=425,
|
weight=425,
|
||||||
group="Polls",
|
hidden=True,
|
||||||
|
group="Voting",
|
||||||
subgroup="Motions",
|
subgroup="Motions",
|
||||||
)
|
)
|
||||||
|
Loading…
Reference in New Issue
Block a user