diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts index ef5249d..2a66d09 100644 --- a/src/app/app.routes.ts +++ b/src/app/app.routes.ts @@ -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 + ) + }, + ] }, { diff --git a/src/app/menu/menu.component.html b/src/app/menu/menu.component.html new file mode 100644 index 0000000..782a6a4 --- /dev/null +++ b/src/app/menu/menu.component.html @@ -0,0 +1,67 @@ +
+
+ +
+
+
+ +
+ + + +
+ {{ item.userName }} + {{ item.userId }} +
+
+ + + + {{ item.userName }} ({{ item.userId }}) + +
+
+
+
+
+ +
+
+

{{ "menu" | translate }}

+ +
+ + + + + + + + + + + + + + + + + + +
#{{ "type" | translate }}{{ "allow" | translate }}
{{ i + 1 }}{{ item.endpoint | translate }}
+
+ +
+ +
+
+
+
\ No newline at end of file diff --git a/src/app/menu/menu.component.scss b/src/app/menu/menu.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/app/menu/menu.component.spec.ts b/src/app/menu/menu.component.spec.ts new file mode 100644 index 0000000..7419205 --- /dev/null +++ b/src/app/menu/menu.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { MenuComponent } from './menu.component'; + +describe('MenuComponent', () => { + let component: MenuComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [MenuComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(MenuComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/menu/menu.component.ts b/src/app/menu/menu.component.ts new file mode 100644 index 0000000..8de6097 --- /dev/null +++ b/src/app/menu/menu.component.ts @@ -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(URIKey.TRANSACTION_ENDPOINTS).subscribe((response) => { + if (!(response instanceof HttpErrorResponse)) { + this.menuItems = response.map(endpoint => ({ + endpoint, + checked: false + })); + } + }); +} + + + defaultPermissions(): Observable { + return this.httpService.requestGET('assets/data/sideMenu.json'); + } + + getAllUsers() { + this.httpService.requestGET(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); + } + } + } + } +} \ No newline at end of file diff --git a/src/app/shared/components/side-nav/side-nav.component.html b/src/app/shared/components/side-nav/side-nav.component.html index 5e7b2e9..c617868 100644 --- a/src/app/shared/components/side-nav/side-nav.component.html +++ b/src/app/shared/components/side-nav/side-nav.component.html @@ -96,6 +96,12 @@ --> +
  • + + + {{ 'menu' | translate }} + +
  • diff --git a/src/app/user-management/reset-password/reset-password.component.ts b/src/app/user-management/reset-password/reset-password.component.ts index 3f1fcef..e386fea 100644 --- a/src/app/user-management/reset-password/reset-password.component.ts +++ b/src/app/user-management/reset-password/reset-password.component.ts @@ -169,6 +169,7 @@ export class ResetPasswordComponent implements OnInit{ .set('size', '1000'); this.httpService.requestGET(URIKey.GET_ALL_USER_URI, params).subscribe({ + next: (response) => { this.allUsersDropdown = response || []; this.isLoading = false; diff --git a/src/app/utils/app.interfaces.ts b/src/app/utils/app.interfaces.ts index 82d3eff..0ba657c 100644 --- a/src/app/utils/app.interfaces.ts +++ b/src/app/utils/app.interfaces.ts @@ -4,6 +4,10 @@ export interface PermissionNode { expanded: boolean; children?: PermissionNode[]; buttons?: PermissionNode[]; + accountToAccount?: boolean; + glToGl?: boolean; + accountToGl?: boolean; + glToAccount?: boolean; } export interface LogsManagementResponse { diff --git a/src/app/utils/uri-enums.ts b/src/app/utils/uri-enums.ts index 9842569..0fb8fb8 100644 --- a/src/app/utils/uri-enums.ts +++ b/src/app/utils/uri-enums.ts @@ -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" + + } \ No newline at end of file diff --git a/src/assets/data/app.uri.json b/src/assets/data/app.uri.json index 4a0ad4e..44ee2a7 100644 --- a/src/assets/data/app.uri.json +++ b/src/assets/data/app.uri.json @@ -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" } ] } diff --git a/src/assets/data/sideMenu.json b/src/assets/data/sideMenu.json index beaed46..1e64e9b 100644 --- a/src/assets/data/sideMenu.json +++ b/src/assets/data/sideMenu.json @@ -70,6 +70,13 @@ } ] }, +{ + "name": "menu", + "route": "/home/menu", + "checked": false, + "expanded": false, + "children": [] +}, { "name": "permissions", "route": "/home/permissions", diff --git a/src/assets/i18n/Arabic.json b/src/assets/i18n/Arabic.json index 27726a9..2a4c5a2 100644 --- a/src/assets/i18n/Arabic.json +++ b/src/assets/i18n/Arabic.json @@ -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": "٢٠" } diff --git a/src/assets/i18n/English.json b/src/assets/i18n/English.json index d642fc2..bd8e133 100644 --- a/src/assets/i18n/English.json +++ b/src/assets/i18n/English.json @@ -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" } diff --git a/tsconfig.json b/tsconfig.json index 5525117..2f1a8d6 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,6 +3,7 @@ { "compileOnSave": false, "compilerOptions": { + "rootDir": "./src", "outDir": "./dist/out-tsc", "strict": true, "noImplicitOverride": true,