Refactor transaction logs UI and logic for clarity

Redesigned the transaction logs component to use a reactive form for date filtering, simplified the table and search logic, and improved pagination and export functionality. Updated SCSS for cleaner date input styling and adjusted i18n keys for new UI labels.
mazdak/UX-transactionLogs
Mazdak Gibran 5 days ago
parent 9039567896
commit cdec4e63ec

@ -1,5 +1,6 @@
<div id="layout-wrapper"> <div id="layout-wrapper">
<div class="inner-pg-sp"> <div class="inner-pg-sp">
<div class="container-fluid"> <div class="container-fluid">
<div class="row"> <div class="row">
<div class="col-12"> <div class="col-12">
@ -7,177 +8,133 @@
</div> </div>
</div> </div>
</div> </div>
<div class="container-fluid"> <div class="container-fluid">
<!-- SEARCH FORM -->
<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 class="table-section"> <div class="card-header font-edit-13-child mb-3">
<div class="row"> {{ "transactionLogs" | translate }}
<div class="col-lg-12"> </div>
<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">
{{ "transactionLogs" | translate }}
<div class="d-flex align-items-center gap-2"> <form [formGroup]="logsSearchForm">
<!-- Date Filter Toggle Button --> <div class="row g-3 mb-3">
<button <div class="col-md-6">
class="btn btn-sm btn-outline-info" <label>
(click)="toggleDateFilters()" {{ "fromDate" | translate }}
[title]="(showDateFilters ? 'hideDateFilters' : 'showDateFilters') | translate" <span class="mandatory">*</span>
> </label>
<i class="fas fa-calendar-alt"></i> <div class="date-input-wrapper">
<span class="d-none d-md-inline ms-1">{{ "dateFilter" | translate }}</span> <input
</button> type="date"
formControlName="fromDate"
<div class="search-box"> class="form-control"
<input [max]="maxDate"
type="text" />
class="form-control form-control-sm" <i class="fas fa-calendar calendar-icon"></i>
[(ngModel)]="searchText" </div>
(ngModelChange)="onSearch($event)" <div class="text-danger"
placeholder="{{ 'search' | translate }}" *ngIf="logsSearchForm.get('fromDate')?.touched && logsSearchForm.get('fromDate')?.invalid">
[disabled]="isLoading" {{ 'fieldRequired' | translate }}
/> </div>
<i class="fas fa-search search-icon"></i> </div>
</div>
<div class="d-flex align-items-center gap-2">
<button
class="btn btn-sm btn-outline-success"
(click)="exportDataInExcel()"
[disabled]="isLoading || transactionLog.length === 0"
title="Export to Excel"
>
<i class="fa fa-download"></i>
</button>
</div>
<button <div class="col-md-6">
class="btn btn-sm btn-outline-secondary" <label>
(click)="toggleTableCard()" {{ "toDate" | translate }} <span class="mandatory">*</span>
[title]="(transactionDataExpanded ? 'collapse' : 'expand') | translate" </label>
> <div class="date-input-wrapper">
<i *ngIf="transactionDataExpanded" class="dripicons-chevron-up"></i> <input
<i *ngIf="!transactionDataExpanded" class="dripicons-chevron-down"></i> type="date"
</button> formControlName="toDate"
</div> class="form-control"
</div> [max]="maxDate"
/>
<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>
<!-- Date Filter Section --> <div class="text-end">
<div class="card-body border-bottom bg-light" *ngIf="showDateFilters"> <button
<div class="row g-3"> class="btn btn-primary"
<div class="col-md-3"> [disabled]="logsSearchForm.invalid"
<label class="form-label small">{{ "fromDate" | translate }}</label> (click)="getlogsData()"
<input >
type="date" {{ "findLogs" | translate }}
class="form-control form-control-sm" </button>
[(ngModel)]="fromDate" </div>
[max]="maxDate" </form>
(change)="onDateFilterChange()" </div>
[disabled]="isLoading" </div>
/> </div>
</div>
<div class="col-md-3">
<label class="form-label small">{{ "toDate" | translate }}</label>
<input
type="date"
class="form-control form-control-sm"
[(ngModel)]="toDate"
[max]="maxDate"
(change)="onDateFilterChange()"
[disabled]="isLoading"
/>
</div>
<div class="col-md-6">
<div class="d-flex flex-wrap gap-2 align-items-end h-100">
<!-- Quick Date Filters -->
<div class="btn-group btn-group-sm">
<button
class="btn btn-outline-primary"
(click)="applyLast7Days()"
[disabled]="isLoading"
>
{{ "last7Days" | translate }}
</button>
<button
class="btn btn-outline-primary"
(click)="applyLast30Days()"
[disabled]="isLoading"
>
{{ "last30Days" | translate }}
</button>
<button
class="btn btn-outline-primary"
(click)="applyThisMonth()"
[disabled]="isLoading"
>
{{ "thisMonth" | translate }}
</button>
</div>
<!-- Clear Filter Button -->
<button
class="btn btn-sm btn-outline-danger"
(click)="clearDateFilters()"
[disabled]="isLoading || (!fromDate && !toDate)"
>
<i class="fas fa-times"></i>
{{ "clearFilter" | translate }}
</button>
</div>
</div>
</div>
<!-- Active Filter Badge -->
<div class="mt-2" *ngIf="fromDate || toDate">
<span class="badge bg-info">
<i class="fas fa-filter me-1"></i>
{{ "activeFilter" | translate }}:
<span *ngIf="fromDate">{{ "from" | translate }} {{ fromDate | date }}</span>
<span *ngIf="fromDate && toDate"> {{ "to" | translate }} </span>
<span *ngIf="toDate">{{ toDate | date }}</span>
</span>
</div>
</div>
<div <!-- TABLE -->
class="card-body" <div class="col-xl-12 mt-4">
*ngIf="transactionDataExpanded; else collapsedTable" <div class="card border">
> <div class="card-body">
<!-- Loading State --> <div class="card-header d-flex justify-content-between align-items-center font-edit-13-child">
<div *ngIf="isLoading" class="text-center py-5"> {{ "transactionLogDetails" | translate }}
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">Loading...</span>
</div>
<p class="text-muted mt-2">{{ "loadingTransactionLogs" | translate }}</p>
<small *ngIf="fromDate || toDate" class="text-info">
{{ "filteringByDate" | translate }}
</small>
</div>
<!-- Error Message --> <div class="d-flex gap-2 align-items-center" *ngIf="allItems.length">
<div *ngIf="!isLoading && errorMessage" class="alert alert-danger alert-dismissible fade show" role="alert"> <div class="search-box">
{{ errorMessage }} <input
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close" (click)="errorMessage = ''"></button> type="text"
</div> class="form-control form-control-sm"
placeholder="{{ 'search' | translate }}"
[(ngModel)]="searchText"
(ngModelChange)="applySearch()"
/>
<i class="fas fa-search search-icon"></i>
</div>
<!-- No Data Found --> <div class="d-flex align-items-center gap-2">
<div *ngIf="!isLoading && !errorMessage && transactionLog.length === 0" class="text-center py-5 text-muted"> <button
<i class="fas fa-database fa-3x mb-3 opacity-50"></i> class="btn btn-sm btn-outline-success"
<h5>{{ "noTransactionLogsFound" | translate }}</h5> (click)="exportDataInExcel()"
<p *ngIf="fromDate || toDate" class="small"> [disabled]="!filteredItems.length"
{{ "tryAdjustingFilters" | translate }} title="Export to Excel"
</p> >
</div> <i class="fa fa-download"></i>
</button>
</div>
<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 class="card-body" *ngIf="logsDataExpanded">
<!-- NO RECORDS -->
<div *ngIf="!filteredItems.length" class="text-center text-muted py-4">
<i class="fas fa-search fa-2x mb-2 opacity-50"></i>
<p>{{ "noTransactionLogsFound" | translate }}</p>
</div>
<!-- Data Table --> <!-- TABLE -->
<div *ngIf="!isLoading && !errorMessage && transactionLog.length > 0" class="table-responsive"> <div *ngIf="filteredItems.length" class="table-responsive">
<table class="table table-hover table-bordered mb-0"> <table class="table table-bordered mb-0">
<thead class="table-light"> <thead class="table-light">
<tr> <tr>
<th scope="col">{{ "logID" | translate }}</th> <th scope="col">{{ "logID" | translate }}</th>
<th scope="col">{{ "organization" | translate }}</th> <th scope="col">{{ "organization" | translate }}</th>
<th scope="col">{{ "transactionID" | translate }}</th> <th scope="col">{{ "transactionID" | translate }}</th>
<th scope="col">{{ "drAccount" | translate }}</th> <th scope="col">{{ "drAccount" | translate }}</th>
@ -190,10 +147,11 @@
<th scope="col">{{ "date" | translate }}</th> <th scope="col">{{ "date" | translate }}</th>
<th scope="col">{{ "channel" | translate }}</th> <th scope="col">{{ "channel" | translate }}</th>
<th scope="col">{{ "createdAt" | translate }}</th> <th scope="col">{{ "createdAt" | translate }}</th>
</tr> </tr>
</thead> </thead>
<tbody>
<tr *ngFor="let log of pagedItems | tableFilter: searchText: [ <tbody>
<tr *ngFor="let log of pagedItems | tableFilter: searchText: [
'logId', 'logId',
'porOrgacode', 'porOrgacode',
'transactionID', 'transactionID',
@ -222,73 +180,63 @@
<td>{{ log.channelCode || '-' }}</td> <td>{{ log.channelCode || '-' }}</td>
<td>{{ log.createdAt | date: 'MMM dd, yyyy HH:mm' }}</td> <td>{{ log.createdAt | date: 'MMM dd, yyyy HH:mm' }}</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
<!-- Pagination --> <!-- PAGINATION FOOTER -->
<div class="d-flex justify-content-between align-items-center mt-3 flex-wrap"> <div class="d-flex justify-content-between align-items-center mt-3 flex-wrap">
<!-- Items per page --> <!-- Items per page -->
<div class="d-flex align-items-center gap-2 mb-2 mb-md-0"> <div class="d-flex align-items-center gap-2 mb-2 mb-md-0">
<span class="text-muted small">{{ "show" | translate }}</span> <span class="text-muted small">{{ "show" | translate }}</span>
<div style="width: 120px;"> <div style="width: 120px;">
<ng-select <ng-select
class="form-select-sm" class="form-select-sm"
[items]="pageSizeOptions" [items]="pageSizeOptions"
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">
<ng-template ng-option-tmp let-item="item"> {{ item.value }} {{ "entries" | translate }}
{{ item.value }} {{ "entries" | translate }} </ng-template>
</ng-template>
<ng-template ng-label-tmp let-item="item"> <ng-template ng-label-tmp let-item="item">
{{ item.value }} {{ "entries" | translate }} {{ item.value }} {{ "entries" | translate }}
</ng-template> </ng-template>
</ng-select> </ng-select>
</div> </div>
</div> </div>
<!-- Page info --> <!-- Page info -->
<div class="text-muted mb-2 mb-md-0"> <div class="text-muted mb-2 mb-md-0">
{{ "page" | translate }} {{ currentPage }} {{ "page" | translate }} {{ currentPage }}
{{ "of" | translate }} {{ totalPages() }} {{ "of" | translate }} {{ totalPages() }}
<span class="d-none d-md-inline"> <span class="d-none d-md-inline">
({{ totalCount }} {{ "totalItems" | translate }}) ({{ filteredItems.length }} {{ "totalItems" | translate }})
</span> </span>
<span *ngIf="fromDate || toDate" class="badge bg-info ms-2"> </div>
<i class="fas fa-filter"></i>
</span>
</div>
<!-- Pagination buttons --> <!-- Pagination buttons -->
<div class="btn-group"> <div class="btn-group">
<button <button
class="btn btn-outline-primary btn-sm" class="btn btn-outline-primary btn-sm"
(click)="previousPage()" (click)="previousPage()"
[disabled]="currentPage === 1 || isLoading" [disabled]="currentPage === 1"
> >
<i class="fas fa-chevron-left me-1"></i> <i class="fas fa-chevron-left me-1"></i>
{{ "previous" | translate }} {{ "previous" | translate }}
</button> </button>
<button <button
class="btn btn-outline-primary btn-sm" class="btn btn-outline-primary btn-sm"
(click)="nextPage()" (click)="nextPage()"
[disabled]="currentPage === totalPages() || isLoading" [disabled]="currentPage >= totalPages()"
> >
{{ "next" | translate }} {{ "next" | translate }}
<i class="fas fa-chevron-right ms-1"></i> <i class="fas fa-chevron-right ms-1"></i>
</button> </button>
</div>
</div>
</div>
</div>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -297,14 +245,4 @@
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<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>

