diff --git a/client/src/app/domain/models/poll/poll.ts b/client/src/app/domain/models/poll/poll.ts index e283416620..39ff2d3c83 100644 --- a/client/src/app/domain/models/poll/poll.ts +++ b/client/src/app/domain/models/poll/poll.ts @@ -26,7 +26,7 @@ export class Poll extends BaseDecimalModel { public votesvalid!: number; public votesinvalid!: number; public votescast!: number; - public vote_count!: number; + public has_voted_user_ids: number[]; public onehundred_percent_base!: PollPercentBase; /** @@ -169,7 +169,7 @@ export class Poll extends BaseDecimalModel { `votesinvalid`, `votescast`, `entitled_users_at_stop`, - `vote_count`, + `has_voted_user_ids`, `sequential_number`, `content_object_id`, `option_ids`, diff --git a/client/src/app/site/pages/meetings/modules/poll/base/base-poll-detail.component.ts b/client/src/app/site/pages/meetings/modules/poll/base/base-poll-detail.component.ts index 2935e980ef..c080473fe6 100644 --- a/client/src/app/site/pages/meetings/modules/poll/base/base-poll-detail.component.ts +++ b/client/src/app/site/pages/meetings/modules/poll/base/base-poll-detail.component.ts @@ -5,6 +5,7 @@ import { filter, map } from 'rxjs/operators'; import { Id } from 'src/app/domain/definitions/key-types'; import { Identifiable } from 'src/app/domain/interfaces'; import { PollContentObject } from 'src/app/domain/models/poll'; +import { MeetingUserRepositoryService } from 'src/app/gateways/repositories/meeting_user'; import { Deferred } from 'src/app/infrastructure/utils/promises'; import { BaseMeetingComponent } from 'src/app/site/pages/meetings/base/base-meeting.component'; import { PollControllerService } from 'src/app/site/pages/meetings/modules/poll/services/poll-controller.service/poll-controller.service'; @@ -81,6 +82,11 @@ export abstract class BasePollDetailComponent { + return this._liveRegisterObservable; + } + public get self(): BasePollDetailComponent { return this; } @@ -103,6 +109,9 @@ export abstract class BasePollDetailComponent = this.meetingSettingsService.get(`users_enable_vote_weight`); + public countVoteAllowedAndPresent = 0; + public countVoteAllowed = 0; + protected get canSeeVotes(): boolean { return (this.hasPerms() && this.poll!.isFinished) || this.poll!.isPublished; } @@ -115,6 +124,7 @@ export abstract class BasePollDetailComponent([]); private _currentOperator!: ViewUser; private _pollId!: Id; + private _liveRegisterObservable = new BehaviorSubject([]); protected repo = inject(PollControllerService); protected route = inject(ActivatedRoute); @@ -125,6 +135,7 @@ export abstract class BasePollDetailComponent([]); + for (const group of this.poll.entitled_groups) { + const meetingUserIds = group.meeting_user_ids ?? []; + meetingUserIds.forEach(mu_id => userIds.add(this.meetingUserRepo.getViewModel(mu_id)?.user_id)); + } + const delegates = new Set([]); + Array.from(userIds).forEach(userId => { + if (this.userRepo.getViewModel(userId)?.vote_delegated_to_id()) { + delegates.add(this.userRepo.getViewModel(userId)?.vote_delegated_to_id()); + } + }); + userIds.update(delegates); + this.subscriptions.push( + this.userRepo + .getViewModelListObservable() + .pipe( + filter(users => !!users.length), + map(users => users.filter(user => userIds.has(user.id))) + ) + .subscribe(users => { + const entries: EntitledUsersTableEntry[] = []; + for (const user of users || []) { + const delegateToId = user.vote_delegated_to_id(); + const voted = (this.poll.has_voted_user_ids ?? []).includes(user.id); + entries.push({ + id: user.id, + user: user, + voted_verbose: `voted:${voted}`, + user_id: user.id, + present: user?.isPresentInMeeting(), + voted: voted, + vote_delegated_to_user_id: delegateToId, + vote_delegated_to: delegateToId ? users.find(utmp => utmp.id === delegateToId) : null + }); + } + this.countVoteAllowedAndPresent = entries.filter(entry => { + const countable = entry.user.isVoteCountable; + const inVoteGroup = this.poll.entitled_group_ids.intersect(entry.user.group_ids()).length; + return countable && inVoteGroup; + }).length; + this.countVoteAllowed = entries.length; + this._liveRegisterObservable.next(entries); + this.cd.markForCheck(); + }) + ); + } + public hasUserVoteDelegation(user: ViewUser): boolean { if (user.isVoteRightDelegated || this._currentOperator.canVoteFor(user)) { return true; diff --git a/client/src/app/site/pages/meetings/modules/poll/components/entitled-users-table/entitled-users-table.component.html b/client/src/app/site/pages/meetings/modules/poll/components/entitled-users-table/entitled-users-table.component.html index 814e7cc20a..7a15e552cd 100644 --- a/client/src/app/site/pages/meetings/modules/poll/components/entitled-users-table/entitled-users-table.component.html +++ b/client/src/app/site/pages/meetings/modules/poll/components/entitled-users-table/entitled-users-table.component.html @@ -29,7 +29,7 @@
({{ 'represented by' | translate }} - {{ entry.vote_delegated_to.getShortName() }}) + {{ entry.vote_delegated_to?.getShortName() }})
} diff --git a/client/src/app/site/pages/meetings/modules/poll/components/poll-progress/poll-progress.component.ts b/client/src/app/site/pages/meetings/modules/poll/components/poll-progress/poll-progress.component.ts index e09f569503..91eda9b5d2 100644 --- a/client/src/app/site/pages/meetings/modules/poll/components/poll-progress/poll-progress.component.ts +++ b/client/src/app/site/pages/meetings/modules/poll/components/poll-progress/poll-progress.component.ts @@ -27,7 +27,7 @@ export class PollProgressComponent extends BaseUiComponent implements OnInit { public max = 1; public get votescast(): number { - return this.poll?.vote_count || 0; + return this.poll?.has_voted_user_ids?.length || 0; } public get canSeeProgressBar(): boolean { diff --git a/client/src/app/site/pages/meetings/pages/assignments/modules/assignment-poll/components/assignment-poll-detail-content/assignment-poll-detail-content.component.html b/client/src/app/site/pages/meetings/pages/assignments/modules/assignment-poll/components/assignment-poll-detail-content/assignment-poll-detail-content.component.html index 27c9975247..65c4940abc 100644 --- a/client/src/app/site/pages/meetings/pages/assignments/modules/assignment-poll/components/assignment-poll-detail-content/assignment-poll-detail-content.component.html +++ b/client/src/app/site/pages/meetings/pages/assignments/modules/assignment-poll/components/assignment-poll-detail-content/assignment-poll-detail-content.component.html @@ -156,11 +156,19 @@

} @if (!hasResults) { -
- - {{ 'No results yet.' | translate }} - -
+ @if (isStarted) { +
+ + {{ 'Voting in progress.' | translate }} + +
+ } @else { +
+ + {{ 'No results yet.' | translate }} + +
+ } } @if (hasResults && !canSeeResults) { diff --git a/client/src/app/site/pages/meetings/pages/assignments/modules/assignment-poll/components/assignment-poll-detail-content/assignment-poll-detail-content.component.ts b/client/src/app/site/pages/meetings/pages/assignments/modules/assignment-poll/components/assignment-poll-detail-content/assignment-poll-detail-content.component.ts index bb72aa5cff..eb01725a9f 100644 --- a/client/src/app/site/pages/meetings/pages/assignments/modules/assignment-poll/components/assignment-poll-detail-content/assignment-poll-detail-content.component.ts +++ b/client/src/app/site/pages/meetings/pages/assignments/modules/assignment-poll/components/assignment-poll-detail-content/assignment-poll-detail-content.component.ts @@ -84,6 +84,10 @@ export class AssignmentPollDetailContentComponent implements OnInit { return this.method === PollMethod.YNA; } + public get isStarted(): boolean { + return this.state === PollState.Started; + } + public get isFinished(): boolean { return this.state === PollState.Finished; } diff --git a/client/src/app/site/pages/meetings/pages/assignments/pages/assignment-polls/components/assignment-poll-detail/assignment-poll-detail.component.html b/client/src/app/site/pages/meetings/pages/assignments/pages/assignment-polls/components/assignment-poll-detail/assignment-poll-detail.component.html index 797abd2e40..fd8d036f42 100644 --- a/client/src/app/site/pages/meetings/pages/assignments/pages/assignment-polls/components/assignment-poll-detail/assignment-poll-detail.component.html +++ b/client/src/app/site/pages/meetings/pages/assignments/pages/assignment-polls/components/assignment-poll-detail/assignment-poll-detail.component.html @@ -39,6 +39,19 @@

{{ poll.title }}

+ @if (poll.isStarted) { + +
+
+ {{ 'Present people allowed to vote:' | translate }} + {{ countVoteAllowedAndPresent }} +
+
+ {{ 'All people allowed to vote:' | translate }} + {{ countVoteAllowed }} +
+
+ } @if (showResults && poll.stateHasVotes && poll.isEVoting) { @@ -63,6 +76,18 @@

{{ poll.title }}

>
+ } @else if (poll.isEVoting && poll.isStarted) { + + +
{{ 'No data available' | translate }}
+
+ + + +
}
} diff --git a/client/src/app/site/pages/meetings/pages/assignments/pages/assignment-polls/components/assignment-poll-detail/assignment-poll-detail.component.scss b/client/src/app/site/pages/meetings/pages/assignments/pages/assignment-polls/components/assignment-poll-detail/assignment-poll-detail.component.scss index 5a649caa54..83b1088bb8 100644 --- a/client/src/app/site/pages/meetings/pages/assignments/pages/assignment-polls/components/assignment-poll-detail/assignment-poll-detail.component.scss +++ b/client/src/app/site/pages/meetings/pages/assignments/pages/assignment-polls/components/assignment-poll-detail/assignment-poll-detail.component.scss @@ -48,3 +48,10 @@ height: 100%; } } + +.poll-content { + text-align: right; + display: grid; + margin-top: -20px; + margin-bottom: 20px; +} diff --git a/client/src/app/site/pages/meetings/pages/motions/modules/motion-poll/components/motion-poll-detail-content/motion-poll-detail-content.component.html b/client/src/app/site/pages/meetings/pages/motions/modules/motion-poll/components/motion-poll-detail-content/motion-poll-detail-content.component.html index 62638868c2..1a30e4e4ae 100644 --- a/client/src/app/site/pages/meetings/pages/motions/modules/motion-poll/components/motion-poll-detail-content/motion-poll-detail-content.component.html +++ b/client/src/app/site/pages/meetings/pages/motions/modules/motion-poll/components/motion-poll-detail-content/motion-poll-detail-content.component.html @@ -18,6 +18,12 @@ } + } @else if (isStarted) { +
+ + {{ 'Voting in progress.' | translate }} + +
} @else {
diff --git a/client/src/app/site/pages/meetings/pages/motions/modules/motion-poll/components/motion-poll-detail-content/motion-poll-detail-content.component.ts b/client/src/app/site/pages/meetings/pages/motions/modules/motion-poll/components/motion-poll-detail-content/motion-poll-detail-content.component.ts index 48b7d82888..23c17aecf0 100644 --- a/client/src/app/site/pages/meetings/pages/motions/modules/motion-poll/components/motion-poll-detail-content/motion-poll-detail-content.component.ts +++ b/client/src/app/site/pages/meetings/pages/motions/modules/motion-poll/components/motion-poll-detail-content/motion-poll-detail-content.component.ts @@ -55,6 +55,10 @@ export class MotionPollDetailContentComponent extends BaseUiComponent implements return this.isFinished || this.isPublished; } + public get isStarted(): boolean { + return this.state === PollState.Started; + } + public get isFinished(): boolean { return this.state === PollState.Finished; } diff --git a/client/src/app/site/pages/meetings/pages/motions/pages/motion-polls/components/motion-poll-detail/motion-poll-detail.component.html b/client/src/app/site/pages/meetings/pages/motions/pages/motion-polls/components/motion-poll-detail/motion-poll-detail.component.html index 2b451cb5a1..ad5b16fb25 100644 --- a/client/src/app/site/pages/meetings/pages/motions/pages/motion-polls/components/motion-poll-detail/motion-poll-detail.component.html +++ b/client/src/app/site/pages/meetings/pages/motions/pages/motion-polls/components/motion-poll-detail/motion-poll-detail.component.html @@ -35,13 +35,25 @@

{{ poll.title | translate }}

- + @if (poll.isStarted) { + +
+
+ {{ 'Present people allowed to vote:' | translate }} + {{ countVoteAllowedAndPresent }} +
+
+ {{ 'All people allowed to vote:' | translate }} + {{ countVoteAllowed }} +
+
+ } - @if (showResults && poll.stateHasVotes && poll.isEVoting) { - @if (poll.type === 'named') { - - } + @if (showResults && poll.stateHasVotes && poll.isEVoting && poll.type === 'named') { + + } + @if (showResults && poll.stateHasVotes && poll.isEVoting) {
@@ -61,9 +73,23 @@

{{ poll.title | translate }}

> + } @else if (poll.isEVoting && poll.isStarted) { + + +
+ {{ 'No data available' | translate }} +
+
+ + + +
} - } + diff --git a/client/src/app/site/pages/meetings/pages/motions/pages/motion-polls/components/motion-poll-detail/motion-poll-detail.component.scss b/client/src/app/site/pages/meetings/pages/motions/pages/motion-polls/components/motion-poll-detail/motion-poll-detail.component.scss index f41e54fc4e..391805aaad 100644 --- a/client/src/app/site/pages/meetings/pages/motions/pages/motion-polls/components/motion-poll-detail/motion-poll-detail.component.scss +++ b/client/src/app/site/pages/meetings/pages/motions/pages/motion-polls/components/motion-poll-detail/motion-poll-detail.component.scss @@ -31,3 +31,10 @@ .openslides-theme .pbl-ngrid-no-data { top: 10%; } + +.poll-content { + text-align: right; + display: grid; + margin-top: -20px; + margin-bottom: 20px; +} diff --git a/client/src/app/site/pages/meetings/pages/polls/polls.subscription.ts b/client/src/app/site/pages/meetings/pages/polls/polls.subscription.ts index eebfacdbdb..052d64db38 100644 --- a/client/src/app/site/pages/meetings/pages/polls/polls.subscription.ts +++ b/client/src/app/site/pages/meetings/pages/polls/polls.subscription.ts @@ -84,6 +84,16 @@ export const getPollDetailSubscriptionConfig: SubscriptionConfigGenerator = (... idField: `global_option_id`, fieldset: FULL_FIELDSET, follow: [{ idField: `vote_ids` }] + }, + { + idField: `entitled_group_ids`, + fieldset: [], + follow: [ + { + idField: `meeting_user_ids`, + fieldset: [`user_id`] + } + ] } ] },