From de2ad1e35ed5378c584b2d7113b746f73ed6a5e3 Mon Sep 17 00:00:00 2001 From: CarolGitonga Date: Sat, 29 Nov 2025 19:17:23 +0300 Subject: [PATCH 1/5] feat: Load RxJS streams into signals --- .../select-budget/select-budget.component.ts | 147 +++++++++++------- 1 file changed, 90 insertions(+), 57 deletions(-) diff --git a/libs/features/budgetting/budgets/src/lib/pages/select-budget/select-budget.component.ts b/libs/features/budgetting/budgets/src/lib/pages/select-budget/select-budget.component.ts index 887a1d2e..9961add6 100644 --- a/libs/features/budgetting/budgets/src/lib/pages/select-budget/select-budget.component.ts +++ b/libs/features/budgetting/budgets/src/lib/pages/select-budget/select-budget.component.ts @@ -1,108 +1,141 @@ -import { Component, OnInit, ViewChild } from '@angular/core'; + +import { Component, inject, computed, effect } from '@angular/core'; import { MatDialog } from '@angular/material/dialog'; import { cloneDeep as ___cloneDeep, flatMap as __flatMap } from 'lodash'; -import { Observable, combineLatest, map, tap } from 'rxjs'; +import { toSignal } from '@angular/core/rxjs-interop'; import { Logger } from '@iote/bricks-angular'; -import { Budget, BudgetRecord, BudgetStatus, OrgBudgetsOverview } from '@app/model/finance/planning/budgets'; +import { + Budget, + BudgetRecord, + BudgetStatus, + OrgBudgetsOverview +} from '@app/model/finance/planning/budgets'; -import { BudgetsStore, OrgBudgetsStore } from '@app/state/finance/budgetting/budgets'; +import { + BudgetsStore, + OrgBudgetsStore +} from '@app/state/finance/budgetting/budgets'; import { CreateBudgetModalComponent } from '../../components/create-budget-modal/create-budget-modal.component'; - @Component({ selector: 'app-select-budget', templateUrl: './select-budget.component.html', - styleUrls: ['./select-budget.component.scss', - '../../components/budget-view-styles.scss'], + styleUrls: [ + './select-budget.component.scss', + '../../components/budget-view-styles.scss' + ], }) -/** List of all active budgets on the system. */ -export class SelectBudgetPageComponent implements OnInit +/** List of all active budgets on the system — Signals Refactor Version */ +export class SelectBudgetPageComponent { - /** Overview which contains all budgets of an organisation */ - overview$!: Observable; - sharedBudgets$: Observable; + // ------------------------------------------- + // 1. Replace constructor DI → inject() + // ------------------------------------------- + private _orgBudgets$$ = inject(OrgBudgetsStore); + private _budgets$$ = inject(BudgetsStore); + private _dialog = inject(MatDialog); + private _logger = inject(Logger); showFilter = false; - // budgetsLoaded: boolean = false; - - allBudgets$: Observable<{overview: BudgetRecord[], budgets: any[]}>; - - constructor(private _orgBudgets$$: OrgBudgetsStore, - private _budgets$$: BudgetsStore, - private _dialog: MatDialog, - private _logger: Logger) - { } - - ngOnInit() { - this.overview$ = this._orgBudgets$$.get(); - this.sharedBudgets$ = this._budgets$$.get(); - - this.allBudgets$ = combineLatest([this.overview$, this._budgets$$.get()]) - .pipe(map(([overview, budgets]) => {return {overview: __flatMap(overview), budgets: __flatMap(budgets)}}), - map((overview) => { - const trBudgets = overview.budgets.map((budget: any) => {budget['endYear'] = budget.startYear + budget.duration - 1; return budget;}) - // this.budgetsLoaded = true; - return {overview: overview.overview, budgets: trBudgets} - })); + // ------------------------------------------- + // 2. Convert Observables → Signals + // ------------------------------------------- + overview = toSignal( + this._orgBudgets$$.get(), + { initialValue: {} as OrgBudgetsOverview } + ); + + sharedBudgets = toSignal( + this._budgets$$.get(), + { initialValue: [] } + ); + + // Raw flattened signals used to compute allBudgets + private overviewRaw = toSignal(this._orgBudgets$$.get(), { initialValue: [] }); + private budgetsRaw = toSignal(this._budgets$$.get(), { initialValue: [] }); + + // ------------------------------------------- + // 3. computed(): merge + transform overview + budgets + // ------------------------------------------- + allBudgets = computed(() => { + const overview = __flatMap(this.overviewRaw()); + const budgets = __flatMap(this.budgetsRaw()); + + const transformed = budgets.map((b: any) => ({ + ...b, + endYear: b.startYear + b.duration - 1 + })); + + return { + overview, + budgets: transformed + }; + }); + + // ------------------------------------------- + // 4. effect(): run side effects when signals change + // ------------------------------------------- + constructor() { + effect(() => { + const data = this.allBudgets(); + this._logger.log(() => `Budgets updated. Count: ${data.budgets.length}`); + }); } + // ------------------------------------------- + // 5. Existing business logic (preserved) + // ------------------------------------------- + applyFilter(event: Event) { const filterValue = (event.target as HTMLInputElement).value; - // this.dataSource.filter = filterValue.trim().toLowerCase(); + // filter logic can be implemented here } - fieldsFilter(value: (Invoice) => boolean) { - // this.filter$$.next(value); + fieldsFilter(value: (Invoice) => boolean) { + // reserved for filter functionality } - toogleFilter(value) { - // this.showFilter = value + toogleFilter(value: boolean) { + this.showFilter = value; } - openDialog(parent : Budget | false): void - { + openDialog(parent: Budget | false): void { const dialog = this._dialog.open(CreateBudgetModalComponent, { height: 'fit-content', width: '600px', - data: parent != null ? parent : false + data: parent ?? false }); dialog.afterClosed().subscribe(() => { - // Dialog after action - }) + // no follow-up action required per original code + }); } - /** - * @TODO - Review and fix - * Returns true if the budget can be activated */ + /** Whether a budget can be activated */ canPromote(record: BudgetRecord) { - // Get's set on Budget Read from user privileges and budget status. return (record.budget as any).canBeActivated; } - /** Activate budget -> Promote to be used in */ - setActive(record: BudgetRecord) - { + /** Activate a budget */ + setActive(record: BudgetRecord) { const toSave = ___cloneDeep(record.budget); - // Clean up budget record values. + // Remove temporary client-only fields delete (toSave as any).canBeActivated; delete (toSave as any).access; - // Set Active toSave.status = BudgetStatus.InUse; + (record as any).updating = true; - ( record).updating = true; - // Fire update this._budgets$$.update(toSave) .subscribe(() => { - ( record).updating = false; - this._logger.log(() => `Updated Budget with id ${toSave.id}. Set as an active budget for this org.`) + (record as any).updating = false; + this._logger.log(() => `Budget ${toSave.id} set to Active`); }); } -} \ No newline at end of file +} From 958f8f63638e4a6780a501085c1ac65d7f630896 Mon Sep 17 00:00:00 2001 From: CarolGitonga Date: Sat, 29 Nov 2025 19:24:24 +0300 Subject: [PATCH 2/5] feat: pass actual reactive Signal value --- .../lib/pages/select-budget/select-budget.component.html | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/libs/features/budgetting/budgets/src/lib/pages/select-budget/select-budget.component.html b/libs/features/budgetting/budgets/src/lib/pages/select-budget/select-budget.component.html index f291feb4..e68be595 100644 --- a/libs/features/budgetting/budgets/src/lib/pages/select-budget/select-budget.component.html +++ b/libs/features/budgetting/budgets/src/lib/pages/select-budget/select-budget.component.html @@ -13,12 +13,14 @@
- +
- + + + +
\ No newline at end of file From 68101501d45f6ba6f9dbaefca895252e63245ae1 Mon Sep 17 00:00:00 2001 From: CarolGitonga Date: Sat, 29 Nov 2025 19:31:13 +0300 Subject: [PATCH 3/5] feat: Pass signals to the child --- .../src/lib/components/budget-table/budget-table.component.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libs/features/budgetting/budgets/src/lib/components/budget-table/budget-table.component.ts b/libs/features/budgetting/budgets/src/lib/components/budget-table/budget-table.component.ts index 77d8fee7..3638b8ba 100644 --- a/libs/features/budgetting/budgets/src/lib/components/budget-table/budget-table.component.ts +++ b/libs/features/budgetting/budgets/src/lib/components/budget-table/budget-table.component.ts @@ -25,6 +25,8 @@ export class BudgetTableComponent { private _sbS = new SubSink(); @Input() budgets$: Observable<{overview: BudgetRecord[], budgets: any[]}>; + // 1. Replace budgets$ (Observable) with budgets (array) + @Input() budgets!: any[]; @Input() canPromote = false; @Output() doPromote: EventEmitter = new EventEmitter(); From afb0bdac660bb5d2a33696a7ec6fbc8352a29146 Mon Sep 17 00:00:00 2001 From: CarolGitonga Date: Sat, 29 Nov 2025 19:54:11 +0300 Subject: [PATCH 4/5] feat: Clean up the code --- .../select-budget.component.html | 2 -- .../select-budget/select-budget.component.ts | 24 +++++++------------ 2 files changed, 9 insertions(+), 17 deletions(-) diff --git a/libs/features/budgetting/budgets/src/lib/pages/select-budget/select-budget.component.html b/libs/features/budgetting/budgets/src/lib/pages/select-budget/select-budget.component.html index e68be595..0aac1ebb 100644 --- a/libs/features/budgetting/budgets/src/lib/pages/select-budget/select-budget.component.html +++ b/libs/features/budgetting/budgets/src/lib/pages/select-budget/select-budget.component.html @@ -19,8 +19,6 @@
- -
\ No newline at end of file diff --git a/libs/features/budgetting/budgets/src/lib/pages/select-budget/select-budget.component.ts b/libs/features/budgetting/budgets/src/lib/pages/select-budget/select-budget.component.ts index 9961add6..fd675bd8 100644 --- a/libs/features/budgetting/budgets/src/lib/pages/select-budget/select-budget.component.ts +++ b/libs/features/budgetting/budgets/src/lib/pages/select-budget/select-budget.component.ts @@ -32,9 +32,8 @@ import { CreateBudgetModalComponent } from '../../components/create-budget-modal /** List of all active budgets on the system — Signals Refactor Version */ export class SelectBudgetPageComponent { - // ------------------------------------------- - // 1. Replace constructor DI → inject() - // ------------------------------------------- + + //Replace constructor DI → inject() private _orgBudgets$$ = inject(OrgBudgetsStore); private _budgets$$ = inject(BudgetsStore); private _dialog = inject(MatDialog); @@ -42,9 +41,8 @@ export class SelectBudgetPageComponent showFilter = false; - // ------------------------------------------- - // 2. Convert Observables → Signals - // ------------------------------------------- + + // Convert Observables → Signals overview = toSignal( this._orgBudgets$$.get(), { initialValue: {} as OrgBudgetsOverview } @@ -58,10 +56,9 @@ export class SelectBudgetPageComponent // Raw flattened signals used to compute allBudgets private overviewRaw = toSignal(this._orgBudgets$$.get(), { initialValue: [] }); private budgetsRaw = toSignal(this._budgets$$.get(), { initialValue: [] }); - - // ------------------------------------------- - // 3. computed(): merge + transform overview + budgets - // ------------------------------------------- + + // computed(): merge + transform overview + budgets + allBudgets = computed(() => { const overview = __flatMap(this.overviewRaw()); const budgets = __flatMap(this.budgetsRaw()); @@ -77,9 +74,8 @@ export class SelectBudgetPageComponent }; }); - // ------------------------------------------- - // 4. effect(): run side effects when signals change - // ------------------------------------------- + + // effect(): run side effects when signals change constructor() { effect(() => { const data = this.allBudgets(); @@ -87,9 +83,7 @@ export class SelectBudgetPageComponent }); } - // ------------------------------------------- // 5. Existing business logic (preserved) - // ------------------------------------------- applyFilter(event: Event) { const filterValue = (event.target as HTMLInputElement).value; From 3f279c106a64cdb362003edd435afa2060d052cc Mon Sep 17 00:00:00 2001 From: CarolGitonga Date: Fri, 20 Feb 2026 15:46:37 +0300 Subject: [PATCH 5/5] Refactor SelectBudget and BudgetTable to Angular Signals --- .../budget-table/budget-table.component.ts | 126 ++++++++++-------- .../select-budget.component.html | 6 +- .../select-budget/select-budget.component.ts | 119 ++++++++--------- 3 files changed, 125 insertions(+), 126 deletions(-) diff --git a/libs/features/budgetting/budgets/src/lib/components/budget-table/budget-table.component.ts b/libs/features/budgetting/budgets/src/lib/components/budget-table/budget-table.component.ts index 3638b8ba..5bfcd36a 100644 --- a/libs/features/budgetting/budgets/src/lib/components/budget-table/budget-table.component.ts +++ b/libs/features/budgetting/budgets/src/lib/components/budget-table/budget-table.component.ts @@ -1,13 +1,24 @@ -import { Component, EventEmitter, Input, Output, ViewChild } from '@angular/core'; -import { MatTable, MatTableDataSource } from '@angular/material/table'; +// NOTE: This component requires Angular 17+. +// input(), output(), effect(), computed() and ChangeDetectionStrategy.OnPush +// are Angular 17 features used here for signal-based reactivity and zoneless support. + +import { + Component, + AfterViewInit, + ViewChild, + inject, + effect, + computed, + input, + output, + ChangeDetectionStrategy, +} from '@angular/core'; +import { MatTableDataSource } from '@angular/material/table'; import { MatPaginator } from '@angular/material/paginator'; import { MatDialog } from '@angular/material/dialog'; import { MatSort } from '@angular/material/sort'; import { Router } from '@angular/router'; -import { SubSink } from 'subsink'; -import { Observable, tap } from 'rxjs'; - import { Budget, BudgetRecord } from '@app/model/finance/planning/budgets'; import { ShareBudgetModalComponent } from '../share-budget-modal/share-budget-modal.component'; @@ -18,52 +29,56 @@ import { ChildBudgetsModalComponent } from '../../modals/child-budgets-modal/chi selector: 'app-budget-table', templateUrl: './budget-table.component.html', styleUrls: ['./budget-table.component.scss'], + // OnPush: Angular only checks this component when its signal inputs change. + // With provideExperimentalZonelessChangeDetection() in root providers this + // becomes a fully zoneless component — no Zone.js ticking required. + changeDetection: ChangeDetectionStrategy.OnPush, }) +export class BudgetTableComponent implements AfterViewInit { -export class BudgetTableComponent { - - private _sbS = new SubSink(); + // --- inject() replaces constructor parameters --- + private _router$$ = inject(Router); + private _dialog = inject(MatDialog); - @Input() budgets$: Observable<{overview: BudgetRecord[], budgets: any[]}>; - // 1. Replace budgets$ (Observable) with budgets (array) - @Input() budgets!: any[]; - @Input() canPromote = false; + // --- Signal-based inputs replace @Input() + Observable pattern (Angular 17) --- + // The parent binds plain values; Angular wraps them into signals automatically. + // Child reads them with () — e.g. this.budgets() + budgets = input<{ overview: BudgetRecord[]; budgets: any[] }>(); + canPromote = input(false); - @Output() doPromote: EventEmitter = new EventEmitter(); - - dataSource = new MatTableDataSource(); + // --- Signal-based output replaces @Output() + EventEmitter (Angular 17) --- + doPromote = output(); + readonly dataSource = new MatTableDataSource(); displayedColumns: string[] = ['name', 'status', 'startYear', 'duration', 'actions']; @ViewChild(MatPaginator) paginator: MatPaginator; @ViewChild('sort', { static: true }) sort: MatSort; - overviewBudgets: BudgetRecord[] = []; - - constructor(private _router$$: Router, - private _dialog: MatDialog, - ) { } + // --- computed() replaces the plain overviewBudgets class field --- + // Recalculates automatically whenever the budgets input signal changes. + overviewBudgets = computed(() => this.budgets()?.overview ?? []); - ngOnInit(): void { - this._sbS.sink = this.budgets$.pipe(tap((o) => { - this.overviewBudgets = o.overview; - this.dataSource.data = o.budgets; - })).subscribe(); + constructor() { + // --- effect() replaces ngOnInit + SubSink subscription --- + // Runs reactively whenever the budgets input signal emits a new value. + // Keeps the imperative MatTableDataSource in sync with signal state. + effect(() => { + this.dataSource.data = this.budgets()?.budgets ?? []; + }); } - /** - * Checks whether the user has access to a certain feature. - * - * @TODO @IanOdhiambo9 - Please put proper access control architecture in place. - */ - access(requested:any) - { + /** + * Checks whether the user has access to a certain feature. + * @TODO @IanOdhiambo9 - Please put proper access control architecture in place. + */ + access(requested: any) { switch (requested) { case 'view': case 'clone': - return true; //budget.access.owner || budget.access.view || budget.access.edit; + return true; case 'edit': - return true; // (budget.access.owner || budget.access.edit) && budget.status !== BudgetStatus.InUse && budget.status !== BudgetStatus.InUse; + return true; } return false; } @@ -83,17 +98,18 @@ export class BudgetTableComponent { } promote() { - if (this.canPromote) + // Read signal value with () instead of plain property access + if (this.canPromote()) { this.doPromote.emit(); + } } /** Open share screen to configure budget access. */ - openShareBudgetDialog(parent: Budget | false): void - { + openShareBudgetDialog(parent: Budget | false): void { this._dialog.open(ShareBudgetModalComponent, { panelClass: 'no-pad-dialog', width: '600px', - data: parent != null ? parent : false + data: parent ?? false, }); } @@ -102,18 +118,21 @@ export class BudgetTableComponent { this._dialog.open(CreateBudgetModalComponent, { height: 'fit-content', width: '600px', - data: parent != null ? parent : false + data: parent ?? false, }); } - openChildBudgetDialog(parent : Budget): void - { - let children: any = this.overviewBudgets.find((budget) => budget.budget.id === parent.id)!?.children; - children = children?.map((child) => child.budget) + openChildBudgetDialog(parent: Budget): void { + // Read computed signal with () — replaces plain array access + let children: any = this.overviewBudgets().find( + (budget) => budget.budget.id === parent.id + )?.children; + children = children?.map((child: any) => child.budget); + this._dialog.open(ChildBudgetsModalComponent, { height: 'fit-content', minWidth: '600px', - data: {parent: parent, budgets: children} + data: { parent, budgets: children }, }); } @@ -121,22 +140,17 @@ export class BudgetTableComponent { this._router$$.navigate(['budgets', budgetId, action]).then(() => this._dialog.closeAll()); } - deleteBudget(budget: Budget) { - + deleteBudget(_budget: Budget) { + // TODO: implement delete } translateStatus(status: number) { switch (status) { - case 1: - return 'BUDGET.STATUS.ACTIVE'; - case 0: - return 'BUDGET.STATUS.DESIGN'; - case 9: - return 'BUDGET.STATUS.NO-USE'; - case -1: - return 'BUDGET.STATUS.DELETED'; - default: - return ''; + case 1: return 'BUDGET.STATUS.ACTIVE'; + case 0: return 'BUDGET.STATUS.DESIGN'; + case 9: return 'BUDGET.STATUS.NO-USE'; + case -1: return 'BUDGET.STATUS.DELETED'; + default: return ''; } } } diff --git a/libs/features/budgetting/budgets/src/lib/pages/select-budget/select-budget.component.html b/libs/features/budgetting/budgets/src/lib/pages/select-budget/select-budget.component.html index 0aac1ebb..3bb1d90c 100644 --- a/libs/features/budgetting/budgets/src/lib/pages/select-budget/select-budget.component.html +++ b/libs/features/budgetting/budgets/src/lib/pages/select-budget/select-budget.component.html @@ -12,13 +12,13 @@ -
+
- - + +
\ No newline at end of file diff --git a/libs/features/budgetting/budgets/src/lib/pages/select-budget/select-budget.component.ts b/libs/features/budgetting/budgets/src/lib/pages/select-budget/select-budget.component.ts index fd675bd8..ff338ef3 100644 --- a/libs/features/budgetting/budgets/src/lib/pages/select-budget/select-budget.component.ts +++ b/libs/features/budgetting/budgets/src/lib/pages/select-budget/select-budget.component.ts @@ -1,26 +1,23 @@ +// NOTE: This component requires Angular 17+. +// signal(), computed(), effect(), toSignal(), inject() and ChangeDetectionStrategy.OnPush +// are Angular 16/17 features. +// To enable fully zoneless change detection, add provideExperimentalZonelessChangeDetection() +// to the root providers in app.module.ts and remove zone.js from polyfills. -import { Component, inject, computed, effect } from '@angular/core'; +import { Component, inject, signal, computed, effect, ChangeDetectionStrategy } from '@angular/core'; +import { toSignal } from '@angular/core/rxjs-interop'; import { MatDialog } from '@angular/material/dialog'; import { cloneDeep as ___cloneDeep, flatMap as __flatMap } from 'lodash'; -import { toSignal } from '@angular/core/rxjs-interop'; import { Logger } from '@iote/bricks-angular'; -import { - Budget, - BudgetRecord, - BudgetStatus, - OrgBudgetsOverview -} from '@app/model/finance/planning/budgets'; - -import { - BudgetsStore, - OrgBudgetsStore -} from '@app/state/finance/budgetting/budgets'; +import { Budget, BudgetRecord, BudgetStatus, OrgBudgetsOverview } from '@app/model/finance/planning/budgets'; +import { BudgetsStore, OrgBudgetsStore } from '@app/state/finance/budgetting/budgets'; import { CreateBudgetModalComponent } from '../../components/create-budget-modal/create-budget-modal.component'; + @Component({ selector: 'app-select-budget', templateUrl: './select-budget.component.html', @@ -28,108 +25,96 @@ import { CreateBudgetModalComponent } from '../../components/create-budget-modal './select-budget.component.scss', '../../components/budget-view-styles.scss' ], + // OnPush: Angular only re-checks this component when its signals change. + // Combined with provideExperimentalZonelessChangeDetection() in root providers + // this makes the component fully "zoneless" — no Zone.js ticking required. + changeDetection: ChangeDetectionStrategy.OnPush, }) -/** List of all active budgets on the system — Signals Refactor Version */ +/** List of all active budgets on the system — Signals Refactor */ export class SelectBudgetPageComponent { - - //Replace constructor DI → inject() + // --- inject() replaces constructor parameters --- private _orgBudgets$$ = inject(OrgBudgetsStore); - private _budgets$$ = inject(BudgetsStore); - private _dialog = inject(MatDialog); - private _logger = inject(Logger); - - showFilter = false; - - - // Convert Observables → Signals - overview = toSignal( - this._orgBudgets$$.get(), - { initialValue: {} as OrgBudgetsOverview } - ); - - sharedBudgets = toSignal( - this._budgets$$.get(), - { initialValue: [] } - ); - - // Raw flattened signals used to compute allBudgets - private overviewRaw = toSignal(this._orgBudgets$$.get(), { initialValue: [] }); + private _budgets$$ = inject(BudgetsStore); + private _dialog = inject(MatDialog); + private _logger = inject(Logger); + + // --- UI state as a signal instead of a plain boolean --- + showFilter = signal(false); + + // --- Convert RxJS observables → signals via toSignal() --- + // toSignal subscribes automatically and keeps the signal in sync. + // initialValue is returned until the first observable emission. + private overviewRaw = toSignal(this._orgBudgets$$.get(), { initialValue: {} as OrgBudgetsOverview }); private budgetsRaw = toSignal(this._budgets$$.get(), { initialValue: [] }); - - // computed(): merge + transform overview + budgets - + + // --- computed() replaces combineLatest + map pipeline --- + // Recalculates automatically whenever overviewRaw or budgetsRaw change. allBudgets = computed(() => { - const overview = __flatMap(this.overviewRaw()); - const budgets = __flatMap(this.budgetsRaw()); + const overview = __flatMap(this.overviewRaw()) as BudgetRecord[]; + const budgets = __flatMap(this.budgetsRaw()); - const transformed = budgets.map((b: any) => ({ + const enriched = budgets.map((b: any) => ({ ...b, - endYear: b.startYear + b.duration - 1 + endYear: b.startYear + b.duration - 1, })); - return { - overview, - budgets: transformed - }; + return { overview, budgets: enriched }; }); - - // effect(): run side effects when signals change constructor() { + // --- effect() replaces imperative subscribe() side effects --- + // Runs reactively whenever allBudgets signal changes. effect(() => { const data = this.allBudgets(); - this._logger.log(() => `Budgets updated. Count: ${data.budgets.length}`); + this._logger.log(() => `[SelectBudgetPage] Budgets updated. Count: ${data.budgets.length}`); }); } - // 5. Existing business logic (preserved) - applyFilter(event: Event) { - const filterValue = (event.target as HTMLInputElement).value; - // filter logic can be implemented here + const _filterValue = (event.target as HTMLInputElement).value; + // TODO: wire up table filter } - fieldsFilter(value: (Invoice) => boolean) { - // reserved for filter functionality + fieldsFilter(_value: (invoice: any) => boolean) { + // TODO: wire up field-level filter } toogleFilter(value: boolean) { - this.showFilter = value; + // .set() mutates the signal — triggers OnPush change detection automatically + this.showFilter.set(value); } openDialog(parent: Budget | false): void { const dialog = this._dialog.open(CreateBudgetModalComponent, { height: 'fit-content', width: '600px', - data: parent ?? false + data: parent ?? false, }); dialog.afterClosed().subscribe(() => { - // no follow-up action required per original code + // Dialog after action }); } - /** Whether a budget can be activated */ + /** @TODO - Review and fix. Returns true if the budget can be activated. */ canPromote(record: BudgetRecord) { return (record.budget as any).canBeActivated; } - /** Activate a budget */ + /** Activate budget — promote to be used in production. */ setActive(record: BudgetRecord) { const toSave = ___cloneDeep(record.budget); - // Remove temporary client-only fields delete (toSave as any).canBeActivated; delete (toSave as any).access; toSave.status = BudgetStatus.InUse; (record as any).updating = true; - this._budgets$$.update(toSave) - .subscribe(() => { - (record as any).updating = false; - this._logger.log(() => `Budget ${toSave.id} set to Active`); - }); + this._budgets$$.update(toSave).subscribe(() => { + (record as any).updating = false; + this._logger.log(() => `Budget ${toSave.id} set to Active.`); + }); } }