From 97a5bb4aa6d86a11f41ec7b62067b83f3ac54141 Mon Sep 17 00:00:00 2001 From: Sean Engelhardt Date: Mon, 24 Feb 2020 16:55:07 +0100 Subject: [PATCH] Cleanup Voting, enhance UI and UX removed certain unnecesary fields cleaned up a lot of code redone some of the UI some database and server adjustments --- .../assignment-repository.service.ts | 21 --- .../users/user-repository.service.ts | 15 +- .../ui-services/base-poll-dialog.service.ts | 26 ++-- .../breadcrumb/breadcrumb.component.html | 14 -- .../breadcrumb/breadcrumb.component.scss | 4 - .../breadcrumb/breadcrumb.component.spec.ts | 26 ---- .../breadcrumb/breadcrumb.component.ts | 69 --------- .../progress-snack-bar.component.scss | 24 +-- .../models/assignments/assignment-poll.ts | 2 + .../assignments/assignment-related-user.ts | 1 - .../app/shared/models/motions/motion-poll.ts | 1 + .../src/app/shared/models/poll/base-poll.ts | 6 +- .../app/shared/pipes/poll-key-verbose.pipe.ts | 2 +- .../pipes/poll-percent-base.pipe.spec.ts | 2 +- .../shared/pipes/poll-percent-base.pipe.ts | 2 +- client/src/app/shared/shared.module.ts | 3 - .../assignment-detail.component.html | 10 +- .../assignment-detail.component.spec.ts | 11 +- .../assignment-detail.component.ts | 14 +- ...ment-poll-detail-component.scss-theme.scss | 12 ++ .../assignment-poll-detail.component.html | 91 +++++------ .../assignment-poll-detail.component.scss | 63 +++++--- .../assignment-poll-detail.component.ts | 23 +-- .../assignment-poll-dialog.component.html | 36 ++--- .../assignment-poll-dialog.component.ts | 88 +---------- .../assignment-poll-vote.component.html | 4 - .../assignment-poll-vote.component.ts | 7 +- .../assignment-poll.component.html | 64 +++++--- .../assignment-poll.component.scss | 53 +++---- .../assignment-poll.component.spec.ts | 5 +- .../assignment-poll.component.ts | 73 --------- .../models/view-assignment-poll.ts | 51 ++++--- .../assignments/models/view-assignment.ts | 4 +- .../services/assignment-pdf.service.ts | 16 +- .../assignment-poll-dialog.service.ts | 5 +- .../services/assignment-poll-pdf.service.ts | 19 ++- .../services/assignment-poll.service.ts | 21 ++- .../site/motions/models/view-motion-poll.ts | 91 +++-------- .../motion-detail.component.spec.ts | 4 +- .../motion-detail/motion-detail.component.ts | 18 ++- .../motion-poll-detail.component.html | 138 +++++++++-------- .../motion-poll-detail.component.scss | 70 +++++---- ...tion-poll-detail.component.scss-theme.scss | 8 - .../motion-poll-dialog.component.spec.ts | 2 +- .../motion-poll-dialog.component.ts | 6 +- .../motion-poll-vote.component.html | 15 +- .../motion-poll-vote.component.scss | 24 ++- .../motion-poll-vote.component.spec.ts | 3 +- .../motion-poll/motion-poll.component.html | 141 +++++++++++------- .../motion-poll/motion-poll.component.scss | 89 +++++------ .../motion-poll.component.scss-theme.scss | 5 - .../motion-poll/motion-poll.component.spec.ts | 3 +- .../motion-poll/motion-poll.component.ts | 11 -- .../motions/services/motion-pdf.service.ts | 14 +- .../services/motion-poll-dialog.service.ts | 5 +- .../motions/services/motion-poll.service.ts | 15 +- .../components/base-poll-detail.component.ts | 89 +---------- .../components/base-poll-dialog.component.ts | 7 +- .../polls/components/base-poll.component.ts | 19 +++ .../poll-form/poll-form.component.html | 2 + .../poll-form/poll-form.component.spec.ts | 4 +- .../poll-form/poll-form.component.ts | 67 +++++---- .../poll-progress.component.html | 17 ++- .../poll-progress.component.scss | 10 ++ .../poll-progress.component.spec.ts | 3 +- .../poll-progress/poll-progress.component.ts | 26 ++-- .../app/site/polls/models/has-view-polls.ts | 5 + .../app/site/polls/models/view-base-poll.ts | 76 +++++++--- .../app/site/polls/services/poll.service.ts | 22 ++- client/src/app/site/users/models/view-user.ts | 1 + .../assignment/assignment-slide-data.ts | 1 - .../assignment-slide.component.html | 2 - .../styles/global-components-style.scss | 8 +- client/src/assets/styles/poll-colors.scss | 6 +- .../src/assets/styles/poll-common-styles.scss | 63 -------- client/src/styles.scss | 2 + openslides/assignments/config_variables.py | 16 +- .../assignments/migrations/0008_voting_1.py | 7 +- .../assignments/migrations/0010_voting_3.py | 1 + openslides/assignments/models.py | 42 ++---- openslides/assignments/projector.py | 5 +- openslides/assignments/serializers.py | 2 +- openslides/assignments/views.py | 46 +----- tests/integration/assignments/test_viewset.py | 49 ------ 84 files changed, 893 insertions(+), 1255 deletions(-) delete mode 100644 client/src/app/shared/components/breadcrumb/breadcrumb.component.html delete mode 100644 client/src/app/shared/components/breadcrumb/breadcrumb.component.scss delete mode 100644 client/src/app/shared/components/breadcrumb/breadcrumb.component.spec.ts delete mode 100644 client/src/app/shared/components/breadcrumb/breadcrumb.component.ts create mode 100644 client/src/app/site/assignments/components/assignment-poll-detail/assignment-poll-detail-component.scss-theme.scss create mode 100644 client/src/app/site/polls/models/has-view-polls.ts delete mode 100644 client/src/assets/styles/poll-common-styles.scss diff --git a/client/src/app/core/repositories/assignments/assignment-repository.service.ts b/client/src/app/core/repositories/assignments/assignment-repository.service.ts index ecd6775fa..764a181d7 100644 --- a/client/src/app/core/repositories/assignments/assignment-repository.service.ts +++ b/client/src/app/core/repositories/assignments/assignment-repository.service.ts @@ -80,7 +80,6 @@ export class AssignmentRepositoryService extends BaseIsAgendaItemAndListOfSpeake private readonly restPath = '/rest/assignments/assignment/'; private readonly candidatureOtherPath = '/candidature_other/'; private readonly candidatureSelfPath = '/candidature_self/'; - private readonly markElectedPath = '/mark_elected/'; /** * Constructor for the Assignment Repository. @@ -158,26 +157,6 @@ export class AssignmentRepositoryService extends BaseIsAgendaItemAndListOfSpeake await this.httpService.delete(this.restPath + assignment.id + this.candidatureSelfPath); } - /** - * change the 'elected' state of an election candidate - * - * @param assignmentRelatedUser - * @param assignment - * @param elected true if the candidate is to be elected, false if unelected - */ - public async markElected( - assignmentRelatedUser: ViewAssignmentRelatedUser, - assignment: ViewAssignment, - elected: boolean - ): Promise { - const data = { user: assignmentRelatedUser.user_id }; - if (elected) { - await this.httpService.post(this.restPath + assignment.id + this.markElectedPath, data); - } else { - await this.httpService.delete(this.restPath + assignment.id + this.markElectedPath, data); - } - } - /** * Sends a request to sort an assignment's candidates * diff --git a/client/src/app/core/repositories/users/user-repository.service.ts b/client/src/app/core/repositories/users/user-repository.service.ts index 35f74ed29..c317d27ea 100644 --- a/client/src/app/core/repositories/users/user-repository.service.ts +++ b/client/src/app/core/repositories/users/user-repository.service.ts @@ -125,6 +125,18 @@ export class UserRepositoryService extends BaseRepository { return this.translate.instant(plural ? 'Participants' : 'Participant'); }; @@ -145,12 +157,13 @@ export class UserRepositoryService extends BaseRepository this.getFullName(viewModel); viewModel.getShortName = () => this.getShortName(viewModel); + viewModel.getLevelAndNumber = () => this.getLevelAndNumber(viewModel); return viewModel; } diff --git a/client/src/app/core/ui-services/base-poll-dialog.service.ts b/client/src/app/core/ui-services/base-poll-dialog.service.ts index cff02ba23..0e45c36c1 100644 --- a/client/src/app/core/ui-services/base-poll-dialog.service.ts +++ b/client/src/app/core/ui-services/base-poll-dialog.service.ts @@ -8,7 +8,6 @@ import { PollState, PollType } from 'app/shared/models/poll/base-poll'; import { mediumDialogSettings } from 'app/shared/utils/dialog-settings'; import { BasePollDialogComponent } from 'app/site/polls/components/base-poll-dialog.component'; import { ViewBasePoll } from 'app/site/polls/models/view-base-poll'; -import { PollService } from '../../site/polls/services/poll.service'; /** * Abstract class for showing a poll dialog. Has to be subclassed to provide the right `PollService` @@ -17,42 +16,35 @@ import { PollService } from '../../site/polls/services/poll.service'; providedIn: 'root' }) export abstract class BasePollDialogService { - protected dialogComponent: ComponentType; + protected dialogComponent: ComponentType>; - public constructor( - private dialog: MatDialog, - private mapper: CollectionStringMapperService, - private service: PollService - ) {} + public constructor(private dialog: MatDialog, private mapper: CollectionStringMapperService) {} /** * Opens the dialog to enter votes and edit the meta-info for a poll. * * @param data Passing the (existing or new) data for the poll */ - public async openDialog(poll: Partial & Collection): Promise { - if (!poll.poll) { - this.service.fillDefaultPollData(poll); - } + public async openDialog(viewPoll: Partial & Collection): Promise { const dialogRef = this.dialog.open(this.dialogComponent, { - data: poll, + data: viewPoll, ...mediumDialogSettings }); const result = await dialogRef.afterClosed().toPromise(); if (result) { - const repo = this.mapper.getRepository(poll.collectionString); - if (!poll.poll) { + const repo = this.mapper.getRepository(viewPoll.collectionString); + if (!viewPoll.poll) { await repo.create(result); } else { let update = result; - if (poll.state !== PollState.Created) { + if (viewPoll.state !== PollState.Created) { update = { title: result.title, onehundred_percent_base: result.onehundred_percent_base, majority_method: result.majority_method, description: result.description }; - if (poll.type === PollType.Analog) { + if (viewPoll.type === PollType.Analog) { update = { ...update, votes: result.votes, @@ -60,7 +52,7 @@ export abstract class BasePollDialogService { }; } } - await repo.patch(update, poll); + await repo.patch(update, viewPoll); } } } diff --git a/client/src/app/shared/components/breadcrumb/breadcrumb.component.html b/client/src/app/shared/components/breadcrumb/breadcrumb.component.html deleted file mode 100644 index 67c604bb5..000000000 --- a/client/src/app/shared/components/breadcrumb/breadcrumb.component.html +++ /dev/null @@ -1,14 +0,0 @@ -
- - - - {{ breadcrumb.label }} - - - -
diff --git a/client/src/app/shared/components/breadcrumb/breadcrumb.component.scss b/client/src/app/shared/components/breadcrumb/breadcrumb.component.scss deleted file mode 100644 index a7ccf127e..000000000 --- a/client/src/app/shared/components/breadcrumb/breadcrumb.component.scss +++ /dev/null @@ -1,4 +0,0 @@ -.active-breadcrumb { - // Theme - color: rgba($color: #317796, $alpha: 1); -} diff --git a/client/src/app/shared/components/breadcrumb/breadcrumb.component.spec.ts b/client/src/app/shared/components/breadcrumb/breadcrumb.component.spec.ts deleted file mode 100644 index 7739ffcba..000000000 --- a/client/src/app/shared/components/breadcrumb/breadcrumb.component.spec.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; - -import { E2EImportsModule } from 'e2e-imports.module'; - -import { BreadcrumbComponent } from './breadcrumb.component'; - -describe('BreadcrumbComponent', () => { - let component: BreadcrumbComponent; - let fixture: ComponentFixture; - - beforeEach(async(() => { - TestBed.configureTestingModule({ - imports: [E2EImportsModule] - }).compileComponents(); - })); - - beforeEach(() => { - fixture = TestBed.createComponent(BreadcrumbComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/client/src/app/shared/components/breadcrumb/breadcrumb.component.ts b/client/src/app/shared/components/breadcrumb/breadcrumb.component.ts deleted file mode 100644 index 6b33d4524..000000000 --- a/client/src/app/shared/components/breadcrumb/breadcrumb.component.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { Component, Input, OnInit } from '@angular/core'; - -/** - * Describes, how one breadcrumb can look like. - */ -export interface Breadcrumb { - label: string; - action: () => any; - active?: boolean; -} - -@Component({ - selector: 'os-breadcrumb', - templateUrl: './breadcrumb.component.html', - styleUrls: ['./breadcrumb.component.scss'] -}) -export class BreadcrumbComponent implements OnInit { - /** - * A list of all breadcrumbs, that should be rendered. - * - * @param labels A list of strings or the interface `Breadcrumb`. - */ - @Input() - public set breadcrumbs(labels: string[] | Breadcrumb[]) { - this.breadcrumbList = []; - - // https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-3.html#caveats - for (const breadcrumb of labels) { - if (typeof breadcrumb === 'string') { - this.breadcrumbList.push({ label: breadcrumb, action: null }); - } else { - this.breadcrumbList.push(breadcrumb); - } - } - } - - /** - * The current active index, if not the last one. - * - * @param index The index as number. - */ - @Input() - public set activeIndex(index: number) { - for (const breadcrumb of this.breadcrumbList) { - breadcrumb.active = false; - } - this.breadcrumbList[index].active = true; - } - - /** - * The list of the breadcrumbs built by the input. - */ - public breadcrumbList: Breadcrumb[] = []; - - /** - * Default constructor. - */ - public constructor() {} - - /** - * OnInit. - * Sets the last breadcrumb as the active breadcrumb if not defined before. - */ - public ngOnInit(): void { - if (this.breadcrumbList.length && !this.breadcrumbList.some(breadcrumb => breadcrumb.active)) { - this.breadcrumbList[this.breadcrumbList.length - 1].active = true; - } - } -} diff --git a/client/src/app/shared/components/progress-snack-bar/progress-snack-bar.component.scss b/client/src/app/shared/components/progress-snack-bar/progress-snack-bar.component.scss index 10876178e..4c8ae7488 100644 --- a/client/src/app/shared/components/progress-snack-bar/progress-snack-bar.component.scss +++ b/client/src/app/shared/components/progress-snack-bar/progress-snack-bar.component.scss @@ -5,16 +5,22 @@ 'message action' 'bar action'; grid-template-columns: auto min-content; -} -.message { - grid-area: message; -} + .mat-progress-bar-buffer { + // TODO theme + // background-color: mat-color($background, card) !important; + background-color: white !important; + } -.bar { - grid-area: bar; -} + .message { + grid-area: message; + } -.action { - grid-area: action; + .bar { + grid-area: bar; + } + + .action { + grid-area: action; + } } diff --git a/client/src/app/shared/models/assignments/assignment-poll.ts b/client/src/app/shared/models/assignments/assignment-poll.ts index d61527091..65b17d59c 100644 --- a/client/src/app/shared/models/assignments/assignment-poll.ts +++ b/client/src/app/shared/models/assignments/assignment-poll.ts @@ -14,6 +14,8 @@ export enum AssignmentPollMethods { */ export class AssignmentPoll extends BasePoll { public static COLLECTIONSTRING = 'assignments/assignment-poll'; + public static defaultGroupsConfig = 'assignment_poll_default_groups'; + public static defaultPollMethodConfig = 'assignment_poll_method'; public id: number; public assignment_id: number; diff --git a/client/src/app/shared/models/assignments/assignment-related-user.ts b/client/src/app/shared/models/assignments/assignment-related-user.ts index 5325d2a9f..07b931dc5 100644 --- a/client/src/app/shared/models/assignments/assignment-related-user.ts +++ b/client/src/app/shared/models/assignments/assignment-related-user.ts @@ -8,7 +8,6 @@ export class AssignmentRelatedUser extends BaseModel { public id: number; public user_id: number; - public elected: boolean; public assignment_id: number; public weight: number; diff --git a/client/src/app/shared/models/motions/motion-poll.ts b/client/src/app/shared/models/motions/motion-poll.ts index 95d5a8714..061cce803 100644 --- a/client/src/app/shared/models/motions/motion-poll.ts +++ b/client/src/app/shared/models/motions/motion-poll.ts @@ -12,6 +12,7 @@ export enum MotionPollMethods { */ export class MotionPoll extends BasePoll { public static COLLECTIONSTRING = 'motions/motion-poll'; + public static defaultGroupsConfig = 'motion_poll_default_groups'; public id: number; public motion_id: number; diff --git a/client/src/app/shared/models/poll/base-poll.ts b/client/src/app/shared/models/poll/base-poll.ts index af893b136..275339b8b 100644 --- a/client/src/app/shared/models/poll/base-poll.ts +++ b/client/src/app/shared/models/poll/base-poll.ts @@ -2,7 +2,7 @@ import { BaseDecimalModel } from '../base/base-decimal-model'; import { BaseOption } from './base-option'; export enum PollColor { - yes = '#9fd773', + yes = '#4caf50', no = '#cc6c5b', abstain = '#a6a6a6', votesvalid = '#e2e2e2', @@ -76,6 +76,10 @@ export abstract class BasePoll = any> extends return this.isFinished || this.isPublished; } + public get nextState(): PollState { + return this.state + 1; + } + protected getDecimalFields(): (keyof BasePoll)[] { return ['votesvalid', 'votesinvalid', 'votescast']; } diff --git a/client/src/app/shared/pipes/poll-key-verbose.pipe.ts b/client/src/app/shared/pipes/poll-key-verbose.pipe.ts index c3d406e2e..7e0a35768 100644 --- a/client/src/app/shared/pipes/poll-key-verbose.pipe.ts +++ b/client/src/app/shared/pipes/poll-key-verbose.pipe.ts @@ -19,6 +19,6 @@ const PollValues = { }) export class PollKeyVerbosePipe implements PipeTransform { public transform(value: string): string { - return PollValues[value]; + return PollValues[value] || value; } } diff --git a/client/src/app/shared/pipes/poll-percent-base.pipe.spec.ts b/client/src/app/shared/pipes/poll-percent-base.pipe.spec.ts index dbf1eadea..880847114 100644 --- a/client/src/app/shared/pipes/poll-percent-base.pipe.spec.ts +++ b/client/src/app/shared/pipes/poll-percent-base.pipe.spec.ts @@ -6,7 +6,7 @@ import { AssignmentPollService } from 'app/site/assignments/services/assignment- import { MotionPollService } from 'app/site/motions/services/motion-poll.service'; import { PollPercentBasePipe } from './poll-percent-base.pipe'; -fdescribe('PollPercentBasePipe', () => { +describe('PollPercentBasePipe', () => { beforeEach(() => { TestBed.configureTestingModule({ imports: [E2EImportsModule] diff --git a/client/src/app/shared/pipes/poll-percent-base.pipe.ts b/client/src/app/shared/pipes/poll-percent-base.pipe.ts index 51e948f03..da48a9c6d 100644 --- a/client/src/app/shared/pipes/poll-percent-base.pipe.ts +++ b/client/src/app/shared/pipes/poll-percent-base.pipe.ts @@ -40,7 +40,7 @@ export class PollPercentBasePipe implements PipeTransform { const percentNumber = (value / totalByBase) * 100; if (percentNumber > 0) { const result = percentNumber % 1 === 0 ? percentNumber : percentNumber.toFixed(this.decimalPlaces); - return `(${result}%)`; + return `(${result} %)`; } } return null; diff --git a/client/src/app/shared/shared.module.ts b/client/src/app/shared/shared.module.ts index 8f2ec7e08..3c7209208 100644 --- a/client/src/app/shared/shared.module.ts +++ b/client/src/app/shared/shared.module.ts @@ -111,7 +111,6 @@ import { GlobalSpinnerComponent } from 'app/site/common/components/global-spinne import { HeightResizingDirective } from './directives/height-resizing.directive'; import { TrustPipe } from './pipes/trust.pipe'; import { LocalizedDatePipe } from './pipes/localized-date.pipe'; -import { BreadcrumbComponent } from './components/breadcrumb/breadcrumb.component'; import { ChartsComponent } from './components/charts/charts.component'; import { CheckInputComponent } from './components/check-input/check-input.component'; import { BannerComponent } from './components/banner/banner.component'; @@ -277,7 +276,6 @@ import { PollPercentBasePipe } from './pipes/poll-percent-base.pipe'; ChartsModule, TrustPipe, LocalizedDatePipe, - BreadcrumbComponent, ChartsComponent, CheckInputComponent, BannerComponent, @@ -335,7 +333,6 @@ import { PollPercentBasePipe } from './pipes/poll-percent-base.pipe'; HeightResizingDirective, TrustPipe, LocalizedDatePipe, - BreadcrumbComponent, ChartsComponent, CheckInputComponent, BannerComponent, diff --git a/client/src/app/site/assignments/components/assignment-detail/assignment-detail.component.html b/client/src/app/site/assignments/components/assignment-detail/assignment-detail.component.html index c815910e6..29863703c 100644 --- a/client/src/app/site/assignments/components/assignment-detail/assignment-detail.component.html +++ b/client/src/app/site/assignments/components/assignment-detail/assignment-detail.component.html @@ -67,7 +67,7 @@ - + @@ -162,7 +162,12 @@
@@ -285,7 +290,6 @@ Number poll candidates
- diff --git a/client/src/app/site/assignments/components/assignment-detail/assignment-detail.component.spec.ts b/client/src/app/site/assignments/components/assignment-detail/assignment-detail.component.spec.ts index 681e83e39..1c51f8bb8 100644 --- a/client/src/app/site/assignments/components/assignment-detail/assignment-detail.component.spec.ts +++ b/client/src/app/site/assignments/components/assignment-detail/assignment-detail.component.spec.ts @@ -1,9 +1,11 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { E2EImportsModule } from 'e2e-imports.module'; + +import { PollProgressComponent } from 'app/site/polls/components/poll-progress/poll-progress.component'; import { AssignmentDetailComponent } from './assignment-detail.component'; import { AssignmentPollVoteComponent } from '../assignment-poll-vote/assignment-poll-vote.component'; import { AssignmentPollComponent } from '../assignment-poll/assignment-poll.component'; -import { E2EImportsModule } from '../../../../../e2e-imports.module'; describe('AssignmentDetailComponent', () => { let component: AssignmentDetailComponent; @@ -12,7 +14,12 @@ describe('AssignmentDetailComponent', () => { beforeEach(async(() => { TestBed.configureTestingModule({ imports: [E2EImportsModule], - declarations: [AssignmentDetailComponent, AssignmentPollComponent, AssignmentPollVoteComponent] + declarations: [ + AssignmentDetailComponent, + AssignmentPollComponent, + AssignmentPollVoteComponent, + PollProgressComponent + ] }).compileComponents(); })); diff --git a/client/src/app/site/assignments/components/assignment-detail/assignment-detail.component.ts b/client/src/app/site/assignments/components/assignment-detail/assignment-detail.component.ts index 2e329c3d4..6ad6054f4 100644 --- a/client/src/app/site/assignments/components/assignment-detail/assignment-detail.component.ts +++ b/client/src/app/site/assignments/components/assignment-detail/assignment-detail.component.ts @@ -23,6 +23,7 @@ import { ViewTag } from 'app/site/tags/models/view-tag'; import { ViewUser } from 'app/site/users/models/view-user'; import { AssignmentPdfExportService } from '../../services/assignment-pdf-export.service'; import { AssignmentPollDialogService } from '../../services/assignment-poll-dialog.service'; +import { AssignmentPollService } from '../../services/assignment-poll.service'; import { AssignmentPhases, ViewAssignment } from '../../models/view-assignment'; import { ViewAssignmentPoll } from '../../models/view-assignment-poll'; import { ViewAssignmentRelatedUser } from '../../models/view-assignment-related-user'; @@ -176,7 +177,8 @@ export class AssignmentDetailComponent extends BaseViewComponent implements OnIn private promptService: PromptService, private pdfService: AssignmentPdfExportService, private mediafileRepo: MediafileRepositoryService, - private pollDialog: AssignmentPollDialogService + private pollDialog: AssignmentPollDialogService, + private assignmentPollService: AssignmentPollService ) { super(title, translate, matSnackBar); this.subscriptions.push( @@ -306,11 +308,15 @@ export class AssignmentDetailComponent extends BaseViewComponent implements OnIn * Creates a new Poll */ public openDialog(): void { - this.pollDialog.openDialog({ + // TODO: That is not really a ViewObject + const dialogData = { collectionString: ViewAssignmentPoll.COLLECTIONSTRING, assignment_id: this.assignment.id, - assignment: this.assignment - }); + assignment: this.assignment, + ...this.assignmentPollService.getDefaultPollData() + }; + + this.pollDialog.openDialog(dialogData); } /** diff --git a/client/src/app/site/assignments/components/assignment-poll-detail/assignment-poll-detail-component.scss-theme.scss b/client/src/app/site/assignments/components/assignment-poll-detail/assignment-poll-detail-component.scss-theme.scss new file mode 100644 index 000000000..15514244e --- /dev/null +++ b/client/src/app/site/assignments/components/assignment-poll-detail/assignment-poll-detail-component.scss-theme.scss @@ -0,0 +1,12 @@ +@import '~@angular/material/theming'; + +@mixin os-assignment-poll-detail-style($theme) { + $background: map-get($theme, background); + + .assignment-result-table { + border-collapse: collapse; + tr { + border-bottom: 1px solid mat-color($background, focused-button); + } + } +} 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 7564bdaa7..0c534ba76 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 @@ -21,46 +21,51 @@ {{ poll.typeVerbose | translate }}
-

Result

- -
+
- - - {{ 'Candidates' | translate }} - {{ row.user }} - -
- - {{ 'Yes' | translate }} - {{ row.yes }} - + + + + + + + + + + + +
CandidatesVotes
+ + {{ row.votingOption | pollKeyVerbose | translate }} + + +
+ {{ row.votingOptionSubtitle }} +
+
+
+
+ + {{ vote.vote | pollKeyVerbose | translate }} + + + {{ vote.vote | pollKeyVerbose | translate }} + - - {{ 'No' | translate }} - {{ row.no }} - - - - {{ 'Abstain' | translate }} - {{ row.abstain }} - -
- -
- - {{ 'Votes' | translate }} - {{ row.yes }} - -
- - - - + + {{ vote.amount | parsePollNumber }} + + {{ vote.amount | pollPercentBase: poll }} + + +
+ +
- {{ voteOptionStyle[vote.votes[option.user_id].value].icon }} {{ vote.votes[option.user_id].valueVerbose | translate }} + {{ voteOptionStyle[vote.votes[option.user_id].value].icon }} + {{ vote.votes[option.user_id].valueVerbose | translate }}
@@ -123,17 +129,18 @@
-
-
+
+ {{ 'Groups' | translate }}: {{ group.getTitle() | translate }}, -
+ -
{{ 'Required majority' | translate }}: {{ poll.majorityMethodVerbose | translate }}
-
{{ '100% base' | translate }}: {{ poll.percentBaseVerbose | translate }}
+ + {{ '100% base' | translate }}: {{ poll.percentBaseVerbose | translate }} +
@@ -141,7 +148,7 @@ - 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 760728c24..ded606744 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,9 +1,22 @@ @import '~assets/styles/variables.scss'; @import '~assets/styles/poll-colors.scss'; -.result-wrapper { +%assignment-result-wrapper { + margin-top: 2em; display: grid; grid-gap: 10px; +} + +.result-wrapper-bar-chart { + @extend %assignment-result-wrapper; + grid-template-areas: + 'results' + 'chart' + 'names'; +} + +.result-wrapper-pie-chart { + @extend %assignment-result-wrapper; grid-template-areas: 'chart' 'results' @@ -11,7 +24,7 @@ } @include desktop { - .result-wrapper { + .result-wrapper-pie-chart { grid-template-areas: 'results chart' 'names names'; @@ -19,13 +32,34 @@ } } -.result-table { +.assignment-result-table { grid-area: results; + th { + text-align: left; + font-weight: initial; + } + + tr { + height: 48px; + } + + tr:last-child { + border-bottom: none; + } + + .single-result { + display: flex; + } } -.result-chart { +.assignment-result-chart { grid-area: chart; +} + +.pie-chart { max-width: 300px; + margin-left: auto; + margin-right: auto; } .named-result-table { @@ -36,27 +70,14 @@ } } -.poll-content { +.assignment-poll-meta { + display: grid; + text-align: right; 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 { + display: block; height: 500px; } 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 d3c0a5662..11fde092b 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 @@ -14,6 +14,7 @@ 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'; +import { VotingResult } from 'app/site/polls/models/view-base-poll'; import { PollService } from 'app/site/polls/services/poll.service'; import { AssignmentPollDialogService } from '../../services/assignment-poll-dialog.service'; import { ViewAssignmentPoll } from '../../models/view-assignment-poll'; @@ -43,13 +44,6 @@ export class AssignmentPollDetailComponent extends BasePollDetailComponent + + -
+
@@ -33,36 +35,30 @@ >
- + +
- + Publish immediately If you want to publish after creating, you have to fill at least one of the fields.
- - - + +
- + + diff --git a/client/src/app/site/assignments/components/assignment-poll-dialog/assignment-poll-dialog.component.ts b/client/src/app/site/assignments/components/assignment-poll-dialog/assignment-poll-dialog.component.ts index 25d645230..d2b5ac789 100644 --- a/client/src/app/site/assignments/components/assignment-poll-dialog/assignment-poll-dialog.component.ts +++ b/client/src/app/site/assignments/components/assignment-poll-dialog/assignment-poll-dialog.component.ts @@ -11,9 +11,7 @@ import { GeneralValueVerbose, VoteValue, VoteValueVerbose } from 'app/shared/mod import { AssignmentPollMethodsVerbose } from 'app/site/assignments/models/view-assignment-poll'; import { BasePollDialogComponent } from 'app/site/polls/components/base-poll-dialog.component'; import { PollFormComponent } from 'app/site/polls/components/poll-form/poll-form.component'; -import { CalculablePollKey, PollVoteValue } from 'app/site/polls/services/poll.service'; import { ViewUser } from 'app/site/users/models/view-user'; -import { ViewAssignmentOption } from '../../models/view-assignment-option'; import { ViewAssignmentPoll } from '../../models/view-assignment-poll'; type OptionsObject = { user_id: number; user: ViewUser }[]; @@ -26,7 +24,7 @@ type OptionsObject = { user_id: number; user: ViewUser }[]; templateUrl: './assignment-poll-dialog.component.html', styleUrls: ['./assignment-poll-dialog.component.scss'] }) -export class AssignmentPollDialogComponent extends BasePollDialogComponent implements OnInit { +export class AssignmentPollDialogComponent extends BasePollDialogComponent implements OnInit { /** * The summary values that will have fields in the dialog */ @@ -41,7 +39,7 @@ export class AssignmentPollDialogComponent extends BasePollDialogComponent imple public specialValues: [number, string][]; @ViewChild('pollForm', { static: true }) - protected pollForm: PollFormComponent; + protected pollForm: PollFormComponent; /** * vote entries for each option in this component. Is empty if method @@ -65,13 +63,14 @@ export class AssignmentPollDialogComponent extends BasePollDialogComponent imple title: Title, protected translate: TranslateService, matSnackbar: MatSnackBar, - public dialogRef: MatDialogRef, + public dialogRef: MatDialogRef>, @Inject(MAT_DIALOG_DATA) public pollData: Partial ) { super(title, translate, matSnackbar, dialogRef); } public ngOnInit(): void { + // TODO: not solid. // on new poll creation, poll.options does not exist, so we have to build a substitute from the assignment candidates this.options = this.pollData.options ? this.pollData.options @@ -154,85 +153,6 @@ export class AssignmentPollDialogComponent extends BasePollDialogComponent imple } } - /** - * Validates candidates input (every candidate has their options filled in), - * submits and closes the dialog if successful, else displays an error popup. - * TODO better validation - */ - public submit(): void { - /*const error = this.data.options.find(dataoption => { - this.optionPollKeys.some(key => { - const keyValue = dataoption.votes.find(o => o.value === key); - return !keyValue || keyValue.weight === undefined; - }); - }); - if (error) { - this.matSnackBar.open( - this.translate.instant('Please fill in the values for each candidate'), - this.translate.instant('OK'), - { - duration: 1000 - } - ); - } else { - this.dialogRef.close(this.data); - }*/ - } - - /** - * TODO: currently unused - * - * @param key poll option to be labeled - * @returns a label for a poll option - */ - public getLabel(key: CalculablePollKey): string { - // return this.pollService.getLabel(key); - throw new Error('TODO'); - } - - /** - * Updates a vote value - * - * @param value the value to update - * @param candidate the candidate for whom to update the value - * @param newData the new value - */ - public setValue(value: PollVoteValue, candidate: ViewAssignmentOption, newData: string): void { - /*const vote = candidate.votes.find(v => v.value === value); - if (vote) { - vote.weight = parseFloat(newData); - } else { - candidate.votes.push({ - value: value, - weight: parseFloat(newData) - }); - }*/ - } - - /** - * Retrieves the current value for a voting option - * - * @param value the vote value (e.g. 'Abstain') - * @param candidate the pollOption - * @returns the currently entered number or undefined if no number has been set - */ - public getValue(value: PollVoteValue, candidate: ViewAssignmentOption): number | undefined { - /*const val = candidate.votes.find(v => v.value === value); - return val ? val.weight : undefined;*/ - throw new Error('TODO'); - } - - /** - * Retrieves a per-poll value - * - * @param value - * @returns integer or undefined - */ - public getSumValue(value: any /*SummaryPollKey*/): number | undefined { - // return this.data[value] || undefined; - throw new Error('TODO'); - } - /** * Sets a per-poll value * diff --git a/client/src/app/site/assignments/components/assignment-poll-vote/assignment-poll-vote.component.html b/client/src/app/site/assignments/components/assignment-poll-vote/assignment-poll-vote.component.html index 23afcbe0f..402db21d2 100644 --- a/client/src/app/site/assignments/components/assignment-poll-vote/assignment-poll-vote.component.html +++ b/client/src/app/site/assignments/components/assignment-poll-vote/assignment-poll-vote.component.html @@ -1,8 +1,4 @@ - -
- -
diff --git a/client/src/app/site/assignments/components/assignment-poll-vote/assignment-poll-vote.component.ts b/client/src/app/site/assignments/components/assignment-poll-vote/assignment-poll-vote.component.ts index 874638b76..4262be382 100644 --- a/client/src/app/site/assignments/components/assignment-poll-vote/assignment-poll-vote.component.ts +++ b/client/src/app/site/assignments/components/assignment-poll-vote/assignment-poll-vote.component.ts @@ -50,7 +50,10 @@ export class AssignmentPollVoteComponent extends BasePollVoteComponent { this.votes = votes; @@ -91,8 +94,6 @@ export class AssignmentPollVoteComponent extends BasePollVoteComponent vote.option.poll_id === this.poll.id && vote.user_id === this.user.id diff --git a/client/src/app/site/assignments/components/assignment-poll/assignment-poll.component.html b/client/src/app/site/assignments/components/assignment-poll/assignment-poll.component.html index 4be419001..8d69ac5ee 100644 --- a/client/src/app/site/assignments/components/assignment-poll/assignment-poll.component.html +++ b/client/src/app/site/assignments/components/assignment-poll/assignment-poll.component.html @@ -1,64 +1,75 @@
-
+
+ {{ poll.title }} -
- + + +
+ {{ poll.typeVerbose | translate }} · + {{ poll.stateVerbose | translate }} - - - {{ poll.typeVerbose | 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 cbde03551..a9003cd2f 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 @@ -2,53 +2,51 @@ @import '~assets/styles/poll-colors.scss'; .poll-content { + text-align: right; + display: grid; padding-top: 20px; } .result-wrapper { display: grid; - grid-gap: 10px; - grid-template-areas: - 'chart' - 'results' - 'names'; -} + grid-gap: 2em; + margin: 2em; + grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); -@include desktop { - .result-wrapper { - grid-template-areas: - 'results chart' - 'names names'; - grid-template-columns: 2fr 1fr; - } -} - -.result-table { - grid-area: results; - - tr { - height: 48px; - min-height: 48px; - th:first-child, - td:first-child { - padding-left: 24px; - } - th:last-child, - td:last-child { - padding-right: 24px; + .result-table { + // display: block; + th { + text-align: right; + font-weight: initial; } - .result-cell-definition { - text-align: center; + tr { + height: 48px; + border-bottom: none !important; + + .result-cell-definition { + text-align: right; + } + } + + .yes { + color: $votes-yes-color; + } + + .no { + color: $votes-no-color; + } + + .abstain { + color: $votes-abstain-color; } } -} -.result-chart { - grid-area: chart; - max-width: 300px; - margin-left: auto; - margin-right: auto; + .doughnut-chart { + display: block; + margin-top: auto; + margin-bottom: auto; + } } .named-result-table { diff --git a/client/src/app/site/motions/modules/motion-poll/motion-poll-detail/motion-poll-detail.component.scss-theme.scss b/client/src/app/site/motions/modules/motion-poll/motion-poll-detail/motion-poll-detail.component.scss-theme.scss index 8d2ad67ef..ea65f9b52 100644 --- a/client/src/app/site/motions/modules/motion-poll/motion-poll-detail/motion-poll-detail.component.scss-theme.scss +++ b/client/src/app/site/motions/modules/motion-poll/motion-poll-detail/motion-poll-detail.component.scss-theme.scss @@ -1,12 +1,4 @@ @import '~@angular/material/theming'; @mixin os-motion-poll-detail-style($theme) { - $background: map-get($theme, background); - - .result-table { - border-collapse: collapse; - tr { - border-bottom: 1px solid mat-color($background, focused-button); - } - } } diff --git a/client/src/app/site/motions/modules/motion-poll/motion-poll-dialog/motion-poll-dialog.component.spec.ts b/client/src/app/site/motions/modules/motion-poll/motion-poll-dialog/motion-poll-dialog.component.spec.ts index 17da30260..773ff4751 100644 --- a/client/src/app/site/motions/modules/motion-poll/motion-poll-dialog/motion-poll-dialog.component.spec.ts +++ b/client/src/app/site/motions/modules/motion-poll/motion-poll-dialog/motion-poll-dialog.component.spec.ts @@ -5,7 +5,7 @@ import { E2EImportsModule } from 'e2e-imports.module'; import { MotionPollDialogComponent } from './motion-poll-dialog.component'; -fdescribe('MotionPollDialogComponent', () => { +describe('MotionPollDialogComponent', () => { let component: MotionPollDialogComponent; let fixture: ComponentFixture; diff --git a/client/src/app/site/motions/modules/motion-poll/motion-poll-dialog/motion-poll-dialog.component.ts b/client/src/app/site/motions/modules/motion-poll/motion-poll-dialog/motion-poll-dialog.component.ts index 652450db5..4112858f4 100644 --- a/client/src/app/site/motions/modules/motion-poll/motion-poll-dialog/motion-poll-dialog.component.ts +++ b/client/src/app/site/motions/modules/motion-poll/motion-poll-dialog/motion-poll-dialog.component.ts @@ -15,18 +15,18 @@ import { PollFormComponent } from 'app/site/polls/components/poll-form/poll-form templateUrl: './motion-poll-dialog.component.html', styleUrls: ['./motion-poll-dialog.component.scss'] }) -export class MotionPollDialogComponent extends BasePollDialogComponent { +export class MotionPollDialogComponent extends BasePollDialogComponent { public motionPollMethods = { YNA: MotionPollMethodsVerbose.YNA }; @ViewChild('pollForm', { static: false }) - protected pollForm: PollFormComponent; + protected pollForm: PollFormComponent; public constructor( private fb: FormBuilder, title: Title, protected translate: TranslateService, matSnackbar: MatSnackBar, - public dialogRef: MatDialogRef, + public dialogRef: MatDialogRef>, @Inject(MAT_DIALOG_DATA) public pollData: Partial ) { super(title, translate, matSnackbar, dialogRef); diff --git a/client/src/app/site/motions/modules/motion-poll/motion-poll-vote/motion-poll-vote.component.html b/client/src/app/site/motions/modules/motion-poll/motion-poll-vote/motion-poll-vote.component.html index 013201c8e..dbb0500b3 100644 --- a/client/src/app/site/motions/modules/motion-poll/motion-poll-vote/motion-poll-vote.component.html +++ b/client/src/app/site/motions/modules/motion-poll/motion-poll-vote/motion-poll-vote.component.html @@ -1,10 +1,10 @@ -
+
- +
-

+

{{ option.label | translate }} -

- - - - +
+
diff --git a/client/src/app/site/motions/modules/motion-poll/motion-poll-vote/motion-poll-vote.component.scss b/client/src/app/site/motions/modules/motion-poll/motion-poll-vote/motion-poll-vote.component.scss index 1a7dfce5c..eb949fffa 100644 --- a/client/src/app/site/motions/modules/motion-poll/motion-poll-vote/motion-poll-vote.component.scss +++ b/client/src/app/site/motions/modules/motion-poll/motion-poll-vote/motion-poll-vote.component.scss @@ -1,17 +1,33 @@ @import '~assets/styles/poll-colors.scss'; +.vote-button-grid { + display: grid; + grid-gap: 20px; + margin-top: 2em; + grid-template-columns: repeat(auto-fit, minmax(100px, 1fr)); +} + +.vote-button { + display: inline-grid; + grid-gap: 1em; + margin: auto; + + .vote-label { + text-align: center; + } +} + .voted-yes { background-color: $votes-yes-color; + color: $vote-active-color; } .voted-no { background-color: $votes-no-color; + color: $vote-active-color; } .voted-abstain { background-color: $votes-abstain-color; -} - -.vote-label { - margin-left: 1em; + color: $vote-active-color; } diff --git a/client/src/app/site/motions/modules/motion-poll/motion-poll-vote/motion-poll-vote.component.spec.ts b/client/src/app/site/motions/modules/motion-poll/motion-poll-vote/motion-poll-vote.component.spec.ts index baaa1e2e1..e04ef7674 100644 --- a/client/src/app/site/motions/modules/motion-poll/motion-poll-vote/motion-poll-vote.component.spec.ts +++ b/client/src/app/site/motions/modules/motion-poll/motion-poll-vote/motion-poll-vote.component.spec.ts @@ -2,6 +2,7 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { E2EImportsModule } from 'e2e-imports.module'; +import { PollProgressComponent } from 'app/site/polls/components/poll-progress/poll-progress.component'; import { MotionPollVoteComponent } from './motion-poll-vote.component'; describe('MotionPollVoteComponent', () => { @@ -11,7 +12,7 @@ describe('MotionPollVoteComponent', () => { beforeEach(async(() => { TestBed.configureTestingModule({ imports: [E2EImportsModule], - declarations: [MotionPollVoteComponent] + declarations: [MotionPollVoteComponent, PollProgressComponent] }).compileComponents(); })); 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 aa773e4e1..25ca5369a 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 @@ -1,44 +1,68 @@ -
+
- - - {{ poll.title }} - + +
+ + + + {{ poll.title }} + + + +
+ + + {{ poll.typeVerbose | translate }} · + + + + + {{ poll.stateVerbose }} + +
+
- + +
- -
-
- {{ poll.typeVerbose | translate }} -
- - - {{ poll.stateVerbose }} - -
+ +
+
-
+ + + + -