@ -1,139 +1,29 @@
// Date filter section .date-input-wrapper {
.date-filter-section { position: relative;
transition: all 0.3s ease;
.date-input-group { input[type="date"] {
position: relative; cursor: pointer;
padding-right: 35px;
.form-control {
padding-right: 2.5rem;
}
.date-icon { &::-webkit-calendar-picker-indicator {
position: absolute; position: absolute;
right: 0.75rem; left: 0;
top: 50%; top: 0;
transform: translateY(-50%); width: 100%;
color: #6c757d; height: 100%;
} margin: 0;
} padding: 0;
cursor: pointer;
.quick-filter-btn { opacity: 0;
&.active {
background-color: var(--bs-primary);
color: white;
border-color: var(--bs-primary);
}
}
}
// Table styling
.table-responsive {
min-height: 400px;
table {
font-size: 0.875rem;
th {
font-weight: 600;
white-space: nowrap;
background-color: #f8f9fa;
}
td {
vertical-align: middle;
code {
font-size: 0.75rem;
background-color: #f8f9fa;
padding: 0.2rem 0.4rem;
border-radius: 0.25rem;
}
.badge {
font-size: 0.7rem;
padding: 0.25rem 0.5rem;
}
} }
} }
}
// Loading spinner
.spinner-border {
width: 3rem;
height: 3rem;
}
// Search box
.search-box {
position: relative;
min-width: 200px;
.search-icon { .calendar-icon {
position: absolute; position: absolute;
right: 0.75rem; right: 10px;
top: 50%; top: 50%;
transform: translateY(-50%); transform: translateY(-50%);
color: #6c757d;
pointer-events: none; pointer-events: none;
} color: #6c757d;
.form-control {
padding-right: 2.5rem;
}
}
// Active filter badge
.active-filter-badge {
animation: fadeIn 0.3s ease;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
// Mobile responsive adjustments
@media (max-width: 768px) {
.date-filter-section {
.btn-group {
width: 100%;
.btn {
flex: 1;
font-size: 0.75rem;
padding: 0.375rem 0.5rem;
}
}
}
.table-responsive {
overflow-x: auto;
table {
min-width: 1200px;
}
}
.search-box {
min-width: 150px;
}
}
// Dark mode support (if needed)
[data-bs-theme="dark"] {
.table-light {
background-color: #2d333b !important;
color: #adb5bd;
}
.search-box .search-icon {
color: #adb5bd;
} }
} }

