Merge branch 'dev-pending-20-01-2026' into PRE-PRODUCTION-2026

mazdak/UX-2367
Naeem Ullah 6 days ago
commit f7e8b340c8

@ -35,6 +35,10 @@ export class AppComponent {
this.direction = this.storageService.getItem('direction') || directions.LTR; this.direction = this.storageService.getItem('direction') || directions.LTR;
this.storageService.setItem('direction', this.direction); this.storageService.setItem('direction', this.direction);
// if (typeof document !== 'undefined') {
// document.documentElement.setAttribute('dir', this.direction);
// }
const userStr = this.storageService.getItem('user'); const userStr = this.storageService.getItem('user');
if (userStr) { if (userStr) {
try { try {

@ -29,7 +29,7 @@
<div class="card-body"> <div class="card-body">
<form [formGroup]="loginForm" class="form-horizontal"> <form [formGroup]="loginForm" class="form-horizontal">
<div class="mb-3"> <div class="mb-3">
<label for="USER_ID" class="form-label">{{"userName" | translate}}</label> <label for="USER_ID" class="form-label">{{"userId" | translate}}</label>
<div class="input-group auth-pass-inputgroup"> <div class="input-group auth-pass-inputgroup">
<input type="text" class="form-control" id="USER_ID" formControlName="USER_ID"> <input type="text" class="form-control" id="USER_ID" formControlName="USER_ID">
</div> </div>

@ -87,13 +87,13 @@ export class LoginComponent {
this.storageService.setItem('direction', this.direction); this.storageService.setItem('direction', this.direction);
} }
else { else {
this.direction = directions.RTL; this.direction = directions.LTR;
this.storageService.setItem('direction', this.direction); this.storageService.setItem('direction', this.direction);
} }
if (typeof document !== 'undefined') { if (typeof document !== 'undefined') {
document.documentElement.setAttribute('dir', this.direction); document.documentElement.setAttribute('dir', this.direction);
document.documentElement.setAttribute('lang', document.documentElement.setAttribute('lang',
this.direction === directions.RTL ? 'ar' : 'en'); this.direction === directions.LTR ? 'ar' : 'en');
} }
} }
@ -124,6 +124,7 @@ export class LoginComponent {
} }
} }
onLangChange() { onLangChange() {
const selectedLang = this.currentLanguage.value; const selectedLang = this.currentLanguage.value;

@ -1 +1,3 @@
<p>dashboard works!</p> <div class="dashboard-bg">
<p>dashboard works!</p>
</div>

@ -0,0 +1,14 @@
.dashboard-bg {
position: relative;
min-height: 100vh;
z-index: 1;
}
.dashboard-bg::after {
content: "";
position: absolute;
inset: 0;
background: url('/assets/images/logo.png') center / contain no-repeat;
background-size: 350px;
opacity: 0.5; /* less transparent */
z-index: -1;
}

@ -4,9 +4,7 @@
<div class="container-fluid"> <div class="container-fluid">
<div class="row"> <div class="row">
<div class="col-12"> <div class="col-12">
<div <div class="d-sm-flex align-items-center justify-content-between navbar-header p-0"></div>
class="d-sm-flex align-items-center justify-content-between navbar-header p-0"
></div>
</div> </div>
</div> </div>
</div> </div>
@ -21,7 +19,7 @@
</div> </div>
<form [formGroup]="logsSearchForm"> <form [formGroup]="logsSearchForm">
<div class="row g-3 mb-3 "> <div class="row g-3 mb-3">
<div class="col-md-6"> <div class="col-md-6">
<label> <label>
{{ "fromDate" | translate }} {{ "fromDate" | translate }}
@ -36,6 +34,10 @@
/> />
<i class="fas fa-calendar calendar-icon"></i> <i class="fas fa-calendar calendar-icon"></i>
</div> </div>
<div class="text-danger"
*ngIf="logsSearchForm.get('fromDate')?.touched && logsSearchForm.get('fromDate')?.invalid">
{{ 'fieldRequired' | translate }}
</div>
</div> </div>
<div class="col-md-6"> <div class="col-md-6">
@ -51,6 +53,16 @@
/> />
<i class="fas fa-calendar calendar-icon"></i> <i class="fas fa-calendar calendar-icon"></i>
</div> </div>
<div class="text-danger"
*ngIf="logsSearchForm.get('toDate')?.touched && logsSearchForm.get('toDate')?.invalid">
{{ 'fieldRequired' | translate }}
</div>
<div class="text-danger" *ngIf="
logsSearchForm.touched &&
logsSearchForm.errors?.['fromDateGreaterThanOrEqualToToDate']
">
{{ 'toDateInvalidError' | translate }}
</div>
</div> </div>
</div> </div>
@ -72,43 +84,49 @@
<div class="col-xl-12 mt-4"> <div class="col-xl-12 mt-4">
<div class="card border"> <div class="card border">
<div class="card-body"> <div class="card-body">
<div <div class="card-header d-flex justify-content-between align-items-center font-edit-13-child">
class="card-header d-flex justify-content-between align-items-center font-edit-13-child" {{ "activityLogDetails" | translate }}
>
{{ "activityLogDetails" | translate }}
<div class="d-flex gap-2 align-items-center" *ngIf="allItems.length"> <div class="d-flex gap-2 align-items-center" *ngIf="allItems.length">
<div class="search-box"> <div class="search-box">
<input <input
type="text" type="text"
class="form-control form-control-sm" class="form-control form-control-sm"
placeholder="{{ 'search' | translate }}" placeholder="{{ 'search' | translate }}"
[(ngModel)]="searchText" [(ngModel)]="searchText"
(ngModelChange)="applySearch()" (ngModelChange)="applySearch()"
/> />
<i class="fas fa-search search-icon"></i> <i class="fas fa-search search-icon"></i>
</div> </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> <button
</div> class="btn btn-sm btn-outline-success"
(click)="exportDataInExcel()"
<i class="materialdesignicons" (click)="toggleTableCard()"> [disabled]="!filteredItems.length"
<ng-container *ngIf="logsDataExpanded; else down"> title="Export to Excel"
<i class="dripicons-chevron-up float-end"></i> >
</ng-container> <i class="fa fa-download"></i>
</button>
</div>
<ng-template #down> <button
<i class="dripicons-chevron-down float-end"></i> class="btn btn-sm btn-outline-secondary"
</ng-template> (click)="toggleTableCard()"
</i> [title]="(logsDataExpanded ? 'collapse' : 'expand') | translate"
>
<i *ngIf="logsDataExpanded" class="dripicons-chevron-up"></i>
<i *ngIf="!logsDataExpanded" class="dripicons-chevron-down"></i>
</button>
</div>
</div> </div>
</div> </div>
</div>
<div class="card-body" *ngIf="logsDataExpanded"> <div class="card-body" *ngIf="logsDataExpanded">
<!-- NO RECORDS --> <!-- NO RECORDS -->
<div *ngIf="!filteredItems.length" class="text-center text-muted"> <div *ngIf="!filteredItems.length" class="text-center text-muted py-4">
{{ "noLoggingDetailsFound" | translate }} <i class="fas fa-search fa-2x mb-2 opacity-50"></i>
<p>{{ "noLoggingDetailsFound" | translate }}</p>
</div> </div>
<!-- TABLE --> <!-- TABLE -->
@ -137,43 +155,59 @@
</tbody> </tbody>
</table> </table>
<!-- FOOTER --> <!-- PAGINATION FOOTER -->
<div <div class="d-flex justify-content-between align-items-center mt-3 flex-wrap">
class="d-flex justify-content-between align-items-center mt-3" <!-- Items per page -->
> <div class="d-flex align-items-center gap-2 mb-2 mb-md-0">
<ng-select <span class="text-muted small">{{ "show" | translate }}</span>
[items]="pageSizeOptions" <div style="width: 120px;">
bindLabel="label" <ng-select
bindValue="value" class="form-select-sm"
[(ngModel)]="itemsPerPage" [items]="pageSizeOptions"
(change)="itemsPerPageChanged()" bindValue="value"
[searchable]="false" [(ngModel)]="itemsPerPage"
[clearable]="false"> (change)="itemsPerPageChanged()"
</ng-select> [searchable]="false"
[clearable]="false"
[dropdownPosition]="'top'"
>
<ng-template ng-option-tmp let-item="item">
{{ item.value }} {{ "entries" | translate }}
</ng-template>
<div class="text-muted"> <ng-template ng-label-tmp let-item="item">
{{ item.value }} {{ "entries" | translate }}
</ng-template>
</ng-select>
</div>
</div>
<!-- Page info -->
<div class="text-muted mb-2 mb-md-0">
{{ "page" | translate }} {{ currentPage }} {{ "page" | translate }} {{ currentPage }}
{{ "of" | translate }} {{ totalPages() }} ({{ {{ "of" | translate }} {{ totalPages() }}
filteredItems.length <span class="d-none d-md-inline">
}} ({{ filteredItems.length }} {{ "totalItems" | translate }})
{{ "totalItems" | translate }}) </span>
</div> </div>
<!-- Pagination buttons -->
<div class="btn-group"> <div class="btn-group">
<button <button
class="btn btn-primary" class="btn btn-outline-primary btn-sm"
(click)="previousPage()" (click)="previousPage()"
[disabled]="currentPage === 1" [disabled]="currentPage === 1"
> >
<i class="fas fa-chevron-left me-1"></i>
{{ "previous" | translate }} {{ "previous" | translate }}
</button> </button>
<button <button
class="btn btn-primary" class="btn btn-outline-primary btn-sm"
(click)="nextPage()" (click)="nextPage()"
[disabled]="currentPage >= totalPages()" [disabled]="currentPage >= totalPages()"
> >
{{ "next" | translate }} {{ "next" | translate }}
<i class="fas fa-chevron-right ms-1"></i>
</button> </button>
</div> </div>
</div> </div>
@ -183,4 +217,4 @@
</div> </div>
</div> </div>
</div> </div>
</div> </div>

@ -162,8 +162,22 @@ export class LoggingComponent implements OnInit {
} }
exportDataInExcel(): void { exportDataInExcel(): void {
const sanitizedData = this.filteredItems.map(item => {
if (item.requestBody) {
try {
const body = JSON.parse(item.requestBody);
if (body.password) {
body.password = '******';
}
return { ...item, requestBody: JSON.stringify(body) };
} catch {
return item;
}
}
return item;
});
this.excelExportService.exportExcel( this.excelExportService.exportExcel(
this.filteredItems, sanitizedData,
LOGGING_DETAILS_FILE_NAME, LOGGING_DETAILS_FILE_NAME,
); );
} }

@ -24,11 +24,11 @@ export class AuthenticationService {
constructor(private buttonManagementService: ButtonManagementService, private httpService: HttpURIService, private router: Router, private credentialService: CredentialService, private i18nService: I18NService, private storageService: StorageService) { constructor(private buttonManagementService: ButtonManagementService, private httpService: HttpURIService, private router: Router, private credentialService: CredentialService, private i18nService: I18NService, private storageService: StorageService) {
} }
authenticate(uCreds: UserCredentials): Observable<any> { authenticate(uCreds: UserCredentials): Observable<any> {
const userJson = this.storageService.getItem('user'); // const userJson = this.storageService.getItem('user');
if (this.storageService.getItem('user') != null) { // if (this.storageService.getItem('user') != null) {
this.i18nService.error(ErrorMessages.ALREADY_LOGGED_IN, []); // this.i18nService.error(ErrorMessages.ALREADY_LOGGED_IN, []);
return new Observable(); // empty // return new Observable(); // empty
} // }
this.credentialService.setPorOrgacode(HiddenValues.POR_ORGACODE); this.credentialService.setPorOrgacode(HiddenValues.POR_ORGACODE);
this.credentialService.setUserId(uCreds.userId); this.credentialService.setUserId(uCreds.userId);
@ -81,7 +81,7 @@ export class AuthenticationService {
isAdminUser(){ isAdminUser(){
if (this.storageService && this.storageService.getItem('user') != null) { if (this.storageService && this.storageService.getItem('user') != null) {
let cachedUser = JSON.parse(this.storageService.getItem('user') || '{}'); let cachedUser = JSON.parse(this.storageService.getItem('user') || '{}');
return cachedUser.user.role === HiddenValues.ADMIN_USER || cachedUser.user.role === HiddenValues.SUPER_ADMIN; return cachedUser.user.role === HiddenValues.SUPER_ADMIN;
} }
return false; return false;
} }

@ -1,28 +1,66 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { saveAs } from 'file-saver'; import { saveAs } from 'file-saver';
import * as XLSX from 'xlsx'; import * as XLSX from 'xlsx';
import { TranslateService } from '@ngx-translate/core';
import { EXCEL_FILE_EXTENSION, EXCEL_FILE_TYPE } from '../../utils/app.constants'; import { EXCEL_FILE_EXTENSION, EXCEL_FILE_TYPE } from '../../utils/app.constants';
import { StorageService } from './storage.service';
import { supportedLanguages } from '../../utils/enums';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
}) })
export class ExcelExportService { export class ExcelExportService {
constructor() { }
private fileType = EXCEL_FILE_TYPE; private fileType = EXCEL_FILE_TYPE;
private fileExtension = EXCEL_FILE_EXTENSION; private fileExtension = EXCEL_FILE_EXTENSION;
constructor(private translate: TranslateService, private storageService: StorageService) { }
public exportExcel(jsonData: any[], fileName: string): void { public exportExcel(jsonData: any[], fileName: string): void {
const ws: XLSX.WorkSheet = XLSX.utils.json_to_sheet(jsonData); const translatedData = this.translateHeaders(jsonData);
const wb: XLSX.WorkBook = { Sheets: { 'data': ws }, SheetNames: ['data'] };
const ws: XLSX.WorkSheet = XLSX.utils.json_to_sheet(translatedData);
const wb: XLSX.WorkBook = { Sheets: { data: ws }, SheetNames: ['data'] };
const excelBuffer: any = XLSX.write(wb, { bookType: 'xlsx', type: 'array' }); const excelBuffer: any = XLSX.write(wb, { bookType: 'xlsx', type: 'array' });
this.saveExcelFile(excelBuffer, fileName); this.saveExcelFile(excelBuffer, fileName);
} }
private translateHeaders(data: any[]): any[] {
if (!data?.length) return data;
return data.map(row => {
const newRow: any = {};
Object.keys(row).forEach(key => {
const candidates = [
key,
key.toLowerCase(),
key.toUpperCase(),
key.replace(/([A-Z])/g, '_$1').toUpperCase()
];
let translatedKey = key;
for (const candidate of candidates) {
const value = this.translate.instant(candidate);
if (value !== candidate) {
translatedKey = value;
break;
}
}
newRow[translatedKey] = row[key];
});
return newRow;
});
}
private saveExcelFile(buffer: any, fileName: string): void { private saveExcelFile(buffer: any, fileName: string): void {
const data: Blob = new Blob([buffer], {type: this.fileType}); const data: Blob = new Blob([buffer], { type: this.fileType });
saveAs.saveAs(data, fileName + this.fileExtension); saveAs(data, fileName + this.fileExtension);
} }
} }

@ -3,9 +3,7 @@
<div class="container-fluid"> <div class="container-fluid">
<div class="row"> <div class="row">
<div class="col-12"> <div class="col-12">
<div <div class="d-sm-flex align-items-center justify-content-between navbar-header p-0"></div>
class="d-sm-flex align-items-center justify-content-between navbar-header p-0"
></div>
</div> </div>
</div> </div>
</div> </div>
@ -19,9 +17,7 @@
<div class="col-lg-12"> <div class="col-lg-12">
<div class="card-body mt-2 p-0"> <div class="card-body mt-2 p-0">
<div class="card mb-0 mt-2"> <div class="card mb-0 mt-2">
<div <div class="card-header font-edit-13-child d-flex justify-content-between align-items-center">
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="d-flex align-items-center gap-2">
@ -32,169 +28,164 @@
[(ngModel)]="searchText" [(ngModel)]="searchText"
(ngModelChange)="onSearch($event)" (ngModelChange)="onSearch($event)"
placeholder="{{ 'search' | translate }}" placeholder="{{ 'search' | translate }}"
[disabled]="isLoading"
/> />
<i class="fas fa-search search-icon"></i> <i class="fas fa-search search-icon"></i>
</div> </div>
<div class="d-flex align-items-center gap-2"> <div class="d-flex align-items-center gap-2">
<i <button
class="btn btn-sm btn-outline-success"
(click)="exportDataInExcel()" (click)="exportDataInExcel()"
id="downloadReport" [disabled]="isLoading || transactionLog.length === 0"
class="fa fa-download" title="Export to Excel"
></i> >
<i class="fa fa-download"></i>
</button>
</div> </div>
<i <button
class="materialdesignicons" class="btn btn-sm btn-outline-secondary"
(click)="toggleTableCard()" (click)="toggleTableCard()"
[title]="(transactionDataExpanded ? 'collapse' : 'expand') | translate"
> >
<ng-container <i *ngIf="transactionDataExpanded" class="dripicons-chevron-up"></i>
*ngIf=" <i *ngIf="!transactionDataExpanded" class="dripicons-chevron-down"></i>
transactionDataExpanded; </button>
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>
<div <div
class="card-body" class="card-body"
*ngIf=" *ngIf="transactionDataExpanded; else collapsedTable"
transactionDataExpanded && allItems.length;
else noRecordsFound
"
> >
<div *ngIf="isLoading" class="text-center text-muted"> <!-- Loading State -->
<p>{{ "loadingTransactionLogs" | translate }}</p> <div *ngIf="isLoading" class="text-center py-5">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">Loading...</span>
</div>
<p class="text-muted mt-2">{{ "loadingTransactionLogs" | translate }}</p>
</div> </div>
<div <!-- Error Message -->
*ngIf="!isLoading && transactionLog.length === 0" <div *ngIf="!isLoading && errorMessage" class="alert alert-danger alert-dismissible fade show" role="alert">
class="text-center text-muted" {{ errorMessage }}
> <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close" (click)="errorMessage = ''"></button>
<p>{{ "noTransactionLogsFound" | translate }}</p>
</div> </div>
<div *ngIf="errorMessage" class="alert alert-danger"> <!-- No Data Found -->
{{ errorMessage }} <div *ngIf="!isLoading && !errorMessage && transactionLog.length === 0" class="text-center py-5 text-muted">
<i class="fas fa-database fa-3x mb-3 opacity-50"></i>
<h5>{{ "noTransactionLogsFound" | translate }}</h5>
</div> </div>
<div <!-- Data Table -->
*ngIf="!isLoading && transactionLog.length > 0" <div *ngIf="!isLoading && !errorMessage && transactionLog.length > 0" class="table-responsive">
class="table-responsive" <table class="table table-hover table-bordered mb-0">
>
<table class="table mb-0 border">
<thead class="table-light"> <thead class="table-light">
<tr> <tr>
<th>{{ "logID" | translate }}</th> <th scope="col">{{ "logID" | translate }}</th>
<th>{{ "organization" | translate }}</th> <th scope="col">{{ "organization" | translate }}</th>
<th>{{ "transactionID" | translate }}</th> <th scope="col">{{ "transactionID" | translate }}</th>
<th>{{ "drAccount" | translate }}</th> <th scope="col">{{ "drAccount" | translate }}</th>
<th>{{ "crAccount" | translate }}</th> <th scope="col">{{ "crAccount" | translate }}</th>
<th>{{ "drPcaGlacode" | translate }}</th> <th scope="col">{{ "drPcaGlacode" | translate }}</th>
<th>{{ "crPcaglacode" | translate }}</th> <th scope="col">{{ "crPcaglacode" | translate }}</th>
<th>{{ "transactionCode" | translate }}</th> <th scope="col">{{ "transactionUri" | translate }}</th>
<th>{{ "transactionUri" | translate }}</th> <th scope="col">{{ "transactionCode" | translate }}</th>
<th>{{ "paymentMode" | translate }}</th> <th scope="col">{{ "paymentMode" | translate }}</th>
<th>{{ "date" | translate }}</th> <th scope="col">{{ "date" | translate }}</th>
<th>{{ "channel" | translate }}</th> <th scope="col">{{ "channel" | translate }}</th>
<th>{{ "createdAt" | translate }}</th> <th scope="col">{{ "createdAt" | translate }}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr <tr *ngFor="let log of filteredItems | tableFilter: searchText: [
*ngFor=" 'logId',
let log of ( 'porOrgacode',
allItems 'transactionID',
| tableFilter 'drMbmbkmsnumber',
: searchText 'crMbmbkmsnumber',
: [ 'drPcaGlacode',
'logID', 'crPcaglacode',
'organization', 'transactionUri',
'transactionID', 'transactionCode',
'drAccount', 'ppmPymdcode',
'drPcaGlacode', 'sgtGntrdate',
'crPcaglacode', 'channelCode',
'crAccount', 'createdAt'
'transactionUri', ]">
'transactionCode', <td><code>{{ log.logId || '-' }}</code></td>
'paymentMode', <td>{{ log.porOrgacode || '-' }}</td>
'date', <td><strong>{{ log.transactionID || '-' }}</strong></td>
'channel', <td>{{ log.drMbmbkmsnumber || '-' }}</td>
'createdAt', <td>{{ log.crMbmbkmsnumber || '-' }}</td>
] <td>{{ log.drPcaGlacode || '-' }}</td>
).slice( <td>{{ log.crPcaglacode || '-' }}</td>
(currentPage - 1) * itemsPerPage, <td><small class="text-muted">{{ log.transactionUri || 'N/A' }}</small></td>
currentPage * itemsPerPage <td><span class="badge bg-secondary">{{ log.transactionCode || 'N/A' }}</span></td>
) <td>{{ log.ppmPymdcode || '-' }}</td>
" <td>{{ log.sgtGntrdate | date: 'MMM dd, yyyy' }}</td>
> <td>{{ log.channelCode || '-' }}</td>
<td>{{ log.logId }}</td> <td>{{ log.createdAt | date: 'MMM dd, yyyy HH:mm' }}</td>
<td>{{ log.porOrgacode }}</td>
<td>{{ log.transactionID }}</td>
<td>{{ log.drMbmbkmsnumber || "-" }}</td>
<td>{{ log.crMbmbkmsnumber || "-" }}</td>
<td>{{ log.drPcaGlacode || "-" }}</td>
<td>{{ log.crPcaglacode || "-" }}</td>
<td>{{log.transactionUri || "N/A"}}</td>
<td>{{log.transactionCode || "N/A"}}</td>
<td>{{ log.ppmPymdcode }}</td>
<td>
{{ log.sgtGntrdate | date: "MMM dd, yyyy" }}
</td>
<td>{{ log.channelCode }}</td>
<td>
{{
log.createdAt | date: "MMM dd, yyyy HH:mm"
}}
</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
<div <!-- Pagination -->
class="d-flex justify-content-between align-items-center mt-3" <div class="d-flex justify-content-between align-items-center mt-3 flex-wrap">
> <!-- Items per page -->
<div class="form-group mb-0"> <div class="d-flex align-items-center gap-2 mb-2 mb-md-0">
<ng-select <span class="text-muted small">{{ "show" | translate }}</span>
class="form-select-sm" <div style="width: 120px;">
[items]="pageSizeOptions" <ng-select
bindLabel="label" class="form-select-sm"
bindValue="value" [items]="pageSizeOptions"
[(ngModel)]="itemsPerPage" bindValue="value"
(change)="itemsPerPageChanged()" [(ngModel)]="itemsPerPage"
[searchable]="false" (change)="itemsPerPageChanged()"
[clearable]="false" [searchable]="false"
[dropdownPosition]="'top'" [clearable]="false"
> [dropdownPosition]="'top'"
</ng-select> [disabled]="isLoading"
>
<ng-template ng-option-tmp let-item="item">
{{ item.value }} {{ "entries" | translate }}
</ng-template>
<ng-template ng-label-tmp let-item="item">
{{ item.value }} {{ "entries" | translate }}
</ng-template>
</ng-select>
</div>
</div> </div>
<div class="text-muted"> <!-- Page info -->
<div class="text-muted mb-2 mb-md-0">
{{ "page" | translate }} {{ currentPage }} {{ "page" | translate }} {{ currentPage }}
{{ "of" | translate }} {{ totalPages() }} ({{ {{ "of" | translate }} {{ totalPages() }}
totalCount <span class="d-none d-md-inline">
}} ({{ totalCount }} {{ "totalItems" | translate }})
{{ "totalItems" | translate }}) </span>
</div> </div>
<!-- Pagination buttons -->
<div class="btn-group"> <div class="btn-group">
<button <button
class="btn btn-primary waves-effect waves-light" class="btn btn-outline-primary btn-sm"
(click)="previousPage()" (click)="previousPage()"
[disabled]="currentPage === 1 || isLoading"
> >
<i class="fas fa-chevron-left me-1"></i>
{{ "previous" | translate }} {{ "previous" | translate }}
</button> </button>
<button <button
class="btn btn-primary waves-effect waves-light" class="btn btn-outline-primary btn-sm"
(click)="nextPage()" (click)="nextPage()"
[disabled]="currentPage === totalPages() || isLoading"
> >
{{ "next" | translate }} {{ "next" | translate }}
<i class="fas fa-chevron-right ms-1"></i>
</button> </button>
</div> </div>
</div> </div>
@ -211,11 +202,13 @@
</div> </div>
</div> </div>
</div> </div>
<ng-template #noRecordsFound>
<div <ng-template #collapsedTable>
*ngIf="!isLoading && allItems.length === 0" <div class="card-body text-center py-4 text-muted" *ngIf="!isLoading">
class="text-center text-muted mt-3" <i class="dripicons-chevron-down fa-2x mb-2"></i>
> <p>{{ "tableCollapsed" | translate }}</p>
<p>{{ "noTransactionLogsFound" | translate }}</p> <button class="btn btn-sm btn-outline-primary" (click)="toggleTableCard()">
{{ "showTable" | translate }}
</button>
</div> </div>
</ng-template> </ng-template>

