Merge pull request 'setup user email' (#34) from mazdak/setup-user-email into dev-pending-01-01-2026

Reviewed-on: https://ct.mfsys.com.pk/aConnect/aConnect-UX/pulls/34
mazdak/UX-1985
Naeem Ullah 3 weeks ago
commit bcf4b208cc

@ -5,10 +5,10 @@
<div class="col-xl-11"></div> <div class="col-xl-11"></div>
<div class="col-xl-1 mt-2"> <div class="col-xl-1 mt-2">
<div class="page-title-right float-end mx-3"> <div class="page-title-right float-end mx-3">
<select class="form-select" [formControl]="currentLanguage" (change)="onLangChange()" style="width: 100px;"> <ng-select class="form-select" [formControl]="currentLanguage" [items]="languageOptions" bindLabel="label"
<option [value]="supportedLanguages.ENGLISH">{{"english" | translate}}</option> bindValue="value" [clearable]="false" [searchable]="false" style="width: 100px;" (change)="onLangChange()">
<option [value]="supportedLanguages.ARABIC">{{"arabic" | translate}}</option> </ng-select>
</select>
</div> </div>
</div> </div>
</div> </div>

@ -13,10 +13,11 @@ import { directions, FormConstants, HiddenValues, supportedLanguages } from '../
import { environment } from '../../../environments/environment'; import { environment } from '../../../environments/environment';
import { AuthenticationService } from '../../services/authenticate.service'; import { AuthenticationService } from '../../services/authenticate.service';
import { UserCredentials } from '../authenticate'; import { UserCredentials } from '../authenticate';
import { NgSelectModule } from '@ng-select/ng-select';
@Component({ @Component({
selector: 'app-login', selector: 'app-login',
imports: [TranslateModule, ReactiveFormsModule, CommonModule, PasswordHideShowComponent], imports: [TranslateModule, ReactiveFormsModule, CommonModule, PasswordHideShowComponent, NgSelectModule],
templateUrl: './login.component.html', templateUrl: './login.component.html',
styleUrl: './login.component.scss' styleUrl: './login.component.scss'
}) })
@ -28,7 +29,11 @@ export class LoginComponent {
currentLanguage = new FormControl(); currentLanguage = new FormControl();
passwordType: string = 'password'; passwordType: string = 'password';
direction: string = ''; direction: string = '';
supportedLanguages = supportedLanguages; languageOptions = [
{ label: 'English', value: supportedLanguages.ENGLISH },
{ label: 'Arabic', value: supportedLanguages.ARABIC }
];
ucred: UserCredentials = new UserCredentials(); ucred: UserCredentials = new UserCredentials();
@ViewChild(PasswordHideShowComponent) passwordHideShow?: PasswordHideShowComponent; @ViewChild(PasswordHideShowComponent) passwordHideShow?: PasswordHideShowComponent;
@ -64,7 +69,7 @@ export class LoginComponent {
initializeLanguage(): void { initializeLanguage(): void {
if (isPlatformBrowser(this.platformId)) { if (isPlatformBrowser(this.platformId)) {
const savedLanguage = this.storageService.getItem('language') || 'English'; const savedLanguage = this.storageService.getItem('language') || supportedLanguages.ENGLISH;;
this.storageService.setItem('language', savedLanguage); this.storageService.setItem('language', savedLanguage);
this.currentLanguage.setValue(savedLanguage) this.currentLanguage.setValue(savedLanguage)
this.translateService.setDefaultLang(savedLanguage); this.translateService.setDefaultLang(savedLanguage);

@ -12,3 +12,17 @@ export interface SetupUser {
email: string; email: string;
role: string; role: string;
} }
export interface TransactionLog {
logId: number;
porOrgacode: string;
transactionID: string;
drMbmbkmsnumber: string;
crMbmbkmsnumber: string;
ppmPymdcode: string;
sgtGntrdate: string;
channelCode: string;
createdAt: string;
}

@ -0,0 +1,28 @@
import { Pipe, PipeTransform } from '@angular/core';
import { TransactionLog } from '../../models/user';
@Pipe({
name: 'transactionLogFilter',
standalone: true
})
export class TransactionLogFilterPipe implements PipeTransform {
transform(logs: TransactionLog[], searchText: string): TransactionLog[] {
if (!logs || !searchText) {
return logs;
}
const search = searchText.toLowerCase();
return logs.filter(log =>
log.logId?.toString().toLowerCase().includes(search) ||
log.porOrgacode?.toLowerCase().includes(search) ||
log.transactionID?.toLowerCase().includes(search) ||
log.drMbmbkmsnumber?.toLowerCase().includes(search) ||
log.crMbmbkmsnumber?.toLowerCase().includes(search) ||
log.ppmPymdcode?.toLowerCase().includes(search) ||
log.sgtGntrdate?.toLowerCase().includes(search) ||
log.channelCode?.toLowerCase().includes(search) ||
log.createdAt?.toLowerCase().includes(search)
);
}
}

@ -1,24 +1,71 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { map, Observable } from 'rxjs'; import { BehaviorSubject, map, Observable } from 'rxjs';
import { URIKey } from '../../utils/uri-enums'; import { URIKey } from '../../utils/uri-enums';
import { HttpErrorResponse, HttpParams } from '@angular/common/http'; import { HttpErrorResponse, HttpParams } from '@angular/common/http';
import { HttpURIService } from '../../app.http.uri.service'; import { HttpURIService } from '../../app.http.uri.service';
import { TransactionLog } from '../../models/user';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
}) })
export class TransactionLogService { export class TransactionLogService {
private logsSubject = new BehaviorSubject<TransactionLog[]>([]);
private currentPageSubject = new BehaviorSubject<number>(1);
private totalCountSubject = new BehaviorSubject<number>(0);
private itemsPerPageSubject = new BehaviorSubject<number>(5);
logs$ = this.logsSubject.asObservable();
currentPage$ = this.currentPageSubject.asObservable();
totalCount$ = this.totalCountSubject.asObservable();
itemsPerPage$ = this.itemsPerPageSubject.asObservable();
constructor(private httpUriService: HttpURIService) {} constructor(private httpUriService: HttpURIService) {}
getTransactionLogs(): Observable<any[]> { loadLogs(): void {
const params = new HttpParams(); const params = new HttpParams();
return this.httpUriService.requestGET(URIKey.TRANSACTION_LOGS, params).pipe(map((response: any) => { this.httpUriService
if (!(response instanceof HttpErrorResponse)) { .requestGET<any>(URIKey.TRANSACTION_LOGS, params)
return Array.isArray(response) ? response : []; .subscribe({
next: (res) => {
const logs = Array.isArray(res) ? res : res?.data;
this.logsSubject.next(logs ?? []);
this.totalCountSubject.next(logs.length);
},
error: (err) => console.error(err)
});
} }
return [];
}) setItemsPerPage(itemsPerPage: number): void {
); this.itemsPerPageSubject.next(itemsPerPage);
this.currentPageSubject.next(1);
}
nextPage(): void {
const totalPages = this.getTotalPages();
const currentPage = this.currentPageSubject.value;
if (currentPage < totalPages) {
this.currentPageSubject.next(currentPage + 1);
}
}
previousPage(): void {
const currentPage = this.currentPageSubject.value;
if (currentPage > 1) {
this.currentPageSubject.next(currentPage - 1);
}
}
goToPage(page: number): void {
const totalPages = this.getTotalPages();
if (page > 0 && page <= totalPages) {
this.currentPageSubject.next(page);
}
}
getTotalPages(): number {
const totalCount = this.totalCountSubject.value;
const itemsPerPage = this.itemsPerPageSubject.value;
return Math.ceil(totalCount / itemsPerPage);
} }
} }

@ -139,26 +139,6 @@
<td></td> <td></td>
<td></td> <td></td>
<!-- <td>
<div
class="d-flex justify-content-center gap-2">
<button class="btn btn-info btn-sm"
title="View">
<i class="mdi mdi-eye-outline"></i>
</button>
<button *ngIf="buttonPermissions?.edit" class="btn btn-secondary btn-sm"
title="Edit">
<i class="fas fa-pen"></i>
</button>
<button *ngIf="buttonPermissions?.delete" class="btn btn-danger btn-sm"
title="Delete">
<i class="fas fa-trash-alt"></i>
</button>
</div>
</td> -->
</tr> </tr>
</tbody> </tbody>
</table> </table>

@ -21,11 +21,28 @@
<div class="card mb-0 mt-2"> <div class="card mb-0 mt-2">
<div class="card-header font-edit-13-child d-flex justify-content-between align-items-center"> <div class="card-header font-edit-13-child d-flex justify-content-between align-items-center">
{{'transactionLogs' | translate}} {{'transactionLogs' | translate}}
<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($event)" placeholder="{{ 'search' | translate }}">
<i class="fas fa-search search-icon"></i>
</div>
<div class="d-flex align-items-center gap-2"> <div class="d-flex align-items-center gap-2">
<i (click)="exportDataInExcel()" id="downloadReport" class="fa fa-download"></i> <i (click)="exportDataInExcel()" id="downloadReport" class="fa fa-download"></i>
</div> </div>
<i class="materialdesignicons">
<ng-container *ngIf="renewalDataExpanded; else collapsedIcon">
<i class="dripicons-chevron-up float-end"></i>
</ng-container>
<ng-template #collapsedIcon>
<i class="dripicons-chevron-down float-end"></i>
</ng-template>
</i>
</div> </div>
</div>
<div class="card-body"> <div class="card-body">
<div *ngIf="isLoading" class="text-center text-muted"> <div *ngIf="isLoading" class="text-center text-muted">
@ -56,7 +73,12 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr *ngFor="let log of logs"> <tr *ngFor="
let log of (
(logs$ | async) ?? []
| transactionLogFilter: searchText
).slice((currentPage - 1) * itemsPerPage, currentPage * itemsPerPage)
">
<td>{{ log.logId }}</td> <td>{{ log.logId }}</td>
<td>{{ log.porOrgacode }}</td> <td>{{ log.porOrgacode }}</td>
<td>{{ log.transactionID }}</td> <td>{{ log.transactionID }}</td>
@ -69,6 +91,31 @@
</tr> </tr>
</tbody> </tbody>
</table> </table>
<div
class="d-flex justify-content-between align-items-center mt-3">
<div class="form-group mb-0">
<ng-select class="form-select-sm" [items]="pageSizeOptions" bindLabel="label" bindValue="value"
[(ngModel)]="itemsPerPage" (change)="onPageSizeChange(itemsPerPage)" [searchable]="false" [clearable]="false"
[dropdownPosition]="'top'">
</ng-select>
</div>
<div class="text-muted">
{{ 'page' | translate }} {{ currentPage }} {{ 'of' | translate }} {{ getTotalPages() }} ({{ totalCount }} {{ 'totalItems' | translate }})
</div>
<div class="btn-group">
<button
class="btn btn-primary waves-effect waves-light" (click)="previousPage()">
{{ 'previous' | translate }}
</button>
<button
class="btn btn-primary waves-effect waves-light" (click)="nextPage()">
{{ 'next' | translate }}
</button>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>

@ -4,62 +4,83 @@ import { CommonModule } from '@angular/common';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
import { ExcelExportService } from '../shared/services/excel-export.service'; import { ExcelExportService } from '../shared/services/excel-export.service';
import { TRANSACTION_LOGS_FILE_NAME } from '../utils/app.constants'; import { TRANSACTION_LOGS_FILE_NAME } from '../utils/app.constants';
import { pageSizeOptions } from '../utils/app.constants';
interface TransactionLog { import { NgSelectModule } from '@ng-select/ng-select';
logId: number; import { FormsModule, ReactiveFormsModule } from '@angular/forms';
porOrgacode: string; import { TransactionLogFilterPipe } from '../shared/pipes/transactionLogFilter.pipe';
drMbmbkmsnumber: string; import {TransactionLog} from "../models/user"
crMbmbkmsnumber: string;
crPcaglacode: string;
drPcaGlacode: string;
ppmPymdcode: string;
sgtGntrdate: string;
sgtGntrcreateat: string;
channelCode: string;
transactionID: string;
createdAt: string;
updatedAt: string;
}
@Component({ @Component({
selector: 'app-transaction-logs', selector: 'app-transaction-logs',
templateUrl: './transaction-logs.component.html', templateUrl: './transaction-logs.component.html',
providers: [TransactionLogService], providers: [TransactionLogService],
imports:[CommonModule, TranslateModule] imports: [CommonModule, TranslateModule, NgSelectModule, FormsModule, ReactiveFormsModule, TransactionLogFilterPipe, ]
}) })
export class TransactionLogsComponent implements OnInit { export class TransactionLogsComponent implements OnInit {
currentPage: number = 1;
totalCount: number = 0;
renewalDataExpanded: boolean = true;
pageSizeOptions = pageSizeOptions;
itemsPerPage: number = 5;
logs: TransactionLog[] = []; logs: TransactionLog[] = [];
isLoading = false; isLoading = false;
errorMessage: string = ''; errorMessage: string = '';
searchText: string = '';
constructor( constructor(
private transactionLogService: TransactionLogService, private transactionLogService: TransactionLogService,
private excelExportService: ExcelExportService private excelExportService: ExcelExportService
) {} ) {}
ngOnInit(): void { get logs$(){
this.loadLogs(); return this.transactionLogService.logs$;
} }
ngOnInit(): void {
this.transactionLogService.loadLogs();
loadLogs() { this.transactionLogService.logs$.subscribe((logs: TransactionLog[]) => {
this.isLoading = true; this.logs = logs;
this.errorMessage = ''; });
this.transactionLogService.getTransactionLogs().subscribe({ this.transactionLogService.currentPage$.subscribe((page) => {
next: (res) => { this.currentPage = page;
this.logs = res; });
this.isLoading = false;
}, this.transactionLogService.totalCount$.subscribe((count) => {
error: (err) => { this.totalCount = count;
console.error('Failed to load transaction logs', err); });
this.errorMessage = 'Failed to load transaction logs. Please try again.';
this.isLoading = false; this.transactionLogService.itemsPerPage$.subscribe((size) => {
} this.itemsPerPage = size;
}); });
} }
onSearch(value: string): void {
this.searchText = value;
}
get paginatedLogs(): TransactionLog[] {
const start = (this.currentPage - 1) * this.itemsPerPage;
const end = start + this.itemsPerPage;
return this.logs.slice(start, end);
}
exportDataInExcel(){ exportDataInExcel(){
this.excelExportService.exportExcel(this.logs, TRANSACTION_LOGS_FILE_NAME) this.excelExportService.exportExcel(this.logs, TRANSACTION_LOGS_FILE_NAME)
} }
getTotalPages(): number {
return this.transactionLogService.getTotalPages();
}
onPageSizeChange(pageSize: number): void {
this.transactionLogService.setItemsPerPage(pageSize);
}
nextPage(): void {
this.transactionLogService.nextPage();
}
previousPage(): void {
this.transactionLogService.previousPage();
}
} }

@ -126,7 +126,7 @@
<label for="userRole" class="text-nowrap"> <label for="userRole" class="text-nowrap">
{{ 'SelectRole' | translate }}<span class="mandatory">*</span> {{ 'SelectRole' | translate }}<span class="mandatory">*</span>
</label> </label>
<div class="password-wrapper position-relative w-100"> <div class="position-relative w-100">
<ng-select id="userRole" class="form-select" formControlName="userRole" [items]="roleOptions" bindLabel="label" <ng-select id="userRole" class="form-select" formControlName="userRole" [items]="roleOptions" bindLabel="label"
bindValue="value" placeholder="{{ 'SelectRole' | translate }}" > bindValue="value" placeholder="{{ 'SelectRole' | translate }}" >
</ng-select> </ng-select>

@ -81,7 +81,7 @@ export class SetupUserComponent implements OnInit {
const newUser : SetupUser = { const newUser : SetupUser = {
userId: this.userForm.value.userId, userId: this.userForm.value.userId,
userFullname: this.userForm.value.userFullname, userFullname: this.userForm.value.userFullname,
email: `${this.userForm.value.userId}@dummy.com`, email: this.userForm.value.email,
role: this.userForm.value.userRole, role: this.userForm.value.userRole,
porOrgacode: this.storageService.getItem('POR_ORGACODE'), porOrgacode: this.storageService.getItem('POR_ORGACODE'),
password: this.userForm.value.defaultPassword password: this.userForm.value.defaultPassword
@ -138,7 +138,7 @@ ngOnInit(): void {
userFullname: ['', [Validators.required, Validators.maxLength(500)]], userFullname: ['', [Validators.required, Validators.maxLength(500)]],
defaultPassword: ['', Validators.required], defaultPassword: ['', Validators.required],
email: ['', [Validators.required, Validators.email]], email: ['', [Validators.required, Validators.email]],
userRole: ['', Validators.required] userRole: [null, Validators.required]
}); });
this.userService.loadUsers(); this.userService.loadUsers();

@ -283,8 +283,9 @@
<div class="d-flex justify-content-between align-items-center mt-3"> <div class="d-flex justify-content-between align-items-center mt-3">
<div class="form-group mb-0"> <div class="form-group mb-0">
<ng-select class="form-select-sm" [items]="pageSizeOptions" bindLabel="label" bindValue="value" <ng-select class="form-select-sm" [items]="pageSizeOptions" bindLabel="label" bindValue="value"
[(ngModel)]="itemsPerPage" [searchable]="false" [clearable]="false"> [(ngModel)]="itemsPerPage" [searchable]="false" [clearable]="false" appendTo="body" [dropdownPosition]="'top'">
</ng-select> </ng-select>
</div> </div>
<div class="text-muted"> <div class="text-muted">

@ -12,17 +12,10 @@
</label> </label>
<div class="col-sm-4"> <div class="col-sm-4">
<select <ng-select class="form-select" formControlName="userCode" [items]="users" bindLabel="userName" bindValue="userId"
class="form-select" placeholder="{{ 'choose' | translate }}" (change)="onUserChange()">
formControlName="userCode" </ng-select>
(change)="onUserChange()">
<option value="" disabled selected>
{{ 'choose' | translate }}
</option>
<option *ngFor="let user of users" [value]="user.userId">
{{ user.userName }}
</option>
</select>
</div> </div>
</div> </div>

@ -10,10 +10,11 @@ import { URIKey } from '../utils/uri-enums';
import { HttpErrorResponse, HttpParams } from '@angular/common/http'; import { HttpErrorResponse, HttpParams } from '@angular/common/http';
import { FormConstants, HiddenValues, SuccessMessages } from '../utils/enums'; import { FormConstants, HiddenValues, SuccessMessages } from '../utils/enums';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { NgSelectModule } from '@ng-select/ng-select';
@Component({ @Component({
selector: 'app-user-permissions', selector: 'app-user-permissions',
imports: [TranslateModule, ReactiveFormsModule, CommonModule], imports: [TranslateModule, ReactiveFormsModule, CommonModule, TranslateModule, NgSelectModule],
templateUrl: './user-permissions.component.html', templateUrl: './user-permissions.component.html',
styleUrl: './user-permissions.component.scss' styleUrl: './user-permissions.component.scss'
}) })
@ -27,7 +28,7 @@ export class UserPermissionsComponent {
constructor(private credentialService: CredentialService, private fb: FormBuilder, private httpService: HttpURIService, private i18nService: I18NService) { constructor(private credentialService: CredentialService, private fb: FormBuilder, private httpService: HttpURIService, private i18nService: I18NService) {
this.permission = this.fb.group({ this.permission = this.fb.group({
allocation: [''], allocation: [''],
userCode: [''], userCode: [null],
userRole: [''], userRole: [''],
}); });

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

@ -166,7 +166,7 @@ ng-select.form-select-sm {
border: none !important; border: none !important;
} }
.ng-select .ng-select-container{ .ng-select .ng-select-container{
width: 98% !important; width: 100% !important;
} }
#downloadReport { #downloadReport {

Loading…
Cancel
Save