Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,55 +1,92 @@
<table mat-table #sort="matSort" [dataSource]="dataSource" multiTemplateDataRows class="mat-elevation-z8" matSort>

<table
mat-table
#sort="matSort"
[dataSource]="dataSource"
multiTemplateDataRows
class="mat-elevation-z8"
matSort
>
<!-- name Column -->
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Budget Name </th>
<th mat-header-cell *matHeaderCellDef mat-sort-header>Budget Name</th>
<td mat-cell *matCellDef="let row">
{{row.name ? row.name : '-' }} <span *ngIf="row.childrenList?.length > 0" class="badge primary"
(click)="openChildBudgetDialog(row)"> Linked Budgets ({{ row?.childrenList?.length }}) </span>
{{ row.name ? row.name : '-' }}
<span
*ngIf="row.childrenList?.length > 0"
class="badge primary"
(click)="openChildBudgetDialog(row)"
>
Linked Budgets ({{ row?.childrenList?.length }})
</span>
</td>
</ng-container>

<!-- status Column -->
<ng-container matColumnDef="status">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Status </th>
<th mat-header-cell *matHeaderCellDef mat-sort-header>Status</th>
<td mat-cell *matCellDef="let row">
<span class="badge" [ngClass]="row.status == 1 ? 'active': 'draft'">
{{translateStatus(row.status) | transloco}}
<span class="badge" [ngClass]="row.status === 1 ? 'active' : 'draft'">
{{ translateStatus(row.status) | transloco }}
</span>
</td>
</ng-container>

<!-- Start year Column -->
<ng-container matColumnDef="startYear">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Start Year </th>
<td mat-cell *matCellDef="let row"> {{row.startYear ? row.startYear : '-'}} </td>
<th mat-header-cell *matHeaderCellDef mat-sort-header>Start Year</th>
<td mat-cell *matCellDef="let row">
{{ row.startYear ? row.startYear : '-' }}
</td>
</ng-container>

<!-- Duration Column -->
<ng-container matColumnDef="duration">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Duration (Years) </th>
<td mat-cell *matCellDef="let row"> {{row.duration ? row.duration : '-' }} </td>
<th mat-header-cell *matHeaderCellDef mat-sort-header>Duration (Years)</th>
<td mat-cell *matCellDef="let row">
{{ row.duration ? row.duration : '-' }}
</td>
</ng-container>

<!-- actions Column -->
<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Actions </th>
<th mat-header-cell *matHeaderCellDef mat-sort-header>Actions</th>
<td mat-cell *matCellDef="let row">
<div class="budget-btn-container" fxLayout="row" fxLayoutALign="start center" fxLayoutGap="10px">

<button color="primary" class="secondary-action" mat-mini-fab (click)="openCloneBudgetDialog(row)"
matTooltip="{{ 'BUDGET.VIEW-RECORD.ACTIONS.CLONE'| transloco }}" *ngIf="access('clone')">
<div
class="budget-btn-container"
fxLayout="row"
fxLayoutALign="start center"
fxLayoutGap="10px"
>
<button
color="primary"
class="secondary-action"
mat-mini-fab
(click)="openCloneBudgetDialog(row)"
matTooltip="{{ 'BUDGET.VIEW-RECORD.ACTIONS.CLONE' | transloco }}"
*ngIf="access('clone')"
>
<i class="fas fa-copy fa-sm"></i>
</button>

<button color="primary" class="secondary-action" mat-mini-fab
matTooltip="{{'BUDGET.VIEW-RECORD.ACTIONS.VIEW' | transloco}}" (click)="goToDetail(row.id!, 'view')"
*ngIf="access('view')">
<button
color="primary"
class="secondary-action"
mat-mini-fab
matTooltip="{{ 'BUDGET.VIEW-RECORD.ACTIONS.VIEW' | transloco }}"
(click)="goToDetail(row.id!, 'view')"
*ngIf="access('view')"
>
<i class="fas fa-eye fa-sm"></i>
</button>

<button color="primary" mat-mini-fab matTooltip="{{ 'BUDGET.VIEW-RECORD.ACTIONS.EDIT' | transloco }}"
color="primary" (click)="goToDetail(row.id!, 'edit')" *ngIf="access('edit')">
<button
color="primary"
mat-mini-fab
matTooltip="{{ 'BUDGET.VIEW-RECORD.ACTIONS.EDIT' | transloco }}"
color="primary"
(click)="goToDetail(row.id!, 'edit')"
*ngIf="access('edit')"
>
<i class="fas fa-edit fa-sm"></i>
</button>

Expand All @@ -59,8 +96,13 @@
<i class="fas fa-share-alt fa-sm"></i>
</button> -->

<button color="primary" class="delete-btn" matTooltip="Delete" mat-mini-fab
(click)="openShareBudgetDialog(row)">
<button
color="primary"
class="delete-btn"
matTooltip="Delete"
mat-mini-fab
(click)="openShareBudgetDialog(row)"
>
<i class="fas fa-trash-alt fa-sm"></i>
</button>

Expand All @@ -72,6 +114,9 @@
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns; sticky: true"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr>
</table>
<mat-paginator [pageSizeOptions]="[10, 25, 100]" aria-label="Select page of users"></mat-paginator>
<mat-paginator
[pageSizeOptions]="[10, 25, 100]"
aria-label="Select page of users"
></mat-paginator>
Original file line number Diff line number Diff line change
@@ -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<OrgBudgetsOverview>;
sharedBudgets$: Observable<any[]>;
export class SelectBudgetPageComponent implements OnInit {
overview = signal<OrgBudgetsOverview | null>(null);
sharedBudgets = signal<any[]>([]);

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;

(<any> record).updating = true;
// Fire update
this._budgets$$.update(toSave)
.subscribe(() => {
(<any> 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.`
);
});
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export interface AddNoteToBudgetResult {
success: boolean;
noteId?: string;
message?: string;
}
Original file line number Diff line number Diff line change
@@ -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()
) {}
}
Original file line number Diff line number Diff line change
@@ -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<void> {
// 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' };
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
interface CommandHandler<TCommand> {
execute(command: TCommand, toolkit: any): Promise<void>;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export abstract class FunctionHandler<TCommand, TResult>
implements CommandHandler<TCommand>
{
abstract execute(command: TCommand, toolkit?: any): Promise<TResult>;
}