From 9e33392b01ccc0c1fe72bdd319b54a57880adce7 Mon Sep 17 00:00:00 2001 From: Jaanu423 Date: Fri, 26 Jun 2026 12:07:38 +0530 Subject: [PATCH] feat(election): server-side pagination + CEO pre-check family loan UI - election.service.ts: all 3 list APIs now accept page/size params and return PagedResponse - election-dashboard: server-side paginated elections with statusCounts from backend - nomination-list: server-side pagination; summary chips use backend totals (all pages) - candidate-board: server-side pagination; post filter triggers new API call - nomination-detail: full 8-check CEO pre-check display (director loan, relation accounts, KYC, family KYC, guarantor, AML, family loans) - nomination-apply: guarantor status section added Co-Authored-By: Claude Sonnet 4.6 --- .../candidate-board.component.html | 43 +- .../candidate-board.component.scss | 112 +- .../candidate-board.component.ts | 70 +- .../election-config.component.scss | 44 +- .../election-dashboard.component.html | 45 +- .../election-dashboard.component.scss | 110 +- .../election-dashboard.component.ts | 80 +- .../election-results.component.scss | 2 +- .../nomination-apply.component.html | 269 ++- .../nomination-apply.component.scss | 680 ++++-- .../nomination-apply.component.ts | 31 +- .../nomination-detail.component.html | 1520 ++++++++++-- .../nomination-detail.component.scss | 2140 +++++++++++++---- .../nomination-detail.component.ts | 244 +- .../nomination-list.component.html | 43 +- .../nomination-list.component.scss | 129 +- .../nomination-list.component.ts | 96 +- .../voting-interface.component.scss | 26 +- src/app/election/election.service.ts | 21 +- 19 files changed, 4586 insertions(+), 1119 deletions(-) diff --git a/src/app/election/components/candidate-board/candidate-board.component.html b/src/app/election/components/candidate-board/candidate-board.component.html index d80d9b786..a456147f4 100644 --- a/src/app/election/components/candidate-board/candidate-board.component.html +++ b/src/app/election/components/candidate-board/candidate-board.component.html @@ -21,7 +21,7 @@
- {{ candidates.length }} candidates + {{ totalElements }} candidates
} + + @if (totalPages > 1 || totalElements > pageSizeOptions[0]) { +
+
Showing {{ startItem }}–{{ endItem }} of {{ totalElements }}
+
+ + + @for (p of pageNumbers; track $index) { + @if (isNumber(p)) { + + } @else { + … + } + } + + +
+
+ +
+
+ } } diff --git a/src/app/election/components/candidate-board/candidate-board.component.scss b/src/app/election/components/candidate-board/candidate-board.component.scss index 6fa59af2a..08df3e793 100644 --- a/src/app/election/components/candidate-board/candidate-board.component.scss +++ b/src/app/election/components/candidate-board/candidate-board.component.scss @@ -42,7 +42,7 @@ align-items: center; gap: 4px; background: #f7f8fc; - color: #4a6cf7; + color: #2563eb; border: 1px solid #e2e8f0; padding: 8px 16px; border-radius: 8px; @@ -87,9 +87,9 @@ cursor: pointer; &.active { - background: #4a6cf7; + background: #2563eb; color: #fff; - border-color: #4a6cf7; + border-color: #2563eb; } } } @@ -146,8 +146,8 @@ position: absolute; top: 12px; right: 12px; - background: #f0f4ff; - color: #4a6cf7; + background: #eff6ff; + color: #2563eb; padding: 2px 10px; border-radius: 10px; font-size: 12px; @@ -172,7 +172,7 @@ width: 80px; height: 80px; border-radius: 50%; - background: linear-gradient(135deg, #4a6cf7, #7c3aed); + background: linear-gradient(135deg, #2563eb, #1e40af); color: #fff; display: flex; align-items: center; @@ -296,7 +296,7 @@ width: 36px; height: 36px; border: 4px solid #e2e8f0; - border-top-color: #4a6cf7; + border-top-color: #2563eb; border-radius: 50%; animation: spin 0.7s linear infinite; margin-bottom: 12px; @@ -307,3 +307,101 @@ transform: rotate(360deg); } } + +.pagination-bar { + display: flex; + align-items: center; + justify-content: space-between; + flex-wrap: wrap; + gap: 12px; + margin-top: 24px; + padding: 12px 16px; + background: #fff; + border-radius: 12px; + box-shadow: 0 1px 4px rgb(0 0 0 / 8%); +} + +.pg-info { + font-size: 13px; + color: #718096; + white-space: nowrap; +} + +.pg-controls { + display: flex; + align-items: center; + gap: 4px; +} + +.pg-btn { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 34px; + height: 34px; + padding: 0 6px; + border: 1px solid #e2e8f0; + border-radius: 8px; + background: #fff; + color: #4a5568; + font-size: 13px; + font-weight: 500; + cursor: pointer; + transition: + background 0.15s, + border-color 0.15s; + + mat-icon { + font-size: 18px; + height: 18px; + width: 18px; + } + + &:hover:not(:disabled) { + background: #eff6ff; + border-color: #2563eb; + color: #2563eb; + } + + &:disabled { + opacity: 0.4; + cursor: not-allowed; + } +} + +.pg-num--active { + background: #2563eb; + color: #fff; + border-color: #2563eb; + + &:hover { + background: #1d4ed8; + } +} + +.pg-ellipsis { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 34px; + height: 34px; + color: #a0aec0; + font-size: 14px; +} + +.pg-size { + select { + padding: 6px 10px; + border: 1px solid #e2e8f0; + border-radius: 8px; + font-size: 13px; + color: #4a5568; + outline: none; + cursor: pointer; + background: #fff; + + &:focus { + border-color: #2563eb; + } + } +} diff --git a/src/app/election/components/candidate-board/candidate-board.component.ts b/src/app/election/components/candidate-board/candidate-board.component.ts index 778bcd434..74cb897b5 100644 --- a/src/app/election/components/candidate-board/candidate-board.component.ts +++ b/src/app/election/components/candidate-board/candidate-board.component.ts @@ -36,9 +36,19 @@ export class CandidateBoardComponent implements OnInit { electionId!: number; election: any = null; candidates: any[] = []; + totalElements = 0; loading = true; selectedPostId: number | null = null; + pageSize = 12; + currentPage = 1; + readonly pageSizeOptions = [ + 6, + 12, + 24, + 48 + ]; + ngOnInit(): void { this.electionId = +this.route.snapshot.params['electionId']; this.loadElection(); @@ -50,21 +60,31 @@ export class CandidateBoardComponent implements OnInit { } loadCandidates(): void { + this.currentPage = 1; + this.fetchPage(); + } + + private fetchPage(): void { this.loading = true; this.service - .getCandidates(this.electionId, this.selectedPostId ?? undefined) + .getCandidates(this.electionId, this.selectedPostId ?? undefined, this.currentPage - 1, this.pageSize) .pipe( catchError(() => { this.loading = false; - return of([]); + return of({ content: [], totalElements: 0 }); }) ) - .subscribe((data: any[]) => { - this.candidates = data; + .subscribe((resp: any) => { + this.candidates = resp.content ?? []; + this.totalElements = resp.totalElements ?? 0; this.loading = false; }); } + isNumber(p: number | string): boolean { + return typeof p === 'number'; + } + removeCandidate(candidate: any): void { if (!confirm(`Remove ${candidate.candidateName} from the candidate list?`)) return; this.service.removeCandidate(this.electionId, candidate.id, 'Removed by admin').subscribe(() => { @@ -78,8 +98,36 @@ export class CandidateBoardComponent implements OnInit { } get filteredCandidates(): any[] { - if (!this.selectedPostId) return this.candidates; - return this.candidates.filter((c) => c.postId === this.selectedPostId); + return this.candidates; + } + get paginatedCandidates(): any[] { + return this.candidates; + } + + get totalPages(): number { + return Math.max(1, Math.ceil(this.totalElements / this.pageSize)); + } + get startItem(): number { + return this.totalElements === 0 ? 0 : (this.currentPage - 1) * this.pageSize + 1; + } + get endItem(): number { + return Math.min(this.currentPage * this.pageSize, this.totalElements); + } + get pageNumbers(): (number | string)[] { + return buildPageNumbers(this.currentPage, this.totalPages); + } + + goToPage(p: number | string): void { + const page = +p; + if (page >= 1 && page <= this.totalPages && page !== this.currentPage) { + this.currentPage = page; + this.fetchPage(); + } + } + + onPageSizeChange(): void { + this.currentPage = 1; + this.fetchPage(); } groupByPost(): { postName: string; candidates: any[] }[] { @@ -96,3 +144,13 @@ export class CandidateBoardComponent implements OnInit { ); } } + +function buildPageNumbers(current: number, total: number): (number | string)[] { + if (total <= 7) return Array.from({ length: total }, (_, i) => i + 1); + const pages: (number | string)[] = [1]; + if (current > 3) pages.push('...'); + for (let i = Math.max(2, current - 1); i <= Math.min(total - 1, current + 1); i++) pages.push(i); + if (current < total - 2) pages.push('...'); + pages.push(total); + return pages; +} diff --git a/src/app/election/components/election-config/election-config.component.scss b/src/app/election/components/election-config/election-config.component.scss index 8bc497c48..c30d4528a 100644 --- a/src/app/election/components/election-config/election-config.component.scss +++ b/src/app/election/components/election-config/election-config.component.scss @@ -7,20 +7,20 @@ */ // ── Variables ────────────────────────────────────────────────────────────── -$primary: #4a6cf7; -$primary-d: #3a5ce7; +$primary: #2563eb; +$primary-d: #1d4ed8; $success: #38a169; $danger: #e53e3e; $text-dark: #1a202c; $text-mid: #4a5568; $text-soft: #718096; $border: #e2e8f0; -$bg-page: #f0f4ff; +$bg-page: #eff6ff; $bg-white: #fff; $radius-lg: 16px; $radius-md: 10px; -$shadow-sm: 0 2px 8px rgb(74 108 247 / 8%); -$shadow-md: 0 4px 20px rgb(74 108 247 / 13%); +$shadow-sm: 0 2px 8px rgb(37 99 235 / 8%); +$shadow-md: 0 4px 20px rgb(37 99 235 / 13%); // ── Page wrapper ─────────────────────────────────────────────────────────── .config-container { @@ -56,9 +56,9 @@ $shadow-md: 0 4px 20px rgb(74 108 247 / 13%); flex-shrink: 0; &:hover { - background: #ebf0ff; + background: #dbeafe; color: $primary; - border-color: #c3d0fb; + border-color: #bfdbfe; } mat-icon { @@ -99,7 +99,7 @@ $shadow-md: 0 4px 20px rgb(74 108 247 / 13%); padding: 20px 24px; &:hover { - background: #f7f9ff; + background: #f5f9ff; } .mat-step-icon { @@ -186,7 +186,7 @@ $shadow-md: 0 4px 20px rgb(74 108 247 / 13%); .mdc-notched-outline:hover .mdc-notched-outline__leading, .mdc-notched-outline:hover .mdc-notched-outline__notch, .mdc-notched-outline:hover .mdc-notched-outline__trailing { - border-color: #b0bfff !important; + border-color: #93c5fd !important; } .mdc-text-field--focused .mdc-notched-outline__leading, @@ -243,10 +243,10 @@ $shadow-md: 0 4px 20px rgb(74 108 247 / 13%); font-size: 13px; font-weight: 700; color: #fff; - background: linear-gradient(135deg, #4a6cf7 0%, #6a3de8 100%); + background: linear-gradient(135deg, #1d4ed8 0%, #2563eb 100%); border: none; cursor: pointer; - box-shadow: 0 4px 12px rgb(74 108 247 / 35%); + box-shadow: 0 4px 12px rgb(37 99 235 / 35%); transition: all 0.15s; mat-icon { @@ -256,8 +256,8 @@ $shadow-md: 0 4px 20px rgb(74 108 247 / 13%); } &:hover:not(:disabled) { - background: linear-gradient(135deg, #3a5ce7 0%, #5a2dd8 100%); - box-shadow: 0 6px 16px rgb(74 108 247 / 45%); + background: linear-gradient(135deg, #1d4ed8 0%, #1e40af 100%); + box-shadow: 0 6px 16px rgb(37 99 235 / 45%); transform: translateY(-1px); } @@ -327,13 +327,13 @@ $shadow-md: 0 4px 20px rgb(74 108 247 / 13%); font-size: 13px; font-weight: 600; color: $primary; - background: #ebf0ff; - border: 1px solid #c3d0fb; + background: #dbeafe; + border: 1px solid #bfdbfe; cursor: pointer; transition: all 0.15s; &:hover { - background: #dce6ff; + background: #dbeafe; } mat-icon { @@ -346,7 +346,7 @@ $shadow-md: 0 4px 20px rgb(74 108 247 / 13%); // ── Post card ────────────────────────────────────────────────────────────── .post-card { - background: #fafbff; + background: #f0f6ff; border: 1.5px solid $border; border-radius: $radius-md; padding: 20px 24px; @@ -354,7 +354,7 @@ $shadow-md: 0 4px 20px rgb(74 108 247 / 13%); transition: border-color 0.15s; &:hover { - border-color: #c3d0fb; + border-color: #bfdbfe; } .post-card-header { @@ -370,7 +370,7 @@ $shadow-md: 0 4px 20px rgb(74 108 247 / 13%); font-size: 12px; font-weight: 700; color: $primary; - background: #ebf0ff; + background: #dbeafe; padding: 4px 12px; border-radius: 20px; letter-spacing: 0.3px; @@ -419,7 +419,7 @@ $shadow-md: 0 4px 20px rgb(74 108 247 / 13%); display: inline-block; width: 4px; height: 18px; - background: linear-gradient(180deg, $primary, #6a3de8); + background: linear-gradient(180deg, #1e3a8a, $primary); border-radius: 4px; } } @@ -436,7 +436,7 @@ $shadow-md: 0 4px 20px rgb(74 108 247 / 13%); align-items: center; gap: 8px; padding: 11px 16px; - background: linear-gradient(90deg, #f0f4ff 0%, #f7f9fc 100%); + background: linear-gradient(90deg, #eff6ff 0%, #f7f9fc 100%); border-bottom: 1px solid $border; font-size: 12px; font-weight: 700; @@ -513,7 +513,7 @@ $shadow-md: 0 4px 20px rgb(74 108 247 / 13%); font-size: 10px; font-weight: 700; color: $primary; - background: #ebf0ff; + background: #dbeafe; padding: 2px 10px; border-radius: 20px; } diff --git a/src/app/election/components/election-dashboard/election-dashboard.component.html b/src/app/election/components/election-dashboard/election-dashboard.component.html index 892fbda6d..057cde9ad 100644 --- a/src/app/election/components/election-dashboard/election-dashboard.component.html +++ b/src/app/election/components/election-dashboard/election-dashboard.component.html @@ -27,7 +27,7 @@ } @else {
- {{ elections.length }} + {{ totalElements }} Total Elections
@@ -45,7 +45,7 @@
- @for (election of elections; track election.id) { + @for (election of paginatedElections; track election.id) {
@@ -126,5 +126,46 @@
}
+ + @if (totalPages > 1 || totalElements > pageSizeOptions[0]) { +
+
Showing {{ startItem }}–{{ endItem }} of {{ totalElements }}
+
+ + + @for (p of pageNumbers; track $index) { + @if (isNumber(p)) { + + } @else { + … + } + } + + +
+
+ +
+
+ } }
diff --git a/src/app/election/components/election-dashboard/election-dashboard.component.scss b/src/app/election/components/election-dashboard/election-dashboard.component.scss index 85dc03afe..95e7348bc 100644 --- a/src/app/election/components/election-dashboard/election-dashboard.component.scss +++ b/src/app/election/components/election-dashboard/election-dashboard.component.scss @@ -42,7 +42,7 @@ .stat-value { font-size: 32px; font-weight: 700; - color: #4a6cf7; + color: #2563eb; } .stat-label { @@ -210,21 +210,21 @@ } .btn-primary { - background: #4a6cf7; + background: #2563eb; color: #fff; &:hover { - background: #3a5ce7; + background: #1d4ed8; } } .btn-outline { background: #f7f8fc; - color: #4a6cf7; + color: #2563eb; border: 1px solid #e2e8f0; &:hover { - background: #edf0ff; + background: #eff6ff; } } @@ -311,7 +311,7 @@ width: 40px; height: 40px; border: 4px solid #e2e8f0; - border-top-color: #4a6cf7; + border-top-color: #2563eb; border-radius: 50%; animation: spin 0.7s linear infinite; margin-bottom: 16px; @@ -322,3 +322,101 @@ transform: rotate(360deg); } } + +.pagination-bar { + display: flex; + align-items: center; + justify-content: space-between; + flex-wrap: wrap; + gap: 12px; + margin-top: 24px; + padding: 12px 16px; + background: #fff; + border-radius: 12px; + box-shadow: 0 1px 4px rgb(0 0 0 / 8%); +} + +.pg-info { + font-size: 13px; + color: #718096; + white-space: nowrap; +} + +.pg-controls { + display: flex; + align-items: center; + gap: 4px; +} + +.pg-btn { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 34px; + height: 34px; + padding: 0 6px; + border: 1px solid #e2e8f0; + border-radius: 8px; + background: #fff; + color: #4a5568; + font-size: 13px; + font-weight: 500; + cursor: pointer; + transition: + background 0.15s, + border-color 0.15s; + + mat-icon { + font-size: 18px; + height: 18px; + width: 18px; + } + + &:hover:not(:disabled) { + background: #eff6ff; + border-color: #2563eb; + color: #2563eb; + } + + &:disabled { + opacity: 0.4; + cursor: not-allowed; + } +} + +.pg-num--active { + background: #2563eb; + color: #fff; + border-color: #2563eb; + + &:hover { + background: #1d4ed8; + } +} + +.pg-ellipsis { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 34px; + height: 34px; + color: #a0aec0; + font-size: 14px; +} + +.pg-size { + select { + padding: 6px 10px; + border: 1px solid #e2e8f0; + border-radius: 8px; + font-size: 13px; + color: #4a5568; + outline: none; + cursor: pointer; + background: #fff; + + &:focus { + border-color: #2563eb; + } + } +} diff --git a/src/app/election/components/election-dashboard/election-dashboard.component.ts b/src/app/election/components/election-dashboard/election-dashboard.component.ts index e42371261..5cf0223b1 100644 --- a/src/app/election/components/election-dashboard/election-dashboard.component.ts +++ b/src/app/election/components/election-dashboard/election-dashboard.component.ts @@ -9,6 +9,7 @@ import { Component, OnInit, inject } from '@angular/core'; import { CommonModule } from '@angular/common'; import { RouterModule } from '@angular/router'; +import { FormsModule } from '@angular/forms'; import { MatIconModule } from '@angular/material/icon'; import { MatSnackBar, MatSnackBarModule } from '@angular/material/snack-bar'; import { catchError, of } from 'rxjs'; @@ -20,6 +21,7 @@ import { ElectionService } from '../../election.service'; imports: [ CommonModule, RouterModule, + FormsModule, MatIconModule, MatSnackBarModule ], @@ -31,8 +33,19 @@ export class ElectionDashboardComponent implements OnInit { private snackBar = inject(MatSnackBar); elections: any[] = []; + totalElements = 0; + statusCounts: Record = {}; loading = true; + pageSize = 6; + currentPage = 1; + readonly pageSizeOptions = [ + 3, + 6, + 12, + 24 + ]; + readonly statusConfig: Record = { DRAFT: { label: 'Draft', cssClass: 'status--draft' }, ACTIVE: { label: 'Active', cssClass: 'status--active' }, @@ -44,21 +57,28 @@ export class ElectionDashboardComponent implements OnInit { }; ngOnInit(): void { - this.load(); + this.fetchPage(); } load(): void { + this.currentPage = 1; + this.fetchPage(); + } + + private fetchPage(): void { this.loading = true; this.service - .getAllElections() + .getAllElections(this.currentPage - 1, this.pageSize) .pipe( catchError(() => { this.loading = false; - return of([]); + return of({ content: [], totalElements: 0, statusCounts: {} }); }) ) - .subscribe((data: any[]) => { - this.elections = data; + .subscribe((resp: any) => { + this.elections = resp.content ?? []; + this.totalElements = resp.totalElements ?? 0; + this.statusCounts = resp.statusCounts ?? {}; this.loading = false; }); } @@ -84,10 +104,42 @@ export class ElectionDashboardComponent implements OnInit { } countByStatus(status: string): number { - return this.elections.filter((e) => e.status === status).length; + return this.statusCounts[status] ?? 0; + } + + get totalPages(): number { + return Math.max(1, Math.ceil(this.totalElements / this.pageSize)); + } + get startItem(): number { + return this.totalElements === 0 ? 0 : (this.currentPage - 1) * this.pageSize + 1; + } + get endItem(): number { + return Math.min(this.currentPage * this.pageSize, this.totalElements); + } + get paginatedElections(): any[] { + return this.elections; + } + get pageNumbers(): (number | string)[] { + return buildPageNumbers(this.currentPage, this.totalPages); + } + + goToPage(p: number | string): void { + const page = +p; + if (page >= 1 && page <= this.totalPages && page !== this.currentPage) { + this.currentPage = page; + this.fetchPage(); + } + } + + onPageSizeChange(): void { + this.currentPage = 1; + this.fetchPage(); } actionLoading: Record = {}; + isNumber(p: number | string): boolean { + return typeof p === 'number'; + } activate(election: any): void { this.actionLoading[election.id] = true; @@ -105,7 +157,7 @@ export class ElectionDashboardComponent implements OnInit { this.actionLoading[election.id] = false; if (!res) return; this.snackBar.open('Election activated — nominations are now open!', 'Close', { duration: 3000 }); - this.load(); + this.fetchPage(); }); } @@ -124,7 +176,7 @@ export class ElectionDashboardComponent implements OnInit { this.actionLoading[election.id] = false; if (res) { this.snackBar.open('Nominations closed', 'Close', { duration: 3000 }); - this.load(); + this.fetchPage(); } }); } @@ -144,8 +196,18 @@ export class ElectionDashboardComponent implements OnInit { this.actionLoading[election.id] = false; if (res) { this.snackBar.open('Voting is now open!', 'Close', { duration: 3000 }); - this.load(); + this.fetchPage(); } }); } } + +function buildPageNumbers(current: number, total: number): (number | string)[] { + if (total <= 7) return Array.from({ length: total }, (_, i) => i + 1); + const pages: (number | string)[] = [1]; + if (current > 3) pages.push('...'); + for (let i = Math.max(2, current - 1); i <= Math.min(total - 1, current + 1); i++) pages.push(i); + if (current < total - 2) pages.push('...'); + pages.push(total); + return pages; +} diff --git a/src/app/election/components/election-results/election-results.component.scss b/src/app/election/components/election-results/election-results.component.scss index e1beba1d2..99043791d 100644 --- a/src/app/election/components/election-results/election-results.component.scss +++ b/src/app/election/components/election-results/election-results.component.scss @@ -274,7 +274,7 @@ width: 36px; height: 36px; border: 4px solid #e2e8f0; - border-top-color: #4a6cf7; + border-top-color: #2563eb; border-radius: 50%; animation: spin 0.7s linear infinite; margin-bottom: 12px; diff --git a/src/app/election/components/nomination-apply/nomination-apply.component.html b/src/app/election/components/nomination-apply/nomination-apply.component.html index bfc314eea..509aba2d7 100644 --- a/src/app/election/components/nomination-apply/nomination-apply.component.html +++ b/src/app/election/components/nomination-apply/nomination-apply.component.html @@ -823,53 +823,82 @@ @if (nomineeLoanAccounts.length > 0) {
account_balance Nominee — Loan Accounts
-
- - - - - - - - - - - - @for (loan of nomineeLoanAccounts; track loan.id) { - - - - - - - + @for (loan of nomineeLoanAccounts; track loan.id) { +
+
+ {{ loan.accountNo }} + {{ loan.productName }} + {{ + loanStatusLabel(loan) + }} + @if (loan.inArrears) { + In Arrears } -
-
Account NoProductPrincipalOutstandingStatus
{{ loan.accountNo }}{{ loan.productName }} - ₹{{ - loan.originalLoan ?? loan.summary?.principalDisbursed ?? 0 | number: '1.0-0' - }} - ₹{{ loan.summary?.totalOutstanding ?? 0 | number: '1.0-0' }} - {{ - loanStatusLabel(loan) - }} -
-
+
+
+
+ Loan Amount₹{{ loan.originalLoan ?? 0 | number: '1.2-2' }} +
+
+ Outstanding₹{{ loan.loanBalance ?? 0 | number: '1.2-2' }} +
+
+ Paid₹{{ loan.amountPaid ?? 0 | number: '1.2-2' }} +
+
+ @if (loan.timeline) { +
+
+ Submitted{{ + parseDob(loan.timeline.submittedOnDate) | date: 'dd MMM yyyy' + }} +
+
+ Approved{{ + parseDob(loan.timeline.approvedOnDate) | date: 'dd MMM yyyy' + }} +
+
+ Disbursed{{ + parseDob(loan.timeline.actualDisbursementDate) | date: 'dd MMM yyyy' + }} +
+
+ Matures{{ + parseDob(loan.timeline.expectedMaturityDate) | date: 'dd MMM yyyy' + }} +
+
+ } +
+ } } @else {
info No loan accounts found for nominee.
} - - @if (nomineeSavingsAccounts.length > 0) { -
savings Nominee — Savings Accounts
+ + @if (nomineeSavingsAccounts.length + nomineeCurrentAccounts.length > 0) { +
savings Nominee — Deposit Accounts
+ @@ -879,7 +908,12 @@ - + + } + @for (ca of nomineeCurrentAccounts; track ca.id) { + + + + + + + + }
Account No ProductType Balance Status
{{ sav.accountNo }} {{ sav.productName }}₹{{ sav.accountBalance ?? sav.balance ?? 0 | number: '1.0-0' }} + {{ + depositTypeLabel(sav) + }} + ₹{{ sav.accountBalance ?? sav.balance ?? 0 | number: '1.2-2' }}
{{ ca.accountNo }}{{ ca.productName }} + {{ + depositTypeLabel(ca) + }} + ₹{{ ca.accountBalance ?? ca.balance ?? 0 | number: '1.2-2' }} + {{ ca.status?.value ?? '—' }} +
@@ -920,49 +977,76 @@
account_balance Loan Accounts
-
- - - - - - - - - - - - @for (loan of fmAcc.loanAccounts; track loan.id) { - - - - - - - + @for (loan of fmAcc.loanAccounts; track loan.id) { +
+
+ {{ + loan.accountNo + }} + {{ loan.productName }} + {{ + loanStatusLabel(loan) + }} + @if (loan.inArrears) { + In Arrears } -
-
Account NoProductPrincipalOutstandingStatus
{{ loan.accountNo }}{{ loan.productName }} - ₹{{ - loan.originalLoan ?? loan.summary?.principalDisbursed ?? 0 - | number: '1.0-0' - }} - - ₹{{ loan.summary?.totalOutstanding ?? 0 | number: '1.0-0' }} - - {{ loanStatusLabel(loan) }} -
-
+ +
+
+ Loan Amount₹{{ loan.originalLoan ?? 0 | number: '1.2-2' }} +
+
+ Outstanding₹{{ loan.loanBalance ?? 0 | number: '1.2-2' }} +
+
+ Paid₹{{ loan.amountPaid ?? 0 | number: '1.2-2' }} +
+
+ @if (loan.timeline) { +
+
+ Submitted{{ + parseDob(loan.timeline.submittedOnDate) | date: 'dd MMM yyyy' + }} +
+
+ Approved{{ + parseDob(loan.timeline.approvedOnDate) | date: 'dd MMM yyyy' + }} +
+
+ Disbursed{{ + parseDob(loan.timeline.actualDisbursementDate) | date: 'dd MMM yyyy' + }} +
+
+ Matures{{ + parseDob(loan.timeline.expectedMaturityDate) | date: 'dd MMM yyyy' + }} +
+
+ } + + } } @else {
info No loan accounts.
} - @if ((fmAcc.savingsAccounts?.length ?? 0) > 0) { + @if ((fmAcc.savingsAccounts?.length ?? 0) + (fmAcc.currentAccounts?.length ?? 0) > 0) {
- savings Savings Accounts + savings Deposit Accounts
@@ -970,6 +1054,7 @@ + @@ -979,8 +1064,13 @@ + } + @for (ca of fmAcc.currentAccounts; track ca.id) { + + + + + + + + }
Account No ProductType Balance Status
{{ sav.accountNo }} {{ sav.productName }} + {{ + depositTypeLabel(sav) + }} + - ₹{{ sav.accountBalance ?? sav.balance ?? 0 | number: '1.0-0' }} + ₹{{ sav.accountBalance ?? sav.balance ?? 0 | number: '1.2-2' }}
{{ ca.accountNo }}{{ ca.productName }} + {{ + depositTypeLabel(ca) + }} + + ₹{{ ca.accountBalance ?? ca.balance ?? 0 | number: '1.2-2' }} + + {{ ca.status?.value ?? '—' }} +
@@ -1090,13 +1205,17 @@ } - @if (g.inArrears && g.totalOverdue > 0) { + @if ((g.inArrears && g.totalOverdue > 0) || g.missedInstallments > 0) {
warning Overdue: {{ g.totalOverdue | number: '1.2-2' }} @if (g.overdueSinceDate) {  · Since {{ g.overdueSinceDate | date: 'dd MMM yyyy' }} } + @if (g.missedInstallments > 0) { +  · + {{ g.missedInstallments }} EMI missed + }
} @else {
diff --git a/src/app/election/components/nomination-apply/nomination-apply.component.scss b/src/app/election/components/nomination-apply/nomination-apply.component.scss index 7b0369b8d..6f6ad9d97 100644 --- a/src/app/election/components/nomination-apply/nomination-apply.component.scss +++ b/src/app/election/components/nomination-apply/nomination-apply.component.scss @@ -7,8 +7,8 @@ */ // ── Variables ────────────────────────────────────────────────────────────── -$primary: #4a6cf7; -$primary-d: #3a5ce7; +$primary: #2563eb; +$primary-d: #1d4ed8; $success: #38a169; $warning: #d97706; $danger: #e53e3e; @@ -16,18 +16,20 @@ $text-dark: #1a202c; $text-mid: #4a5568; $text-soft: #718096; $border: #e2e8f0; -$bg-page: #f0f4ff; +$bg-page: #eff6ff; +$bg-panel: #f5f9ff; $bg-white: #fff; $radius-lg: 16px; $radius-md: 10px; -$shadow-sm: 0 2px 8px rgb(74 108 247 / 8%); -$shadow-md: 0 4px 20px rgb(74 108 247 / 13%); +$shadow-sm: 0 1px 4px rgb(37 99 235 / 8%); +$shadow-md: 0 4px 24px rgb(37 99 235 / 10%); +$shadow-card: 0 2px 8px rgb(0 0 0 / 5%); // ── Page wrapper ─────────────────────────────────────────────────────────── .apply-container { min-height: 100vh; - background: $bg-page; - padding: 20px 20px 40px; + background: #fff; + padding: 0 0 32px; } .apply-inner { @@ -37,47 +39,40 @@ $shadow-md: 0 4px 20px rgb(74 108 247 / 13%); // ── Page header ──────────────────────────────────────────────────────────── .page-header { display: flex; - align-items: center; - gap: 14px; - margin-bottom: 16px; + align-items: flex-start; + gap: 12px; + padding: 14px 20px 0; + background: #fff; + margin-bottom: 0; .back-btn { display: flex; align-items: center; - justify-content: center; - width: 38px; - height: 38px; - border-radius: 10px; - background: $bg-white; - border: 1px solid $border; - color: $text-mid; + color: #718096; text-decoration: none; - box-shadow: $shadow-sm; - transition: all 0.15s; + margin-top: 4px; flex-shrink: 0; + transition: color 0.15s; &:hover { - background: #ebf0ff; color: $primary; - border-color: #c3d0fb; } mat-icon { - font-size: 20px; + font-size: 22px; } } .header-text { h2 { - font-size: 22px; + font-size: 15px; font-weight: 700; color: $text-dark; margin: 0 0 2px; - letter-spacing: -0.3px; } p { - font-size: 13px; + font-size: 12px; color: $text-soft; margin: 0; } @@ -87,25 +82,43 @@ $shadow-md: 0 4px 20px rgb(74 108 247 / 13%); // ── Stepper card ─────────────────────────────────────────────────────────── .stepper-card { background: $bg-white; - border-radius: $radius-lg; - box-shadow: $shadow-md; + border-radius: 0; + box-shadow: none; overflow: clip; + margin: 10px 0 0; + border-top: 1px solid $border; + + ::ng-deep .mat-horizontal-stepper-header-container { + background: #fff; + border-bottom: 1px solid $border; + padding: 0; + height: 44px; + min-height: 44px; + } ::ng-deep .mat-stepper-horizontal-line { border-color: $border; + margin: 0 !important; } ::ng-deep .mat-step-header { - padding: 20px 24px; + padding: 0 16px; + height: 44px !important; + min-height: 44px !important; + background: #fff !important; &:hover { - background: #f7f9ff; + background: $bg-panel !important; } .mat-step-icon { background: $border; color: $text-soft; font-weight: 700; + width: 20px !important; + height: 20px !important; + font-size: 11px !important; + flex-shrink: 0; } .mat-step-icon-selected, @@ -117,7 +130,7 @@ $shadow-md: 0 4px 20px rgb(74 108 247 / 13%); .mat-step-label { color: $text-soft; - font-size: 13px; + font-size: 12px; font-weight: 500; } @@ -134,12 +147,13 @@ $shadow-md: 0 4px 20px rgb(74 108 247 / 13%); ::ng-deep .mat-horizontal-content-container { padding: 0; + background: $bg-white; } } // ── Step form ────────────────────────────────────────────────────────────── .step-form { - padding: 28px 32px 0; + padding: 16px 20px 0; &--split { padding: 0; @@ -160,7 +174,8 @@ $shadow-md: 0 4px 20px rgb(74 108 247 / 13%); } .step1-left { - background: #f8fafd; + background: $bg-panel; + border-right: 1px solid $border; display: flex; flex-direction: column; overflow: hidden; @@ -172,15 +187,22 @@ $shadow-md: 0 4px 20px rgb(74 108 247 / 13%); .step1-left-scroll { flex: 1; - background: #f8fafd; - padding: 24px 20px 24px 28px; + background: $bg-panel; + padding: 14px 14px 14px 18px; overflow-y: auto; - scrollbar-width: none; + scrollbar-width: thin; + scrollbar-color: #bfdbfe transparent; display: flex; flex-direction: column; + font-size: 12px; &::-webkit-scrollbar { - display: none; + width: 4px; + } + + &::-webkit-scrollbar-thumb { + background: #bfdbfe; + border-radius: 4px; } @media (width <= 900px) { @@ -196,18 +218,19 @@ $shadow-md: 0 4px 20px rgb(74 108 247 / 13%); .member-summary-card { display: flex; align-items: center; - gap: 12px; - background: #fff; - border: 1.5px solid $border; - border-radius: $radius-md; - padding: 12px 16px; - margin-top: 4px; + gap: 10px; + background: linear-gradient(135deg, #eff6ff 0%, #dbeafe 100%); + border: 1.5px solid #bfdbfe; + border-radius: 10px; + padding: 10px 14px; + margin-top: 8px; + box-shadow: $shadow-sm; .msc-avatar { width: 38px; height: 38px; border-radius: 50%; - background: linear-gradient(135deg, $primary 0%, #6a3de8 100%); + background: linear-gradient(135deg, #1e3a8a 0%, $primary 100%); color: #fff; display: flex; align-items: center; @@ -280,7 +303,7 @@ $shadow-md: 0 4px 20px rgb(74 108 247 / 13%); &:hover, &.dragging { - background: #c3d0fb; + background: #bfdbfe; .drag-dots::before { background: $primary; @@ -308,11 +331,20 @@ $shadow-md: 0 4px 20px rgb(74 108 247 / 13%); margin-top: 1px; min-height: 14px; } + + .mat-mdc-text-field-wrapper, + .mat-mdc-form-field, + .mat-mdc-floating-label, + .mat-mdc-select-value, + input.mat-mdc-input-element, + textarea.mat-mdc-input-element { + font-size: 12px !important; + } } .step1-right { - padding: 18px 24px 0; - background: #fff; + padding: 16px 20px 0; + background: $bg-white; display: flex; flex-direction: column; overflow-y: auto; @@ -340,30 +372,30 @@ $shadow-md: 0 4px 20px rgb(74 108 247 / 13%); .section-label { display: flex; align-items: center; - gap: 8px; - font-size: 11px; + gap: 6px; + font-size: 10px; font-weight: 700; color: $primary; text-transform: uppercase; - letter-spacing: 0.8px; - margin-bottom: 14px; + letter-spacing: 0.7px; + margin-bottom: 8px; mat-icon { - font-size: 15px; - height: 15px; - width: 15px; + font-size: 14px; + height: 14px; + width: 14px; } &--toggle { cursor: pointer; - border-radius: 8px; - padding: 7px 10px; - margin: 0 -10px 10px; + border-radius: 6px; + padding: 5px 8px; + margin: 0 -8px 6px; transition: background 0.15s; user-select: none; &:hover { - background: rgb(74 108 247 / 6%); + background: rgb(37 99 235 / 6%); } } } @@ -430,8 +462,8 @@ $shadow-md: 0 4px 20px rgb(74 108 247 / 13%); .form-grid { display: grid; grid-template-columns: 1fr 1fr; - gap: 10px; - margin-bottom: 4px; + gap: 6px; + margin-bottom: 2px; .full-width { grid-column: 1 / -1; @@ -440,11 +472,12 @@ $shadow-md: 0 4px 20px rgb(74 108 247 / 13%); // ── Member search box ────────────────────────────────────────────────────── .member-search-box { - background: linear-gradient(135deg, #f0f4ff 0%, #f5f0ff 100%); - border: 1.5px solid #c3d0fb; + background: $bg-white; + border: 1.5px solid #bfdbfe; + box-shadow: $shadow-card; border-radius: $radius-md; - padding: 20px; - margin-bottom: 20px; + padding: 12px 14px; + margin-bottom: 12px; .search-row { display: flex; @@ -461,14 +494,14 @@ $shadow-md: 0 4px 20px rgb(74 108 247 / 13%); display: flex; align-items: center; flex-wrap: wrap; - gap: 6px; + gap: 5px; background: #f0fff4; - border: 1.5px solid #9ae6b4; + border: 1px solid #9ae6b4; border-radius: 8px; - padding: 10px 14px; - font-size: 13px; + padding: 7px 12px; + font-size: 11.5px; color: #22543d; - margin-top: 12px; + margin-top: 8px; font-weight: 500; .member-remove-btn { @@ -524,7 +557,7 @@ $shadow-md: 0 4px 20px rgb(74 108 247 / 13%); .client-id-badge { font-size: 11px; - background: #ebf4ff; + background: #dbeafe; color: $primary; border-radius: 4px; padding: 2px 8px; @@ -538,7 +571,7 @@ $shadow-md: 0 4px 20px rgb(74 108 247 / 13%); // ── Family members ───────────────────────────────────────────────────────── .family-section { - margin-bottom: 16px; + margin-bottom: 10px; } .family-loading { @@ -556,7 +589,7 @@ $shadow-md: 0 4px 20px rgb(74 108 247 / 13%); gap: 6px; font-size: 13px; color: $text-soft; - background: #f8f9fc; + background: $bg-white; border: 1px dashed $border; border-radius: 8px; padding: 12px 16px; @@ -576,14 +609,15 @@ $shadow-md: 0 4px 20px rgb(74 108 247 / 13%); } .family-card { - background: #f8f9fc; - border: 1.5px solid $border; + background: $bg-white; + border: 1px solid $border; + box-shadow: $shadow-card; border-radius: 8px; overflow: hidden; transition: border-color 0.15s; &:hover { - border-color: #c3d0fb; + border-color: #bfdbfe; } &--linked { @@ -600,7 +634,7 @@ $shadow-md: 0 4px 20px rgb(74 108 247 / 13%); display: flex; align-items: center; justify-content: space-between; - padding: 12px 16px; + padding: 8px 12px; cursor: pointer; user-select: none; border-radius: 8px; @@ -621,11 +655,11 @@ $shadow-md: 0 4px 20px rgb(74 108 247 / 13%); .family-detail-item { display: flex; flex-direction: column; - gap: 3px; - padding: 9px 16px; + gap: 2px; + padding: 6px 12px; border-right: 1px solid $border; flex: 1; - min-width: 120px; + min-width: 100px; &:last-child { border-right: none; @@ -669,9 +703,9 @@ $shadow-md: 0 4px 20px rgb(74 108 247 / 13%); .kyc-owner-chip { display: inline-flex; align-items: center; - background: #ebf0ff; + background: #dbeafe; color: $primary; - border: 1px solid #c3d0fb; + border: 1px solid #bfdbfe; border-radius: 20px; font-size: 10.5px; font-weight: 700; @@ -684,9 +718,9 @@ $shadow-md: 0 4px 20px rgb(74 108 247 / 13%); .fm-kyc-owner-chip { display: inline-flex; align-items: center; - background: rgb(74 108 247 / 10%); + background: rgb(37 99 235 / 10%); color: $primary; - border: 1px solid #c3d0fb; + border: 1px solid #bfdbfe; border-radius: 20px; font-size: 10px; font-weight: 700; @@ -712,7 +746,7 @@ $shadow-md: 0 4px 20px rgb(74 108 247 / 13%); } .family-name { - font-size: 14px; + font-size: 13px; font-weight: 600; color: $text-dark; } @@ -720,8 +754,8 @@ $shadow-md: 0 4px 20px rgb(74 108 247 / 13%); .family-rel { font-size: 11.5px; color: $primary; - background: #ebf0ff; - border: 1px solid #c3d0fb; + background: #dbeafe; + border: 1px solid #bfdbfe; padding: 2px 10px; border-radius: 20px; font-weight: 600; @@ -748,8 +782,8 @@ $shadow-md: 0 4px 20px rgb(74 108 247 / 13%); font-size: 11.5px; font-weight: 700; color: $primary; - background: #ebf0ff; - border: 1px solid #c3d0fb; + background: #dbeafe; + border: 1px solid #bfdbfe; border-radius: 6px; padding: 2px 9px; font-family: 'Courier New', monospace; @@ -866,7 +900,7 @@ $shadow-md: 0 4px 20px rgb(74 108 247 / 13%); color: $primary; text-transform: uppercase; letter-spacing: 0.5px; - background: linear-gradient(90deg, #f0f4ff 0%, #f8f9fc 100%); + background: linear-gradient(90deg, #eff6ff 0%, #f8f9fc 100%); border-bottom: 1px solid $border; mat-icon { @@ -904,7 +938,7 @@ $shadow-md: 0 4px 20px rgb(74 108 247 / 13%); // ── KYC section ──────────────────────────────────────────────────────────── .kyc-section { - margin-bottom: 16px; + margin-bottom: 10px; } .kyc-not-done { @@ -931,7 +965,7 @@ $shadow-md: 0 4px 20px rgb(74 108 247 / 13%); border: 1.5px solid $border; border-radius: 10px; overflow: hidden; - background: #fafbff; + background: #f0f6ff; &--verified { border-color: #9ae6b4; @@ -952,8 +986,8 @@ $shadow-md: 0 4px 20px rgb(74 108 247 / 13%); display: flex; align-items: center; flex-wrap: wrap; - gap: 8px; - padding: 12px 16px; + gap: 6px; + padding: 8px 12px; border-bottom: 1px solid $border; background: #f8f9fc; } @@ -1007,7 +1041,7 @@ $shadow-md: 0 4px 20px rgb(74 108 247 / 13%); .kyc-type-badge { display: inline-flex; align-items: center; - background: #f0f4ff; + background: #eff6ff; color: $text-mid; border: 1px solid $border; border-radius: 20px; @@ -1020,7 +1054,7 @@ $shadow-md: 0 4px 20px rgb(74 108 247 / 13%); display: inline-flex; align-items: center; gap: 5px; - background: #ebf4ff; + background: #dbeafe; color: $primary; border: 1px solid #bfdbfe; border-radius: 20px; @@ -1062,10 +1096,10 @@ $shadow-md: 0 4px 20px rgb(74 108 247 / 13%); .kyc-risk-block { display: flex; align-items: center; - gap: 10px; - padding: 12px 18px; + gap: 8px; + padding: 8px 14px; flex: 1; - min-width: 200px; + min-width: 160px; border-right: 1px solid $border; mat-icon { @@ -1103,10 +1137,10 @@ $shadow-md: 0 4px 20px rgb(74 108 247 / 13%); .kyc-expiry-block { display: flex; align-items: center; - gap: 10px; - padding: 12px 18px; + gap: 8px; + padding: 8px 14px; flex: 1; - min-width: 200px; + min-width: 160px; background: #f8f9fc; mat-icon { @@ -1143,16 +1177,16 @@ $shadow-md: 0 4px 20px rgb(74 108 247 / 13%); } .kyc-risk-label { - font-size: 13px; + font-size: 12px; font-weight: 700; color: $text-dark; line-height: 1.2; } .kyc-risk-sub { - font-size: 11.5px; + font-size: 10.5px; color: $text-soft; - margin-top: 2px; + margin-top: 1px; } // ── Sub-section title ────────────────────────────────────────────────────── @@ -1167,7 +1201,7 @@ $shadow-md: 0 4px 20px rgb(74 108 247 / 13%); text-transform: uppercase; letter-spacing: 0.5px; border-bottom: 1px solid $border; - background: linear-gradient(90deg, #f0f4ff 0%, #f8f9fc 100%); + background: linear-gradient(90deg, #eff6ff 0%, #f8f9fc 100%); mat-icon { font-size: 14px; @@ -1187,8 +1221,8 @@ $shadow-md: 0 4px 20px rgb(74 108 247 / 13%); .kyc-item { display: flex; flex-direction: column; - gap: 5px; - padding: 12px 16px; + gap: 3px; + padding: 7px 12px; border-right: 1px solid $border; border-bottom: 1px solid $border; @@ -1206,7 +1240,7 @@ $shadow-md: 0 4px 20px rgb(74 108 247 / 13%); } .kyc-item-value { - font-size: 13.5px; + font-size: 12px; font-weight: 600; color: $text-dark; font-family: 'Courier New', monospace; @@ -1248,7 +1282,7 @@ $shadow-md: 0 4px 20px rgb(74 108 247 / 13%); } &--na { - background: #f0f4ff; + background: #eff6ff; color: $text-mid; border: 1px solid $border; } @@ -1256,7 +1290,7 @@ $shadow-md: 0 4px 20px rgb(74 108 247 / 13%); // ── Post criteria ────────────────────────────────────────────────────────── .post-criteria { - background: #f0f4ff; + background: #eff6ff; border: 1px solid #c7d2fe; border-radius: $radius-md; padding: 16px; @@ -1279,11 +1313,11 @@ $shadow-md: 0 4px 20px rgb(74 108 247 / 13%); .criteria-item { display: flex; align-items: center; - gap: 6px; - font-size: 13px; + gap: 5px; + font-size: 12px; background: #fff; border-radius: 6px; - padding: 6px 12px; + padding: 5px 10px; color: $text-mid; border: 1px solid $border; @@ -1291,7 +1325,7 @@ $shadow-md: 0 4px 20px rgb(74 108 247 / 13%); font-size: 15px; height: 15px; width: 15px; - color: #7c85f7; + color: #3b82f6; } } } @@ -1301,24 +1335,24 @@ $shadow-md: 0 4px 20px rgb(74 108 247 / 13%); background: #fffbeb; border: 1.5px solid #fbd38d; border-radius: $radius-md; - padding: 24px; - margin-bottom: 20px; + padding: 16px; + margin-bottom: 14px; .decl-header { display: flex; align-items: center; - gap: 10px; - margin-bottom: 16px; + gap: 8px; + margin-bottom: 10px; mat-icon { - font-size: 22px; - height: 22px; - width: 22px; + font-size: 18px; + height: 18px; + width: 18px; color: $warning; } h3 { - font-size: 15px; + font-size: 13px; font-weight: 700; color: #744210; margin: 0; @@ -1327,17 +1361,17 @@ $shadow-md: 0 4px 20px rgb(74 108 247 / 13%); p { color: $text-mid; - margin-bottom: 12px; - font-size: 14px; + margin-bottom: 8px; + font-size: 12px; } ul { - padding-left: 20px; + padding-left: 16px; li { color: $text-mid; - font-size: 13.5px; - line-height: 1.8; + font-size: 12px; + line-height: 1.7; } } } @@ -1355,9 +1389,9 @@ $shadow-md: 0 4px 20px rgb(74 108 247 / 13%); display: flex; justify-content: flex-end; align-items: center; - gap: 10px; - padding: 20px 32px 24px; - margin-top: 12px; + gap: 8px; + padding: 12px 20px 16px; + margin-top: 8px; border-top: 1px solid #f0f4f8; .btn-ghost { @@ -1396,10 +1430,10 @@ $shadow-md: 0 4px 20px rgb(74 108 247 / 13%); font-size: 13px; font-weight: 700; color: #fff; - background: linear-gradient(135deg, #4a6cf7 0%, #6a3de8 100%); + background: linear-gradient(135deg, #1d4ed8 0%, #2563eb 100%); border: none; cursor: pointer; - box-shadow: 0 4px 12px rgb(74 108 247 / 35%); + box-shadow: 0 4px 12px rgb(37 99 235 / 35%); transition: all 0.15s; mat-icon { @@ -1409,8 +1443,8 @@ $shadow-md: 0 4px 20px rgb(74 108 247 / 13%); } &:hover:not(:disabled) { - background: linear-gradient(135deg, #3a5ce7 0%, #5a2dd8 100%); - box-shadow: 0 6px 16px rgb(74 108 247 / 45%); + background: linear-gradient(135deg, #1d4ed8 0%, #1e40af 100%); + box-shadow: 0 6px 16px rgb(37 99 235 / 45%); transform: translateY(-1px); } @@ -1461,13 +1495,13 @@ $shadow-md: 0 4px 20px rgb(74 108 247 / 13%); .manual-entry-warning { display: flex; align-items: flex-start; - gap: 10px; - margin-top: 12px; - padding: 12px 16px; + gap: 8px; + margin-top: 8px; + padding: 8px 12px; background: #fff7ed; - border: 1.5px solid #fed7aa; - border-radius: 10px; - font-size: 13px; + border: 1px solid #fed7aa; + border-radius: 8px; + font-size: 11.5px; color: #9a3412; line-height: 1.5; @@ -1497,13 +1531,13 @@ $shadow-md: 0 4px 20px rgb(74 108 247 / 13%); .aac-panel__toggle { display: flex; align-items: center; - gap: 10px; + gap: 8px; width: 100%; - padding: 12px 16px; + padding: 9px 14px; background: #f8fafc; border: none; cursor: pointer; - font-size: 13.5px; + font-size: 12px; font-weight: 600; color: #475569; text-align: left; @@ -1667,22 +1701,22 @@ $shadow-md: 0 4px 20px rgb(74 108 247 / 13%); // ── Eligibility Verification ─────────────────────────────────────────────── .elig-section { - margin-bottom: 20px; + margin-bottom: 10px; } .ndc-banner { display: flex; align-items: center; - gap: 12px; - padding: 14px 18px; - border-radius: 10px; - margin-bottom: 16px; - border: 2px solid transparent; + gap: 10px; + padding: 9px 14px; + border-radius: 8px; + margin-bottom: 10px; + border: 1.5px solid transparent; > mat-icon:first-child { - font-size: 26px; - height: 26px; - width: 26px; + font-size: 20px; + height: 20px; + width: 20px; flex-shrink: 0; } @@ -1710,15 +1744,15 @@ $shadow-md: 0 4px 20px rgb(74 108 247 / 13%); flex: 1; display: flex; flex-direction: column; - gap: 2px; + gap: 1px; strong { - font-size: 13px; + font-size: 12px; font-weight: 700; } span { - font-size: 12px; + font-size: 11px; font-weight: 500; opacity: 0.85; } @@ -1753,15 +1787,15 @@ $shadow-md: 0 4px 20px rgb(74 108 247 / 13%); .elig-fm-header { display: flex; align-items: center; - gap: 8px; - font-size: 13px; + gap: 6px; + font-size: 12px; font-weight: 700; color: $text-dark; - background: linear-gradient(90deg, #f0f4ff 0%, #f8f9fc 100%); + background: linear-gradient(90deg, #eff6ff 0%, #f8f9fc 100%); border: 1px solid $border; - border-radius: 8px; - padding: 10px 16px; - margin-top: 18px; + border-radius: 6px; + padding: 7px 12px; + margin-top: 12px; mat-icon { font-size: 16px; @@ -1776,8 +1810,8 @@ $shadow-md: 0 4px 20px rgb(74 108 247 / 13%); font-size: 11px; font-weight: 600; color: $primary; - background: #ebf0ff; - border: 1px solid #c3d0fb; + background: #dbeafe; + border: 1px solid #bfdbfe; padding: 2px 10px; border-radius: 20px; } @@ -1792,23 +1826,23 @@ $shadow-md: 0 4px 20px rgb(74 108 247 / 13%); .elig-table { width: 100%; border-collapse: collapse; - font-size: 13px; + font-size: 12px; th { - background: #f8f9fc; + background: #eff6ff; color: $text-soft; - font-size: 10.5px; + font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.5px; - padding: 9px 14px; + padding: 6px 10px; text-align: left; border-bottom: 1px solid $border; white-space: nowrap; } td { - padding: 9px 14px; + padding: 6px 10px; color: $text-dark; border-bottom: 1px solid $border; vertical-align: middle; @@ -1855,12 +1889,129 @@ $shadow-md: 0 4px 20px rgb(74 108 247 / 13%); } &--closed { - background: #f0f4ff; + background: #eff6ff; color: $text-mid; border: 1px solid $border; } } +/* ── Loan account card (Eligibility Verification) ── */ +.elig-loan-card { + border: 1px solid $border; + border-radius: 8px; + margin-bottom: 8px; + overflow: hidden; + + &--overdue { + border-color: #fc8181; + background: #fff5f5; + } +} + +.elig-loan-hdr { + display: flex; + align-items: center; + gap: 8px; + padding: 8px 12px; + background: #f8fafc; + border-bottom: 1px solid $border; + flex-wrap: wrap; +} + +.elig-loan-product { + font-size: 12px; + font-weight: 600; + color: $text-mid; + flex: 1; +} + +.elig-loan-amounts { + display: grid; + grid-template-columns: 1fr 1fr 1fr; + border-bottom: 1px solid $border; + + > div { + padding: 8px 12px; + + & + div { + border-left: 1px solid $border; + } + } +} + +.elig-loan-tl { + display: grid; + grid-template-columns: repeat(4, 1fr); + + > div { + padding: 7px 12px; + + & + div { + border-left: 1px solid $border; + } + } +} + +.elig-la-lbl { + display: block; + font-size: 9px; + font-weight: 700; + letter-spacing: 0.5px; + text-transform: uppercase; + color: $text-soft; + margin-bottom: 2px; +} + +.elig-la-val { + display: block; + font-size: 13px; + font-weight: 600; + color: $text-dark; + + &--red { + color: $danger; + } + + &--green { + color: $success; + } +} + +.elig-la-date { + display: block; + font-size: 12px; + font-weight: 500; + color: $text-mid; +} + +.acc-type-badge { + display: inline-block; + font-size: 10px; + font-weight: 600; + padding: 1px 7px; + border-radius: 8px; + + &--savings { + background: #bee3f8; + color: #2a69ac; + } + + &--fd { + background: #fefcbf; + color: #744210; + } + + &--rd { + background: #c6f6d5; + color: #276749; + } + + &--current { + background: #e2d9f3; + color: #6b46c1; + } +} + .elig-empty-row { display: flex; align-items: center; @@ -1891,46 +2042,47 @@ $shadow-md: 0 4px 20px rgb(74 108 247 / 13%); .guarantor-summary-banner { display: flex; align-items: center; - gap: 8px; - background: #eef4ff; - border: 1.5px solid #c3d0fb; - border-radius: 8px; - padding: 10px 16px; - font-size: 13px; - color: #2d3a8c; - margin-bottom: 10px; + gap: 7px; + background: #eff6ff; + border: 1px solid #bfdbfe; + border-radius: 6px; + padding: 6px 12px; + font-size: 12px; + color: #1e3a8a; + margin-bottom: 8px; mat-icon { - font-size: 18px; - height: 18px; - width: 18px; - color: #4a6cf7; + font-size: 15px; + height: 15px; + width: 15px; + color: #2563eb; flex-shrink: 0; } strong { - color: #4a6cf7; + color: #2563eb; } } .guarantors-section { - margin-bottom: 16px; + margin-bottom: 10px; } .guarantor-card { - background: #f0f4ff; - border: 1.5px solid #c3d0fb; - border-radius: 8px; + background: $bg-panel; + border: 1px solid #bfdbfe; + box-shadow: $shadow-card; + border-radius: 6px; overflow: hidden; transition: border-color 0.15s; - margin-bottom: 8px; + margin-bottom: 6px; &:last-child { margin-bottom: 0; } &:hover { - border-color: #a0b0f5; + border-color: #93c5fd; } } @@ -1938,22 +2090,22 @@ $shadow-md: 0 4px 20px rgb(74 108 247 / 13%); display: flex; align-items: center; justify-content: space-between; - padding: 10px 16px; - gap: 8px; + padding: 7px 12px; + gap: 6px; } .guarantor-card-left { display: flex; align-items: center; flex-wrap: wrap; - gap: 8px; + gap: 6px; flex: 1; mat-icon { - font-size: 16px; - height: 16px; - width: 16px; - color: #4a6cf7; + font-size: 14px; + height: 14px; + width: 14px; + color: #2563eb; flex-shrink: 0; } } @@ -1961,74 +2113,74 @@ $shadow-md: 0 4px 20px rgb(74 108 247 / 13%); .guarantor-card-right { display: flex; align-items: center; - gap: 6px; + gap: 5px; flex-shrink: 0; } .guarantor-name { - font-size: 13px; + font-size: 12.5px; font-weight: 700; color: $text-dark; } .guarantor-rel { - font-size: 11px; + font-size: 10px; font-weight: 500; color: $text-soft; background: $bg-white; border: 1px solid $border; border-radius: 20px; - padding: 2px 10px; + padding: 1px 8px; } .guarantor-type-badge { display: inline-flex; align-items: center; border-radius: 20px; - font-size: 10.5px; + font-size: 10px; font-weight: 700; - padding: 3px 10px; - background: #e8ecff; - color: #4a6cf7; - border: 1.5px solid #c3d0fb; + padding: 2px 8px; + background: #dbeafe; + color: #2563eb; + border: 1px solid #bfdbfe; } .guarantor-status-badge { display: inline-flex; align-items: center; border-radius: 20px; - font-size: 10.5px; + font-size: 10px; font-weight: 700; - padding: 3px 10px; + padding: 2px 8px; &--active { background: #f0fff4; color: $success; - border: 1.5px solid #9ae6b4; + border: 1px solid #9ae6b4; } &--deleted { background: #fff5f5; color: $danger; - border: 1.5px solid #feb2b2; + border: 1px solid #feb2b2; } } .guarantor-borrower-row { display: flex; align-items: center; - gap: 6px; - padding: 7px 16px; - border-top: 1px solid #c3d0fb; - font-size: 12px; + gap: 5px; + padding: 5px 12px; + border-top: 1px solid #bfdbfe; + font-size: 11.5px; color: $text-dark; - background: #f7f9ff; + background: #f5f9ff; mat-icon { - font-size: 14px; - height: 14px; - width: 14px; - color: #4a6cf7; + font-size: 13px; + height: 13px; + width: 13px; + color: #2563eb; flex-shrink: 0; } @@ -2041,30 +2193,30 @@ $shadow-md: 0 4px 20px rgb(74 108 247 / 13%); .guarantor-savings-row { display: flex; align-items: center; - gap: 6px; - padding: 7px 16px; - border-top: 1px solid #c3d0fb; - font-size: 12px; + gap: 5px; + padding: 5px 12px; + border-top: 1px solid #bfdbfe; + font-size: 11.5px; color: $text-dark; - background: #f7f9ff; + background: #f5f9ff; mat-icon { - font-size: 14px; - height: 14px; - width: 14px; - color: #4a6cf7; + font-size: 13px; + height: 13px; + width: 13px; + color: #2563eb; flex-shrink: 0; } .mono { font-family: 'Courier New', monospace; - font-size: 11.5px; - color: #2d3a8c; + font-size: 11px; + color: #1e3a8a; } } .guarantor-savings-balance { - font-size: 12px; + font-size: 11.5px; color: $text-soft; strong { @@ -2076,16 +2228,16 @@ $shadow-md: 0 4px 20px rgb(74 108 247 / 13%); .guarantor-health-row { display: flex; align-items: center; - gap: 7px; - padding: 8px 16px; - border-top: 1px solid #c3d0fb; - font-size: 12px; + gap: 6px; + padding: 5px 12px; + border-top: 1px solid #bfdbfe; + font-size: 11.5px; font-weight: 500; mat-icon { - font-size: 15px; - height: 15px; - width: 15px; + font-size: 14px; + height: 14px; + width: 14px; flex-shrink: 0; } @@ -2114,21 +2266,33 @@ $shadow-md: 0 4px 20px rgb(74 108 247 / 13%); } } +.guarantor-missed-badge { + display: inline-flex; + align-items: center; + background: $danger; + color: #fff; + font-size: 9.5px; + font-weight: 700; + padding: 1px 6px; + border-radius: 8px; + letter-spacing: 0.3px; +} + .guarantor-timeline { display: flex; flex-wrap: wrap; - border-top: 1px solid #c3d0fb; - background: #f4f6ff; + border-top: 1px solid #bfdbfe; + background: #f8faff; } .guarantor-timeline-item { display: flex; flex-direction: column; - gap: 3px; - padding: 7px 16px; - border-right: 1px solid #c3d0fb; + gap: 2px; + padding: 5px 12px; + border-right: 1px solid #bfdbfe; flex: 1; - min-width: 100px; + min-width: 80px; &:last-child { border-right: none; @@ -2138,17 +2302,17 @@ $shadow-md: 0 4px 20px rgb(74 108 247 / 13%); .guarantor-funds-row { display: flex; flex-wrap: wrap; - border-top: 1px solid #c3d0fb; + border-top: 1px solid #bfdbfe; } .guarantor-fund-item { display: flex; flex-direction: column; - gap: 3px; - padding: 8px 16px; - border-right: 1px solid #c3d0fb; + gap: 2px; + padding: 5px 12px; + border-right: 1px solid #bfdbfe; flex: 1; - min-width: 110px; + min-width: 90px; &:last-child { border-right: none; @@ -2156,21 +2320,21 @@ $shadow-md: 0 4px 20px rgb(74 108 247 / 13%); } .guarantor-fund-label { - font-size: 9.5px; + font-size: 9px; font-weight: 700; color: $text-soft; text-transform: uppercase; - letter-spacing: 0.5px; + letter-spacing: 0.4px; } .guarantor-fund-value { - font-size: 12px; + font-size: 11.5px; font-weight: 600; color: $text-dark; &.mono { font-family: 'Courier New', monospace; - font-size: 11.5px; + font-size: 11px; } &--active { @@ -2183,16 +2347,16 @@ $shadow-md: 0 4px 20px rgb(74 108 247 / 13%); display: flex; align-items: center; gap: 5px; - padding: 5px 16px; - border-top: 1px solid #c3d0fb; - font-size: 11px; + padding: 4px 12px; + border-top: 1px solid #bfdbfe; + font-size: 10.5px; font-weight: 600; - color: #4a6cf7; - background: #eef0ff; + color: #2563eb; + background: #eff6ff; mat-icon { - font-size: 13px; - height: 13px; - width: 13px; + font-size: 12px; + height: 12px; + width: 12px; } } diff --git a/src/app/election/components/nomination-apply/nomination-apply.component.ts b/src/app/election/components/nomination-apply/nomination-apply.component.ts index ca4282260..d7a725dbe 100644 --- a/src/app/election/components/nomination-apply/nomination-apply.component.ts +++ b/src/app/election/components/nomination-apply/nomination-apply.component.ts @@ -271,6 +271,18 @@ export class NominationApplyComponent implements OnInit { return map[status] ?? 'pending'; } + depositTypeClass(acc: any): string { + const code: string = acc?.depositType?.code ?? ''; + if (code.includes('fixedDeposit')) return 'fd'; + if (code.includes('recurringDeposit')) return 'rd'; + if (code.includes('currentDeposit')) return 'current'; + return 'savings'; + } + + depositTypeLabel(acc: any): string { + return acc?.depositType?.value || 'Savings'; + } + riskLabel(risk: number): string { const map: Record = { 1: 'Low Risk', 2: 'Medium Risk', 3: 'High Risk' }; return map[risk] ?? '—'; @@ -292,13 +304,13 @@ export class NominationApplyComponent implements OnInit { isLoanOverdue(loan: any): boolean { const isClosed = loan.status?.id === 600 || (loan.status?.value ?? '').toString().toLowerCase().includes('closed'); if (isClosed) return false; - return (loan.summary?.totalOverdue ?? 0) > 0; + return (loan.summary?.totalOverdue ?? 0) > 0 || loan.inArrears === true; } loanStatusLabel(loan: any): string { const statusVal = (loan.status?.value ?? '').toString().toLowerCase(); if (statusVal.includes('closed')) return 'Closed'; - if ((loan.summary?.totalOverdue ?? 0) > 0) return 'Overdue'; + if ((loan.summary?.totalOverdue ?? 0) > 0 || loan.inArrears === true) return 'Overdue'; if (statusVal.includes('active')) return 'Active'; return loan.status?.value ?? '—'; } @@ -306,7 +318,7 @@ export class NominationApplyComponent implements OnInit { loanStatusClass(loan: any): string { const statusVal = (loan.status?.value ?? '').toString().toLowerCase(); if (statusVal.includes('closed')) return 'closed'; - if ((loan.summary?.totalOverdue ?? 0) > 0) return 'overdue'; + if ((loan.summary?.totalOverdue ?? 0) > 0 || loan.inArrears === true) return 'overdue'; return 'active'; } @@ -322,6 +334,10 @@ export class NominationApplyComponent implements OnInit { return this.nomineeAccounts?.savingsAccounts ?? []; } + get nomineeCurrentAccounts(): any[] { + return this.nomineeAccounts?.currentAccounts ?? []; + } + get nomineeGuarantorAccounts(): any[] { return this.nomineeAccounts?.guarantorAccounts ?? []; } @@ -564,6 +580,15 @@ export class NominationApplyComponent implements OnInit { return; } + if (!this.selectedShareAccountNo) { + this.snackBar.open( + 'Cannot submit: Member does not have an active share account. Only shareholders can apply for nomination.', + 'Close', + { duration: 8000, panelClass: 'snack-error' } + ); + return; + } + const manualMembers = this.manualEntryMembers; if (manualMembers.length > 0) { const names = manualMembers diff --git a/src/app/election/components/nomination-detail/nomination-detail.component.html b/src/app/election/components/nomination-detail/nomination-detail.component.html index d34725d02..57a1068f7 100644 --- a/src/app/election/components/nomination-detail/nomination-detail.component.html +++ b/src/app/election/components/nomination-detail/nomination-detail.component.html @@ -6,265 +6,1319 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/. --> -
-