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..c7fa36c6 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,10 @@ -import { Component, EventEmitter, Input, Output, ViewChild } from '@angular/core'; +import { Component, EventEmitter, input, Output, ViewChild, effect, inject, signal } from '@angular/core'; import { MatTable, 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'; @@ -22,10 +19,15 @@ import { ChildBudgetsModalComponent } from '../../modals/child-budgets-modal/chi export class BudgetTableComponent { - private _sbS = new SubSink(); + // REFACTORED: Use inject() instead of constructor injection + private _router$$ = inject(Router); + private _dialog = inject(MatDialog); - @Input() budgets$: Observable<{overview: BudgetRecord[], budgets: any[]}>; - @Input() canPromote = false; + // REFACTORED: Use signal-based input instead of @Input() with Observable + budgets$ = input<{overview: BudgetRecord[], budgets: any[]}>({ overview: [], budgets: [] }); + + // REFACTORED: Convert @Input to signal-based input + canPromote = input(false); @Output() doPromote: EventEmitter = new EventEmitter(); @@ -36,17 +38,17 @@ export class BudgetTableComponent { @ViewChild(MatPaginator) paginator: MatPaginator; @ViewChild('sort', { static: true }) sort: MatSort; - overviewBudgets: BudgetRecord[] = []; - - constructor(private _router$$: Router, - private _dialog: MatDialog, - ) { } + // REFACTORED: Use signal for overviewBudgets + overviewBudgets = signal([]); - ngOnInit(): void { - this._sbS.sink = this.budgets$.pipe(tap((o) => { - this.overviewBudgets = o.overview; - this.dataSource.data = o.budgets; - })).subscribe(); + // REFACTORED: Use effect instead of ngOnInit subscription + constructor() { + // Effect runs automatically when budgets$ signal changes + effect(() => { + const budgetsData = this.budgets$(); + this.overviewBudgets.set(budgetsData.overview); + this.dataSource.data = budgetsData.budgets; + }); } /** @@ -54,7 +56,7 @@ export class BudgetTableComponent { * * @TODO @IanOdhiambo9 - Please put proper access control architecture in place. */ - access(requested:any) + access(requested: any) { switch (requested) { case 'view': @@ -81,7 +83,7 @@ export class BudgetTableComponent { } promote() { - if (this.canPromote) + if (this.canPromote()) this.doPromote.emit(); } @@ -104,9 +106,10 @@ export class BudgetTableComponent { }); } - openChildBudgetDialog(parent : Budget): void + openChildBudgetDialog(parent: Budget): void { - let children: any = this.overviewBudgets.find((budget) => budget.budget.id === parent.id)!?.children; + // REFACTORED: Access signal value with () + let children: any = this.overviewBudgets().find((budget) => budget.budget.id === parent.id)!?.children; children = children?.map((child) => child.budget) this._dialog.open(ChildBudgetsModalComponent, { height: 'fit-content', @@ -137,4 +140,4 @@ export class BudgetTableComponent { return ''; } } -} +} \ No newline at end of file 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..eeeae9e1 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 887a1d2e..42bef98f 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,4 +1,5 @@ -import { Component, OnInit, ViewChild } from '@angular/core'; +import { Component, OnInit, ViewChild, signal, computed, effect, inject } 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'; @@ -22,33 +23,71 @@ import { CreateBudgetModalComponent } from '../../components/create-budget-modal /** List of all active budgets on the system. */ export class SelectBudgetPageComponent implements OnInit { - /** Overview which contains all budgets of an organisation */ - overview$!: Observable; - sharedBudgets$: Observable; - - showFilter = false; + // REFACTORED: Use inject() instead of constructor injection + private _orgBudgets$$ = inject(OrgBudgetsStore); + private _budgets$$ = inject(BudgetsStore); + private _dialog = inject(MatDialog); + private _logger = inject(Logger); - // budgetsLoaded: boolean = false; - - allBudgets$: Observable<{overview: BudgetRecord[], budgets: any[]}>; + // REFACTORED: Convert Observables to Signals using toSignal() + /** Overview which contains all budgets of an organisation */ + overview = toSignal(this._orgBudgets$$.get(), { + initialValue: null as OrgBudgetsOverview | null + }); + + sharedBudgets = toSignal(this._budgets$$.get(), { + initialValue: [] as any[] + }); + + // REFACTORED: Convert combined observable to signal + allBudgets = toSignal( + combineLatest([this._orgBudgets$$.get(), 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; + }); + return { + overview: overview.overview, + budgets: trBudgets + } + }) + ), + { + initialValue: { overview: [], budgets: [] } + } + ); + + // REFACTORED: Convert to signal + showFilter = signal(false); + + // REFACTORED: Use effects instead of subscriptions + constructor() { + // Effect for logging when overview loads + effect(() => { + const overview = this.overview(); + if (overview) { + this._logger.log(() => `Overview loaded with data`); + } + }); - constructor(private _orgBudgets$$: OrgBudgetsStore, - private _budgets$$: BudgetsStore, - private _dialog: MatDialog, - private _logger: Logger) - { } + // Effect for logging when budgets change + effect(() => { + const budgets = this.allBudgets(); + this._logger.log(() => `All budgets updated. Count: ${budgets.budgets.length}`); + }); + } 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} - })); + // REFACTORED: No longer need to manually subscribe - signals handle this automatically + // The toSignal() calls in the property declarations handle the subscriptions } applyFilter(event: Event) { @@ -60,11 +99,12 @@ export class SelectBudgetPageComponent implements OnInit // this.filter$$.next(value); } - toogleFilter(value) { - // this.showFilter = value + toogleFilter(value: boolean) { + // REFACTORED: Use signal.set() instead of direct assignment + this.showFilter.set(value); } - openDialog(parent : Budget | false): void + openDialog(parent: Budget | false): void { const dialog = this._dialog.open(CreateBudgetModalComponent, { height: 'fit-content',