Refactor menu permissions UI & endpoint handling

mazdak/UI-2896
Mazdak Gibran 1 month ago
parent dd649b51de
commit 1616545600

@ -45,33 +45,14 @@
</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>
<tr *ngFor="let item of menuItems; let i = index">
<td class="text-muted">{{ i + 1 }}</td>
<td>{{ item.name | translate }}</td>
<td class="text-center"><input type="checkbox"
[(ngModel)]="node.accountToAccount" /></td>
[(ngModel)]="item.checked" /></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>

@ -9,7 +9,7 @@ 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 { filter, Observable, take } from 'rxjs';
import { SuccessMessages } from '../utils/enums';
import { URIService } from '../app.uri';
@ -24,6 +24,13 @@ export class MenuComponent {
permission: FormGroup;
showPermissions = false;
permissions: PermissionNode[] = [];
saving = false;
menuItems = [
{ name: 'accountToAccount', endpoint: URIKey.ACCOUNT_TO_ACCOUNT, checked: true },
{ name: 'glToGl', endpoint: URIKey.GL_TO_GL, checked: false },
{ name: 'accountToGl', endpoint: URIKey.ACCOUNT_TO_GL, checked: false },
{ name: 'glToAccount', endpoint: URIKey.GL_TO_ACCOUNT, checked: false }
];
constructor(
private credentialService: CredentialService,
@ -32,14 +39,9 @@ export class MenuComponent {
private i18nService: I18NService,
private uriService: URIService
) {
this.permission = this.fb.group({
allocation: [''],
userCode: [null],
userRole: [''],
});
this.permission = this.fb.group({ userCode: [null] });
this.defaultPermissions().subscribe((data: PermissionNode[]) => {
this.permissions = data;
this.permissions = data; // needed for savePermissions
});
}
@ -52,7 +54,6 @@ export class MenuComponent {
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,
@ -61,56 +62,116 @@ export class MenuComponent {
}
});
}
// mapEndpointsToPermissions(endpoints: string[]): PermissionNode[] {
// return [{
// name: 'menu',
// checked: false,
// expanded: false,
// children: [
// { name: 'accountToAccount', checked: endpoints.includes(this.uriService.getURIForRequest(URIKey.ACCOUNT_TO_ACCOUNT)), expanded: false },
// { name: 'glToGl', checked: endpoints.includes(this.uriService.getURIForRequest(URIKey.GL_TO_GL)), expanded: false },
// { name: 'accountToGl', checked: endpoints.includes(this.uriService.getURIForRequest(URIKey.ACCOUNT_TO_GL)), expanded: false },
// { name: 'glToAccount', checked: endpoints.includes(this.uriService.getURIForRequest(URIKey.GL_TO_ACCOUNT)), expanded: false }
// ]
// }];
// }
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) => {
const userId = this.permission.get('userCode')?.value;
const params = new HttpParams().set('userId', userId);
this.httpService.requestGET(URIKey.TRANSACTION_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));
}
}
const allowedEndpoints: string[] = (response as any[])
.filter(p => p.allowed)
.map(p => this.normalizeUrl(p.transactionEndpoint)); // ← normalize
this.uriService.canSubscribe.pipe(
filter(ready => ready === true),
take(1)
).subscribe(() => {
this.menuItems.forEach(item => {
const resolvedEndpoint = this.normalizeUrl(
this.uriService.getURIForRequest(item.endpoint) // ← normalize
);
item.checked = allowedEndpoints.includes(resolvedEndpoint);
});
});
} else {
this.menuItems.forEach(item => item.checked = false);
}
});
}
// Add this helper method to the class
private normalizeUrl(url: string): string {
return url?.trim().toLowerCase().replace(/\/+$/, ''); // trim, lowercase, remove trailing slash
}
// savePermissions() {
// const selectedUser = this.permission.get('userCode')?.value;
// const menuNode = this.permissions.find(x => x.name === 'menu');
// if (!menuNode) return;
// const nameToURIKey: { [key: string]: URIKey } = {
// accountToAccount: URIKey.ACCOUNT_TO_ACCOUNT,
// glToGl: URIKey.GL_TO_GL,
// accountToGl: URIKey.ACCOUNT_TO_GL,
// glToAccount: URIKey.GL_TO_ACCOUNT
// };
// const transactionEndpoints: string[] = (menuNode.children || [])
// .filter(c => c.checked)
// .map(c => this.uriService.getURIForRequest(nameToURIKey[c.name]));
// const payload = { userId: selectedUser, transactionEndpoints, permissions: JSON.stringify(this.permissions) };
// this.httpService.requestPOST(URIKey.TRANSACTION_PERMISSIONS_ASSIGN, payload).subscribe((response: any) => {
// if (!(response instanceof HttpErrorResponse)) {
// this.i18nService.success(SuccessMessages.SAVED_SUCCESSFULLY, []);
// this.permission.get('userCode')?.setValue(selectedUser);
// this.onUserChange();
// }
// });
// }
savePermissions() {
// Step 3 - extract menu node and build endpoints array
const menuNode = this.permissions.find(x => x.name === 'menu');
if (!menuNode) return;
if (this.saving) return; // ← guard against double clicks
this.saving = true;
const transactionEndpoints: string[] = [];
const selectedUser = this.permission.get('userCode')?.value;
// 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));
this.uriService.canSubscribe.pipe(
filter(ready => ready === true),
take(1)
).subscribe(() => {
const transactionEndpoints: string[] = this.menuItems
.filter(item => item.checked)
.map(item => this.uriService.getURIForRequest(item.endpoint));
// Step 1 - new payload structure
const payload = {
userId: this.permission.get('userCode')?.value,
transactionEndpoints
};
const payload = { userId: selectedUser, transactionEndpoints };
this.httpService.requestPUT(URIKey.TRANSACTION_PERMISSIONS_ASSIGN, payload).subscribe((response: any) => {
this.httpService.requestPOST(URIKey.TRANSACTION_PERMISSIONS_ASSIGN, payload)
.subscribe({
next: (response: any) => {
if (!(response instanceof HttpErrorResponse)) {
this.i18nService.success(SuccessMessages.SAVED_SUCCESSFULLY, []);
this.permission.get('userCode')?.setValue(null);
this.showPermissions = false;
this.onUserChange(); // this triggers one GET — expected
}
},
complete: () => {
this.saving = false; // ← reset after done
},
error: () => {
this.saving = false;
}
});
});
}
updatePermissions(savedPermissions: PermissionNode[], existingPermissions: PermissionNode[]): void {
for (const existingNode of existingPermissions) {
const savedNode = savedPermissions.find(node => node.name === existingNode.name);

@ -20,6 +20,7 @@ export enum URIKey {
GL_TO_GL = "GL_TO_GL",
ACCOUNT_TO_GL = "ACCOUNT_TO_GL",
GL_TO_ACCOUNT = "GL_TO_ACCOUNT",
TRANSACTION_PERMISSIONS_ASSIGN = "TRANSACTION_PERMISSIONS_ASSIGN"
TRANSACTION_PERMISSIONS_ASSIGN = "TRANSACTION_PERMISSIONS_ASSIGN",
TRANSACTION_PERMISSIONS = "TRANSACTION_PERMISSIONS"
}

@ -111,6 +111,11 @@
"Id": "ENTITY_GL_TO_ACCOUNT",
"URI": "/transactions/gl-account",
"UUID": "GL_TO_ACCOUNT"
},
{
"Id": "ENTITY_TRANSACTION_PERMISSIONS",
"URI": "/transaction-permissions/{userId}",
"UUID": "TRANSACTION_PERMISSIONS"
}
]
}

@ -75,10 +75,6 @@
"route": "/home/menu",
"checked": false,
"expanded": false,
"accountToAccount": false,
"glToGl": false,
"accountToGl": false,
"glToAccount": false,
"children": []
},
{

Loading…
Cancel
Save