@ -0,0 +1,125 @@
:host {
display: block;
}
.card-header {
background-color: #f8f9fa;
border-bottom: 1px solid rgba(0, 0, 0, 0.125);
}
.search-box {
position: relative;
min-width: 200px;
.form-control {
padding-left: 2.5rem;
padding-right: 1rem;
}
.search-icon {
position: absolute;
left: 1rem;
top: 50%;
transform: translateY(-50%);
color: #6c757d;
pointer-events: none;
}
}
.table {
th {
font-weight: 600;
background-color: #f8f9fa;
border-bottom: 2px solid #dee2e6;
white-space: nowrap;
}
td {
vertical-align: middle;
}
}
.btn-group-sm > .btn {
padding: 0.25rem 0.5rem;
font-size: 0.875rem;
}
.badge {
font-size: 0.75em;
}
.alert-info {
background-color: #e7f3ff;
border-color: #b3d7ff;
color: #004085;
.btn-close {
padding: 0.5rem;
}
}
.spinner-border {
width: 3rem;
height: 3rem;
}
// Responsive adjustments
@media (max-width: 768px) {
.card-header .d-flex {
flex-direction: column;
align-items: stretch !important;
gap: 0.75rem !important;
}
.search-box {
min-width: 100%;
}
.table-responsive {
font-size: 0.875rem;
}
.d-flex.justify-content-between {
flex-direction: column;
gap: 1rem;
> div {
width: 100%;
justify-content: center !important;
}
}
.btn-group {
width: 100%;
.btn {
flex: 1;
}
}
}
// Loading overlay
.text-center.py-5 {
min-height: 200px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
// Hover effects
.table-hover tbody tr:hover {
background-color: rgba(0, 123, 255, 0.05);
}
// Date filter styles
.form-control-sm {
min-width: 120px;
}
// Active state for filter buttons
.btn-outline-primary.active {
background-color: #0d6efd;
color: white;
border-color: #0d6efd;
}

@ -7,20 +7,19 @@ import { pageSizeOptions } from '../utils/app.constants';
import { NgSelectModule } from '@ng-select/ng-select'; import { NgSelectModule } from '@ng-select/ng-select';
import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { TableFilterPipe } from '../shared/pipes/table-filter.pipe'; import { TableFilterPipe } from '../shared/pipes/table-filter.pipe';
import {TransactionLog} from "../models/user" import { TransactionLog } from "../models/user";
import { URIKey } from '../utils/uri-enums'; import { URIKey } from '../utils/uri-enums';
import { HttpParams } from '@angular/common/http'; import { HttpParams } from '@angular/common/http';
import { HttpURIService } from '../app.http.uri.service'; import { HttpURIService } from '../app.http.uri.service';
import { formatDate } from '@angular/common';
@Component({ @Component({
selector: 'app-transaction-logs', selector: 'app-transaction-logs',
templateUrl: './transaction-logs.component.html', templateUrl: './transaction-logs.component.html',
imports: [CommonModule, TranslateModule, NgSelectModule, FormsModule, ReactiveFormsModule, TableFilterPipe ] styleUrls: ['./transaction-logs.component.scss'],
imports: [CommonModule, TranslateModule, NgSelectModule, FormsModule, ReactiveFormsModule, TableFilterPipe]
}) })
export class TransactionLogsComponent implements OnInit { export class TransactionLogsComponent implements OnInit {
onPageSizeChange(arg0: number) {
throw new Error('Method not implemented.');
}
currentPage: number = 1; currentPage: number = 1;
totalCount: number = 0; totalCount: number = 0;
renewalDataExpanded: boolean = true; renewalDataExpanded: boolean = true;
@ -32,75 +31,215 @@ throw new Error('Method not implemented.');
errorMessage: string = ''; errorMessage: string = '';
searchText: string = ''; searchText: string = '';
allItems: TransactionLog[] = []; allItems: TransactionLog[] = [];
transactionDataExpanded: boolean = true transactionDataExpanded: boolean = true;
// Date range properties
fromDate: string = '';
toDate: string = '';
maxDate: string = '';
showDateFilters: boolean = false;
constructor( constructor(
private excelExportService: ExcelExportService, private excelExportService: ExcelExportService,
private httpService: HttpURIService, private httpService: HttpURIService,
) {} ) {}
ngOnInit(): void { ngOnInit(): void {
// Set max date to today
this.maxDate = formatDate(new Date(), 'yyyy-MM-dd', 'en-US');
// Optionally set default date range (last 30 days)
const today = new Date();
const lastMonth = new Date();
lastMonth.setDate(today.getDate() - 30);
this.fromDate = formatDate(lastMonth, 'yyyy-MM-dd', 'en-US');
this.toDate = formatDate(today, 'yyyy-MM-dd', 'en-US');
this.loadTransactionLogs(); this.loadTransactionLogs();
} }
loadTransactionLogs(): void {
this.isLoading = true; loadTransactionLogs(): void {
const params = new HttpParams(); this.isLoading = true;
this.httpService this.errorMessage = '';
.requestGET<any>(URIKey.TRANSACTION_LOGS, params)
.subscribe({ // Build query parameters
next: (res) => { let params = new HttpParams();
const logs = Array.isArray(res) ? res : res?.data;
this.transactionLog = logs ?? []; // Add pagination parameters
this.allItems = [...this.transactionLog]; params = params.set('page', this.currentPage.toString());
this.totalCount = this.transactionLog.length; params = params.set('limit', this.itemsPerPage.toString());
this.updatePagedItems(); // Add date filters if provided
this.isLoading = false; if (this.fromDate) {
}, params = params.set('fromDate', this.fromDate);
error: (err) => { }
console.error('Error fetching logging details data:', err);
this.transactionLog = []; if (this.toDate) {
this.isLoading = false; params = params.set('toDate', this.toDate);
}
// Add search filter if provided
if (this.searchText && this.searchText.trim() !== '') {
params = params.set('search', this.searchText.trim());
}
this.httpService
.requestGET<any>(URIKey.TRANSACTION_LOGS, params)
.subscribe({
next: (res) => {
const logs = Array.isArray(res) ? res : res?.data;
this.transactionLog = logs ?? [];
this.allItems = [...this.transactionLog];
// Get total count from response
if (res?.total !== undefined) {
this.totalCount = res.total;
} else if (res?.pagination?.total !== undefined) {
this.totalCount = res.pagination.total;
} else if (res?.meta?.total !== undefined) {
this.totalCount = res.meta.total;
} else {
this.totalCount = this.transactionLog.length;
}
this.updatePagedItems();
this.isLoading = false;
},
error: (err) => {
console.error('Error fetching transaction logs:', err);
this.errorMessage = err.message || 'Failed to load transaction logs. Please try again.';
this.transactionLog = [];
this.allItems = [];
this.totalCount = 0;
this.pagedItems = [];
this.isLoading = false;
},
complete: () => {
this.isLoading = false;
}
});
}
// Date filter change handler
onDateFilterChange(): void {
// Validate date range
if (this.fromDate && this.toDate) {
const from = new Date(this.fromDate);
const to = new Date(this.toDate);
if (from > to) {
this.errorMessage = 'From date cannot be after To date';
return;
} }
}); }
}
// Reset to first page and reload
this.currentPage = 1;
this.loadTransactionLogs();
}
// Clear date filters
clearDateFilters(): void {
this.fromDate = '';
this.toDate = '';
this.currentPage = 1;
this.loadTransactionLogs();
}
// Toggle date filter visibility
toggleDateFilters(): void {
this.showDateFilters = !this.showDateFilters;
}
// Apply default date range (Last 7 days)
applyLast7Days(): void {
const today = new Date();
const lastWeek = new Date();
lastWeek.setDate(today.getDate() - 7);
this.fromDate = formatDate(lastWeek, 'yyyy-MM-dd', 'en-US');
this.toDate = formatDate(today, 'yyyy-MM-dd', 'en-US');
this.onDateFilterChange();
}
// Apply default date range (Last 30 days)
applyLast30Days(): void {
const today = new Date();
const lastMonth = new Date();
lastMonth.setDate(today.getDate() - 30);
this.fromDate = formatDate(lastMonth, 'yyyy-MM-dd', 'en-US');
this.toDate = formatDate(today, 'yyyy-MM-dd', 'en-US');
this.onDateFilterChange();
}
// Apply default date range (This month)
applyThisMonth(): void {
const today = new Date();
const firstDay = new Date(today.getFullYear(), today.getMonth(), 1);
this.fromDate = formatDate(firstDay, 'yyyy-MM-dd', 'en-US');
this.toDate = formatDate(today, 'yyyy-MM-dd', 'en-US');
this.onDateFilterChange();
}
toggleTableCard(): void { toggleTableCard(): void {
this.transactionDataExpanded = !this.transactionDataExpanded; this.transactionDataExpanded = !this.transactionDataExpanded;
} }
itemsPerPageChanged(): void { itemsPerPageChanged(): void {
this.currentPage = 1; this.currentPage = 1;
this.updatePagedItems(); this.loadTransactionLogs();
} }
onSearch(value: string): void { onSearch(value: string): void {
this.searchText = value; this.searchText = value;
} this.currentPage = 1;
// Debounce search to avoid too many API calls
clearTimeout((this as any).searchTimeout);
(this as any).searchTimeout = setTimeout(() => {
this.loadTransactionLogs();
}, 300);
}
totalPages(): number { totalPages(): number {
return Math.ceil(this.allItems.length / this.itemsPerPage); return Math.ceil(this.totalCount / this.itemsPerPage);
} }
previousPage(): void { previousPage(): void {
if (this.currentPage > 1) { if (this.currentPage > 1) {
this.currentPage--; this.currentPage--;
this.updatePagedItems(); this.loadTransactionLogs();
} }
} }
updatePagedItems(): void { updatePagedItems(): void {
// For client-side display with table filter pipe
const startIndex = (this.currentPage - 1) * this.itemsPerPage; const startIndex = (this.currentPage - 1) * this.itemsPerPage;
const endIndex = startIndex + this.itemsPerPage; const endIndex = startIndex + this.itemsPerPage;
// If using server-side pagination, use transactionLog directly
// If using client-side filtering with tableFilter pipe, this might not be needed
this.pagedItems = this.allItems.slice(startIndex, endIndex); this.pagedItems = this.allItems.slice(startIndex, endIndex);
} }
nextPage(): void { nextPage(): void {
if (this.currentPage < this.totalPages()) { if (this.currentPage < this.totalPages()) {
this.currentPage++; this.currentPage++;
this.updatePagedItems(); this.loadTransactionLogs();
} }
} }
exportDataInExcel() { exportDataInExcel(): void {
this.excelExportService.exportExcel(this.transactionLog, TRANSACTION_LOGS_FILE_NAME) // Export filtered data
const dataToExport = this.allItems.length > 0 ? this.allItems : this.transactionLog;
this.excelExportService.exportExcel(dataToExport, TRANSACTION_LOGS_FILE_NAME);
}
// Get filtered items for display (used in template)
get filteredItems(): TransactionLog[] {
return this.allItems;
} }
} }

@ -174,24 +174,8 @@ constructor(
next: (response) => { next: (response) => {
if (!(response instanceof HttpErrorResponse)) { if (!(response instanceof HttpErrorResponse)) {
this.i18nService.success(SuccessMessages.CHANGE_PASSWORD_SUCCESS, []); this.i18nService.success(SuccessMessages.CHANGE_PASSWORD_SUCCESS, []);
this.authService.logout();
const userStr = this.storageService.getItem('user'); this.router.navigate(['login']);
if (userStr) {
const user = JSON.parse(userStr);
user.requiresPasswordChange = false;
if (user.user) {
user.user.firstLogin = false;
}
if (payload.newPassword) {
this.storageService.setItem(FormConstants.PASSWORD, payload.newPassword);
}
this.storageService.setItem('user', JSON.stringify(user));
this.authService.updateCredentialsAfterPasswordChange(payload.newPassword);
}
this.router.navigate(['/home/dashboard']);
} }
}, },
error: (error) => { error: (error) => {

@ -1,4 +1,4 @@
<div class="modal fade" id="resetPasswordModal" tabindex="-1"> <div class="modal fade" id="resetPasswordModal" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered"> <div class="modal-dialog modal-dialog-centered">
<div class="modal-content"> <div class="modal-content">
@ -6,73 +6,144 @@
<h5 class="modal-title"> <h5 class="modal-title">
{{ 'resetPassword' | translate }} {{ 'resetPassword' | translate }}
</h5> </h5>
<button type="button" class="btn-close" (click)="closeModal()"></button> <button type="button" class="btn-close" (click)="closeModal()" aria-label="Close"></button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<form [formGroup]="resetPasswordForm"> <!-- Show warning if no user is selected -->
<div *ngIf="!userId" class="alert alert-warning mb-3">
<i class="fas fa-exclamation-triangle me-2"></i>
{{ 'noUserSelected' | translate }}
</div>
<div class="mb-3"> <!-- User ID display -->
<label class="form-label">{{ 'userID' | translate }}</label> <div class="mb-3" *ngIf="userId">
<input [readonly]="true" class="form-control" formControlName="userId" /> <label class="form-label fw-bold">
{{ 'userID' | translate }} <span class="mandatory">*</span>
</label>
<div class="input-group">
<input
type="text"
class="form-control bg-light"
[value]="userId"
readonly
placeholder="{{ 'userID' | translate }}"
/>
<span class="input-group-text">
<i class="fas fa-user"></i>
</span>
</div> </div>
</div>
<form [formGroup]="resetPasswordForm" *ngIf="userId">
<!-- New Password Field -->
<div class="mb-3"> <div class="mb-3">
<label class="form-label">{{ 'enterNewPassword' | translate }}</label> <label class="form-label fw-bold">
{{ 'enterNewPassword' | translate }} <span class="mandatory">*</span>
<div class="password-wrapper"> </label>
<input class="form-control" formControlName="newPassword" placeholder="{{'enterNewPassword' | translate}}" [type]="newPasswordType" autocomplete="off" appNoWhitespaces /> <div class="password-wrapper input-group">
<input
<app-password-hide-show #newPasswordPsh class="password-eye align-items-stretch" [showPassword]="true" class="form-control"
(onEyeClick)="togglePasswordType()"> formControlName="newPassword"
</app-password-hide-show> placeholder="{{'enterNewPassword' | translate}}"
[type]="newPasswordType"
autocomplete="new-password"
appNoWhitespaces
[attr.disabled]="isSubmitting ? true : null"
/>
<button
class="btn btn-outline-secondary"
type="button"
(click)="togglePasswordType()"
[attr.disabled]="isSubmitting ? true : null"
>
<i class="fas" [class.fa-eye]="newPasswordType === 'password'"
[class.fa-eye-slash]="newPasswordType === 'text'"></i>
</button>
</div> </div>
<div class="text-danger" *ngIf="resetPasswordForm.get('newPassword')?.touched ">
<!-- Validation messages -->
<div class="text-danger mt-1" *ngIf="resetPasswordForm.get('newPassword')?.touched">
<div *ngIf="resetPasswordForm.get('newPassword')?.hasError('required')"> <div *ngIf="resetPasswordForm.get('newPassword')?.hasError('required')">
{{ 'fieldRequired' | translate }} {{ 'fieldRequired' | translate }}
</div> </div>
<div *ngIf="resetPasswordForm.get('newPassword')?.hasError('minlength')">
{{ 'passwordMinLength' | translate }}
</div>
<div *ngIf="resetPasswordForm.get('newPassword')?.hasError('maxlength')">
{{ 'passwordMaxLength' | translate }}
</div>
<div *ngIf="resetPasswordForm.get('newPassword')?.hasError('pattern')"> <div *ngIf="resetPasswordForm.get('newPassword')?.hasError('pattern')">
{{ 'passwordPattern' | translate }} {{ 'passwordPattern' | translate }}
</div> </div>
</div>
<!-- Password requirements hint -->
<div class="form-text text-muted">
</div> </div>
</div> </div>
<!-- Confirm Password Field -->
<div class="mb-3"> <div class="mb-3">
<label class="form-label">{{ 'confirmPassword' | translate }}</label> <label class="form-label fw-bold">
{{ 'confirmPassword' | translate }} <span class="mandatory">*</span>
<div class="password-wrapper"> </label>
<input class="form-control" formControlName="confirmPassword" placeholder="{{'confirmPassword' | translate}}" [type]="confirmPasswordType" autocomplete="off" appNoWhitespaces /> <div class="password-wrapper input-group">
<input
<app-password-hide-show #confirmPasswordPsh class="password-eye align-items-stretch" [showPassword]="true" class="form-control"
(onEyeClick)="toggleConfirmPasswordType()"> formControlName="confirmPassword"
</app-password-hide-show> placeholder="{{'confirmPassword' | translate}}"
[type]="confirmPasswordType"
autocomplete="new-password"
appNoWhitespaces
[attr.disabled]="isSubmitting ? true : null"
/>
<button
class="btn btn-outline-secondary"
type="button"
(click)="toggleConfirmPasswordType()"
[attr.disabled]="isSubmitting ? true : null"
>
<i class="fas" [class.fa-eye]="confirmPasswordType === 'password'"
[class.fa-eye-slash]="confirmPasswordType === 'text'"></i>
</button>
</div> </div>
<div class="text-danger" *ngIf="resetPasswordForm.get('confirmPassword')?.touched ">
<!-- Validation messages -->
<div class="text-danger mt-1" *ngIf="resetPasswordForm.get('confirmPassword')?.touched">
<div *ngIf="resetPasswordForm.get('confirmPassword')?.hasError('required')"> <div *ngIf="resetPasswordForm.get('confirmPassword')?.hasError('required')">
{{ 'fieldRequired' | translate }} {{ 'fieldRequired' | translate }}
</div> </div>
<div *ngIf="resetPasswordForm.hasError('passwordMismatch')"> <div *ngIf="resetPasswordForm.get('confirmPassword')?.hasError('passwordMismatch')">
{{ 'passwordsDoNotMatch' | translate }} {{ 'passwordsDoNotMatch' | translate }}
</div> </div>
</div> </div>
</div> </div>
</form> </form>
</div> </div>
<div class="modal-footer d-flex justify-content-end gap-2"> <div class="modal-footer d-flex justify-content-end gap-2">
<button type="button" class="btn btn-outline-secondary btn-sm px-4" (click)="closeModal()"> <button
{{ 'cancel' | translate }} type="button"
class="btn btn-outline-secondary btn-sm px-4"
(click)="closeModal()"
[attr.disabled]="isSubmitting ? true : null"
>
{{ 'cancel' | translate }}
</button> </button>
<button type="button" class="btn btn-primary btn-sm px-4" [disabled]="resetPasswordForm.invalid" (click)="submit()"> <button
{{ 'save' | translate }} type="button"
class="btn btn-primary btn-sm px-4"
[disabled]="resetPasswordForm.invalid || !userId || isSubmitting"
(click)="submit()"
>
<span *ngIf="isSubmitting" class="spinner-border spinner-border-sm me-2"></span>
{{ isSubmitting ? ('saving' | translate) : ('save' | translate) }}
</button> </button>
</div> </div>
</div> </div>
</div> </div>
</div> </div>

@ -1,30 +1,33 @@
import { Component, Input, OnInit, SimpleChanges, ViewChild } from '@angular/core'; import { Component, Input, OnInit, OnChanges, SimpleChanges, Output, EventEmitter, ViewChild } from '@angular/core';
import { FormBuilder, FormGroup, Validators, AbstractControl, ValidationErrors, ReactiveFormsModule } from '@angular/forms'; import { FormBuilder, FormGroup, Validators, AbstractControl, ValidationErrors, ReactiveFormsModule } from '@angular/forms';
import { HttpURIService } from '../../app.http.uri.service'; import { HttpURIService } from '../../app.http.uri.service';
import { URIKey } from '../../utils/uri-enums'; import { URIKey } from '../../utils/uri-enums';
import { I18NService } from '../../services/i18n.service'; import { I18NService } from '../../services/i18n.service';
import { StorageService } from '../../shared/services/storage.service'; import { StorageService } from '../../shared/services/storage.service';
import { SuccessMessages } from '../../utils/enums'; import { SuccessMessages, ErrorMessages } from '../../utils/enums';
import { HttpErrorResponse } from '@angular/common/http'; import { HttpErrorResponse } from '@angular/common/http';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
import { CommonModule } from '@angular/common'; 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';
@Component({ @Component({
selector: 'app-reset-password-modal', selector: 'app-reset-password-modal',
standalone: true, standalone: true,
imports: [TranslateModule, ReactiveFormsModule, CommonModule, PasswordHideShowComponent], imports: [TranslateModule, ReactiveFormsModule, CommonModule],
templateUrl: './reset-password-modal.component.html' templateUrl: './reset-password-modal.component.html'
}) })
export class ResetPasswordModalComponent implements OnInit { export class ResetPasswordModalComponent implements OnInit, OnChanges {
newPasswordType: string = 'password' newPasswordType: string = 'password';
confirmPasswordType: string = 'password' confirmPasswordType: string = 'password';
@Input() userId!: string;
@Input() userId: string | null = null;
@Output() modalClosed = new EventEmitter<void>();
resetPasswordForm!: FormGroup; resetPasswordForm!: FormGroup;
@ViewChild('newPasswordPsh') passwordHideShow?:PasswordHideShowComponent isSubmitting: boolean = false;
@ViewChild('confirmPasswordPsh') confirmPasswordHideShow?:PasswordHideShowComponent
@ViewChild('newPasswordPsh') passwordHideShow?: PasswordHideShowComponent;
@ViewChild('confirmPasswordPsh') confirmPasswordHideShow?: PasswordHideShowComponent;
constructor( constructor(
private fb: FormBuilder, private fb: FormBuilder,
@ -32,50 +35,80 @@ export class ResetPasswordModalComponent implements OnInit {
private i18nService: I18NService, private i18nService: I18NService,
private storageService: StorageService private storageService: StorageService
) {} ) {}
togglePasswordType() {
this.newPasswordType = this.passwordHideShow?.showPassword ? 'password' : 'text';
}
toggleConfirmPasswordType() {
this.confirmPasswordType = this.confirmPasswordHideShow?.showPassword ? 'password' : 'text';
}
ngOnInit(): void { ngOnInit(): void {
this.initializeForm();
}
initializeForm(): void {
this.resetPasswordForm = this.fb.group({ this.resetPasswordForm = this.fb.group({
userId: [''],
newPassword: ['', [ newPassword: ['', [
Validators.required, Validators.required,
Validators.minLength(8), Validators.minLength(8),
Validators.maxLength(20), Validators.maxLength(20),
Validators.pattern(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&]).+$/) Validators.pattern(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]+$/)
]], ]],
confirmPassword: ['', Validators.required] confirmPassword: ['', Validators.required]
}, { validators: this.passwordMatchValidator }); }, {
} validators: this.passwordMatchValidator,
updateOn: 'blur'
});
ngOnChanges(changes: SimpleChanges): void { // Update confirm password validation when new password changes
if (changes['userId'] && this.resetPasswordForm) { this.resetPasswordForm.get('newPassword')?.valueChanges.subscribe(() => {
this.resetPasswordForm.reset({ this.resetPasswordForm.get('confirmPassword')?.updateValueAndValidity();
userId: this.userId,
newPassword: '',
confirmPassword: ''
}); });
}
this.newPasswordType = 'password'; ngOnChanges(changes: SimpleChanges): void {
this.confirmPasswordType = 'password'; if (changes['userId'] && this.resetPasswordForm) {
console.log('ResetPasswordModal: userId changed to:', this.userId);
// Reset the form when userId changes
this.resetForm();
// Update the form with new userId if needed
if (this.userId) {
// You might want to fetch user details here if needed
console.log('Preparing reset password for user:', this.userId);
}
}
}
this.passwordHideShow?.reset(); togglePasswordType() {
this.confirmPasswordHideShow?.reset(); this.newPasswordType = this.newPasswordType === 'password' ? 'text' : 'password';
} }
}
toggleConfirmPasswordType() {
this.confirmPasswordType = this.confirmPasswordType === 'password' ? 'text' : 'password';
}
passwordMatchValidator(group: AbstractControl): ValidationErrors | null { passwordMatchValidator(group: AbstractControl): ValidationErrors | null {
const newPassword = group.get('newPassword')?.value; const newPassword = group.get('newPassword')?.value;
const confirmPassword = group.get('confirmPassword')?.value; const confirmPassword = group.get('confirmPassword')?.value;
return newPassword === confirmPassword ? null : { passwordMismatch: true };
if (!newPassword || !confirmPassword) return null;
// If passwords don't match, set error on confirmPassword field
if (newPassword !== confirmPassword) {
group.get('confirmPassword')?.setErrors({ passwordMismatch: true });
return { passwordMismatch: true };
} else {
group.get('confirmPassword')?.setErrors(null);
return null;
}
} }
submit() { submit() {
if (this.resetPasswordForm.invalid) return; if (this.resetPasswordForm.invalid || !this.userId) {
console.error('Form is invalid or no userId available');
this.resetPasswordForm.markAllAsTouched();
return;
}
console.log('Submitting reset password for userId:', this.userId);
this.isSubmitting = true;
const payload = { const payload = {
userId: this.userId, userId: this.userId,
@ -85,28 +118,56 @@ export class ResetPasswordModalComponent implements OnInit {
this.httpURIService.requestPOST(URIKey.RESET_PASSWORD_URI, payload) this.httpURIService.requestPOST(URIKey.RESET_PASSWORD_URI, payload)
.subscribe({ .subscribe({
next: (res) => { next: (response) => {
if (!(res instanceof HttpErrorResponse)) { if (!(response instanceof HttpErrorResponse)) {
this.i18nService.success(SuccessMessages.RESET_PASSWORD_SUCCESS, []); this.i18nService.success(SuccessMessages.RESET_PASSWORD_SUCCESS, []);
this.closeModal(); this.closeModal();
} else {
this.i18nService.error(ErrorMessages.RESET_PASSWORD_FAILED, []);
} }
this.isSubmitting = false;
},
error: (error) => {
console.error('Error resetting password:', error);
this.isSubmitting = false;
} }
}); });
} }
closeModal() { resetForm(): void {
this.resetPasswordForm.reset(); this.resetPasswordForm.reset();
this.isSubmitting = false;
this.newPasswordType = 'password'; this.newPasswordType = 'password';
this.confirmPasswordType = 'password'; this.confirmPasswordType = 'password';
// Reset password hide/show components
this.passwordHideShow?.reset(); this.passwordHideShow?.reset();
this.confirmPasswordHideShow?.reset(); this.confirmPasswordHideShow?.reset();
// Reset form validation state
Object.keys(this.resetPasswordForm.controls).forEach(key => {
const control = this.resetPasswordForm.get(key);
control?.markAsPristine();
control?.markAsUntouched();
});
}
closeModal() {
this.resetForm();
this.modalClosed.emit();
// Remove the modal backdrop and reset modal state
const modal = document.getElementById('resetPasswordModal'); const modal = document.getElementById('resetPasswordModal');
modal?.classList.remove('show'); if (modal) {
modal?.setAttribute('aria-hidden', 'true'); modal.classList.remove('show');
modal!.style.display = 'none'; modal.setAttribute('aria-hidden', 'true');
modal.style.display = 'none';
}
document.body.classList.remove('modal-open'); document.body.classList.remove('modal-open');
document.querySelector('.modal-backdrop')?.remove(); const backdrop = document.querySelector('.modal-backdrop');
if (backdrop) {
backdrop.remove();
}
} }
} }

@ -10,13 +10,12 @@ import { I18NService } from '../../services/i18n.service';
import { ErrorMessages, SuccessMessages } from '../../utils/enums'; import { ErrorMessages, SuccessMessages } from '../../utils/enums';
import { HttpErrorResponse, HttpParams } from '@angular/common/http'; import { HttpErrorResponse, HttpParams } from '@angular/common/http';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { NgSelectComponent } from "@ng-select/ng-select"; import { NgSelectModule } from '@ng-select/ng-select';
import { SetupUser } from '../../models/user'; import { SetupUser } from '../../models/user';
import { error } from 'console';
@Component({ @Component({
selector: 'app-reset-password', selector: 'app-reset-password',
imports: [TranslateModule, PasswordHideShowComponent, CommonModule, ReactiveFormsModule, NgSelectComponent], imports: [TranslateModule, PasswordHideShowComponent, CommonModule, ReactiveFormsModule, NgSelectModule],
templateUrl: './reset-password.component.html', templateUrl: './reset-password.component.html',
styleUrl: './reset-password.component.scss' styleUrl: './reset-password.component.scss'
}) })
@ -27,46 +26,53 @@ export class ResetPasswordComponent implements OnInit{
isLoading: boolean = false; isLoading: boolean = false;
allUsersDropdown: SetupUser[] = []; allUsersDropdown: SetupUser[] = [];
selectedUserId: string | null = null; selectedUserId: string | null = null;
isSubmitting: boolean = false;
@ViewChild('psh1') passwordHideShow1?: PasswordHideShowComponent; @ViewChild('psh1') passwordHideShow1?: PasswordHideShowComponent;
@ViewChild('psh2') passwordHideShow2?: PasswordHideShowComponent; @ViewChild('psh2') passwordHideShow2?: PasswordHideShowComponent;
constructor(private fb: FormBuilder, private httpService: HttpURIService, private httpURIService: HttpURIService, private storageService: StorageService, private i18nService: I18NService, private router: Router){} constructor(
private fb: FormBuilder,
private httpService: HttpURIService,
private storageService: StorageService,
private i18nService: I18NService,
private router: Router
){}
ngOnInit(): void { ngOnInit(): void {
const userIdValue = this.storageService.getItem('USER_ID') || null; this.initializeForm();
this.loadUsersForDropdown();
}
initializeForm(): void {
this.resetPasswordForm = this.fb.group({ this.resetPasswordForm = this.fb.group({
userId: [null], userId: [null, Validators.required], // Changed to empty string and required
newPassword: ['', [ newPassword: ['', [
Validators.required, Validators.required,
Validators.minLength(8), Validators.minLength(8),
Validators.maxLength(20), Validators.maxLength(20),
Validators.pattern(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]+$/) Validators.pattern(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]+$/)
] ]],
],
confirmPassword: ['', [ confirmPassword: ['', [
Validators.required, Validators.required,
Validators.minLength(8), Validators.minLength(8),
Validators.maxLength(20), Validators.maxLength(20),
Validators.pattern(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]+$/) Validators.pattern(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]+$/)
] ]]
] }, {
},
{
validators: this.passwordMatchValidator validators: this.passwordMatchValidator
} });
);
this.resetPasswordForm.get('newPassword')?.valueChanges.subscribe(()=>{ // Update confirm password validation when new password changes
this.resetPasswordForm.get('confirmPassword')?.updateValueAndValidity(); this.resetPasswordForm.get('newPassword')?.valueChanges.subscribe(() => {
}); this.resetPasswordForm.get('confirmPassword')?.updateValueAndValidity();
});
this.loadUsersForDropdown();
} }
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';
} }
@ -74,10 +80,13 @@ export class ResetPasswordComponent implements OnInit{
passwordMatchValidator(group: AbstractControl): ValidationErrors | null { passwordMatchValidator(group: AbstractControl): ValidationErrors | null {
const newPassword = group.get('newPassword'); const newPassword = group.get('newPassword');
const confirmPassword = group.get('confirmPassword'); const confirmPassword = group.get('confirmPassword');
if (!newPassword || !confirmPassword) return null; if (!newPassword || !confirmPassword) return null;
if (confirmPassword.errors && !confirmPassword.errors['passwordMismatch']) { if (confirmPassword.errors && !confirmPassword.errors['passwordMismatch']) {
return null; return null;
} }
if (newPassword.value !== confirmPassword.value) { if (newPassword.value !== confirmPassword.value) {
confirmPassword.setErrors({ passwordMismatch: true }); confirmPassword.setErrors({ passwordMismatch: true });
return { passwordMismatch: true }; return { passwordMismatch: true };
@ -87,48 +96,88 @@ export class ResetPasswordComponent implements OnInit{
} }
} }
onSubmit() { onSubmit() {
if (this.resetPasswordForm.invalid) return; if (this.resetPasswordForm.invalid) {
this.resetPasswordForm.markAllAsTouched();
return;
}
this.isSubmitting = true;
const selectedId = this.resetPasswordForm.get('userId')?.value; const selectedId = this.resetPasswordForm.get('userId')?.value;
console.log("userid.....", selectedId)
const payload = { const payload = {
userId: selectedId, userId: selectedId,
newPassword: this.resetPasswordForm.get('newPassword')?.value, newPassword: this.resetPasswordForm.get('newPassword')?.value,
porOrgaCode: this.storageService.getItem('POR_ORGACODE') porOrgaCode: this.storageService.getItem('POR_ORGACODE')
}; };
this.httpURIService.requestPOST(URIKey.RESET_PASSWORD_URI, payload) this.httpService.requestPOST(URIKey.RESET_PASSWORD_URI, payload).subscribe({
.subscribe({ next: (response) => {
next: (response) => { if (!(response instanceof HttpErrorResponse)) {
if (!(response instanceof HttpErrorResponse)) { this.i18nService.success(SuccessMessages.RESET_PASSWORD_SUCCESS, []);
this.i18nService.success(SuccessMessages.RESET_PASSWORD_SUCCESS, []); this.resetForm(); // Reset form after successful submission
this.router.navigate(['/dashboard']);
}
} }
this.isSubmitting = false;
},
error: (error) => {
console.error('Error resetting password:', error);
this.isSubmitting = false;
}
});
}
// Method to properly reset the form
resetForm(): void {
// Reset the form values
this.resetPasswordForm.reset();
// Reset password visibility
this.passwordType1 = 'password';
this.passwordType2 = 'password';
// Reset password hide/show components if available
if (this.passwordHideShow1) {
this.passwordHideShow1.showPassword = false;
}
if (this.passwordHideShow2) {
this.passwordHideShow2.showPassword = false;
}
// Mark all controls as untouched and pristine
Object.keys(this.resetPasswordForm.controls).forEach(key => {
const control = this.resetPasswordForm.get(key);
control?.markAsUntouched();
control?.markAsPristine();
});
// Reset ng-select by clearing the value
setTimeout(() => {
this.resetPasswordForm.patchValue({
userId: null,
newPassword: '',
confirmPassword: ''
}); });
} }, 0);
}
loadUsersForDropdown(): void{ loadUsersForDropdown(): void {
this.isLoading = true; this.isLoading = true;
let params = new HttpParams().set('page', '0').set('size', '1000') let params = new HttpParams()
.set('page', '0')
.set('size', '1000');
this.httpService.requestGET<SetupUser[]>(URIKey.GET_ALL_USER_URI, params).subscribe({ this.httpService.requestGET<SetupUser[]>(URIKey.GET_ALL_USER_URI, params).subscribe({
next: (response)=>{ next: (response) => {
this.allUsersDropdown = response; this.allUsersDropdown = response || [];
this.isLoading = false this.isLoading = false;
}, },
error:(err)=>{ error: (err) => {
console.error('Error fetching users:', err); console.error('Error fetching users:', err);
this.allUsersDropdown = []; this.allUsersDropdown = [];
this.isLoading = false; this.isLoading = false;
} }
}) });
}
} }
}

