Add transaction permissions menu and component
Introduce a new Menu feature to manage transaction permissions: adds MenuComponent (template, spec and scss), registers a /home/menu route with ActivityGuard, and adds a side-nav entry. Extend PermissionNode with boolean flags for transaction types and add logic in the component to load default menu data, fetch user permissions, map endpoint URIs to booleans, and save selections by sending endpoint arrays. Add new URI enum keys and corresponding entries in assets/app.uri.json, insert a 'menu' node in sideMenu.json, and add i18n strings for English and Arabic. Also add rootDir to tsconfig and a small whitespace fix in reset-password component's GET subscription.mazdak/UI-2896
parent
387002c494
commit
dd649b51de
@ -0,0 +1,86 @@
|
|||||||
|
<div class="page-content">
|
||||||
|
<div class="container-fluid">
|
||||||
|
<!-- User Selection Card -->
|
||||||
|
<div class="card shadow-sm mb-4">
|
||||||
|
<div class="card-body">
|
||||||
|
<form [formGroup]="permission" class="row align-items-center">
|
||||||
|
<label class="col-md-2 col-form-label fw-semibold">
|
||||||
|
{{ "userCode" | translate }}
|
||||||
|
</label>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<ng-select class="form-select" formControlName="userCode" [items]="users" bindLabel="userName"
|
||||||
|
bindValue="userId" placeholder="{{ 'choose' | translate }}" (change)="onUserChange()"
|
||||||
|
[searchable]="true" [clearable]="false" [dropdownPosition]="'auto'" [virtualScroll]="true"
|
||||||
|
[bufferAmount]="20" appendTo="body">
|
||||||
|
<!-- Custom template for dropdown options -->
|
||||||
|
<ng-template ng-option-tmp let-item="item">
|
||||||
|
<div class="d-flex flex-column">
|
||||||
|
<span class="fw-medium">{{ item.userName }}</span>
|
||||||
|
<small class="text-muted">{{ item.userId }}</small>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
<!-- Optional: Custom template for selected item -->
|
||||||
|
<ng-template ng-label-tmp let-item="item">
|
||||||
|
<span>{{ item.userName }} ({{ item.userId }})</span>
|
||||||
|
</ng-template>
|
||||||
|
</ng-select>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card shadow-sm" *ngIf="showPermissions">
|
||||||
|
<div class="card-body">
|
||||||
|
<h4 class="card-title mb-3">{{ "menu" | translate }}</h4>
|
||||||
|
|
||||||
|
<div class="table-responsive scrollable-table">
|
||||||
|
<table class="table table-hover table-bordered table-sm permission-table">
|
||||||
|
<thead class="table-light">
|
||||||
|
<tr>
|
||||||
|
<th style="width: 50px">#</th>
|
||||||
|
<th>{{ "type" | translate }}</th>
|
||||||
|
<th style="width: 100px;" class="text-center">{{ "allow" | translate }}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
|
||||||
|
<tbody>
|
||||||
|
<ng-container *ngFor="let node of permissions">
|
||||||
|
<ng-container *ngIf="node.name === 'menu'">
|
||||||
|
<tr>
|
||||||
|
<td class="text-muted">{{ "one" | translate }}</td>
|
||||||
|
<td>{{ "ACCOUNT_TO_ACCOUNT" | translate }}</td>
|
||||||
|
<td class="text-center"><input type="checkbox"
|
||||||
|
[(ngModel)]="node.accountToAccount" /></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="text-muted">{{ "two" | translate }}</td>
|
||||||
|
<td>{{ "GL_TO_GL" | translate }}</td>
|
||||||
|
<td class="text-center"><input type="checkbox" [(ngModel)]="node.glToGl" /></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="text-muted">{{ "three" | translate }}</td>
|
||||||
|
<td>{{ "ACCOUNT_TO_GL" | translate }}</td>
|
||||||
|
<td class="text-center"><input type="checkbox" [(ngModel)]="node.accountToGl" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="text-muted">{{ "four" | translate }}</td>
|
||||||
|
<td>{{ "GL_TO_ACCOUNT" | translate }}</td>
|
||||||
|
<td class="text-center"><input type="checkbox" [(ngModel)]="node.glToAccount" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</ng-container>
|
||||||
|
</ng-container>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="text-end mt-3">
|
||||||
|
<button class="btn btn-primary btn-sm px-4" (click)="savePermissions()">
|
||||||
|
{{ "save" | translate }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { MenuComponent } from './menu.component';
|
||||||
|
|
||||||
|
describe('MenuComponent', () => {
|
||||||
|
let component: MenuComponent;
|
||||||
|
let fixture: ComponentFixture<MenuComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [MenuComponent]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(MenuComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -0,0 +1,133 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { FormBuilder, FormGroup, FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||||
|
import { PermissionNode } from '../utils/app.interfaces';
|
||||||
|
import { CredentialService } from '../services/credential.service';
|
||||||
|
import { I18NService } from '../services/i18n.service';
|
||||||
|
import { HttpURIService } from '../app.http.uri.service';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { URIKey } from '../utils/uri-enums';
|
||||||
|
import { HttpErrorResponse, HttpParams } from '@angular/common/http';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { NgSelectModule } from '@ng-select/ng-select';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { SuccessMessages } from '../utils/enums';
|
||||||
|
import { URIService } from '../app.uri';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-menu',
|
||||||
|
imports: [TranslateModule, ReactiveFormsModule, CommonModule, TranslateModule, NgSelectModule, FormsModule],
|
||||||
|
templateUrl: './menu.component.html',
|
||||||
|
styleUrl: './menu.component.scss'
|
||||||
|
})
|
||||||
|
export class MenuComponent {
|
||||||
|
users: any[] = [];
|
||||||
|
permission: FormGroup;
|
||||||
|
showPermissions = false;
|
||||||
|
permissions: PermissionNode[] = [];
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private credentialService: CredentialService,
|
||||||
|
private fb: FormBuilder,
|
||||||
|
private httpService: HttpURIService,
|
||||||
|
private i18nService: I18NService,
|
||||||
|
private uriService: URIService
|
||||||
|
) {
|
||||||
|
this.permission = this.fb.group({
|
||||||
|
allocation: [''],
|
||||||
|
userCode: [null],
|
||||||
|
userRole: [''],
|
||||||
|
});
|
||||||
|
|
||||||
|
this.defaultPermissions().subscribe((data: PermissionNode[]) => {
|
||||||
|
this.permissions = data;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.getAllUsers();
|
||||||
|
}
|
||||||
|
defaultPermissions(): Observable<PermissionNode[]> {
|
||||||
|
return this.httpService.requestGET<PermissionNode[]>('assets/data/sideMenu.json');
|
||||||
|
}
|
||||||
|
|
||||||
|
getAllUsers() {
|
||||||
|
this.httpService.requestGET<any[]>(URIKey.GET_ALL_USER_URI).subscribe((response) => {
|
||||||
|
console.log(URIKey.GET_ALL_USER_URI);
|
||||||
|
if (!(response instanceof HttpErrorResponse)) {
|
||||||
|
this.users = response.map(item => ({
|
||||||
|
userName: item.userFullname,
|
||||||
|
userId: item.userId,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onUserChange() {
|
||||||
|
this.showPermissions = true;
|
||||||
|
this.defaultPermissions().subscribe((data: PermissionNode[]) => {
|
||||||
|
this.permissions = data;
|
||||||
|
const params = new HttpParams().set('userId', this.permission.get('userCode')?.value);
|
||||||
|
this.httpService.requestGET(URIKey.USER_GET_PERMISSIONS, params).subscribe((response: any) => {
|
||||||
|
if (!(response instanceof HttpErrorResponse)) {
|
||||||
|
// Step 4 - reverse map endpoints back to booleans
|
||||||
|
const menuNode = this.permissions.find(x => x.name === 'menu');
|
||||||
|
if (menuNode && response.transactionEndpoints) {
|
||||||
|
menuNode.accountToAccount = response.transactionEndpoints.includes(this.uriService.getURIForRequest(URIKey.ACCOUNT_TO_ACCOUNT));
|
||||||
|
menuNode.glToGl = response.transactionEndpoints.includes(this.uriService.getURIForRequest(URIKey.GL_TO_GL));
|
||||||
|
menuNode.accountToGl = response.transactionEndpoints.includes(this.uriService.getURIForRequest(URIKey.ACCOUNT_TO_GL));
|
||||||
|
menuNode.glToAccount = response.transactionEndpoints.includes(this.uriService.getURIForRequest(URIKey.GL_TO_ACCOUNT));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
savePermissions() {
|
||||||
|
// Step 3 - extract menu node and build endpoints array
|
||||||
|
const menuNode = this.permissions.find(x => x.name === 'menu');
|
||||||
|
if (!menuNode) return;
|
||||||
|
|
||||||
|
const transactionEndpoints: string[] = [];
|
||||||
|
|
||||||
|
// Step 2 - map booleans to endpoint strings
|
||||||
|
if (menuNode.accountToAccount) transactionEndpoints.push(this.uriService.getURIForRequest(URIKey.ACCOUNT_TO_ACCOUNT));
|
||||||
|
if (menuNode.glToGl) transactionEndpoints.push(this.uriService.getURIForRequest(URIKey.GL_TO_GL));
|
||||||
|
if (menuNode.accountToGl) transactionEndpoints.push(this.uriService.getURIForRequest(URIKey.ACCOUNT_TO_GL));
|
||||||
|
if (menuNode.glToAccount) transactionEndpoints.push(this.uriService.getURIForRequest(URIKey.GL_TO_ACCOUNT));
|
||||||
|
|
||||||
|
// Step 1 - new payload structure
|
||||||
|
const payload = {
|
||||||
|
userId: this.permission.get('userCode')?.value,
|
||||||
|
transactionEndpoints
|
||||||
|
};
|
||||||
|
|
||||||
|
this.httpService.requestPUT(URIKey.TRANSACTION_PERMISSIONS_ASSIGN, payload).subscribe((response: any) => {
|
||||||
|
if (!(response instanceof HttpErrorResponse)) {
|
||||||
|
this.i18nService.success(SuccessMessages.SAVED_SUCCESSFULLY, []);
|
||||||
|
this.permission.get('userCode')?.setValue(null);
|
||||||
|
this.showPermissions = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
updatePermissions(savedPermissions: PermissionNode[], existingPermissions: PermissionNode[]): void {
|
||||||
|
for (const existingNode of existingPermissions) {
|
||||||
|
const savedNode = savedPermissions.find(node => node.name === existingNode.name);
|
||||||
|
|
||||||
|
if (savedNode) {
|
||||||
|
// Update state from saved node
|
||||||
|
existingNode.checked = savedNode.checked;
|
||||||
|
//existingNode.expanded = savedNode.expanded;
|
||||||
|
|
||||||
|
// Recursively update children if they exist
|
||||||
|
if (existingNode.children) {
|
||||||
|
this.updatePermissions(savedNode.children || [], existingNode.children);
|
||||||
|
}
|
||||||
|
if (existingNode.buttons) {
|
||||||
|
this.updatePermissions(savedNode.buttons || [], existingNode.buttons);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue