Motion poll detail als slide
Refactor the code to use the motion poll detail als slide component
This commit is contained in:
parent
3c36441967
commit
29a9a09bc6
@ -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>
|
@ -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;
|
||||
}
|
||||
}
|
@ -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();
|
||||
});
|
||||
});
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
@ -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: [
|
||||
{
|
||||
|
@ -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>
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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));
|
||||
|
@ -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;
|
||||
|
@ -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 => ({
|
||||
|
@ -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'">
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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(
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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).
|
||||
*/
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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<
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
</ng-container>
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user