@ -3,222 +3,372 @@
<div class="container-fluid"> <div class="container-fluid">
<div class="row"> <div class="row">
<div class="col-12"> <div class="col-12">
<div class="d-sm-flex align-items-center justify-content-between navbar-header p-0"> <div
class="d-sm-flex align-items-center justify-content-between navbar-header p-0"
</div> ></div>
</div> </div>
</div> </div>
<div class="container-fluid"> <div class="container-fluid">
<div class="col-xl-12 mt-4"> <div class="col-xl-12 mt-4">
<div class="card border"> <div class="card border">
<div class="card-body">
<div class="table-section">
<div class="row">
<div class="col-lg-12">
<div class="card-body mt-2 p-0">
<div class="card mb-0 mt-2">
<div
class="card-header font-edit-13-child d-flex justify-content-between align-items-center"
>
{{ "setupUser" | translate }}
</div>
<div class="card-body"> <div class="card-body">
<div class="table-section"> <form [formGroup]="userForm">
<div class="row"> <div class="row g-3 mb-3">
<div class="col-lg-12"> <!-- User ID -->
<div class="card-body mt-2 p-0"> <div class="col-md-6">
<div class="card mb-0 mt-2"> <div class="d-flex align-items-center gap-2">
<div <label for="userId" class="text-nowrap">
class="card-header font-edit-13-child d-flex justify-content-between align-items-center"> {{ "userId" | translate
{{'setupUser' | translate}} }}<span class="mandatory">*</span>
</div> </label>
<div class="card-body"> <div
<form [formGroup]="userForm"> class="password-wrapper position-relative w-100"
<div class="row g-3 mb-3"> >
<div class="col-md-6"> <input
<div class="d-flex align-items-center gap-2"> type="text"
<label for="userId" class="text-nowrap"> id="userId"
{{ 'userId' | translate }}<span class="form-control"
class="mandatory">*</span> formControlName="userId"
</label> name="userId"
<div class="password-wrapper position-relative w-100"> placeholder="{{ 'userID' | translate }}"
<div class="d-flex flex-row align-items-stretch"> appNoWhitespaces
<input type="text" id="userId" [readonly]="mode === 'edit'"
class="form-control" [class.bg-light]="mode === 'edit'"
formControlName="userId" />
name="userId"
placeholder="{{ 'userID' | translate }}" appNoWhitespaces <div
/> class="text-danger"
</div> *ngIf="
userForm.get('userId')?.touched &&
userForm.get('userId')?.invalid
"
>
<div
*ngIf="
userForm.get('userId')?.errors?.[
'required'
]
"
>
{{ "fieldRequired" | translate }}
</div>
<div
*ngIf="
userForm.get('userId')?.errors?.[
'minlength'
]
"
>
{{ "userIdMinLength" | translate }}
</div>
<div
*ngIf="
userForm.get('userId')?.errors?.[
'pattern'
]
"
>
{{
"emptySpaceRestriction" | translate
}}
</div>
</div>
</div>
</div>
</div>
<!-- Full Name -->
<div class="col-md-6">
<div class="d-flex align-items-start gap-2">
<label
for="userFullname"
class="text-nowrap mt-2"
>
{{ "userFullname" | translate
}}<span class="mandatory">*</span>
</label>
<div
class="password-wrapper position-relative w-100"
>
<input
id="userFullname"
class="form-control"
formControlName="userFullname"
name="userFullname"
maxlength="500"
placeholder="{{ 'userFullname' | translate }}"
/>
<div class="text-danger" *ngIf="userForm.get('userId')?.touched && userForm.get('userId')?.invalid"> <div
<div *ngIf=" class="text-danger"
userForm.get('userId')?.errors?.['required'] && *ngIf="
!userForm.get('userId')?.value userForm.get('userFullname')?.touched &&
"> userForm.get('userFullname')?.invalid
{{ 'fieldRequired' | translate }} "
</div> >
<div
</div> *ngIf="
<div class="text-danger" *ngIf=" userForm.get('userFullname')
userForm.get('userId')?.errors?.['minlength'] && ?.errors?.['required']
userForm.get('userId')?.value "
"> >
{{'userIdMinLength' | translate }} {{ "fieldRequired" | translate }}
</div> </div>
<div
<div class="text-danger" *ngIf=" *ngIf="
userForm.get('userId')?.errors?.['pattern'] && userForm.get('userFullname')
userForm.get('userId')?.value ?.errors?.['minlength']
"> "
{{'emptySpaceRestriction' | translate}} >
</div > {{ "nameMinLength" | translate }}
</div>
</div> </div>
</div> </div>
</div> </div>
<div class="col-md-6"> </div>
<div class="d-flex align-items-start gap-2"> </div>
<label for="name"
class="text-nowrap mt-2">
{{ 'name' | translate }}<span
class="mandatory">*</span>
</label>
<div class="password-wrapper position-relative w-100">
<input id="userFullname"
class="form-control"
formControlName="userFullname"
name="userFullname"
maxlength="500"
placeholder="{{ 'Full Name' | translate }}"
rows="3" />
<div class="text-danger" *ngIf="userForm.get('userFullname')?.touched && userForm.get('userFullname')?.invalid">
<div *ngIf="
userForm.get('userFullname')?.errors?.['required'] &&
!userForm.get('userFullname')?.value
">
{{ 'fieldRequired' | translate }}
</div>
</div>
<div class="text-danger" *ngIf="
userForm.get('userFullname')?.errors?.['minlength'] &&
userForm.get('userFullname')?.value
">
{{'nameMinLength' | translate }}
</div>
</div>
</div>
</div>
</div> <div class="row g-3 mb-3">
<div class="row g-3 mb-3"> <!-- Email -->
<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="email" class="text-nowrap"> <label for="email" class="text-nowrap">
{{ 'email' | translate }}<span {{ "email" | translate
class="mandatory">*</span> }}<span class="mandatory">*</span>
</label> </label>
<div class="password-wrapper position-relative w-100"> <div
<input id="email" class="password-wrapper position-relative w-100"
class="form-control" >
formControlName="email" <input
name="email" id="email"
placeholder="{{ 'email' | translate }}" appNoWhitespaces/> class="form-control"
formControlName="email"
<div class="text-danger" *ngIf="userForm.get('email')?.errors?.['required'] name="email"
&& userForm.get('email')?.touched"> placeholder="{{ 'email' | translate }}"
{{ 'fieldRequired' | translate }} appNoWhitespaces
</div> />
<div class="text-danger" *ngIf="userForm.get('email')?.errors?.['email']
&& userForm.get('email')?.touched">
{{"invalidEmail" | translate}}
</div>
</div>
</div>
</div>
<div class="col-md-6">
<div class="d-flex align-items-center gap-2">
<label for="defaultPassword" class="text-nowrap">
{{ 'defaultPassword' | translate }}<span
class="mandatory">*</span>
</label>
<div class="password-wrapper position-relative w-100">
<input id="defaultPassword"
class="form-control"
formControlName="defaultPassword"
name="defaultPassword"
placeholder="{{ 'passwordPlaceHolder' | translate }}" appNoWhitespaces/>
<div class="text-danger" *ngIf="userForm.get('defaultPassword')?.touched && userForm.get('defaultPassword')?.invalid">
<div *ngIf="userForm.get('defaultPassword')?.hasError('required')">
{{ 'fieldRequired' | translate }}
</div>
<div *ngIf="
!userForm.get('defaultPassword')?.hasError('required') &&
userForm.get('defaultPassword')?.hasError('pattern')
">
{{ 'passwordPattern' | translate }}
</div>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-6">
<div class="d-flex align-items-center gap-2">
<label for="userRole" class="text-nowrap">
{{ 'SelectRole' | translate }}<span class="mandatory">*</span>
</label>
<div class="position-relative w-100">
<ng-select id="userRole" class="form-select form-select-narrow" formControlName="userRole" [items]="roleOptions" bindLabel="label"
bindValue="value" placeholder="{{ 'SelectRole' | translate }}" >
</ng-select>
<div class="text-danger" *ngIf="userForm.get('userRole')?.touched && userForm.get('userRole')?.invalid">
{{ 'fieldRequired' | translate }}
</div>
</div>
</div>
</div>
<div class="row g-3 mb-3">
<div class="col-md-6 ms-auto text-end">
<button type="button" class="btn btn-primary waves-effect waves-light" (click)="onSubmit()" [disabled]="userForm.invalid"
>
{{ 'save' | translate }}
</button>
</div>
</div>
<div
</form> class="text-danger"
</div> *ngIf="
</div> userForm.get('email')?.touched &&
</div> userForm.get('email')?.invalid
"
>
<div
*ngIf="
userForm.get('email')?.errors?.[
'required'
]
"
>
{{ "fieldRequired" | translate }}
</div>
<div
*ngIf="
userForm.get('email')?.errors?.[
'email'
]
"
>
{{ "invalidEmail" | translate }}
</div>
</div> </div>
</div>
</div> </div>
</div>
<!-- Password -->
<div class="col-md-6">
<div class="d-flex align-items-center gap-2">
<label
for="defaultPassword"
class="text-nowrap"
>
{{ "password" | translate
}}<span class="mandatory" *ngIf="mode === 'create'">*</span>
<span class="text-muted small" *ngIf="mode === 'edit'"></span>
</label>
<div
class="password-wrapper position-relative w-100"
>
<input
id="defaultPassword"
type="password"
class="form-control"
formControlName="defaultPassword"
name="defaultPassword"
placeholder="{{
mode === 'create' ? ('passwordPlaceHolder' | translate) : ('newPasswordOptional' | translate)
}}"
appNoWhitespaces
/>
<div
class="text-danger"
*ngIf="
userForm.get('defaultPassword')
?.touched &&
userForm.get('defaultPassword')?.invalid
"
>
<div
*ngIf="
userForm.get('defaultPassword')
?.errors?.['required'] &&
mode === 'create'
"
>
{{ "fieldRequired" | translate }}
</div>
<div
*ngIf="
!userForm.get('defaultPassword')
?.errors?.['required'] &&
userForm.get('defaultPassword')
?.errors?.['pattern']
"
>
{{ "passwordPattern" | translate }}
</div>
</div>
</div>
</div>
</div>
</div> </div>
<div class="row g-3 mb-3">
<!-- User Role -->
<div class="col-md-6">
<div class="d-flex align-items-center gap-2">
<label for="userRole" class="text-nowrap">
{{ "SelectRole" | translate
}}<span class="mandatory">*</span>
</label>
<div class="position-relative w-100">
<ng-select
id="userRole"
class="form-select form-select-narrow"
formControlName="userRole"
[items]="roleOptions"
bindLabel="label"
bindValue="value"
placeholder="{{
'SelectRole' | translate
}}"
>
</ng-select>
<div
class="text-danger"
*ngIf="
userForm.get('userRole')?.touched &&
userForm.get('userRole')?.invalid
"
>
{{ "fieldRequired" | translate }}
</div>
</div>
</div>
</div>
</div>
<!-- Action Buttons -->
<div class="row g-3 mb-3">
<div class="col-md-6 ms-auto text-end">
<!-- Save button for create mode -->
<button
*ngIf="mode === 'create'"
type="button"
class="btn btn-primary waves-effect waves-light"
(click)="onSubmit()"
[disabled]="userForm.invalid"
>
{{ "save" | translate }}
</button>
<!-- Update button for edit mode -->
<button
*ngIf="mode === 'edit'"
type="button"
class="btn btn-primary waves-effect waves-light me-2"
(click)="onUpdate()"
[disabled]="userForm.invalid || !isFormDirty()"
>
{{ "update" | translate }}
</button>
<!-- Cancel button for edit mode -->
<button
*ngIf="mode === 'edit'"
type="button"
class="btn btn-secondary waves-effect waves-light"
(click)="cancelEdit()"
>
{{ "cancel" | translate }}
</button>
</div>
</div>
</form>
</div> </div>
</div>
</div> </div>
</div>
</div> </div>
</div>
</div> </div>
</div>
</div>
</div>
<div class="container-fluid"> <div class="container-fluid">
<div class="col-xl-12 mt-4"> <div class="col-xl-12 mt-4">
<div class="card border"> <div class="card border">
<div class="card-body" *ngIf="renewalDataExpanded && allItems.length; else noRecordsFound"> <div
class="card-body"
*ngIf="
renewalDataExpanded && allItems.length;
else noRecordsFound
"
>
<div class="table-section"> <div class="table-section">
<div class="row"> <div class="row">
<div class="col-lg-12"> <div class="col-lg-12">
<div class="card-body mt-2 p-0"> <div class="card-body mt-2 p-0">
<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
{{'SetupUserDetails' | translate}} class="card-header font-edit-13-child d-flex justify-content-between align-items-center"
>
{{ "SetupUserDetails" | translate }}
<div class="d-flex align-items-center gap-2"> <div class="d-flex align-items-center gap-2">
<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"
placeholder="{{ 'search' | translate }}"> placeholder="{{ 'search' | translate }}"
/>
<i class="fas fa-search search-icon"></i> <i class="fas fa-search search-icon"></i>
</div> </div>
<i class="materialdesignicons" (click)="toggleTableCard()"> <i
<ng-container *ngIf="userSetupDataExpanded; else collapsedIcon"> class="materialdesignicons"
(click)="toggleTableCard()"
>
<ng-container
*ngIf="
userSetupDataExpanded;
else collapsedIcon
"
>
<i class="dripicons-chevron-up float-end"></i> <i class="dripicons-chevron-up float-end"></i>
</ng-container> </ng-container>
<ng-template #collapsedIcon> <ng-template #collapsedIcon>
@ -227,79 +377,146 @@
</i> </i>
</div> </div>
</div> </div>
<div class="card-body" *ngIf="userSetupDataExpanded && allItems.length; else noRecordsFound"> <div
class="card-body"
*ngIf="
userSetupDataExpanded && allItems.length;
else noRecordsFound
"
>
<div class="table-responsive"> <div class="table-responsive">
<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: 30%">{{'userID' | translate}}</th> <th style="width: 25%">
<th style="width: 30%">{{'Name' | translate}}</th> {{ "userID" | translate }}
<th style="width: 30%">{{'Role' | translate}}</th> </th>
<th style="width: 10%">{{'action' | translate}}</th> <th style="width: 25%">
{{ "userFullname" | translate }}
</th>
<th style="width: 25%">
{{ "email" | translate }}
</th>
<th style="width: 15%">
{{ "Role" | translate }}
</th>
<th style="width: 10%">
{{ "action" | translate }}
</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr *ngFor="let item of (allItems | tableFilter: searchText : ['userId', 'userFullname']).slice((currentPage-1)*itemsPerPage, currentPage*itemsPerPage)"> <tr
*ngFor="
let item of (
<td>{{ item.userId }}</td> allItems
<td>{{ item.userFullname }}</td> | tableFilter
<td>{{item.role}}</td> : searchText
: ['userId', 'userFullname', 'email']
).slice(
<td> (currentPage - 1) * itemsPerPage,
<div class="d-flex justify-content-center gap-2"> currentPage * itemsPerPage
)
<button class="btn btn-info btn-sm" title="View" (click)="onView(item.userId)"> "
<i class="mdi mdi-eye-outline"></i> >
</button> <td>{{ item.userId }}</td>
<button class="btn btn-warning btn-sm" *ngIf="buttonPermissions?.resetPasswordButton" title="Reset Password" (click)="openResetPasswordModal(item.userId)"> <td>{{ item.userFullname }}</td>
<i class="mdi mdi-lock-reset"></i> <td>{{ item.email }}</td>
</button> <td>{{ getRoleLabel(item.role) }}</td>
<button *ngIf="buttonPermissions?.delete" class="btn btn-danger btn-sm" title="Delete"
(click)="confirmDelete(item.userId)"> <td>
<i class="fas fa-trash-alt"></i> <div
</button> class="d-flex justify-content-center gap-2"
>
</div> <button
</td> class="btn btn-info btn-sm"
title="View/Edit"
(click)="onView(item.userId)"
>
<i class="mdi mdi-eye-outline"></i>
</button>
<button
class="btn btn-warning btn-sm"
*ngIf="
buttonPermissions?.resetPasswordButton
"
title="Reset Password"
(click)="
openResetPasswordModal(item.userId)
"
>
<i class="mdi mdi-lock-reset"></i>
</button>
<button
*ngIf="buttonPermissions?.delete"
class="btn btn-danger btn-sm"
title="Delete"
(click)="confirmDelete(item.userId)"
>
<i class="fas fa-trash-alt"></i>
</button>
</div>
</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
<app-reset-password-modal [userId]="selectedUserId"> <app-reset-password-modal
</app-reset-password-modal> [userId]="selectedUserIdForReset ?? ''"
<div class="d-flex justify-content-between align-items-center mt-3"> (modalClosed)="closeResetPasswordModal()">
<div class="form-group mb-0"> </app-reset-password-modal>
<ng-select class="form-select-sm" <div
[items]="pageSizeOptions" class="d-flex justify-content-between align-items-center mt-3"
bindLabel="label" >
bindValue="value" <div class="form-group mb-0">
[(ngModel)]="itemsPerPage" <ng-select
(change)="itemsPerPageChanged()" class="form-select-sm"
[searchable]="false" [items]="pageSizeOptions"
[clearable]="false" bindLabel="label"
[dropdownPosition]="'top'"> bindValue="value"
</ng-select> [(ngModel)]="itemsPerPage"
</div> (change)="itemsPerPageChanged()"
[searchable]="false"
<div class="text-muted" *ngIf="allItems.length > 1"> [clearable]="false"
{{'page' | translate}} {{currentPage}} {{'of' | [dropdownPosition]="'top'"
translate}} {{totalPages()}} ({{allItems.length}} >
{{'totalItems' | translate}}) <ng-template ng-option-tmp let-item="item">
</div> {{ item.value }} {{ "entries" | translate }}
</ng-template>
<ng-template ng-label-tmp let-item="item">
{{ item.value }} {{ "entries" | translate }}
</ng-template>
</ng-select>
</div>
<div class="btn-group"> <div
<button class="btn btn-primary waves-effect waves-light" (click)="previousPage()"> class="text-muted"
{{ 'previous' | translate }} *ngIf="allItems.length > 1"
</button> >
<button class="btn btn-primary waves-effect waves-light" (click)="nextPage()"> {{ "page" | translate }} {{ currentPage }}
{{ 'next' | translate }} {{ "of" | translate }} {{ totalPages() }} ({{
</button> allItems.length
</div> }}
</div> {{ "totalItems" | translate }})
</div>
<div class="btn-group">
<button
class="btn btn-primary waves-effect waves-light"
(click)="previousPage()"
[disabled]="currentPage === 1"
>
{{ "previous" | translate }}
</button>
<button
class="btn btn-primary waves-effect waves-light"
(click)="nextPage()"
[disabled]="currentPage === totalPages()"
>
{{ "next" | translate }}
</button>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -315,7 +532,10 @@
</div> </div>
</div> </div>
<ng-template #noRecordsFound> <ng-template #noRecordsFound>
<div *ngIf="!isLoading && allItems.length === 0" class="text-center text-muted mt-3"> <div
<p>{{'noUserDetailsFound' | translate}}</p> *ngIf="!isLoading && allItems.length === 0"
</div> class="text-center text-muted mt-3"
>
<p>{{ "noUserDetailsFound" | translate }}</p>
</div>
</ng-template> </ng-template>

