added virtual scrolling for single votes tables
This commit is contained in:
parent
93dc78c7d6
commit
d4599a435b
@ -14,8 +14,9 @@
|
||||
<!-- vScrollAuto () -->
|
||||
<pbl-ngrid
|
||||
[ngClass]="cssClasses"
|
||||
[vScrollFixed]="vScrollFixed"
|
||||
[showHeader]="!showFilterBar"
|
||||
[attr.vScrollFixed]="vScrollFixed !== -1 ? vScrollFixed : false"
|
||||
[attr.vScrollAuto]="vScrollFixed === -1"
|
||||
[showHeader]="!showFilterBar || !fullScreen"
|
||||
matCheckboxSelection="selection"
|
||||
[dataSource]="dataSource"
|
||||
[columns]="columnSet"
|
||||
|
@ -47,7 +47,7 @@ export interface ColumnRestriction {
|
||||
* Creates a sort-filter-bar and table with virtual scrolling, where projector and multi select is already
|
||||
* embedded
|
||||
*
|
||||
* Takes a repository-service, a sort-service and a filter-service as an input to display data
|
||||
* Takes a repository-service (or simple Observable), a sort-service and a filter-service as an input to display data
|
||||
* Requires multi-select information
|
||||
* Double binds selected rows
|
||||
*
|
||||
@ -65,6 +65,7 @@ export interface ColumnRestriction {
|
||||
* <os-list-view-table
|
||||
* [listObservableProvider]="motionRepo"
|
||||
* [filterService]="filterService"
|
||||
* [filterProps]="filterProps"
|
||||
* [sortService]="sortService"
|
||||
* [columns]="motionColumnDefinition"
|
||||
* [restricted]="restrictedColumns"
|
||||
@ -96,11 +97,17 @@ export class ListViewTableComponent<V extends BaseViewModel, M extends BaseModel
|
||||
private ngrid: PblNgridComponent;
|
||||
|
||||
/**
|
||||
* The required repository
|
||||
* The required repository (prioritized over listObservable)
|
||||
*/
|
||||
@Input()
|
||||
public listObservableProvider: HasViewModelListObservable<V>;
|
||||
|
||||
/**
|
||||
* ...or the required observable
|
||||
*/
|
||||
@Input()
|
||||
public listObservable: Observable<V[]>;
|
||||
|
||||
/**
|
||||
* The currently active sorting service for the list view
|
||||
*/
|
||||
@ -193,6 +200,13 @@ export class ListViewTableComponent<V extends BaseViewModel, M extends BaseModel
|
||||
@Input()
|
||||
public vScrollFixed = 110;
|
||||
|
||||
/**
|
||||
* Determines whether the table should have a fixed 100vh height or not.
|
||||
* If not, the height must be set by the component
|
||||
*/
|
||||
@Input()
|
||||
public fullScreen = true;
|
||||
|
||||
/**
|
||||
* Option to apply additional classes to the virtual-scrolling-list.
|
||||
*/
|
||||
@ -211,8 +225,8 @@ export class ListViewTableComponent<V extends BaseViewModel, M extends BaseModel
|
||||
*/
|
||||
public get cssClasses(): CssClassDefinition {
|
||||
const defaultClasses = {
|
||||
'virtual-scroll-with-head-bar ngrid-hide-head': this.showFilterBar,
|
||||
'virtual-scroll-full-page': !this.showFilterBar,
|
||||
'virtual-scroll-with-head-bar ngrid-hide-head': this.fullScreen && this.showFilterBar,
|
||||
'virtual-scroll-full-page': this.fullScreen && !this.showFilterBar,
|
||||
multiselect: this.multiSelect
|
||||
};
|
||||
return Object.assign(this._cssClasses, defaultClasses);
|
||||
@ -468,8 +482,10 @@ export class ListViewTableComponent<V extends BaseViewModel, M extends BaseModel
|
||||
* to the used search and filter services
|
||||
*/
|
||||
private getListObservable(): void {
|
||||
if (this.listObservableProvider) {
|
||||
const listObservable = this.listObservableProvider.getViewModelListObservable();
|
||||
if (this.listObservableProvider || this.listObservable) {
|
||||
const listObservable = this.listObservableProvider
|
||||
? this.listObservableProvider.getViewModelListObservable()
|
||||
: this.listObservable;
|
||||
if (this.filterService && this.sortService) {
|
||||
// filtering and sorting
|
||||
this.filterService.initFilters(listObservable);
|
||||
@ -521,15 +537,24 @@ export class ListViewTableComponent<V extends BaseViewModel, M extends BaseModel
|
||||
// custom filter predicates
|
||||
if (this.filterProps && this.filterProps.length) {
|
||||
for (const prop of this.filterProps) {
|
||||
if (item[prop]) {
|
||||
// find nested props
|
||||
const split = prop.split('.');
|
||||
let currValue: any = item;
|
||||
for (const subProp of split) {
|
||||
if (currValue) {
|
||||
currValue = currValue[subProp];
|
||||
}
|
||||
}
|
||||
|
||||
if (currValue) {
|
||||
let propertyAsString = '';
|
||||
// If the property is a function, call it.
|
||||
if (typeof item[prop] === 'function') {
|
||||
propertyAsString = '' + item[prop]();
|
||||
} else if (item[prop].constructor === Array) {
|
||||
propertyAsString = item[prop].join('');
|
||||
if (typeof currValue === 'function') {
|
||||
propertyAsString = '' + currValue();
|
||||
} else if (currValue.constructor === Array) {
|
||||
propertyAsString = currValue.join('');
|
||||
} else {
|
||||
propertyAsString = '' + item[prop];
|
||||
propertyAsString = '' + currValue;
|
||||
}
|
||||
|
||||
if (propertyAsString) {
|
||||
@ -647,8 +672,10 @@ export class ListViewTableComponent<V extends BaseViewModel, M extends BaseModel
|
||||
* This function changes the height of the row for virtual-scrolling in the relating `.scss`-file.
|
||||
*/
|
||||
private changeRowHeight(): void {
|
||||
if (this.vScrollFixed > 0) {
|
||||
document.documentElement.style.setProperty('--pbl-height', this.vScrollFixed + 'px');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the array of selected items against the datastore data. This is
|
||||
|
@ -68,40 +68,58 @@
|
||||
[hasPadding]="false"
|
||||
[legendPosition]="isVotedPoll ? 'right' : 'top'"
|
||||
></os-charts>
|
||||
</div>
|
||||
|
||||
<!-- Named Result -->
|
||||
<div class="named-result-table" *ngIf="poll.type === 'named' && votesDataSource.data">
|
||||
<!-- Single Votes Table -->
|
||||
<ng-container class="named-result-table" *ngIf="poll.type === 'named'">
|
||||
<h3>{{ 'Single votes' | translate }}</h3>
|
||||
<mat-form-field>
|
||||
<input matInput [(ngModel)]="votesDataSource.filter" placeholder="Filter" />
|
||||
</mat-form-field>
|
||||
<mat-table [dataSource]="votesDataSource">
|
||||
<ng-container matColumnDef="users" sticky>
|
||||
<mat-header-cell *matHeaderCellDef>{{ 'User' | translate }}</mat-header-cell>
|
||||
<mat-cell *matCellDef="let row">
|
||||
<div *ngIf="row.user">{{ row.user.getFullName() }}</div>
|
||||
<div *ngIf="!row.user">{{ 'Unknown user' | translate }}</div>
|
||||
</mat-cell>
|
||||
</ng-container>
|
||||
<ng-container
|
||||
[matColumnDef]="'votes-' + option.user_id"
|
||||
*ngFor="let option of poll.options"
|
||||
sticky
|
||||
<os-list-view-table
|
||||
*ngIf="votesDataObservable"
|
||||
[listObservable]="votesDataObservable"
|
||||
[columns]="columnDefinitionSingleVotes"
|
||||
[filterProps]="filterProps"
|
||||
[allowProjector]="false"
|
||||
[fullScreen]="false"
|
||||
[vScrollFixed]="isVotedPoll ? -1 : 60"
|
||||
listStorageKey="assignment-poll-vote"
|
||||
[cssClasses]="{ 'single-votes-table': true }"
|
||||
>
|
||||
<mat-header-cell *matHeaderCellDef>
|
||||
<div *ngIf="option.user">{{ option.user.getFullName() }}</div>
|
||||
<div *ngIf="!option.user">{{ 'Unknown user' | translate }}</div>
|
||||
</mat-header-cell>
|
||||
<mat-cell *matCellDef="let row">
|
||||
{{ row.votes[option.user_id] }}
|
||||
</mat-cell>
|
||||
</ng-container>
|
||||
<!-- Header -->
|
||||
<div *pblNgridHeaderCellDef="'user'; col as col">
|
||||
{{ col.label | translate }}
|
||||
</div>
|
||||
<div *pblNgridHeaderCellDef="'*'; col as col">
|
||||
{{ col.label | translate }}
|
||||
</div>
|
||||
|
||||
<mat-header-row *matHeaderRowDef="columnDefinitionPerName"></mat-header-row>
|
||||
<mat-row *matRowDef="let row; columns: columnDefinitionPerName"></mat-row>
|
||||
</mat-table>
|
||||
<!-- Content -->
|
||||
<div *pblNgridCellDef="'user'; row as vote">
|
||||
<b *ngIf="vote.user">{{ vote.user.getFullName() }}</b>
|
||||
<b *ngIf="!vote.user">{{ 'Anonymous' | translate }}</b>
|
||||
</div>
|
||||
<!-- Y/N/(A) -->
|
||||
<ng-container *ngIf="poll.pollmethod !== AssignmentPollMethods.Votes">
|
||||
<ng-container *ngFor="let option of poll.options">
|
||||
<div
|
||||
*pblNgridCellDef="'votes-' + option.user_id; row as vote"
|
||||
[ngClass]="voteOptionStyle[vote.votes[option.user_id].value].css"
|
||||
class="vote-field"
|
||||
>
|
||||
<mat-icon> {{ voteOptionStyle[vote.votes[option.user_id].value].icon }}</mat-icon> {{ vote.votes[option.user_id].valueVerbose | translate }}
|
||||
</div>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
<!-- Votes method -->
|
||||
<ng-container *ngIf="poll.pollmethod === AssignmentPollMethods.Votes">
|
||||
<div *pblNgridCellDef="'votes'; row as vote">
|
||||
<div *ngFor="let candidate of vote.votes">{{ candidate }}</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
</os-list-view-table>
|
||||
<div *ngIf="!votesDataObservable">
|
||||
{{ 'The individual votes were anonymized.' | translate }}
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
<!-- Meta Infos -->
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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<ViewAssignmentPoll> {
|
||||
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<ViewA
|
||||
}
|
||||
return columns;
|
||||
}
|
||||
|
||||
public columnDefinitionPerName: string[];
|
||||
|
||||
private _chartType: ChartType = 'horizontalBar';
|
||||
|
||||
public constructor(
|
||||
@ -54,17 +60,45 @@ export class AssignmentPollDetailComponent extends BasePollDetailComponent<ViewA
|
||||
groupRepo: GroupRepositoryService,
|
||||
prompt: PromptService,
|
||||
pollDialog: AssignmentPollDialogService,
|
||||
private operator: OperatorService
|
||||
private operator: OperatorService,
|
||||
private viewport: ViewportService
|
||||
) {
|
||||
super(title, translate, matSnackbar, repo, route, groupRepo, prompt, pollDialog);
|
||||
}
|
||||
|
||||
public onPollWithOptionsLoaded(): void {
|
||||
this.columnDefinitionPerName = ['users'].concat(this.poll.options.map(option => '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<ViewA
|
||||
if (!votes[userId]) {
|
||||
votes[userId] = {
|
||||
user: vote.user,
|
||||
votes: {}
|
||||
votes: this.isVotedPoll ? [] : {}
|
||||
};
|
||||
}
|
||||
votes[userId].votes[option.user_id] =
|
||||
this.poll.pollmethod === AssignmentPollMethods.Votes ? vote.weight : vote.valueVerbose;
|
||||
// on votes method, we fill an array with all chosen candidates
|
||||
// on YN(A) we map candidate ids to the vote
|
||||
if (this.isVotedPoll) {
|
||||
if (vote.weight > 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<ViewA
|
||||
this.isReady = true;
|
||||
}
|
||||
|
||||
private getVoteColumnDefinition(prop: string, label: string): PblColumnDefinition {
|
||||
return {
|
||||
prop: prop,
|
||||
label: label,
|
||||
minWidth: 80,
|
||||
width: 'auto'
|
||||
};
|
||||
}
|
||||
|
||||
protected initChartData(): void {
|
||||
if (this.isVotedPoll) {
|
||||
this._chartType = 'doughnut';
|
||||
|
@ -63,30 +63,38 @@
|
||||
<!-- Named table: only show if votes are present -->
|
||||
<div class="named-result-table" *ngIf="poll.type === 'named'">
|
||||
<h3>{{ 'Single votes' | translate }}</h3>
|
||||
<div *ngIf="votesDataSource.data">
|
||||
<mat-form-field>
|
||||
<input matInput [(ngModel)]="votesDataSource.filter" placeholder="Filter" />
|
||||
</mat-form-field>
|
||||
<mat-table [dataSource]="votesDataSource">
|
||||
<ng-container matColumnDef="key" sticky>
|
||||
<mat-header-cell *matHeaderCellDef>{{ 'Participant' | translate }}</mat-header-cell>
|
||||
<mat-cell *matCellDef="let vote">
|
||||
<os-list-view-table
|
||||
[listObservable]="votesDataObservable"
|
||||
[columns]="columnDefinition"
|
||||
[filterProps]="filterProps"
|
||||
[allowProjector]="false"
|
||||
[fullScreen]="false"
|
||||
[vScrollFixed]="60"
|
||||
listStorageKey="motion-poll-vote"
|
||||
[cssClasses]="{ 'single-votes-table': true }"
|
||||
>
|
||||
<!-- Header -->
|
||||
<div *pblNgridHeaderCellDef="'*'; col as col">
|
||||
{{ col.label | translate }}
|
||||
</div>
|
||||
|
||||
<!-- Content -->
|
||||
<div *pblNgridCellDef="'user'; row as vote">
|
||||
<div *ngIf="vote.user">{{ vote.user.getFullName() }}</div>
|
||||
<div *ngIf="!vote.user">{{ 'Anonymous' | translate }}</div>
|
||||
</mat-cell>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="value" sticky>
|
||||
<mat-header-cell *matHeaderCellDef>{{ 'Vote' | translate }}</mat-header-cell>
|
||||
<mat-cell *matCellDef="let vote">{{ vote.valueVerbose }}</mat-cell>
|
||||
</ng-container>
|
||||
|
||||
<mat-header-row *matHeaderRowDef="columnDefinition"></mat-header-row>
|
||||
<mat-row *matRowDef="let vote; columns: columnDefinition"></mat-row>
|
||||
</mat-table>
|
||||
</div>
|
||||
<div *ngIf="!votesDataSource.data">
|
||||
<div *pblNgridCellDef="'vote'; row as vote" class="vote-cell">
|
||||
<div class="vote-cell-icon-container" [ngClass]="voteOptionStyle[vote.value].css">
|
||||
<mat-icon>{{ voteOptionStyle[vote.value].icon }}</mat-icon>
|
||||
</div>
|
||||
<div>{{ vote.valueVerbose | translate }}</div>
|
||||
</div>
|
||||
|
||||
<!-- No Results -->
|
||||
<div *pblNgridNoDataRef class="pbl-ngrid-no-data">
|
||||
{{ 'The individual votes were made anonymous.' | translate }}
|
||||
</div>
|
||||
</os-list-view-table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -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%;
|
||||
}
|
||||
|
@ -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<ViewMotionPoll> 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;
|
||||
|
@ -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<V extends ViewBasePoll> extends Ba
|
||||
*/
|
||||
public groupObservable: Observable<ViewGroup[]> = 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<V extends ViewBasePoll> extends Ba
|
||||
*/
|
||||
public chartDataSubject: BehaviorSubject<ChartData> = new BehaviorSubject(null);
|
||||
|
||||
// The datasource for the votes-per-user table
|
||||
public votesDataSource: MatTableDataSource<BaseVoteData> = new MatTableDataSource();
|
||||
// The observable for the votes-per-user table
|
||||
public votesDataObservable: Observable<BaseVoteData[]>;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
@ -88,7 +106,6 @@ export abstract class BasePollDetailComponent<V extends ViewBasePoll> extends Ba
|
||||
protected pollDialog: BasePollDialogService<V>
|
||||
) {
|
||||
super(title, translate, matSnackbar);
|
||||
this.votesDataSource.filterPredicate = this.dataSourceFilterPredicate;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -141,26 +158,14 @@ export abstract class BasePollDetailComponent<V extends ViewBasePoll> 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]);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,3 +1,3 @@
|
||||
<span> {{ 'Casted votes' }}: {{ poll.voted_id.length }} / {{ max }} </span>
|
||||
<span> {{ 'Cast votes' }}: {{ poll.voted_id.length }} / {{ max }} </span>
|
||||
|
||||
<mat-progress-bar class="voting-progress-bar" [value]="valueInPercent"></mat-progress-bar>
|
||||
|
@ -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;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user