Added reset button

This commit is contained in:
Jim Martens 2023-08-12 13:01:27 +02:00
parent d4d7c9de99
commit 800b7536e8
10 changed files with 135 additions and 46 deletions

View File

@ -8,7 +8,8 @@
<ng-container *ngIf="(viewModel$ | async) as data; else spinner"> <ng-container *ngIf="(viewModel$ | async) as data; else spinner">
<app-election [viewModel]="data" (valueChanges)="onValueChanges($event)" <app-election [viewModel]="data" (valueChanges)="onValueChanges($event)"
[electedCandidates]="electedCandidates$ | async" [electedCandidates]="electedCandidates$ | async"
(calculate)="onCalculate($event)"></app-election> (calculate)="onCalculate($event)"
(reset)="onReset()"></app-election>
</ng-container> </ng-container>
</div> </div>
</div> </div>

View File

@ -11,7 +11,7 @@ import {
loadElectionResultAction, loadElectionResultAction,
loadPartiesAction, loadPartiesAction,
loadSingleElectionAction, loadSingleElectionAction,
modifyElectionResultAction modifyElectionResultAction, resetElectionResult
} from "../store/elections.actions"; } from "../store/elections.actions";
import {DEFAULT_RESULT, ElectionResult} from "../model/election-result"; import {DEFAULT_RESULT, ElectionResult} from "../model/election-result";
import {FormElectionResult} from "../model/form-election-result"; import {FormElectionResult} from "../model/form-election-result";
@ -111,4 +111,8 @@ export class ElectionContainerComponent implements OnInit {
onCalculate(event: ElectionResult) { onCalculate(event: ElectionResult) {
this.store.dispatch(calculateAction({payload: event})); this.store.dispatch(calculateAction({payload: event}));
} }
onReset() {
this.store.dispatch(resetElectionResult());
}
} }

View File

@ -2,11 +2,14 @@
<app-election-result [electedCandidates]="electedCandidates" [parties]="viewModel.parties"></app-election-result> <app-election-result [electedCandidates]="electedCandidates" [parties]="viewModel.parties"></app-election-result>
<button class="mt-2" mat-raised-button i18n #calculateButton (keyup.enter)="onCalculate()" (click)="onCalculate()"> <div class="row">
<mat-spinner class="spinner" *ngIf="showSpinner"></mat-spinner> <button class="mt-2" mat-raised-button i18n="@@button.calculate" #calculateButton (keyup.enter)="onCalculate()" (click)="onCalculate()">
Calculate <mat-spinner class="spinner" *ngIf="showSpinner"></mat-spinner>
</button> Calculate
</button>
<button class="mt-2" mat-raised-button i18n="@@button.reset" (keyup.enter)="onReset()" (click)="onReset()">Reset</button>
</div>
<form [formGroup]="form"> <form [formGroup]="form">
<mat-tab-group> <mat-tab-group>

View File

@ -9,3 +9,7 @@
.mt-2 { .mt-2 {
margin-top: 2em; margin-top: 2em;
} }
.row {
@include mixins.justifyContent(space-between);
}

View File

