Extract seat allocation and elected candidates to separate components

This commit is contained in:
Jim Martens 2023-08-11 21:49:32 +02:00
parent 641bba55e0
commit 93acd0935e
13 changed files with 289 additions and 62 deletions

View File

@ -0,0 +1,20 @@
<table mat-table [dataSource]="dataSource"
matSort (matSortChange)="announceSortChange($event)">
<ng-container matColumnDef="name">
<th mat-header-cell mat-sort-header *matHeaderCellDef i18n
i18n-sortActionDescription sortActionDescription="Sort by name">Name</th>
<td mat-cell *matCellDef="let row">{{row.name}}</td>
</ng-container>
<ng-container matColumnDef="profession">
<th mat-header-cell mat-sort-header *matHeaderCellDef i18n
i18n-sortActionDescription sortActionDescription="Sort by profession">Profession</th>
<td mat-cell *matCellDef="let row">{{row.profession}}</td>
</ng-container>
<ng-container matColumnDef="elected">
<th mat-header-cell *matHeaderCellDef i18n>Elected by</th>
<td mat-cell *matCellDef="let row">{{row.elected}}</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="resultColumns"></tr>
<tr mat-row *matRowDef="let row; columns: resultColumns"></tr>
</table>

View File

@ -0,0 +1,21 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ElectedCandidatesComponent } from './elected-candidates.component';
describe('ElectedCandidatesComponent', () => {
let component: ElectedCandidatesComponent;
let fixture: ComponentFixture<ElectedCandidatesComponent>;
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [ElectedCandidatesComponent]
});
fixture = TestBed.createComponent(ElectedCandidatesComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,105 @@
import {AfterViewInit, Component, Input, ViewChild} from '@angular/core';
import {ElectedCandidates} from "../model/elected-candidates";
import {MatSort, Sort} from "@angular/material/sort";
import {MatTableDataSource} from "@angular/material/table";
import {LiveAnnouncer} from "@angular/cdk/a11y";
import {ElectionSource} from "../model/election-source";
export interface ViewModel {
electedCandidates: ElectedCandidates;
partyAbbreviation: string;
}
interface PresentedCandidate {
name: string;
profession: string;
elected: string;
}
@Component({
selector: 'app-elected-candidates',
templateUrl: './elected-candidates.component.html',
styleUrls: ['./elected-candidates.component.scss']
})
export class ElectedCandidatesComponent implements AfterViewInit {
@ViewChild(MatSort) sort: MatSort | null = null;
resultColumns: string[] = ['name', 'profession', 'elected'];
electedCandidatesOfParty: PresentedCandidate[] = [];
dataSource: MatTableDataSource<PresentedCandidate> = new MatTableDataSource<PresentedCandidate>();
constructor(private liveAnnouncer: LiveAnnouncer) {
}
private _viewModel: ViewModel | null = null;
get viewModel(): ViewModel | null {
return this._viewModel;
}
@Input() set viewModel(value: ViewModel | null) {
if (value != null) {
this._viewModel = value;
const electedCandidates: PresentedCandidate[] = [];
const overallCandidates = this._viewModel.electedCandidates.overallResult.electedCandidates;
if (this._viewModel.partyAbbreviation in overallCandidates) {
electedCandidates.push(
...overallCandidates[this._viewModel.partyAbbreviation].map(
candidate => {
return {
name: candidate.candidate.name,
profession: candidate.candidate.profession,
elected: this.getElectedMessage((<any>ElectionSource)[candidate.elected])
};
}
)
);
}
for (const constituency in this._viewModel.electedCandidates.constituencyResults) {
const electedResult = this._viewModel.electedCandidates.constituencyResults[constituency];
if (this._viewModel.partyAbbreviation in electedResult.electedCandidates) {
electedCandidates.push(
...electedResult.electedCandidates[this._viewModel.partyAbbreviation].map(
candidate => {
return {
name: candidate.candidate.name,
profession: candidate.candidate.profession,
elected: this.getElectedMessage((<any>ElectionSource)[candidate.elected])
};
}
)
);
}
}
this.electedCandidatesOfParty = electedCandidates;
this.dataSource.data = this.electedCandidatesOfParty;
}
}
ngAfterViewInit() {
this.dataSource.sort = this.sort;
}
announceSortChange(sortState: Sort) {
if (sortState.direction) {
this.liveAnnouncer.announce($localize`Sorted ${sortState.direction}ending`)
} else {
this.liveAnnouncer.announce($localize`Sorting cleared`)
}
}
getElectedMessage(elected: ElectionSource): string {
switch(elected) {
case ElectionSource.NOT_ELECTED:
return $localize`Not elected`;
case ElectionSource.CONSTITUENCY:
return $localize`Constituency list vote order`;
case ElectionSource.OVERALL_NOMINATION_ORDER:
return $localize`District list position order`;
case ElectionSource.OVERALL_VOTE_ORDER:
return $localize`District list vote order`;
default:
console.error(elected);
return 'oops';
}
}
}

View File

@ -1,15 +1,12 @@
<table mat-table [dataSource]="dataSource" [hidden]="electedCandidates == null" matSort (matSortChange)="announceSortChange($event)">
<ng-container matColumnDef="party">
<th mat-header-cell mat-sort-header *matHeaderCellDef i18n
i18n-sortActionDescription sortActionDescription="Sort by party">Party</th>
<td mat-cell *matCellDef="let row">{{row.party}}</td>
</ng-container>
<ng-container matColumnDef="seats">
<th mat-header-cell mat-sort-header i18n-sortActionDescription sortActionDescription="Sort by seats"
*matHeaderCellDef i18n>Seats</th>
<td mat-cell *matCellDef="let row">{{row.seats}}</td>
</ng-container>
<div [hidden]="electedCandidates == null || parties == null">
<h2 class="mat-headline-6" i18n>Seat allocation and elected candidates</h2>
<mat-tab-group>
<mat-tab i18n-label label="Seat allocation">
<app-seat-allocation [parties]="parties" [electedCandidates]="electedCandidates"></app-seat-allocation>
</mat-tab>
<mat-tab *ngFor="let abbreviation of partyAbbreviations" i18n-label label="{{abbreviation}}">
<app-elected-candidates [viewModel]="getViewModel(abbreviation)"></app-elected-candidates>
</mat-tab>
</mat-tab-group>
</div>
<tr mat-header-row *matHeaderRowDef="resultColumns"></tr>
<tr mat-row *matRowDef="let row; columns: resultColumns"></tr>
</table>

View File

@ -0,0 +1,3 @@
[hidden] {
display: none !important;
}

View File

@ -1,9 +1,8 @@
import {AfterViewInit, Component, Input, ViewChild} from '@angular/core';
import {Component, Input} from '@angular/core';
import {Party} from "../model/party";
import {ElectedCandidates} from "../model/elected-candidates";
import {MatSort, Sort} from "@angular/material/sort";
import {LiveAnnouncer} from "@angular/cdk/a11y";
import {MatTableDataSource} from "@angular/material/table";
import {ViewModel} from "../elected-candidates/elected-candidates.component";
import {electedCandidates} from "../store";
export interface PartySeats {
party: string;
@ -15,50 +14,29 @@ export interface PartySeats {
templateUrl: './election-result.component.html',
styleUrls: ['./election-result.component.scss']
})
export class ElectionResultComponent implements AfterViewInit {
@ViewChild(MatSort) sort: MatSort|null = null;
resultColumns: string[] = ['party', 'seats'];
seatsPerParty: PartySeats[] = [];
dataSource: MatTableDataSource<PartySeats> = new MatTableDataSource<PartySeats>();
export class ElectionResultComponent {
ngAfterViewInit() {
this.dataSource.sort = this.sort;
}
@Input() parties: Map<string, Party>|null = null;
@Input() electedCandidates: ElectedCandidates | null = null;
@Input() parties: Map<string, Party> = new Map<string, Party>();
private _electedCandidates: ElectedCandidates | null = null;
get electedCandidates(): ElectedCandidates | null {
return this._electedCandidates;
}
@Input() set electedCandidates(value: ElectedCandidates | null) {
this._electedCandidates = value;
if (value != null) {
const newSeatAllocations = [];
for (const partyAbbreviation in value.seatAllocation) {
if (!this.parties.has(partyAbbreviation)) {
continue;
}
newSeatAllocations.push({
party: this.parties.get(partyAbbreviation)!.abbreviation,
seats: value.seatAllocation[partyAbbreviation]
})
}
this.seatsPerParty = newSeatAllocations;
this.dataSource.data = this.seatsPerParty;
get partyAbbreviations() {
if (this.electedCandidates == null) {
return null;
}
}
constructor(private liveAnnouncer: LiveAnnouncer) {
}
announceSortChange(sortState: Sort) {
if (sortState.direction) {
this.liveAnnouncer.announce($localize`Sorted ${sortState.direction}ending`)
} else {
this.liveAnnouncer.announce($localize`Sorting cleared`)
const electedParties: string[] = [];
for (const party in this.electedCandidates.seatAllocation) {
electedParties.push(party);
}
return electedParties;
}
getViewModel(partyAbbreviation: string): ViewModel|null {
if (this.electedCandidates == null) {
return null;
}
return {
partyAbbreviation,
electedCandidates: this.electedCandidates
};
}
}

View File

@ -213,8 +213,6 @@ export class ElectionComponent implements OnInit {
map.set(number, modifiedConstituencyResults[number]);
}
this.viewModel.electionResult.constituencyResults = map;
this.valueChanges.emit(modifiedValue);
}
});

View File

@ -17,6 +17,10 @@ import { ElectionContainerComponent } from './election-container/election-contai
import {MatButtonModule} from "@angular/material/button";
import { ElectionResultComponent } from './election-result/election-result.component';
import {MatSortModule} from "@angular/material/sort";
import {MatStepperModule} from "@angular/material/stepper";
import { SeatAllocationComponent } from './seat-allocation/seat-allocation.component';
import {MatIconModule} from "@angular/material/icon";
import { ElectedCandidatesComponent } from './elected-candidates/elected-candidates.component';
@NgModule({
@ -24,7 +28,9 @@ import {MatSortModule} from "@angular/material/sort";
LandingPageComponent,
ElectionComponent,
ElectionContainerComponent,
ElectionResultComponent
ElectionResultComponent,
SeatAllocationComponent,
ElectedCandidatesComponent
],
imports: [
CommonModule,
@ -40,6 +46,8 @@ import {MatSortModule} from "@angular/material/sort";
ReactiveFormsModule,
MatButtonModule,
MatSortModule,
MatStepperModule,
MatIconModule,
],
exports: [
LandingPageComponent

View File

@ -0,0 +1,16 @@
<table mat-table [dataSource]="dataSource"
matSort (matSortChange)="announceSortChange($event)">
<ng-container matColumnDef="party">
<th mat-header-cell mat-sort-header *matHeaderCellDef i18n
i18n-sortActionDescription sortActionDescription="Sort by party">Party</th>
<td mat-cell *matCellDef="let row">{{row.party}}</td>
</ng-container>
<ng-container matColumnDef="seats">
<th mat-header-cell mat-sort-header *matHeaderCellDef i18n
i18n-sortActionDescription sortActionDescription="Sort by seats">Seats</th>
<td mat-cell *matCellDef="let row">{{row.seats}}</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="resultColumns"></tr>
<tr mat-row *matRowDef="let row; columns: resultColumns"></tr>
</table>

View File

@ -0,0 +1,21 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { SeatAllocationComponent } from './seat-allocation.component';
describe('SeatAllocationComponent', () => {
let component: SeatAllocationComponent;
let fixture: ComponentFixture<SeatAllocationComponent>;
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [SeatAllocationComponent]
});
fixture = TestBed.createComponent(SeatAllocationComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,60 @@
import {AfterViewInit, Component, Input, ViewChild} from '@angular/core';
import {MatSort, Sort} from "@angular/material/sort";
import {MatTableDataSource} from "@angular/material/table";
import {PartySeats} from "../election-result/election-result.component";
import {ElectedCandidates} from "../model/elected-candidates";
import {Party} from "../model/party";
import {LiveAnnouncer} from "@angular/cdk/a11y";
@Component({
selector: 'app-seat-allocation',
templateUrl: './seat-allocation.component.html',
styleUrls: ['./seat-allocation.component.scss']
})
export class SeatAllocationComponent implements AfterViewInit {
@ViewChild(MatSort) sort: MatSort|null = null;
resultColumns: string[] = ['party', 'seats'];
seatsPerParty: PartySeats[] = [];
dataSource: MatTableDataSource<PartySeats> = new MatTableDataSource<PartySeats>();
@Input() parties: Map<string, Party>|null = null;
constructor(private liveAnnouncer: LiveAnnouncer) {
}
ngAfterViewInit() {
this.dataSource.sort = this.sort;
}
announceSortChange(sortState: Sort) {
if (sortState.direction) {
this.liveAnnouncer.announce($localize`Sorted ${sortState.direction}ending`)
} else {
this.liveAnnouncer.announce($localize`Sorting cleared`)
}
}
private _electedCandidates: ElectedCandidates | null = null;
get electedCandidates(): ElectedCandidates | null {
return this._electedCandidates;
}
@Input() set electedCandidates(value: ElectedCandidates | null) {
this._electedCandidates = value;
if (value != null) {
const newSeatAllocations = [];
for (const partyAbbreviation in value.seatAllocation) {
if (!this.parties!.has(partyAbbreviation)) {
continue;
}
newSeatAllocations.push({
party: this.parties!.get(partyAbbreviation)!.abbreviation,
seats: value.seatAllocation[partyAbbreviation]
})
}
this.seatsPerParty = newSeatAllocations;
this.dataSource.data = this.seatsPerParty;
}
}
}