Merge branch 'dev-pending-09-12-2025' into aconnect-UX/1715

aconnect-UX/1715
atif118-mfsys 4 hours ago
commit 180ecd38a1

@ -3,3 +3,11 @@ export class User {
Email?: string="";
Password: string="";
}
export interface SetupUser {
userId: string;
userFullname: string;
defaultPassword: string;
email: string;
role: string;
}

@ -0,0 +1,126 @@
import { Injectable } from '@angular/core';
import { SetupUser } from '../models/user';
import { BehaviorSubject, Observable } from 'rxjs';
import { URIKey } from '../utils/uri-enums';
import { URIService } from '../app.uri';
import { HttpURIService } from '../app.http.uri.service';
import { HttpParams } from '@angular/common/http';
@Injectable({
providedIn: 'root'
})
export class UserSetupService {
private usersSubject = new BehaviorSubject<SetupUser[]>([]);
private currentPageSubject = new BehaviorSubject<number>(1);
private totalCountSubject = new BehaviorSubject<number>(0);
private searchTextSubject = new BehaviorSubject<string>('');
private itemsPerPageSubject = new BehaviorSubject<number>(5);
private paginatedUsersSubject = new BehaviorSubject<SetupUser[]>([]);
users$ = this.usersSubject.asObservable();
currentPage$ = this.currentPageSubject.asObservable();
totalCount$ = this.totalCountSubject.asObservable();
searchText$ = this.searchTextSubject.asObservable();
itemsPerPage$ = this.itemsPerPageSubject.asObservable();
paginatedUsers$ = this.paginatedUsersSubject.asObservable();
constructor(private httpURIService: HttpURIService, private uriService: URIService) { }
loadUsers(): void {
this.uriService.canSubscribe.subscribe(can => {
if (can) {
this.httpURIService
.requestGET<any>(URIKey.GET_ALL_USERS)
.subscribe({
next: (res) => {
const users = Array.isArray(res) ? res : res?.data;
this.usersSubject.next(users ?? []);
this.totalCountSubject.next(users.length);
this.applyPagination();
},
error: (err) => console.error(err)
});
}
});
}
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 {
this.itemsPerPageSubject.next(itemsPerPage);
this.currentPageSubject.next(1);
this.applyPagination();
}
nextPage(): void {
const totalPages = this.getTotalPages();
const currentPage = this.currentPageSubject.value;
if (currentPage < totalPages) {
this.currentPageSubject.next(currentPage + 1);
this.applyPagination();
}
}
previousPage(): void {
const currentPage = this.currentPageSubject.value;
if (currentPage > 1) {
this.currentPageSubject.next(currentPage - 1);
this.applyPagination();
}
}
goToPage(page: number): void {
const totalPages = this.getTotalPages();
if (page > 0 && page <= totalPages) {
this.currentPageSubject.next(page);
this.applyPagination();
}
}
getTotalPages(): number {
const totalCount = this.totalCountSubject.value;
const itemsPerPage = this.itemsPerPageSubject.value;
return Math.ceil(totalCount / itemsPerPage);
}
addUser(payload: SetupUser): Observable<SetupUser> {
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)
}
}

@ -30,7 +30,7 @@
</a>
</li>
<li>
<a routerLink="/home/changePassword" routerLinkActive="mm-active" style="cursor: pointer">
<a routerLink="/home/changePassword" routerLinkActive="mm-active" (click)="navigateToChangePassword()" style="cursor: pointer">
<span> {{ 'changePassword' | translate }}</span>
</a>
</li>

