Integrate CRUD Apis for Setup User #15

Closed
mazdak.gibran wants to merge 22 commits from dev-pending-09-12-2025 into mazdak/UX-1572

@ -4,6 +4,7 @@ import { BehaviorSubject, Observable } from 'rxjs';
import { URIKey } from '../utils/uri-enums'; import { URIKey } from '../utils/uri-enums';
import { URIService } from '../app.uri'; import { URIService } from '../app.uri';
import { HttpURIService } from '../app.http.uri.service'; import { HttpURIService } from '../app.http.uri.service';
import { HttpParams } from '@angular/common/http';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
@ -12,15 +13,11 @@ export class UserSetupService {
private usersSubject = new BehaviorSubject<SetupUser[]>([]); private usersSubject = new BehaviorSubject<SetupUser[]>([]);
private currentPageSubject = new BehaviorSubject<number>(1); private currentPageSubject = new BehaviorSubject<number>(1);
private totalCountSubject = new BehaviorSubject<number>(0); private totalCountSubject = new BehaviorSubject<number>(0);
private searchTextSubject = new BehaviorSubject<string>('');
private itemsPerPageSubject = new BehaviorSubject<number>(5); private itemsPerPageSubject = new BehaviorSubject<number>(5);
private paginatedUsersSubject = new BehaviorSubject<SetupUser[]>([]);
users$ = this.usersSubject.asObservable(); users$ = this.usersSubject.asObservable();
currentPage$ = this.currentPageSubject.asObservable(); currentPage$ = this.currentPageSubject.asObservable();
totalCount$ = this.totalCountSubject.asObservable(); totalCount$ = this.totalCountSubject.asObservable();
searchText$ = this.searchTextSubject.asObservable();
itemsPerPage$ = this.itemsPerPageSubject.asObservable(); itemsPerPage$ = this.itemsPerPageSubject.asObservable();
paginatedUsers$ = this.paginatedUsersSubject.asObservable();
constructor(private httpURIService: HttpURIService, private uriService: URIService) { } constructor(private httpURIService: HttpURIService, private uriService: URIService) { }
@ -34,7 +31,7 @@ loadUsers(): void {
const users = Array.isArray(res) ? res : res?.data; const users = Array.isArray(res) ? res : res?.data;
this.usersSubject.next(users ?? []); this.usersSubject.next(users ?? []);
this.totalCountSubject.next(users.length); this.totalCountSubject.next(users.length);
this.applyPagination();
}, },
error: (err) => console.error(err) error: (err) => console.error(err)
}); });
@ -42,36 +39,10 @@ loadUsers(): void {
}); });
} }
private applyPagination(): void {
const allUsers = this.usersSubject.value;
const searchText = this.searchTextSubject.value.toLowerCase();
const currentPage = this.currentPageSubject.value;
const itemsPerPage = this.itemsPerPageSubject.value;
let filtered = allUsers.filter(user =>
user.userId.toLowerCase().includes(searchText) ||
user.userFullname.toLowerCase().includes(searchText) ||
user.email.toLowerCase().includes(searchText)
);
const totalCount = filtered.length;
const startIndex = (currentPage - 1) * itemsPerPage;
const paginatedUsers = filtered.slice(startIndex, startIndex + itemsPerPage);
this.paginatedUsersSubject.next(paginatedUsers);
this.totalCountSubject.next(totalCount);
}
setSearchText(searchText: string): void {
this.searchTextSubject.next(searchText);
this.currentPageSubject.next(1);
this.applyPagination();
}
setItemsPerPage(itemsPerPage: number): void { setItemsPerPage(itemsPerPage: number): void {
this.itemsPerPageSubject.next(itemsPerPage); this.itemsPerPageSubject.next(itemsPerPage);
this.currentPageSubject.next(1); this.currentPageSubject.next(1);
this.applyPagination();
} }
nextPage(): void { nextPage(): void {
@ -79,7 +50,6 @@ loadUsers(): void {
const currentPage = this.currentPageSubject.value; const currentPage = this.currentPageSubject.value;
if (currentPage < totalPages) { if (currentPage < totalPages) {
this.currentPageSubject.next(currentPage + 1); this.currentPageSubject.next(currentPage + 1);
this.applyPagination();
} }
} }
@ -87,7 +57,7 @@ loadUsers(): void {
const currentPage = this.currentPageSubject.value; const currentPage = this.currentPageSubject.value;
if (currentPage > 1) { if (currentPage > 1) {
this.currentPageSubject.next(currentPage - 1); this.currentPageSubject.next(currentPage - 1);
this.applyPagination();
} }
} }
@ -95,7 +65,7 @@ loadUsers(): void {
const totalPages = this.getTotalPages(); const totalPages = this.getTotalPages();
if (page > 0 && page <= totalPages) { if (page > 0 && page <= totalPages) {
this.currentPageSubject.next(page); this.currentPageSubject.next(page);
this.applyPagination();
} }
} }
@ -109,4 +79,17 @@ loadUsers(): void {
return this.httpURIService.requestPOST<SetupUser>(URIKey.CREATE_USER, payload); return this.httpURIService.requestPOST<SetupUser>(URIKey.CREATE_USER, payload);
} }
getUserById(userId: any){
const params = new HttpParams().set('userId', userId)
return this.httpURIService.requestGET(URIKey.GET_USER_BY_ID, params);
}
deleteUser(userId: any){
const params = new HttpParams().set('userId', userId)
console.log("params success",params)
return this.httpURIService.requestDELETE(URIKey.DELETE_USER, params)
}
} }

@ -0,0 +1,22 @@
import { Pipe, PipeTransform } from '@angular/core';
import { SetupUser } from '../../models/user';
@Pipe({
name: 'userFilter',
standalone: true
})
export class UserFilterPipe implements PipeTransform {
transform(users: SetupUser[], searchText: string): SetupUser[] {
if (!users || !searchText.trim()) {
return users;
}
const search = searchText.toLowerCase();
return users.filter(user =>
user.userId.toLowerCase().includes(search) ||
user.userFullname.toLowerCase().includes(search) ||
user.email.toLowerCase().includes(search)
);
}
}

