Enhance transaction logs UI and fix translations

Improved the transaction logs component with better table UI, pagination, loading/error states, and date filtering. Updated SCSS for responsive design and visual enhancements. Fixed translation keys and placeholders in user management and constants. Adjusted login direction logic and commented out duplicate login check in authentication service.
dev-pending-20-01-2026
Naeem Ullah 6 days ago
parent 244ff1067a
commit 6e4f1c3c58

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

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

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

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

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

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

@ -95,7 +95,7 @@
for="userFullname" for="userFullname"
class="text-nowrap mt-2" class="text-nowrap mt-2"
> >
{{ "name" | translate {{ "userFullname" | translate
}}<span class="mandatory">*</span> }}<span class="mandatory">*</span>
</label> </label>
<div <div
@ -107,9 +107,7 @@
formControlName="userFullname" formControlName="userFullname"
name="userFullname" name="userFullname"
maxlength="500" maxlength="500"
placeholder="{{ placeholder="{{ 'userFullname' | translate }}"
'Full Name' | translate
}}"
/> />
<div <div
@ -394,10 +392,10 @@
{{ "userID" | translate }} {{ "userID" | translate }}
</th> </th>
<th style="width: 25%"> <th style="width: 25%">
{{ "Name" | translate }} {{ "userFullname" | translate }}
</th> </th>
<th style="width: 25%"> <th style="width: 25%">
{{ "Email" | translate }} {{ "email" | translate }}
</th> </th>
<th style="width: 15%"> <th style="width: 15%">
{{ "Role" | translate }} {{ "Role" | translate }}
@ -481,6 +479,13 @@
[clearable]="false" [clearable]="false"
[dropdownPosition]="'top'" [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> </ng-select>
</div> </div>

@ -5,9 +5,9 @@ export const CONSTANTS = {
}; };
export const pageSizeOptions = [ export const pageSizeOptions = [
{ value: 10, label: '10 items' }, { value: 10, label: '10' },
{ value: 20, label: '20 items' }, { value: 20, label: '20' },
{ value: 50, label: '50 items' }, { value: 50, label: '50' },
]; ];
export const toDateAfterFromDateValidator: ValidatorFn = ( export const toDateAfterFromDateValidator: ValidatorFn = (

@ -83,7 +83,7 @@
"ThirdPartyID": "معرف الحفلة الثالثة", "ThirdPartyID": "معرف الحفلة الثالثة",
"name": "اسم", "name": "اسم",
"EnterThirdPartyName": "أدخل اسم الطرف الثالث", "EnterThirdPartyName": "أدخل اسم الطرف الثالث",
"Email":"البريد الإلكتروني", "email": "البريد الإلكتروني",
"invalidEmail": "أدخل بريدًا إلكترونيًا صالحًا يحتوي على @", "invalidEmail": "أدخل بريدًا إلكترونيًا صالحًا يحتوي على @",
"Address": "تبوك", "Address": "تبوك",
"phoneNumber": "رقم الهاتف", "phoneNumber": "رقم الهاتف",
@ -179,7 +179,6 @@
"invalidEmailFormatError": "تنسيق البريد الإلكتروني غير صالح.", "invalidEmailFormatError": "تنسيق البريد الإلكتروني غير صالح.",
"passNotMatch": "كلمة السر غير متطابقة", "passNotMatch": "كلمة السر غير متطابقة",
"POR_ORGACODE": "المصرف", "POR_ORGACODE": "المصرف",
"purposeSetup": "أضف غرض المعاملة", "purposeSetup": "أضف غرض المعاملة",
"purpcodeLabel": "كود الغرض", "purpcodeLabel": "كود الغرض",
@ -277,7 +276,6 @@
"requestUri": "طلب URI", "requestUri": "طلب URI",
"method": "طريقة", "method": "طريقة",
"id": "بطاقة تعريف", "id": "بطاقة تعريف",
"logId": "معرف السجل", "logId": "معرف السجل",
"porOrgacode": "رمز المنظمة", "porOrgacode": "رمز المنظمة",
"drMbmbkmsnumber": "رقم الحساب المدين", "drMbmbkmsnumber": "رقم الحساب المدين",
@ -290,5 +288,12 @@
"drPcaGlacode": "حساب DR GL", "drPcaGlacode": "حساب DR GL",
"transactionUri": "معرّف المعاملة", "transactionUri": "معرّف المعاملة",
"transactionCode": "رمز المعاملة", "transactionCode": "رمز المعاملة",
"channelCode": "رمز القناة" "channelCode": "رمز القناة",
"userFullname": "الاسم الكامل",
"show": "عرض",
"tableCollapsed": "الجدول مطوي",
"showTable": "عرض الجدول",
"collapse": "يطوي",
"expand": "توسيع",
"entries": "إدخالات"
} }

@ -83,7 +83,6 @@
"ThirdPartyID":"Third Party ID", "ThirdPartyID":"Third Party ID",
"name":"Name", "name":"Name",
"EnterThirdPartyName":"Enter Third Party Name", "EnterThirdPartyName":"Enter Third Party Name",
"Email":"Email",
"email":"Email", "email":"Email",
"Address":"Address", "Address":"Address",
"phoneNumber":"Phone Number", "phoneNumber":"Phone Number",
@ -166,7 +165,6 @@
"ERR_APP_B_0004":"Session timed out", "ERR_APP_B_0004":"Session timed out",
"ERR_APP_B_0005":"Unauthorized: {{value1}}.", "ERR_APP_B_0005":"Unauthorized: {{value1}}.",
"ERR_MDL_B_0001": "Purpose Code already exists", "ERR_MDL_B_0001": "Purpose Code already exists",
"feedbackSetup": "Feedback Setup", "feedbackSetup": "Feedback Setup",
"credentials": "Credentials", "credentials": "Credentials",
"credentialsTitle": "Feedback Credentials Setup", "credentialsTitle": "Feedback Credentials Setup",
@ -292,6 +290,12 @@
"sgtGntrdate": "Transaction Date", "sgtGntrdate": "Transaction Date",
"sgtGntrcreateat": "Creation Date", "sgtGntrcreateat": "Creation Date",
"updatedAt": "Updated At", "updatedAt": "Updated At",
"channelCode": "Channel Code",
"channelCode": "Channel Code" "userFullname" : "Full Name",
"show": "Show",
"entries": "entries",
"tableCollapsed": "Table collapsed",
"showTable": "Show Table",
"collapse": "Collapse",
"expand": "Expand"
} }
Loading…
Cancel
Save