Some overall improvements
Common: delete unused motion poll list Poll Create form: Fix ugly multi line mat hints (workaround, see https://github.com/angular/components/issues/5227 ) Poll List: Fix too tiny column size user_has_voted_valid (ceck icon) was not shown Motion Poll Card: Enhance subtitle layout (type + state) Assignment Poll Card: Open warning after clicking the hint icon Assignment Poll Chart: Show Absolute values and percents in chart label Assignment Detail: Add new ballot button with plus icon instead of chart icon
This commit is contained in:
parent
64f2720b1a
commit
3c9f6ed278
@ -21,26 +21,24 @@ import { PollData } from 'app/site/polls/services/poll.service';
|
|||||||
name: 'pollPercentBase'
|
name: 'pollPercentBase'
|
||||||
})
|
})
|
||||||
export class PollPercentBasePipe implements PipeTransform {
|
export class PollPercentBasePipe implements PipeTransform {
|
||||||
private decimalPlaces = 3;
|
|
||||||
|
|
||||||
public constructor(
|
public constructor(
|
||||||
private assignmentPollService: AssignmentPollService,
|
private assignmentPollService: AssignmentPollService,
|
||||||
private motionPollService: MotionPollService
|
private motionPollService: MotionPollService
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public transform(value: number, poll: PollData): string | null {
|
public transform(value: number, poll: PollData): string | null {
|
||||||
let totalByBase: number;
|
// logic handles over the pollService to avoid circular dependencies
|
||||||
|
let voteValueInPercent: string;
|
||||||
if ((<any>poll).assignment) {
|
if ((<any>poll).assignment) {
|
||||||
totalByBase = this.assignmentPollService.getPercentBase(poll);
|
voteValueInPercent = this.assignmentPollService.getVoteValueInPercent(value, poll);
|
||||||
} else {
|
} else {
|
||||||
totalByBase = this.motionPollService.getPercentBase(poll);
|
voteValueInPercent = this.motionPollService.getVoteValueInPercent(value, poll);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (totalByBase && totalByBase > 0) {
|
if (voteValueInPercent) {
|
||||||
const percentNumber = (value / totalByBase) * 100;
|
return `(${voteValueInPercent})`;
|
||||||
const result = percentNumber % 1 === 0 ? percentNumber : percentNumber.toFixed(this.decimalPlaces);
|
} else {
|
||||||
return `(${result} %)`;
|
|
||||||
}
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
@ -75,7 +75,7 @@
|
|||||||
<!-- New Ballot button -->
|
<!-- New Ballot button -->
|
||||||
<div class="new-ballot-button" *ngIf="assignment && hasPerms('createPoll')">
|
<div class="new-ballot-button" *ngIf="assignment && hasPerms('createPoll')">
|
||||||
<button mat-stroked-button (click)="openDialog()">
|
<button mat-stroked-button (click)="openDialog()">
|
||||||
<mat-icon color="primary">poll</mat-icon>
|
<mat-icon>add</mat-icon>
|
||||||
<span translate>New ballot</span>
|
<span translate>New ballot</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -14,7 +14,7 @@ import { PromptService } from 'app/core/ui-services/prompt.service';
|
|||||||
import { ChartType } from 'app/shared/components/charts/charts.component';
|
import { ChartType } from 'app/shared/components/charts/charts.component';
|
||||||
import { VoteValue } from 'app/shared/models/poll/base-vote';
|
import { VoteValue } from 'app/shared/models/poll/base-vote';
|
||||||
import { BasePollDetailComponent } from 'app/site/polls/components/base-poll-detail.component';
|
import { BasePollDetailComponent } from 'app/site/polls/components/base-poll-detail.component';
|
||||||
import { PollService, PollTableData, VotingResult } from 'app/site/polls/services/poll.service';
|
import { PollTableData, VotingResult } from 'app/site/polls/services/poll.service';
|
||||||
import { AssignmentPollDialogService } from '../../services/assignment-poll-dialog.service';
|
import { AssignmentPollDialogService } from '../../services/assignment-poll-dialog.service';
|
||||||
import { AssignmentPollService } from '../../services/assignment-poll.service';
|
import { AssignmentPollService } from '../../services/assignment-poll.service';
|
||||||
import { ViewAssignmentPoll } from '../../models/view-assignment-poll';
|
import { ViewAssignmentPoll } from '../../models/view-assignment-poll';
|
||||||
@ -25,7 +25,7 @@ import { ViewAssignmentPoll } from '../../models/view-assignment-poll';
|
|||||||
styleUrls: ['./assignment-poll-detail.component.scss'],
|
styleUrls: ['./assignment-poll-detail.component.scss'],
|
||||||
encapsulation: ViewEncapsulation.None
|
encapsulation: ViewEncapsulation.None
|
||||||
})
|
})
|
||||||
export class AssignmentPollDetailComponent extends BasePollDetailComponent<ViewAssignmentPoll> {
|
export class AssignmentPollDetailComponent extends BasePollDetailComponent<ViewAssignmentPoll, AssignmentPollService> {
|
||||||
public columnDefinitionSingleVotes: PblColumnDefinition[];
|
public columnDefinitionSingleVotes: PblColumnDefinition[];
|
||||||
|
|
||||||
public filterProps = ['user.getFullName'];
|
public filterProps = ['user.getFullName'];
|
||||||
@ -47,10 +47,9 @@ export class AssignmentPollDetailComponent extends BasePollDetailComponent<ViewA
|
|||||||
groupRepo: GroupRepositoryService,
|
groupRepo: GroupRepositoryService,
|
||||||
prompt: PromptService,
|
prompt: PromptService,
|
||||||
pollDialog: AssignmentPollDialogService,
|
pollDialog: AssignmentPollDialogService,
|
||||||
pollService: PollService,
|
protected pollService: AssignmentPollService,
|
||||||
votesRepo: AssignmentVoteRepositoryService,
|
votesRepo: AssignmentVoteRepositoryService,
|
||||||
private operator: OperatorService,
|
private operator: OperatorService,
|
||||||
private assignmentPollService: AssignmentPollService,
|
|
||||||
private router: Router
|
private router: Router
|
||||||
) {
|
) {
|
||||||
super(title, translate, matSnackbar, repo, route, groupRepo, prompt, pollDialog, pollService, votesRepo);
|
super(title, translate, matSnackbar, repo, route, groupRepo, prompt, pollDialog, pollService, votesRepo);
|
||||||
@ -144,11 +143,11 @@ export class AssignmentPollDetailComponent extends BasePollDetailComponent<ViewA
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getTableData(): PollTableData[] {
|
||||||
|
return this.pollService.generateTableData(this.poll);
|
||||||
|
}
|
||||||
|
|
||||||
protected onDeleted(): void {
|
protected onDeleted(): void {
|
||||||
this.router.navigate(['assignments', this.poll.assignment_id]);
|
this.router.navigate(['assignments', this.poll.assignment_id]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public getTableData(): PollTableData[] {
|
|
||||||
return this.assignmentPollService.generateTableData(this.poll);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -9,10 +9,12 @@ import { TranslateService } from '@ngx-translate/core';
|
|||||||
import { AssignmentPollRepositoryService } from 'app/core/repositories/assignments/assignment-poll-repository.service';
|
import { AssignmentPollRepositoryService } from 'app/core/repositories/assignments/assignment-poll-repository.service';
|
||||||
import { PromptService } from 'app/core/ui-services/prompt.service';
|
import { PromptService } from 'app/core/ui-services/prompt.service';
|
||||||
import { ChartType } from 'app/shared/components/charts/charts.component';
|
import { ChartType } from 'app/shared/components/charts/charts.component';
|
||||||
|
import { VotingPrivacyWarningComponent } from 'app/shared/components/voting-privacy-warning/voting-privacy-warning.component';
|
||||||
|
import { infoDialogSettings } from 'app/shared/utils/dialog-settings';
|
||||||
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 { AssignmentPollDialogService } from '../../services/assignment-poll-dialog.service';
|
import { AssignmentPollDialogService } from '../../services/assignment-poll-dialog.service';
|
||||||
import { AssignmentPollPdfService } from '../../services/assignment-poll-pdf.service';
|
import { AssignmentPollPdfService } from '../../services/assignment-poll-pdf.service';
|
||||||
|
import { AssignmentPollService } from '../../services/assignment-poll.service';
|
||||||
import { ViewAssignmentPoll } from '../../models/view-assignment-poll';
|
import { ViewAssignmentPoll } from '../../models/view-assignment-poll';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -62,7 +64,7 @@ export class AssignmentPollComponent extends BasePollComponent<ViewAssignmentPol
|
|||||||
promptService: PromptService,
|
promptService: PromptService,
|
||||||
repo: AssignmentPollRepositoryService,
|
repo: AssignmentPollRepositoryService,
|
||||||
pollDialog: AssignmentPollDialogService,
|
pollDialog: AssignmentPollDialogService,
|
||||||
public pollService: PollService,
|
private pollService: AssignmentPollService,
|
||||||
private formBuilder: FormBuilder,
|
private formBuilder: FormBuilder,
|
||||||
private pdfService: AssignmentPollPdfService
|
private pdfService: AssignmentPollPdfService
|
||||||
) {
|
) {
|
||||||
@ -81,4 +83,8 @@ export class AssignmentPollComponent extends BasePollComponent<ViewAssignmentPol
|
|||||||
public printBallot(): void {
|
public printBallot(): void {
|
||||||
this.pdfService.printBallots(this.poll);
|
this.pdfService.printBallots(this.poll);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public openVotingWarning(): void {
|
||||||
|
this.dialog.open(VotingPrivacyWarningComponent, infoDialogSettings);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,8 @@ import {
|
|||||||
AssignmentPollPercentBase
|
AssignmentPollPercentBase
|
||||||
} from 'app/shared/models/assignments/assignment-poll';
|
} from 'app/shared/models/assignments/assignment-poll';
|
||||||
import { MajorityMethod, VOTE_UNDOCUMENTED } from 'app/shared/models/poll/base-poll';
|
import { MajorityMethod, VOTE_UNDOCUMENTED } from 'app/shared/models/poll/base-poll';
|
||||||
|
import { ParsePollNumberPipe } from 'app/shared/pipes/parse-poll-number.pipe';
|
||||||
|
import { PollKeyVerbosePipe } from 'app/shared/pipes/poll-key-verbose.pipe';
|
||||||
import { PollData, PollService, PollTableData, VotingResult } from 'app/site/polls/services/poll.service';
|
import { PollData, PollService, PollTableData, VotingResult } from 'app/site/polls/services/poll.service';
|
||||||
import { ViewAssignmentPoll } from '../models/view-assignment-poll';
|
import { ViewAssignmentPoll } from '../models/view-assignment-poll';
|
||||||
|
|
||||||
@ -41,10 +43,12 @@ export class AssignmentPollService extends PollService {
|
|||||||
public constructor(
|
public constructor(
|
||||||
config: ConfigService,
|
config: ConfigService,
|
||||||
constants: ConstantsService,
|
constants: ConstantsService,
|
||||||
private translate: TranslateService,
|
pollKeyVerbose: PollKeyVerbosePipe,
|
||||||
|
parsePollNumber: ParsePollNumberPipe,
|
||||||
|
protected translate: TranslateService,
|
||||||
private pollRepo: AssignmentPollRepositoryService
|
private pollRepo: AssignmentPollRepositoryService
|
||||||
) {
|
) {
|
||||||
super(constants);
|
super(constants, translate, pollKeyVerbose, parsePollNumber);
|
||||||
config
|
config
|
||||||
.get<AssignmentPollPercentBase>('assignment_poll_default_100_percent_base')
|
.get<AssignmentPollPercentBase>('assignment_poll_default_100_percent_base')
|
||||||
.subscribe(base => (this.defaultPercentBase = base));
|
.subscribe(base => (this.defaultPercentBase = base));
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Component, OnInit, ViewEncapsulation } from '@angular/core';
|
import { Component, ViewEncapsulation } from '@angular/core';
|
||||||
import { MatSnackBar } from '@angular/material';
|
import { MatSnackBar } from '@angular/material';
|
||||||
import { Title } from '@angular/platform-browser';
|
import { Title } from '@angular/platform-browser';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
@ -14,8 +14,8 @@ import { PromptService } from 'app/core/ui-services/prompt.service';
|
|||||||
import { ViewMotion } from 'app/site/motions/models/view-motion';
|
import { ViewMotion } from 'app/site/motions/models/view-motion';
|
||||||
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 { MotionPollService } from 'app/site/motions/services/motion-poll.service';
|
||||||
import { BasePollDetailComponent } from 'app/site/polls/components/base-poll-detail.component';
|
import { BasePollDetailComponent } from 'app/site/polls/components/base-poll-detail.component';
|
||||||
import { PollService } from 'app/site/polls/services/poll.service';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'os-motion-poll-detail',
|
selector: 'os-motion-poll-detail',
|
||||||
@ -23,7 +23,7 @@ import { PollService } from 'app/site/polls/services/poll.service';
|
|||||||
styleUrls: ['./motion-poll-detail.component.scss'],
|
styleUrls: ['./motion-poll-detail.component.scss'],
|
||||||
encapsulation: ViewEncapsulation.None
|
encapsulation: ViewEncapsulation.None
|
||||||
})
|
})
|
||||||
export class MotionPollDetailComponent extends BasePollDetailComponent<ViewMotionPoll> implements OnInit {
|
export class MotionPollDetailComponent extends BasePollDetailComponent<ViewMotionPoll, MotionPollService> {
|
||||||
public motion: ViewMotion;
|
public motion: ViewMotion;
|
||||||
public columnDefinition: PblColumnDefinition[] = [
|
public columnDefinition: PblColumnDefinition[] = [
|
||||||
{
|
{
|
||||||
@ -49,7 +49,7 @@ export class MotionPollDetailComponent extends BasePollDetailComponent<ViewMotio
|
|||||||
groupRepo: GroupRepositoryService,
|
groupRepo: GroupRepositoryService,
|
||||||
prompt: PromptService,
|
prompt: PromptService,
|
||||||
pollDialog: MotionPollDialogService,
|
pollDialog: MotionPollDialogService,
|
||||||
pollService: PollService,
|
pollService: MotionPollService,
|
||||||
votesRepo: MotionVoteRepositoryService,
|
votesRepo: MotionVoteRepositoryService,
|
||||||
private operator: OperatorService,
|
private operator: OperatorService,
|
||||||
private router: Router
|
private router: Router
|
||||||
|
@ -1,36 +0,0 @@
|
|||||||
<os-head-bar>
|
|
||||||
<div class="title-slot">Motions poll list</div>
|
|
||||||
|
|
||||||
<!-- Menu -->
|
|
||||||
<div class="menu-slot">
|
|
||||||
<button type="button" mat-icon-button [matMenuTriggerFor]="pollMenu"><mat-icon>more_vert</mat-icon></button>
|
|
||||||
</div>
|
|
||||||
</os-head-bar>
|
|
||||||
|
|
||||||
<os-list-view-table
|
|
||||||
[listObservableProvider]="repo"
|
|
||||||
[vScrollFixed]="64"
|
|
||||||
[columns]="tableColumnDefinition"
|
|
||||||
[listStorageKey]="'motion-polls'"
|
|
||||||
>
|
|
||||||
<div *pblNgridCellDef="'title'; row as poll; rowContext as context" class="cell-slot fill">
|
|
||||||
<a
|
|
||||||
class="detail-link"
|
|
||||||
(click)="saveScrollIndex('motion-polls', rowContext.identity)"
|
|
||||||
[routerLink]="poll.id"
|
|
||||||
*ngIf="!isMultiSelect"
|
|
||||||
></a>
|
|
||||||
<span>{{ poll.title }}</span>
|
|
||||||
</div>
|
|
||||||
<div *pblNgridCellDef="'state'; row as poll; rowContext as context" class="cell-slot fill">
|
|
||||||
<span>{{ poll.stateVerbose }}</span>
|
|
||||||
</div>
|
|
||||||
</os-list-view-table>
|
|
||||||
|
|
||||||
<mat-menu #pollMenu="matMenu">
|
|
||||||
<!-- Settings -->
|
|
||||||
<button mat-menu-item *osPerms="'core.can_manage_config'" routerLink="/settings/polls">
|
|
||||||
<mat-icon>settings</mat-icon>
|
|
||||||
<span translate>Settings</span>
|
|
||||||
</button>
|
|
||||||
</mat-menu>
|
|
@ -1,27 +0,0 @@
|
|||||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
|
||||||
|
|
||||||
import { E2EImportsModule } from 'e2e-imports.module';
|
|
||||||
|
|
||||||
import { MotionPollListComponent } from './motion-poll-list.component';
|
|
||||||
|
|
||||||
describe('MotionPollListComponent', () => {
|
|
||||||
let component: MotionPollListComponent;
|
|
||||||
let fixture: ComponentFixture<MotionPollListComponent>;
|
|
||||||
|
|
||||||
beforeEach(async(() => {
|
|
||||||
TestBed.configureTestingModule({
|
|
||||||
imports: [E2EImportsModule],
|
|
||||||
declarations: [MotionPollListComponent]
|
|
||||||
}).compileComponents();
|
|
||||||
}));
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
fixture = TestBed.createComponent(MotionPollListComponent);
|
|
||||||
component = fixture.componentInstance;
|
|
||||||
fixture.detectChanges();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should create', () => {
|
|
||||||
expect(component).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,45 +0,0 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
|
||||||
import { MatSnackBar } from '@angular/material';
|
|
||||||
import { Title } from '@angular/platform-browser';
|
|
||||||
|
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
|
||||||
import { PblColumnDefinition } from '@pebula/ngrid';
|
|
||||||
|
|
||||||
import { StorageService } from 'app/core/core-services/storage.service';
|
|
||||||
import { MotionPollRepositoryService } from 'app/core/repositories/motions/motion-poll-repository.service';
|
|
||||||
import { BaseListViewComponent } from 'app/site/base/base-list-view';
|
|
||||||
import { ViewMotionPoll } from 'app/site/motions/models/view-motion-poll';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'os-motion-poll-list',
|
|
||||||
templateUrl: './motion-poll-list.component.html',
|
|
||||||
styleUrls: ['./motion-poll-list.component.scss']
|
|
||||||
})
|
|
||||||
export class MotionPollListComponent extends BaseListViewComponent<ViewMotionPoll> implements OnInit {
|
|
||||||
public tableColumnDefinition: PblColumnDefinition[] = [
|
|
||||||
{
|
|
||||||
prop: 'title',
|
|
||||||
width: 'auto'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: 'state',
|
|
||||||
width: 'auto'
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
public polls: ViewMotionPoll[] = [];
|
|
||||||
|
|
||||||
public constructor(
|
|
||||||
title: Title,
|
|
||||||
protected translate: TranslateService,
|
|
||||||
matSnackbar: MatSnackBar,
|
|
||||||
storage: StorageService,
|
|
||||||
public repo: MotionPollRepositoryService
|
|
||||||
) {
|
|
||||||
super(title, translate, matSnackbar, storage);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ngOnInit(): void {
|
|
||||||
this.subscriptions.push(this.repo.getViewModelListObservable().subscribe(polls => (this.polls = polls)));
|
|
||||||
}
|
|
||||||
}
|
|
@ -2,10 +2,8 @@ import { NgModule } from '@angular/core';
|
|||||||
import { RouterModule, Routes } from '@angular/router';
|
import { RouterModule, Routes } from '@angular/router';
|
||||||
|
|
||||||
import { MotionPollDetailComponent } from './motion-poll-detail/motion-poll-detail.component';
|
import { MotionPollDetailComponent } from './motion-poll-detail/motion-poll-detail.component';
|
||||||
import { MotionPollListComponent } from './motion-poll-list/motion-poll-list.component';
|
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{ path: '', component: MotionPollListComponent, pathMatch: 'full' },
|
|
||||||
{ path: 'new', component: MotionPollDetailComponent },
|
{ path: 'new', component: MotionPollDetailComponent },
|
||||||
{ path: ':id', component: MotionPollDetailComponent }
|
{ path: ':id', component: MotionPollDetailComponent }
|
||||||
];
|
];
|
||||||
|
@ -4,7 +4,6 @@ import { NgModule } from '@angular/core';
|
|||||||
import { SharedModule } from 'app/shared/shared.module';
|
import { SharedModule } from 'app/shared/shared.module';
|
||||||
import { PollsModule } from 'app/site/polls/polls.module';
|
import { PollsModule } from 'app/site/polls/polls.module';
|
||||||
import { MotionPollDetailComponent } from './motion-poll-detail/motion-poll-detail.component';
|
import { MotionPollDetailComponent } from './motion-poll-detail/motion-poll-detail.component';
|
||||||
import { MotionPollListComponent } from './motion-poll-list/motion-poll-list.component';
|
|
||||||
import { MotionPollRoutingModule } from './motion-poll-routing.module';
|
import { MotionPollRoutingModule } from './motion-poll-routing.module';
|
||||||
import { MotionPollVoteComponent } from './motion-poll-vote/motion-poll-vote.component';
|
import { MotionPollVoteComponent } from './motion-poll-vote/motion-poll-vote.component';
|
||||||
import { MotionPollComponent } from './motion-poll/motion-poll.component';
|
import { MotionPollComponent } from './motion-poll/motion-poll.component';
|
||||||
@ -12,6 +11,6 @@ import { MotionPollComponent } from './motion-poll/motion-poll.component';
|
|||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [CommonModule, SharedModule, MotionPollRoutingModule, PollsModule],
|
imports: [CommonModule, SharedModule, MotionPollRoutingModule, PollsModule],
|
||||||
exports: [MotionPollComponent],
|
exports: [MotionPollComponent],
|
||||||
declarations: [MotionPollComponent, MotionPollDetailComponent, MotionPollListComponent, MotionPollVoteComponent]
|
declarations: [MotionPollComponent, MotionPollDetailComponent, MotionPollVoteComponent]
|
||||||
})
|
})
|
||||||
export class MotionPollModule {}
|
export class MotionPollModule {}
|
||||||
|
@ -2,15 +2,25 @@
|
|||||||
<!-- Poll Infos -->
|
<!-- Poll Infos -->
|
||||||
<div class="poll-title-wrapper">
|
<div class="poll-title-wrapper">
|
||||||
<!-- Title Area -->
|
<!-- Title Area -->
|
||||||
<div class="poll-title-area spacer-bottom-20">
|
<div class="poll-title-area">
|
||||||
<!-- Title -->
|
<!-- Title -->
|
||||||
<span class="poll-title">
|
<span class="poll-title">
|
||||||
<a [routerLink]="pollLink">
|
<a [routerLink]="pollLink">
|
||||||
{{ poll.title | translate }}
|
{{ poll.title | translate }}
|
||||||
</a>
|
</a>
|
||||||
</span>
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="italic">
|
<!-- Dot Menu -->
|
||||||
|
<span class="poll-actions" *osPerms="'motions.can_manage_polls'">
|
||||||
|
<button mat-icon-button [matMenuTriggerFor]="pollDetailMenu">
|
||||||
|
<mat-icon class="small-icon">more_horiz</mat-icon>
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Subtitle -->
|
||||||
|
<div class="italic spacer-bottom-20">
|
||||||
<span *osPerms="'motions.can_manage_polls'; and: poll.type === 'pseudoanonymous'">
|
<span *osPerms="'motions.can_manage_polls'; and: poll.type === 'pseudoanonymous'">
|
||||||
<button mat-icon-button color="warn" (click)="openVotingWarning()">
|
<button mat-icon-button color="warn" (click)="openVotingWarning()">
|
||||||
<mat-icon>
|
<mat-icon>
|
||||||
@ -29,15 +39,6 @@
|
|||||||
{{ poll.stateVerbose | translate }}
|
{{ poll.stateVerbose | translate }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Dot Menu -->
|
|
||||||
<span class="poll-actions" *osPerms="'motions.can_manage_polls'">
|
|
||||||
<button mat-icon-button [matMenuTriggerFor]="pollDetailMenu">
|
|
||||||
<mat-icon class="small-icon">more_horiz</mat-icon>
|
|
||||||
</button>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Change state button -->
|
<!-- Change state button -->
|
||||||
<div *osPerms="'motions.can_manage_polls'; and: !hideChangeState">
|
<div *osPerms="'motions.can_manage_polls'; and: !hideChangeState">
|
||||||
|
@ -7,6 +7,8 @@ import { MotionPollRepositoryService } from 'app/core/repositories/motions/motio
|
|||||||
import { ConfigService } from 'app/core/ui-services/config.service';
|
import { ConfigService } from 'app/core/ui-services/config.service';
|
||||||
import { MotionPoll, MotionPollMethod } from 'app/shared/models/motions/motion-poll';
|
import { MotionPoll, MotionPollMethod } from 'app/shared/models/motions/motion-poll';
|
||||||
import { MajorityMethod, PercentBase } from 'app/shared/models/poll/base-poll';
|
import { MajorityMethod, PercentBase } from 'app/shared/models/poll/base-poll';
|
||||||
|
import { ParsePollNumberPipe } from 'app/shared/pipes/parse-poll-number.pipe';
|
||||||
|
import { PollKeyVerbosePipe } from 'app/shared/pipes/poll-key-verbose.pipe';
|
||||||
import { PollData, PollService, PollTableData, VotingResult } from 'app/site/polls/services/poll.service';
|
import { PollData, PollService, PollTableData, VotingResult } from 'app/site/polls/services/poll.service';
|
||||||
import { ViewMotionOption } from '../models/view-motion-option';
|
import { ViewMotionOption } from '../models/view-motion-option';
|
||||||
import { ViewMotionPoll } from '../models/view-motion-poll';
|
import { ViewMotionPoll } from '../models/view-motion-poll';
|
||||||
@ -43,10 +45,12 @@ export class MotionPollService extends PollService {
|
|||||||
public constructor(
|
public constructor(
|
||||||
config: ConfigService,
|
config: ConfigService,
|
||||||
constants: ConstantsService,
|
constants: ConstantsService,
|
||||||
private translate: TranslateService,
|
pollKeyVerbose: PollKeyVerbosePipe,
|
||||||
|
parsePollNumber: ParsePollNumberPipe,
|
||||||
|
protected translate: TranslateService,
|
||||||
private pollRepo: MotionPollRepositoryService
|
private pollRepo: MotionPollRepositoryService
|
||||||
) {
|
) {
|
||||||
super(constants);
|
super(constants, translate, pollKeyVerbose, parsePollNumber);
|
||||||
config
|
config
|
||||||
.get<PercentBase>('motion_poll_default_100_percent_base')
|
.get<PercentBase>('motion_poll_default_100_percent_base')
|
||||||
.subscribe(base => (this.defaultPercentBase = base));
|
.subscribe(base => (this.defaultPercentBase = base));
|
||||||
|
@ -27,7 +27,8 @@ export interface BaseVoteData {
|
|||||||
user?: ViewUser;
|
user?: ViewUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
export abstract class BasePollDetailComponent<V extends ViewBasePoll> extends BaseViewComponent implements OnInit {
|
export abstract class BasePollDetailComponent<V extends ViewBasePoll, S extends PollService> extends BaseViewComponent
|
||||||
|
implements OnInit {
|
||||||
/**
|
/**
|
||||||
* All the groups of users.
|
* All the groups of users.
|
||||||
*/
|
*/
|
||||||
@ -100,7 +101,7 @@ export abstract class BasePollDetailComponent<V extends ViewBasePoll> extends Ba
|
|||||||
protected groupRepo: GroupRepositoryService,
|
protected groupRepo: GroupRepositoryService,
|
||||||
protected promptService: PromptService,
|
protected promptService: PromptService,
|
||||||
protected pollDialog: BasePollDialogService<V>,
|
protected pollDialog: BasePollDialogService<V>,
|
||||||
protected pollService: PollService,
|
protected pollService: S,
|
||||||
protected votesRepo: BaseRepository<ViewBaseVote, BaseVote, object>
|
protected votesRepo: BaseRepository<ViewBaseVote, BaseVote, object>
|
||||||
) {
|
) {
|
||||||
super(title, translate, matSnackbar);
|
super(title, translate, matSnackbar);
|
||||||
|
@ -10,20 +10,35 @@
|
|||||||
[filterProps]="filterProps"
|
[filterProps]="filterProps"
|
||||||
[filterService]="filterService"
|
[filterService]="filterService"
|
||||||
>
|
>
|
||||||
<div *pblNgridCellDef="'title'; row as poll; rowContext as context;" class="cell-slot fill">
|
<!-- Poll Title -->
|
||||||
|
<div *pblNgridCellDef="'title'; row as poll; rowContext as context" class="cell-slot fill">
|
||||||
<a class="detail-link" [routerLink]="poll.parentLink" *ngIf="!isMultiSelect"></a>
|
<a class="detail-link" [routerLink]="poll.parentLink" *ngIf="!isMultiSelect"></a>
|
||||||
<span>{{ poll.title }}</span>
|
<span>{{ poll.title }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div *pblNgridCellDef="'classType'; row as poll;" class="cell-slot fill">
|
|
||||||
|
<!-- Motion Or Assigmnent Title Title -->
|
||||||
|
<div *pblNgridCellDef="'classType'; row as poll" class="cell-slot fill">
|
||||||
<a class="detail-link" [routerLink]="poll.parentLink" *ngIf="!isMultiSelect"></a>
|
<a class="detail-link" [routerLink]="poll.parentLink" *ngIf="!isMultiSelect"></a>
|
||||||
<span>{{ poll.getContentObject().getListTitle() }}</span>
|
<span>{{ poll.getContentObject().getListTitle() }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div *pblNgridCellDef="'state'; row as poll;" class="cell-slot fill">
|
|
||||||
|
<!-- State -->
|
||||||
|
<div *pblNgridCellDef="'state'; row as poll" class="cell-slot fill">
|
||||||
<a class="detail-link" [routerLink]="poll.parentLink" *ngIf="!isMultiSelect"></a>
|
<a class="detail-link" [routerLink]="poll.parentLink" *ngIf="!isMultiSelect"></a>
|
||||||
<span>{{ poll.stateVerbose | translate }}</span>
|
<span>{{ poll.stateVerbose | translate }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div *pblNgridCellDef="'votability'; row as poll;" class="cell-slot fill">
|
|
||||||
<mat-icon *ngIf="poll.user_has_voted_valid" color="accent" matTooltip="{{ 'You have already voted.' | translate }}">check_circle</mat-icon>
|
<!-- Voted Indicator -->
|
||||||
<mat-icon *ngIf="!poll.user_has_voted_valid && poll.canBeVotedFor" color="warn" matTooltip="{{ 'Voting is currently in progress.' | translate }}">warning</mat-icon>
|
<div *pblNgridCellDef="'votability'; row as poll" class="cell-slot fill">
|
||||||
|
<mat-icon *ngIf="poll.user_has_voted" color="accent" matTooltip="{{ 'You have already voted.' | translate }}">
|
||||||
|
check_circle
|
||||||
|
</mat-icon>
|
||||||
|
<mat-icon
|
||||||
|
*ngIf="!poll.user_has_voted && poll.canBeVotedFor"
|
||||||
|
color="warn"
|
||||||
|
matTooltip="{{ 'Voting is currently in progress.' | translate }}"
|
||||||
|
>
|
||||||
|
warning
|
||||||
|
</mat-icon>
|
||||||
</div>
|
</div>
|
||||||
</os-list-view-table>
|
</os-list-view-table>
|
||||||
|
@ -29,7 +29,7 @@ export class PollListComponent extends BaseListViewComponent<ViewBasePoll> {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
prop: 'state',
|
prop: 'state',
|
||||||
width: '70px'
|
width: 'auto'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
prop: 'votability',
|
prop: 'votability',
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
|
|
||||||
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
|
||||||
import { _ } from 'app/core/translate/translation-marker';
|
import { _ } from 'app/core/translate/translation-marker';
|
||||||
import { ChartData, ChartDate } from 'app/shared/components/charts/charts.component';
|
import { ChartData, ChartDate } from 'app/shared/components/charts/charts.component';
|
||||||
import { AssignmentPollMethod } from 'app/shared/models/assignments/assignment-poll';
|
import { AssignmentPollMethod } from 'app/shared/models/assignments/assignment-poll';
|
||||||
@ -11,6 +13,8 @@ import {
|
|||||||
PollType,
|
PollType,
|
||||||
VOTE_UNDOCUMENTED
|
VOTE_UNDOCUMENTED
|
||||||
} from 'app/shared/models/poll/base-poll';
|
} from 'app/shared/models/poll/base-poll';
|
||||||
|
import { ParsePollNumberPipe } from 'app/shared/pipes/parse-poll-number.pipe';
|
||||||
|
import { PollKeyVerbosePipe } from 'app/shared/pipes/poll-key-verbose.pipe';
|
||||||
import { AssignmentPollMethodVerbose } from 'app/site/assignments/models/view-assignment-poll';
|
import { AssignmentPollMethodVerbose } from 'app/site/assignments/models/view-assignment-poll';
|
||||||
import {
|
import {
|
||||||
MajorityMethodVerbose,
|
MajorityMethodVerbose,
|
||||||
@ -21,6 +25,7 @@ import {
|
|||||||
} from 'app/site/polls/models/view-base-poll';
|
} from 'app/site/polls/models/view-base-poll';
|
||||||
import { ConstantsService } from '../../../core/core-services/constants.service';
|
import { ConstantsService } from '../../../core/core-services/constants.service';
|
||||||
|
|
||||||
|
const PERCENT_DECIMAL_PLACES = 3;
|
||||||
/**
|
/**
|
||||||
* The possible keys of a poll object that represent numbers.
|
* The possible keys of a poll object that represent numbers.
|
||||||
* TODO Should be 'key of MotionPoll|AssinmentPoll if type of key is number'
|
* TODO Should be 'key of MotionPoll|AssinmentPoll if type of key is number'
|
||||||
@ -178,12 +183,32 @@ export abstract class PollService {
|
|||||||
*/
|
*/
|
||||||
public pollValues: CalculablePollKey[] = ['yes', 'no', 'abstain', 'votesvalid', 'votesinvalid', 'votescast'];
|
public pollValues: CalculablePollKey[] = ['yes', 'no', 'abstain', 'votesvalid', 'votesinvalid', 'votescast'];
|
||||||
|
|
||||||
public constructor(constants: ConstantsService) {
|
public constructor(
|
||||||
|
constants: ConstantsService,
|
||||||
|
protected translate: TranslateService,
|
||||||
|
private pollKeyVerbose: PollKeyVerbosePipe,
|
||||||
|
private parsePollNumber: ParsePollNumberPipe
|
||||||
|
) {
|
||||||
constants
|
constants
|
||||||
.get<OpenSlidesSettings>('Settings')
|
.get<OpenSlidesSettings>('Settings')
|
||||||
.subscribe(settings => (this.isElectronicVotingEnabled = settings.ENABLE_ELECTRONIC_VOTING));
|
.subscribe(settings => (this.isElectronicVotingEnabled = settings.ENABLE_ELECTRONIC_VOTING));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* return the total number of votes depending on the selected percent base
|
||||||
|
*/
|
||||||
|
public abstract getPercentBase(poll: PollData): number;
|
||||||
|
|
||||||
|
public getVoteValueInPercent(value: number, poll: PollData): string | null {
|
||||||
|
const totalByBase = this.getPercentBase(poll);
|
||||||
|
if (totalByBase && totalByBase > 0) {
|
||||||
|
const percentNumber = (value / totalByBase) * 100;
|
||||||
|
const result = percentNumber % 1 === 0 ? percentNumber : percentNumber.toFixed(PERCENT_DECIMAL_PLACES);
|
||||||
|
return `${result} %`;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Assigns the default poll data to the object. To be extended in subclasses
|
* Assigns the default poll data to the object. To be extended in subclasses
|
||||||
* @param poll the poll/object to fill
|
* @param poll the poll/object to fill
|
||||||
@ -268,8 +293,24 @@ export abstract class PollService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public generateChartData(poll: PollData | ViewBasePoll): ChartData {
|
public generateChartData(poll: PollData | ViewBasePoll): ChartData {
|
||||||
|
const fields = this.getPollDataFields(poll);
|
||||||
|
|
||||||
|
const data: ChartData = fields.map(key => {
|
||||||
|
return {
|
||||||
|
data: this.getResultFromPoll(poll, key),
|
||||||
|
label: key.toUpperCase(),
|
||||||
|
backgroundColor: PollColor[key],
|
||||||
|
hoverBackgroundColor: PollColor[key]
|
||||||
|
} as ChartDate;
|
||||||
|
});
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getPollDataFields(poll: PollData | ViewBasePoll): CalculablePollKey[] {
|
||||||
let fields: CalculablePollKey[];
|
let fields: CalculablePollKey[];
|
||||||
let isAssignment: boolean;
|
let isAssignment: boolean;
|
||||||
|
|
||||||
if (poll instanceof ViewBasePoll) {
|
if (poll instanceof ViewBasePoll) {
|
||||||
isAssignment = poll.pollClassType === 'assignment';
|
isAssignment = poll.pollClassType === 'assignment';
|
||||||
} else {
|
} else {
|
||||||
@ -294,16 +335,7 @@ export abstract class PollService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const data: ChartData = fields.map(key => {
|
return fields;
|
||||||
return {
|
|
||||||
data: this.getResultFromPoll(poll, key),
|
|
||||||
label: key.toUpperCase(),
|
|
||||||
backgroundColor: PollColor[key],
|
|
||||||
hoverBackgroundColor: PollColor[key]
|
|
||||||
} as ChartDate;
|
|
||||||
});
|
|
||||||
|
|
||||||
return data;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -314,7 +346,17 @@ export abstract class PollService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public getChartLabels(poll: PollData): string[] {
|
public getChartLabels(poll: PollData): string[] {
|
||||||
return poll.options.map(candidate => candidate.user.short_name);
|
const fields = this.getPollDataFields(poll);
|
||||||
|
return poll.options.map(option => {
|
||||||
|
const votingResults = fields.map(field => {
|
||||||
|
const votingKey = this.translate.instant(this.pollKeyVerbose.transform(field));
|
||||||
|
const resultValue = this.parsePollNumber.transform(option[field]);
|
||||||
|
const resultInPercent = this.getVoteValueInPercent(option[field], poll);
|
||||||
|
return `${votingKey} ${resultValue} (${resultInPercent})`;
|
||||||
|
});
|
||||||
|
|
||||||
|
return `${option.user.short_name} · ${votingResults.join(' · ')}`;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public isVoteDocumented(vote: number): boolean {
|
public isVoteDocumented(vote: number): boolean {
|
||||||
|
@ -864,6 +864,32 @@ button.mat-menu-item.selected {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fix to enable multi line mat hints. See:
|
||||||
|
* https://github.com/angular/components/issues/5227
|
||||||
|
*/
|
||||||
|
.mat-form-field {
|
||||||
|
.mat-form-field-wrapper {
|
||||||
|
padding-bottom: 0;
|
||||||
|
|
||||||
|
.mat-form-field-underline {
|
||||||
|
position: initial !important;
|
||||||
|
display: block;
|
||||||
|
margin-top: -1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-form-field-subscript-wrapper,
|
||||||
|
.mat-form-field-ripple {
|
||||||
|
position: initial !important;
|
||||||
|
display: table;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-form-field-subscript-wrapper {
|
||||||
|
min-height: calc(1em + 1px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Use to disable events on (i.e) matMenuTriggerFor
|
* Use to disable events on (i.e) matMenuTriggerFor
|
||||||
*/
|
*/
|
||||||
|
Loading…
Reference in New Issue
Block a user