@ -88,7 +88,7 @@
</div> </div>
<div class="card-body"> <div class="card-body">
<form> <form [formGroup]="changePasswordForm">
<div class="row g-3 mb-3"> <div class="row g-3 mb-3">
<div class="col-md-6"> <div class="col-md-6">
<div class="d-flex align-items-center gap-2"> <div class="d-flex align-items-center gap-2">
@ -100,14 +100,16 @@
<div class="d-flex flex-row align-items-stretch"> <div class="d-flex flex-row align-items-stretch">
<input type="text" id="oldPassword" <input type="text" id="oldPassword"
class="form-control" class="form-control"
formControlName="oldPassword"
type="{{passwordType}}" type="{{passwordType}}"
placeholder="{{ 'oldPassword' | translate }}" appNoWhitespaces placeholder="{{ 'oldPassword' | translate }}" appNoWhitespaces
/> />
<app-password-hide-show #psh class="password-eye align-items-stretch" [showPassword]="true" (onEyeClick)="togglePasswordType()"></app-password-hide-show> <app-password-hide-show #psh class="password-eye align-items-stretch" [showPassword]="true" (onEyeClick)="togglePasswordType()"></app-password-hide-show>
</div> </div>
<!-- <div class="text-danger"> <div class="text-danger" *ngIf="changePasswordForm.get('oldPassword')?.touched &&
{{ 'requiredField' | translate }} changePasswordForm.get('oldPassword')?.invalid">
</div> --> {{ 'fieldRequired' | translate }}
</div>
</div> </div>
</div> </div>
</div> </div>
@ -122,6 +124,7 @@
<input id="enterNewPassword" <input id="enterNewPassword"
class="form-control" class="form-control"
formControlName="enterNewPassword"
type="{{passwordType1}}" type="{{passwordType1}}"
maxlength="500" maxlength="500"
placeholder="{{ 'enterNewPassword' | translate }}" appNoWhitespaces placeholder="{{ 'enterNewPassword' | translate }}" appNoWhitespaces
@ -130,6 +133,9 @@
</div> </div>
<div class="text-danger" *ngIf="newPasswordError$">
{{ newPasswordError$ | translate }}
</div>
</div> </div>
</div> </div>
@ -145,6 +151,7 @@
<input id="confirmPassword" <input id="confirmPassword"
class="form-control" class="form-control"
formControlName="confirmPassword"
type="{{passwordType2}}" type="{{passwordType2}}"
maxlength="500" maxlength="500"
placeholder="{{ 'confirmPassword' | translate }}" appNoWhitespaces placeholder="{{ 'confirmPassword' | translate }}" appNoWhitespaces
@ -153,6 +160,9 @@
</div> </div>
<div class="text-danger" *ngIf="confirmPasswordError$">
{{ confirmPasswordError$ | translate }}
</div>
</div> </div>
</div> </div>
<div class="col-md-6"> <div class="col-md-6">
@ -163,7 +173,8 @@
<div class="col-md-6 ms-auto text-end"> <div class="col-md-6 ms-auto text-end">
<button <button
class="btn btn-primary waves-effect waves-light" class="btn btn-primary waves-effect waves-light"
(click)="onSubmit()"
[disabled]="changePasswordForm.invalid"
>{{'save' | translate}}</button> >{{'save' | translate}}</button>

@ -1,10 +1,10 @@
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { Component, ViewChild } from '@angular/core'; import { Component, OnInit, ViewChild } from '@angular/core';
import { FormControl, FormGroup, FormsModule, ReactiveFormsModule } from '@angular/forms'; import { AbstractControl, FormBuilder, FormControl, FormGroup, FormsModule, ReactiveFormsModule, ValidationErrors, Validators } from '@angular/forms';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
import { PasswordHideShowComponent } from '../../shared/components/password-hide-show/password-hide-show.component'; import { PasswordHideShowComponent } from '../../shared/components/password-hide-show/password-hide-show.component';
import { StorageService } from '../../shared/services/storage.service'; import { HttpURIService } from '../../app.http.uri.service';
import { Router } from '@angular/router'; import { URIKey } from '../../utils/uri-enums';
@Component({ @Component({
selector: 'app-change-password', selector: 'app-change-password',
@ -13,15 +13,12 @@ import { Router } from '@angular/router';
styleUrl: './change-password.component.scss' styleUrl: './change-password.component.scss'
}) })
export class ChangePasswordComponent{ export class ChangePasswordComponent implements OnInit{
isFirstLogin = false; isFirstLogin = false;
loginForm!: FormGroup; changePasswordForm!: FormGroup;
currentLanguage = new FormControl(); currentLanguage = new FormControl();
httpService: any; httpService: any;
constructor(private storageService: StorageService, private router: Router){}
onLangChange() {
throw new Error('Method not implemented.');
}
passwordType: string = 'password'; passwordType: string = 'password';
passwordType1: string = 'password'; passwordType1: string = 'password';
passwordType2: string = 'password'; passwordType2: string = 'password';
@ -29,6 +26,7 @@ passwordType2: string = 'password';
@ViewChild('psh') passwordHideShow?: PasswordHideShowComponent; @ViewChild('psh') passwordHideShow?: PasswordHideShowComponent;
@ViewChild('psh1') passwordHideShow1 ?: PasswordHideShowComponent; @ViewChild('psh1') passwordHideShow1 ?: PasswordHideShowComponent;
@ViewChild('psh2') passwordHideShow2 ?: PasswordHideShowComponent; @ViewChild('psh2') passwordHideShow2 ?: PasswordHideShowComponent;
constructor(private fb: FormBuilder, private httpURIService: HttpURIService){}
togglePasswordType() { togglePasswordType() {
this.passwordType = this.passwordHideShow?.showPassword ? 'password' : 'text'; this.passwordType = this.passwordHideShow?.showPassword ? 'password' : 'text';
@ -40,31 +38,53 @@ passwordType2: string = 'password';
this.passwordType2 = this.passwordHideShow2?.showPassword ? 'password' : 'text'; this.passwordType2 = this.passwordHideShow2?.showPassword ? 'password' : 'text';
} }
ngOnInit(): void { passwordMatchValidator(group: AbstractControl): ValidationErrors | null {
// Call the method to check if first-time login const newPassword = group.get('enterNewPassword')?.value;
this.checkIfFirstTimeChangePasswordOrNot(); const confirmPassword = group.get('confirmPassword')?.value;
return newPassword === confirmPassword ? null : { passwordMismatch: true };
}
ngOnInit(): void {
this.changePasswordForm = this.fb.group({
oldPassword: ['', Validators.required],
enterNewPassword: ['',[ Validators.required, Validators.minLength(6)]],
confirmPassword: ['', [Validators.required, Validators.minLength(6)]]
},
{
validators: this.passwordMatchValidator
}
)
}
get newPasswordError$() {
const control = this.changePasswordForm.get('newPassword');
if (!control || !control.touched) return null;
if (control.hasError('required')) return 'fieldRequired';
if (control.hasError('minlength')) return 'passwordTooShort';
return null;
}
get confirmPasswordError$() {
const control = this.changePasswordForm.get('confirmPassword');
if (!control || !control.touched) return null;
if (control.hasError('required')) return 'fieldRequired';
if (control.hasError('minlength')) return 'passwordTooShort';
if (this.changePasswordForm.hasError('passwordMismatch')) return 'passwordsDoNotMatch';
return null;
} }
checkIfFirstTimeChangePasswordOrNot() { onSubmit(){
const fromMenu = history.state?.['fromMenu']; if(this.changePasswordForm.invalid){return}
if (fromMenu) { const payload = {
this.isFirstLogin = false; oldPassword: this.changePasswordForm.value.oldPassword,
} else { newPassword: this.changePasswordForm.value.enterNewPassword
try {
const currentUser: any = JSON.parse(this.storageService.getItem('user') || '{}');
// Check if user exists and has isFirstLogin flag
if (currentUser?.user?.isFirstLogin) {
this.isFirstLogin = true;
} else {
this.isFirstLogin = false;
}
} catch (error) {
console.error('Error parsing user data:', error);
this.isFirstLogin = false;
}
} }
this.httpURIService.requestPOST(URIKey.CHANGE_PASSWORD_URI, payload)
.subscribe();
} }
} }

