diff --git a/client/src/app/shared/components/assignment-poll-detail-content/assignment-poll-detail-content.component.html b/client/src/app/shared/components/assignment-poll-detail-content/assignment-poll-detail-content.component.html index c8815b5c3..54cb8e68f 100644 --- a/client/src/app/shared/components/assignment-poll-detail-content/assignment-poll-detail-content.component.html +++ b/client/src/app/shared/components/assignment-poll-detail-content/assignment-poll-detail-content.component.html @@ -1,5 +1,5 @@
- +
@@ -51,4 +51,18 @@
{{ 'Candidates' | translate }}
+ + +
+ + {{ 'No results yet.' | translate }} + +
+ + +
+ + {{ 'Counting of votes is in progress ...' | translate }} + +
diff --git a/client/src/app/shared/components/assignment-poll-detail-content/assignment-poll-detail-content.component.ts b/client/src/app/shared/components/assignment-poll-detail-content/assignment-poll-detail-content.component.ts index d16d898ee..7bbaed96a 100644 --- a/client/src/app/shared/components/assignment-poll-detail-content/assignment-poll-detail-content.component.ts +++ b/client/src/app/shared/components/assignment-poll-detail-content/assignment-poll-detail-content.component.ts @@ -1,6 +1,12 @@ import { Component, Input } from '@angular/core'; +import { Title } from '@angular/platform-browser'; +import { TranslateService } from '@ngx-translate/core'; + +import { BaseComponent } from 'app/base.component'; +import { OperatorService } from 'app/core/core-services/operator.service'; import { AssignmentPollMethod } from 'app/shared/models/assignments/assignment-poll'; +import { PollState } from 'app/shared/models/poll/base-poll'; import { ViewAssignmentPoll } from 'app/site/assignments/models/view-assignment-poll'; import { AssignmentPollService } from 'app/site/assignments/modules/assignment-poll/services/assignment-poll.service'; import { PollData, PollTableData, VotingResult } from 'app/site/polls/services/poll.service'; @@ -10,16 +16,18 @@ import { PollData, PollTableData, VotingResult } from 'app/site/polls/services/p templateUrl: './assignment-poll-detail-content.component.html', styleUrls: ['./assignment-poll-detail-content.component.scss'] }) -export class AssignmentPollDetailContentComponent { +export class AssignmentPollDetailContentComponent extends BaseComponent { @Input() public poll: ViewAssignmentPoll | PollData; - public constructor(private pollService: AssignmentPollService) {} - private get method(): string { return this.poll.pollmethod; } + private get state(): PollState { + return this.poll.state; + } + public get showYHeader(): boolean { return this.isMethodY || this.isMethodYN || this.isMethodYNA; } @@ -44,10 +52,35 @@ export class AssignmentPollDetailContentComponent { return this.method === AssignmentPollMethod.YNA; } + public get isFinished(): boolean { + return this.state === PollState.Finished; + } + + public get isPublished(): boolean { + return this.state === PollState.Published; + } + public get tableData(): PollTableData[] { return this.pollService.generateTableData(this.poll); } + public get hasResults(): boolean { + return this.isFinished || this.isPublished; + } + + public get canSeeResults(): boolean { + return this.operator.hasPerms(this.permission.assignmentsCanManage) || this.isPublished; + } + + public constructor( + titleService: Title, + translateService: TranslateService, + private pollService: AssignmentPollService, + private operator: OperatorService + ) { + super(titleService, translateService); + } + public getVoteClass(votingResult: VotingResult): string { const votingClass = votingResult.vote; if (this.isMethodN && votingClass === 'no') { diff --git a/client/src/app/shared/components/charts/charts.component.ts b/client/src/app/shared/components/charts/charts.component.ts index 2deb0faaa..b50b1b816 100644 --- a/client/src/app/shared/components/charts/charts.component.ts +++ b/client/src/app/shared/components/charts/charts.component.ts @@ -74,7 +74,9 @@ export class ChartsComponent extends BaseViewComponentDirective { @Input() public set data(inputData: ChartData) { - this.progressInputData(inputData); + if (inputData) { + this.progressInputData(inputData); + } } /** @@ -85,6 +87,9 @@ export class ChartsComponent extends BaseViewComponentDirective { return { responsive: true, maintainAspectRatio: false, + animation: { + duration: 0 + }, tooltips: { enabled: false }, @@ -96,6 +101,9 @@ export class ChartsComponent extends BaseViewComponentDirective { return { responsive: true, maintainAspectRatio: false, + animation: { + duration: 0 + }, tooltips: { enabled: false }, diff --git a/client/src/app/shared/components/motion-poll-detail-content/motion-poll-detail-content.component.html b/client/src/app/shared/components/motion-poll-detail-content/motion-poll-detail-content.component.html index 3b8f2f314..833cf0698 100644 --- a/client/src/app/shared/components/motion-poll-detail-content/motion-poll-detail-content.component.html +++ b/client/src/app/shared/components/motion-poll-detail-content/motion-poll-detail-content.component.html @@ -1,39 +1,55 @@ -
- - - - - - - - - - +
+ + +
{{ 'Votes' | translate }}
- - {{ row.votingOption | pollKeyVerbose | translate }} - - - {{ row.votingOption | pollKeyVerbose | translate }} - -
+ + + + + + + + - - + + - - - - -
{{ 'Votes' | translate }}
+ + {{ row.votingOption | pollKeyVerbose | translate }} + + + {{ row.votingOption | pollKeyVerbose | translate }} + + - - {{ row.value[0].amount | pollPercentBase: poll:'motion' }} - - + + {{ row.value[0].amount | pollPercentBase: poll:'motion' }} + + - {{ row.value[0].amount | parsePollNumber }} -
+ + + {{ row.value[0].amount | parsePollNumber }} + + + + - -
- + +
+ +
+ + + +
+ + {{ 'No results yet.' | translate }} + +
+ + +
+ + {{ 'Counting of votes is in progress ...' | translate }} +
diff --git a/client/src/app/shared/components/motion-poll-detail-content/motion-poll-detail-content.component.ts b/client/src/app/shared/components/motion-poll-detail-content/motion-poll-detail-content.component.ts index 6689877a4..7d0c7fce0 100644 --- a/client/src/app/shared/components/motion-poll-detail-content/motion-poll-detail-content.component.ts +++ b/client/src/app/shared/components/motion-poll-detail-content/motion-poll-detail-content.component.ts @@ -1,7 +1,11 @@ -import { Component, Input, OnInit } from '@angular/core'; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input } from '@angular/core'; +import { Title } from '@angular/platform-browser'; -import { BehaviorSubject } from 'rxjs'; +import { TranslateService } from '@ngx-translate/core'; +import { BaseComponent } from 'app/base.component'; +import { OperatorService } from 'app/core/core-services/operator.service'; +import { PollState } from 'app/shared/models/poll/base-poll'; 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'; @@ -10,31 +14,65 @@ 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'] + styleUrls: ['./motion-poll-detail-content.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush }) -export class MotionPollDetailContentComponent implements OnInit { - @Input() - public poll: ViewMotionPoll | PollData; +export class MotionPollDetailContentComponent extends BaseComponent { + private _poll: ViewMotionPoll | PollData; + + public chartData: ChartData; + public tableData: PollTableData[]; @Input() - public chartData: BehaviorSubject; + public set poll(pollData: ViewMotionPoll | PollData) { + this._poll = pollData; + this.setTableData(); + this.setChartData(); + this.cd.markForCheck(); + } + + public get poll(): ViewMotionPoll | PollData { + return this._poll; + } @Input() public iconSize: 'large' | 'gigantic' = 'large'; - public get hasVotes(): boolean { - return this.poll && !!this.poll.options; + private get state(): PollState { + return this.poll.state; } - public constructor(private motionPollService: MotionPollService) {} - - public ngOnInit(): void {} - - public getTableData(): PollTableData[] { - return this.motionPollService.generateTableData(this.poll); + public get hasResults(): boolean { + return this.isFinished || this.isPublished; } - public get showChart(): boolean { - return this.motionPollService.showChart(this.poll) && this.chartData && !!this.chartData.value; + public get isFinished(): boolean { + return this.state === PollState.Finished; + } + + public get isPublished(): boolean { + return this.state === PollState.Published; + } + + public get canSeeResults(): boolean { + return this.operator.hasPerms(this.permission.motionsCanManagePolls) || this.isPublished; + } + + public constructor( + titleService: Title, + translate: TranslateService, + private pollService: MotionPollService, + private cd: ChangeDetectorRef, + private operator: OperatorService + ) { + super(titleService, translate); + } + + private setTableData(): void { + this.tableData = this.pollService.generateTableData(this.poll); + } + + private setChartData(): void { + this.chartData = this.pollService.generateChartData(this.poll); } } diff --git a/client/src/app/site/assignments/modules/assignment-poll/components/assignment-poll-detail/assignment-poll-detail.component.html b/client/src/app/site/assignments/modules/assignment-poll/components/assignment-poll-detail/assignment-poll-detail.component.html index b6879f66a..07302b53d 100644 --- a/client/src/app/site/assignments/modules/assignment-poll/components/assignment-poll-detail/assignment-poll-detail.component.html +++ b/client/src/app/site/assignments/modules/assignment-poll/components/assignment-poll-detail/assignment-poll-detail.component.html @@ -28,22 +28,17 @@
-
+
-
- +
+
-
+

{{ 'Single votes' | translate }}

('users_activate_vote_weight') diff --git a/client/src/app/site/assignments/modules/assignment-poll/components/assignment-poll/assignment-poll.component.html b/client/src/app/site/assignments/modules/assignment-poll/components/assignment-poll/assignment-poll.component.html index b049cada1..34f09ac08 100644 --- a/client/src/app/site/assignments/modules/assignment-poll/components/assignment-poll/assignment-poll.component.html +++ b/client/src/app/site/assignments/modules/assignment-poll/components/assignment-poll/assignment-poll.component.html @@ -12,9 +12,7 @@
{{ poll.typeVerbose | translate }} · @@ -64,19 +62,8 @@
-
- -
- - -
- - {{ 'Counting of votes is in progress ...' | translate }} - -
+ +
@@ -95,9 +82,7 @@ matTooltip="{{ 'More' | translate }}" *ngIf="poll.isPublished" > - - visibility - + visibility
diff --git a/client/src/app/site/cinema/components/cinema/cinema.component.ts b/client/src/app/site/cinema/components/cinema/cinema.component.ts index eeb992999..78c74edcd 100644 --- a/client/src/app/site/cinema/components/cinema/cinema.component.ts +++ b/client/src/app/site/cinema/components/cinema/cinema.component.ts @@ -1,4 +1,4 @@ -import { Component, OnInit, ViewChild } from '@angular/core'; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit, ViewChild } from '@angular/core'; import { MatSnackBar } from '@angular/material/snack-bar'; import { Title } from '@angular/platform-browser'; @@ -90,7 +90,8 @@ export class CinemaComponent extends BaseViewComponentDirective implements OnIni private projectorService: ProjectorService, private projectorRepo: ProjectorRepositoryService, private closService: CurrentListOfSpeakersService, - private listOfSpeakersRepo: ListOfSpeakersRepositoryService + private listOfSpeakersRepo: ListOfSpeakersRepositoryService, + private cd: ChangeDetectorRef ) { super(title, translate, snackBar); } diff --git a/client/src/app/site/cinema/components/poll-collection/poll-collection.component.html b/client/src/app/site/cinema/components/poll-collection/poll-collection.component.html index 797f4f5ed..0caa505fd 100644 --- a/client/src/app/site/cinema/components/poll-collection/poll-collection.component.html +++ b/client/src/app/site/cinema/components/poll-collection/poll-collection.component.html @@ -1,13 +1,32 @@ - -

