feat(election,family): family member KYC display, collapsible cards, linked/manual badges, create-as-client flow

- 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>
This commit is contained in:
Jaanu423
2026-06-19 17:30:29 +05:30
co-authored by Claude Sonnet 4.6
parent 822fa4c68d
commit 10b301c30c
38 changed files with 7846 additions and 131 deletions
@@ -0,0 +1,130 @@
<!--
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-dashboard">
<div class="page-header">
<h2 class="page-title">Election Management</h2>
<a routerLink="/election/config/new" class="btn-primary"> <mat-icon>add</mat-icon> Create Election </a>
</div>
@if (loading) {
<div class="loading-state">
<div class="spinner"></div>
<p>Loading elections...</p>
</div>
} @else if (elections.length === 0) {
<div class="empty-state">
<mat-icon class="empty-icon">how_to_vote</mat-icon>
<h3>No elections found</h3>
<p>Create a new election to get started.</p>
<a routerLink="/election/config/new" class="btn-primary">Create Election</a>
</div>
} @else {
<div class="stats-row">
<div class="stat-card">
<span class="stat-value">{{ elections.length }}</span>
<span class="stat-label">Total Elections</span>
</div>
<div class="stat-card">
<span class="stat-value">{{ countByStatus('ACTIVE') }}</span>
<span class="stat-label">Active</span>
</div>
<div class="stat-card">
<span class="stat-value">{{ countByStatus('VOTING_OPEN') }}</span>
<span class="stat-label">Voting Open</span>
</div>
<div class="stat-card">
<span class="stat-value">{{ countByStatus('RESULT_DECLARED') }}</span>
<span class="stat-label">Completed</span>
</div>
</div>
<div class="elections-grid">
@for (election of elections; track election.id) {
<div class="election-card">
<div class="card-header">
<div>
<h3 class="election-title">{{ election.title }}</h3>
<span class="election-type">{{ election.electionType }}</span>
</div>
<span class="status-chip" [ngClass]="statusClass(election.status)">
{{ statusLabel(election.status) }}
</span>
</div>
<div class="card-body">
<div class="date-row">
<div class="date-item">
<mat-icon>event_note</mat-icon>
<div>
<span class="date-label">Nominations</span>
<span class="date-value">
{{ election.nominationStartDate | date: 'dd MMM' }} –
{{ election.nominationEndDate | date: 'dd MMM yyyy' }}
</span>
</div>
</div>
<div class="date-item">
<mat-icon>how_to_vote</mat-icon>
<div>
<span class="date-label">Election Date</span>
<span class="date-value">{{ election.electionDate | date: 'dd MMM yyyy' }}</span>
</div>
</div>
</div>
@if (election.description) {
<p class="description">{{ election.description }}</p>
}
</div>
<div class="card-actions">
<a [routerLink]="['/election', election.id, 'nominations']" class="btn-outline">
<mat-icon>people</mat-icon> Nominations
</a>
<a [routerLink]="['/election', election.id, 'candidates']" class="btn-outline">
<mat-icon>person_pin</mat-icon> Candidates
</a>
@if (election.status === 'DRAFT') {
<button class="btn-activate" [disabled]="actionLoading[election.id]" (click)="activate(election)">
<mat-icon>play_circle</mat-icon>
{{ actionLoading[election.id] ? 'Activating...' : 'Activate' }}
</button>
}
@if (canNominate(election)) {
<a [routerLink]="['/election', election.id, 'nomination', 'apply']" class="btn-primary">
<mat-icon>person_add</mat-icon> Nominate Member
</a>
}
@if (election.status === 'ACTIVE') {
<button class="btn-warn" [disabled]="actionLoading[election.id]" (click)="closeNominations(election)">
<mat-icon>lock</mat-icon> Close Nominations
</button>
}
@if (election.status === 'NOMINATION_CLOSED') {
<button class="btn-activate" [disabled]="actionLoading[election.id]" (click)="openVoting(election)">
<mat-icon>how_to_vote</mat-icon> Open Voting
</button>
}
@if (canVote(election)) {
<a [routerLink]="['/election', election.id, 'vote']" class="btn-vote">
<mat-icon>ballot</mat-icon> Vote Now
</a>
}
@if (canViewResults(election)) {
<a [routerLink]="['/election', election.id, 'results']" class="btn-results">
<mat-icon>bar_chart</mat-icon> Results
</a>
}
</div>
</div>
}
</div>
}
</div>