@ -22,27 +22,30 @@
{{'resetPassword' | translate}} {{'resetPassword' | translate}}
</div> </div>
<div class="card-body"> <div class="card-body">
<form > <form [formGroup]="resetPasswordForm">
<div class="row g-3 mb-3"> <div class="row g-3 mb-3">
<div class="col-md-6"> <div class="col-md-6">
<div class="d-flex align-items-center gap-2"> <div class="d-flex align-items-center gap-2">
<label for="userID" class="text-nowrap"> <label for="userID" class="text-nowrap" >
{{ 'userID' | translate }}<span {{ 'userID' | translate }}<span
class="mandatory">*</span> class="mandatory">*</span>
</label> </label>
<div class="password-wrapper position-relative w-100"> <div class="password-wrapper position-relative w-100">
<div class="d-flex flex-row align-items-stretch"> <div class="d-flex flex-row align-items-stretch">
<input type="text" id="userID" <input
type="text"
id="userID"
class="form-control" class="form-control"
formControlName="userId"
placeholder="{{ 'userID' | translate }}" appNoWhitespaces placeholder="{{ 'userID' | translate }}"
appNoWhitespaces
/> />
</div> </div>
<!-- <div class="text-danger"> <div class="text-danger" *ngIf="resetPasswordForm.get('userId')?.touched &&
{{ 'requiredField' | translate }} resetPasswordForm.get('userId')?.invalid">
</div> --> {{ 'fieldRequired' | translate }}
</div>
</div> </div>
</div> </div>
</div> </div>
@ -53,9 +56,10 @@
{{ 'enterNewPassword' | translate }}<span {{ 'enterNewPassword' | translate }}<span
class="mandatory">*</span> class="mandatory">*</span>
</label> </label>
<div class="password-wrapper position-relative w-100"> <div class="w-100">
<div class="password-wrapper">
<input id="enterNewPassword" <input id="enterNewPassword"
formControlName="newPassword"
class="form-control" class="form-control"
autocomplete="new-password" autocomplete="new-password"
type="{{passwordType1}}" type="{{passwordType1}}"
@ -63,12 +67,15 @@
placeholder="{{ 'enterNewPassword' | translate }}" appNoWhitespaces placeholder="{{ 'enterNewPassword' | translate }}" appNoWhitespaces
rows="3" /> rows="3" />
<app-password-hide-show #psh1 class="password-eye align-items-stretch" [showPassword]="true" (onEyeClick)="togglePasswordType1()"></app-password-hide-show> <app-password-hide-show #psh1 class="password-eye align-items-stretch" [showPassword]="true" (onEyeClick)="togglePasswordType1()"></app-password-hide-show>
</div>
<div class="text-danger mt-1" *ngIf="newPasswordError">
{{ newPasswordError | translate }}
</div> </div>
</div> </div>
</div> </div>
</div>
</div> </div>
<div class="row g-3 mb-3"> <div class="row g-3 mb-3">
<div class="col-md-6"> <div class="col-md-6">
@ -77,22 +84,20 @@
{{ 'confirmPassword' | translate }}<span {{ 'confirmPassword' | translate }}<span
class="mandatory">*</span> class="mandatory">*</span>
</label> </label>
<div class="password-wrapper position-relative w-100"> <div class="w-100">
<div class="password-wrapper">
<input id="confirmPassword" <input id="confirmPassword"
class="form-control" class="form-control"
type="{{passwordType2}}" type="{{passwordType2}}"
formControlName="confirmPassword"
placeholder="{{ 'confirmPassword' | translate }}" appNoWhitespaces/> placeholder="{{ 'confirmPassword' | translate }}" appNoWhitespaces/>
<app-password-hide-show class="password-eye align-items-stretch" #psh2 [showPassword]="true" (onEyeClick)="togglePasswordType2()"></app-password-hide-show> <app-password-hide-show class="password-eye align-items-stretch" #psh2 [showPassword]="true" (onEyeClick)="togglePasswordType2()"></app-password-hide-show>
<!-- <div class="text-danger">
<div>
{{ 'requiredField' | translate }}
</div>
</div>
<div>
{{ 'expiryBeforeRenewal' | translate }}
</div> -->
</div> </div>
<div class="text-danger" *ngIf="confirmPasswordError">
{{ confirmPasswordError | translate }}
</div>
</div>
</div> </div>
</div> </div>
<div class="col-md-6"> <div class="col-md-6">
@ -103,14 +108,12 @@
<div class="col-md-6 ms-auto text-end"> <div class="col-md-6 ms-auto text-end">
<button <button
class="btn btn-primary waves-effect waves-light" class="btn btn-primary waves-effect waves-light"
(click)="onSubmit()"
[disabled]="resetPasswordForm.invalid"
>{{'save' | translate}}</button> >{{'save' | translate}}</button>
</div> </div>
</div> </div>
</form> </form>
</div> </div>
</div> </div>

