@@ -302,33 +313,59 @@
Tenure Period
-
- Tenure Start Date *
+
+
+ Tenure Start Date *
+ @if (isTenureStartAutoFilled) {
+ Auto
+ }
+
-
-
+
+
+ @if (isTenureStartAutoFilled) {
+ Derived from election result date
+ }
@if (submitted && !form.tenureStartDate) {
Tenure start date is required
}
-
- Tenure End Date *
+
+
+ Tenure End Date *
+ @if (isTenureEndAutoFilled) {
+ Auto
+ }
+
-
-
+
+
+ @if (isTenureEndAutoFilled) {
+ Derived from election duration ({{ selectedElection?.durationYears }} yr)
+ }
@if (submitted && !form.tenureEndDate) {
Tenure end date is required
}
diff --git a/src/app/election/components/director-board/director-board.component.scss b/src/app/election/components/director-board/director-board.component.scss
index 44eaa5fd7..337c6cc50 100644
--- a/src/app/election/components/director-board/director-board.component.scss
+++ b/src/app/election/components/director-board/director-board.component.scss
@@ -1032,6 +1032,56 @@ $font:
font-style: normal;
}
+.auto-badge {
+ display: inline-block;
+ font-size: 9px;
+ font-weight: 700;
+ background: #d1fae5;
+ color: #065f46;
+ padding: 1px 6px;
+ border-radius: 6px;
+ letter-spacing: 0.3px;
+ text-transform: uppercase;
+ margin-left: 6px;
+ vertical-align: middle;
+}
+
+.tenure-expired {
+ background: #fef2f2;
+ border: 1px solid #fecaca;
+}
+
+.expired-date {
+ color: #dc2626;
+ display: flex;
+ align-items: center;
+ gap: 4px;
+}
+
+.expired-tag {
+ display: inline-block;
+ font-size: 9px;
+ font-weight: 700;
+ background: #fee2e2;
+ color: #dc2626;
+ padding: 1px 6px;
+ border-radius: 6px;
+ letter-spacing: 0.3px;
+ text-transform: uppercase;
+}
+
+.field-auto-filled {
+ ::ng-deep .mat-mdc-form-field-flex {
+ background: #f0fdf4 !important;
+ }
+
+ ::ng-deep .mdc-notched-outline__leading,
+ ::ng-deep .mdc-notched-outline__notch,
+ ::ng-deep .mdc-notched-outline__trailing {
+ border-color: #86efac !important;
+ }
+}
+
@media (width <= 600px) {
.board-page {
padding: 16px;
diff --git a/src/app/election/components/director-board/director-board.component.ts b/src/app/election/components/director-board/director-board.component.ts
index 37ff51968..8a7204990 100644
--- a/src/app/election/components/director-board/director-board.component.ts
+++ b/src/app/election/components/director-board/director-board.component.ts
@@ -77,6 +77,7 @@ export class DirectorBoardComponent implements OnInit {
saving = false;
deleting: number | null = null;
submitted = false;
+ selectedElection: any = null;
// ── Step-1: election + WON candidates ─────────────────────────
elections: any[] = [];
@@ -115,6 +116,8 @@ export class DirectorBoardComponent implements OnInit {
pickPending(p: any): void {
this.editingId = null;
+ this.selectedElectionId = p.electionId;
+ this.selectedElection = null;
this.form = {
...this.emptyForm(),
electionId: p.electionId,
@@ -122,9 +125,17 @@ export class DirectorBoardComponent implements OnInit {
candidateName: p.candidateName,
postName: p.postName
};
- this.selectedElectionId = p.electionId;
this.formStep = 2;
this.showForm = true;
+ this.service.getElection(p.electionId).subscribe((election: any) => {
+ this.selectedElection = election;
+ if (election?.durationEndDate) {
+ this.form.tenureEndDate = new Date(election.durationEndDate);
+ if (election?.resultDate) {
+ this.form.tenureStartDate = new Date(election.resultDate);
+ }
+ }
+ });
}
load(): void {
@@ -133,7 +144,11 @@ export class DirectorBoardComponent implements OnInit {
.getDirectors({ status: this.filterStatus || undefined, page: this.page, size: this.pageSize })
.subscribe({
next: (data: any) => {
- this.directors = data?.content ?? [];
+ const todayStr = new Date().toISOString().slice(0, 10);
+ this.directors = (data?.content ?? []).map((d: any) => ({
+ ...d,
+ isExpired: d.tenureEndDate ? d.tenureEndDate < todayStr : false
+ }));
this.totalElements = data?.totalElements ?? 0;
this.loading = false;
},
@@ -154,6 +169,7 @@ export class DirectorBoardComponent implements OnInit {
openAppoint(): void {
this.submitted = false;
this.editingId = null;
+ this.selectedElection = null;
this.form = this.emptyForm();
this.formStep = 1;
this.selectedElectionId = null;
@@ -172,6 +188,7 @@ export class DirectorBoardComponent implements OnInit {
selectElection(electionId: number): void {
this.selectedElectionId = electionId;
+ this.selectedElection = this.elections.find((e) => e.id === electionId) ?? null;
this.wonCandidates = [];
this.wonLoading = true;
this.service.getResults(electionId).subscribe((results: any[]) => {
@@ -181,12 +198,21 @@ export class DirectorBoardComponent implements OnInit {
}
pickCandidate(candidate: any): void {
+ const tenureEndDate = this.selectedElection?.durationEndDate
+ ? new Date(this.selectedElection.durationEndDate)
+ : null;
+ const tenureStartDate =
+ this.selectedElection?.durationEndDate && this.selectedElection?.resultDate
+ ? new Date(this.selectedElection.resultDate)
+ : null;
this.form = {
...this.emptyForm(),
electionId: this.selectedElectionId,
candidateId: candidate.candidateId,
candidateName: candidate.candidateName,
- postName: candidate.postName
+ postName: candidate.postName,
+ tenureStartDate,
+ tenureEndDate
};
this.formStep = 2;
}
@@ -197,15 +223,26 @@ export class DirectorBoardComponent implements OnInit {
onTenureStartChange(val: Date | null): void {
if (!val) return;
+ if (this.isTenureStartAutoFilled) return;
const end = new Date(val);
end.setFullYear(end.getFullYear() + 5);
this.form.tenureEndDate = end;
}
+ get isTenureStartAutoFilled(): boolean {
+ return !!this.selectedElection?.durationEndDate && !!this.selectedElection?.resultDate;
+ }
+
+ get isTenureEndAutoFilled(): boolean {
+ return !!this.selectedElection?.durationEndDate;
+ }
+
// ── Open edit drawer (skip step 1) ────────────────────────────
openEdit(d: any): void {
this.submitted = false;
this.editingId = d.id;
+ this.selectedElectionId = d.electionId;
+ this.selectedElection = null;
this.form = {
electionId: d.electionId,
candidateId: d.candidateId,
@@ -221,6 +258,12 @@ export class DirectorBoardComponent implements OnInit {
};
this.formStep = 2;
this.showForm = true;
+ this.service.getElection(d.electionId).subscribe((election: any) => {
+ this.selectedElection = election;
+ if (election?.durationEndDate && !this.form.tenureEndDate) {
+ this.form.tenureEndDate = new Date(election.durationEndDate);
+ }
+ });
}
closeForm(): void {
@@ -229,6 +272,7 @@ export class DirectorBoardComponent implements OnInit {
this.form = this.emptyForm();
this.formStep = 1;
this.submitted = false;
+ this.selectedElection = null;
}
save(): void {
@@ -273,20 +317,29 @@ export class DirectorBoardComponent implements OnInit {
});
}
- confirmDelete(id: number): void {
- if (!confirm('Remove this director appointment?')) return;
- this.deleting = id;
- this.service.deleteDirector(id).subscribe({
- next: () => {
- this.deleting = null;
- this.load();
- this.snackBar.open('Appointment removed.', 'Close', { duration: 3000 });
- },
- error: () => {
- this.deleting = null;
- this.snackBar.open('Failed to remove appointment.', 'Close', { duration: 4000 });
- }
- });
+ confirmRemove(d: any): void {
+ if (!confirm(`Mark ${d.candidateName} as Removed? This will update their status but keep the record for audit.`))
+ return;
+ this.deleting = d.id;
+ this.service
+ .updateDirector(d.id, {
+ ...d,
+ status: 'REMOVED',
+ appointmentDate: d.appointmentDate,
+ tenureStartDate: d.tenureStartDate,
+ tenureEndDate: d.tenureEndDate
+ })
+ .subscribe({
+ next: () => {
+ this.deleting = null;
+ this.load();
+ this.snackBar.open('Director marked as Removed.', 'Close', { duration: 3000 });
+ },
+ error: () => {
+ this.deleting = null;
+ this.snackBar.open('Failed to remove director.', 'Close', { duration: 4000 });
+ }
+ });
}
get totalPages(): number {
diff --git a/src/app/election/components/election-config/election-config.component.html b/src/app/election/components/election-config/election-config.component.html
index 8de0e4557..d31aca9a3 100644
--- a/src/app/election/components/election-config/election-config.component.html
+++ b/src/app/election/components/election-config/election-config.component.html
@@ -91,6 +91,12 @@
Must be after Election Date
+
+
+ Duration (Years)
+
+ Elected board tenure. No new election until this period expires.
+
@@ -190,6 +196,12 @@
{{ basicForm.value.resultDate ? (basicForm.value.resultDate | date: 'dd MMM yyyy') : '—' }}
+ @if (basicForm.value.durationYears) {
+
+ Duration
+ {{ basicForm.value.durationYears }} year(s)
+
+ }
@if (basicForm.value.description) {
Description
diff --git a/src/app/election/components/election-config/election-config.component.ts b/src/app/election/components/election-config/election-config.component.ts
index 53a6c1c2d..6005cd157 100644
--- a/src/app/election/components/election-config/election-config.component.ts
+++ b/src/app/election/components/election-config/election-config.component.ts
@@ -94,7 +94,8 @@ export class ElectionConfigComponent implements OnInit {
null,
Validators.required
],
- resultDate: [null]
+ resultDate: [null],
+ durationYears: [null]
},
{ validators: this.dateOrderValidator() }
);
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 e35347808..55f36739a 100644
--- a/src/app/election/components/election-dashboard/election-dashboard.component.html
+++ b/src/app/election/components/election-dashboard/election-dashboard.component.html
@@ -82,6 +82,15 @@
{{ election.electionDate | date: 'dd MMM yyyy' }}