@ -5,7 +5,7 @@ import { NgSelectModule } from '@ng-select/ng-select';
import { TranslateModule } from '@ngx-translate/core'; 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 { FormBuilder, Validators, FormGroup } from '@angular/forms'; import { FormBuilder, Validators, FormGroup, AbstractControl } from '@angular/forms';
import { ButtonManagementService } from '../../services/button-management.service'; import { ButtonManagementService } from '../../services/button-management.service';
import { StorageService } from '../../shared/services/storage.service'; import { StorageService } from '../../shared/services/storage.service';
import { TableFilterPipe } from '../../shared/pipes/table-filter.pipe'; import { TableFilterPipe } from '../../shared/pipes/table-filter.pipe';
@ -13,50 +13,53 @@ 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 { I18NService } from '../../services/i18n.service'; import { I18NService } from '../../services/i18n.service';
import { SuccessMessages } from '../../utils/enums'; import { SuccessMessages, ErrorMessages } from '../../utils/enums';
import { ResetPasswordModalComponent } from '../reset-password-modal/reset-password-modal.component'; import { ResetPasswordModalComponent } from '../reset-password-modal/reset-password-modal.component';
@Component({ @Component({
selector: 'app-setup-user', selector: 'app-setup-user',
standalone: true, standalone: true,
imports: [TranslateModule, ReactiveFormsModule, FormsModule, CommonModule, NgSelectModule, TableFilterPipe, ResetPasswordModalComponent ], imports: [TranslateModule, ReactiveFormsModule, FormsModule, CommonModule, NgSelectModule, TableFilterPipe, ResetPasswordModalComponent],
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; userForm!: FormGroup;
selectedUserIdForEdit: string | null = null; // For edit form
selectedUserIdForReset: string | null = null; // For reset password modal
showForm = false; showForm = false;
selectedUserId!: any;
showDeleteModal = false; showDeleteModal = false;
userIdToDelete: any = null; userIdToDelete: any = null;
allItems: SetupUser[] = []; allItems: SetupUser[] = [];
currentPage: number = 1; currentPage: number = 1;
pageSizeOptions = pageSizeOptions pageSizeOptions = pageSizeOptions;
itemsPerPage: number = 10; itemsPerPage: number = 10;
pagedItems: any[] = []; pagedItems: any[] = [];
searchText: any = ''; searchText: any = '';
renewalDataExpanded: boolean = true; renewalDataExpanded: boolean = true;
totalCount: number = 0; totalCount: number = 0;
mode: 'edit' | 'view' = 'view'; mode: 'create' | 'edit' = 'create';
userSetupDataExpanded: boolean = true userSetupDataExpanded: boolean = true;
buttonPermissions: any; buttonPermissions: any;
isLoading: boolean = false; isLoading: boolean = false;
setupUserList: SetupUser[] = []; setupUserList: SetupUser[] = [];
// Store original user data for comparison
originalUserData: SetupUser | null = null;
roleOptions = [ roleOptions = [
{ label: 'Admin', value: 'ADMIN' }, { label: 'Admin', value: 'ADMIN' },
{ label: 'User', value: 'USER' }, { label: 'User', value: 'USER' },
]; ];
constructor( constructor(
private fb: FormBuilder, private fb: FormBuilder,
private buttonManagementService: ButtonManagementService, private buttonManagementService: ButtonManagementService,
private storageService: StorageService, private storageService: StorageService,
private httpService: HttpURIService, private httpService: HttpURIService,
private i18nService: I18NService private i18nService: I18NService
){} ) {}
onSearch(value: string): void { onSearch(value: string): void {
this.searchText = value; this.searchText = value;
@ -82,95 +85,202 @@ export class SetupUserComponent implements OnInit {
this.currentPage = 1; this.currentPage = 1;
this.updatePagedItems(); this.updatePagedItems();
} }
// Submit for creating new user
onSubmit() { onSubmit() {
if (this.userForm.invalid) { if (this.userForm.invalid) {
this.userForm.markAllAsTouched(); this.userForm.markAllAsTouched();
return; return;
} }
const newUser : SetupUser = { const newUser: SetupUser = {
userId: this.userForm.value.userId, userId: this.userForm.value.userId.trim(),
userFullname: this.userForm.value.userFullname, userFullname: this.userForm.value.userFullname.trim(),
email: this.userForm.value.email, email: this.userForm.value.email.trim(),
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
} };
this.httpService.requestPOST<SetupUser[]>(URIKey.CREATE_USER, newUser).subscribe({ this.isLoading = true;
this.httpService.requestPOST<SetupUser>(URIKey.CREATE_USER, newUser).subscribe({
next: (response) => { next: (response) => {
if (!(response instanceof HttpErrorResponse)) { if (!(response instanceof HttpErrorResponse)) {
this.i18nService.success(SuccessMessages.USER_CREATED_SUCCESS, []); this.i18nService.success(SuccessMessages.USER_CREATED_SUCCESS, []);
this.userForm.reset(); this.resetForm();
this.mode = 'edit'; this.loadUsersDirect();
this.loadUsersDirect()
} }
this.isLoading = false;
},
error: (error) => {
console.error('Error creating user:', error);
this.isLoading = false;
}
});
}
// Update existing user
onUpdate() {
if (this.userForm.invalid) {
this.userForm.markAllAsTouched();
return;
} }
const updatedUser: any = {
userId: this.selectedUserIdForEdit,
userFullname: this.userForm.value.userFullname.trim(),
email: this.userForm.value.email.trim(),
role: this.userForm.value.userRole,
porOrgacode: this.storageService.getItem('POR_ORGACODE')
};
// Only include password if it was provided
if (this.userForm.value.defaultPassword && this.userForm.value.defaultPassword.trim()) {
updatedUser.password = this.userForm.value.defaultPassword;
}
let params = new HttpParams().set('userId', this.selectedUserIdForEdit!);
this.isLoading = true;
this.httpService.requestPUT<SetupUser>(URIKey.UPDATE_USER, updatedUser, undefined, params).subscribe({
next: (response) => {
if (!(response instanceof HttpErrorResponse)) {
this.i18nService.success(SuccessMessages.USER_UPDATED_SUCCESS, []);
this.resetForm();
this.loadUsersDirect();
}
this.isLoading = false;
},
error: (error) => {
console.error('Error updating user:', error);
this.isLoading = false;
}
}); });
}
// Cancel edit mode
cancelEdit() {
this.resetForm();
}
// Reset form to create mode
resetForm() {
this.userForm.reset();
this.userForm.enable();
this.mode = 'create';
this.selectedUserIdForEdit = null;
this.originalUserData = null;
// Reset form validation state
Object.keys(this.userForm.controls).forEach(key => {
const control = this.userForm.get(key);
control?.markAsPristine();
control?.markAsUntouched();
});
} }
// Check if form has been modified
isFormDirty(): boolean {
if (!this.originalUserData || this.mode !== 'edit') {
return true;
}
const currentValues = this.userForm.value;
// Check if any field has changed (excluding password)
return (
currentValues.userFullname !== this.originalUserData.userFullname ||
currentValues.email !== this.originalUserData.email ||
currentValues.userRole !== this.originalUserData.role ||
(currentValues.defaultPassword && currentValues.defaultPassword.trim() !== '')
);
}
// Get role label for display
getRoleLabel(roleValue: string): string {
const role = this.roleOptions.find(r => r.value === roleValue);
return role ? role.label : roleValue;
}
updatePagedItems(): void { updatePagedItems(): void {
const startIndex = (this.currentPage - 1) * this.itemsPerPage; const startIndex = (this.currentPage - 1) * this.itemsPerPage;
const endIndex = startIndex + this.itemsPerPage; const endIndex = startIndex + this.itemsPerPage;
this.pagedItems = this.allItems.slice(startIndex, endIndex); this.pagedItems = this.allItems.slice(startIndex, endIndex);
} }
getButtonPermissions(){ getButtonPermissions() {
this.buttonPermissions = this.buttonManagementService.buttonPermissions["setupUser"]; this.buttonPermissions = this.buttonManagementService.buttonPermissions["setupUser"];
} }
toggleTableCard(): void { toggleTableCard(): void {
this.userSetupDataExpanded = !this.userSetupDataExpanded; this.userSetupDataExpanded = !this.userSetupDataExpanded;
} }
confirmDelete(userId: string) { confirmDelete(userId: string) {
const confirmed = window.confirm('Are you sure you want to delete this user?'); const confirmed = window.confirm('Are you sure you want to delete this user?');
if (confirmed) { if (confirmed) {
this.onDelete(userId); this.onDelete(userId);
}
} }
}
ngOnInit(): void {
this.getButtonPermissions(); ngOnInit(): void {
this.getButtonPermissions();
this.initializeForm();
this.loadUsersDirect();
}
this.userForm = this.fb.group({ initializeForm(): void {
this.userForm = this.fb.group({
userId: ['', [ userId: ['', [
Validators.required, Validators.required,
Validators.minLength(5), Validators.minLength(5),
Validators.pattern(/^\S+$/) Validators.pattern('^[a-z0-9]*$')
] ]],
],
userFullname: ['', [ userFullname: ['', [
Validators.required, Validators.required,
Validators.minLength(5) Validators.minLength(5),
] Validators.maxLength(500)
], ]],
defaultPassword: ['', [ defaultPassword: ['', [
Validators.required, // Make password optional for edit mode, required for create
(control: AbstractControl) => {
if (this.mode === 'create' && !control.value) {
return { required: true };
}
return null;
},
Validators.pattern( Validators.pattern(
/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,20}$/ /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,20}$/
) )
] ]],
],
email: ['', [Validators.required, Validators.email]], email: ['', [Validators.required, Validators.email]],
userRole: [null, Validators.required] userRole: [null, Validators.required]
}); });
this.loadUsersDirect();
// Update password validation when mode changes
this.userForm.get('defaultPassword')?.valueChanges.subscribe(() => {
const passwordControl = this.userForm.get('defaultPassword');
if (passwordControl) {
passwordControl.updateValueAndValidity();
}
});
} }
loadUsersDirect(): void { loadUsersDirect(): void {
this.isLoading = false; this.isLoading = true;
let params = new HttpParams() let params = new HttpParams()
.set('page', this.currentPage.toString()) .set('page', (this.currentPage - 1).toString())
.set('size', this.itemsPerPage.toString()); .set('size', this.itemsPerPage.toString());
this.httpService.requestGET<any[]>(URIKey.GET_ALL_USER_URI, params).subscribe({ this.httpService.requestGET<any>(URIKey.GET_ALL_USER_URI, params).subscribe({
next: (response) => { next: (response) => {
this.setupUserList = response if (response && response.content) {
this.allItems = [...this.setupUserList]; this.setupUserList = response.content;
this.totalCount = response.totalElements || response.content.length;
} else {
this.setupUserList = response;
}
this.allItems = [...this.setupUserList];
this.updatePagedItems(); this.updatePagedItems();
this.isLoading = false; this.isLoading = false;
}, },
@ -182,55 +292,144 @@ ngOnInit(): void {
}); });
} }
onView(userId: any): void{ onView(userId: string): void {
let params = new HttpParams().set('userId', userId); let params = new HttpParams().set('userId', userId);
this.isLoading = true;
this.httpService.requestGET<SetupUser>(URIKey.GET_USER_BY_ID, params).subscribe({ this.httpService.requestGET<SetupUser>(URIKey.GET_USER_BY_ID, params).subscribe({
next: (response: SetupUser) => { next: (response: SetupUser) => {
// Store original data
this.originalUserData = { ...response };
// Patch form values
this.userForm.patchValue({ this.userForm.patchValue({
userId: response.userId, userId: response.userId,
userFullname: response.userFullname, userFullname: response.userFullname,
email: response.email, email: response.email,
userRole: response.role, userRole: response.role,
defaultPassword: '' defaultPassword: '' // Clear password field for security
}); });
this.userForm.disable();
this.selectedUserId = userId; // Enable form for editing
this.isLoading = false; this.userForm.enable();
// Disable userId field (it shouldn't be changed)
this.userForm.get('userId')?.disable();
// Set the edit userId
this.selectedUserIdForEdit = userId;
this.mode = 'edit'; // Set mode to edit
this.isLoading = false;
// Scroll to form
this.scrollToForm();
}, },
error: (err) => { error: (err) => {
console.error('Error fetching users:', err); console.error('Error fetching user:', err)
this.allItems = [];
this.isLoading = false; this.isLoading = false;
} }
}); });
} }
onDelete(userId: any){ onDelete(userId: string) {
let params = new HttpParams().set('userId', userId); let params = new HttpParams().set('userId', userId);
this.isLoading = true;
this.httpService.requestDELETE<any>(URIKey.DELETE_USER, params).subscribe({ this.httpService.requestDELETE<any>(URIKey.DELETE_USER, params).subscribe({
next: (response) =>{ next: (response) => {
if (!(response instanceof HttpErrorResponse)) { if (!(response instanceof HttpErrorResponse)) {
this.i18nService.success(SuccessMessages.USER_DELETE_SUCCESS, []); this.i18nService.success(SuccessMessages.USER_DELETE_SUCCESS, []);
this.loadUsersDirect(); this.loadUsersDirect();
this.userForm.reset(); this.resetForm();
this.selectedUserId = null; }
} this.isLoading = false;
},
error: (error) => {
console.error('Error deleting user:', error);
this.i18nService.error(ErrorMessages.USER_DELETE_FAILED, []);
this.isLoading = false;
} }
}) });
} }
openResetPasswordModal(userId: string) { openResetPasswordModal(userId: string) {
this.selectedUserId = userId; console.log('Opening reset password modal for userId:', userId);
this.selectedUserIdForReset = userId;
const modal = document.getElementById('resetPasswordModal'); // Show the modal
modal?.classList.add('show'); const modal = document.getElementById('resetPasswordModal');
modal!.style.display = 'block'; if (modal) {
document.body.classList.add('modal-open'); modal.classList.add('show');
modal.style.display = 'block';
document.body.classList.add('modal-open');
const backdrop = document.createElement('div'); // Add backdrop
backdrop.className = 'modal-backdrop fade show'; const backdrop = document.createElement('div');
document.body.appendChild(backdrop); backdrop.className = 'modal-backdrop fade show';
} document.body.appendChild(backdrop);
// Listen for modal close event
const closeModal = () => {
this.closeResetPasswordModal();
};
// Add click event to backdrop
backdrop.addEventListener('click', closeModal);
// Add event listener for escape key
const handleEscape = (event: KeyboardEvent) => {
if (event.key === 'Escape') {
closeModal();
}
};
document.addEventListener('keydown', handleEscape);
// Store event listeners for cleanup
(modal as any).__backdropListener = closeModal;
(modal as any).__escapeListener = handleEscape;
}
}
} // Helper method to scroll to form
private scrollToForm(): void {
setTimeout(() => {
const formElement = document.querySelector('.card.border');
if (formElement) {
formElement.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
}, 100);
}
closeResetPasswordModal(): void {
const modal = document.getElementById('resetPasswordModal');
// Remove event listeners
if (modal) {
const backdrop = document.querySelector('.modal-backdrop');
if (backdrop && (modal as any).__backdropListener) {
backdrop.removeEventListener('click', (modal as any).__backdropListener);
}
if ((modal as any).__escapeListener) {
document.removeEventListener('keydown', (modal as any).__escapeListener);
}
}
// Hide modal
if (modal) {
modal.classList.remove('show');
modal.style.display = 'none';
}
// Remove backdrop
const backdrop = document.querySelector('.modal-backdrop');
if (backdrop) {
backdrop.remove();
}
document.body.classList.remove('modal-open');
// Clear the reset userId
this.selectedUserIdForReset = null;
}
}

@ -5,9 +5,9 @@ export const CONSTANTS = {
}; };
export const pageSizeOptions = [ export const pageSizeOptions = [
{ value: 10, label: '10 items' }, { value: 10, label: '10' },
{ value: 20, label: '20 items' }, { value: 20, label: '20' },
{ value: 50, label: '50 items' }, { value: 50, label: '50' },
]; ];
export const toDateAfterFromDateValidator: ValidatorFn = ( export const toDateAfterFromDateValidator: ValidatorFn = (
@ -30,7 +30,7 @@ export const toDateAfterFromDateValidator: ValidatorFn = (
today.setHours(0, 0, 0, 0); today.setHours(0, 0, 0, 0);
// Rule 1: fromDate must be < toDate // Rule 1: fromDate must be < toDate
if (fromDate >= toDate) { if (fromDate > toDate) {
return { fromDateGreaterThanOrEqualToToDate: true }; return { fromDateGreaterThanOrEqualToToDate: true };
} }

@ -6,6 +6,13 @@ export enum ErrorMessages{
UNAUTHORIZED_REQUEST = "UNAUTHORIZED_REQUEST", UNAUTHORIZED_REQUEST = "UNAUTHORIZED_REQUEST",
ALREADY_LOGGED_IN = "ALREADY_LOGGED_IN", ALREADY_LOGGED_IN = "ALREADY_LOGGED_IN",
ACCESS_DENIED = "ACCESS_DENIED", ACCESS_DENIED = "ACCESS_DENIED",
USER_CREATE_FAILED = "USER_CREATE_FAILED",
USER_UPDATE_FAILED = "USER_UPDATE_FAILED",
USER_DELETE_FAILED = "USER_DELETE_FAILED",
USER_FETCH_FAILED = "USER_FETCH_FAILED",
RESET_PASSWORD_FAILED = "RESET_PASSWORD_FAILED",
CHANGE_PASSWORD_FAILED = "ERR_SEC_0007"
} }
export enum supportedLanguages{ export enum supportedLanguages{
@ -60,7 +67,10 @@ RECORD_DELETED_SUCCESSFULY = "RECORD_DELETED_SUCCESSFULY",
ACCOUNT_CLOSED_SUCCESSFULLY = "ACCOUNT_CLOSED_SUCCESSFULLY", ACCOUNT_CLOSED_SUCCESSFULLY = "ACCOUNT_CLOSED_SUCCESSFULLY",
SUCCESS_MESSAGE = "SUCCESS_MESSAGE", SUCCESS_MESSAGE = "SUCCESS_MESSAGE",
USER_CREATED_SUCCESS = "USER_CREATED_SUCCESS", USER_CREATED_SUCCESS = "USER_CREATED_SUCCESS",
USER_UPDATE_SUCCESS = "USER_UPDATE_SUCCESS",
USER_UPDATED_SUCCESS = "USER_UPDATED_SUCCESS",
USER_DELETE_SUCCESS = "USER_DELETE_SUCCESS" USER_DELETE_SUCCESS = "USER_DELETE_SUCCESS"
} }
export enum MESSAGEKEY { export enum MESSAGEKEY {

@ -14,5 +14,6 @@ export enum URIKey {
FIRST_LOGIN_URI = "FIRST_LOGIN_URI", FIRST_LOGIN_URI = "FIRST_LOGIN_URI",
THIRD_PARTY_REGISTER_URI = "THIRD_PARTY_REGISTER_URI", THIRD_PARTY_REGISTER_URI = "THIRD_PARTY_REGISTER_URI",
TRANSACTION_LOGS = "TRANSACTION_LOGS", TRANSACTION_LOGS = "TRANSACTION_LOGS",
LOGGER_MANAGER_URI = "LOGGER_MANAGER_URI" LOGGER_MANAGER_URI = "LOGGER_MANAGER_URI",
UPDATE_USER = 'UPDATE_USER'
} }

@ -27,6 +27,11 @@
"URI": "/user/createUser", "URI": "/user/createUser",
"UUID": "CREATE_USER" "UUID": "CREATE_USER"
}, },
{
"Id": "ENTITY_UPDATE_USER",
"URI": "/user/updateUser",
"UUID": "UPDATE_USER"
},
{ {
"Id": "ENTITY_GET_ALL_USERS", "Id": "ENTITY_GET_ALL_USERS",
"URI": "/user/getAllUsers", "URI": "/user/getAllUsers",

@ -1,273 +1,299 @@
{ {
"logintoAccount":"تسجيل الدخول إلى حسابك", "logintoAccount": "تسجيل الدخول إلى حسابك",
"userName":"اسم المستخدم", "userName": "اسم المستخدم",
"userNamePlaceHolder":"ادخل اسم المستخدم", "userNamePlaceHolder": "ادخل اسم المستخدم",
"password":"كلمة المرور", "password": "كلمة المرور",
"passwordPlaceHolder":"أدخل كلمة المرور", "passwordPlaceHolder": "أدخل كلمة المرور",
"defaultPassword": "كلمة المرور الافتراضية", "defaultPassword": "كلمة المرور الافتراضية",
"rememberMe":"تذكرنى", "rememberMe": "تذكرنى",
"forgotPassword":"هل نسيت كلمة السر؟", "forgotPassword": "هل نسيت كلمة السر؟",
"passwordTooShort": "كلمة المرور قصيرة جدًا.", "passwordTooShort": "كلمة المرور قصيرة جدًا.",
"passwordsDoNotMatch": "كلمتا المرور غير متطابقتين.", "passwordsDoNotMatch": "كلمتا المرور غير متطابقتين.",
"login":"تسجيل الدخول", "login": "تسجيل الدخول",
"dashboardTitle":"لوحة القيادة", "dashboardTitle": "لوحة القيادة",
"passwordChangeRequired": "تغيير كلمة المرور مطلوب", "passwordChangeRequired": "تغيير كلمة المرور مطلوب",
"monthlyReqTitle":"الطلبات الشهرية", "monthlyReqTitle": "الطلبات الشهرية",
"monthlySmsTitle":"الرسائل القصيرة الشهرية", "monthlySmsTitle": "الرسائل القصيرة الشهرية",
"Dashboard":"لوحة القيادة", "Dashboard": "لوحة القيادة",
"UserManagement":"إدارةالمستخدم", "UserManagement": "إدارةالمستخدم",
"thirdPartyRegistration":"تسجيل الطرف الثالث", "thirdPartyRegistration": "تسجيل الطرف الثالث",
"setupUser":"مستخدم الإعداد", "setupUser": "مستخدم الإعداد",
"resetPassword":"إعادة تعيين كلمة مرور المستخدم", "resetPassword": "إعادة تعيين كلمة مرور المستخدم",
"changePassword":"غير كلمة السر", "changePassword": "غير كلمة السر",
"Logging":"تسجيل", "Logging": "تسجيل",
"loggerManager":"مدير المسجل", "loggerManager": "مدير المسجل",
"SMSBanking":"خدمة الرسائل المصرفية", "SMSBanking": "خدمة الرسائل المصرفية",
"smsLogger":"مسجل الرسائل", "smsLogger": "مسجل الرسائل",
"smsLoggerDetails": "تفاصيل سجل الرسائل القصيرة", "smsLoggerDetails": "تفاصيل سجل الرسائل القصيرة",
"smsGateway":"بوابة الرسائل", "smsGateway": "بوابة الرسائل",
"ibUnblockUser":"إلغاء حظر مستخدم IB", "ibUnblockUser": "إلغاء حظر مستخدم IB",
"ibSupport":"دعم IB", "ibSupport": "دعم IB",
"Permissions":"أذونات", "Permissions": "أذونات",
"permissions":"مدير الأذونات", "permissions": "مدير الأذونات",
"TodayTotalRequests":"إجمالي الطلبات اليوم", "TodayTotalRequests": "إجمالي الطلبات اليوم",
"TodayTotalSms":"مجموع الرسائل القصيرة اليوم", "TodayTotalSms": "مجموع الرسائل القصيرة اليوم",
"DescriptionUserPermission":"وصف", "DescriptionUserPermission": "وصف",
"PermissionsUserPermission":"أذونات", "PermissionsUserPermission": "أذونات",
"SaveUserPermission":"يحفظ", "SaveUserPermission": "يحفظ",
"UpdateUserPermission":"تحديث", "UpdateUserPermission": "تحديث",
"DeleteUserPermission":"حذف", "DeleteUserPermission": "حذف",
"ViewUserPermission":"رأي", "ViewUserPermission": "رأي",
"SelectUser":"اختر المستخدم", "SelectUser": "اختر المستخدم",
"SelectAUser":"حدد مستخدمًا", "SelectAUser": "حدد مستخدمًا",
"loggingTitle":"تسجيل", "loggingTitle": "تسجيل",
"IBChildTitle":"إلغاء حظر عن مستخدم", "IBChildTitle": "إلغاء حظر عن مستخدم",
"IBTitle":"تسجيل", "IBTitle": "تسجيل",
"loggingChildTitle":"مدير المسجل", "loggingChildTitle": "مدير المسجل",
"fromDate":"من التاريخ", "fromDate": "من التاريخ",
"custId": "قيمة هوية العميل", "custId": "قيمة هوية العميل",
"toDate":"حتي اليوم", "toDate": "حتي اليوم",
"findLogs":"البحث عن السجلات", "findLogs": "البحث عن السجلات",
"unBlockCustomer":"إلغاء حظر العميل", "unBlockCustomer": "إلغاء حظر العميل",
"unblockUserDetails": "إلغاء حظر تفاصيل المستخدم", "unblockUserDetails": "إلغاء حظر تفاصيل المستخدم",
"loggerManagerDetails": "تفاصيل مدير السجلات", "loggerManagerDetails": "تفاصيل مدير السجلات",
"fetchCustomer": "ابحث عن العملاء", "fetchCustomer": "ابحث عن العملاء",
"loggingID":"معرف", "loggingID": "معرف",
"firstName":"الاسم الأول", "firstName": "الاسم الأول",
"lastName": "اسم العائلة", "lastName": "اسم العائلة",
"cmpCuststatus":"حالة العميل", "cmpCuststatus": "حالة العميل",
"cmpCustlastlogin": "آخر تسجيل دخول للعميل", "cmpCustlastlogin": "آخر تسجيل دخول للعميل",
"accountNonLocked":"الحساب مغلق", "accountNonLocked": "الحساب مغلق",
"lockTime":"وقت القفل", "lockTime": "وقت القفل",
"accountno":"رقم الحساب", "accountno": "رقم الحساب",
"phoneno":"رقم الهاتف", "phoneno": "رقم الهاتف",
"loggingRequestUri":"طلب Uri", "loggingRequestUri": "طلب Uri",
"loggingResponseCode":"رمز الاستجابة", "loggingResponseCode": "رمز الاستجابة",
"loggingRemoteIP":"بعيد IP", "loggingRemoteIP": "بعيد IP",
"enterIdentityValue": "أدخل قيمة الهوية", "enterIdentityValue": "أدخل قيمة الهوية",
"loggingTimeTaken":"الوقت المستغرق", "loggingTimeTaken": "الوقت المستغرق",
"loggingDate":"تاريخ", "loggingDate": "تاريخ",
"loggingDateTime": "التاريخ والوقت", "loggingDateTime": "التاريخ والوقت",
"loggingMethod":"طريقة", "loggingMethod": "طريقة",
"DataAnalysis":"تحليل البيانات", "DataAnalysis": "تحليل البيانات",
"Analysis":"التحليلات", "Analysis": "التحليلات",
"smsBankingTitle":"خدمة الرسائل المصرفية", "smsBankingTitle": "خدمة الرسائل المصرفية",
"smsBankingChildTitle":"مسجل الرسائل", "smsBankingChildTitle": "مسجل الرسائل",
"smsTrackingID":"معرف تتبع", "smsTrackingID": "معرف تتبع",
"smsMessage":"رسالة", "smsMessage": "رسالة",
"smsNo":"رقم الهاتف", "smsNo": "رقم الهاتف",
"smsOrgaCode":"كود المنظمة", "smsOrgaCode": "كود المنظمة",
"smsDate":"تاريخ", "smsDate": "تاريخ",
"smsStatus":"حالة", "smsStatus": "حالة",
"viewThirdPartyAccounts":"عرض الحسابات", "viewThirdPartyAccounts": "عرض الحسابات",
"ThirdPartyID":"معرف الحفلة الثالثة", "ThirdPartyID": "معرف الحفلة الثالثة",
"name":"اسم", "name": "اسم",
"EnterThirdPartyName":"أدخل اسم الطرف الثالث", "EnterThirdPartyName": "أدخل اسم الطرف الثالث",
"Email":"البريد الإلكتروني", "email": "البريد الإلكتروني",
"invalidEmail": "أدخل بريدًا إلكترونيًا صالحًا يحتوي على @", "invalidEmail": "أدخل بريدًا إلكترونيًا صالحًا يحتوي على @",
"Address":"تبوك", "Address": "تبوك",
"phoneNumber":"رقم الهاتف", "phoneNumber": "رقم الهاتف",
"PhoneNumberPlaceHolder":"أدخل رقم الهاتف", "PhoneNumberPlaceHolder": "أدخل رقم الهاتف",
"regPhoneNo": "يجب أن يتكون رقم الهاتف من 10 أرقام", "regPhoneNo": "يجب أن يتكون رقم الهاتف من 10 أرقام",
"NewNoOfAccounts":"عدد جديد من الحسابات", "NewNoOfAccounts": "عدد جديد من الحسابات",
"EnterNewNumberOfAccounts":"أدخل عددًا جديدًا من الحسابات", "EnterNewNumberOfAccounts": "أدخل عددًا جديدًا من الحسابات",
"TotalNoOfAccounts":"العدد الإجمالي للحسابات", "TotalNoOfAccounts": "العدد الإجمالي للحسابات",
"gridSearch":"يبحث:", "gridSearch": "يبحث:",
"gridShow":"يعرض", "gridShow": "يعرض",
"gridEntries":"إدخالات", "gridEntries": "إدخالات",
"gridFilter":"منقي", "gridFilter": "منقي",
"gridNum10":"عشرة", "gridNum10": "عشرة",
"gridNum25":"خمسة وعشرون", "gridNum25": "خمسة وعشرون",
"gridNum50":"خمسون", "gridNum50": "خمسون",
"gridNum100":"مائة", "gridNum100": "مائة",
"userID":"معرف المستخدم", "userID": "معرف المستخدم",
"userId":"معرف المستخدم", "userId": "معرف المستخدم",
"userContactNumber":"أدخل رقم اتصال المستخدم", "userContactNumber": "أدخل رقم اتصال المستخدم",
"SelectHomeBranch":"حدد الفرع الرئيسي", "SelectHomeBranch": "حدد الفرع الرئيسي",
"SelectRole":"حدد الدور", "SelectRole": "حدد الدور",
"HomeBranch":"فرع المنزل", "HomeBranch": "فرع المنزل",
"Role":"دور", "Role": "دور",
"ResetPassword":"إعادة تعيين كلمة المرور", "ResetPassword": "إعادة تعيين كلمة المرور",
"enterNewPassword":"أدخل كلمة مرور جديدة", "enterNewPassword": "أدخل كلمة مرور جديدة",
"oldPassword":"كلمة المرور القديمة", "oldPassword": "كلمة المرور القديمة",
"newPasswordStatic":"كلمة السر الجديدة", "newPasswordStatic": "كلمة السر الجديدة",
"savePassword":"يحفظ", "savePassword": "يحفظ",
"passwordPattern":"يجب أن تكون كلمة المرور أكثر من 8 أحرف وتتضمن حرفًا كبيرًا وحرفًا صغيرًا ورقمًا وحرفًا خاصًا", "passwordPattern": "يجب أن تكون كلمة المرور أكثر من 8 أحرف وتتضمن حرفًا كبيرًا وحرفًا صغيرًا ورقمًا وحرفًا خاصًا",
"SMSGatewaySelect":"اختر صنف", "SMSGatewaySelect": "اختر صنف",
"selectIdentValueType": "حدد نوع قيمة الهوية", "selectIdentValueType": "حدد نوع قيمة الهوية",
"selectIdentityValue" : "رقم الهوية مطلوب", "selectIdentityValue": "رقم الهوية مطلوب",
"IdTypeSelect":"حدد تحديد النوع", "IdTypeSelect": "حدد تحديد النوع",
"SMSGatewaySyriatel":"سيريتل", "SMSGatewaySyriatel": "سيريتل",
"SMSGatewayTwillio":"تويليو", "SMSGatewayTwillio": "تويليو",
"SMSGatewayJazz":"جاز", "SMSGatewayJazz": "جاز",
"syriatelCredentials":"أوراق اعتماد سيريتل", "syriatelCredentials": "أوراق اعتماد سيريتل",
"twilioCredentials":"أوراق اعتماد تويليو", "twilioCredentials": "أوراق اعتماد تويليو",
"jazzCredentials":"أوراق اعتماد جاز", "jazzCredentials": "أوراق اعتماد جاز",
"accountSID":"الحساب SID", "accountSID": "الحساب SID",
"authToken":"رمز المصادقة", "authToken": "رمز المصادقة",
"fromNumber":"من الرقم", "fromNumber": "من الرقم",
"senderName":"اسم المرسل", "senderName": "اسم المرسل",
"senderNamePlaceHolder":"أدخل اسم المرسل", "senderNamePlaceHolder": "أدخل اسم المرسل",
"message": "رسالة", "message": "رسالة",
"template": "قالب", "template": "قالب",
"language":"اللغة", "language": "اللغة",
"notificationType": "نوع الإشعار", "notificationType": "نوع الإشعار",
"sinceLastDay":"منذ اليوم الماضي", "sinceLastDay": "منذ اليوم الماضي",
"TodayTotalErrorRequest":"طلب إجمالي الخطأ اليوم", "TodayTotalErrorRequest": "طلب إجمالي الخطأ اليوم",
"TodayTotalPendingSms":"مجموع الرسائل القصيرة المعلقة اليوم", "TodayTotalPendingSms": "مجموع الرسائل القصيرة المعلقة اليوم",
"selectTheDates":"الرجاء تحديد التواريخ", "selectTheDates": "الرجاء تحديد التواريخ",
"selectIdentValue":"الرجاء تحديد نوع الهوية", "selectIdentValue": "الرجاء تحديد نوع الهوية",
"noLogsFound":"لم يتم العثور على سجلات بين هذه التواريخ", "noLogsFound": "لم يتم العثور على سجلات بين هذه التواريخ",
"loginSuccess":"تم تسجيل الدخول بنجاح", "loginSuccess": "تم تسجيل الدخول بنجاح",
"passwordNotMatched":"كلمة السر غير متطابقة", "passwordNotMatched": "كلمة السر غير متطابقة",
"passwordPatternNotMatched":"نمط كلمة المرور غير مطابق", "passwordPatternNotMatched": "نمط كلمة المرور غير مطابق",
"successDeleted":"تم الحذف بنجاح", "successDeleted": "تم الحذف بنجاح",
"passwordNotSame":"لا يمكن أن تكون كلمة المرور هي نفسها كلمة المرور القديمة", "passwordNotSame": "لا يمكن أن تكون كلمة المرور هي نفسها كلمة المرور القديمة",
"SuccessSave":"تم الحفظ بنجاح", "SuccessSave": "تم الحفظ بنجاح",
"SuccessFind":"تجده بنجاح", "SuccessFind": "تجده بنجاح",
"customerAlreadyUnblocked": "العميل تم فتح حسابه مسبقًا", "customerAlreadyUnblocked": "العميل تم فتح حسابه مسبقًا",
"SuccessUpdate":"تم التحديث بنجاح", "SuccessUpdate": "تم التحديث بنجاح",
"UpdateFailed":"غير قادر على التحديث", "UpdateFailed": "غير قادر على التحديث",
"formInvalid":"النموذج غير صالح", "formInvalid": "النموذج غير صالح",
"ServerError":"خطأ في الخادم", "ServerError": "خطأ في الخادم",
"fieldsMissing":"الحقول المطلوبة مفقودة أو غير صالحة", "fieldsMissing": "الحقول المطلوبة مفقودة أو غير صالحة",
"selectAll":" اختر الكل " , "selectAll": " اختر الكل ",
"logoutSuccess":"تم تسجيل الخروج بنجاح", "logoutSuccess": "تم تسجيل الخروج بنجاح",
"smsGateWayChanged":"تم تغيير بوابة الرسائل القصيرة بنجاح", "smsGateWayChanged": "تم تغيير بوابة الرسائل القصيرة بنجاح",
"cnic_scnic": "CNIC / SCNIC", "cnic_scnic": "CNIC / SCNIC",
"poc": "POC", "poc": "POC",
"nicop": "S / NICOP", "nicop": "S / NICOP",
"passport": "جواز السفر", "passport": "جواز السفر",
"Next": "التالي", "Next": "التالي",
"ERR_APP_B_0001": "خطأ خادم داخلي", "ERR_APP_B_0001": "خطأ خادم داخلي",
"ERR_APP_B_0002": "خطأ اتصال محتمل مع الوحدة النمطية {{value1}}", "ERR_APP_B_0002": "خطأ اتصال محتمل مع الوحدة النمطية {{value1}}",
"ERR_APP_B_0003": "طلب غير صالح على وحدة {{value1}}", "ERR_APP_B_0003": "طلب غير صالح على وحدة {{value1}}",
"ERR_APP_B_0004": "انتهت الجلسة", "ERR_APP_B_0004": "انتهت الجلسة",
"ERR_APP_B_0005": "غير مصرح به: {{قيمة 1}}.", "ERR_APP_B_0005": "غير مصرح به: {{قيمة 1}}.",
"ERR_MDL_B_0001": "رمز الغرض موجود بالفعل", "ERR_MDL_B_0001": "رمز الغرض موجود بالفعل",
"feedbackSetup": "ردود الفعل الإعداد", "feedbackSetup": "ردود الفعل الإعداد",
"credentials": "أوراق اعتماد", "credentials": "أوراق اعتماد",
"credentialsTitle": "أوراق اعتماد ردود الفعل", "credentialsTitle": "أوراق اعتماد ردود الفعل",
"confirmPassword": "تأكيد كلمة المرور", "confirmPassword": "تأكيد كلمة المرور",
"passwordPlaceholder": "أدخل كلمة المرور", "passwordPlaceholder": "أدخل كلمة المرور",
"confirmPasswordPlaceholder": "أدخل تأكيد كلمة المرور", "confirmPasswordPlaceholder": "أدخل تأكيد كلمة المرور",
"newPasswordPlaceholder": "أدخل كلمة مرور جديدة", "newPasswordPlaceholder": "أدخل كلمة مرور جديدة",
"fieldRequired": "الخانة مطلوبة", "fieldRequired": "الخانة مطلوبة",
"emailLabel": "البريد الإلكتروني", "emailLabel": "البريد الإلكتروني",
"emailRequiredError": "البريد الإلكتروني مطلوب.", "emailRequiredError": "البريد الإلكتروني مطلوب.",
"invalidEmailFormatError": "تنسيق البريد الإلكتروني غير صالح.", "invalidEmailFormatError": "تنسيق البريد الإلكتروني غير صالح.",
"passNotMatch": "كلمة السر غير متطابقة", "passNotMatch": "كلمة السر غير متطابقة",
"POR_ORGACODE": "المصرف",
"POR_ORGACODE": "المصرف", "purposeSetup": "أضف غرض المعاملة",
"purposeSetup": "أضف غرض المعاملة", "purpcodeLabel": "كود الغرض",
"purpcodeLabel": "كود الغرض", "purpdescLabel": "وصف الغرض",
"purpdescLabel": "وصف الغرض", "purpcodePlaceholder": "أدخل رمز الغرض",
"purpcodePlaceholder": "أدخل رمز الغرض", "purpdescPlaceholder": "أدخل وصف الغرض",
"purpdescPlaceholder": "أدخل وصف الغرض", "transactionDetails": "تفاصيل المعاملة",
"transactionDetails": "تفاصيل المعاملة", "lengthExceed": " الطول تجاوز ",
"lengthExceed":" الطول تجاوز ", "transactionLogs": "سجلات المعاملات",
"transactionLogs": "سجلات المعاملات", "loadingTransactionLogs": "جاري تحميل سجلات المعاملات...",
"loadingTransactionLogs": "جاري تحميل سجلات المعاملات...", "noTransactionLogsFound": "لم يتم العثور على سجلات معاملات.",
"noTransactionLogsFound": "لم يتم العثور على سجلات معاملات.", "noThirdPartyRegFound": "لم يتم العثور على تفاصيل تسجيل الطرف الثالث",
"noThirdPartyRegFound":"لم يتم العثور على تفاصيل تسجيل الطرف الثالث", "logID": "معرف السجل",
"logID": "معرف السجل", "organization": "المنظمة",
"organization": "المنظمة", "transactionID": "معرف المعاملة",
"transactionID": "معرف المعاملة", "drAccount": "حساب المدين",
"drAccount": "حساب المدين", "crAccount": "حساب الدائن",
"crAccount": "حساب الدائن", "paymentMode": "وسيلة الدفع",
"paymentMode": "وسيلة الدفع", "channel": "القناة",
"channel": "القناة", "createdAt": "تم الإنشاء في",
"createdAt": "تم الإنشاء في", "refresh": "تحديث",
"refresh": "تحديث", "loading": "جاري التحميل",
"loading": "جاري التحميل", "invalidField": "ادخال غير صحيح",
"invalidField": "ادخال غير صحيح", "action": "اجراء",
"action": "اجراء", "confirmDelete": "هل أنت متأكد أنك تريد حذف؟",
"confirmDelete":"هل أنت متأكد أنك تريد حذف؟", "confirmSave": "هل أنت متأكد أنك تريد الحفظ؟",
"confirmSave":"هل أنت متأكد أنك تريد الحفظ؟", "2-stepAppPassword": "أدخل التحقق بخطوتين الذي تم إنشاؤه - كلمة مرور التطبيق",
"2-stepAppPassword": "أدخل التحقق بخطوتين الذي تم إنشاؤه - كلمة مرور التطبيق", "english": "إنجليزي",
"english": "إنجليزي", "arabic": "عربي",
"arabic": "عربي", "userNameRequired": "اسم المستخدم مطلوب",
"userNameRequired" : "اسم المستخدم مطلوب", "userNamePattterenError": "يُسمح فقط بالأحرف الصغيرة والأرقام",
"userNamePattterenError": "يُسمح فقط بالأحرف الصغيرة والأرقام", "PasswordRequired": "كلمة المرور مطلوبة",
"PasswordRequired": "كلمة المرور مطلوبة", "copyRightsReserved": "جميع الحقوق محفوظة © {{currentYearLong}} لشركة MFSYS Technologies.",
"copyRightsReserved": "جميع الحقوق محفوظة © {{currentYearLong}} لشركة MFSYS Technologies.", "versionAndBuildNumber": "الإصدار {{versionNumber}} البناء {{buildNumber}}",
"versionAndBuildNumber": "الإصدار {{versionNumber}} البناء {{buildNumber}}", "versionBuildDate": "+ تاريخ البناء {{date}}",
"versionBuildDate": "+ تاريخ البناء {{date}}", "dashboard": "لوحة القيادة",
"dashboard":"لوحة القيادة", "date": "تاريخ",
"date" : "تاريخ", "logout": "تسجيل الخروج",
"logout": "تسجيل الخروج", "save": "يحفظ",
"save": "يحفظ", "update": "تحديث",
"update": "تحديث", "cancel": "يلغي",
"cancel": "يلغي", "thirdPartyRegistrationDetails": "تفاصيل تسجيل الطرف الثالث",
"thirdPartyRegistrationDetails": "تفاصيل تسجيل الطرف الثالث", "SetupUserDetails": "إعداد تفاصيل المستخدم",
"SetupUserDetails": "إعداد تفاصيل المستخدم", "search": "يبحث",
"search": "يبحث", "thirdPartyNamePlaceholder": "أدخل اسم الطرف الثالث",
"thirdPartyNamePlaceholder": "أدخل اسم الطرف الثالث", "phoneNumberPlaceholder": "أدخل رقم الهاتف",
"phoneNumberPlaceholder": "أدخل رقم الهاتف", "newNoOfAccountsPlaceholder": "أدخل عدد الحسابات الجديد",
"newNoOfAccountsPlaceholder": "أدخل عدد الحسابات الجديد", "page": "صفحة",
"page": "صفحة", "of": "ل",
"of": "ل", "totalItems": "السجلات",
"totalItems": "السجلات", "record": "سِجِلّ",
"record": "سِجِلّ", "previous": "سابق",
"previous": "سابق", "next": "التالي",
"next": "التالي", "LOGIN_SUCCESSFULLY": "تم تسجيل الدخول بنجاح",
"LOGIN_SUCCESSFULLY":"تم تسجيل الدخول بنجاح", "RESET_PASSWORD_SUCCESS": "تمت إعادة تعيين كلمة المرور بنجاح",
"RESET_PASSWORD_SUCCESS": "تمت إعادة تعيين كلمة المرور بنجاح", "CHANGE_PASSWORD_SUCCESS": "تم تغيير كلمة المرور بنجاح",
"CHANGE_PASSWORD_SUCCESS": "تم تغيير كلمة المرور بنجاح", "ALREADY_LOGGED_IN": "المستخدم مسجل دخوله بالفعل",
"ALREADY_LOGGED_IN": "المستخدم مسجل دخوله بالفعل", "ACCESS_DENIED": "تم الرفض",
"ACCESS_DENIED" : "تم الرفض", "INTERNAL_SERVER_ERROR": "خطأ في الخادم الداخلي",
"INTERNAL_SERVER_ERROR": "خطأ في الخادم الداخلي", "CONNECTION_ERROR": "خطأ في الاتصال",
"CONNECTION_ERROR": "خطأ في الاتصال", "BAD_REQUEST": "اقتراح غير جيد",
"BAD_REQUEST": "اقتراح غير جيد", "FORBIDDEN_REQUEST": "طلب ممنوع",
"FORBIDDEN_REQUEST": "طلب ممنوع", "UNAUTHORIZED_REQUEST": "طلب غير مصرح به",
"UNAUTHORIZED_REQUEST": "طلب غير مصرح به", "edit": "يحرر",
"edit": "يحرر", "delete": "يمسح",
"delete": "يمسح", "deleteUser": "حذف حساب المستخدم",
"deleteUser": "حذف حساب المستخدم", "permissionManagement": "إدارة الأذونات",
"permissionManagement": "إدارة الأذونات", "userCode": "مستخدم",
"userCode": "مستخدم", "choose": "يختار",
"choose" : "يختار", "allow": "يسمح",
"allow": "يسمح", "toDateInvalidError": "يجب أن يكون تاريخ اليوم أكبر من أو يساوي تاريخ البداية",
"toDateInvalidError": "يجب أن يكون تاريخ اليوم أكبر من أو يساوي تاريخ البداية", "noLoggingDetailsFound": "لم يتم العثور على تفاصيل التسجيل",
"noLoggingDetailsFound": "لم يتم العثور على تفاصيل التسجيل", "noUserDetailsFound": "لم يتم العثور على تفاصيل المستخدم",
"noUserDetailsFound": "لم يتم العثور على تفاصيل المستخدم", "ERR_SEC_0001": "البريد الإلكتروني موجود بالفعل",
"ERR_SEC_0001": "البريد الإلكتروني موجود بالفعل", "ERR_SEC_0002": "اسم المستخدم موجود بالفعل",
"ERR_SEC_0002": "اسم المستخدم موجود بالفعل", "ERR_SEC_0003": "كلمة المرور القديمة غير صحيحة",
"ERR_SEC_0003": "كلمة المرور القديمة غير صحيحة", "ERR_SEC_0004": "اسم المستخدم أو كلمة المرور غير صحيحة",
"ERR_SEC_0004": "اسم المستخدم أو كلمة المرور غير صحيحة", "ERR_SEC_0005": "المستخدم غير موجود",
"ERR_SEC_0005": "المستخدم غير موجود", "ERR_SEC_0006": "كلمة المرور التي تم إدخالها غير صحيحة",
"ERR_SEC_0006": "كلمة المرور التي تم إدخالها غير صحيحة", "toDateGreaterThanToday": "يجب أن يكون التاريخ الحالي أقل من التاريخ الحالي",
"toDateGreaterThanToday": "يجب أن يكون التاريخ الحالي أقل من التاريخ الحالي", "fromDateGreaterThanToday": "يجب أن يكون تاريخ البدء أقل من التاريخ الحالي",
"fromDateGreaterThanToday": "يجب أن يكون تاريخ البدء أقل من التاريخ الحالي", "userIdMinLength": "يجب أن يكون معرف المستخدم مكوّنًا من 5 أحرف على الأقل",
"userIdMinLength": "يجب أن يكون معرف المستخدم مكوّنًا من 5 أحرف على الأقل", "nameMinLength": "يجب أن يكون الاسم مكوّنًا من 5 أحرف على الأقل",
"nameMinLength": "يجب أن يكون الاسم مكوّنًا من 5 أحرف على الأقل", "emptySpaceRestriction": "المسافات الفارغة غير مسموح بها",
"emptySpaceRestriction": "المسافات الفارغة غير مسموح بها", "USER_CREATED_SUCCESS": "تم إنشاء المستخدم",
"USER_CREATED_SUCCESS": "تم إنشاء المستخدم", "USER_DELETE_SUCCESS": "تم حذف المستخدم",
"USER_DELETE_SUCCESS": "تم حذف المستخدم", "SAVED_SUCCESSFULLY": "تم الحفظ بنجاح",
"SAVED_SUCCESSFULLY": "تم الحفظ بنجاح", "activityLogs": "سجلات النشاط",
"activityLogs": "سجلات النشاط", "activityLogDetails": "تفاصيل سجل النشاط",
"activityLogDetails": "تفاصيل سجل النشاط", "resetPasswordButton": "إعادة تعيين كلمة المرور",
"resetPasswordButton": "إعادة تعيين كلمة المرور" "dateTime": "التاريخ والوقت",
} "responseCode": "رمز الاستجابة",
"remoteIp": "IP البعيد",
"requestBody": "هيئة الطلب",
"requestUri": "طلب URI",
"method": "طريقة",
"id": "بطاقة تعريف",
"logId": "معرف السجل",
"porOrgacode": "رمز المنظمة",
"drMbmbkmsnumber": "رقم الحساب المدين",
"crMbmbkmsnumber": "رقم حساب الائتمان",
"ppmPymdcode": "رمز الدفع",
"sgtGntrdate": "تاريخ المعاملة",
"sgtGntrcreateat": "تاريخ الإنشاء",
"updatedAt": "تم التحديث في",
"crPcaglacode": "حساب CR GL",
"drPcaGlacode": "حساب DR GL",
"transactionUri": "معرّف المعاملة",
"transactionCode": "رمز المعاملة",
"channelCode": "رمز القناة",
"userFullname": "الاسم الكامل",
"show": "عرض",
"tableCollapsed": "الجدول مطوي",
"showTable": "عرض الجدول",
"collapse": "يطوي",
"expand": "توسيع",
"entries": "إدخالات"
}

@ -83,7 +83,6 @@
"ThirdPartyID":"Third Party ID", "ThirdPartyID":"Third Party ID",
"name":"Name", "name":"Name",
"EnterThirdPartyName":"Enter Third Party Name", "EnterThirdPartyName":"Enter Third Party Name",
"Email":"Email",
"email":"Email", "email":"Email",
"Address":"Address", "Address":"Address",
"phoneNumber":"Phone Number", "phoneNumber":"Phone Number",
@ -166,7 +165,6 @@
"ERR_APP_B_0004":"Session timed out", "ERR_APP_B_0004":"Session timed out",
"ERR_APP_B_0005":"Unauthorized: {{value1}}.", "ERR_APP_B_0005":"Unauthorized: {{value1}}.",
"ERR_MDL_B_0001": "Purpose Code already exists", "ERR_MDL_B_0001": "Purpose Code already exists",
"feedbackSetup": "Feedback Setup", "feedbackSetup": "Feedback Setup",
"credentials": "Credentials", "credentials": "Credentials",
"credentialsTitle": "Feedback Credentials Setup", "credentialsTitle": "Feedback Credentials Setup",
@ -256,7 +254,8 @@
"noUserDetailsFound":"No User Details found", "noUserDetailsFound":"No User Details found",
"noThirdPartyRegFound":"No Third Party Registration Details Found", "noThirdPartyRegFound":"No Third Party Registration Details Found",
"ERR_SEC_0001": "Email already exists", "ERR_SEC_0001": "Email already exists",
"ERR_SEC_0002": "Username already exists", "ERR_SEC_0007": "New password cannot be same as old password",
"ERR_SEC_0002": "User ID already exists",
"ERR_SEC_0003": "Old Password is not correct", "ERR_SEC_0003": "Old Password is not correct",
"ERR_SEC_0004":"Invalid credentials", "ERR_SEC_0004":"Invalid credentials",
"ERR_SEC_0005": "User not found", "ERR_SEC_0005": "User not found",
@ -265,8 +264,9 @@
"fromDateGreaterThanToday": "From Date must be less than Current Date", "fromDateGreaterThanToday": "From Date must be less than Current Date",
"userIdMinLength" : "User ID must be at least 5 characters", "userIdMinLength" : "User ID must be at least 5 characters",
"nameMinLength" : "Name must be at least 5 characters", "nameMinLength" : "Name must be at least 5 characters",
"emptySpaceRestriction" : "Empty spaces are not allowed", "emptySpaceRestriction" : "Empty spaces & capital letters are not allowed",
"USER_CREATED_SUCCESS": "User Created", "USER_CREATED_SUCCESS": "User Created",
"USER_UPDATE_SUCCESS": "User Updated",
"USER_DELETE_SUCCESS": "User Deleted", "USER_DELETE_SUCCESS": "User Deleted",
"SAVED_SUCCESSFULLY" :"Saved Successfully", "SAVED_SUCCESSFULLY" :"Saved Successfully",
"oldAndNewPass":"Old password and new password cannot be the same", "oldAndNewPass":"Old password and new password cannot be the same",
@ -274,5 +274,28 @@
"activityLogDetails": "Activity Log Details", "activityLogDetails": "Activity Log Details",
"transactionCode": "Transaction Code", "transactionCode": "Transaction Code",
"transactionUri": "Transaction URI", "transactionUri": "Transaction URI",
"resetPasswordButton": "Reset Password" "resetPasswordButton": "Reset Password",
"dateTime": "Date Time",
"responseCode": "Response Code",
"remoteIp": "Remote IP",
"requestBody": "Request Body",
"requestUri": "Request URI",
"method": "Method",
"id": "Id",
"logId": "Log ID",
"porOrgacode": "Organization Code",
"drMbmbkmsnumber": "Debit Account Number",
"crMbmbkmsnumber": "Credit Account Number",
"ppmPymdcode": "Payment Code",
"sgtGntrdate": "Transaction Date",
"sgtGntrcreateat": "Creation Date",
"updatedAt": "Updated At",
"channelCode": "Channel Code",
"userFullname" : "Full Name",
"show": "Show",
"entries": "entries",
"tableCollapsed": "Table collapsed",
"showTable": "Show Table",
"collapse": "Collapse",
"expand": "Expand"
} }
Loading…
Cancel
Save