Merge pull request 'mazdak/UI-2896' (#65) from mazdak/UI-2896 into PRE-PRODUCTION-2026

Reviewed-on: https://ct.mfsys.com.pk/aConnect/aConnect-UX/pulls/65
PRE-PRODUCTION-2026
Naeem Ullah 1 month ago
commit b12008db80

@ -139,7 +139,16 @@ export const routes: Routes = [
import('./user-management/change-password/change-password.component').then(
m => m.ChangePasswordComponent
)
}
},
{
path: 'menu',
canActivate: [ActivityGuard],
loadComponent: () =>
import('./menu/menu.component').then(
m => m.MenuComponent
)
},
]
},
{

@ -0,0 +1,67 @@
<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>
<tr *ngFor="let item of menuItems; let i = index">
<td class="text-muted">{{ i + 1 }}</td>
<td>{{ item.endpoint | translate }}</td>
<td class="text-center"><input type="checkbox"
[(ngModel)]="item.checked" /></td>
</tr>
</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,159 @@
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 { filter, Observable, take } 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[] = [];
saving = false;
menuItems: { endpoint: string; checked: boolean }[] = [];
constructor(
private credentialService: CredentialService,
private fb: FormBuilder,
private httpService: HttpURIService,
private i18nService: I18NService,
private uriService: URIService
) {
this.permission = this.fb.group({ userCode: [null] });
this.defaultPermissions().subscribe((data: PermissionNode[]) => {
this.permissions = data;
});
}
ngOnInit() {
this.getAllUsers();
this.loadTransactionEndpoints();
}
loadTransactionEndpoints() {
this.httpService.requestGET<string[]>(URIKey.TRANSACTION_ENDPOINTS).subscribe((response) => {
if (!(response instanceof HttpErrorResponse)) {
this.menuItems = response.map(endpoint => ({
endpoint,
checked: false
}));
}
});
}
defaultPermissions(): Observable<PermissionNode[]> {
return this.httpService.requestGET<PermissionNode[]>('assets/data/sideMenu.json');
}
getAllUsers() {
this.httpService.requestGET<any[]>(URIKey.GET_ALL_USER_URI).subscribe((response) => {
if (!(response instanceof HttpErrorResponse)) {
this.users = response.map(item => ({
userName: item.userFullname,
userId: item.userId,
}));
}
});
}
onUserChange() {
this.showPermissions = true;
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)) {
const allowedEndpoints: string[] = (response as any[])
.filter(p => p.allowed)
.map(p => this.normalizeUrl(p.transactionEndpoint)); // ← normalize
this.menuItems.forEach(item => {
item.checked = allowedEndpoints.some(allowed =>
allowed.endsWith(this.normalizeUrl(item.endpoint))
);
});
} else {
this.menuItems.forEach(item => item.checked = false);
}
});
}
private normalizeUrl(url: string): string {
return url?.trim().toLowerCase().replace(/\/+$/, ''); // trim, lowercase, remove trailing slash
}
savePermissions() {
if (this.saving) return;
this.saving = true;
const selectedUser = this.permission.get('userCode')?.value;
this.uriService.canSubscribe.pipe(
filter(ready => ready === true),
take(1)
).subscribe(() => {
const transactionEndpoints: string[] = this.menuItems
.filter(item => item.checked)
.map(item => item.endpoint);
const payload = {
porOrgacode: this.credentialService.getPorOrgacode(),
userId: selectedUser,
transactionEndpoints
};
this.httpService.requestPOST(URIKey.TRANSACTION_PERMISSIONS_ASSIGN, payload)
.subscribe({
next: (response: any) => {
if (!(response instanceof HttpErrorResponse)) {
this.i18nService.success(SuccessMessages.SAVED_SUCCESSFULLY, []);
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);
if (savedNode) {
// Update state from saved node
existingNode.checked = savedNode.checked;
if (existingNode.children) {
this.updatePermissions(savedNode.children || [], existingNode.children);
}
if (existingNode.buttons) {
this.updatePermissions(savedNode.buttons || [], existingNode.buttons);
}
}
}
}
}

@ -96,6 +96,12 @@
</li>
</ul>
</li> -->
<li *ngIf="permissions.permissions || authService.isAdminUser()">
<a routerLink="/home/menu" routerLinkActive="mm-active">
<i class="fa fa-database "></i>
<span> {{ 'menu' | translate }}</span>
</a>
</li>
<li *ngIf="permissions.permissions || authService.isAdminUser()">
<a routerLink="/home/permissions" routerLinkActive="mm-active">
<i class='fa fa-lock'></i>

@ -169,6 +169,7 @@ export class ResetPasswordComponent implements OnInit{
.set('size', '1000');
this.httpService.requestGET<SetupUser[]>(URIKey.GET_ALL_USER_URI, params).subscribe({
next: (response) => {
this.allUsersDropdown = response || [];
this.isLoading = false;

@ -4,6 +4,10 @@ export interface PermissionNode {
expanded: boolean;
children?: PermissionNode[];
buttons?: PermissionNode[];
accountToAccount?: boolean;
glToGl?: boolean;
accountToGl?: boolean;
glToAccount?: boolean;
}
export interface LogsManagementResponse {

@ -15,5 +15,14 @@ export enum URIKey {
THIRD_PARTY_REGISTER_URI = "THIRD_PARTY_REGISTER_URI",
TRANSACTION_LOGS = "TRANSACTION_LOGS",
LOGGER_MANAGER_URI = "LOGGER_MANAGER_URI",
UPDATE_USER = 'UPDATE_USER'
UPDATE_USER = 'UPDATE_USER',
ACCOUNT_TO_ACCOUNT = "ACCOUNT_TO_ACCOUNT",
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 = "TRANSACTION_PERMISSIONS",
TRANSACTION_ENDPOINTS = "TRANSACTION_ENDPOINTS"
}

@ -86,6 +86,41 @@
"Id": "ENTITY_LOGGER_MANAGER_URI",
"URI": "/logs/getByDate",
"UUID": "LOGGER_MANAGER_URI"
},
{
"Id": "ENTITY_TRANSACTION_PERMISSIONS_ASSIGN",
"URI": "/transaction-permissions/assign",
"UUID": "TRANSACTION_PERMISSIONS_ASSIGN"
},
{
"Id": "ENTITY_ACCOUNT_TO_ACCOUNT",
"URI": "/transactions/accounttoaccount",
"UUID": "ACCOUNT_TO_ACCOUNT"
},
{
"Id": "ENTITY_GL_TO_GL",
"URI": "/transactions/gltogls",
"UUID": "GL_TO_GL"
},
{
"Id": "ENTITY_ACCOUNT_TO_GL",
"URI": "/transactions/accounttogl",
"UUID": "ACCOUNT_TO_GL"
},
{
"Id": "ENTITY_GL_TO_ACCOUNT",
"URI": "/transactions/gl-account",
"UUID": "GL_TO_ACCOUNT"
},
{
"Id": "ENTITY_TRANSACTION_PERMISSIONS",
"URI": "/transaction-permissions/{userId}",
"UUID": "TRANSACTION_PERMISSIONS"
},
{
"Id": "ENTITY_TRANSACTION_ENDPOINTS",
"URI": "/transaction-permissions/endpoints",
"UUID": "TRANSACTION_ENDPOINTS"
}
]
}

@ -70,6 +70,13 @@
}
]
},
{
"name": "menu",
"route": "/home/menu",
"checked": false,
"expanded": false,
"children": []
},
{
"name": "permissions",
"route": "/home/permissions",

@ -313,5 +313,31 @@
"dateRangeError": "الرجاء تحديد نطاق تاريخ صالح",
"exportAllData": "تصدير جميع البيانات",
"transactionLogDetails": "تفاصيل سجلات المعاملات",
"USER_UPDATED_SUCCESS": "تم تحديث المستخدم بنجاح"
"USER_UPDATED_SUCCESS": "تم تحديث المستخدم بنجاح",
"menu": "القائمة",
"type": "نوع المعاملة",
"ACCOUNT_TO_ACCOUNT": "من حساب إلى حساب",
"GL_TO_GL": "من دفتر الأستاذ إلى دفتر الأستاذ",
"ACCOUNT_TO_GL": "من حساب إلى دفتر الأستاذ",
"GL_TO_ACCOUNT": "من دفتر الأستاذ إلى حساب",
"one": "١",
"two": "٢",
"three": "٣",
"four": "٤",
"five": "٥",
"six": "٦",
"seven": "٧",
"eight": "٨",
"nine": "٩",
"ten": "١٠",
"eleven": "١١",
"twelve": "١٢",
"thirteen": "١٣",
"fourteen": "١٤",
"fifteen": "١٥",
"sixteen": "١٦",
"seventeen": "١٧",
"eighteen": "١٨",
"nineteen": "١٩",
"twenty": "٢٠"
}

@ -314,6 +314,31 @@
"dateRangeError": "Please select a valid date range",
"exportAllData": "Export All Data",
"transactionLogDetails": "Transaction Logs Details",
"USER_UPDATED_SUCCESS": "User Updated Successfully"
"USER_UPDATED_SUCCESS": "User Updated Successfully",
"menu": "Transaction Permissions",
"type": "Transaction Type",
"ACCOUNT_TO_ACCOUNT": "Account to Account",
"GL_TO_GL": "General Ledger to General Ledger",
"ACCOUNT_TO_GL": "Account to General Ledger",
"GL_TO_ACCOUNT": "General Ledger to Account",
"one": "1",
"two": "2",
"three": "3",
"four": "4",
"five": "5",
"six": "6",
"seven": "7",
"eight": "8",
"nine": "9",
"ten": "10",
"eleven": "11",
"twelve": "12",
"thirteen": "13",
"fourteen": "14",
"fifteen": "15",
"sixteen": "16",
"seventeen": "17",
"eighteen": "18",
"nineteen": "19",
"twenty": "20"
}

@ -3,6 +3,7 @@
{
"compileOnSave": false,
"compilerOptions": {
"rootDir": "./src",
"outDir": "./dist/out-tsc",
"strict": true,
"noImplicitOverride": true,

Loading…
Cancel
Save