@ -14,7 +14,7 @@ import {ElectionResult} from "../model/election-result";
import {FormElectionResult} from "../model/form-election-result"; import {FormElectionResult} from "../model/form-election-result";
import {ElectedCandidates} from "../model/elected-candidates"; import {ElectedCandidates} from "../model/elected-candidates";
import {mapConstituencyResultsForm, mapOverallResultsForm} from "../store/elections.reducer"; import {mapConstituencyResultsForm, mapOverallResultsForm} from "../store/elections.reducer";
import {debounceTime, distinctUntilChanged} from "rxjs"; import {debounceTime, distinctUntilChanged, filter} from "rxjs";
@Component({ @Component({
selector: 'app-election', selector: 'app-election',
@ -34,6 +34,8 @@ export class ElectionComponent implements OnInit {
readonly valueChanges: EventEmitter<FormElectionResult> = new EventEmitter<FormElectionResult>(); readonly valueChanges: EventEmitter<FormElectionResult> = new EventEmitter<FormElectionResult>();
@Output() @Output()
readonly calculate: EventEmitter<ElectionResult> = new EventEmitter<ElectionResult>(); readonly calculate: EventEmitter<ElectionResult> = new EventEmitter<ElectionResult>();
@Output()
readonly reset: EventEmitter<boolean> = new EventEmitter<boolean>();
constructor(private fb: FormBuilder) { constructor(private fb: FormBuilder) {
this.overallResults = this.fb.group({}); this.overallResults = this.fb.group({});
@ -82,6 +84,7 @@ export class ElectionComponent implements OnInit {
}) })
this.constituencyToId = constituencyToId; this.constituencyToId = constituencyToId;
this.constituencyNumberToName = constituencyNumberToName; this.constituencyNumberToName = constituencyNumberToName;
this.updateForm(data.electionResult);
} }
ngOnInit() { ngOnInit() {
@ -133,6 +136,40 @@ export class ElectionComponent implements OnInit {
this.calculate.emit(electionResult); this.calculate.emit(electionResult);
} }
onReset() {
this.reset.emit(true);
}
private updateForm(electionResult: ModifiedElectionResult) {
for (const votingResult of electionResult.overallResults) {
const formGroup = this.overallResults.get(votingResult.partyAbbreviation);
formGroup?.get('votesOnNomination')?.setValue(votingResult.votesOnNomination, { emitEvent: false });
formGroup?.get('votesThroughHealing')?.setValue(votingResult.votesThroughHealing, { emitEvent: false });
const votesPerPosition = formGroup?.get('votesPerPosition');
const {map, entries} = this.buildVotesPerPosition(votingResult);
for (const number of entries) {
votesPerPosition?.get(number)?.setValue(map.get(number) || 0, { emitEvent: false });
}
}
for (const constituencyNumber of electionResult.constituencyResults.keys()) {
const constituency = this.constituencyToId.get(+constituencyNumber);
const votingResults = electionResult.constituencyResults.get(constituencyNumber);
if (constituency == null || votingResults == null) {
continue;
}
const constituencyFormGroup = this.constituencyResults.get(constituency.name);
for (const votingResult of votingResults) {
const formGroup = constituencyFormGroup?.get(votingResult.partyAbbreviation);
const votesPerPosition = formGroup?.get('votesPerPosition');
for (const number in votingResult.votesPerPosition) {
votesPerPosition?.get(number)?.setValue(votingResult.votesPerPosition[number], { emitEvent: false });
}
}
}
}
private setUpForm(election: Election, electionResult: ModifiedElectionResult) { private setUpForm(election: Election, electionResult: ModifiedElectionResult) {
this.overallResults = this.fb.group({}); this.overallResults = this.fb.group({});
this.constituencyResults = this.fb.group({}); this.constituencyResults = this.fb.group({});
@ -150,15 +187,7 @@ export class ElectionComponent implements OnInit {
partyAbbreviation: this.fb.control<string>(votingResult.partyAbbreviation), partyAbbreviation: this.fb.control<string>(votingResult.partyAbbreviation),
nominationName: this.fb.control<string>(votingResult.nominationName), nominationName: this.fb.control<string>(votingResult.nominationName),
}); });
const map = new Map<string, number>(); const {map, entries} = this.buildVotesPerPosition(votingResult);
for (const number in votingResult.votesPerPosition) {
map.set(number, votingResult.votesPerPosition[number]);
}
const entries = [...map.entries()]
.sort((a, b) => {
return +a[0] - +b[0];
})
.map(entry => entry[0]);
for (const number of entries) { for (const number of entries) {
votesPerPosition.addControl(number, this.fb.control<number>(map.get(number) || 0)); votesPerPosition.addControl(number, this.fb.control<number>(map.get(number) || 0));
} }
@ -193,13 +222,14 @@ export class ElectionComponent implements OnInit {
} }
this.form.valueChanges.pipe( this.form.valueChanges.pipe(
debounceTime(1000), debounceTime(1000),
distinctUntilChanged() distinctUntilChanged(),
).subscribe({ ).subscribe({
next: (value: { next: (value: {
overallResults: { [name: string]: VotingResult }, overallResults: { [name: string]: VotingResult },
constituencyResults: { [name: string]: { [name: string]: VotingResult } } constituencyResults: { [name: string]: { [name: string]: VotingResult } }
}) => { }) => {
const modifiedValue: FormElectionResult = {...value, const modifiedValue: FormElectionResult = {
...value,
constituencyResults: {} constituencyResults: {}
}; };
for (const name in value.constituencyResults) { for (const name in value.constituencyResults) {
@ -217,4 +247,17 @@ export class ElectionComponent implements OnInit {
} }
}); });
} }
private buildVotesPerPosition(votingResult: VotingResult) {
const map = new Map<string, number>();
for (const number in votingResult.votesPerPosition) {
map.set(number, votingResult.votesPerPosition[number]);
}
const entries = [...map.entries()]
.sort((a, b) => {
return +a[0] - +b[0];
})
.map(entry => entry[0]);
return {map, entries};
}
} }