@ -1,26 +1,88 @@
import { Component, ViewChild } from '@angular/core'; import { Component, ViewChild, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators, AbstractControl, ValidationErrors, ReactiveFormsModule } from '@angular/forms';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
import { CommonModule } from '@angular/common';
import { PasswordHideShowComponent } from '../../shared/components/password-hide-show/password-hide-show.component'; import { PasswordHideShowComponent } from '../../shared/components/password-hide-show/password-hide-show.component';
import { URIKey } from '../../utils/uri-enums';
import { HttpURIService } from '../../app.http.uri.service';
@Component({ @Component({
selector: 'app-reset-password', selector: 'app-reset-password',
imports: [TranslateModule, PasswordHideShowComponent], imports: [TranslateModule, PasswordHideShowComponent, CommonModule, ReactiveFormsModule],
templateUrl: './reset-password.component.html', templateUrl: './reset-password.component.html',
styleUrl: './reset-password.component.scss' styleUrl: './reset-password.component.scss'
}) })
export class ResetPasswordComponent { export class ResetPasswordComponent implements OnInit{
passwordType1: string = 'password'; resetPasswordForm!: FormGroup
passwordType2: string = 'password'; passwordType1: string = 'password';
passwordType2: string = 'password';
@ViewChild('psh1') passwordHideShow1?: PasswordHideShowComponent;
@ViewChild('psh2') passwordHideShow2?: PasswordHideShowComponent;
constructor(private fb: FormBuilder, private httpURIService: HttpURIService){}
@ViewChild('psh1') passwordHideShow1 ?: PasswordHideShowComponent; ngOnInit(): void {
@ViewChild('psh2') passwordHideShow2 ?: PasswordHideShowComponent; this.resetPasswordForm = this.fb.group({
userId: ['', Validators.required],
newPassword: ['', [Validators.required, Validators.minLength(6)]],
confirmPassword: ['', [Validators.required, Validators.minLength(6)]]
},
{
validators: this.passwordMatchValidator
}
);
this.resetPasswordForm.get('newPassword')?.valueChanges.subscribe(()=>{
this.resetPasswordForm.get('confirmPassword')?.updateValueAndValidity();
});
}
togglePasswordType1() { togglePasswordType1() {
this.passwordType1 = this.passwordHideShow1?.showPassword ? 'password' : 'text'; this.passwordType1 = this.passwordHideShow1?.showPassword ? 'password' : 'text';
} }
togglePasswordType2() { togglePasswordType2() {
this.passwordType2 = this.passwordHideShow2?.showPassword ? 'password' : 'text'; this.passwordType2 = this.passwordHideShow2?.showPassword ? 'password' : 'text';
} }
passwordMatchValidator(group: AbstractControl): ValidationErrors | null {
const newPassword = group.get('newPassword')?.value;
const confirmPassword = group.get('confirmPassword')?.value;
return newPassword === confirmPassword ? null : { passwordMismatch: true };
}
get newPasswordError() {
const control = this.resetPasswordForm.get('newPassword');
if (!control || !control.touched) return null;
if (control.hasError('required')) return 'fieldRequired';
if (control.hasError('minlength')) return 'passwordTooShort';
return null;
}
get confirmPasswordError() {
const control = this.resetPasswordForm.get('confirmPassword');
if (!control || !control.touched) return null;
if (control.hasError('required')) return 'fieldRequired';
if (control.hasError('minlength')) return 'passwordTooShort';
if (this.resetPasswordForm.hasError('passwordMismatch')) return 'passwordsDoNotMatch';
return null;
}
onSubmit() {
if (this.resetPasswordForm.invalid) return;
const payload = {
userId: this.resetPasswordForm.value.userId,
newPassword: this.resetPasswordForm.value.newPassword
};
this.httpURIService.requestPOST(URIKey.RESET_PASSWORD_URI, payload)
.subscribe();
}
} }

@ -22,7 +22,7 @@
{{'setupUser' | translate}} {{'setupUser' | translate}}
</div> </div>
<div class="card-body"> <div class="card-body">
<form> <form [formGroup]="userForm">
<div class="row g-3 mb-3"> <div class="row g-3 mb-3">
<div class="col-md-6"> <div class="col-md-6">
<div class="d-flex align-items-center gap-2"> <div class="d-flex align-items-center gap-2">
@ -34,15 +34,15 @@
<div class="d-flex flex-row align-items-stretch"> <div class="d-flex flex-row align-items-stretch">
<input type="text" id="userId" <input type="text" id="userId"
class="form-control" class="form-control"
[(ngModel)]="userId" formControlName="userId"
name="userId" name="userId"
placeholder="{{ 'userID' | translate }}" appNoWhitespaces placeholder="{{ 'userID' | translate }}" appNoWhitespaces
/> />
</div>
</div>
<!-- <div class="text-danger"> <div class="text-danger" *ngIf="userForm.get('userId')?.touched && userForm.get('userId')?.invalid">
{{ 'requiredField' | translate }} {{ 'fieldRequired' | translate }}
</div> --> </div>
</div> </div>
</div> </div>
</div> </div>
@ -57,15 +57,18 @@
<input id="userFullname" <input id="userFullname"
class="form-control" class="form-control"
[(ngModel)]="userFullname" formControlName="userFullname"
name="userFullname" name="userFullname"
maxlength="500" maxlength="500"
placeholder="{{ 'userName' | translate }}" appNoWhitespaces placeholder="{{ 'userName' | translate }}" appNoWhitespaces
rows="3" /> rows="3" />
<div class="text-danger" *ngIf="userForm.get('userFullname')?.touched && userForm.get('userFullname')?.invalid">
{{ 'fieldRequired' | translate }}
</div>
</div> </div>
</div> </div>
</div> </div>
@ -80,19 +83,15 @@
<div class="password-wrapper position-relative w-100"> <div class="password-wrapper position-relative w-100">
<input id="defaultPassword" <input id="defaultPassword"
class="form-control" class="form-control"
[(ngModel)]="defaultPassword" formControlName="defaultPassword"
name="defaultPassword" name="defaultPassword"
placeholder="{{ 'passwordPlaceHolder' | translate }}" appNoWhitespaces/> placeholder="{{ 'passwordPlaceHolder' | translate }}" appNoWhitespaces/>
<!-- <div class="text-danger"> <div class="text-danger" *ngIf="userForm.get('defaultPassword')?.touched && userForm.get('defaultPassword')?.invalid">
<div> {{ 'fieldRequired' | translate }}
{{ 'requiredField' | translate }} </div>
</div>
</div>
<div>
{{ 'expiryBeforeRenewal' | translate }}
</div> -->
</div> </div>
</div> </div>
</div> </div>
<div class="col-md-6"> <div class="col-md-6">
@ -101,12 +100,11 @@
</div> </div>
<div class="row g-3 mb-3"> <div class="row g-3 mb-3">
<div class="col-md-6 ms-auto text-end"> <div class="col-md-6 ms-auto text-end">
<button <button type="button" class="btn btn-primary waves-effect waves-light" (click)="onSubmit()" [disabled]="userForm.invalid"
type="button" >
class="btn btn-primary waves-effect waves-light" {{ 'save' | translate }}
(click)="onSubmit()" </button>
>{{'save' | translate}}</button>
</div> </div>
</div> </div>
@ -138,7 +136,7 @@
<div class="search-box"> <div class="search-box">
<input type="text" class="form-control form-control-sm" <input type="text" class="form-control form-control-sm"
[(ngModel)]="searchText" [(ngModel)]="searchText"
(ngModelChange)="onSearch(searchText)" (ngModelChange)="onSearch($event)"
placeholder="{{ 'search' | translate }}"> placeholder="{{ 'search' | translate }}">
<i class="fas fa-search search-icon"></i> <i class="fas fa-search search-icon"></i>
</div> </div>
@ -157,13 +155,19 @@
<table class="table mb-0 border"> <table class="table mb-0 border">
<thead class="table-light"> <thead class="table-light">
<tr> <tr>
<th style="width: 33%">{{'userID' | translate}}</th> <th style="width: 40%">{{'userID' | translate}}</th>
<th style="width: 33%">{{'Name' | translate}}</th> <th style="width: 40%">{{'Name' | translate}}</th>
<th style="width: 33%">{{'action' | translate}}</th> <th style="width: 20%">{{'action' | translate}}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr *ngFor="let item of allItems"> <tr *ngFor="
let item of (
(users$ | async) ?? []
| userFilter: searchText
).slice((currentPage - 1) * itemsPerPage, currentPage * itemsPerPage)
">
<td>{{ item.userId }}</td> <td>{{ item.userId }}</td>
<td>{{ item.userFullname }}</td> <td>{{ item.userFullname }}</td>
@ -171,21 +175,18 @@
<td> <td>
<div class="d-flex justify-content-center gap-2"> <div class="d-flex justify-content-center gap-2">
<button class="btn btn-info btn-sm" title="View"> <button class="btn btn-info btn-sm" title="View" (click)="onView(item.userId)">
<i class="mdi mdi-eye-outline"></i> <i class="mdi mdi-eye-outline"></i>
</button> </button>
<button class="btn btn-secondary btn-sm" title="Edit"> <button class="btn btn-danger btn-sm" title="Delete" (click)="onDelete(item.userId)">
<i class="fas fa-pen"></i>
</button>
<button class="btn btn-danger btn-sm" title="Delete">
<i class="fas fa-trash-alt"></i> <i class="fas fa-trash-alt"></i>
</button> </button>
</div> </div>
</td> </td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
<div class="d-flex justify-content-between align-items-center mt-3"> <div class="d-flex justify-content-between align-items-center mt-3">
@ -203,7 +204,7 @@
</div> </div>
<div class="text-muted"> <div class="text-muted">
{{ 'page' | translate }} {{ currentPage }} {{ 'of' | translate }} {{ totalPages() }} ({{ totalCount }} {{ 'totalItems' | translate }}) {{ 'page' | translate }} {{ currentPage }} {{ 'of' | translate }} {{ getTotalPages() }} ({{ totalCount }} {{ 'totalItems' | translate }})
</div> </div>
<div class="btn-group"> <div class="btn-group">

