feat(election): candidate photos/symbols, voting interface, withdrawal, live vote counts

- Candidate board: show client profile photos and election symbols via CBS ContentStore
  (lazy-fetch base64 via GET /election/{id}/candidate/{id}/symbol), zoom/drag viewer
- Candidate board: live vote count badge per candidate (VOTING_OPEN/CLOSED only)
- Election dashboard: total voters/votes chips on VOTING_OPEN election cards
- Voting interface: fix candidates not loading (paged response .content extraction),
  lazy-load candidate photos and symbols, gold-bordered symbol on ballot card
- Nomination withdrawal: implement withdraw button in nomination-detail and nomination-list
- ImagePreviewDialogComponent: DomSanitizer support for data: URLs
- election.service: uploadCandidateSymbol (FormData), getCandidateSymbol, getClientImage,
  getVoteSummary methods
- main.scss: ipd-dialog / ipd-backdrop styles for image preview dialog

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Jaanu423
2026-06-26 18:38:11 +05:30
co-authored by Claude Sonnet 4.6
parent 9e33392b01
commit f1b829a100
19 changed files with 1217 additions and 182 deletions
@@ -8,6 +8,7 @@
import { Component, OnInit, OnDestroy, inject, Input } from '@angular/core';
import { CommonModule } from '@angular/common';
import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { HttpClient, HttpParams } from '@angular/common/http';
import { catchError, debounceTime, distinctUntilChanged } from 'rxjs/operators';
@@ -439,7 +440,7 @@ export class JointHolderDialogComponent implements OnInit {
[style.cursor]="dragging ? 'grabbing' : zoom > 1 ? 'grab' : 'zoom-in'"
>
<img
[src]="data.url"
[src]="safeUrl"
[alt]="data.title"
class="ipd-img"
[style.transform]="imgTransform"
@@ -548,6 +549,11 @@ export class JointHolderDialogComponent implements OnInit {
export class ImagePreviewDialogComponent {
dialogRef = inject<MatDialogRef<ImagePreviewDialogComponent>>(MatDialogRef);
data = inject<{ url: string; title: string }>(MAT_DIALOG_DATA);
private sanitizer = inject(DomSanitizer);
get safeUrl(): SafeResourceUrl {
return this.sanitizer.bypassSecurityTrustResourceUrl(this.data.url);
}
zoom = 1;
tx = 0;