@ -1,340 +1,171 @@
import { Component, OnInit, OnDestroy } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { CommonModule } from '@angular/common'; import { CommonModule, DatePipe } from '@angular/common';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
import { ExcelExportService } from '../shared/services/excel-export.service'; import { ExcelExportService } from '../shared/services/excel-export.service';
import { TRANSACTION_LOGS_FILE_NAME } from '../utils/app.constants'; import { toDateAfterFromDateValidator, TRANSACTION_LOGS_FILE_NAME } from '../utils/app.constants';
import { pageSizeOptions } from '../utils/app.constants'; 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 { FormBuilder, FormGroup, FormsModule, ReactiveFormsModule, Validators } 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';
import { Subject } from 'rxjs';
import { takeUntil, debounceTime } from 'rxjs/operators';
@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'], styleUrls: ['./transaction-logs.component.scss'],
imports: [CommonModule, TranslateModule, NgSelectModule, FormsModule, ReactiveFormsModule, TableFilterPipe] imports: [
}) TranslateModule,
export class TransactionLogsComponent implements OnInit, OnDestroy { FormsModule,
currentPage: number = 1; NgSelectModule,
totalCount: number = 0; CommonModule,
renewalDataExpanded: boolean = true; ReactiveFormsModule,
TableFilterPipe
],
providers: [DatePipe]})
export class TransactionLogsComponent implements OnInit {
logsSearchForm!: FormGroup;
pageSizeOptions = pageSizeOptions; pageSizeOptions = pageSizeOptions;
itemsPerPage: number = 10; itemsPerPage = 10;
transactionLog: TransactionLog[] = []; currentPage = 1;
maxDate: string;
searchText = '';
logsDataExpanded = true;
isLoading = false; isLoading = false;
errorMessage: string = '';
searchText: string = ''; /** DATA LAYERS (do not mix these) */
allItems: TransactionLog[] = []; allItems: TransactionLog[] = []; // raw API data
transactionDataExpanded: boolean = true; filteredItems: TransactionLog[] = []; // after search
pagedItems: TransactionLog[] = []; // table view
// Date range properties
fromDate: string = '';
toDate: string = '';
maxDate: string = '';
showDateFilters: boolean = false;
// Search subject for debouncing
private searchSubject = new Subject<string>();
private destroy$ = new Subject<void>();
constructor( constructor(
private excelExportService: ExcelExportService, private fb: FormBuilder,
private httpService: HttpURIService, private httpService: HttpURIService,
) {} private datePipe: DatePipe,
private excelExportService: ExcelExportService,
ngOnInit(): void { ) {
// Set max date to today this.maxDate = new Date().toISOString().split('T')[0];
this.maxDate = formatDate(new Date(), 'yyyy-MM-dd', 'en-US');
// Set default date range (last 7 days)
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');
// Set up search debouncing
this.setupSearchDebounce();
this.loadTransactionLogs();
} }
ngOnDestroy(): void { ngOnInit(): void {
this.destroy$.next(); this.logsSearchForm = this.fb.group(
this.destroy$.complete(); {
this.searchSubject.complete(); fromDate: ['', Validators.required],
toDate: ['', Validators.required],
},
{ validators: toDateAfterFromDateValidator },
);
} }
private setupSearchDebounce(): void { getlogsData(): void {
this.searchSubject.pipe( if (this.logsSearchForm.invalid) return;
takeUntil(this.destroy$),
debounceTime(300)
).subscribe((searchValue: string) => {
this.searchText = searchValue;
this.currentPage = 1;
this.loadTransactionLogs();
});
}
loadTransactionLogs(): void {
this.isLoading = true; this.isLoading = true;
this.errorMessage = '';
const { fromDate, toDate } = this.logsSearchForm.value;
// Validate date range before making API call
if (!this.validateDateRange()) { const params = new HttpParams()
this.isLoading = false; .set('fromDate', this.datePipe.transform(fromDate, 'dd-MM-yyyy')!)
return; .set('toDate', this.datePipe.transform(toDate, 'dd-MM-yyyy')!);
}
// 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<TransactionLog[]>(URIKey.TRANSACTION_LOGS, params)
.subscribe({ .subscribe({
next: (res) => { next: (response) => {
const logs = Array.isArray(res) ? res : res?.data; this.allItems = response ?? [];
this.transactionLog = logs ?? []; this.filteredItems = [...this.allItems];
this.allItems = [...this.transactionLog]; this.currentPage = 1;
this.updatePagedItems();
// 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.isLoading = false; this.isLoading = false;
}, },
error: (err) => { error: () => {
console.error('Error fetching transaction logs:', err);
this.errorMessage = err.message || 'Failed to load transaction logs. Please try again.';
this.transactionLog = [];
this.allItems = []; this.allItems = [];
this.totalCount = 0; this.filteredItems = [];
this.pagedItems = [];
this.isLoading = false; this.isLoading = false;
}, },
complete: () => {
this.isLoading = false;
}
}); });
} }
private validateDateRange(): boolean { /** SEARCH — filters DATA, not DOM */
if (this.fromDate && this.toDate) { applySearch(): void {
const from = new Date(this.fromDate); const value = this.searchText.toLowerCase().trim();
const to = new Date(this.toDate);
if (!value) {
if (from > to) { this.filteredItems = [...this.allItems];
this.errorMessage = 'From date cannot be after To date'; } else {
return false; this.filteredItems = this.allItems.filter((item) =>
} [
'logId',
// Optional: Validate date range is not too wide 'porOrgacode',
const diffTime = Math.abs(to.getTime() - from.getTime()); 'transactionID',
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)); 'drMbmbkmsnumber',
'crMbmbkmsnumber',
if (diffDays > 365) { 'crPcaglacode',
this.errorMessage = 'Date range cannot exceed 1 year'; 'drPcaGlacode',
return false; 'amount',
} 'paymentMode',
'ppmPymdcode',
'sgtGntrdate',
'channelCode',
'createdAt',
'transactionUri',
'transactionCode',
].some((key) =>
String((item as any)[key] ?? '')
.toLowerCase()
.includes(value),
),
);
} }
return true;
}
// Date filter change handler
onDateFilterChange(): void {
this.currentPage = 1; this.currentPage = 1;
this.loadTransactionLogs(); this.updatePagedItems();
} }
// Clear date filters updatePagedItems(): void {
clearDateFilters(): void { const start = (this.currentPage - 1) * this.itemsPerPage;
this.fromDate = ''; const end = start + this.itemsPerPage;
this.toDate = ''; this.pagedItems = this.filteredItems.slice(start, end);
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();
}
// Apply date range (Last 3 months)
applyLast3Months(): void {
const today = new Date();
const threeMonthsAgo = new Date();
threeMonthsAgo.setMonth(today.getMonth() - 3);
this.fromDate = formatDate(threeMonthsAgo, 'yyyy-MM-dd', 'en-US');
this.toDate = formatDate(today, 'yyyy-MM-dd', 'en-US');
this.onDateFilterChange();
}
toggleTableCard(): void {
this.transactionDataExpanded = !this.transactionDataExpanded;
}
itemsPerPageChanged(): void {
this.currentPage = 1;
this.loadTransactionLogs();
}
onSearch(value: string): void {
this.searchSubject.next(value);
} }
totalPages(): number { totalPages(): number {
if (this.totalCount === 0) return 1; return Math.ceil(this.filteredItems.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.loadTransactionLogs(); this.updatePagedItems();
} }
} }
nextPage(): void { nextPage(): void {
if (this.currentPage < this.totalPages()) { if (this.currentPage < this.totalPages()) {
this.currentPage++; this.currentPage++;
this.loadTransactionLogs(); this.updatePagedItems();
} }
} }
exportDataInExcel(): void {
// Build export parameters
let params = new HttpParams();
// Include date filters in export if they are set
if (this.fromDate) {
params = params.set('fromDate', this.fromDate);
}
if (this.toDate) {
params = params.set('toDate', this.toDate);
}
if (this.searchText && this.searchText.trim() !== '') {
params = params.set('search', this.searchText.trim());
}
// For large exports, you might want to fetch all data without pagination
params = params.set('limit', '10000'); // Adjust as needed
this.httpService
.requestGET<any>(URIKey.TRANSACTION_LOGS, params)
.subscribe({
next: (res) => {
const logs = Array.isArray(res) ? res : res?.data;
const dataToExport = logs ?? [];
if (dataToExport.length === 0) {
this.errorMessage = 'No data to export';
return;
}
// Generate filename with date range if applicable
let fileName = TRANSACTION_LOGS_FILE_NAME;
if (this.fromDate || this.toDate) {
const from = this.fromDate ? formatDate(new Date(this.fromDate), 'dd-MMM-yyyy', 'en-US') : 'All';
const to = this.toDate ? formatDate(new Date(this.toDate), 'dd-MMM-yyyy', 'en-US') : 'All';
fileName = `TransactionLogs_${from}_to_${to}`;
}
this.excelExportService.exportExcel(dataToExport, fileName);
},
error: (err) => {
console.error('Error exporting data:', err);
this.errorMessage = 'Failed to export data. Please try again.';
}
});
}
// Get filtered items for display (used in template) itemsPerPageChanged(): void {
get filteredItems(): TransactionLog[] { this.currentPage = 1;
return this.allItems; this.updatePagedItems();
} }
// Format date for display toggleTableCard(): void {
formatDateDisplay(dateString: string): string { this.logsDataExpanded = !this.logsDataExpanded;
if (!dateString) return '';
return formatDate(new Date(dateString), 'MMM dd, yyyy', 'en-US');
} }
// Check if date filter is active exportDataInExcel(): void {
get isDateFilterActive(): boolean { this.excelExportService.exportExcel(
return !!this.fromDate || !!this.toDate; this.allItems,
TRANSACTION_LOGS_FILE_NAME,
);
} }
get pagedItems(): TransactionLog[] {
const start = (this.currentPage - 1) * this.itemsPerPage;
return this.allItems.slice(start, start + this.itemsPerPage);
}
} }

@ -301,5 +301,6 @@
"expand": "Expand", "expand": "Expand",
"dateFilter": "Date Filter", "dateFilter": "Date Filter",
"showDateFilters":"Show Date Filter", "showDateFilters":"Show Date Filter",
"hideDateFilters":"Hide Date Filter" "hideDateFilters":"Hide Date Filter",
"transactionLogDetails": "Transaction Logs Details"
} }

Loading…
Cancel
Save