Motion poll detail als slide

Refactor the code to use the motion poll detail als slide component
This commit is contained in:
Sean Engelhardt 2020-03-12 17:08:04 +01:00 committed by FinnStutzenstein
parent 3c36441967
commit 29a9a09bc6
26 changed files with 381 additions and 394 deletions

View File

@ -0,0 +1,39 @@
<div class="result-wrapper" *ngIf="hasVotes">
<!-- result table -->
<table class="result-table">
<tbody>
<tr>
<th></th>
<th colspan="2" translate>Votes</th>
</tr>
<tr *ngFor="let row of getTableData()" [class]="row.votingOption">
<!-- YNA/Valid etc -->
<td>
<os-icon-container *ngIf="row.value[0].icon" [icon]="row.value[0].icon">
{{ row.votingOption | pollKeyVerbose | translate }}
</os-icon-container>
<span *ngIf="!row.value[0].icon">
{{ row.votingOption | pollKeyVerbose | translate }}
</span>
</td>
<!-- Percent numbers -->
<td class="result-cell-definition">
<span *ngIf="row.value[0].showPercent">
{{ row.value[0].amount | pollPercentBase: poll }}
</span>
</td>
<!-- Voices -->
<td class="result-cell-definition">
{{ row.value[0].amount | parsePollNumber }}
</td>
</tr>
</tbody>
</table>
<!-- Chart -->
<div class="doughnut-chart" *ngIf="showChart">
<os-charts type="doughnut" [data]="chartData" [showLegend]="false" [hasPadding]="false"></os-charts>
</div>
</div>

View File

@ -0,0 +1,43 @@
@import '~assets/styles/poll-colors.scss';
.result-wrapper {
display: grid;
grid-gap: 2em;
margin: 2em;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
.result-table {
// display: block;
th {
text-align: right;
font-weight: initial;
}
tr {
height: 48px;
border-bottom: none !important;
.result-cell-definition {
text-align: right;
}
}
.yes {
color: $votes-yes-color;
}
.no {
color: $votes-no-color;
}
.abstain {
color: $votes-abstain-color;
}
}
.doughnut-chart {
display: block;
margin-top: auto;
margin-bottom: auto;
}
}

View File