View File

@ -22,6 +22,8 @@ export enum ActionTypes {
Calculate = '[Elections] Calculate', Calculate = '[Elections] Calculate',
CalculateFinished = '[Elections] Calculate Finished', CalculateFinished = '[Elections] Calculate Finished',
ResetElectionResult = '[Elections] Reset Election Result',
} }
export const loadAllElectionsAction = createAction( export const loadAllElectionsAction = createAction(
@ -77,3 +79,7 @@ export const calculateFinishedAction = createAction(
ActionTypes.CalculateFinished, ActionTypes.CalculateFinished,
props<{payload: ElectedCandidates}>() props<{payload: ElectedCandidates}>()
); );
export const resetElectionResult = createAction(
ActionTypes.ResetElectionResult
);

View File

@ -5,7 +5,7 @@ import {
loadElectionResultFinishedAction, loadElectionResultFinishedAction,
loadPartiesFinishedAction, loadPartiesFinishedAction,
loadSingleElectionFinishedAction, loadSingleElectionFinishedAction,
modifyElectionResultAction modifyElectionResultAction, resetElectionResult
} from "./elections.actions"; } from "./elections.actions";
import {createReducer, on} from "@ngrx/store"; import {createReducer, on} from "@ngrx/store";
import {DEFAULT_RESULT, ElectionResult} from "../model/election-result"; import {DEFAULT_RESULT, ElectionResult} from "../model/election-result";
@ -73,7 +73,17 @@ export const electionsReducer = createReducer(
action) => ({ action) => ({
...state, ...state,
electedCandidates: {...action.payload} electedCandidates: {...action.payload}
})) })),
on(resetElectionResult, (state, action) => ({
...state,
modifiedElectionResult: {
constituencyResults: mapConstituencyResults(state.originalElectionResult.constituencyResults),
electionName: state.originalElectionResult.electionName,
overallResults: state.originalElectionResult.overallResults.map(votingResult => {
return {...votingResult}
})
}
})),
); );
function mapConstituencyResults(results: { [name: string]: VotingResult[] }): { [name: string]: VotingResult[] } { function mapConstituencyResults(results: { [name: string]: VotingResult[] }): { [name: string]: VotingResult[] } {

View File

@ -202,14 +202,20 @@
<context context-type="linenumber">7</context> <context context-type="linenumber">7</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="1566538032864437775" datatype="html"> <trans-unit id="button.calculate" datatype="html">
<source><x id="START_TAG_MAT_SPINNER" ctype="x-mat_spinner" equiv-text="&lt;mat-spinner class=&quot;spinner&quot; *ngIf=&quot;showSpinner&quot;&gt;"/><x id="CLOSE_TAG_MAT_SPINNER" ctype="x-mat_spinner" equiv-text="&lt;/mat-spinner&gt;"/> Calculate <source><x id="START_TAG_MAT_SPINNER" ctype="x-mat_spinner" equiv-text="&lt;mat-spinner class=&quot;spinner&quot; *ngIf=&quot;showSpinner&quot;&gt;"/><x id="CLOSE_TAG_MAT_SPINNER" ctype="x-mat_spinner" equiv-text="&lt;/mat-spinner&gt;"/> Calculate </source>
</source> <target><x id="START_TAG_MAT_SPINNER" ctype="x-mat_spinner" equiv-text="&lt;mat-spinner class=&quot;spinner&quot; *ngIf=&quot;showSpinner&quot;&gt;"/><x id="CLOSE_TAG_MAT_SPINNER" ctype="x-mat_spinner" equiv-text="&lt;/mat-spinner&gt;"/> Berechne </target>
<target><x id="START_TAG_MAT_SPINNER" ctype="x-mat_spinner" equiv-text="&lt;mat-spinner class=&quot;spinner&quot; *ngIf=&quot;showSpinner&quot;&gt;"/><x id="CLOSE_TAG_MAT_SPINNER" ctype="x-mat_spinner" equiv-text="&lt;/mat-spinner&gt;"/> Berechne
</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/elections/election/election.component.html</context> <context context-type="sourcefile">src/app/elections/election/election.component.html</context>
<context context-type="linenumber">6,8</context> <context context-type="linenumber">7,9</context>
</context-group>
</trans-unit>
<trans-unit id="button.reset" datatype="html">
<source>Reset</source>
<target>Zurücksetzen</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/elections/election/election.component.html</context>
<context context-type="linenumber">11</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="2620368114367873419" datatype="html"> <trans-unit id="2620368114367873419" datatype="html">
@ -217,7 +223,7 @@
<target>Bezirksergebnisse</target> <target>Bezirksergebnisse</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/elections/election/election.component.html</context> <context context-type="sourcefile">src/app/elections/election/election.component.html</context>
<context context-type="linenumber">13</context> <context context-type="linenumber">16</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="5278344113066375587" datatype="html"> <trans-unit id="5278344113066375587" datatype="html">
@ -225,7 +231,7 @@
<target>Listenstimmen</target> <target>Listenstimmen</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/elections/election/election.component.html</context> <context context-type="sourcefile">src/app/elections/election/election.component.html</context>
<context context-type="linenumber">20</context> <context context-type="linenumber">23</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="652158218369589359" datatype="html"> <trans-unit id="652158218369589359" datatype="html">
@ -233,7 +239,7 @@
<target>Stimmen durch Heilungsregel</target> <target>Stimmen durch Heilungsregel</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/elections/election/election.component.html</context> <context context-type="sourcefile">src/app/elections/election/election.component.html</context>
<context context-type="linenumber">25</context> <context context-type="linenumber">28</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="4355395580941190554" datatype="html"> <trans-unit id="4355395580941190554" datatype="html">
@ -241,7 +247,7 @@
<target>Wahlkreisergebnisse</target> <target>Wahlkreisergebnisse</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/elections/election/election.component.html</context> <context context-type="sourcefile">src/app/elections/election/election.component.html</context>
<context context-type="linenumber">39</context> <context context-type="linenumber">42</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="8889535199141263248" datatype="html"> <trans-unit id="8889535199141263248" datatype="html">

View File

@ -180,40 +180,46 @@
<context context-type="linenumber">7</context> <context context-type="linenumber">7</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="1566538032864437775" datatype="html"> <trans-unit id="button.calculate" datatype="html">
<source><x id="START_TAG_MAT_SPINNER" ctype="x-mat_spinner" equiv-text="&lt;mat-spinner class=&quot;spinner&quot; *ngIf=&quot;showSpinner&quot;&gt;"/><x id="CLOSE_TAG_MAT_SPINNER" ctype="x-mat_spinner" equiv-text="&lt;/mat-spinner&gt;"/> Calculate <source><x id="START_TAG_MAT_SPINNER" ctype="x-mat_spinner" equiv-text="&lt;mat-spinner class=&quot;spinner&quot; *ngIf=&quot;showSpinner&quot;&gt;"/><x id="CLOSE_TAG_MAT_SPINNER" ctype="x-mat_spinner" equiv-text="&lt;/mat-spinner&gt;"/> Calculate </source>
</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/elections/election/election.component.html</context> <context context-type="sourcefile">src/app/elections/election/election.component.html</context>
<context context-type="linenumber">6,8</context> <context context-type="linenumber">7,9</context>
</context-group>
</trans-unit>
<trans-unit id="button.reset" datatype="html">
<source>Reset</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/elections/election/election.component.html</context>
<context context-type="linenumber">11</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="2620368114367873419" datatype="html"> <trans-unit id="2620368114367873419" datatype="html">
<source>Overall results</source> <source>Overall results</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/elections/election/election.component.html</context> <context context-type="sourcefile">src/app/elections/election/election.component.html</context>
<context context-type="linenumber">13</context> <context context-type="linenumber">16</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="5278344113066375587" datatype="html"> <trans-unit id="5278344113066375587" datatype="html">
<source>Votes on nomination</source> <source>Votes on nomination</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/elections/election/election.component.html</context> <context context-type="sourcefile">src/app/elections/election/election.component.html</context>
<context context-type="linenumber">20</context> <context context-type="linenumber">23</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="652158218369589359" datatype="html"> <trans-unit id="652158218369589359" datatype="html">
<source>Votes through healing</source> <source>Votes through healing</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/elections/election/election.component.html</context> <context context-type="sourcefile">src/app/elections/election/election.component.html</context>
<context context-type="linenumber">25</context> <context context-type="linenumber">28</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="4355395580941190554" datatype="html"> <trans-unit id="4355395580941190554" datatype="html">
<source>Constituency results</source> <source>Constituency results</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/elections/election/election.component.html</context> <context context-type="sourcefile">src/app/elections/election/election.component.html</context>
<context context-type="linenumber">39</context> <context context-type="linenumber">42</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="8889535199141263248" datatype="html"> <trans-unit id="8889535199141263248" datatype="html">

View File

@ -180,40 +180,46 @@
<context context-type="linenumber">7</context> <context context-type="linenumber">7</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="1566538032864437775" datatype="html"> <trans-unit id="button.calculate" datatype="html">
<source><x id="START_TAG_MAT_SPINNER" ctype="x-mat_spinner" equiv-text="&lt;mat-spinner class=&quot;spinner&quot; *ngIf=&quot;showSpinner&quot;&gt;"/><x id="CLOSE_TAG_MAT_SPINNER" ctype="x-mat_spinner" equiv-text="&lt;/mat-spinner&gt;"/> Calculate <source><x id="START_TAG_MAT_SPINNER" ctype="x-mat_spinner" equiv-text="&lt;mat-spinner class=&quot;spinner&quot; *ngIf=&quot;showSpinner&quot;&gt;"/><x id="CLOSE_TAG_MAT_SPINNER" ctype="x-mat_spinner" equiv-text="&lt;/mat-spinner&gt;"/> Calculate </source>
</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/elections/election/election.component.html</context> <context context-type="sourcefile">src/app/elections/election/election.component.html</context>
<context context-type="linenumber">6,8</context> <context context-type="linenumber">7,9</context>
</context-group>
</trans-unit>
<trans-unit id="button.reset" datatype="html">
<source>Reset</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/elections/election/election.component.html</context>
<context context-type="linenumber">11</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="2620368114367873419" datatype="html"> <trans-unit id="2620368114367873419" datatype="html">
<source>Overall results</source> <source>Overall results</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/elections/election/election.component.html</context> <context context-type="sourcefile">src/app/elections/election/election.component.html</context>
<context context-type="linenumber">13</context> <context context-type="linenumber">16</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="5278344113066375587" datatype="html"> <trans-unit id="5278344113066375587" datatype="html">
<source>Votes on nomination</source> <source>Votes on nomination</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/elections/election/election.component.html</context> <context context-type="sourcefile">src/app/elections/election/election.component.html</context>
<context context-type="linenumber">20</context> <context context-type="linenumber">23</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="652158218369589359" datatype="html"> <trans-unit id="652158218369589359" datatype="html">
<source>Votes through healing</source> <source>Votes through healing</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/elections/election/election.component.html</context> <context context-type="sourcefile">src/app/elections/election/election.component.html</context>
<context context-type="linenumber">25</context> <context context-type="linenumber">28</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="4355395580941190554" datatype="html"> <trans-unit id="4355395580941190554" datatype="html">
<source>Constituency results</source> <source>Constituency results</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/elections/election/election.component.html</context> <context context-type="sourcefile">src/app/elections/election/election.component.html</context>
<context context-type="linenumber">39</context> <context context-type="linenumber">42</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="8889535199141263248" datatype="html"> <trans-unit id="8889535199141263248" datatype="html">