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,15 +1,18 @@
import { Component, EventEmitter, Input, Output, ViewChild } from '@angular/core';
import { MatTable, MatTableDataSource } from '@angular/material/table';
import {
Component,
input,
ViewChild,
inject,
effect,
AfterViewInit,
} 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';
import { CreateBudgetModalComponent } from '../create-budget-modal/create-budget-modal.component';
import { ChildBudgetsModalComponent } from '../../modals/child-budgets-modal/child-budgets-modal.component';
Expand All @@ -19,56 +22,50 @@ import { ChildBudgetsModalComponent } from '../../modals/child-budgets-modal/chi
templateUrl: './budget-table.component.html',
styleUrls: ['./budget-table.component.scss'],
})
export class BudgetTableComponent implements AfterViewInit {
budgets = input.required<{ overview: BudgetRecord[]; budgets: any[] }>();
canPromote = input(false);

export class BudgetTableComponent {

private _sbS = new SubSink();

@Input() budgets$: Observable<{overview: BudgetRecord[], budgets: any[]}>;
@Input() canPromote = false;

@Output() doPromote: EventEmitter<void> = new EventEmitter();
@Output() doPromote = new EventEmitter<void>();

dataSource = new MatTableDataSource();
displayedColumns: string[] = [
'name',
'status',
'startYear',
'duration',
'actions',
];

displayedColumns: string[] = ['name', 'status', 'startYear', 'duration', 'actions'];
@ViewChild(MatPaginator) paginator!: MatPaginator;
@ViewChild(MatSort, { static: true }) sort!: MatSort;

@ViewChild(MatPaginator) paginator: MatPaginator;
@ViewChild('sort', { static: true }) sort: MatSort;
private router = inject(Router);
private dialog = inject(MatDialog);

overviewBudgets: BudgetRecord[] = [];

constructor(private _router$$: Router,
private _dialog: MatDialog,
) { }
constructor() {
// REACT TO SIGNAL CHANGES
effect(() => {
const data = this.budgets();

ngOnInit(): void {
this._sbS.sink = this.budgets$.pipe(tap((o) => {
this.overviewBudgets = o.overview;
this.dataSource.data = o.budgets;
})).subscribe();
this.overviewBudgets = data.overview;
this.dataSource.data = data.budgets;
});
}

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)
{
access(requested: any) {
switch (requested) {
case 'view':
case 'clone':
return true; //budget.access.owner || budget.access.view || budget.access.edit;
case 'edit':
return true; // (budget.access.owner || budget.access.edit) && budget.status !== BudgetStatus.InUse && budget.status !== BudgetStatus.InUse;
return true; //budget.access.owner || budget.access.view || budget.access.edit; case 'edit': return true; // (budget.access.owner || budget.access.edit) && budget.status !== BudgetStatus.InUse && budget.status !== BudgetStatus.InUse; } return false;
}
return false;
}

ngAfterViewInit(): void {
this.dataSource.paginator = this.paginator;
this.dataSource.sort = this.sort;
}

filterAccountRecords(event: Event) {
Expand All @@ -81,46 +78,49 @@ export class BudgetTableComponent {
}

promote() {
if (this.canPromote)
if (this.canPromote()) {
this.doPromote.emit();
}
}

/** Open share screen to configure budget access. */
openShareBudgetDialog(parent: Budget | false): void
{
this._dialog.open(ShareBudgetModalComponent, {
openShareBudgetDialog(parent: Budget | false): void {
this.dialog.open(ShareBudgetModalComponent, {
panelClass: 'no-pad-dialog',
width: '600px',
data: parent != null ? parent : false
data: parent ?? false,
});
}

/** Open clone screen to clone and reconfigure budget. */
openCloneBudgetDialog(parent: Budget | false): void {
this._dialog.open(CreateBudgetModalComponent, {
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)
this._dialog.open(ChildBudgetsModalComponent, {
openChildBudgetDialog(parent: Budget): void {
let children: any = this.overviewBudgets.find(
(b) => b.budget.id === parent.id
)?.children;

children = children?.map((child) => child.budget);

this.dialog.open(ChildBudgetsModalComponent, {
height: 'fit-content',
minWidth: '600px',
data: {parent: parent, budgets: children}
data: { parent, budgets: children },
});
}

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) {

console.log(budget);
}

translateStatus(status: number) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
<app-page>
<div>
<kujali-finance-toolbar>
<kujali-finance-search-header-card header (toogleFilterEvent)='toogleFilter($event)'
(searchTableEvent)='applyFilter($event)' [tableData]="[]">
<kujali-finance-search-header-card
header
(toogleFilterEvent)="toogleFilter($event)"
(searchTableEvent)="applyFilter($event)"
[tableData]="[]"
>
<div add-new>
<button mat-flat-button (click)="openDialog(false)" class="add-new">
<i class="bi bi-plus-circle-fill"></i>
{{'HEADER-ADD-NEW' | transloco}}
{{ 'HEADER-ADD-NEW' | transloco }}
</button>
</div>
</kujali-finance-search-header-card>
Expand All @@ -18,7 +22,7 @@
</div>

<div class="table-container">
<app-budget-table [budgets$]="allBudgets$"></app-budget-table>
<app-budget-table [budgets$]="allBudgets()"></app-budget-table>
</div>
</div>
</app-page>
</app-page>
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.`
);
});
}
}
}
Loading