@ -6,15 +6,24 @@ import { TranslateModule } from '@ngx-translate/core';
import { pageSizeOptions } from '../../utils/app.constants'; import { pageSizeOptions } from '../../utils/app.constants';
import { SetupUser } from '../../models/user'; import { SetupUser } from '../../models/user';
import { UserSetupService } from '../../services/user-setup.service'; import { UserSetupService } from '../../services/user-setup.service';
import { UserFilterPipe } from '../../shared/pipes/userFilterPipe';
import { FormBuilder, Validators, FormGroup } from '@angular/forms';
@Component({ @Component({
selector: 'app-setup-user', selector: 'app-setup-user',
standalone: true, standalone: true,
imports: [TranslateModule, ReactiveFormsModule, FormsModule, CommonModule, NgSelectModule], imports: [TranslateModule, ReactiveFormsModule, FormsModule, CommonModule, NgSelectModule, UserFilterPipe],
templateUrl: './setup-user.component.html', templateUrl: './setup-user.component.html',
styleUrl: './setup-user.component.scss' styleUrl: './setup-user.component.scss'
}) })
export class SetupUserComponent implements OnInit { export class SetupUserComponent implements OnInit {
userForm!: FormGroup;
showForm = false;
selectedUserId!: any;
showDeleteModal = false;
userIdToDelete: any = null;
allItems: SetupUser[] = []; allItems: SetupUser[] = [];
currentPage: number = 1; currentPage: number = 1;
pageSizeOptions = pageSizeOptions pageSizeOptions = pageSizeOptions
@ -22,13 +31,16 @@ export class SetupUserComponent implements OnInit {
searchText: string = ''; searchText: string = '';
renewalDataExpanded: boolean = true; renewalDataExpanded: boolean = true;
totalCount: number = 0; totalCount: number = 0;
userId!: string; mode: 'edit' | 'view' = 'view';
userFullname!: string;
defaultPassword!: string;
constructor(private userService: UserSetupService){}
constructor(private userService: UserSetupService, private fb: FormBuilder){}
get users$(){
return this.userService.users$;
}
onSearch(value: string): void { onSearch(value: string): void {
this.userService.setSearchText(value); this.searchText = value;
} }
onPageSizeChange(pageSize: number): void { onPageSizeChange(pageSize: number): void {
@ -42,52 +54,83 @@ export class SetupUserComponent implements OnInit {
previousPage(): void { previousPage(): void {
this.userService.previousPage(); this.userService.previousPage();
} }
totalPages() {
return 1;
}
toggleCard(arg0: string) {
throw new Error('Method not implemented.'); getTotalPages(): number {
return this.userService.getTotalPages();
} }
onSubmit() { onSubmit() {
if(!this.userId || !this.userFullname|| !this.defaultPassword){ if (this.userForm.invalid) {
console.warn('Form incomplete'); this.userForm.markAllAsTouched();
return return;
} }
const newUser : SetupUser = { const newUser : SetupUser = {
userId: this.userId, userId: this.userForm.value.userId,
userFullname: this.userFullname, userFullname: this.userForm.value.userFullname,
email: `${this.userId}@dummy.com` ,// temporary placeholder email: `${this.userForm.value.userId}@dummy.com`,
role: 'ADMIN', role: 'ADMIN',
defaultPassword: this.defaultPassword defaultPassword: this.userForm.value.defaultPassword
} }
this.userService.addUser(newUser).subscribe({ this.userService.addUser(newUser).subscribe({
next: () => { next: () => {
this.userService.loadUsers(); this.userService.loadUsers();
this.userId = ''; this.userService.loadUsers();
this.userFullname = ''; this.userForm.reset();
this.defaultPassword = ''; this.mode = 'edit';
}, },
error: (err: any) => console.error(err) error: (err: any) => console.error(err)
}); });
}
onView(userId: any){
this.mode = 'view';
this.showForm = true;
this.selectedUserId = userId;
this.userService.getUserById(userId).subscribe((user: any)=>{
this.userForm.patchValue({
userId : user.userId,
userFullname : user.userFullname,
defaultPassword : '',
})
})
}
onDelete(userId: any){
this.userService.deleteUser(userId).subscribe({
next: (res: any) => {
this.userService.loadUsers();
this.userForm.reset()
this.selectedUserId = null;
},
error: (err:any) =>{
console.log('user not deleted')
}
});
} }
ngOnInit(): void { ngOnInit(): void {
this.userForm = this.fb.group({
userId: ['', [Validators.required]],
userFullname: ['', [Validators.required, Validators.maxLength(500)]],
defaultPassword: ['', Validators.required]
});
this.userService.loadUsers(); this.userService.loadUsers();
this.userService.paginatedUsers$.subscribe((users: SetupUser[]) => this.allItems = users);
this.userService.users$.subscribe((users: SetupUser[]) => {
this.allItems = users;
});
this.userService.currentPage$.subscribe((page: number) => { this.userService.currentPage$.subscribe((page: number) => {
this.currentPage = page; this.currentPage = page;
}); });
this.userService.totalCount$.subscribe((count: number) => { this.userService.totalCount$.subscribe((count: number) => {
this.totalCount = count; this.totalCount = count;
}); });
this.userService.searchText$.subscribe((text: string) => {
this.searchText = text;
});
this.userService.itemsPerPage$.subscribe((size: number) => { this.userService.itemsPerPage$.subscribe((size: number) => {
this.itemsPerPage = size; this.itemsPerPage = size;
}); });

@ -1 +1,95 @@
<p>user-permissions works!</p> <div class="page-content">
<div class="container-fluid">
<div class="row mt-2 mb-2">
<div class="col-lg-6">
<div class="col-6">
<div class="mt-0 mb-2 d-sm-flex align-items-center justify-content-between font-edit-13">
<h5 class="text-muted fw-bold mt-3">{{'permissionManagement' | translate}}</h5>
</div>
</div>
<div class="card">
<div class="card-body">
<form [formGroup]="permission">
<div class="form-group row mb-2">
<label for="userCode" class="form-label font-edit-13">{{ 'userCode' |
translate
}}</label>
<div class="input-group">
<select class="form-select" id="userCode" formControlName="userCode" (change)="onUserChange()">
<option selected disabled value="">{{ 'choose' |
translate }}
</option>
<option *ngFor="let user of users" [value]="user.userId">
{{user.userName}}
</option>
</select>
</div>
</div>
</form>
</div>
</div>
<div class="card" *ngIf="showPermissions">
<div class="card-body">
<h4 class="card-title">{{ 'permissions' | translate}}</h4>
<hr>
<ul>
<li *ngFor="let node of permissions">
<input
type="checkbox"
[checked]="node.checked"
(change)="toggleNode(node, $event)">
{{node.name | translate}}
<span (click)="toggleExpand(node)">
<span *ngIf="node.children && node.children.length">
<span *ngIf="node.expanded"><i class="bx bx-chevron-down"></i></span>
<span *ngIf="!node.expanded"><i class="bx bx-chevron-right"></i></span>
</span>
</span>
<ul *ngIf="node.children && node.children.length > 0 && node.expanded">
<ng-container *ngTemplateOutlet="treeTemplate; context: { $implicit: node.children }"></ng-container>
</ul>
<ul *ngIf="node.buttons && node.buttons.length > 0 && node.expanded">
<ng-container *ngTemplateOutlet="treeTemplate; context: { $implicit: node.buttons }"></ng-container>
</ul>
</li>
</ul>
<ng-template #treeTemplate let-nodes>
<ul>
<li *ngFor="let node of nodes">
<input
type="checkbox"
[checked]="node.checked"
(change)="toggleNode(node, $event)">
{{node.name | translate}}
<span (click)="toggleExpand(node)">
<span *ngIf="node.children && node.children.length">
<span *ngIf="node.expanded"><i class="bx bx-chevron-down"></i></span>
<span *ngIf="!node.expanded"><i class="bx bx-chevron-right"></i></span>
</span>
<span *ngIf="node.buttons && node.buttons.length">
<span *ngIf="node.expanded"><i class="bx bx-chevron-down"></i></span>
<span *ngIf="!node.expanded"><i class="bx bx-chevron-right"></i></span>
</span>
</span>
<ul *ngIf="node.children && node.children.length > 0 && node.expanded">
<ng-container *ngTemplateOutlet="treeTemplate; context: { $implicit: node.children }"></ng-container>
</ul>
<ul *ngIf="node.buttons && node.buttons.length > 0 && node.expanded">
<ng-container *ngTemplateOutlet="treeTemplate; context: { $implicit: node.buttons }"></ng-container>
</ul>
</li>
</ul>
</ng-template>
<div class="row float-end me-4 mb-3 mt-2 font-edit-13">
<button type="button" class="btn btn-primary font-edit-13 px-3 btn-sm" (click)="savePermissions()">{{ 'save' | translate }}</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>

@ -0,0 +1,19 @@
li {
margin: 5px 0;
}
ul {
list-style-type: none;
margin: 0;
padding-left: 10px; /* Reduce overall left padding */
}
ul ul {
padding-left: 5px; /* Reduce space for nested items */
margin-left: 5px;
}
li {
margin-bottom: 3px; /* Add slight vertical spacing between items */
}

@ -1,11 +1,180 @@
import { Component } from '@angular/core'; import { Component } from '@angular/core';
import { FormBuilder, FormGroup, ReactiveFormsModule } from '@angular/forms';
import { PermissionNode } from '../utils/app.constants';
import { Observable } from 'rxjs';
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 { FormConstants, HiddenValues, SuccessMessages } from '../utils/enums';
import { CommonModule } from '@angular/common';
@Component({ @Component({
selector: 'app-user-permissions', selector: 'app-user-permissions',
imports: [], imports: [TranslateModule, ReactiveFormsModule, CommonModule],
templateUrl: './user-permissions.component.html', templateUrl: './user-permissions.component.html',
styleUrl: './user-permissions.component.scss' styleUrl: './user-permissions.component.scss'
}) })
export class UserPermissionsComponent { export class UserPermissionsComponent {
dropdownOptions: { [key: string]: any[] } = {};
users: any[] = [];
permission: FormGroup;
showPermissions = false;
permissions: PermissionNode[] = [];
constructor(private credentialService: CredentialService, private fb: FormBuilder, private httpService: HttpURIService, private i18nService: I18NService) {
this.permission = this.fb.group({
allocation: [''],
userCode: [''],
userRole: [''],
});
this.defaultPermissions().subscribe((data: PermissionNode[]) => {
this.permissions = data;
});
}
ngOnInit(){
this.getAllUsers();
}
defaultPermissions(): Observable<PermissionNode[]> {
return this.httpService.requestGET<PermissionNode[]>('assets/data/sideMenu.json');
}
toggleNode(node: PermissionNode, event: Event): void {
node.checked = (event.target as HTMLInputElement).checked;
if (node.children) {
this.toggleChildren(node.children, node.checked);
}
if (node.buttons) {
this.toggleButtons(node.buttons, node.checked);
}
this.updateParentState(node);
}
toggleChildren(children: PermissionNode[], isChecked: boolean): void {
children.forEach(child => {
child.checked = isChecked;
if (child.children) {
this.toggleChildren(child.children, isChecked);
}
if (child.buttons) {
this.toggleButtons(child.buttons, child.checked);
}
});
}
toggleButtons(buttons: PermissionNode[], isChecked: boolean): void {
buttons.forEach(button => {
button.checked = isChecked;
});
}
updateParentState(node: PermissionNode): void {
const parent = this.findParent(node, this.permissions);
if (parent) {
parent.checked = parent.children?.some(child => child.checked) || false;
this.updateParentState(parent); // Recursively update ancestors
}
}
findParent(target: PermissionNode, nodes: PermissionNode[]): PermissionNode | null {
for (let node of nodes) {
if (node.children?.includes(target)) {
return node;
}
if (node.children) {
const parent = this.findParent(target, node.children);
if (parent) return parent;
}
}
return null;
}
toggleExpand(node: PermissionNode): void {
node.expanded = !node.expanded;
}
populateDropdowns(data: any) {
data.forEach((control: any) => {
this.dropdownOptions[control.controlId] = control.options;
});
}
onAllocationChange(event: Event): void {
this.showPermissions = false;
const selectedValue = (event.target as HTMLInputElement).value;
if (selectedValue === "R") {
this.permission.get('userCode')?.reset();
}
else if (selectedValue === "U") {
this.permission.get('userRole')?.reset();
}
}
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 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)) {
if (response.permissions) {
this.updatePermissions(JSON.parse(response.permissions), this.permissions);
}
else {
this.defaultPermissions().subscribe((data: PermissionNode[]) => {
this.permissions = data;
});
}
}
})
}
savePermissions() {
let payload = {
// porOrgacode: this.credentialService.getPorOrgacode(),
userId: this.permission.get('userCode')?.value,
permissions: JSON.stringify(this.permissions)
}
this.httpService.requestPUT(URIKey.USER_SAVE_PERMISSION, payload).subscribe((response: any) => {
if (!(response instanceof HttpErrorResponse)) {
this.i18nService.success(SuccessMessages.SAVED_SUCESSFULLY, []);
this.permission.reset();
this.permission.get('userCode')?.setValue("");
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);
}
}
}
}
} }

@ -3,5 +3,12 @@ export enum URIKey {
USER_LOGIN_URI = "USER_LOGIN_URI", USER_LOGIN_URI = "USER_LOGIN_URI",
USER_REFRESH_TOKEN = "USER_REFRESH_TOKEN", USER_REFRESH_TOKEN = "USER_REFRESH_TOKEN",
CREATE_USER = 'CREATE_USER', CREATE_USER = 'CREATE_USER',
GET_ALL_USERS = 'GET_ALL_USERS' GET_ALL_USERS = 'GET_ALL_USERS',
GET_USER_BY_ID = 'GET_USER_BY_ID',
DELETE_USER = 'DELETE_USER',
USER_SAVE_PERMISSION = "USER_SAVE_PERMISSION",
USER_GET_PERMISSIONS = "USER_GET_PERMISSIONS",
GET_ALL_USER_URI = "GET_ALL_USER_URI",
RESET_PASSWORD_URI = "RESET_PASSWORD_URI",
CHANGE_PASSWORD_URI = "CHANGE_PASSWORD_URI"
} }

@ -17,6 +17,11 @@
"URI": "/refreshtoken", "URI": "/refreshtoken",
"UUID": "USER_REFRESH_TOKEN" "UUID": "USER_REFRESH_TOKEN"
}, },
{
"Id": "ENTITY_GET_ALL_USER_URI",
"URI": "/user/getAllUsers",
"UUID": "GET_ALL_USER_URI"
},
{ {
"Id": "ENTITY_CREATE_USER", "Id": "ENTITY_CREATE_USER",
"URI": "/user/createUser", "URI": "/user/createUser",
@ -26,6 +31,31 @@
"Id": "ENTITY_GET_ALL_USERS", "Id": "ENTITY_GET_ALL_USERS",
"URI": "/user/getAllUsers", "URI": "/user/getAllUsers",
"UUID": "GET_ALL_USERS" "UUID": "GET_ALL_USERS"
},
{
"Id" : "ENTITY_GET_USER_BY_ID",
"URI": "/user/getUser",
"UUID": "GET_USER_BY_ID"
},
{
"Id" : "ENTITY_DELETE_USER",
"URI": "/user/deleteUser",
"UUID": "DELETE_USER"
},
{
"Id" : "ENTITY_USER_GET_PERMISSIONS",
"URI": "/user/getPermissions",
"UUID": "USER_GET_PERMISSIONS"
},
{
"Id" : "ENTITY_RESET_PASSWORD_URI",
"URI": "/user/reset-password",
"UUID": "RESET_PASSWORD_URI"
},
{
"Id" : "ENTITY_CHANGE_PASSWORD_URI",
"URI": "/user/change-password",
"UUID": "CHANGE_PASSWORD_URI"
} }
] ]
} }

