Rework assignment poll slide

Reworked assignment poll slide
and refactored the assignment poll detail table
into an own component
This commit is contained in:
Sean 2020-03-20 16:53:30 +01:00
parent b873dc156b
commit 072ec937a1
14 changed files with 238 additions and 161 deletions

View File

@ -0,0 +1,42 @@
<div *ngIf="poll">
<table class="assignment-result-table">
<tbody>
<tr>
<th class="voting-option" translate>Candidates</th>
<th class="result yes">
<span *ngIf="!isMethodY" translate>
Yes
</span>
<span *ngIf="isMethodY" translate>
Votes
</span>
</th>
<th class="result no" translate *ngIf="!isMethodY">No</th>
<th class="result abstain" translate *ngIf="isMethodYNA">Abstain</th>
</tr>
<tr *ngFor="let row of tableData" [class]="row.class">
<td class="voting-option">
<div>
<span>
{{ row.votingOption | pollKeyVerbose | translate }}
</span>
<span class="user-subtitle" *ngIf="row.votingOptionSubtitle">
<br />
{{ row.votingOptionSubtitle }}
</span>
</div>
</td>
<td class="result" *ngFor="let vote of row.value">
<div class="single-result" [ngClass]="getVoteClass(vote)" *ngIf="vote && voteFitsMethod(vote)">
<span>
<span *ngIf="vote.showPercent">
{{ vote.amount | pollPercentBase: poll }}
</span>
{{ vote.amount | parsePollNumber }}
</span>
</div>
</td>
</tr>
</tbody>
</table>
</div>

View File

@ -0,0 +1,49 @@
@import '~assets/styles/poll-styles-common.scss';
.assignment-result-table {
margin-top: 2em;
display: block;
overflow-x: auto;
border-collapse: collapse;
th {
font-weight: initial;
}
tr {
height: 48px;
td:first-child {
padding-right: 1em;
}
}
tr.sums {
border-bottom: none;
td {
padding-top: 1em;
padding-bottom: 1em;
}
}
.result {
text-align: right;
padding-left: 1em;
}
.voting-option {
min-width: 200px;
width: 100%;
text-align: left;
}
.user + .sums {
td {
padding-top: 2em;
}
}
.single-result {
white-space: pre;
}
}

View File

