mazdak/UI-2896
#65
Merged
naeem.ullah
merged 4 commits from mazdak/UI-2896 into PRE-PRODUCTION-2026 1 month ago
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue