diff --git a/client/src/app/shared/components/list-view-table/list-view-table.component.html b/client/src/app/shared/components/list-view-table/list-view-table.component.html index c1ed0905b..8c5e295b9 100644 --- a/client/src/app/shared/components/list-view-table/list-view-table.component.html +++ b/client/src/app/shared/components/list-view-table/list-view-table.component.html @@ -14,8 +14,9 @@ ; + /** + * ...or the required observable + */ + @Input() + public listObservable: Observable; + /** * The currently active sorting service for the list view */ @@ -193,6 +200,13 @@ export class ListViewTableComponent 0) { + document.documentElement.style.setProperty('--pbl-height', this.vScrollFixed + 'px'); + } } /** diff --git a/client/src/app/site/assignments/components/assignment-poll-detail/assignment-poll-detail.component.html b/client/src/app/site/assignments/components/assignment-poll-detail/assignment-poll-detail.component.html index ce6ea2261..7564bdaa7 100644 --- a/client/src/app/site/assignments/components/assignment-poll-detail/assignment-poll-detail.component.html +++ b/client/src/app/site/assignments/components/assignment-poll-detail/assignment-poll-detail.component.html @@ -68,40 +68,58 @@ [hasPadding]="false" [legendPosition]="isVotedPoll ? 'right' : 'top'" > - - -
-

{{ 'Single votes' | translate }}

- - - - - - {{ 'User' | translate }} - -
{{ row.user.getFullName() }}
-
{{ 'Unknown user' | translate }}
-
-
- - -
{{ option.user.getFullName() }}
-
{{ 'Unknown user' | translate }}
-
- - {{ row.votes[option.user_id] }} - -
- - - -
-
+ + + +

{{ 'Single votes' | translate }}

+ + +
+ {{ col.label | translate }} +
+
+ {{ col.label | translate }} +
+ + +
+ {{ vote.user.getFullName() }} + {{ 'Anonymous' | translate }} +
+ + + +
+ {{ voteOptionStyle[vote.votes[option.user_id].value].icon }} {{ vote.votes[option.user_id].valueVerbose | translate }} +
+
+
+ + +
+
{{ candidate }}
+
+
+
+
+ {{ 'The individual votes were anonymized.' | translate }} +
+
diff --git a/client/src/app/site/assignments/components/assignment-poll-detail/assignment-poll-detail.component.scss b/client/src/app/site/assignments/components/assignment-poll-detail/assignment-poll-detail.component.scss index d8dea6a26..760728c24 100644 --- a/client/src/app/site/assignments/components/assignment-poll-detail/assignment-poll-detail.component.scss +++ b/client/src/app/site/assignments/components/assignment-poll-detail/assignment-poll-detail.component.scss @@ -1,4 +1,5 @@ @import '~assets/styles/variables.scss'; +@import '~assets/styles/poll-colors.scss'; .result-wrapper { display: grid; @@ -38,3 +39,74 @@ .poll-content { padding-top: 20px; } + +.chart-wrapper { + &.flex { + display: flex; + + .mat-table { + flex: 2; + .mat-column-votes { + justify-content: center; + } + } + .chart-inner-wrapper { + flex: 3; + } + } +} + +.single-votes-table { + height: 500px; +} + +.openslides-theme .pbl-ngrid-row:hover { + background-color: #f9f9f9; +} + +.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; +} + +.voted-no { + color: $votes-no-color; +} + +.voted-abstain { + color: $votes-abstain-color; +} + +.openslides-theme .pbl-ngrid-no-data { + top: 10%; +} + +.openslides-theme .pbl-ngrid-header-cell:first-child { + & { + overflow: visible; + } + + &::after { + content: ''; + display: block; + position: absolute; + top: 0; + right: -1px; + height: 100%; + border-right: 1px solid #e0e0e0; + } +} + +.vote-field { + display: flex; + align-items: center; + justify-content: center; + width: 100%; + padding-right: 12px; +} diff --git a/client/src/app/site/assignments/components/assignment-poll-detail/assignment-poll-detail.component.ts b/client/src/app/site/assignments/components/assignment-poll-detail/assignment-poll-detail.component.ts index 616b0e024..cbc26e629 100644 --- a/client/src/app/site/assignments/components/assignment-poll-detail/assignment-poll-detail.component.ts +++ b/client/src/app/site/assignments/components/assignment-poll-detail/assignment-poll-detail.component.ts @@ -1,14 +1,16 @@ -import { Component } from '@angular/core'; +import { Component, ViewEncapsulation } from '@angular/core'; import { MatSnackBar } from '@angular/material'; import { Title } from '@angular/platform-browser'; import { ActivatedRoute } from '@angular/router'; import { TranslateService } from '@ngx-translate/core'; +import { PblColumnDefinition } from '@pebula/ngrid'; import { OperatorService } from 'app/core/core-services/operator.service'; import { AssignmentPollRepositoryService } from 'app/core/repositories/assignments/assignment-poll-repository.service'; import { GroupRepositoryService } from 'app/core/repositories/users/group-repository.service'; import { PromptService } from 'app/core/ui-services/prompt.service'; +import { ViewportService } from 'app/core/ui-services/viewport.service'; import { ChartType } from 'app/shared/components/charts/charts.component'; import { AssignmentPollMethods } from 'app/shared/models/assignments/assignment-poll'; import { BasePollDetailComponent } from 'app/site/polls/components/base-poll-detail.component'; @@ -18,9 +20,16 @@ import { ViewAssignmentPoll } from '../../models/view-assignment-poll'; @Component({ selector: 'os-assignment-poll-detail', templateUrl: './assignment-poll-detail.component.html', - styleUrls: ['./assignment-poll-detail.component.scss'] + styleUrls: ['./assignment-poll-detail.component.scss'], + encapsulation: ViewEncapsulation.None }) export class AssignmentPollDetailComponent extends BasePollDetailComponent { + public AssignmentPollMethods = AssignmentPollMethods; + + public columnDefinitionSingleVotes: PblColumnDefinition[]; + + public filterProps = ['user.getFullName']; + public isReady = false; public candidatesLabels: string[] = []; @@ -40,9 +49,6 @@ export class AssignmentPollDetailComponent extends BasePollDetailComponent 'votes-' + option.user_id)); - const votes = {}; let i = -1; + + this.columnDefinitionSingleVotes = [ + { + prop: 'user', + label: 'Participant', + width: '180px', + pin: this.viewport.isMobile ? undefined : 'start' + } + ]; + if (this.isVotedPoll) { + this.columnDefinitionSingleVotes.push(this.getVoteColumnDefinition('votes', 'Votes')); + } + + /** + * builds an object of the following form: + * { + * userId: { + * user: ViewUser, + * votes: { candidateId: voteValue } // for YN(A) + * | candidate_name[] // for Votes + * } + * } + */ for (const option of this.poll.options) { + if (!this.isVotedPoll) { + this.columnDefinitionSingleVotes.push( + this.getVoteColumnDefinition('votes-' + option.user_id, option.user.getFullName()) + ); + } + for (const vote of option.votes) { // if poll was pseudoanonymized, use a negative index to not interfere with // possible named votes (although this should never happen) @@ -72,11 +106,24 @@ export class AssignmentPollDetailComponent extends BasePollDetailComponent 0) { + if (vote.value === 'Y') { + votes[userId].votes.push(option.user.getFullName()); + } else if (vote.value === 'N') { + votes[userId].votes.push(this.translate.instant('No')); + } else if (vote.value === 'A') { + votes[userId].votes.push(this.translate.instant('Abstain')); + } + } + } else { + votes[userId].votes[option.user_id] = vote; + } } } @@ -87,6 +134,15 @@ export class AssignmentPollDetailComponent extends BasePollDetailComponent

{{ 'Single votes' | translate }}

-
- - - - - - {{ 'Participant' | translate }} - -
{{ vote.user.getFullName() }}
-
{{ 'Anonymous' | translate }}
-
-
- - {{ 'Vote' | translate }} - {{ vote.valueVerbose }} - + + +
+ {{ col.label | translate }} +
- - -
-
-
- {{ 'The individual votes were made anonymous.' | translate }} -
+ +
+
{{ vote.user.getFullName() }}
+
{{ 'Anonymous' | translate }}
+
+
+
+ {{ voteOptionStyle[vote.value].icon }} +
+
{{ vote.valueVerbose | translate }}
+
+ + +
+ {{ 'The individual votes were made anonymous.' | translate }} +
+
diff --git a/client/src/app/site/motions/modules/motion-poll/motion-poll-detail/motion-poll-detail.component.scss b/client/src/app/site/motions/modules/motion-poll/motion-poll-detail/motion-poll-detail.component.scss index d1795676c..cbde03551 100644 --- a/client/src/app/site/motions/modules/motion-poll/motion-poll-detail/motion-poll-detail.component.scss +++ b/client/src/app/site/motions/modules/motion-poll/motion-poll-detail/motion-poll-detail.component.scss @@ -1,4 +1,5 @@ @import '~assets/styles/variables.scss'; +@import '~assets/styles/poll-colors.scss'; .poll-content { padding-top: 20px; @@ -56,4 +57,42 @@ font-size: 14px; width: 100%; } + + .vote-cell { + display: flex; + align-items: center; + + .vote-cell-icon-container { + display: flex; + align-items: center; + margin-right: 7px; + } + } +} + +.single-votes-table { + 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; +} + +.voted-no { + color: $votes-no-color; +} + +.voted-abstain { + color: $votes-abstain-color; +} + +.openslides-theme .pbl-ngrid-no-data { + top: 10%; } 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 3f981f18a..4872b7441 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,9 +1,10 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, OnInit, ViewEncapsulation } from '@angular/core'; import { MatSnackBar } from '@angular/material'; import { Title } from '@angular/platform-browser'; import { ActivatedRoute, Router } from '@angular/router'; import { TranslateService } from '@ngx-translate/core'; +import { PblColumnDefinition } from '@pebula/ngrid'; import { OperatorService } from 'app/core/core-services/operator.service'; import { MotionPollRepositoryService } from 'app/core/repositories/motions/motion-poll-repository.service'; @@ -18,11 +19,24 @@ import { BasePollDetailComponent } from 'app/site/polls/components/base-poll-det @Component({ selector: 'os-motion-poll-detail', templateUrl: './motion-poll-detail.component.html', - styleUrls: ['./motion-poll-detail.component.scss'] + styleUrls: ['./motion-poll-detail.component.scss'], + encapsulation: ViewEncapsulation.None }) export class MotionPollDetailComponent extends BasePollDetailComponent implements OnInit { public motion: ViewMotion; - public columnDefinition = ['key', 'value']; + public columnDefinition: PblColumnDefinition[] = [ + { + prop: 'user', + width: 'auto', + label: 'Participant' + }, + { + prop: 'vote', + width: 'auto', + label: 'Vote' + } + ]; + public filterProps = ['user.getFullName', 'valueVerbose']; public set chartType(type: ChartType) { this._chartType = type; diff --git a/client/src/app/site/polls/components/base-poll-detail.component.ts b/client/src/app/site/polls/components/base-poll-detail.component.ts index 93864a656..1620b0963 100644 --- a/client/src/app/site/polls/components/base-poll-detail.component.ts +++ b/client/src/app/site/polls/components/base-poll-detail.component.ts @@ -1,11 +1,11 @@ import { OnInit } from '@angular/core'; -import { MatSnackBar, MatTableDataSource } from '@angular/material'; +import { MatSnackBar } from '@angular/material'; import { Title } from '@angular/platform-browser'; import { ActivatedRoute } from '@angular/router'; import { TranslateService } from '@ngx-translate/core'; import { Label } from 'ng2-charts'; -import { BehaviorSubject, Observable } from 'rxjs'; +import { BehaviorSubject, from, Observable } from 'rxjs'; import { GroupRepositoryService } from 'app/core/repositories/users/group-repository.service'; import { BasePollDialogService } from 'app/core/ui-services/base-poll-dialog.service'; @@ -34,6 +34,24 @@ export abstract class BasePollDetailComponent extends Ba */ public groupObservable: Observable = null; + /** + * Details for the iconification of the votes + */ + public voteOptionStyle = { + Y: { + css: 'voted-yes', + icon: 'thumb_up' + }, + N: { + css: 'voted-no', + icon: 'thumb_down' + }, + A: { + css: 'voted-abstain', + icon: 'trip_origin' + } + }; + /** * The reference to the poll. */ @@ -59,8 +77,8 @@ export abstract class BasePollDetailComponent extends Ba */ public chartDataSubject: BehaviorSubject = new BehaviorSubject(null); - // The datasource for the votes-per-user table - public votesDataSource: MatTableDataSource = new MatTableDataSource(); + // The observable for the votes-per-user table + public votesDataObservable: Observable; /** * Constructor @@ -88,7 +106,6 @@ export abstract class BasePollDetailComponent extends Ba protected pollDialog: BasePollDialogService ) { super(title, translate, matSnackbar); - this.votesDataSource.filterPredicate = this.dataSourceFilterPredicate; } /** @@ -141,26 +158,14 @@ export abstract class BasePollDetailComponent extends Ba protected abstract hasPerms(): boolean; - // custom filter for the data source: only search in usernames - protected dataSourceFilterPredicate(data: BaseVoteData, filter: string): boolean { - return ( - data.user && - data.user - .getFullName() - .trim() - .toLowerCase() - .indexOf(filter.trim().toLowerCase()) !== -1 - ); - } - /** * sets the votes data only if the poll wasn't pseudoanonymized */ protected setVotesData(data: BaseVoteData[]): void { if (data.every(voteDate => !voteDate.user)) { - this.votesDataSource.data = null; + this.votesDataObservable = null; } else { - this.votesDataSource.data = data; + this.votesDataObservable = from([data]); } } diff --git a/client/src/app/site/polls/components/poll-progress/poll-progress.component.html b/client/src/app/site/polls/components/poll-progress/poll-progress.component.html index 7718a161e..558a1b36c 100644 --- a/client/src/app/site/polls/components/poll-progress/poll-progress.component.html +++ b/client/src/app/site/polls/components/poll-progress/poll-progress.component.html @@ -1,3 +1,3 @@ - {{ 'Casted votes' }}: {{ poll.voted_id.length }} / {{ max }} + {{ 'Cast votes' }}: {{ poll.voted_id.length }} / {{ max }} diff --git a/client/src/assets/styles/global-components-style.scss b/client/src/assets/styles/global-components-style.scss index cabf80f1f..bd3674906 100644 --- a/client/src/assets/styles/global-components-style.scss +++ b/client/src/assets/styles/global-components-style.scss @@ -158,6 +158,10 @@ background-color: rgba(0, 0, 0, 0.025); } + .pbl-ngrid-header-row, .pbl-ngrid-row { + align-items: stretch; + } + .mat-progress-bar-buffer { background-color: mat-color($background, card) !important; }