From a1242de38fb8ad277b5c1a71a64ef303759ec91b Mon Sep 17 00:00:00 2001 From: FLAMES39 Date: Sat, 29 Nov 2025 18:11:46 +0300 Subject: [PATCH] Refactor budgeting state to Angular Signals --- .../budget-table/budget-table.component.html | 6 +- .../budget-table/budget-table.component.ts | 67 +++++------ .../select-budget.component.html | 2 +- .../select-budget/select-budget.component.ts | 111 +++++++----------- 4 files changed, 80 insertions(+), 106 deletions(-) diff --git a/libs/features/budgetting/budgets/src/lib/components/budget-table/budget-table.component.html b/libs/features/budgetting/budgets/src/lib/components/budget-table/budget-table.component.html index 2b16fdf6..17b240cf 100644 --- a/libs/features/budgetting/budgets/src/lib/components/budget-table/budget-table.component.html +++ b/libs/features/budgetting/budgets/src/lib/components/budget-table/budget-table.component.html @@ -1,10 +1,10 @@ - +
@@ -74,4 +74,4 @@
Budget Name - {{row.name ? row.name : '-' }} Linked Budgets ({{ row?.childrenList?.length }})
- \ No newline at end of file + \ No newline at end of file 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..535e8475 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, inject, signal,effect,Input, Output, ViewChild } 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,38 +19,48 @@ import { ChildBudgetsModalComponent } from '../../modals/child-budgets-modal/chi export class BudgetTableComponent { - private _sbS = new SubSink(); + // private _sbS = new SubSink(); - @Input() budgets$: Observable<{overview: BudgetRecord[], budgets: any[]}>; @Input() canPromote = false; + @Input({required:true }) budgets!: signal<{overview: BudgetRecord[], budgets: any[]}>; - @Output() doPromote: EventEmitter = new EventEmitter(); + @Output() doPromote = new EventEmitter(); dataSource = new MatTableDataSource(); displayedColumns: string[] = ['name', 'status', 'startYear', 'duration', 'actions']; - @ViewChild(MatPaginator) paginator: MatPaginator; - @ViewChild('sort', { static: true }) sort: MatSort; + @ViewChild(MatPaginator) paginator!: MatPaginator; + @ViewChild('MatSort', { static: true }) sort: MatSort; overviewBudgets: BudgetRecord[] = []; - constructor(private _router$$: Router, - private _dialog: MatDialog, - ) { } - - ngOnInit(): void { - this._sbS.sink = this.budgets$.pipe(tap((o) => { - this.overviewBudgets = o.overview; - this.dataSource.data = o.budgets; - })).subscribe(); + // constructor(private _router$$: Router, + // private _dialog: MatDialog, + // ) { } + + private _router = inject(Router); + private _dialog = inject(MatDialog); + +constructor(){ + + effect(() =>{ + const incomingBudgets = this.budgets(); + this.overviewBudgets = incomingBudgets.overview; + this.dataSource.data = incomingBudgets.budgets; + if (this.paginator) { + this.dataSource.paginator = this.paginator; + } + if (this.sort) { + this.dataSource.sort = this.sort; + } + }); +} + ngAfterViewInit(): void { + this.dataSource.paginator = this.paginator; + this.dataSource.sort = this.sort; } - /** - * 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) { @@ -66,10 +73,7 @@ export class BudgetTableComponent { return false; } - ngAfterViewInit(): void { - this.dataSource.paginator = this.paginator; - this.dataSource.sort = this.sort; - } + filterAccountRecords(event: Event) { const filterValue = (event.target as HTMLInputElement).value; @@ -106,8 +110,7 @@ export class BudgetTableComponent { openChildBudgetDialog(parent : Budget): void { - let children: any = this.overviewBudgets.find((budget) => budget.budget.id === parent.id)!?.children; - children = children?.map((child) => child.budget) + let children: any = this.overviewBudgets.find((budget) => budget.budget.id === parent.id)?.children?.map((child) => child.budget); this._dialog.open(ChildBudgetsModalComponent, { height: 'fit-content', minWidth: '600px', @@ -116,12 +119,10 @@ export class BudgetTableComponent { } goToDetail(budgetId: string, action: string) { - this._router$$.navigate(['budgets', budgetId, action]).then(() => this._dialog.closeAll()); + this._router.navigate(['budgets', budgetId, action]).then(() => this._dialog.closeAll()); } - deleteBudget(budget: Budget) { - - } + deleteBudget(budget: Budget) { } translateStatus(status: number) { switch (status) { 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..d851b887 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 @@ -18,7 +18,7 @@
- +
\ 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..5cddaa2d 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,15 +1,11 @@ -import { Component, OnInit, ViewChild } from '@angular/core'; +import { Component, inject,signal , computed, toSignal } 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 { Logger } from '@iote/bricks-angular'; -import { Budget, BudgetRecord, BudgetStatus, OrgBudgetsOverview } from '@app/model/finance/planning/budgets'; +import { Budget, BudgetRecord, BudgetStatus } 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'; @@ -19,90 +15,67 @@ import { CreateBudgetModalComponent } from '../../components/create-budget-modal styleUrls: ['./select-budget.component.scss', '../../components/budget-view-styles.scss'], }) -/** 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; - - // 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} - })); - } - applyFilter(event: Event) { - const filterValue = (event.target as HTMLInputElement).value; - // this.dataSource.filter = filterValue.trim().toLowerCase(); - } +export class SelectBudgetPageComponent{ - fieldsFilter(value: (Invoice) => boolean) { - // this.filter$$.next(value); - } + showFilter = signal(false); - toogleFilter(value) { - // this.showFilter = value - } + private _orgBudgets = inject(OrgBudgetsStore); + private _budgets$$ = inject(BudgetsStore); + private _dialog = inject(MatDialog); + private _logger = inject(Logger); + + // Convert observables to signals + overview = toSignal(this._orgBudgets.get(), { initialValue: [] }); + sharedBudgets = toSignal(this._budgets$$.get(), { initialValue: [] }); + + allBudgets = computed(() => { + const overview = __flatMap(this.overview()); + const budgets = __flatMap(this.sharedBudgets()).map((budget: any) => { + budget['endYear'] = budget.startYear + budget.duration - 1; + return budget; + }); + return {overview, budgets}; + }); + + // constructor() { } - openDialog(parent : Budget | false): void - { - const dialog = this._dialog.open(CreateBudgetModalComponent, { + openDialog(parent: Budget | false): void { + this._dialog.open(CreateBudgetModalComponent, { height: 'fit-content', width: '600px', data: parent != null ? parent : false }); - - dialog.afterClosed().subscribe(() => { - // Dialog after action - }) } - /** - * @TODO - Review and fix - * Returns true if the 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) - { - const toSave = ___cloneDeep(record.budget); + applyFilter(event: Event) { + const filterValue = (event.target as HTMLInputElement).value; + // this.dataSource.filter = filterValue.trim().toLowerCase(); + } + + fieldsFilter(value: (x: any) => boolean) { + // this.filter$$.next(value); + } + + toogleFilter(value: boolean) { + this.showFilter.set(value); + } - // Clean up budget record values. + setActive(record: BudgetRecord) { + const toSave = ___cloneDeep(record.budget); delete (toSave as any).canBeActivated; delete (toSave as any).access; - - // Set Active toSave.status = BudgetStatus.InUse; - - ( record).updating = true; - // Fire update + (record as any).updating = true; 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(() => `Updated Budget with id ${toSave.id}. Set as an active budget for this org.`); }); } } \ No newline at end of file