@ -210,21 +210,7 @@
"route": "/home/permissions", "route": "/home/permissions",
"checked": false, "checked": false,
"expanded": false, "expanded": false,
"children": [], "children": []
"buttons": [
{
"name": "edit",
"route": "",
"checked": false,
"expanded": false
},
{
"name": "delete",
"route": "",
"checked": false,
"expanded": false
}
]
} }
] ]
} }

@ -7,6 +7,8 @@
"defaultPassword": "كلمة المرور الافتراضية", "defaultPassword": "كلمة المرور الافتراضية",
"rememberMe":"تذكرنى", "rememberMe":"تذكرنى",
"forgotPassword":"هل نسيت كلمة السر؟", "forgotPassword":"هل نسيت كلمة السر؟",
"passwordTooShort": "كلمة المرور قصيرة جدًا.",
"passwordsDoNotMatch": "كلمتا المرور غير متطابقتين.",
"login":"تسجيل الدخول", "login":"تسجيل الدخول",
"dashboardTitle":"لوحة القيادة", "dashboardTitle":"لوحة القيادة",
"passwordChangeRequired": "تغيير كلمة المرور مطلوب", "passwordChangeRequired": "تغيير كلمة المرور مطلوب",
@ -222,5 +224,11 @@
"CONNECTION_ERROR": "خطأ في الاتصال", "CONNECTION_ERROR": "خطأ في الاتصال",
"BAD_REQUEST": "اقتراح غير جيد", "BAD_REQUEST": "اقتراح غير جيد",
"FORBIDDEN_REQUEST": "طلب ممنوع", "FORBIDDEN_REQUEST": "طلب ممنوع",
"UNAUTHORIZED_REQUEST": "طلب غير مصرح به" "UNAUTHORIZED_REQUEST": "طلب غير مصرح به",
"edit": "يحرر",
"delete": "يمسح",
"deleteUser": "حذف حساب المستخدم",
"permissionManagement": "إدارة الأذونات",
"userCode": "مستخدم",
"choose" : "يختار"
} }

