From 9002e07c4dcf187b07ad1266b5350da230b90c97 Mon Sep 17 00:00:00 2001 From: Eliud Metto Date: Sat, 29 Nov 2025 19:17:30 +0300 Subject: [PATCH] Added a new add note feature --- .../budget-table/budget-table.component.html | 97 +++++++++---- .../select-budget/select-budget.component.ts | 134 ++++++++++-------- .../src/lib/domain/add-note-result.ts | 5 + .../src/lib/domain/add-note.command.ts | 8 ++ .../src/lib/domain/add-note.handler.ts | 38 +++++ .../src/lib/domain/command.interface.ts | 3 + .../src/lib/domain/function-handler.ts | 5 + 7 files changed, 203 insertions(+), 87 deletions(-) create mode 100644 libs/model/budgetting/budget-notes/src/lib/domain/add-note-result.ts create mode 100644 libs/model/budgetting/budget-notes/src/lib/domain/add-note.command.ts create mode 100644 libs/model/budgetting/budget-notes/src/lib/domain/add-note.handler.ts create mode 100644 libs/model/budgetting/budget-notes/src/lib/domain/command.interface.ts create mode 100644 libs/model/budgetting/budget-notes/src/lib/domain/function-handler.ts 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..5c228e27 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,55 +1,92 @@ - - +
- + - + - - + + - - + + - + - +
Budget Name Budget Name - {{row.name ? row.name : '-' }} Linked Budgets ({{ row?.childrenList?.length }}) + {{ row.name ? row.name : '-' }} + + Linked Budgets ({{ row?.childrenList?.length }}) + Status Status - - {{translateStatus(row.status) | transloco}} + + {{ translateStatus(row.status) | transloco }} Start Year {{row.startYear ? row.startYear : '-'}} Start Year + {{ row.startYear ? row.startYear : '-' }} + Duration (Years) {{row.duration ? row.duration : '-' }} Duration (Years) + {{ row.duration ? row.duration : '-' }} + Actions Actions -
- - - - @@ -59,8 +96,13 @@ --> - @@ -72,6 +114,9 @@
- \ 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..099851a8 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,120 @@ -import { Component, OnInit, ViewChild } from '@angular/core'; +import { Component, OnInit, signal, computed, effect } from '@angular/core'; import { MatDialog } from '@angular/material/dialog'; +import { toSignal } from '@angular/core/rxjs-interop'; 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, + 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 -{ - /** Overview which contains all budgets of an organisation */ - overview$!: Observable; - sharedBudgets$: Observable; +export class SelectBudgetPageComponent implements OnInit { + overview = signal(null); + sharedBudgets = signal([]); + + showFilter = signal(false); + + allBudgets = computed(() => { + const o = this.overview(); + const b = this.sharedBudgets(); - showFilter = false; + if (!o) return { overview: [], budgets: [] }; - // budgetsLoaded: boolean = false; + const flatOverview = __flatMap(o); + const flatBudgets = __flatMap(b); - allBudgets$: Observable<{overview: BudgetRecord[], budgets: any[]}>; + const transformed = flatBudgets.map((budget) => ({ + ...budget, + endYear: budget.startYear + budget.duration - 1, + })); - constructor(private _orgBudgets$$: OrgBudgetsStore, - private _budgets$$: BudgetsStore, - private _dialog: MatDialog, - private _logger: Logger) - { } + return { + overview: flatOverview, + budgets: transformed, + }; + }); + + constructor( + private orgBudgetsStore: OrgBudgetsStore, + private budgetsStore: 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} - })); + const org$ = toSignal(this.orgBudgetsStore.get(), { + initialValue: null, + }); + + const shared$ = toSignal(this.budgetsStore.get(), { + initialValue: [], + }); + + effect(() => { + this.overview.set(org$()); + this.sharedBudgets.set(shared$()); + }); } applyFilter(event: Event) { - const filterValue = (event.target as HTMLInputElement).value; - // this.dataSource.filter = filterValue.trim().toLowerCase(); + const filterValue = (event.target as HTMLInputElement).value; // this.dataSource.filter = filterValue.trim().toLowerCase(); } - fieldsFilter(value: (Invoice) => boolean) { + fieldsFilter(value: (Invoice) => boolean) { // this.filter$$.next(value); } - toogleFilter(value) { - // this.showFilter = value + toogleFilter(value: boolean) { + this.showFilter.set(value); } - 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 + data: 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) - { + setActive(record: BudgetRecord) { const toSave = ___cloneDeep(record.budget); - // Clean up budget record values. delete (toSave as any).canBeActivated; delete (toSave as any).access; - // Set Active toSave.status = BudgetStatus.InUse; - ( 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 = true; + + this.budgetsStore.update(toSave).subscribe(() => { + (record as any).updating = false; + this.logger.log( + () => `Updated Budget ${toSave.id}. Set as active for this org.` + ); + }); } -} \ No newline at end of file +} diff --git a/libs/model/budgetting/budget-notes/src/lib/domain/add-note-result.ts b/libs/model/budgetting/budget-notes/src/lib/domain/add-note-result.ts new file mode 100644 index 00000000..1524169a --- /dev/null +++ b/libs/model/budgetting/budget-notes/src/lib/domain/add-note-result.ts @@ -0,0 +1,5 @@ +export interface AddNoteToBudgetResult { + success: boolean; + noteId?: string; + message?: string; +} diff --git a/libs/model/budgetting/budget-notes/src/lib/domain/add-note.command.ts b/libs/model/budgetting/budget-notes/src/lib/domain/add-note.command.ts new file mode 100644 index 00000000..abcca30b --- /dev/null +++ b/libs/model/budgetting/budget-notes/src/lib/domain/add-note.command.ts @@ -0,0 +1,8 @@ +export class AddNoteToBudgetCommand { + constructor( + public readonly budgetId: string, + public readonly content: string, + public readonly authorId: string, + public readonly createdAt: Date = new Date() + ) {} +} diff --git a/libs/model/budgetting/budget-notes/src/lib/domain/add-note.handler.ts b/libs/model/budgetting/budget-notes/src/lib/domain/add-note.handler.ts new file mode 100644 index 00000000..6a678978 --- /dev/null +++ b/libs/model/budgetting/budget-notes/src/lib/domain/add-note.handler.ts @@ -0,0 +1,38 @@ +import { AddNoteToBudgetResult } from './add-note-result'; +import { AddNoteToBudgetCommand } from './add-note.command'; +import { FunctionHandler } from './function-handler'; + +export class AddNoteToBudgetHandler extends FunctionHandler< + AddNoteToBudgetCommand, + AddNoteToBudgetResult +> { + async execute(command: AddNoteToBudgetCommand, toolkit: any): Promise { + // Basic validation + if (!command.content || command.content.trim() === '') { + // return { success: false, message: 'Note content cannot be empty' }; + } + + if (!command.budgetId) { + // return { success: false, message: 'Budget ID is required' }; + } + + // Access repository from toolkit + const budgetRepository = toolkit?.getRepository?.('BudgetRepository'); + if (!budgetRepository) { + // return { success: false, message: 'Repository not available in toolkit' }; + } + + try { + // Call repository method + const noteId = await budgetRepository.addNote(command.budgetId, { + content: command.content, + authorId: command.authorId, + createdAt: command.createdAt, + }); + + // return { success: true, noteId }; + } catch (error: any) { + // return { success: false, message: error.message || 'Failed to add note' }; + } + } +} diff --git a/libs/model/budgetting/budget-notes/src/lib/domain/command.interface.ts b/libs/model/budgetting/budget-notes/src/lib/domain/command.interface.ts new file mode 100644 index 00000000..2953edd9 --- /dev/null +++ b/libs/model/budgetting/budget-notes/src/lib/domain/command.interface.ts @@ -0,0 +1,3 @@ +interface CommandHandler { + execute(command: TCommand, toolkit: any): Promise; +} diff --git a/libs/model/budgetting/budget-notes/src/lib/domain/function-handler.ts b/libs/model/budgetting/budget-notes/src/lib/domain/function-handler.ts new file mode 100644 index 00000000..64c52b39 --- /dev/null +++ b/libs/model/budgetting/budget-notes/src/lib/domain/function-handler.ts @@ -0,0 +1,5 @@ +export abstract class FunctionHandler + implements CommandHandler +{ + abstract execute(command: TCommand, toolkit?: any): Promise; +}