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.storageService.setItem('direction', this.direction);
// if (typeof document !== 'undefined') {
// document.documentElement.setAttribute('dir', this.direction);
// }
const userStr = this.storageService.getItem('user');
if (userStr) {
try {

@ -29,7 +29,7 @@
<div class="card-body">
<form [formGroup]="loginForm" class="form-horizontal">
<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">
<input type="text" class="form-control" id="USER_ID" formControlName="USER_ID">
</div>

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

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

@ -162,8 +162,22 @@ export class LoggingComponent implements OnInit {
}
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.filteredItems,
sanitizedData,
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) {
}
authenticate(uCreds: UserCredentials): Observable<any> {
const userJson = this.storageService.getItem('user');
if (this.storageService.getItem('user') != null) {
this.i18nService.error(ErrorMessages.ALREADY_LOGGED_IN, []);
return new Observable(); // empty
}
// const userJson = this.storageService.getItem('user');
// if (this.storageService.getItem('user') != null) {
// this.i18nService.error(ErrorMessages.ALREADY_LOGGED_IN, []);
// return new Observable(); // empty
// }
this.credentialService.setPorOrgacode(HiddenValues.POR_ORGACODE);
this.credentialService.setUserId(uCreds.userId);
@ -81,7 +81,7 @@ export class AuthenticationService {
isAdminUser(){
if (this.storageService && this.storageService.getItem('user') != null) {
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;
}

@ -1,28 +1,66 @@
import { Injectable } from '@angular/core';
import { saveAs } from 'file-saver';
import * as XLSX from 'xlsx';
import { TranslateService } from '@ngx-translate/core';
import { EXCEL_FILE_EXTENSION, EXCEL_FILE_TYPE } from '../../utils/app.constants';
import { StorageService } from './storage.service';
import { supportedLanguages } from '../../utils/enums';
@Injectable({
providedIn: 'root'
})
export class ExcelExportService {
constructor() { }
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 {
const ws: XLSX.WorkSheet = XLSX.utils.json_to_sheet(jsonData);
const wb: XLSX.WorkBook = { Sheets: { 'data': ws }, SheetNames: ['data'] };
const translatedData = this.translateHeaders(jsonData);
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' });
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 {
const data: Blob = new Blob([buffer], {type: this.fileType});
saveAs.saveAs(data, fileName + this.fileExtension);
const data: Blob = new Blob([buffer], { type: this.fileType });
saveAs(data, fileName + this.fileExtension);
}
}

@ -3,9 +3,7 @@
<div class="container-fluid">
<div class="row">
<div class="col-12">
<div
class="d-sm-flex align-items-center justify-content-between navbar-header p-0"
></div>
<div class="d-sm-flex align-items-center justify-content-between navbar-header p-0"></div>
</div>
</div>
</div>
@ -19,9 +17,7 @@
<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"
>
<div class="card-header font-edit-13-child d-flex justify-content-between align-items-center">
{{ "transactionLogs" | translate }}
<div class="d-flex align-items-center gap-2">
@ -32,169 +28,164 @@
[(ngModel)]="searchText"
(ngModelChange)="onSearch($event)"
placeholder="{{ 'search' | translate }}"
[disabled]="isLoading"
/>
<i class="fas fa-search search-icon"></i>
</div>
<div class="d-flex align-items-center gap-2">
<i
<button
class="btn btn-sm btn-outline-success"
(click)="exportDataInExcel()"
id="downloadReport"
class="fa fa-download"
></i>
[disabled]="isLoading || transactionLog.length === 0"
title="Export to Excel"
>
<i class="fa fa-download"></i>
</button>
</div>
<i
class="materialdesignicons"
<button
class="btn btn-sm btn-outline-secondary"
(click)="toggleTableCard()"
[title]="(transactionDataExpanded ? 'collapse' : 'expand') | translate"
>
<ng-container
*ngIf="
transactionDataExpanded;
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>
<i *ngIf="transactionDataExpanded" class="dripicons-chevron-up"></i>
<i *ngIf="!transactionDataExpanded" class="dripicons-chevron-down"></i>
</button>
</div>
</div>
<div
class="card-body"
*ngIf="
transactionDataExpanded && allItems.length;
else noRecordsFound
"
*ngIf="transactionDataExpanded; else collapsedTable"
>
<div *ngIf="isLoading" class="text-center text-muted">
<p>{{ "loadingTransactionLogs" | translate }}</p>
<!-- Loading State -->
<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
*ngIf="!isLoading && transactionLog.length === 0"
class="text-center text-muted"
>
<p>{{ "noTransactionLogsFound" | translate }}</p>
<!-- Error Message -->
<div *ngIf="!isLoading && errorMessage" class="alert alert-danger alert-dismissible fade show" role="alert">
{{ errorMessage }}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close" (click)="errorMessage = ''"></button>
</div>
<div *ngIf="errorMessage" class="alert alert-danger">
{{ errorMessage }}
<!-- No Data Found -->
<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
*ngIf="!isLoading && transactionLog.length > 0"
class="table-responsive"
>
<table class="table mb-0 border">
<!-- Data Table -->
<div *ngIf="!isLoading && !errorMessage && transactionLog.length > 0" class="table-responsive">
<table class="table table-hover table-bordered mb-0">
<thead class="table-light">
<tr>
<th>{{ "logID" | translate }}</th>
<th>{{ "organization" | translate }}</th>
<th>{{ "transactionID" | translate }}</th>
<th>{{ "drAccount" | translate }}</th>
<th>{{ "crAccount" | translate }}</th>
<th>{{ "drPcaGlacode" | translate }}</th>
<th>{{ "crPcaglacode" | translate }}</th>
<th>{{ "transactionCode" | translate }}</th>
<th>{{ "transactionUri" | translate }}</th>
<th>{{ "paymentMode" | translate }}</th>
<th>{{ "date" | translate }}</th>
<th>{{ "channel" | translate }}</th>
<th>{{ "createdAt" | translate }}</th>
<th scope="col">{{ "logID" | translate }}</th>
<th scope="col">{{ "organization" | translate }}</th>
<th scope="col">{{ "transactionID" | translate }}</th>
<th scope="col">{{ "drAccount" | translate }}</th>
<th scope="col">{{ "crAccount" | translate }}</th>
<th scope="col">{{ "drPcaGlacode" | translate }}</th>
<th scope="col">{{ "crPcaglacode" | translate }}</th>
<th scope="col">{{ "transactionUri" | translate }}</th>
<th scope="col">{{ "transactionCode" | translate }}</th>
<th scope="col">{{ "paymentMode" | translate }}</th>
<th scope="col">{{ "date" | translate }}</th>
<th scope="col">{{ "channel" | translate }}</th>
<th scope="col">{{ "createdAt" | translate }}</th>
</tr>
</thead>
<tbody>
<tr
*ngFor="
let log of (
allItems
| tableFilter
: searchText
: [
'logID',
'organization',
'transactionID',
'drAccount',
'drPcaGlacode',
'crPcaglacode',
'crAccount',
'transactionUri',
'transactionCode',
'paymentMode',
'date',
'channel',
'createdAt',
]
).slice(
(currentPage - 1) * itemsPerPage,
currentPage * itemsPerPage
)
"
>
<td>{{ log.logId }}</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 *ngFor="let log of filteredItems | tableFilter: searchText: [
'logId',
'porOrgacode',
'transactionID',
'drMbmbkmsnumber',
'crMbmbkmsnumber',
'drPcaGlacode',
'crPcaglacode',
'transactionUri',
'transactionCode',
'ppmPymdcode',
'sgtGntrdate',
'channelCode',
'createdAt'
]">
<td><code>{{ log.logId || '-' }}</code></td>
<td>{{ log.porOrgacode || '-' }}</td>
<td><strong>{{ log.transactionID || '-' }}</strong></td>
<td>{{ log.drMbmbkmsnumber || '-' }}</td>
<td>{{ log.crMbmbkmsnumber || '-' }}</td>
<td>{{ log.drPcaGlacode || '-' }}</td>
<td>{{ log.crPcaglacode || '-' }}</td>
<td><small class="text-muted">{{ log.transactionUri || 'N/A' }}</small></td>
<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.createdAt | date: 'MMM dd, yyyy HH:mm' }}</td>
</tr>
</tbody>
</table>
<div
class="d-flex justify-content-between align-items-center mt-3"
>
<div class="form-group mb-0">
<ng-select
class="form-select-sm"
[items]="pageSizeOptions"
bindLabel="label"
bindValue="value"
[(ngModel)]="itemsPerPage"
(change)="itemsPerPageChanged()"
[searchable]="false"
[clearable]="false"
[dropdownPosition]="'top'"
>
</ng-select>
<!-- Pagination -->
<div class="d-flex justify-content-between align-items-center mt-3 flex-wrap">
<!-- Items per page -->
<div class="d-flex align-items-center gap-2 mb-2 mb-md-0">
<span class="text-muted small">{{ "show" | translate }}</span>
<div style="width: 120px;">
<ng-select
class="form-select-sm"
[items]="pageSizeOptions"
bindValue="value"
[(ngModel)]="itemsPerPage"
(change)="itemsPerPageChanged()"
[searchable]="false"
[clearable]="false"
[dropdownPosition]="'top'"
[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 class="text-muted">
<!-- Page info -->
<div class="text-muted mb-2 mb-md-0">
{{ "page" | translate }} {{ currentPage }}
{{ "of" | translate }} {{ totalPages() }} ({{
totalCount
}}
{{ "totalItems" | translate }})
{{ "of" | translate }} {{ totalPages() }}
<span class="d-none d-md-inline">
({{ totalCount }} {{ "totalItems" | translate }})
</span>
</div>
<!-- Pagination buttons -->
<div class="btn-group">
<button
class="btn btn-primary waves-effect waves-light"
class="btn btn-outline-primary btn-sm"
(click)="previousPage()"
[disabled]="currentPage === 1 || isLoading"
>
<i class="fas fa-chevron-left me-1"></i>
{{ "previous" | translate }}
</button>
<button
class="btn btn-primary waves-effect waves-light"
class="btn btn-outline-primary btn-sm"
(click)="nextPage()"
[disabled]="currentPage === totalPages() || isLoading"
>
{{ "next" | translate }}
<i class="fas fa-chevron-right ms-1"></i>
</button>
</div>
</div>
@ -211,11 +202,13 @@
</div>
</div>
</div>
<ng-template #noRecordsFound>
<div
*ngIf="!isLoading && allItems.length === 0"
class="text-center text-muted mt-3"
>
<p>{{ "noTransactionLogsFound" | translate }}</p>
<ng-template #collapsedTable>
<div class="card-body text-center py-4 text-muted" *ngIf="!isLoading">
<i class="dripicons-chevron-down fa-2x mb-2"></i>
<p>{{ "tableCollapsed" | translate }}</p>
<button class="btn btn-sm btn-outline-primary" (click)="toggleTableCard()">
{{ "showTable" | translate }}
</button>
</div>
</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 { FormsModule, ReactiveFormsModule } from '@angular/forms';
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 { HttpParams } from '@angular/common/http';
import { HttpURIService } from '../app.http.uri.service';
import { formatDate } from '@angular/common';
@Component({
selector: 'app-transaction-logs',
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 {
onPageSizeChange(arg0: number) {
throw new Error('Method not implemented.');
}
currentPage: number = 1;
totalCount: number = 0;
renewalDataExpanded: boolean = true;
@ -32,75 +31,215 @@ throw new Error('Method not implemented.');
errorMessage: string = '';
searchText: string = '';
allItems: TransactionLog[] = [];
transactionDataExpanded: boolean = true
transactionDataExpanded: boolean = true;
// Date range properties
fromDate: string = '';
toDate: string = '';
maxDate: string = '';
showDateFilters: boolean = false;
constructor(
private excelExportService: ExcelExportService,
private httpService: HttpURIService,
) {}
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();
}
loadTransactionLogs(): void {
this.isLoading = true;
const params = new HttpParams();
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];
this.totalCount = this.transactionLog.length;
this.updatePagedItems();
this.isLoading = false;
},
error: (err) => {
console.error('Error fetching logging details data:', err);
this.transactionLog = [];
this.isLoading = false;
loadTransactionLogs(): void {
this.isLoading = true;
this.errorMessage = '';
// Build query parameters
let params = new HttpParams();
// Add pagination parameters
params = params.set('page', this.currentPage.toString());
params = params.set('limit', this.itemsPerPage.toString());
// Add date filters if provided
if (this.fromDate) {
params = params.set('fromDate', this.fromDate);
}
if (this.toDate) {
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 {
this.transactionDataExpanded = !this.transactionDataExpanded;
}
this.transactionDataExpanded = !this.transactionDataExpanded;
}
itemsPerPageChanged(): void {
this.currentPage = 1;
this.updatePagedItems();
this.loadTransactionLogs();
}
onSearch(value: string): void {
this.searchText = value;
}
this.searchText = value;
this.currentPage = 1;
totalPages(): number {
return Math.ceil(this.allItems.length / this.itemsPerPage);
// Debounce search to avoid too many API calls
clearTimeout((this as any).searchTimeout);
(this as any).searchTimeout = setTimeout(() => {
this.loadTransactionLogs();
}, 300);
}
totalPages(): number {
return Math.ceil(this.totalCount / this.itemsPerPage);
}
previousPage(): void {
if (this.currentPage > 1) {
this.currentPage--;
this.updatePagedItems();
this.loadTransactionLogs();
}
}
updatePagedItems(): void {
// For client-side display with table filter pipe
const startIndex = (this.currentPage - 1) * 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);
}
nextPage(): void {
if (this.currentPage < this.totalPages()) {
this.currentPage++;
this.updatePagedItems();
this.loadTransactionLogs();
}
}
exportDataInExcel() {
this.excelExportService.exportExcel(this.transactionLog, TRANSACTION_LOGS_FILE_NAME)
exportDataInExcel(): void {
// 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) => {
if (!(response instanceof HttpErrorResponse)) {
this.i18nService.success(SuccessMessages.CHANGE_PASSWORD_SUCCESS, []);
const userStr = this.storageService.getItem('user');
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']);
this.authService.logout();
this.router.navigate(['login']);
}
},
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-content">
@ -6,72 +6,143 @@
<h5 class="modal-title">
{{ 'resetPassword' | translate }}
</h5>
<button type="button" class="btn-close" (click)="closeModal()"></button>
<button type="button" class="btn-close" (click)="closeModal()" aria-label="Close"></button>
</div>
<div class="modal-body">
<form [formGroup]="resetPasswordForm">
<div class="mb-3">
<label class="form-label">{{ 'userID' | translate }}</label>
<input [readonly]="true" class="form-control" formControlName="userId" />
<!-- 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>
<!-- User ID display -->
<div class="mb-3" *ngIf="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 class="mb-3">
<label class="form-label">{{ 'enterNewPassword' | translate }}</label>
</div>
<div class="password-wrapper">
<input class="form-control" formControlName="newPassword" placeholder="{{'enterNewPassword' | translate}}" [type]="newPasswordType" autocomplete="off" appNoWhitespaces />
<app-password-hide-show #newPasswordPsh class="password-eye align-items-stretch" [showPassword]="true"
(onEyeClick)="togglePasswordType()">
</app-password-hide-show>
<form [formGroup]="resetPasswordForm" *ngIf="userId">
<!-- New Password Field -->
<div class="mb-3">
<label class="form-label fw-bold">
{{ 'enterNewPassword' | translate }} <span class="mandatory">*</span>
</label>
<div class="password-wrapper input-group">
<input
class="form-control"
formControlName="newPassword"
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 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')">
{{ 'fieldRequired' | translate }}
</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')">
{{ 'passwordPattern' | translate }}
</div>
</div>
<!-- Password requirements hint -->
<div class="form-text text-muted">
</div>
</div>
<!-- Confirm Password Field -->
<div class="mb-3">
<label class="form-label">{{ 'confirmPassword' | translate }}</label>
<div class="password-wrapper">
<input class="form-control" formControlName="confirmPassword" placeholder="{{'confirmPassword' | translate}}" [type]="confirmPasswordType" autocomplete="off" appNoWhitespaces />
<app-password-hide-show #confirmPasswordPsh class="password-eye align-items-stretch" [showPassword]="true"
(onEyeClick)="toggleConfirmPasswordType()">
</app-password-hide-show>
<label class="form-label fw-bold">
{{ 'confirmPassword' | translate }} <span class="mandatory">*</span>
</label>
<div class="password-wrapper input-group">
<input
class="form-control"
formControlName="confirmPassword"
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 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')">
{{ 'fieldRequired' | translate }}
</div>
<div *ngIf="resetPasswordForm.hasError('passwordMismatch')">
<div *ngIf="resetPasswordForm.get('confirmPassword')?.hasError('passwordMismatch')">
{{ 'passwordsDoNotMatch' | translate }}
</div>
</div>
</div>
</form>
</div>
<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()">
{{ 'cancel' | translate }}
<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()"
[attr.disabled]="isSubmitting ? true : null"
>
{{ 'cancel' | translate }}
</button>
<button type="button" class="btn btn-primary btn-sm px-4" [disabled]="resetPasswordForm.invalid" (click)="submit()">
{{ 'save' | translate }}
<button
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>
</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 { HttpURIService } from '../../app.http.uri.service';
import { URIKey } from '../../utils/uri-enums';
import { I18NService } from '../../services/i18n.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 { TranslateModule } from '@ngx-translate/core';
import { CommonModule } from '@angular/common';
import { PasswordHideShowComponent } from '../../shared/components/password-hide-show/password-hide-show.component';
@Component({
selector: 'app-reset-password-modal',
standalone: true,
imports: [TranslateModule, ReactiveFormsModule, CommonModule, PasswordHideShowComponent],
imports: [TranslateModule, ReactiveFormsModule, CommonModule],
templateUrl: './reset-password-modal.component.html'
})
export class ResetPasswordModalComponent implements OnInit {
newPasswordType: string = 'password'
confirmPasswordType: string = 'password'
@Input() userId!: string;
export class ResetPasswordModalComponent implements OnInit, OnChanges {
newPasswordType: string = 'password';
confirmPasswordType: string = 'password';
@Input() userId: string | null = null;
@Output() modalClosed = new EventEmitter<void>();
resetPasswordForm!: FormGroup;
@ViewChild('newPasswordPsh') passwordHideShow?:PasswordHideShowComponent
@ViewChild('confirmPasswordPsh') confirmPasswordHideShow?:PasswordHideShowComponent
isSubmitting: boolean = false;
@ViewChild('newPasswordPsh') passwordHideShow?: PasswordHideShowComponent;
@ViewChild('confirmPasswordPsh') confirmPasswordHideShow?: PasswordHideShowComponent;
constructor(
private fb: FormBuilder,
@ -32,50 +35,80 @@ export class ResetPasswordModalComponent implements OnInit {
private i18nService: I18NService,
private storageService: StorageService
) {}
togglePasswordType() {
this.newPasswordType = this.passwordHideShow?.showPassword ? 'password' : 'text';
}
toggleConfirmPasswordType() {
this.confirmPasswordType = this.confirmPasswordHideShow?.showPassword ? 'password' : 'text';
}
ngOnInit(): void {
this.initializeForm();
}
initializeForm(): void {
this.resetPasswordForm = this.fb.group({
userId: [''],
newPassword: ['', [
Validators.required,
Validators.minLength(8),
Validators.maxLength(20),
Validators.pattern(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&]).+$/)
Validators.pattern(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]+$/)
]],
confirmPassword: ['', Validators.required]
}, { validators: this.passwordMatchValidator });
}, {
validators: this.passwordMatchValidator,
updateOn: 'blur'
});
// Update confirm password validation when new password changes
this.resetPasswordForm.get('newPassword')?.valueChanges.subscribe(() => {
this.resetPasswordForm.get('confirmPassword')?.updateValueAndValidity();
});
}
ngOnChanges(changes: SimpleChanges): void {
if (changes['userId'] && this.resetPasswordForm) {
this.resetPasswordForm.reset({
userId: this.userId,
newPassword: '',
confirmPassword: ''
});
if (changes['userId'] && this.resetPasswordForm) {
console.log('ResetPasswordModal: userId changed to:', this.userId);
this.newPasswordType = 'password';
this.confirmPasswordType = 'password';
// Reset the form when userId changes
this.resetForm();
this.passwordHideShow?.reset();
this.confirmPasswordHideShow?.reset();
// 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);
}
}
}
}
togglePasswordType() {
this.newPasswordType = this.newPasswordType === 'password' ? 'text' : 'password';
}
toggleConfirmPasswordType() {
this.confirmPasswordType = this.confirmPasswordType === 'password' ? 'text' : 'password';
}
passwordMatchValidator(group: AbstractControl): ValidationErrors | null {
const newPassword = group.get('newPassword')?.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() {
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 = {
userId: this.userId,
@ -85,28 +118,56 @@ export class ResetPasswordModalComponent implements OnInit {
this.httpURIService.requestPOST(URIKey.RESET_PASSWORD_URI, payload)
.subscribe({
next: (res) => {
if (!(res instanceof HttpErrorResponse)) {
next: (response) => {
if (!(response instanceof HttpErrorResponse)) {
this.i18nService.success(SuccessMessages.RESET_PASSWORD_SUCCESS, []);
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.isSubmitting = false;
this.newPasswordType = 'password';
this.confirmPasswordType = 'password';
// Reset password hide/show components
this.passwordHideShow?.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');
modal?.classList.remove('show');
modal?.setAttribute('aria-hidden', 'true');
modal!.style.display = 'none';
if (modal) {
modal.classList.remove('show');
modal.setAttribute('aria-hidden', 'true');
modal.style.display = 'none';
}
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 { HttpErrorResponse, HttpParams } from '@angular/common/http';
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 { error } from 'console';
@Component({
selector: 'app-reset-password',
imports: [TranslateModule, PasswordHideShowComponent, CommonModule, ReactiveFormsModule, NgSelectComponent],
imports: [TranslateModule, PasswordHideShowComponent, CommonModule, ReactiveFormsModule, NgSelectModule],
templateUrl: './reset-password.component.html',
styleUrl: './reset-password.component.scss'
})
@ -27,46 +26,53 @@ export class ResetPasswordComponent implements OnInit{
isLoading: boolean = false;
allUsersDropdown: SetupUser[] = [];
selectedUserId: string | null = null;
isSubmitting: boolean = false;
@ViewChild('psh1') passwordHideShow1?: 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 {
const userIdValue = this.storageService.getItem('USER_ID') || null;
this.initializeForm();
this.loadUsersForDropdown();
}
initializeForm(): void {
this.resetPasswordForm = this.fb.group({
userId: [null],
userId: [null, Validators.required], // Changed to empty string and required
newPassword: ['', [
Validators.required,
Validators.minLength(8),
Validators.maxLength(20),
Validators.pattern(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]+$/)
]
],
]],
confirmPassword: ['', [
Validators.required,
Validators.minLength(8),
Validators.maxLength(20),
Validators.pattern(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]+$/)
]
]
},
{
]]
}, {
validators: this.passwordMatchValidator
}
);
this.resetPasswordForm.get('newPassword')?.valueChanges.subscribe(()=>{
this.resetPasswordForm.get('confirmPassword')?.updateValueAndValidity();
});
});
this.loadUsersForDropdown();
// Update confirm password validation when new password changes
this.resetPasswordForm.get('newPassword')?.valueChanges.subscribe(() => {
this.resetPasswordForm.get('confirmPassword')?.updateValueAndValidity();
});
}
togglePasswordType1() {
togglePasswordType1() {
this.passwordType1 = this.passwordHideShow1?.showPassword ? 'password' : 'text';
}
togglePasswordType2() {
this.passwordType2 = this.passwordHideShow2?.showPassword ? 'password' : 'text';
}
@ -74,10 +80,13 @@ export class ResetPasswordComponent implements OnInit{
passwordMatchValidator(group: AbstractControl): ValidationErrors | null {
const newPassword = group.get('newPassword');
const confirmPassword = group.get('confirmPassword');
if (!newPassword || !confirmPassword) return null;
if (confirmPassword.errors && !confirmPassword.errors['passwordMismatch']) {
return null;
}
if (newPassword.value !== confirmPassword.value) {
confirmPassword.setErrors({ passwordMismatch: true });
return { passwordMismatch: true };
@ -87,48 +96,88 @@ export class ResetPasswordComponent implements OnInit{
}
}
onSubmit() {
if (this.resetPasswordForm.invalid) return;
if (this.resetPasswordForm.invalid) {
this.resetPasswordForm.markAllAsTouched();
return;
}
this.isSubmitting = true;
const selectedId = this.resetPasswordForm.get('userId')?.value;
console.log("userid.....", selectedId)
const payload = {
userId: selectedId,
newPassword: this.resetPasswordForm.get('newPassword')?.value,
porOrgaCode: this.storageService.getItem('POR_ORGACODE')
};
this.httpURIService.requestPOST(URIKey.RESET_PASSWORD_URI, payload)
.subscribe({
next: (response) => {
if (!(response instanceof HttpErrorResponse)) {
this.i18nService.success(SuccessMessages.RESET_PASSWORD_SUCCESS, []);
this.router.navigate(['/dashboard']);
}
this.httpService.requestPOST(URIKey.RESET_PASSWORD_URI, payload).subscribe({
next: (response) => {
if (!(response instanceof HttpErrorResponse)) {
this.i18nService.success(SuccessMessages.RESET_PASSWORD_SUCCESS, []);
this.resetForm(); // Reset form after successful submission
}
});
}
this.isSubmitting = false;
},
error: (error) => {
console.error('Error resetting password:', error);
this.isSubmitting = false;
}
});
}
loadUsersForDropdown(): void{
this.isLoading = true;
let params = new HttpParams().set('page', '0').set('size', '1000')
// 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 {
this.isLoading = true;
let params = new HttpParams()
.set('page', '0')
.set('size', '1000');
this.httpService.requestGET<SetupUser[]>(URIKey.GET_ALL_USER_URI, params).subscribe({
next: (response)=>{
this.allUsersDropdown = response;
this.isLoading = false
next: (response) => {
this.allUsersDropdown = response || [];
this.isLoading = false;
},
error:(err)=>{
error: (err) => {
console.error('Error fetching users:', err);
this.allUsersDropdown = [];
this.isLoading = false;
}
})
}
});
}
}

@ -3,222 +3,372 @@
<div class="container-fluid">
<div class="row">
<div class="col-12">
<div class="d-sm-flex align-items-center justify-content-between navbar-header p-0">
</div>
<div
class="d-sm-flex align-items-center justify-content-between navbar-header p-0"
></div>
</div>
</div>
<div class="container-fluid">
<div class="col-xl-12 mt-4">
<div class="card border">
<div class="col-xl-12 mt-4">
<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="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">
<form [formGroup]="userForm">
<div class="row g-3 mb-3">
<div class="col-md-6">
<div class="d-flex align-items-center gap-2">
<label for="userId" class="text-nowrap">
{{ 'userId' | translate }}<span
class="mandatory">*</span>
</label>
<div class="password-wrapper position-relative w-100">
<div class="d-flex flex-row align-items-stretch">
<input type="text" id="userId"
class="form-control"
formControlName="userId"
name="userId"
placeholder="{{ 'userID' | translate }}" appNoWhitespaces
/>
</div>
<div class="text-danger" *ngIf="userForm.get('userId')?.touched && userForm.get('userId')?.invalid">
<div *ngIf="
userForm.get('userId')?.errors?.['required'] &&
!userForm.get('userId')?.value
">
{{ 'fieldRequired' | translate }}
</div>
</div>
<div class="text-danger" *ngIf="
userForm.get('userId')?.errors?.['minlength'] &&
userForm.get('userId')?.value
">
{{'userIdMinLength' | translate }}
</div>
<div class="text-danger" *ngIf="
userForm.get('userId')?.errors?.['pattern'] &&
userForm.get('userId')?.value
">
{{'emptySpaceRestriction' | translate}}
</div >
</div>
</div>
</div>
<div class="col-md-6">
<div class="d-flex align-items-start gap-2">
<label for="name"
class="text-nowrap mt-2">
{{ 'name' | translate }}<span
class="mandatory">*</span>
</label>
<div class="password-wrapper position-relative w-100">
<form [formGroup]="userForm">
<div class="row g-3 mb-3">
<!-- User ID -->
<div class="col-md-6">
<div class="d-flex align-items-center gap-2">
<label for="userId" class="text-nowrap">
{{ "userId" | translate
}}<span class="mandatory">*</span>
</label>
<div
class="password-wrapper position-relative w-100"
>
<input
type="text"
id="userId"
class="form-control"
formControlName="userId"
name="userId"
placeholder="{{ 'userID' | translate }}"
appNoWhitespaces
[readonly]="mode === 'edit'"
[class.bg-light]="mode === 'edit'"
/>
<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="col-md-6">
<div class="d-flex align-items-center gap-2">
<label for="email" class="text-nowrap">
{{ 'email' | translate }}<span
class="mandatory">*</span>
</label>
<div class="password-wrapper position-relative w-100">
<input id="email"
class="form-control"
formControlName="email"
name="email"
placeholder="{{ 'email' | translate }}" appNoWhitespaces/>
<div
class="text-danger"
*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>
<div class="text-danger" *ngIf="userForm.get('email')?.errors?.['required']
&& userForm.get('email')?.touched">
{{ 'fieldRequired' | translate }}
</div>
<div class="text-danger" *ngIf="userForm.get('email')?.errors?.['email']
&& userForm.get('email')?.touched">
{{"invalidEmail" | translate}}
</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>
</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('userFullname')?.touched &&
userForm.get('userFullname')?.invalid
"
>
<div
*ngIf="
userForm.get('userFullname')
?.errors?.['required']
"
>
{{ "fieldRequired" | translate }}
</div>
<div
*ngIf="
userForm.get('userFullname')
?.errors?.['minlength']
"
>
{{ "nameMinLength" | translate }}
</div>
</div>
</div>
</div>
</div>
</div>
<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 class="row g-3 mb-3">
<!-- Email -->
<div class="col-md-6">
<div class="d-flex align-items-center gap-2">
<label for="email" class="text-nowrap">
{{ "email" | translate
}}<span class="mandatory">*</span>
</label>
<div
class="password-wrapper position-relative w-100"
>
<input
id="email"
class="form-control"
formControlName="email"
name="email"
placeholder="{{ 'email' | translate }}"
appNoWhitespaces
/>
</div>
</div>
<div
class="text-danger"
*ngIf="
userForm.get('email')?.touched &&
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 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>
<!-- 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('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
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 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>
</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>
</form>
</div>
</div>
</div>
</div>
</div>
<!-- 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 class="container-fluid">
<div class="col-xl-12 mt-4">
<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="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">
{{'SetupUserDetails' | translate}}
<div
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="search-box">
<input type="text" class="form-control form-control-sm"
<input
type="text"
class="form-control form-control-sm"
[(ngModel)]="searchText"
placeholder="{{ 'search' | translate }}">
placeholder="{{ 'search' | translate }}"
/>
<i class="fas fa-search search-icon"></i>
</div>
<i class="materialdesignicons" (click)="toggleTableCard()">
<ng-container *ngIf="userSetupDataExpanded; else collapsedIcon">
<i
class="materialdesignicons"
(click)="toggleTableCard()"
>
<ng-container
*ngIf="
userSetupDataExpanded;
else collapsedIcon
"
>
<i class="dripicons-chevron-up float-end"></i>
</ng-container>
<ng-template #collapsedIcon>
@ -227,79 +377,146 @@
</i>
</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">
<table class="table mb-0 border">
<thead class="table-light">
<tr>
<th style="width: 30%">{{'userID' | translate}}</th>
<th style="width: 30%">{{'Name' | translate}}</th>
<th style="width: 30%">{{'Role' | translate}}</th>
<th style="width: 10%">{{'action' | translate}}</th>
<th style="width: 25%">
{{ "userID" | 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>
</thead>
<tbody>
<tr *ngFor="let item of (allItems | tableFilter: searchText : ['userId', 'userFullname']).slice((currentPage-1)*itemsPerPage, currentPage*itemsPerPage)">
<td>{{ item.userId }}</td>
<td>{{ item.userFullname }}</td>
<td>{{item.role}}</td>
<tr
*ngFor="
let item of (
allItems
| tableFilter
: searchText
: ['userId', 'userFullname', 'email']
).slice(
(currentPage - 1) * itemsPerPage,
currentPage * itemsPerPage
)
"
>
<td>{{ item.userId }}</td>
<td>{{ item.userFullname }}</td>
<td>{{ item.email }}</td>
<td>{{ getRoleLabel(item.role) }}</td>
<td>
<div class="d-flex justify-content-center gap-2">
<button class="btn btn-info btn-sm" title="View" (click)="onView(item.userId)">
<i class="mdi mdi-eye-outline"></i>
</button>
<button class="btn btn-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>
<td>
<div
class="d-flex justify-content-center gap-2"
>
<button
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>
</tbody>
</table>
<app-reset-password-modal [userId]="selectedUserId">
</app-reset-password-modal>
<div class="d-flex justify-content-between align-items-center mt-3">
<div class="form-group mb-0">
<ng-select class="form-select-sm"
[items]="pageSizeOptions"
bindLabel="label"
bindValue="value"
[(ngModel)]="itemsPerPage"
(change)="itemsPerPageChanged()"
[searchable]="false"
[clearable]="false"
[dropdownPosition]="'top'">
</ng-select>
</div>
<app-reset-password-modal
[userId]="selectedUserIdForReset ?? ''"
(modalClosed)="closeResetPasswordModal()">
</app-reset-password-modal>
<div
class="d-flex justify-content-between align-items-center mt-3"
>
<div class="form-group mb-0">
<ng-select
class="form-select-sm"
[items]="pageSizeOptions"
bindLabel="label"
bindValue="value"
[(ngModel)]="itemsPerPage"
(change)="itemsPerPageChanged()"
[searchable]="false"
[clearable]="false"
[dropdownPosition]="'top'"
>
<ng-template ng-option-tmp let-item="item">
{{ item.value }} {{ "entries" | translate }}
</ng-template>
<div class="text-muted" *ngIf="allItems.length > 1">
{{'page' | translate}} {{currentPage}} {{'of' |
translate}} {{totalPages()}} ({{allItems.length}}
{{'totalItems' | translate}})
</div>
<ng-template ng-label-tmp let-item="item">
{{ item.value }} {{ "entries" | translate }}
</ng-template>
</ng-select>
</div>
<div
class="text-muted"
*ngIf="allItems.length > 1"
>
{{ "page" | translate }} {{ currentPage }}
{{ "of" | translate }} {{ totalPages() }} ({{
allItems.length
}}
{{ "totalItems" | translate }})
</div>
<div class="btn-group">
<button class="btn btn-primary waves-effect waves-light" (click)="previousPage()">
{{ 'previous' | translate }}
</button>
<button class="btn btn-primary waves-effect waves-light" (click)="nextPage()">
{{ 'next' | translate }}
</button>
</div>
</div>
<div 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>
@ -315,7 +532,10 @@
</div>
</div>
<ng-template #noRecordsFound>
<div *ngIf="!isLoading && allItems.length === 0" class="text-center text-muted mt-3">
<p>{{'noUserDetailsFound' | translate}}</p>
</div>
<div
*ngIf="!isLoading && allItems.length === 0"
class="text-center text-muted mt-3"
>
<p>{{ "noUserDetailsFound" | translate }}</p>
</div>
</ng-template>

@ -5,7 +5,7 @@ import { NgSelectModule } from '@ng-select/ng-select';
import { TranslateModule } from '@ngx-translate/core';
import { pageSizeOptions } from '../../utils/app.constants';
import { SetupUser } from '../../models/user';
import { FormBuilder, Validators, FormGroup } from '@angular/forms';
import { FormBuilder, Validators, FormGroup, AbstractControl } from '@angular/forms';
import { ButtonManagementService } from '../../services/button-management.service';
import { StorageService } from '../../shared/services/storage.service';
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 { HttpURIService } from '../../app.http.uri.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';
@Component({
selector: 'app-setup-user',
standalone: true,
imports: [TranslateModule, ReactiveFormsModule, FormsModule, CommonModule, NgSelectModule, TableFilterPipe, ResetPasswordModalComponent ],
imports: [TranslateModule, ReactiveFormsModule, FormsModule, CommonModule, NgSelectModule, TableFilterPipe, ResetPasswordModalComponent],
templateUrl: './setup-user.component.html',
styleUrl: './setup-user.component.scss'
})
export class SetupUserComponent implements OnInit {
userForm!: FormGroup;
selectedUserIdForEdit: string | null = null; // For edit form
selectedUserIdForReset: string | null = null; // For reset password modal
showForm = false;
selectedUserId!: any;
showDeleteModal = false;
userIdToDelete: any = null;
allItems: SetupUser[] = [];
currentPage: number = 1;
pageSizeOptions = pageSizeOptions
pageSizeOptions = pageSizeOptions;
itemsPerPage: number = 10;
pagedItems: any[] = [];
searchText: any = '';
renewalDataExpanded: boolean = true;
totalCount: number = 0;
mode: 'edit' | 'view' = 'view';
mode: 'create' | 'edit' = 'create';
userSetupDataExpanded: boolean = true
userSetupDataExpanded: boolean = true;
buttonPermissions: any;
isLoading: boolean = false;
setupUserList: SetupUser[] = [];
isLoading: boolean = false;
setupUserList: SetupUser[] = [];
// Store original user data for comparison
originalUserData: SetupUser | null = null;
roleOptions = [
{ label: 'Admin', value: 'ADMIN' },
{ label: 'User', value: 'USER' },
];
constructor(
private fb: FormBuilder,
private buttonManagementService: ButtonManagementService,
private storageService: StorageService,
private httpService: HttpURIService,
private i18nService: I18NService
){}
) {}
onSearch(value: string): void {
this.searchText = value;
@ -82,94 +85,201 @@ export class SetupUserComponent implements OnInit {
this.currentPage = 1;
this.updatePagedItems();
}
// Submit for creating new user
onSubmit() {
if (this.userForm.invalid) {
this.userForm.markAllAsTouched();
return;
}
const newUser : SetupUser = {
userId: this.userForm.value.userId,
userFullname: this.userForm.value.userFullname,
email: this.userForm.value.email,
const newUser: SetupUser = {
userId: this.userForm.value.userId.trim(),
userFullname: this.userForm.value.userFullname.trim(),
email: this.userForm.value.email.trim(),
role: this.userForm.value.userRole,
porOrgacode: this.storageService.getItem('POR_ORGACODE'),
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) => {
if (!(response instanceof HttpErrorResponse)) {
this.i18nService.success(SuccessMessages.USER_CREATED_SUCCESS, []);
this.userForm.reset();
this.mode = 'edit';
this.loadUsersDirect()
this.resetForm();
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 {
const startIndex = (this.currentPage - 1) * this.itemsPerPage;
const endIndex = startIndex + this.itemsPerPage;
this.pagedItems = this.allItems.slice(startIndex, endIndex);
}
const startIndex = (this.currentPage - 1) * this.itemsPerPage;
const endIndex = startIndex + this.itemsPerPage;
this.pagedItems = this.allItems.slice(startIndex, endIndex);
}
getButtonPermissions(){
getButtonPermissions() {
this.buttonPermissions = this.buttonManagementService.buttonPermissions["setupUser"];
}
toggleTableCard(): void {
this.userSetupDataExpanded = !this.userSetupDataExpanded;
}
confirmDelete(userId: string) {
const confirmed = window.confirm('Are you sure you want to delete this user?');
if (confirmed) {
this.onDelete(userId);
this.userSetupDataExpanded = !this.userSetupDataExpanded;
}
}
ngOnInit(): void {
confirmDelete(userId: string) {
const confirmed = window.confirm('Are you sure you want to delete this user?');
if (confirmed) {
this.onDelete(userId);
}
}
this.getButtonPermissions();
ngOnInit(): void {
this.getButtonPermissions();
this.initializeForm();
this.loadUsersDirect();
}
this.userForm = this.fb.group({
initializeForm(): void {
this.userForm = this.fb.group({
userId: ['', [
Validators.required,
Validators.minLength(5),
Validators.pattern(/^\S+$/)
]
],
Validators.pattern('^[a-z0-9]*$')
]],
userFullname: ['', [
Validators.required,
Validators.minLength(5)
]
],
Validators.minLength(5),
Validators.maxLength(500)
]],
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(
/^(?=.*[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]],
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 {
this.isLoading = false;
let params = new HttpParams()
.set('page', this.currentPage.toString())
.set('size', this.itemsPerPage.toString());
this.isLoading = true;
let params = new HttpParams()
.set('page', (this.currentPage - 1).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) => {
this.setupUserList = response
if (response && response.content) {
this.setupUserList = response.content;
this.totalCount = response.totalElements || response.content.length;
} else {
this.setupUserList = response;
}
this.allItems = [...this.setupUserList];
this.updatePagedItems();
this.isLoading = false;
@ -182,55 +292,144 @@ ngOnInit(): void {
});
}
onView(userId: any): void{
onView(userId: string): void {
let params = new HttpParams().set('userId', userId);
this.isLoading = true;
this.httpService.requestGET<SetupUser>(URIKey.GET_USER_BY_ID, params).subscribe({
next: (response: SetupUser) => {
// Store original data
this.originalUserData = { ...response };
// Patch form values
this.userForm.patchValue({
userId: response.userId,
userFullname: response.userFullname,
email: response.email,
userRole: response.role,
defaultPassword: ''
});
this.userForm.disable();
this.selectedUserId = userId;
this.isLoading = false;
userId: response.userId,
userFullname: response.userFullname,
email: response.email,
userRole: response.role,
defaultPassword: '' // Clear password field for security
});
// Enable form for editing
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) => {
console.error('Error fetching users:', err);
this.allItems = [];
console.error('Error fetching user:', err)
this.isLoading = false;
}
});
}
onDelete(userId: any){
onDelete(userId: string) {
let params = new HttpParams().set('userId', userId);
this.isLoading = true;
this.httpService.requestDELETE<any>(URIKey.DELETE_USER, params).subscribe({
next: (response) =>{
next: (response) => {
if (!(response instanceof HttpErrorResponse)) {
this.i18nService.success(SuccessMessages.USER_DELETE_SUCCESS, []);
this.loadUsersDirect();
this.userForm.reset();
this.selectedUserId = null;
this.i18nService.success(SuccessMessages.USER_DELETE_SUCCESS, []);
this.loadUsersDirect();
this.resetForm();
}
this.isLoading = false;
},
error: (error) => {
console.error('Error deleting user:', error);
this.i18nService.error(ErrorMessages.USER_DELETE_FAILED, []);
this.isLoading = false;
}
});
}
openResetPasswordModal(userId: string) {
console.log('Opening reset password modal for userId:', userId);
this.selectedUserIdForReset = userId;
// Show the modal
const modal = document.getElementById('resetPasswordModal');
if (modal) {
modal.classList.add('show');
modal.style.display = 'block';
document.body.classList.add('modal-open');
// Add backdrop
const backdrop = document.createElement('div');
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);
}
openResetPasswordModal(userId: string) {
this.selectedUserId = userId;
closeResetPasswordModal(): void {
const modal = document.getElementById('resetPasswordModal');
const modal = document.getElementById('resetPasswordModal');
modal?.classList.add('show');
modal!.style.display = 'block';
document.body.classList.add('modal-open');
// Remove event listeners
if (modal) {
const backdrop = document.querySelector('.modal-backdrop');
if (backdrop && (modal as any).__backdropListener) {
backdrop.removeEventListener('click', (modal as any).__backdropListener);
}
const backdrop = document.createElement('div');
backdrop.className = 'modal-backdrop fade show';
document.body.appendChild(backdrop);
}
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 = [
{ value: 10, label: '10 items' },
{ value: 20, label: '20 items' },
{ value: 50, label: '50 items' },
{ value: 10, label: '10' },
{ value: 20, label: '20' },
{ value: 50, label: '50' },
];
export const toDateAfterFromDateValidator: ValidatorFn = (
@ -30,7 +30,7 @@ export const toDateAfterFromDateValidator: ValidatorFn = (
today.setHours(0, 0, 0, 0);
// Rule 1: fromDate must be < toDate
if (fromDate >= toDate) {
if (fromDate > toDate) {
return { fromDateGreaterThanOrEqualToToDate: true };
}

@ -6,6 +6,13 @@ export enum ErrorMessages{
UNAUTHORIZED_REQUEST = "UNAUTHORIZED_REQUEST",
ALREADY_LOGGED_IN = "ALREADY_LOGGED_IN",
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{
@ -60,7 +67,10 @@ RECORD_DELETED_SUCCESSFULY = "RECORD_DELETED_SUCCESSFULY",
ACCOUNT_CLOSED_SUCCESSFULLY = "ACCOUNT_CLOSED_SUCCESSFULLY",
SUCCESS_MESSAGE = "SUCCESS_MESSAGE",
USER_CREATED_SUCCESS = "USER_CREATED_SUCCESS",
USER_UPDATE_SUCCESS = "USER_UPDATE_SUCCESS",
USER_UPDATED_SUCCESS = "USER_UPDATED_SUCCESS",
USER_DELETE_SUCCESS = "USER_DELETE_SUCCESS"
}
export enum MESSAGEKEY {

@ -14,5 +14,6 @@ export enum URIKey {
FIRST_LOGIN_URI = "FIRST_LOGIN_URI",
THIRD_PARTY_REGISTER_URI = "THIRD_PARTY_REGISTER_URI",
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",
"UUID": "CREATE_USER"
},
{
"Id": "ENTITY_UPDATE_USER",
"URI": "/user/updateUser",
"UUID": "UPDATE_USER"
},
{
"Id": "ENTITY_GET_ALL_USERS",
"URI": "/user/getAllUsers",

@ -1,273 +1,299 @@
{
"logintoAccount":"تسجيل الدخول إلى حسابك",
"userName":"اسم المستخدم",
"userNamePlaceHolder":"ادخل اسم المستخدم",
"password":"كلمة المرور",
"passwordPlaceHolder":"أدخل كلمة المرور",
"defaultPassword": "كلمة المرور الافتراضية",
"rememberMe":"تذكرنى",
"forgotPassword":"هل نسيت كلمة السر؟",
"passwordTooShort": "كلمة المرور قصيرة جدًا.",
"passwordsDoNotMatch": "كلمتا المرور غير متطابقتين.",
"login":"تسجيل الدخول",
"dashboardTitle":"لوحة القيادة",
"passwordChangeRequired": "تغيير كلمة المرور مطلوب",
"monthlyReqTitle":"الطلبات الشهرية",
"monthlySmsTitle":"الرسائل القصيرة الشهرية",
"Dashboard":"لوحة القيادة",
"UserManagement":"إدارةالمستخدم",
"thirdPartyRegistration":"تسجيل الطرف الثالث",
"setupUser":"مستخدم الإعداد",
"resetPassword":"إعادة تعيين كلمة مرور المستخدم",
"changePassword":"غير كلمة السر",
"Logging":"تسجيل",
"loggerManager":"مدير المسجل",
"SMSBanking":"خدمة الرسائل المصرفية",
"smsLogger":"مسجل الرسائل",
"smsLoggerDetails": "تفاصيل سجل الرسائل القصيرة",
"smsGateway":"بوابة الرسائل",
"ibUnblockUser":"إلغاء حظر مستخدم IB",
"ibSupport":"دعم IB",
"Permissions":"أذونات",
"permissions":"مدير الأذونات",
"TodayTotalRequests":"إجمالي الطلبات اليوم",
"TodayTotalSms":"مجموع الرسائل القصيرة اليوم",
"DescriptionUserPermission":"وصف",
"PermissionsUserPermission":"أذونات",
"SaveUserPermission":"يحفظ",
"UpdateUserPermission":"تحديث",
"DeleteUserPermission":"حذف",
"ViewUserPermission":"رأي",
"SelectUser":"اختر المستخدم",
"SelectAUser":"حدد مستخدمًا",
"loggingTitle":"تسجيل",
"IBChildTitle":"إلغاء حظر عن مستخدم",
"IBTitle":"تسجيل",
"loggingChildTitle":"مدير المسجل",
"fromDate":"من التاريخ",
"custId": "قيمة هوية العميل",
"toDate":"حتي اليوم",
"findLogs":"البحث عن السجلات",
"unBlockCustomer":"إلغاء حظر العميل",
"unblockUserDetails": "إلغاء حظر تفاصيل المستخدم",
"loggerManagerDetails": "تفاصيل مدير السجلات",
"fetchCustomer": "ابحث عن العملاء",
"loggingID":"معرف",
"firstName":"الاسم الأول",
"lastName": "اسم العائلة",
"cmpCuststatus":"حالة العميل",
"cmpCustlastlogin": "آخر تسجيل دخول للعميل",
"accountNonLocked":"الحساب مغلق",
"lockTime":"وقت القفل",
"accountno":"رقم الحساب",
"phoneno":"رقم الهاتف",
"loggingRequestUri":"طلب Uri",
"loggingResponseCode":"رمز الاستجابة",
"loggingRemoteIP":"بعيد IP",
"enterIdentityValue": "أدخل قيمة الهوية",
"loggingTimeTaken":"الوقت المستغرق",
"loggingDate":"تاريخ",
"loggingDateTime": "التاريخ والوقت",
"loggingMethod":"طريقة",
"DataAnalysis":"تحليل البيانات",
"Analysis":"التحليلات",
"smsBankingTitle":"خدمة الرسائل المصرفية",
"smsBankingChildTitle":"مسجل الرسائل",
"smsTrackingID":"معرف تتبع",
"smsMessage":"رسالة",
"smsNo":"رقم الهاتف",
"smsOrgaCode":"كود المنظمة",
"smsDate":"تاريخ",
"smsStatus":"حالة",
"viewThirdPartyAccounts":"عرض الحسابات",
"ThirdPartyID":"معرف الحفلة الثالثة",
"name":"اسم",
"EnterThirdPartyName":"أدخل اسم الطرف الثالث",
"Email":"البريد الإلكتروني",
"invalidEmail": "أدخل بريدًا إلكترونيًا صالحًا يحتوي على @",
"Address":"تبوك",
"phoneNumber":"رقم الهاتف",
"PhoneNumberPlaceHolder":"أدخل رقم الهاتف",
"regPhoneNo": "يجب أن يتكون رقم الهاتف من 10 أرقام",
"NewNoOfAccounts":"عدد جديد من الحسابات",
"EnterNewNumberOfAccounts":"أدخل عددًا جديدًا من الحسابات",
"TotalNoOfAccounts":"العدد الإجمالي للحسابات",
"gridSearch":"يبحث:",
"gridShow":"يعرض",
"gridEntries":"إدخالات",
"gridFilter":"منقي",
"gridNum10":"عشرة",
"gridNum25":"خمسة وعشرون",
"gridNum50":"خمسون",
"gridNum100":"مائة",
"userID":"معرف المستخدم",
"userId":"معرف المستخدم",
"userContactNumber":"أدخل رقم اتصال المستخدم",
"SelectHomeBranch":"حدد الفرع الرئيسي",
"SelectRole":"حدد الدور",
"HomeBranch":"فرع المنزل",
"Role":"دور",
"ResetPassword":"إعادة تعيين كلمة المرور",
"enterNewPassword":"أدخل كلمة مرور جديدة",
"oldPassword":"كلمة المرور القديمة",
"newPasswordStatic":"كلمة السر الجديدة",
"savePassword":"يحفظ",
"passwordPattern":"يجب أن تكون كلمة المرور أكثر من 8 أحرف وتتضمن حرفًا كبيرًا وحرفًا صغيرًا ورقمًا وحرفًا خاصًا",
"SMSGatewaySelect":"اختر صنف",
"selectIdentValueType": "حدد نوع قيمة الهوية",
"selectIdentityValue" : "رقم الهوية مطلوب",
"IdTypeSelect":"حدد تحديد النوع",
"SMSGatewaySyriatel":"سيريتل",
"SMSGatewayTwillio":"تويليو",
"SMSGatewayJazz":"جاز",
"syriatelCredentials":"أوراق اعتماد سيريتل",
"twilioCredentials":"أوراق اعتماد تويليو",
"jazzCredentials":"أوراق اعتماد جاز",
"accountSID":"الحساب SID",
"authToken":"رمز المصادقة",
"fromNumber":"من الرقم",
"senderName":"اسم المرسل",
"senderNamePlaceHolder":"أدخل اسم المرسل",
"message": "رسالة",
"template": "قالب",
"language":"اللغة",
"notificationType": "نوع الإشعار",
"sinceLastDay":"منذ اليوم الماضي",
"TodayTotalErrorRequest":"طلب إجمالي الخطأ اليوم",
"TodayTotalPendingSms":"مجموع الرسائل القصيرة المعلقة اليوم",
"selectTheDates":"الرجاء تحديد التواريخ",
"selectIdentValue":"الرجاء تحديد نوع الهوية",
"noLogsFound":"لم يتم العثور على سجلات بين هذه التواريخ",
"loginSuccess":"تم تسجيل الدخول بنجاح",
"passwordNotMatched":"كلمة السر غير متطابقة",
"passwordPatternNotMatched":"نمط كلمة المرور غير مطابق",
"successDeleted":"تم الحذف بنجاح",
"passwordNotSame":"لا يمكن أن تكون كلمة المرور هي نفسها كلمة المرور القديمة",
"SuccessSave":"تم الحفظ بنجاح",
"SuccessFind":"تجده بنجاح",
"customerAlreadyUnblocked": "العميل تم فتح حسابه مسبقًا",
"SuccessUpdate":"تم التحديث بنجاح",
"UpdateFailed":"غير قادر على التحديث",
"formInvalid":"النموذج غير صالح",
"ServerError":"خطأ في الخادم",
"fieldsMissing":"الحقول المطلوبة مفقودة أو غير صالحة",
"selectAll":" اختر الكل " ,
"logoutSuccess":"تم تسجيل الخروج بنجاح",
"smsGateWayChanged":"تم تغيير بوابة الرسائل القصيرة بنجاح",
"cnic_scnic": "CNIC / SCNIC",
"poc": "POC",
"nicop": "S / NICOP",
"passport": "جواز السفر",
"Next": "التالي",
"ERR_APP_B_0001": "خطأ خادم داخلي",
"ERR_APP_B_0002": "خطأ اتصال محتمل مع الوحدة النمطية {{value1}}",
"ERR_APP_B_0003": "طلب غير صالح على وحدة {{value1}}",
"ERR_APP_B_0004": "انتهت الجلسة",
"ERR_APP_B_0005": "غير مصرح به: {{قيمة 1}}.",
"ERR_MDL_B_0001": "رمز الغرض موجود بالفعل",
"logintoAccount": "تسجيل الدخول إلى حسابك",
"userName": "اسم المستخدم",
"userNamePlaceHolder": "ادخل اسم المستخدم",
"password": "كلمة المرور",
"passwordPlaceHolder": "أدخل كلمة المرور",
"defaultPassword": "كلمة المرور الافتراضية",
"rememberMe": "تذكرنى",
"forgotPassword": "هل نسيت كلمة السر؟",
"passwordTooShort": "كلمة المرور قصيرة جدًا.",
"passwordsDoNotMatch": "كلمتا المرور غير متطابقتين.",
"login": "تسجيل الدخول",
"dashboardTitle": "لوحة القيادة",
"passwordChangeRequired": "تغيير كلمة المرور مطلوب",
"monthlyReqTitle": "الطلبات الشهرية",
"monthlySmsTitle": "الرسائل القصيرة الشهرية",
"Dashboard": "لوحة القيادة",
"UserManagement": "إدارةالمستخدم",
"thirdPartyRegistration": "تسجيل الطرف الثالث",
"setupUser": "مستخدم الإعداد",
"resetPassword": "إعادة تعيين كلمة مرور المستخدم",
"changePassword": "غير كلمة السر",
"Logging": "تسجيل",
"loggerManager": "مدير المسجل",
"SMSBanking": "خدمة الرسائل المصرفية",
"smsLogger": "مسجل الرسائل",
"smsLoggerDetails": "تفاصيل سجل الرسائل القصيرة",
"smsGateway": "بوابة الرسائل",
"ibUnblockUser": "إلغاء حظر مستخدم IB",
"ibSupport": "دعم IB",
"Permissions": "أذونات",
"permissions": "مدير الأذونات",
"TodayTotalRequests": "إجمالي الطلبات اليوم",
"TodayTotalSms": "مجموع الرسائل القصيرة اليوم",
"DescriptionUserPermission": "وصف",
"PermissionsUserPermission": "أذونات",
"SaveUserPermission": "يحفظ",
"UpdateUserPermission": "تحديث",
"DeleteUserPermission": "حذف",
"ViewUserPermission": "رأي",
"SelectUser": "اختر المستخدم",
"SelectAUser": "حدد مستخدمًا",
"loggingTitle": "تسجيل",
"IBChildTitle": "إلغاء حظر عن مستخدم",
"IBTitle": "تسجيل",
"loggingChildTitle": "مدير المسجل",
"fromDate": "من التاريخ",
"custId": "قيمة هوية العميل",
"toDate": "حتي اليوم",
"findLogs": "البحث عن السجلات",
"unBlockCustomer": "إلغاء حظر العميل",
"unblockUserDetails": "إلغاء حظر تفاصيل المستخدم",
"loggerManagerDetails": "تفاصيل مدير السجلات",
"fetchCustomer": "ابحث عن العملاء",
"loggingID": "معرف",
"firstName": "الاسم الأول",
"lastName": "اسم العائلة",
"cmpCuststatus": "حالة العميل",
"cmpCustlastlogin": "آخر تسجيل دخول للعميل",
"accountNonLocked": "الحساب مغلق",
"lockTime": "وقت القفل",
"accountno": "رقم الحساب",
"phoneno": "رقم الهاتف",
"loggingRequestUri": "طلب Uri",
"loggingResponseCode": "رمز الاستجابة",
"loggingRemoteIP": "بعيد IP",
"enterIdentityValue": "أدخل قيمة الهوية",
"loggingTimeTaken": "الوقت المستغرق",
"loggingDate": "تاريخ",
"loggingDateTime": "التاريخ والوقت",
"loggingMethod": "طريقة",
"DataAnalysis": "تحليل البيانات",
"Analysis": "التحليلات",
"smsBankingTitle": "خدمة الرسائل المصرفية",
"smsBankingChildTitle": "مسجل الرسائل",
"smsTrackingID": "معرف تتبع",
"smsMessage": "رسالة",
"smsNo": "رقم الهاتف",
"smsOrgaCode": "كود المنظمة",
"smsDate": "تاريخ",
"smsStatus": "حالة",
"viewThirdPartyAccounts": "عرض الحسابات",
"ThirdPartyID": "معرف الحفلة الثالثة",
"name": "اسم",
"EnterThirdPartyName": "أدخل اسم الطرف الثالث",
"email": "البريد الإلكتروني",
"invalidEmail": "أدخل بريدًا إلكترونيًا صالحًا يحتوي على @",
"Address": "تبوك",
"phoneNumber": "رقم الهاتف",
"PhoneNumberPlaceHolder": "أدخل رقم الهاتف",
"regPhoneNo": "يجب أن يتكون رقم الهاتف من 10 أرقام",
"NewNoOfAccounts": "عدد جديد من الحسابات",
"EnterNewNumberOfAccounts": "أدخل عددًا جديدًا من الحسابات",
"TotalNoOfAccounts": "العدد الإجمالي للحسابات",
"gridSearch": "يبحث:",
"gridShow": "يعرض",
"gridEntries": "إدخالات",
"gridFilter": "منقي",
"gridNum10": "عشرة",
"gridNum25": "خمسة وعشرون",
"gridNum50": "خمسون",
"gridNum100": "مائة",
"userID": "معرف المستخدم",
"userId": "معرف المستخدم",
"userContactNumber": "أدخل رقم اتصال المستخدم",
"SelectHomeBranch": "حدد الفرع الرئيسي",
"SelectRole": "حدد الدور",
"HomeBranch": "فرع المنزل",
"Role": "دور",
"ResetPassword": "إعادة تعيين كلمة المرور",
"enterNewPassword": "أدخل كلمة مرور جديدة",
"oldPassword": "كلمة المرور القديمة",
"newPasswordStatic": "كلمة السر الجديدة",
"savePassword": "يحفظ",
"passwordPattern": "يجب أن تكون كلمة المرور أكثر من 8 أحرف وتتضمن حرفًا كبيرًا وحرفًا صغيرًا ورقمًا وحرفًا خاصًا",
"SMSGatewaySelect": "اختر صنف",
"selectIdentValueType": "حدد نوع قيمة الهوية",
"selectIdentityValue": "رقم الهوية مطلوب",
"IdTypeSelect": "حدد تحديد النوع",
"SMSGatewaySyriatel": "سيريتل",
"SMSGatewayTwillio": "تويليو",
"SMSGatewayJazz": "جاز",
"syriatelCredentials": "أوراق اعتماد سيريتل",
"twilioCredentials": "أوراق اعتماد تويليو",
"jazzCredentials": "أوراق اعتماد جاز",
"accountSID": "الحساب SID",
"authToken": "رمز المصادقة",
"fromNumber": "من الرقم",
"senderName": "اسم المرسل",
"senderNamePlaceHolder": "أدخل اسم المرسل",
"message": "رسالة",
"template": "قالب",
"language": "اللغة",
"notificationType": "نوع الإشعار",
"sinceLastDay": "منذ اليوم الماضي",
"TodayTotalErrorRequest": "طلب إجمالي الخطأ اليوم",
"TodayTotalPendingSms": "مجموع الرسائل القصيرة المعلقة اليوم",
"selectTheDates": "الرجاء تحديد التواريخ",
"selectIdentValue": "الرجاء تحديد نوع الهوية",
"noLogsFound": "لم يتم العثور على سجلات بين هذه التواريخ",
"loginSuccess": "تم تسجيل الدخول بنجاح",
"passwordNotMatched": "كلمة السر غير متطابقة",
"passwordPatternNotMatched": "نمط كلمة المرور غير مطابق",
"successDeleted": "تم الحذف بنجاح",
"passwordNotSame": "لا يمكن أن تكون كلمة المرور هي نفسها كلمة المرور القديمة",
"SuccessSave": "تم الحفظ بنجاح",
"SuccessFind": "تجده بنجاح",
"customerAlreadyUnblocked": "العميل تم فتح حسابه مسبقًا",
"SuccessUpdate": "تم التحديث بنجاح",
"UpdateFailed": "غير قادر على التحديث",
"formInvalid": "النموذج غير صالح",
"ServerError": "خطأ في الخادم",
"fieldsMissing": "الحقول المطلوبة مفقودة أو غير صالحة",
"selectAll": " اختر الكل ",
"logoutSuccess": "تم تسجيل الخروج بنجاح",
"smsGateWayChanged": "تم تغيير بوابة الرسائل القصيرة بنجاح",
"cnic_scnic": "CNIC / SCNIC",
"poc": "POC",
"nicop": "S / NICOP",
"passport": "جواز السفر",
"Next": "التالي",
"ERR_APP_B_0001": "خطأ خادم داخلي",
"ERR_APP_B_0002": "خطأ اتصال محتمل مع الوحدة النمطية {{value1}}",
"ERR_APP_B_0003": "طلب غير صالح على وحدة {{value1}}",
"ERR_APP_B_0004": "انتهت الجلسة",
"ERR_APP_B_0005": "غير مصرح به: {{قيمة 1}}.",
"ERR_MDL_B_0001": "رمز الغرض موجود بالفعل",
"feedbackSetup": "ردود الفعل الإعداد",
"credentials": "أوراق اعتماد",
"credentialsTitle": "أوراق اعتماد ردود الفعل",
"confirmPassword": "تأكيد كلمة المرور",
"passwordPlaceholder": "أدخل كلمة المرور",
"confirmPasswordPlaceholder": "أدخل تأكيد كلمة المرور",
"newPasswordPlaceholder": "أدخل كلمة مرور جديدة",
"fieldRequired": "الخانة مطلوبة",
"emailLabel": "البريد الإلكتروني",
"emailRequiredError": "البريد الإلكتروني مطلوب.",
"invalidEmailFormatError": "تنسيق البريد الإلكتروني غير صالح.",
"passNotMatch": "كلمة السر غير متطابقة",
"feedbackSetup": "ردود الفعل الإعداد",
"credentials": "أوراق اعتماد",
"credentialsTitle": "أوراق اعتماد ردود الفعل",
"confirmPassword": "تأكيد كلمة المرور",
"passwordPlaceholder": "أدخل كلمة المرور",
"confirmPasswordPlaceholder": "أدخل تأكيد كلمة المرور",
"newPasswordPlaceholder": "أدخل كلمة مرور جديدة",
"fieldRequired": "الخانة مطلوبة",
"emailLabel": "البريد الإلكتروني",
"emailRequiredError": "البريد الإلكتروني مطلوب.",
"invalidEmailFormatError": "تنسيق البريد الإلكتروني غير صالح.",
"passNotMatch": "كلمة السر غير متطابقة",
"POR_ORGACODE": "المصرف",
"purposeSetup": "أضف غرض المعاملة",
"purpcodeLabel": "كود الغرض",
"purpdescLabel": "وصف الغرض",
"purpcodePlaceholder": "أدخل رمز الغرض",
"purpdescPlaceholder": "أدخل وصف الغرض",
"transactionDetails": "تفاصيل المعاملة",
"lengthExceed":" الطول تجاوز ",
"transactionLogs": "سجلات المعاملات",
"loadingTransactionLogs": "جاري تحميل سجلات المعاملات...",
"noTransactionLogsFound": "لم يتم العثور على سجلات معاملات.",
"noThirdPartyRegFound":"لم يتم العثور على تفاصيل تسجيل الطرف الثالث",
"logID": "معرف السجل",
"organization": "المنظمة",
"transactionID": "معرف المعاملة",
"drAccount": "حساب المدين",
"crAccount": "حساب الدائن",
"paymentMode": "وسيلة الدفع",
"channel": "القناة",
"createdAt": "تم الإنشاء في",
"refresh": "تحديث",
"loading": "جاري التحميل",
"invalidField": "ادخال غير صحيح",
"action": "اجراء",
"confirmDelete":"هل أنت متأكد أنك تريد حذف؟",
"confirmSave":"هل أنت متأكد أنك تريد الحفظ؟",
"2-stepAppPassword": "أدخل التحقق بخطوتين الذي تم إنشاؤه - كلمة مرور التطبيق",
"english": "إنجليزي",
"arabic": "عربي",
"userNameRequired" : "اسم المستخدم مطلوب",
"userNamePattterenError": "يُسمح فقط بالأحرف الصغيرة والأرقام",
"PasswordRequired": "كلمة المرور مطلوبة",
"copyRightsReserved": "جميع الحقوق محفوظة © {{currentYearLong}} لشركة MFSYS Technologies.",
"versionAndBuildNumber": "الإصدار {{versionNumber}} البناء {{buildNumber}}",
"versionBuildDate": "+ تاريخ البناء {{date}}",
"dashboard":"لوحة القيادة",
"date" : "تاريخ",
"logout": "تسجيل الخروج",
"save": "يحفظ",
"update": "تحديث",
"cancel": "يلغي",
"thirdPartyRegistrationDetails": "تفاصيل تسجيل الطرف الثالث",
"SetupUserDetails": "إعداد تفاصيل المستخدم",
"search": "يبحث",
"thirdPartyNamePlaceholder": "أدخل اسم الطرف الثالث",
"phoneNumberPlaceholder": "أدخل رقم الهاتف",
"newNoOfAccountsPlaceholder": "أدخل عدد الحسابات الجديد",
"page": "صفحة",
"of": "ل",
"totalItems": "السجلات",
"record": "سِجِلّ",
"previous": "سابق",
"next": "التالي",
"LOGIN_SUCCESSFULLY":"تم تسجيل الدخول بنجاح",
"RESET_PASSWORD_SUCCESS": "تمت إعادة تعيين كلمة المرور بنجاح",
"CHANGE_PASSWORD_SUCCESS": "تم تغيير كلمة المرور بنجاح",
"ALREADY_LOGGED_IN": "المستخدم مسجل دخوله بالفعل",
"ACCESS_DENIED" : "تم الرفض",
"INTERNAL_SERVER_ERROR": "خطأ في الخادم الداخلي",
"CONNECTION_ERROR": "خطأ في الاتصال",
"BAD_REQUEST": "اقتراح غير جيد",
"FORBIDDEN_REQUEST": "طلب ممنوع",
"UNAUTHORIZED_REQUEST": "طلب غير مصرح به",
"edit": "يحرر",
"delete": "يمسح",
"deleteUser": "حذف حساب المستخدم",
"permissionManagement": "إدارة الأذونات",
"userCode": "مستخدم",
"choose" : "يختار",
"allow": "يسمح",
"toDateInvalidError": "يجب أن يكون تاريخ اليوم أكبر من أو يساوي تاريخ البداية",
"noLoggingDetailsFound": "لم يتم العثور على تفاصيل التسجيل",
"noUserDetailsFound": "لم يتم العثور على تفاصيل المستخدم",
"ERR_SEC_0001": "البريد الإلكتروني موجود بالفعل",
"ERR_SEC_0002": "اسم المستخدم موجود بالفعل",
"ERR_SEC_0003": "كلمة المرور القديمة غير صحيحة",
"ERR_SEC_0004": "اسم المستخدم أو كلمة المرور غير صحيحة",
"ERR_SEC_0005": "المستخدم غير موجود",
"ERR_SEC_0006": "كلمة المرور التي تم إدخالها غير صحيحة",
"toDateGreaterThanToday": "يجب أن يكون التاريخ الحالي أقل من التاريخ الحالي",
"fromDateGreaterThanToday": "يجب أن يكون تاريخ البدء أقل من التاريخ الحالي",
"userIdMinLength": "يجب أن يكون معرف المستخدم مكوّنًا من 5 أحرف على الأقل",
"nameMinLength": "يجب أن يكون الاسم مكوّنًا من 5 أحرف على الأقل",
"emptySpaceRestriction": "المسافات الفارغة غير مسموح بها",
"USER_CREATED_SUCCESS": "تم إنشاء المستخدم",
"USER_DELETE_SUCCESS": "تم حذف المستخدم",
"SAVED_SUCCESSFULLY": "تم الحفظ بنجاح",
"activityLogs": "سجلات النشاط",
"activityLogDetails": "تفاصيل سجل النشاط",
"resetPasswordButton": "إعادة تعيين كلمة المرور"
"POR_ORGACODE": "المصرف",
"purposeSetup": "أضف غرض المعاملة",
"purpcodeLabel": "كود الغرض",
"purpdescLabel": "وصف الغرض",
"purpcodePlaceholder": "أدخل رمز الغرض",
"purpdescPlaceholder": "أدخل وصف الغرض",
"transactionDetails": "تفاصيل المعاملة",
"lengthExceed": " الطول تجاوز ",
"transactionLogs": "سجلات المعاملات",
"loadingTransactionLogs": "جاري تحميل سجلات المعاملات...",
"noTransactionLogsFound": "لم يتم العثور على سجلات معاملات.",
"noThirdPartyRegFound": "لم يتم العثور على تفاصيل تسجيل الطرف الثالث",
"logID": "معرف السجل",
"organization": "المنظمة",
"transactionID": "معرف المعاملة",
"drAccount": "حساب المدين",
"crAccount": "حساب الدائن",
"paymentMode": "وسيلة الدفع",
"channel": "القناة",
"createdAt": "تم الإنشاء في",
"refresh": "تحديث",
"loading": "جاري التحميل",
"invalidField": "ادخال غير صحيح",
"action": "اجراء",
"confirmDelete": "هل أنت متأكد أنك تريد حذف؟",
"confirmSave": "هل أنت متأكد أنك تريد الحفظ؟",
"2-stepAppPassword": "أدخل التحقق بخطوتين الذي تم إنشاؤه - كلمة مرور التطبيق",
"english": "إنجليزي",
"arabic": "عربي",
"userNameRequired": "اسم المستخدم مطلوب",
"userNamePattterenError": "يُسمح فقط بالأحرف الصغيرة والأرقام",
"PasswordRequired": "كلمة المرور مطلوبة",
"copyRightsReserved": "جميع الحقوق محفوظة © {{currentYearLong}} لشركة MFSYS Technologies.",
"versionAndBuildNumber": "الإصدار {{versionNumber}} البناء {{buildNumber}}",
"versionBuildDate": "+ تاريخ البناء {{date}}",
"dashboard": "لوحة القيادة",
"date": "تاريخ",
"logout": "تسجيل الخروج",
"save": "يحفظ",
"update": "تحديث",
"cancel": "يلغي",
"thirdPartyRegistrationDetails": "تفاصيل تسجيل الطرف الثالث",
"SetupUserDetails": "إعداد تفاصيل المستخدم",
"search": "يبحث",
"thirdPartyNamePlaceholder": "أدخل اسم الطرف الثالث",
"phoneNumberPlaceholder": "أدخل رقم الهاتف",
"newNoOfAccountsPlaceholder": "أدخل عدد الحسابات الجديد",
"page": "صفحة",
"of": "ل",
"totalItems": "السجلات",
"record": "سِجِلّ",
"previous": "سابق",
"next": "التالي",
"LOGIN_SUCCESSFULLY": "تم تسجيل الدخول بنجاح",
"RESET_PASSWORD_SUCCESS": "تمت إعادة تعيين كلمة المرور بنجاح",
"CHANGE_PASSWORD_SUCCESS": "تم تغيير كلمة المرور بنجاح",
"ALREADY_LOGGED_IN": "المستخدم مسجل دخوله بالفعل",
"ACCESS_DENIED": "تم الرفض",
"INTERNAL_SERVER_ERROR": "خطأ في الخادم الداخلي",
"CONNECTION_ERROR": "خطأ في الاتصال",
"BAD_REQUEST": "اقتراح غير جيد",
"FORBIDDEN_REQUEST": "طلب ممنوع",
"UNAUTHORIZED_REQUEST": "طلب غير مصرح به",
"edit": "يحرر",
"delete": "يمسح",
"deleteUser": "حذف حساب المستخدم",
"permissionManagement": "إدارة الأذونات",
"userCode": "مستخدم",
"choose": "يختار",
"allow": "يسمح",
"toDateInvalidError": "يجب أن يكون تاريخ اليوم أكبر من أو يساوي تاريخ البداية",
"noLoggingDetailsFound": "لم يتم العثور على تفاصيل التسجيل",
"noUserDetailsFound": "لم يتم العثور على تفاصيل المستخدم",
"ERR_SEC_0001": "البريد الإلكتروني موجود بالفعل",
"ERR_SEC_0002": "اسم المستخدم موجود بالفعل",
"ERR_SEC_0003": "كلمة المرور القديمة غير صحيحة",
"ERR_SEC_0004": "اسم المستخدم أو كلمة المرور غير صحيحة",
"ERR_SEC_0005": "المستخدم غير موجود",
"ERR_SEC_0006": "كلمة المرور التي تم إدخالها غير صحيحة",
"toDateGreaterThanToday": "يجب أن يكون التاريخ الحالي أقل من التاريخ الحالي",
"fromDateGreaterThanToday": "يجب أن يكون تاريخ البدء أقل من التاريخ الحالي",
"userIdMinLength": "يجب أن يكون معرف المستخدم مكوّنًا من 5 أحرف على الأقل",
"nameMinLength": "يجب أن يكون الاسم مكوّنًا من 5 أحرف على الأقل",
"emptySpaceRestriction": "المسافات الفارغة غير مسموح بها",
"USER_CREATED_SUCCESS": "تم إنشاء المستخدم",
"USER_DELETE_SUCCESS": "تم حذف المستخدم",
"SAVED_SUCCESSFULLY": "تم الحفظ بنجاح",
"activityLogs": "سجلات النشاط",
"activityLogDetails": "تفاصيل سجل النشاط",
"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",
"name":"Name",
"EnterThirdPartyName":"Enter Third Party Name",
"Email":"Email",
"email":"Email",
"Address":"Address",
"phoneNumber":"Phone Number",
@ -166,7 +165,6 @@
"ERR_APP_B_0004":"Session timed out",
"ERR_APP_B_0005":"Unauthorized: {{value1}}.",
"ERR_MDL_B_0001": "Purpose Code already exists",
"feedbackSetup": "Feedback Setup",
"credentials": "Credentials",
"credentialsTitle": "Feedback Credentials Setup",
@ -256,7 +254,8 @@
"noUserDetailsFound":"No User Details found",
"noThirdPartyRegFound":"No Third Party Registration Details Found",
"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_0004":"Invalid credentials",
"ERR_SEC_0005": "User not found",
@ -265,8 +264,9 @@
"fromDateGreaterThanToday": "From Date must be less than Current Date",
"userIdMinLength" : "User ID 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_UPDATE_SUCCESS": "User Updated",
"USER_DELETE_SUCCESS": "User Deleted",
"SAVED_SUCCESSFULLY" :"Saved Successfully",
"oldAndNewPass":"Old password and new password cannot be the same",
@ -274,5 +274,28 @@
"activityLogDetails": "Activity Log Details",
"transactionCode": "Transaction Code",
"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