@ -40,6 +40,12 @@ export class SideNavComponent {
this.sidebarService.currentSubModule = this.storageService.getItem('currentSubModule') ?? 'dashboard';
this.closeSidebarMenu();
}
navigateToChangePassword() {
this.router.navigate(['/home/changePassword'], {
state: { fromMenu: true }
});
}
closeSidebarMenu(): void {
if (isPlatformBrowser(this.platformId)) {
const subMenus = document.querySelectorAll('#sidebar-menu .sub-menu');

@ -47,7 +47,7 @@
<!-- Submit Button -->
<div class="mt-3 d-grid">
<button class="btn btn-primary waves-effect waves-light" type="submit">
<button class="btn btn-primary waves-effect waves-light" type="button">
{{'save' | translate}}
</button>
</div>

@ -41,18 +41,30 @@ passwordType2: string = 'password';
}
ngOnInit(): void {
// Call the method to check if first-time login
this.checkIfFirstTimeChangePasswordOrNot();
}
checkIfFirstTimeChangePasswordOrNot(){
let currentUser: any = JSON.parse(this.storageService.getItem('user')!)
if(currentUser?.user?.isFirstLogin){
this.isFirstLogin = true;
}
else{
checkIfFirstTimeChangePasswordOrNot() {
const fromMenu = history.state?.['fromMenu'];
if (fromMenu) {
this.isFirstLogin = false;
} else {
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;
}
}
}
}

@ -26,15 +26,16 @@
<div class="row g-3 mb-3">
<div class="col-md-6">
<div class="d-flex align-items-center gap-2">
<label for="userID" class="text-nowrap">
{{ 'userID' | translate }}<span
<label for="userId" class="text-nowrap">
{{ 'userId' | translate }}<span
class="mandatory">*</span>
</label>
<div class="password-wrapper position-relative w-100">
<div class="d-flex flex-row align-items-stretch">
<input type="text" id="userID"
<input type="text" id="userId"
class="form-control"
[(ngModel)]="userId"
name="userId"
placeholder="{{ 'userID' | translate }}" appNoWhitespaces
/>
@ -54,9 +55,10 @@
</label>
<div class="password-wrapper position-relative w-100">
<input id="name"
<input id="userFullname"
class="form-control"
[(ngModel)]="userFullname"
name="userFullname"
maxlength="500"
placeholder="{{ 'userName' | translate }}" appNoWhitespaces
rows="3" />
@ -71,15 +73,16 @@
<div class="row g-3 mb-3">
<div class="col-md-6">
<div class="d-flex align-items-center gap-2">
<label for="phoneNumber" class="text-nowrap">
{{ 'phoneNumber' | translate }}<span
<label for="defaultPassword" class="text-nowrap">
{{ 'defaultPassword' | translate }}<span
class="mandatory">*</span>
</label>
<div class="password-wrapper position-relative w-100">
<input id="phoneNumber"
<input id="defaultPassword"
class="form-control"
placeholder="{{ 'userContactNumber' | translate }}" appNoWhitespaces/>
[(ngModel)]="defaultPassword"
name="defaultPassword"
placeholder="{{ 'passwordPlaceHolder' | translate }}" appNoWhitespaces/>
<!-- <div class="text-danger">
<div>
@ -98,10 +101,10 @@
</div>
<div class="row g-3 mb-3">
<div class="col-md-6 ms-auto text-end">
<button
class="btn btn-primary waves-effect waves-light"
>{{'save' | translate}}</button>
<button type="button" class="btn btn-primary waves-effect waves-light" (click)="onSubmit()"
[hidden]="mode === 'view' && showForm">
{{ 'save' | translate }}
</button>
</div>
@ -133,6 +136,8 @@
<div class="d-flex align-items-center gap-2">
<div class="search-box">
<input type="text" class="form-control form-control-sm"
[(ngModel)]="searchText"
(ngModelChange)="onSearch(searchText)"
placeholder="{{ 'search' | translate }}">
<i class="fas fa-search search-icon"></i>
</div>
@ -151,31 +156,25 @@
<table class="table mb-0 border">
<thead class="table-light">
<tr>
<th>{{'userID' | translate}}</th>
<th>{{'Name' | translate}}</th>
<th>{{'phoneNumber' | translate}}</th>
<th>{{'action' | translate}}</th>
<th style="width: 40%">{{'userID' | translate}}</th>
<th style="width: 40%">{{'Name' | translate}}</th>
<th style="width: 20%">{{'action' | translate}}</th>
</tr>
</thead>
<tbody>
<tr>
<td></td>
<td></td>
<td></td>
<tr *ngFor="let item of allItems">
<td>{{ item.userId }}</td>
<td>{{ item.userFullname }}</td>
<td>
<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>
</button>
<button class="btn btn-secondary btn-sm" title="Edit">
<i class="fas fa-pen"></i>
</button>
<button class="btn btn-danger btn-sm" title="Delete">
<button class="btn btn-danger btn-sm" title="Delete" (click)="onDelete(item.userId)">
<i class="fas fa-trash-alt"></i>
</button>
</div>
@ -190,6 +189,7 @@
bindLabel="label"
bindValue="value"
[(ngModel)]="itemsPerPage"
(change)="onPageSizeChange(itemsPerPage)"
[searchable]="false"
[clearable]="false"
[dropdownPosition]="'top'">
@ -197,14 +197,14 @@
</div>
<div class="text-muted">
{{ 'page' | translate }} {{ 'of' | translate }} ({{ 'totalItems' | translate }})
{{ 'page' | translate }} {{ currentPage }} {{ 'of' | translate }} {{ getTotalPages() }} ({{ totalCount }} {{ 'totalItems' | translate }})
</div>
<div class="btn-group">
<button class="btn btn-primary waves-effect waves-light">
<button class="btn btn-primary waves-effect waves-light" (click)="previousPage()">
{{ 'previous' | translate }}
</button>
<button class="btn btn-primary waves-effect waves-light">
<button class="btn btn-primary waves-effect waves-light" (click)="nextPage()">
{{ 'next' | translate }}
</button>
</div>

@ -1,9 +1,12 @@
import { CommonModule } from '@angular/common';
import { Component } from '@angular/core';
import { Component, OnInit } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { NgSelectModule } from '@ng-select/ng-select';
import { TranslateModule } from '@ngx-translate/core';
import { pageSizeOptions } from '../../utils/app.constants';
import { SetupUser } from '../../models/user';
import { UserSetupService } from '../../services/user-setup.service';
import { error } from 'node:console';
@Component({
selector: 'app-setup-user',
@ -12,20 +15,38 @@ import { pageSizeOptions } from '../../utils/app.constants';
templateUrl: './setup-user.component.html',
styleUrl: './setup-user.component.scss'
})
export class SetupUserComponent {
allItems: any[] = [];
export class SetupUserComponent implements OnInit {
allItems: SetupUser[] = [];
currentPage: number = 1;
pageSizeOptions = pageSizeOptions
itemsPerPage: number = 5;
searchText: any;
searchText: string = '';
renewalDataExpanded: boolean = true;
totalCount: number = 0;
userId!: string;
userFullname!: string;
defaultPassword!: string;
mode: 'edit' | 'view' = 'view';
showForm = false;
selectedUserId!: any;
user: any;
nextPage() {
throw new Error('Method not implemented.');
constructor(private userService: UserSetupService){}
onSearch(value: string): void {
this.userService.setSearchText(value);
}
onPageSizeChange(pageSize: number): void {
this.userService.setItemsPerPage(pageSize);
}
previousPage() {
throw new Error('Method not implemented.');
nextPage(): void {
this.userService.nextPage();
}
previousPage(): void {
this.userService.previousPage();
}
totalPages() {
@ -35,10 +56,80 @@ export class SetupUserComponent {
toggleCard(arg0: string) {
throw new Error('Method not implemented.');
}
getTotalPages(): number {
return this.userService.getTotalPages();
}
onSubmit() {
throw new Error('Method not implemented.');
if(!this.userId || !this.userFullname|| !this.defaultPassword){
console.warn('Form incomplete');
return
}
const newUser : SetupUser = {
userId: this.userId,
userFullname: this.userFullname,
email: `${this.userId}@dummy.com` ,// temporary placeholder
role: 'ADMIN',
defaultPassword: this.defaultPassword
}
this.userService.addUser(newUser).subscribe({
next: () => {
this.userService.loadUsers();
this.userId = '';
this.userFullname = '';
this.defaultPassword = '';
},
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.userId = user.userId;
this.userFullname = user.userFullname;
this.defaultPassword = '';
})
}
onDelete(userId: any){
this.userService.deleteUser(userId).subscribe({
next: (res: any) => {
this.userService.loadUsers();
this.showForm = false;
this.userId = '';
this.userFullname = '';
this.defaultPassword = '';
this.selectedUserId = null;
},
error: (err:any) =>{
console.log('user not deleted')
}
});
}
ngOnInit(): void {
this.userService.loadUsers();
this.userService.paginatedUsers$.subscribe((users: SetupUser[]) => this.allItems = users);
this.userService.currentPage$.subscribe((page: number) => {
this.currentPage = page;
});
this.userService.totalCount$.subscribe((count: number) => {
this.totalCount = count;
});
this.userService.searchText$.subscribe((text: string) => {
this.searchText = text;
});
this.userService.itemsPerPage$.subscribe((size: number) => {
this.itemsPerPage = size;
});
}
}

@ -2,6 +2,10 @@
export enum URIKey {
USER_LOGIN_URI = "USER_LOGIN_URI",
USER_REFRESH_TOKEN = "USER_REFRESH_TOKEN",
CREATE_USER = 'CREATE_USER',
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"

@ -21,6 +21,26 @@
"Id": "ENTITY_GET_ALL_USER_URI",
"URI": "/user/getAllUsers",
"UUID": "GET_ALL_USER_URI"
},
{
"Id": "ENTITY_CREATE_USER",
"URI": "/user/createUser",
"UUID": "CREATE_USER"
},
{
"Id": "ENTITY_GET_ALL_USERS",
"URI": "/user/getAllUsers",
"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"
}
]
}

@ -4,6 +4,7 @@
"userNamePlaceHolder":"ادخل اسم المستخدم",
"password":"كلمة المرور",
"passwordPlaceHolder":"أدخل كلمة المرور",
"defaultPassword": "كلمة المرور الافتراضية",
"rememberMe":"تذكرنى",
"forgotPassword":"هل نسيت كلمة السر؟",
"login":"تسجيل الدخول",
@ -96,6 +97,7 @@
"gridNum50":"خمسون",
"gridNum100":"مائة",
"userID":"معرف المستخدم",
"userId":"معرف المستخدم",
"userContactNumber":"أدخل رقم اتصال المستخدم",
"SelectHomeBranch":"حدد الفرع الرئيسي",
"SelectRole":"حدد الدور",

@ -5,6 +5,7 @@
"userNamePlaceHolder":"Enter Username",
"password":"Password",
"passwordPlaceHolder":"Enter Password",
"defaultPassword":"Default Password",
"rememberMe":"Remember me",
"forgotPassword":"Forgot password?",
"login":"Login",
@ -98,6 +99,7 @@
"gridNum50":"50",
"gridNum100":"100",
"userID":"User ID",
"userId":"User ID",
"userContactNumber":"Enter User Contact Number",
"SelectHomeBranch":"Select Home Branch",
"SelectRole":"Select Role",

Loading…
Cancel
Save