@ -0,0 +1,26 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { E2EImportsModule } from 'e2e-imports.module';
import { AssignmentPollDetailContentComponent } from './assignment-poll-detail-content.component';
describe('AssignmentPollDetailContentComponent', () => {
let component: AssignmentPollDetailContentComponent;
let fixture: ComponentFixture<AssignmentPollDetailContentComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [E2EImportsModule]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(AssignmentPollDetailContentComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,55 @@
import { Component, Input } from '@angular/core';
import { AssignmentPollMethod } from 'app/shared/models/assignments/assignment-poll';
import { ViewAssignmentPoll } from 'app/site/assignments/models/view-assignment-poll';
import { AssignmentPollService } from 'app/site/assignments/services/assignment-poll.service';
import { PollData, PollTableData, VotingResult } from 'app/site/polls/services/poll.service';
@Component({
selector: 'os-assignment-poll-detail-content',
templateUrl: './assignment-poll-detail-content.component.html',
styleUrls: ['./assignment-poll-detail-content.component.scss']
})
export class AssignmentPollDetailContentComponent {
@Input()
public poll: ViewAssignmentPoll | PollData;
public constructor(private pollService: AssignmentPollService) {}
private get method(): string {
return this.poll.pollmethod;
}
public get isMethodY(): boolean {
return this.method === AssignmentPollMethod.Votes;
}
public get isMethodYN(): boolean {
return this.method === AssignmentPollMethod.YN;
}
public get isMethodYNA(): boolean {
return this.method === AssignmentPollMethod.YNA;
}
public get tableData(): PollTableData[] {
return this.pollService.generateTableData(this.poll);
}
public getVoteClass(votingResult: VotingResult): string {
return votingResult.vote;
}
public voteFitsMethod(result: VotingResult): boolean {
if (this.isMethodY) {
if (result.vote === 'abstain' || result.vote === 'no') {
return false;
}
} else if (this.isMethodYN) {
if (result.vote === 'abstain') {
return false;
}
}
return true;
}
}

View File

@ -123,6 +123,7 @@ import { PollKeyVerbosePipe } from './pipes/poll-key-verbose.pipe';
import { PollPercentBasePipe } from './pipes/poll-percent-base.pipe'; import { PollPercentBasePipe } from './pipes/poll-percent-base.pipe';
import { VotingPrivacyWarningComponent } from './components/voting-privacy-warning/voting-privacy-warning.component'; import { VotingPrivacyWarningComponent } from './components/voting-privacy-warning/voting-privacy-warning.component';
import { MotionPollDetailContentComponent } from './components/motion-poll-detail-content/motion-poll-detail-content.component'; import { MotionPollDetailContentComponent } from './components/motion-poll-detail-content/motion-poll-detail-content.component';
import { AssignmentPollDetailContentComponent } from './components/assignment-poll-detail-content/assignment-poll-detail-content.component';
/** /**
* Share Module for all "dumb" components and pipes. * Share Module for all "dumb" components and pipes.
@ -288,7 +289,8 @@ import { MotionPollDetailContentComponent } from './components/motion-poll-detai
PollKeyVerbosePipe, PollKeyVerbosePipe,
PollPercentBasePipe, PollPercentBasePipe,
VotingPrivacyWarningComponent, VotingPrivacyWarningComponent,
MotionPollDetailContentComponent MotionPollDetailContentComponent,
AssignmentPollDetailContentComponent
], ],
declarations: [ declarations: [
PermsDirective, PermsDirective,
@ -347,7 +349,8 @@ import { MotionPollDetailContentComponent } from './components/motion-poll-detai
PollKeyVerbosePipe, PollKeyVerbosePipe,
PollPercentBasePipe, PollPercentBasePipe,
VotingPrivacyWarningComponent, VotingPrivacyWarningComponent,
MotionPollDetailContentComponent MotionPollDetailContentComponent,
AssignmentPollDetailContentComponent
], ],
providers: [ providers: [
{ {

View File

@ -28,54 +28,9 @@
</span> </span>
</div> </div>
<div class="assignment-result-wrapper" *ngIf="poll.stateHasVotes"> <div class="assignment-result-wrapper" *ngIf="poll && poll.stateHasVotes">
<!-- Result Table --> <!-- Result Table -->
<div> <os-assignment-poll-detail-content [poll]="poll"></os-assignment-poll-detail-content>
<table class="assignment-result-table">
<tbody>
<tr>
<th class="voting-option" translate>Candidates</th>
<th class="result yes">
<span *ngIf="!poll.isMethodY" translate>
Yes
</span>
<span *ngIf="poll.isMethodY" translate>
Votes
</span>
</th>
<th class="result no" translate *ngIf="!poll.isMethodY">No</th>
<th class="result abstain" translate *ngIf="poll.isMethodYNA">Abstain</th>
</tr>
<tr *ngFor="let row of getTableData()" [class]="row.class">
<td class="voting-option">
<div>
<span>
{{ row.votingOption | pollKeyVerbose | translate }}
</span>
<span class="user-subtitle" *ngIf="row.votingOptionSubtitle">
<br />
{{ row.votingOptionSubtitle }}
</span>
</div>
</td>
<td class="result" *ngFor="let vote of row.value">
<div
class="single-result"
[ngClass]="getVoteClass(vote)"
*ngIf="vote && voteFitsMethod(vote)"
>
<span>
<span *ngIf="vote.showPercent">
{{ vote.amount | pollPercentBase: poll }}
</span>
{{ vote.amount | parsePollNumber }}
</span>
</div>
</td>
</tr>
</tbody>
</table>
</div>
<!-- Result Chart --> <!-- Result Chart -->
<div class="chart-wrapper"> <div class="chart-wrapper">

View File

@ -1,55 +1,4 @@
@import '~assets/styles/poll-colors.scss';
@import '~assets/styles/poll-styles-common.scss';
.assignment-result-wrapper { .assignment-result-wrapper {
.assignment-result-table {
margin-top: 2em;
display: block;
overflow-x: auto;
border-collapse: collapse;
th {
font-weight: initial;
}
tr {
height: 48px;
td:first-child {
padding-right: 1em;
}
}
tr.sums {
border-bottom: none;
td {
padding-top: 1em;
padding-bottom: 1em;
}
}
.result {
text-align: right;
padding-left: 1em;
}
.voting-option {
min-width: 200px;
width: 100%;
text-align: left;
}
.user + .sums {
td {
padding-top: 2em;
}
}
.single-result {
white-space: pre;
}
}
.chart-wrapper { .chart-wrapper {
margin-top: 2em; margin-top: 2em;
.pie-chart { .pie-chart {

View File

@ -13,7 +13,6 @@ import { GroupRepositoryService } from 'app/core/repositories/users/group-reposi
import { PromptService } from 'app/core/ui-services/prompt.service'; import { PromptService } from 'app/core/ui-services/prompt.service';
import { VoteValue } from 'app/shared/models/poll/base-vote'; import { VoteValue } from 'app/shared/models/poll/base-vote';
import { BasePollDetailComponent } from 'app/site/polls/components/base-poll-detail.component'; import { BasePollDetailComponent } from 'app/site/polls/components/base-poll-detail.component';
import { PollTableData, VotingResult } from 'app/site/polls/services/poll.service';
import { AssignmentPollDialogService } from '../../services/assignment-poll-dialog.service'; import { AssignmentPollDialogService } from '../../services/assignment-poll-dialog.service';
import { AssignmentPollService } from '../../services/assignment-poll.service'; import { AssignmentPollService } from '../../services/assignment-poll.service';
import { ViewAssignmentPoll } from '../../models/view-assignment-poll'; import { ViewAssignmentPoll } from '../../models/view-assignment-poll';
@ -121,27 +120,6 @@ export class AssignmentPollDetailComponent extends BasePollDetailComponent<ViewA
return this.operator.hasPerms('assignments.can_manage'); return this.operator.hasPerms('assignments.can_manage');
} }
public getVoteClass(votingResult: VotingResult): string {
return votingResult.vote;
}
public voteFitsMethod(result: VotingResult): boolean {
if (this.poll.isMethodY) {
if (result.vote === 'abstain' || result.vote === 'no') {
return false;
}
} else if (this.poll.isMethodYN) {
if (result.vote === 'abstain') {
return false;
}
}
return true;
}
public getTableData(): PollTableData[] {
return this.pollService.generateTableData(this.poll);
}
protected onDeleted(): void { protected onDeleted(): void {
this.router.navigate(['assignments', this.poll.assignment_id]); this.router.navigate(['assignments', this.poll.assignment_id]);
} }

View File

@ -13,7 +13,14 @@ import {
import { MajorityMethod, VOTE_UNDOCUMENTED } from 'app/shared/models/poll/base-poll'; import { MajorityMethod, VOTE_UNDOCUMENTED } from 'app/shared/models/poll/base-poll';
import { ParsePollNumberPipe } from 'app/shared/pipes/parse-poll-number.pipe'; import { ParsePollNumberPipe } from 'app/shared/pipes/parse-poll-number.pipe';
import { PollKeyVerbosePipe } from 'app/shared/pipes/poll-key-verbose.pipe'; import { PollKeyVerbosePipe } from 'app/shared/pipes/poll-key-verbose.pipe';
import { PollData, PollService, PollTableData, VotingResult } from 'app/site/polls/services/poll.service'; import {
PollData,
PollDataOption,
PollService,
PollTableData,
VotingResult
} from 'app/site/polls/services/poll.service';
import { ViewAssignmentOption } from '../models/view-assignment-option';
import { ViewAssignmentPoll } from '../models/view-assignment-poll'; import { ViewAssignmentPoll } from '../models/view-assignment-poll';
@Injectable({ @Injectable({
@ -80,7 +87,7 @@ export class AssignmentPollService extends PollService {
return poll; return poll;
} }
private getGlobalVoteKeys(poll: ViewAssignmentPoll): VotingResult[] { private getGlobalVoteKeys(poll: ViewAssignmentPoll | PollData): VotingResult[] {
return [ return [
{ {
vote: 'amount_global_no', vote: 'amount_global_no',
@ -95,30 +102,45 @@ export class AssignmentPollService extends PollService {
]; ];
} }
public generateTableData(poll: ViewAssignmentPoll): PollTableData[] { public generateTableData(poll: ViewAssignmentPoll | PollData): PollTableData[] {
console.log('poll: ', poll);
const tableData: PollTableData[] = poll.options const tableData: PollTableData[] = poll.options
.sort((a, b) => { .sort((a, b) => {
if (this.sortByVote) { if (this.sortByVote) {
return b.yes - a.yes; return b.yes - a.yes;
} else { } else {
return b.weight - a.weight; // PollData does not have weight, we need to rely on the order of things.
if (a.weight && b.weight) {
return b.weight - a.weight;
}
} }
}) })
.map(candidate => ({ .map((candidate: ViewAssignmentOption) => {
votingOption: candidate.user.short_name, const pollTableEntry: PollTableData = {
votingOptionSubtitle: candidate.user.getLevelAndNumber(), class: 'user',
class: 'user', value: super.getVoteTableKeys(poll).map(
value: super.getVoteTableKeys(poll).map( key =>
key => ({
({ vote: key.vote,
vote: key.vote, amount: candidate[key.vote],
amount: candidate[key.vote], icon: key.icon,
icon: key.icon, hide: key.hide,
hide: key.hide, showPercent: key.showPercent
showPercent: key.showPercent } as VotingResult)
} as VotingResult) )
) };
}));
// Since pollData does not have any subtitle option
if (candidate instanceof ViewAssignmentOption) {
pollTableEntry.votingOption = candidate.user.short_name;
pollTableEntry.votingOptionSubtitle = candidate.user.getLevelAndNumber();
} else {
pollTableEntry.votingOption = (candidate as PollDataOption).user.short_name;
}
return pollTableEntry;
});
tableData.push(...this.formatVotingResultToTableData(this.getGlobalVoteKeys(poll), poll)); tableData.push(...this.formatVotingResultToTableData(this.getGlobalVoteKeys(poll), poll));
tableData.push(...this.formatVotingResultToTableData(super.getSumTableKeys(poll), poll)); tableData.push(...this.formatVotingResultToTableData(super.getSumTableKeys(poll), poll));
return tableData; return tableData;

View File

@ -105,17 +105,22 @@ export interface PollData {
pollmethod: string; pollmethod: string;
type: string; type: string;
onehundred_percent_base: string; onehundred_percent_base: string;
options: { options: PollDataOption[];
user?: {
short_name: string;
};
yes?: number;
no?: number;
abstain?: number;
}[];
votesvalid: number; votesvalid: number;
votesinvalid: number; votesinvalid: number;
votescast: number; votescast: number;
amount_global_no?: number;
amount_global_abstain?: number;
}
export interface PollDataOption {
user?: {
short_name?: string;
};
yes?: number;
no?: number;
abstain?: number;
weight?: number;
} }
interface OpenSlidesSettings { interface OpenSlidesSettings {
@ -126,7 +131,7 @@ interface OpenSlidesSettings {
* Interface describes the possible data for the result-table. * Interface describes the possible data for the result-table.
*/ */
export interface PollTableData { export interface PollTableData {
votingOption: string; votingOption?: string;
votingOptionSubtitle?: string; votingOptionSubtitle?: string;
class?: string; class?: string;
value: VotingResult[]; value: VotingResult[];

View File

@ -3,11 +3,7 @@
<h1 class="assignment-title">{{ data.data.assignment.title }}</h1> <h1 class="assignment-title">{{ data.data.assignment.title }}</h1>
<h2 class="poll-title">{{ data.data.poll.title }}</h2> <h2 class="poll-title">{{ data.data.poll.title }}</h2>
</div> </div>
<div class="charts-wrapper" *ngIf="data.data.poll.state === PollState.Published"> <div *ngIf="data.data.poll.state === PollState.Published">
<os-charts <os-assignment-poll-detail-content [poll]="data.data.poll"></os-assignment-poll-detail-content>
[labels]="pollService.getChartLabels(data.data.poll)"
[data]="chartDataSubject | async"
[hasPadding]="false"
></os-charts>
</div> </div>
</ng-container> </ng-container>

View File

@ -5,7 +5,3 @@
.slidetitle { .slidetitle {
margin-bottom: 15px; margin-bottom: 15px;
} }
.charts-wrapper {
position: relative;
}

View File

@ -63,7 +63,7 @@ async def assignment_poll_slide(
options = get_models(all_data, "assignments/assignment-option", poll["options_id"]) options = get_models(all_data, "assignments/assignment-option", poll["options_id"])
for option in sorted(options, key=lambda option: option["weight"]): for option in sorted(options, key=lambda option: option["weight"]):
option_data: Dict[str, Any] = { option_data: Dict[str, Any] = {
"user": {"full_name": await get_user_name(all_data, option["user_id"])} "user": {"short_name": await get_user_name(all_data, option["user_id"])}
} }
if poll["state"] == AssignmentPoll.STATE_PUBLISHED: if poll["state"] == AssignmentPoll.STATE_PUBLISHED:
option_data["yes"] = float(option["yes"]) option_data["yes"] = float(option["yes"])

View File

@ -16,3 +16,4 @@ roman>=2.0,<3.2
setuptools>=29.0,<42.0 setuptools>=29.0,<42.0
typing_extensions>=3.6.6,<3.8 typing_extensions>=3.6.6,<3.8
websockets>=8.0,<9.0 websockets>=8.0,<9.0
twisted>=19.0,<20.0