@ -0,0 +1,26 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { E2EImportsModule } from 'e2e-imports.module';
import { MotionPollDetailContentComponent } from './motion-poll-detail-content.component';
describe('MotionPollDetailContentComponent', () => {
let component: MotionPollDetailContentComponent;
let fixture: ComponentFixture<MotionPollDetailContentComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [E2EImportsModule]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(MotionPollDetailContentComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,37 @@
import { Component, Input, OnInit } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
import { ViewMotionPoll } from 'app/site/motions/models/view-motion-poll';
import { MotionPollService } from 'app/site/motions/services/motion-poll.service';
import { PollData, PollTableData } from 'app/site/polls/services/poll.service';
import { ChartData } from '../charts/charts.component';
@Component({
selector: 'os-motion-poll-detail-content',
templateUrl: './motion-poll-detail-content.component.html',
styleUrls: ['./motion-poll-detail-content.component.scss']
})
export class MotionPollDetailContentComponent implements OnInit {
@Input()
public poll: ViewMotionPoll | PollData;
@Input()
public chartData: BehaviorSubject<ChartData>;
public get hasVotes(): boolean {
return this.poll && !!this.poll.options;
}
public constructor(private motionPollService: MotionPollService) {}
public ngOnInit(): void {}
public getTableData(): PollTableData[] {
return this.motionPollService.generateTableData(this.poll);
}
public get showChart(): boolean {
return this.motionPollService.showChart(this.poll) && this.chartData && !!this.chartData.value;
}
}

View File

@ -73,10 +73,6 @@ export abstract class BasePoll<
return this.state === PollState.Published;
}
public get isPercentBaseValidOrCast(): boolean {
return this.onehundred_percent_base === PercentBase.Valid || this.onehundred_percent_base === PercentBase.Cast;
}
public get isPercentBaseCast(): boolean {
return this.onehundred_percent_base === PercentBase.Cast;
}

View File

@ -122,6 +122,7 @@ import { ReversePipe } from './pipes/reverse.pipe';
import { PollKeyVerbosePipe } from './pipes/poll-key-verbose.pipe';
import { PollPercentBasePipe } from './pipes/poll-percent-base.pipe';
import { VotingPrivacyWarningComponent } from './components/voting-privacy-warning/voting-privacy-warning.component';
import { MotionPollDetailContentComponent } from './components/motion-poll-detail-content/motion-poll-detail-content.component';
/**
* Share Module for all "dumb" components and pipes.
@ -286,7 +287,8 @@ import { VotingPrivacyWarningComponent } from './components/voting-privacy-warni
ReversePipe,
PollKeyVerbosePipe,
PollPercentBasePipe,
VotingPrivacyWarningComponent
VotingPrivacyWarningComponent,
MotionPollDetailContentComponent
],
declarations: [
PermsDirective,
@ -344,7 +346,8 @@ import { VotingPrivacyWarningComponent } from './components/voting-privacy-warni
ReversePipe,
PollKeyVerbosePipe,
PollPercentBasePipe,
VotingPrivacyWarningComponent
VotingPrivacyWarningComponent,
MotionPollDetailContentComponent
],
providers: [
{

View File

@ -38,7 +38,7 @@
<th class="result voted-no" translate *ngIf="!poll.isMethodY">No</th>
<th class="result voted-abstain" translate *ngIf="poll.isMethodYNA">Abstain</th>
</tr>
<tr *ngFor="let row of poll.tableData" [class]="row.class">
<tr *ngFor="let row of getTableData()" [class]="row.class">
<td class="voting-option">
<div>
<span>

View File

@ -13,9 +13,9 @@ import { GroupRepositoryService } from 'app/core/repositories/users/group-reposi
import { PromptService } from 'app/core/ui-services/prompt.service';
import { ChartType } from 'app/shared/components/charts/charts.component';
import { BasePollDetailComponent } from 'app/site/polls/components/base-poll-detail.component';
import { VotingResult } from 'app/site/polls/models/view-base-poll';
import { PollService } from 'app/site/polls/services/poll.service';
import { PollService, PollTableData, VotingResult } from 'app/site/polls/services/poll.service';
import { AssignmentPollDialogService } from '../../services/assignment-poll-dialog.service';
import { AssignmentPollService } from '../../services/assignment-poll.service';
import { ViewAssignmentPoll } from '../../models/view-assignment-poll';
@Component({
@ -48,7 +48,8 @@ export class AssignmentPollDetailComponent extends BasePollDetailComponent<ViewA
pollDialog: AssignmentPollDialogService,
pollService: PollService,
votesRepo: AssignmentVoteRepositoryService,
private operator: OperatorService
private operator: OperatorService,
private assignmentPollService: AssignmentPollService
) {
super(title, translate, matSnackbar, repo, route, groupRepo, prompt, pollDialog, pollService, votesRepo);
}
@ -133,4 +134,8 @@ export class AssignmentPollDetailComponent extends BasePollDetailComponent<ViewA
}
return true;
}
public getTableData(): PollTableData[] {
return this.assignmentPollService.generateTableData(this.poll);
}
}

View File

@ -8,7 +8,7 @@ import {
} from 'app/shared/models/assignments/assignment-poll';
import { BaseViewModel } from 'app/site/base/base-view-model';
import { ProjectorElementBuildDeskriptor } from 'app/site/base/projectable';
import { PollClassType, PollTableData, ViewBasePoll, VotingResult } from 'app/site/polls/models/view-base-poll';
import { PollClassType, ViewBasePoll } from 'app/site/polls/models/view-base-poll';
import { ViewAssignment } from './view-assignment';
import { ViewAssignmentOption } from './view-assignment-option';
@ -39,19 +39,6 @@ export class ViewAssignmentPoll extends ViewBasePoll<AssignmentPoll, AssignmentP
public readonly tableChartData: Map<string, BehaviorSubject<ChartData>> = new Map();
public readonly pollClassType = PollClassType.Assignment;
protected globalVoteKeys: VotingResult[] = [
{
vote: 'amount_global_no',
showPercent: false,
hide: this.poll.amount_global_no === -2 || this.poll.amount_global_no === 0
},
{
vote: 'amount_global_abstain',
showPercent: false,
hide: this.poll.amount_global_abstain === -2 || this.poll.amount_global_abstain === 0
}
];
public get pollmethodVerbose(): string {
return AssignmentPollMethodVerbose[this.pollmethod];
}
@ -77,62 +64,6 @@ export class ViewAssignmentPoll extends ViewBasePoll<AssignmentPoll, AssignmentP
};
}
public generateTableData(): PollTableData[] {
const tableData: PollTableData[] = this.options.map(candidate => ({
votingOption: candidate.user.short_name,
votingOptionSubtitle: candidate.user.getLevelAndNumber(),
class: 'user',
value: this.voteTableKeys.map(
key =>
({
vote: key.vote,
amount: candidate[key.vote],
icon: key.icon,
hide: key.hide,
showPercent: key.showPercent
} as VotingResult)
)
}));
tableData.push(
...this.sumTableKeys
.filter(key => {
return !key.hide;
})
.map(key => ({
votingOption: key.vote,
class: 'sums',
value: [
{
amount: this[key.vote],
hide: key.hide,
showPercent: key.showPercent
} as VotingResult
]
}))
);
tableData.push(
...this.globalVoteKeys
.filter(key => {
return !key.hide;
})
.map(key => ({
votingOption: key.vote,
class: 'sums',
value: [
{
amount: this[key.vote],
hide: key.hide,
showPercent: key.showPercent
} as VotingResult
]
}))
);
return tableData;
}
protected getDecimalFields(): string[] {
return AssignmentPoll.DECIMAL_FIELDS;
}

View File

@ -6,7 +6,8 @@ import { HtmlToPdfService } from 'app/core/pdf-services/html-to-pdf.service';
import { ParsePollNumberPipe } from 'app/shared/pipes/parse-poll-number.pipe';
import { PollKeyVerbosePipe } from 'app/shared/pipes/poll-key-verbose.pipe';
import { PollPercentBasePipe } from 'app/shared/pipes/poll-percent-base.pipe';
import { PollTableData } from 'app/site/polls/models/view-base-poll';
import { PollTableData } from 'app/site/polls/services/poll.service';
import { AssignmentPollService } from './assignment-poll.service';
import { ViewAssignment } from '../models/view-assignment';
import { ViewAssignmentPoll } from '../models/view-assignment-poll';
@ -30,7 +31,8 @@ export class AssignmentPdfService {
private htmlToPdfService: HtmlToPdfService,
private pollKeyVerbose: PollKeyVerbosePipe,
private parsePollNumber: ParsePollNumberPipe,
private pollPercentBase: PollPercentBasePipe
private pollPercentBase: PollPercentBasePipe,
private assignmentPollService: AssignmentPollService
) {}
/**
@ -182,7 +184,7 @@ export class AssignmentPdfService {
}
]);
const tableData = poll.generateTableData();
const tableData = this.assignmentPollService.generateTableData(poll);
for (const pollResult of tableData) {
const voteOption = this.translate.instant(this.pollKeyVerbose.transform(pollResult.votingOption));

View File

@ -11,7 +11,8 @@ import {
AssignmentPollPercentBase
} from 'app/shared/models/assignments/assignment-poll';
import { MajorityMethod } from 'app/shared/models/poll/base-poll';
import { PollData, PollService } 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';
@Injectable({
providedIn: 'root'
@ -72,6 +73,60 @@ export class AssignmentPollService extends PollService {
return poll;
}
private getGlobalVoteKeys(poll: ViewAssignmentPoll): VotingResult[] {
return [
{
vote: 'amount_global_no',
showPercent: false,
hide: poll.amount_global_no === -2 || poll.amount_global_no === 0
},
{
vote: 'amount_global_abstain',
showPercent: false,
hide: poll.amount_global_abstain === -2 || poll.amount_global_abstain === 0
}
];
}
public generateTableData(poll: ViewAssignmentPoll): PollTableData[] {
const tableData: PollTableData[] = poll.options.map(candidate => ({
votingOption: candidate.user.short_name,
votingOptionSubtitle: candidate.user.getLevelAndNumber(),
class: 'user',
value: super.getVoteTableKeys(poll).map(
key =>
({
vote: key.vote,
amount: candidate[key.vote],
icon: key.icon,
hide: key.hide,
showPercent: key.showPercent
} as VotingResult)
)
}));
tableData.push(...this.formatVotingResultToTableData(super.getSumTableKeys(poll), poll));
tableData.push(...this.formatVotingResultToTableData(this.getGlobalVoteKeys(poll), poll));
return tableData;
}
private formatVotingResultToTableData(resultList: VotingResult[], poll: PollData): PollTableData[] {
return resultList
.filter(key => {
return !key.hide;
})
.map(key => ({
votingOption: key.vote,
class: 'sums',
value: [
{
amount: poll[key.vote],
hide: key.hide,
showPercent: key.showPercent
} as VotingResult
]
}));
}
private sumOptionsYN(poll: PollData): number {
return poll.options.reduce((o, n) => {
o += n.yes > 0 ? n.yes : 0;

View File

@ -3,7 +3,7 @@ import { PercentBase } from 'app/shared/models/poll/base-poll';
import { BaseViewModel } from 'app/site/base/base-view-model';
import { ProjectorElementBuildDeskriptor } from 'app/site/base/projectable';
import { ViewMotionOption } from 'app/site/motions/models/view-motion-option';
import { PollClassType, PollTableData, ViewBasePoll, VotingResult } from 'app/site/polls/models/view-base-poll';
import { PollClassType, ViewBasePoll } from 'app/site/polls/models/view-base-poll';
import { ViewMotion } from './view-motion';
export interface MotionPollTitleInformation {
@ -34,10 +34,6 @@ export class ViewMotionPoll extends ViewBasePoll<MotionPoll, MotionPollMethod, P
return this.options[0];
}
public get hasPresentableValues(): boolean {
return this.result.hasPresentableValues;
}
public get hasVotes(): boolean {
return this.result && !!this.result.votes.length;
}
@ -46,31 +42,6 @@ export class ViewMotionPoll extends ViewBasePoll<MotionPoll, MotionPollMethod, P
return this.motion;
}
public generateTableData(): PollTableData[] {
let tableData: PollTableData[] = this.options.flatMap(vote =>
this.voteTableKeys.map(key => this.createTableDataEntry(key, vote))
);
tableData.push(...this.sumTableKeys.map(key => this.createTableDataEntry(key)));
tableData = tableData.filter(localeTableData => !localeTableData.value.some(result => result.hide));
return tableData;
}
private createTableDataEntry(result: VotingResult, vote?: ViewMotionOption): PollTableData {
return {
votingOption: result.vote,
value: [
{
amount: vote ? vote[result.vote] : this[result.vote],
hide: result.hide,
icon: result.icon,
showPercent: result.showPercent
}
]
};
}
public getSlide(): ProjectorElementBuildDeskriptor {
return {
getBasicProjectorElement: options => ({

View File

@ -32,50 +32,7 @@
<div *ngIf="!poll.hasVotes || !poll.stateHasVotes">{{ 'No results to show' | translate }}</div>
<div *ngIf="poll.stateHasVotes">
<div class="result-wrapper" *ngIf="poll.hasVotes">
<!-- result table -->
<table class="result-table">
<tbody>
<tr>
<th></th>
<th colspan="2" translate>Votes</th>
</tr>
<tr *ngFor="let row of poll.tableData" [class]="row.votingOption">
<!-- YNA/Valid etc -->
<td>
<os-icon-container *ngIf="row.value[0].icon" [icon]="row.value[0].icon">
{{ row.votingOption | pollKeyVerbose | translate }}
</os-icon-container>
<span *ngIf="!row.value[0].icon">
{{ row.votingOption | pollKeyVerbose | translate }}
</span>
</td>
<!-- Percent numbers -->
<td class="result-cell-definition">
<span *ngIf="row.value[0].showPercent">
{{ row.value[0].amount | pollPercentBase: poll }}
</span>
</td>
<!-- Voices -->
<td class="result-cell-definition">
{{ row.value[0].amount | parsePollNumber }}
</td>
</tr>
</tbody>
</table>
<!-- Chart -->
<div class="doughnut-chart" *ngIf="poll.hasPresentableValues && chartDataSubject.value">
<os-charts
[type]="chartType"
[data]="chartDataSubject"
[showLegend]="false"
[hasPadding]="false"
></os-charts>
</div>
</div>
<os-motion-poll-detail-content [poll]="poll" [chartData]="chartDataSubject"> </os-motion-poll-detail-content>
<!-- Named table: only show if votes are present -->
<div class="named-result-table" *ngIf="poll.type === 'named'">

View File

@ -7,48 +7,6 @@
padding-top: 20px;
}
.result-wrapper {
display: grid;
grid-gap: 2em;
margin: 2em;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
.result-table {
// display: block;
th {
text-align: right;
font-weight: initial;
}
tr {
height: 48px;
border-bottom: none !important;
.result-cell-definition {
text-align: right;
}
}
.yes {
color: $votes-yes-color;
}
.no {
color: $votes-no-color;
}
.abstain {
color: $votes-abstain-color;
}
}
.doughnut-chart {
display: block;
margin-top: auto;
margin-bottom: auto;
}
}
.named-result-table {
grid-area: names;
.mat-form-field {
@ -72,13 +30,6 @@
height: 500px;
}
.openslides-theme os-list-view-table os-sort-filter-bar .custom-table-header {
&,
.action-buttons .input-container input {
background: white;
}
}
.voted-yes {
color: $votes-yes-color;
}

View File

@ -1,4 +1,12 @@
@import '~@angular/material/theming';
@mixin os-motion-poll-detail-style($theme) {
$background: map-get($theme, background);
os-list-view-table os-sort-filter-bar .custom-table-header {
&,
.action-buttons .input-container input {
background: mat-color($background, card);
}
}
}

View File

@ -11,7 +11,6 @@ import { MotionPollRepositoryService } from 'app/core/repositories/motions/motio
import { MotionVoteRepositoryService } from 'app/core/repositories/motions/motion-vote-repository.service';
import { GroupRepositoryService } from 'app/core/repositories/users/group-repository.service';
import { PromptService } from 'app/core/ui-services/prompt.service';
import { ChartType } from 'app/shared/components/charts/charts.component';
import { ViewMotion } from 'app/site/motions/models/view-motion';
import { ViewMotionPoll } from 'app/site/motions/models/view-motion-poll';
import { MotionPollDialogService } from 'app/site/motions/services/motion-poll-dialog.service';
@ -38,18 +37,9 @@ export class MotionPollDetailComponent extends BasePollDetailComponent<ViewMotio
label: 'Vote'
}
];
public filterProps = ['user.getFullName', 'valueVerbose'];
public set chartType(type: ChartType) {
this._chartType = type;
}
public get chartType(): ChartType {
return this._chartType;
}
private _chartType: ChartType = 'doughnut';
public constructor(
title: Title,
translate: TranslateService,
@ -60,9 +50,9 @@ export class MotionPollDetailComponent extends BasePollDetailComponent<ViewMotio
prompt: PromptService,
pollDialog: MotionPollDialogService,
pollService: PollService,
votesRepo: MotionVoteRepositoryService,
private operator: OperatorService,
private router: Router,
votesRepo: MotionVoteRepositoryService
private router: Router
) {
super(title, translate, matSnackbar, repo, route, groupRepo, prompt, pollDialog, pollService, votesRepo);
}

View File

@ -12,9 +12,9 @@ import { infoDialogSettings } from 'app/shared/utils/dialog-settings';
import { ViewMotionPoll } from 'app/site/motions/models/view-motion-poll';
import { MotionPollDialogService } from 'app/site/motions/services/motion-poll-dialog.service';
import { MotionPollPdfService } from 'app/site/motions/services/motion-poll-pdf.service';
import { MotionPollService } from 'app/site/motions/services/motion-poll.service';
import { BasePollComponent } from 'app/site/polls/components/base-poll.component';
import { PollTableData } from 'app/site/polls/models/view-base-poll';
import { PollService } from 'app/site/polls/services/poll.service';
import { PollService, PollTableData } from 'app/site/polls/services/poll.service';
/**
* Component to show a motion-poll.
@ -45,7 +45,7 @@ export class MotionPollComponent extends BasePollComponent<ViewMotionPoll> {
}
public get showChart(): boolean {
return this.poll.hasPresentableValues;
return this.motionPollService.showChart(this.poll);
}
public get hideChangeState(): boolean {
@ -53,7 +53,9 @@ export class MotionPollComponent extends BasePollComponent<ViewMotionPoll> {
}
public get reducedPollTableData(): PollTableData[] {
return this.poll.tableData.filter(data => ['yes', 'no', 'abstain', 'votesinvalid'].includes(data.votingOption));
return this.motionPollService
.generateTableData(this.poll)
.filter(data => ['yes', 'no', 'abstain', 'votesinvalid'].includes(data.votingOption));
}
/**
@ -74,7 +76,8 @@ export class MotionPollComponent extends BasePollComponent<ViewMotionPoll> {
public pollRepo: MotionPollRepositoryService,
pollDialog: MotionPollDialogService,
public pollService: PollService,
private pdfService: MotionPollPdfService
private pdfService: MotionPollPdfService,
private motionPollService: MotionPollService
) {
super(titleService, matSnackBar, translate, dialog, promptService, pollRepo, pollDialog);
}

View File

@ -16,6 +16,7 @@ import { PollKeyVerbosePipe } from 'app/shared/pipes/poll-key-verbose.pipe';
import { PollPercentBasePipe } from 'app/shared/pipes/poll-percent-base.pipe';
import { getRecommendationTypeName } from 'app/shared/utils/recommendation-type-names';
import { MotionExportInfo } from './motion-export.service';
import { MotionPollService } from './motion-poll.service';
import { ChangeRecoMode, InfoToExport, LineNumberingMode, PERSONAL_NOTE_ID } from '../motions.constants';
import { ViewMotion } from '../models/view-motion';
import { ViewMotionAmendedParagraph } from '../models/view-motion-amended-paragraph';
@ -67,7 +68,8 @@ export class MotionPdfService {
private commentRepo: MotionCommentSectionRepositoryService,
private pollKeyVerbose: PollKeyVerbosePipe,
private pollPercentBase: PollPercentBasePipe,
private parsePollNumber: ParsePollNumberPipe
private parsePollNumber: ParsePollNumberPipe,
private motionPollService: MotionPollService
) {}
/**
@ -370,7 +372,7 @@ export class MotionPdfService {
const column3 = [];
motion.polls.forEach(poll => {
if (poll.hasVotes) {
const tableData = poll.generateTableData();
const tableData = this.motionPollService.generateTableData(poll);
tableData.forEach(votingResult => {
const votingOption = this.translate.instant(

View File

@ -7,7 +7,9 @@ import { MotionPollRepositoryService } from 'app/core/repositories/motions/motio
import { ConfigService } from 'app/core/ui-services/config.service';
import { MotionPoll, MotionPollMethod } from 'app/shared/models/motions/motion-poll';
import { MajorityMethod, PercentBase } from 'app/shared/models/poll/base-poll';
import { PollData, PollService } 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 { ViewMotionPoll } from '../models/view-motion-poll';
interface PollResultData {
yes?: number;
@ -71,6 +73,38 @@ export class MotionPollService extends PollService {
return poll;
}
public generateTableData(poll: PollData | ViewMotionPoll): PollTableData[] {
let tableData: PollTableData[] = poll.options.flatMap(vote =>
super.getVoteTableKeys(poll).map(key => this.createTableDataEntry(poll, key, vote))
);
tableData.push(...super.getSumTableKeys(poll).map(key => this.createTableDataEntry(poll, key)));
tableData = tableData.filter(localeTableData => !localeTableData.value.some(result => result.hide));
return tableData;
}
private createTableDataEntry(
poll: PollData | ViewMotionPoll,
result: VotingResult,
vote?: ViewMotionOption
): PollTableData {
return {
votingOption: result.vote,
value: [
{
amount: vote ? vote[result.vote] : poll[result.vote],
hide: result.hide,
icon: result.icon,
showPercent: result.showPercent
}
]
};
}
public showChart(poll: PollData): boolean {
return poll && poll.options && poll.options.some(option => option.yes >= 0 && option.no >= 0);
}
public getPercentBase(poll: PollData): number {
const base: PercentBase = poll.onehundred_percent_base as PercentBase;

View File

@ -12,7 +12,7 @@ import { BaseRepository } from 'app/core/repositories/base-repository';
import { GroupRepositoryService } from 'app/core/repositories/users/group-repository.service';
import { BasePollDialogService } from 'app/core/ui-services/base-poll-dialog.service';
import { PromptService } from 'app/core/ui-services/prompt.service';
import { ChartData, ChartType } from 'app/shared/components/charts/charts.component';
import { ChartData } from 'app/shared/components/charts/charts.component';
import { BaseVote } from 'app/shared/models/poll/base-vote';
import { BaseViewComponent } from 'app/site/base/base-view';
import { ViewGroup } from 'app/site/users/models/view-group';
@ -60,11 +60,6 @@ export abstract class BasePollDetailComponent<V extends ViewBasePoll> extends Ba
*/
public poll: V = null;
/**
* Sets the type of the shown chart, if votes are entered.
*/
public abstract get chartType(): ChartType;
/**
* The different labels for the votes (used for chart).
*/

View File

@ -4,10 +4,6 @@ import { ViewBasePoll } from './view-base-poll';
import { ViewBaseVote } from './view-base-vote';
export class ViewBaseOption<M extends BaseOption<M> = any> extends BaseViewModel<M> {
public get hasPresentableValues(): boolean {
return this.yes >= 0 && this.no >= 0;
}
public get option(): M {
return this._model;
}

View File

@ -1,4 +1,4 @@
import { BasePoll, PercentBase, PollType } from 'app/shared/models/poll/base-poll';
import { BasePoll } from 'app/shared/models/poll/base-poll';
import { BaseProjectableViewModel } from 'app/site/base/base-projectable-view-model';
import { BaseViewModel } from 'app/site/base/base-view-model';
import { ProjectorElementBuildDeskriptor } from 'app/site/base/projectable';
@ -11,32 +11,6 @@ export enum PollClassType {
Assignment = 'assignment'
}
/**
* Interface describes the possible data for the result-table.
*/
export interface PollTableData {
votingOption: string;
votingOptionSubtitle?: string;
class?: string;
value: VotingResult[];
}
export interface VotingResult {
vote?:
| 'yes'
| 'no'
| 'abstain'
| 'votesvalid'
| 'votesinvalid'
| 'votescast'
| 'amount_global_no'
| 'amount_global_abstain';
amount?: number;
icon?: string;
hide?: boolean;
showPercent?: boolean;
}
export const PollClassTypeVerbose = {
motion: 'Motion poll',
assignment: 'Assignment poll'
@ -94,52 +68,6 @@ export abstract class ViewBasePoll<
PM extends string = string,
PB extends string = string
> extends BaseProjectableViewModel<M> {
private _tableData: PollTableData[] = [];
protected voteTableKeys: VotingResult[] = [
{
vote: 'yes',
icon: 'thumb_up',
showPercent: true
},
{
vote: 'no',
icon: 'thumb_down',
showPercent: true
},
{
vote: 'abstain',
icon: 'trip_origin',
showPercent: this.showAbstainPercent
}
];
protected sumTableKeys: VotingResult[] = [
{
vote: 'votesvalid',
hide: this.poll.votesvalid === -2,
showPercent: this.poll.isPercentBaseValidOrCast
},
{
vote: 'votesinvalid',
icon: 'not_interested',
hide: this.poll.type !== PollType.Analog || this.poll.votesinvalid === -2,
showPercent: this.poll.isPercentBaseCast
},
{
vote: 'votescast',
hide: this.poll.type !== PollType.Analog || this.poll.votescast === -2,
showPercent: this.poll.isPercentBaseCast
}
];
public get tableData(): PollTableData[] {
if (!this._tableData.length) {
this._tableData = this.generateTableData();
}
return this._tableData;
}
public get poll(): M {
return this._model;
}
@ -172,14 +100,6 @@ export abstract class ViewBasePoll<
public abstract get percentBaseVerbose(): string;
public get showAbstainPercent(): boolean {
return (
this.poll.onehundred_percent_base === PercentBase.YNA ||
this.poll.onehundred_percent_base === PercentBase.Valid ||
this.poll.onehundred_percent_base === PercentBase.Cast
);
}
public abstract readonly pollClassType: 'motion' | 'assignment';
public canBeVotedFor: () => boolean;
@ -187,8 +107,6 @@ export abstract class ViewBasePoll<
public abstract getSlide(): ProjectorElementBuildDeskriptor;
public abstract getContentObject(): BaseViewModel;
public abstract generateTableData(): PollTableData[];
}
export interface ViewBasePoll<

View File

@ -9,7 +9,8 @@ import {
MajorityMethodVerbose,
PercentBaseVerbose,
PollPropertyVerbose,
PollTypeVerbose
PollTypeVerbose,
ViewBasePoll
} from 'app/site/polls/models/view-base-poll';
import { ConstantsService } from '../../../core/core-services/constants.service';
@ -108,6 +109,32 @@ interface OpenSlidesSettings {
ENABLE_ELECTRONIC_VOTING: boolean;
}
/**
* Interface describes the possible data for the result-table.
*/
export interface PollTableData {
votingOption: string;
votingOptionSubtitle?: string;
class?: string;
value: VotingResult[];
}
export interface VotingResult {
vote?:
| 'yes'
| 'no'
| 'abstain'
| 'votesvalid'
| 'votesinvalid'
| 'votescast'
| 'amount_global_no'
| 'amount_global_abstain';
amount?: number;
icon?: string;
hide?: boolean;
showPercent?: boolean;
}
/**
* Shared service class for polls. Used by child classes {@link MotionPollService}
* and {@link AssignmentPollService}
@ -179,7 +206,58 @@ export abstract class PollService {
return PollPropertyVerbose[key];
}
public generateChartData(poll: PollData): ChartData {
public getVoteTableKeys(poll: PollData | ViewBasePoll): VotingResult[] {
return [
{
vote: 'yes',
icon: 'thumb_up',
showPercent: true
},
{
vote: 'no',
icon: 'thumb_down',
showPercent: true
},
{
vote: 'abstain',
icon: 'trip_origin',
showPercent: this.showAbstainPercent(poll)
}
];
}
private showAbstainPercent(poll: PollData | ViewBasePoll): boolean {
return (
poll.onehundred_percent_base === PercentBase.YNA ||
poll.onehundred_percent_base === PercentBase.Valid ||
poll.onehundred_percent_base === PercentBase.Cast
);
}
public getSumTableKeys(poll: PollData | ViewBasePoll): VotingResult[] {
return [
{
vote: 'votesvalid',
hide: poll.votesvalid === -2,
showPercent:
poll.onehundred_percent_base === PercentBase.Valid ||
poll.onehundred_percent_base === PercentBase.Cast
},
{
vote: 'votesinvalid',
icon: 'not_interested',
hide: poll.votesinvalid === -2,
showPercent: poll.onehundred_percent_base === PercentBase.Cast
},
{
vote: 'votescast',
hide: poll.votescast === -2,
showPercent: poll.onehundred_percent_base === PercentBase.Cast
}
];
}
public generateChartData(poll: PollData | ViewBasePoll): ChartData {
let fields: CalculablePollKey[];
// TODO: PollData should either be `ViewBasePoll` or `BasePoll` to get SOLID

View File

@ -6,40 +6,11 @@
</h1>
<h2 class="poll-title">{{ data.data.poll.title }}</h2>
</div>
<div class="poll-chart-wrapper" *ngIf="data.data.poll.state === PollState.Published">
<div class="doughnut-chart">
<os-charts
[type]="'doughnut'"
[data]="chartDataSubject"
[pieChartOptions]="{ maintainAspectRatio: false, responsive: true }"
[showLegend]="false"
[hasPadding]="false"
>
</os-charts>
</div>
<div class="vote-legend">
<div class="votes-yes" *ngIf="pollService.isVoteDocumented(voteYes)">
<os-icon-container icon="thumb_up" size="large">
{{ voteYes | parsePollNumber }}
{{ voteYes | pollPercentBase: data.data.poll }}
</os-icon-container>
</div>
<div class="votes-no" *ngIf="pollService.isVoteDocumented(voteNo)">
<os-icon-container icon="thumb_down" size="large">
{{ voteNo | parsePollNumber }}
{{ voteNo | pollPercentBase: data.data.poll }}
</os-icon-container>
</div>
<div class="votes-abstain" *ngIf="pollService.isVoteDocumented(voteAbstain)">
<os-icon-container icon="trip_origin" size="large">
{{ voteAbstain | parsePollNumber }}
{{ voteAbstain | pollPercentBase: data.data.poll }}
</os-icon-container>
</div>
</div>
<div *ngIf="data.data.poll.state === PollState.Published">
<os-motion-poll-detail-content [poll]="pollData" [chartData]="chartDataSubject"> </os-motion-poll-detail-content>
</div>
<div *ngIf="data.data.poll.state !== PollState.Published">
<!-- TODO -->
{{ "Nothing to see here!" | translate }}
{{ 'Nothing to see here!' | translate }}
</div>
</ng-container>

View File

@ -3,38 +3,3 @@
.motion-title {
margin: 0 0 10px;
}
.poll-chart-wrapper {
display: grid;
grid-gap: 10px;
grid-template-areas: 'chart legend';
grid-template-columns: min-content auto;
.doughnut-chart {
grid-area: chart;
margin-top: auto;
margin-bottom: auto;
}
.vote-legend {
grid-area: legend;
margin-top: auto;
margin-bottom: auto;
div + div {
margin-top: 10px;
}
.votes-yes {
color: $votes-yes-color;
}
.votes-no {
color: $votes-no-color;
}
.votes-abstain {
color: $votes-abstain-color;
}
}
}

View File

@ -1,7 +1,8 @@
import { Component } from '@angular/core';
import { PollState } from 'app/shared/models/poll/base-poll';
import { PollService } from 'app/site/polls/services/poll.service';
import { MotionPollService } from 'app/site/motions/services/motion-poll.service';
import { PollData, PollService, PollTableData } from 'app/site/polls/services/poll.service';
import { BasePollSlideComponent } from 'app/slides/polls/base-poll-slide.component';
import { MotionPollSlideData } from './motion-poll-slide-data';
@ -13,19 +14,29 @@ import { MotionPollSlideData } from './motion-poll-slide-data';
export class MotionPollSlideComponent extends BasePollSlideComponent<MotionPollSlideData> {
public PollState = PollState;
public pollData: PollData;
public voteYes: number;
public voteNo: number;
public voteAbstain: number;
public constructor(pollService: PollService) {
public constructor(pollService: PollService, private motionPollService: MotionPollService) {
super(pollService);
this.chartDataSubject.subscribe(() => {
if (this.data && this.data.data) {
const result = this.data.data.poll.options[0];
this.pollData = this.data.data.poll as PollData;
const result = this.pollData.options[0];
this.voteYes = result.yes;
this.voteNo = result.no;
this.voteAbstain = result.abstain;
}
});
}
public showChart(): boolean {
return this.motionPollService.showChart(this.pollData);
}
public getTableData(): PollTableData[] {
return this.motionPollService.generateTableData(this.pollData);
}
}