- nomination-apply: show full KYC for linked family members via nested forkJoin, accordion expand/collapse cards, owner chips on KYC headers, 'Create as Client' button for manual entries - family-members-tab: show Linked Client / Manual Entry badges, entry type in detail grid, 'Create as Client' button navigating with prefill state - create-client + client-general-step: accept prefillData @Input from navigation state, patch form fields from family member data - Add MPL-2.0 headers to all election module files Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
105 lines
3.6 KiB
HTML
105 lines
3.6 KiB
HTML
<!--
|
|
Copyright since 2025 Mifos Initiative
|
|
|
|
This Source Code Form is subject to the terms of the Mozilla Public
|
|
License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
-->
|
|
|
|
<div class="election-results">
|
|
<div class="page-header">
|
|
<a routerLink="/election" class="back-link"><mat-icon>arrow_back</mat-icon></a>
|
|
<div>
|
|
<h2>Election Results</h2>
|
|
@if (election) {
|
|
<p class="sub-title">{{ election.title }}</p>
|
|
}
|
|
</div>
|
|
@if (canDeclare) {
|
|
<button class="btn-declare" (click)="declareResults()" [disabled]="declaring">
|
|
<mat-icon>gavel</mat-icon>
|
|
{{ declaring ? 'Declaring...' : 'Declare Results' }}
|
|
</button>
|
|
}
|
|
</div>
|
|
|
|
@if (loading) {
|
|
<div class="loading-state">
|
|
<div class="spinner"></div>
|
|
<p>Loading results...</p>
|
|
</div>
|
|
} @else if (results.length === 0) {
|
|
<div class="empty-state">
|
|
<mat-icon>bar_chart</mat-icon>
|
|
<h3>Results not yet declared</h3>
|
|
@if (canDeclare) {
|
|
<p>Voting is closed. Click "Declare Results" to compute and publish the results.</p>
|
|
} @else {
|
|
<p>Results will appear here once the election is complete and results are declared.</p>
|
|
}
|
|
</div>
|
|
} @else {
|
|
<div class="results-meta">
|
|
<div class="meta-chip">
|
|
<mat-icon>how_to_vote</mat-icon>
|
|
Election: {{ election?.electionDate | date: 'dd MMM yyyy' }}
|
|
</div>
|
|
@if (results[0]?.declaredDate) {
|
|
<div class="meta-chip">
|
|
<mat-icon>gavel</mat-icon>
|
|
Declared: {{ results[0].declaredDate | date: 'dd MMM yyyy HH:mm' }}
|
|
</div>
|
|
}
|
|
</div>
|
|
|
|
@for (group of groupByPost(); track group.postName) {
|
|
<div class="post-result">
|
|
<div class="post-result-header">
|
|
<h3>{{ group.postName }}</h3>
|
|
</div>
|
|
|
|
<!-- Winner Spotlight -->
|
|
@for (result of group.results; track result.id) {
|
|
@if (result.rankPosition === 1 && result.result === 'WON') {
|
|
<div class="winner-banner">
|
|
<div class="trophy-icon"><mat-icon>emoji_events</mat-icon></div>
|
|
<div class="winner-info">
|
|
<span class="winner-label">Winner</span>
|
|
<h4>{{ result.candidateName }}</h4>
|
|
<span class="winner-votes">{{ result.voteCount | number }} votes</span>
|
|
</div>
|
|
</div>
|
|
}
|
|
}
|
|
|
|
<!-- Results Table with Bar Chart -->
|
|
<div class="results-table">
|
|
@for (result of group.results; track result.id) {
|
|
<div class="result-row" [class.winner]="result.result === 'WON'">
|
|
<div class="rank">
|
|
@if (result.result === 'WON') {
|
|
<mat-icon class="won-icon">emoji_events</mat-icon>
|
|
} @else {
|
|
<span># {{ result.rankPosition }}</span>
|
|
}
|
|
</div>
|
|
<div class="candidate-name">{{ result.candidateName }}</div>
|
|
<div class="vote-bar">
|
|
<div
|
|
class="bar-fill"
|
|
[class.bar-won]="result.result === 'WON'"
|
|
[style.width.%]="barWidth(result.voteCount, maxVotes(group.results))"
|
|
></div>
|
|
<span class="vote-count">{{ result.voteCount | number }} votes</span>
|
|
</div>
|
|
<div class="result-badge" [class.won]="result.result === 'WON'" [class.lost]="result.result === 'LOST'">
|
|
{{ result.result }}
|
|
</div>
|
|
</div>
|
|
}
|
|
</div>
|
|
</div>
|
|
}
|
|
}
|
|
</div>
|