- {{ getPollVoteTitle(poll) }} -

+ -
- -
+ -
- -
-
+ + +

+ {{ getPollVoteTitle(poll) }} +

+ +
+ + +
+ +
+ + +
+
+
diff --git a/client/src/app/site/cinema/components/poll-collection/poll-collection.component.ts b/client/src/app/site/cinema/components/poll-collection/poll-collection.component.ts index 5aec27166..725774cdf 100644 --- a/client/src/app/site/cinema/components/poll-collection/poll-collection.component.ts +++ b/client/src/app/site/cinema/components/poll-collection/poll-collection.component.ts @@ -1,25 +1,52 @@ -import { Component, Input, OnInit } from '@angular/core'; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnInit } from '@angular/core'; import { MatSnackBar } from '@angular/material/snack-bar'; import { Title } from '@angular/platform-browser'; import { TranslateService } from '@ngx-translate/core'; import { map } from 'rxjs/operators'; +import { ViewAssignment } from 'app/site/assignments/models/view-assignment'; +import { ViewAssignmentPoll } from 'app/site/assignments/models/view-assignment-poll'; import { BaseViewComponentDirective } from 'app/site/base/base-view'; import { BaseViewModel } from 'app/site/base/base-view-model'; +import { ViewMotion } from 'app/site/motions/models/view-motion'; +import { ViewMotionPoll } from 'app/site/motions/models/view-motion-poll'; import { ViewBasePoll } from 'app/site/polls/models/view-base-poll'; import { PollListObservableService } from 'app/site/polls/services/poll-list-observable.service'; @Component({ selector: 'os-poll-collection', templateUrl: './poll-collection.component.html', - styleUrls: ['./poll-collection.component.scss'] + styleUrls: ['./poll-collection.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush }) export class PollCollectionComponent extends BaseViewComponentDirective implements OnInit { public polls: ViewBasePoll[]; + public lastPublishedPoll: ViewBasePoll; + + private _currentProjection: BaseViewModel; + + public get currentProjection(): BaseViewModel { + return this._currentProjection; + } + + /** + * CLEANUP: This function belongs to "HasViewPolls"/ ViewModelWithPolls + */ + public get hasProjectedModelOpenPolls(): boolean { + if (this.currentProjection instanceof ViewMotion || this.currentProjection instanceof ViewAssignment) { + const currPolls: ViewMotionPoll[] | ViewAssignmentPoll[] = this.currentProjection.polls; + return currPolls.some((p: ViewMotionPoll | ViewAssignmentPoll) => p.isStarted); + } + return false; + } + @Input() - private currentProjection: BaseViewModel; + public set currentProjection(viewModel: BaseViewModel) { + this._currentProjection = viewModel; + this.updateLastPublished(); + } private get showExtendedTitle(): boolean { const areAllPollsSameModel = this.polls.every( @@ -27,7 +54,7 @@ export class PollCollectionComponent extends BaseViewComponentDirective implemen ); if (this.currentProjection && areAllPollsSameModel) { - return this.polls[0].getContentObject() !== this.currentProjection; + return this.polls[0]?.getContentObject() !== this.currentProjection; } else { return !areAllPollsSameModel; } @@ -37,7 +64,8 @@ export class PollCollectionComponent extends BaseViewComponentDirective implemen title: Title, translate: TranslateService, snackBar: MatSnackBar, - private pollService: PollListObservableService + private pollService: PollListObservableService, + private cd: ChangeDetectorRef ) { super(title, translate, snackBar); } @@ -46,9 +74,17 @@ export class PollCollectionComponent extends BaseViewComponentDirective implemen this.subscriptions.push( this.pollService .getViewModelListObservable() - .pipe(map(polls => polls.filter(poll => poll.canBeVotedFor()))) + .pipe( + map(polls => { + return polls.filter(poll => { + return poll.canBeVotedFor(); + }); + }) + ) .subscribe(polls => { this.polls = polls; + this.cd.markForCheck(); + this.updateLastPublished(); }) ); } @@ -57,6 +93,10 @@ export class PollCollectionComponent extends BaseViewComponentDirective implemen return poll.id; } + public getPollDetailLink(poll: ViewBasePoll): string { + return poll.parentLink; + } + public getPollVoteTitle(poll: ViewBasePoll): string { const contentObject = poll.getContentObject(); const listTitle = contentObject.getListTitle(); @@ -70,7 +110,35 @@ export class PollCollectionComponent extends BaseViewComponentDirective implemen } } - public getPollDetailLink(poll: ViewBasePoll): string { - return poll.parentLink; + /** + * Helper function to detect new latest published polls and set them. + */ + private updateLastPublished(): void { + const lastPublished = this.getLastfinshedPoll(this.currentProjection); + if (lastPublished !== this.lastPublishedPoll) { + this.lastPublishedPoll = lastPublished; + this.cd.markForCheck(); + } + } + + /** + * CLEANUP: This function belongs to "HasViewPolls"/ ViewModelWithPolls + * *class* (is an interface right now) + * + * @param viewModel + */ + private getLastfinshedPoll(viewModel: BaseViewModel): ViewBasePoll { + if (viewModel instanceof ViewMotion || viewModel instanceof ViewAssignment) { + let currPolls: ViewMotionPoll[] | ViewAssignmentPoll[] = viewModel.polls; + /** + * Although it should, since the union type could use `.filter + * without any problem, without an any cast it will not work + */ + currPolls = (currPolls as any[]) + .filter((p: ViewMotionPoll | ViewAssignmentPoll) => p.stateHasVotes) + .reverse(); + return currPolls[0]; + } + return null; } } diff --git a/client/src/app/site/motions/models/view-motion.ts b/client/src/app/site/motions/models/view-motion.ts index 8dbba0814..783f9cf9c 100644 --- a/client/src/app/site/motions/models/view-motion.ts +++ b/client/src/app/site/motions/models/view-motion.ts @@ -11,6 +11,7 @@ import { ProjectorElementBuildDeskriptor } from 'app/site/base/projectable'; import { Searchable } from 'app/site/base/searchable'; import { SlideOptions } from 'app/site/base/slide-options'; import { ViewMediafile } from 'app/site/mediafiles/models/view-mediafile'; +import { HasViewPolls } from 'app/site/polls/models/has-view-polls'; import { ViewTag } from 'app/site/tags/models/view-tag'; import { ViewUser } from 'app/site/users/models/view-user'; import { AmendmentType } from '../motions.constants'; @@ -355,7 +356,7 @@ export class ViewMotion } } -interface TIMotionRelations { +interface TIMotionRelations extends HasViewPolls { category?: ViewCategory; submitters: ViewSubmitter[]; supporters?: ViewUser[]; @@ -369,7 +370,6 @@ interface TIMotionRelations { amendments?: ViewMotion[]; changeRecommendations?: ViewMotionChangeRecommendation[]; diffLines?: DiffLinesInParagraph[]; - polls: ViewMotionPoll[]; } export interface ViewMotion extends MotionWithoutNestedModels, TIMotionRelations {} diff --git a/client/src/app/site/motions/modules/motion-poll/motion-poll-detail/motion-poll-detail.component.html b/client/src/app/site/motions/modules/motion-poll/motion-poll-detail/motion-poll-detail.component.html index 959cb52ed..f4c201569 100644 --- a/client/src/app/site/motions/modules/motion-poll/motion-poll-detail/motion-poll-detail.component.html +++ b/client/src/app/site/motions/modules/motion-poll/motion-poll-detail/motion-poll-detail.component.html @@ -29,68 +29,63 @@
-

{{ 'No results yet.' | translate }}

+ -
- - + +
+

{{ 'Single votes' | translate }}

+ + +
+ {{ col.label | translate }} +
- -
-

{{ 'Single votes' | translate }}

- - -
- {{ col.label | translate }} -
+ +
+
+ {{ vote.user.getShortName() }} - -
-
- {{ vote.user.getShortName() }} +
+ +
+ {{ vote.user.getLevelAndNumber() }} +
-
- -
- {{ vote.user.getLevelAndNumber() }} -
+ +
+ {{ 'Vote weight' | translate }}: {{ vote.user.vote_weight }} +
- -
- {{ 'Vote weight' | translate }}: {{ vote.user.vote_weight }} -
- - -
- - ({{ 'represented by' | translate }} - {{ getUsersVoteDelegation(vote.user).getShortName().trim() }}) - -
+ +
+ + ({{ 'represented by' | translate }} + {{ getUsersVoteDelegation(vote.user).getShortName().trim() }}) +
-
{{ 'Anonymous' | translate }}
-
-
- {{ voteOptionStyle[vote.value].icon }} -
-
{{ vote.valueVerbose | translate }}
-
- -
- {{ 'The individual votes were anonymized.' | translate }} +
{{ 'Anonymous' | translate }}
+
+
+ {{ voteOptionStyle[vote.value].icon }} +
+
{{ vote.valueVerbose | translate }}
+
+ +
+ {{ 'The individual votes were anonymized.' | translate }}
diff --git a/client/src/app/site/motions/modules/motion-poll/motion-poll-detail/motion-poll-detail.component.ts b/client/src/app/site/motions/modules/motion-poll/motion-poll-detail/motion-poll-detail.component.ts index 5a19ce701..14d9dde30 100644 --- a/client/src/app/site/motions/modules/motion-poll/motion-poll-detail/motion-poll-detail.component.ts +++ b/client/src/app/site/motions/modules/motion-poll/motion-poll-detail/motion-poll-detail.component.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, Component, ViewEncapsulation } from '@angular/core'; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ViewEncapsulation } from '@angular/core'; import { MatSnackBar } from '@angular/material/snack-bar'; import { Title } from '@angular/platform-browser'; import { ActivatedRoute, Router } from '@angular/router'; @@ -44,6 +44,10 @@ export class MotionPollDetailComponent extends BasePollDetailComponentDirective< public isVoteWeightActive: boolean; + public get showResults(): boolean { + return this.hasPerms() || this.poll.isPublished; + } + public constructor( title: Title, translate: TranslateService, @@ -57,7 +61,8 @@ export class MotionPollDetailComponent extends BasePollDetailComponentDirective< votesRepo: MotionVoteRepositoryService, configService: ConfigService, protected operator: OperatorService, - private router: Router + private router: Router, + protected cd: ChangeDetectorRef ) { super( title, @@ -70,7 +75,8 @@ export class MotionPollDetailComponent extends BasePollDetailComponentDirective< pollDialog, pollService, votesRepo, - operator + operator, + cd ); configService .get('users_activate_vote_weight') diff --git a/client/src/app/site/motions/modules/motion-poll/motion-poll/motion-poll.component.html b/client/src/app/site/motions/modules/motion-poll/motion-poll/motion-poll.component.html index 3dd2a821e..303bddda0 100644 --- a/client/src/app/site/motions/modules/motion-poll/motion-poll/motion-poll.component.html +++ b/client/src/app/site/motions/modules/motion-poll/motion-poll/motion-poll.component.html @@ -23,9 +23,7 @@
@@ -42,7 +40,12 @@
-
- +