feat: add tree view in chart of accounts component
Add tree view for GL accounts in chart of accounts component.
This commit is contained in:
@@ -80,6 +80,118 @@
|
||||
|
||||
<div class="container" *ngIf="viewGroup.value === 'treeView'">
|
||||
|
||||
TREE VIEW
|
||||
<div class="m-b-20" fxLayout="row" fxLayoutAlign="end" fxLayoutGap="20px">
|
||||
<button mat-raised-button (click)="nestedTreeControl.expandAll()"> <!-- Bug(angular-material): https://github.com/angular/material2/issues/12170 -->
|
||||
Expand All
|
||||
</button>
|
||||
<button mat-raised-button (click)="nestedTreeControl.collapseAll()">
|
||||
Collapse All
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div fxLayout="row" fxLayoutGap="4%" fxLayout.lt-md="column">
|
||||
|
||||
<div fxFlex="48%" *ngIf="glAccount">
|
||||
|
||||
<mat-card>
|
||||
|
||||
<mat-card-content>
|
||||
|
||||
<div fxLayout="row wrap" class="content">
|
||||
|
||||
<div fxFlex="50%" class="header">
|
||||
Account Name
|
||||
</div>
|
||||
|
||||
<div fxFlex="50%">
|
||||
{{ glAccount.name }}
|
||||
</div>
|
||||
|
||||
<div fxFlex="50%" class="header">
|
||||
GL Code
|
||||
</div>
|
||||
|
||||
<div fxFlex="50%">
|
||||
{{ glAccount.glCode }}
|
||||
</div>
|
||||
|
||||
<div fxFlex="50%" class="header">
|
||||
Account Type
|
||||
</div>
|
||||
|
||||
<div fxFlex="50%">
|
||||
{{ glAccount.type }}
|
||||
</div>
|
||||
|
||||
<div fxFlex="50%" class="header">
|
||||
Account Usage
|
||||
</div>
|
||||
|
||||
<div fxFlex="50%">
|
||||
{{ glAccount.usage }}
|
||||
</div>
|
||||
|
||||
<div fxFlex="50%" class="header">
|
||||
Manual Entries Allowed
|
||||
</div>
|
||||
|
||||
<div fxFlex="50%">
|
||||
{{ glAccount.manualEntriesAllowed }}
|
||||
</div>
|
||||
|
||||
<div fxFlex="50%" class="header">
|
||||
Description
|
||||
</div>
|
||||
|
||||
<div fxFlex="50%">
|
||||
{{ glAccount.description }}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</mat-card-content>
|
||||
|
||||
</mat-card>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="mat-elevation-z8" fxFlex>
|
||||
|
||||
<mat-tree [dataSource]="nestedTreeDataSource" [treeControl]="nestedTreeControl" class="gl-account-tree">
|
||||
|
||||
<mat-tree-node *matTreeNodeDef="let node" matTreeNodeToggle>
|
||||
<li class="mat-tree-node">
|
||||
<button mat-icon-button disabled></button>
|
||||
<span (click)="viewGLAccountNode(node)">
|
||||
<span *ngIf="node.glCode">{{ '(' + node.glCode + ')' }}</span>
|
||||
{{ node.name }}
|
||||
</span>
|
||||
</li>
|
||||
</mat-tree-node>
|
||||
|
||||
<mat-nested-tree-node *matTreeNodeDef="let node; when: hasNestedChild">
|
||||
<li>
|
||||
<div class="mat-tree-node">
|
||||
<button mat-icon-button matTreeNodeToggle [attr.aria-label]="'toggle ' + node.name">
|
||||
<mat-icon class="mat-icon-rtl-mirror">
|
||||
{{ nestedTreeControl.isExpanded(node) ? 'expand_more' : 'chevron_right' }}
|
||||
</mat-icon>
|
||||
</button>
|
||||
<span (click)="viewGLAccountNode(node)">
|
||||
<span *ngIf="node.glCode">{{ '(' + node.glCode + ')' }}</span>
|
||||
{{ node.name }}
|
||||
</span>
|
||||
</div>
|
||||
<ul [class.gl-account-tree-invisible]="!nestedTreeControl.isExpanded(node)">
|
||||
<ng-container matTreeNodeOutlet></ng-container>
|
||||
</ul>
|
||||
</li>
|
||||
</mat-nested-tree-node>
|
||||
|
||||
</mat-tree>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -2,6 +2,36 @@ table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.gl-account-tree-invisible {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.gl-account-tree ul,
|
||||
.gl-account-tree li {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
.gl-account-tree {
|
||||
li {
|
||||
span {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
div {
|
||||
margin: 1rem 0;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.header {
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
.disabled {
|
||||
color: red;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
import { Component, ViewChild, OnInit, AfterViewInit } from '@angular/core';
|
||||
import { MatPaginator, MatSort, MatTableDataSource } from '@angular/material';
|
||||
import { MatPaginator, MatSort, MatTableDataSource, MatTreeNestedDataSource } from '@angular/material';
|
||||
import { NestedTreeControl } from '@angular/cdk/tree';
|
||||
import { FormControl } from '@angular/forms';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
|
||||
import { of } from 'rxjs';
|
||||
|
||||
import { GLAccountNode } from './gl-account-node.model';
|
||||
import { GlAccountTreeService } from './gl-account-tree.service';
|
||||
|
||||
@Component({
|
||||
selector: 'mifosx-chart-of-accounts',
|
||||
templateUrl: './chart-of-accounts.component.html',
|
||||
@@ -19,14 +25,32 @@ export class ChartOfAccountsComponent implements AfterViewInit, OnInit {
|
||||
@ViewChild(MatPaginator) paginator: MatPaginator;
|
||||
@ViewChild(MatSort) sort: MatSort;
|
||||
|
||||
constructor(private route: ActivatedRoute) {
|
||||
//
|
||||
nestedTreeControl: NestedTreeControl<GLAccountNode>;
|
||||
nestedTreeDataSource: MatTreeNestedDataSource<GLAccountNode>;
|
||||
glAccount: GLAccountNode;
|
||||
//
|
||||
|
||||
constructor(private glAccountTreeService: GlAccountTreeService,
|
||||
private route: ActivatedRoute) {
|
||||
this.route.data.subscribe((data: { glAccountData: any }) => {
|
||||
this.glAccountData = data.glAccountData;
|
||||
glAccountTreeService.initialize(this.glAccountData);
|
||||
});
|
||||
//
|
||||
this.nestedTreeControl = new NestedTreeControl<GLAccountNode>(this._getChildren);
|
||||
this.nestedTreeDataSource = new MatTreeNestedDataSource();
|
||||
//
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.tableDataSource = new MatTableDataSource(this.glAccountData);
|
||||
//
|
||||
this.glAccountTreeService.treeDataChange.subscribe((glAccountTreeData: any) => {
|
||||
this.nestedTreeDataSource.data = glAccountTreeData;
|
||||
this.nestedTreeControl.expand(this.nestedTreeDataSource.data[0]);
|
||||
});
|
||||
//
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
@@ -49,4 +73,18 @@ export class ChartOfAccountsComponent implements AfterViewInit, OnInit {
|
||||
console.log(glAccount);
|
||||
}
|
||||
|
||||
//
|
||||
hasNestedChild = (_: number, node: GLAccountNode) => node.children.length;
|
||||
|
||||
viewGLAccountNode(glAccount: GLAccountNode) {
|
||||
if (glAccount.glCode) {
|
||||
this.glAccount = glAccount;
|
||||
} else {
|
||||
delete this.glAccount;
|
||||
}
|
||||
}
|
||||
|
||||
private _getChildren = (node: GLAccountNode) => of(node.children);
|
||||
//
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
export class GLAccountNode {
|
||||
children: GLAccountNode[];
|
||||
name: string;
|
||||
glCode: string;
|
||||
type: string;
|
||||
usage: string;
|
||||
manualEntriesAllowed: boolean;
|
||||
description: string;
|
||||
constructor(name: string,
|
||||
glCode: string = '',
|
||||
type: string = '',
|
||||
usage: string = '',
|
||||
manualEntriesAllowed: boolean = false,
|
||||
description: string = '') {
|
||||
this.name = name;
|
||||
this.glCode = glCode;
|
||||
this.type = type;
|
||||
this.usage = usage;
|
||||
this.manualEntriesAllowed = manualEntriesAllowed;
|
||||
this.description = description;
|
||||
this.children = [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import { TestBed, inject } from '@angular/core/testing';
|
||||
|
||||
import { GlAccountTreeService } from './gl-account-tree.service';
|
||||
|
||||
describe('GlAccountTreeService', () => {
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
providers: [GlAccountTreeService]
|
||||
});
|
||||
});
|
||||
|
||||
it('should be created', inject([GlAccountTreeService], (service: GlAccountTreeService) => {
|
||||
expect(service).toBeTruthy();
|
||||
}));
|
||||
});
|
||||
@@ -0,0 +1,70 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
|
||||
import { GLAccountNode } from './gl-account-node.model';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class GlAccountTreeService {
|
||||
|
||||
glAccountData: any;
|
||||
|
||||
treeDataChange = new BehaviorSubject<GLAccountNode[]>([]);
|
||||
|
||||
get treeData(): GLAccountNode[] { return this.treeDataChange.value; }
|
||||
|
||||
constructor() { }
|
||||
|
||||
initialize(glAccountData: any) {
|
||||
const data = this.buildGLAccountTree(glAccountData);
|
||||
this.treeDataChange.next(data);
|
||||
}
|
||||
|
||||
buildGLAccountTree(glAccountData: any): GLAccountNode[] {
|
||||
const glAccountTree: GLAccountNode[] = [];
|
||||
|
||||
glAccountTree.push(new GLAccountNode('GL ACCOUNTS'));
|
||||
glAccountTree[0].children.push(new GLAccountNode('ASSET'));
|
||||
glAccountTree[0].children.push(new GLAccountNode('EQUITY'));
|
||||
glAccountTree[0].children.push(new GLAccountNode('EXPENSE'));
|
||||
glAccountTree[0].children.push(new GLAccountNode('INCOME'));
|
||||
glAccountTree[0].children.push(new GLAccountNode('LIABILITY'));
|
||||
|
||||
glAccountData.sort((glAccountOne: any, glAccountTwo: any) => {
|
||||
if (!glAccountOne.parentId) {
|
||||
glAccountOne.parentId = 0;
|
||||
}
|
||||
return glAccountOne.parentId - glAccountTwo.parentId;
|
||||
});
|
||||
|
||||
const glAccounts: GLAccountNode[] = [];
|
||||
|
||||
for (const glAccount of glAccountData) {
|
||||
glAccounts[glAccount.id] =
|
||||
new GLAccountNode(glAccount.name, glAccount.glCode, glAccount.type.value, glAccount.usage.value, glAccount.manualEntriesAllowed, glAccount.description);
|
||||
}
|
||||
|
||||
for (const glAccount of glAccountData) {
|
||||
if (glAccount.parentId === 0) {
|
||||
if (glAccount.type.value === 'ASSET') {
|
||||
glAccountTree[0].children[0].children.push(glAccounts[glAccount.id]);
|
||||
} else if (glAccount.type.value === 'EQUITY') {
|
||||
glAccountTree[0].children[1].children.push(glAccounts[glAccount.id]);
|
||||
} else if (glAccount.type.value === 'EXPENSE') {
|
||||
glAccountTree[0].children[2].children.push(glAccounts[glAccount.id]);
|
||||
} else if (glAccount.type.value === 'INCOME') {
|
||||
glAccountTree[0].children[3].children.push(glAccounts[glAccount.id]);
|
||||
} else if (glAccount.type.value === 'LIABILITY') {
|
||||
glAccountTree[0].children[4].children.push(glAccounts[glAccount.id]);
|
||||
}
|
||||
} else {
|
||||
glAccounts[glAccount.parentId].children.push(glAccounts[glAccount.id]);
|
||||
}
|
||||
}
|
||||
|
||||
return glAccountTree;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -33,6 +33,7 @@ import { MatCheckboxModule,
|
||||
MatTooltipModule,
|
||||
MatSnackBarModule,
|
||||
MatTableModule,
|
||||
MatTreeModule,
|
||||
MatSortModule,
|
||||
MatPaginatorModule,
|
||||
MatDividerModule,
|
||||
@@ -98,6 +99,7 @@ import { AuthenticationInterceptor } from './authentication/authentication.inter
|
||||
MatTooltipModule,
|
||||
MatSnackBarModule,
|
||||
MatTableModule,
|
||||
MatTreeModule,
|
||||
MatSortModule,
|
||||
MatPaginatorModule,
|
||||
MatDividerModule
|
||||
@@ -143,6 +145,7 @@ import { AuthenticationInterceptor } from './authentication/authentication.inter
|
||||
MatTooltipModule,
|
||||
MatSnackBarModule,
|
||||
MatTableModule,
|
||||
MatTreeModule,
|
||||
MatSortModule,
|
||||
MatPaginatorModule,
|
||||
MatDividerModule,
|
||||
|
||||
Reference in New Issue
Block a user