@ -136,6 +136,8 @@
"passwordPatternNotMatched":"Password Pattern Not Matched", "passwordPatternNotMatched":"Password Pattern Not Matched",
"successDeleted":"Successfully Deleted", "successDeleted":"Successfully Deleted",
"passwordNotSame":"Password Cannot be same as Old Password", "passwordNotSame":"Password Cannot be same as Old Password",
"passwordTooShort": "Password is too short.",
"passwordsDoNotMatch": "Passwords do not match.",
"SuccessSave":"Successfully Saved", "SuccessSave":"Successfully Saved",
"SuccessFind":"Successfully Find", "SuccessFind":"Successfully Find",
"customerAlreadyUnblocked": "Customer Already UnBlocked", "customerAlreadyUnblocked": "Customer Already UnBlocked",
@ -221,5 +223,11 @@
"CONNECTION_ERROR": "Connection Error", "CONNECTION_ERROR": "Connection Error",
"BAD_REQUEST": "Bad Request", "BAD_REQUEST": "Bad Request",
"FORBIDDEN_REQUEST": "Forbidden Request", "FORBIDDEN_REQUEST": "Forbidden Request",
"UNAUTHORIZED_REQUEST": "Unauthorized Request" "UNAUTHORIZED_REQUEST": "Unauthorized Request",
"edit": "Edit",
"delete": "Delete",
"deleteUser": "Delete User",
"permissionManagement": "Permission Managment",
"userCode": "User",
"choose" : "Choose"
} }
Loading…
Cancel
Save