Refactor transaction logs UI and logic for clarity #64

Open
mazdak.gibran wants to merge 1 commits from mazdak/UX-transactionLogs into dev-pending-01-02-2026

@ -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">
@ -9,36 +10,91 @@
</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">
<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">
{{ "transactionLogs" | translate }} {{ "transactionLogs" | translate }}
</div>
<div class="d-flex align-items-center gap-2"> <form [formGroup]="logsSearchForm">
<!-- Date Filter Toggle Button --> <div class="row g-3 mb-3">
<div class="col-md-6">
<label>
{{ "fromDate" | translate }}
<span class="mandatory">*</span>
</label>
<div class="date-input-wrapper">
<input
type="date"
formControlName="fromDate"
class="form-control"
[max]="maxDate"
/>
<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">
<label>
{{ "toDate" | translate }} <span class="mandatory">*</span>
</label>
<div class="date-input-wrapper">
<input
type="date"
formControlName="toDate"
class="form-control"
[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>
<div class="text-end">
<button <button
class="btn btn-sm btn-outline-info" class="btn btn-primary"
(click)="toggleDateFilters()" [disabled]="logsSearchForm.invalid"
[title]="(showDateFilters ? 'hideDateFilters' : 'showDateFilters') | translate" (click)="getlogsData()"
> >
<i class="fas fa-calendar-alt"></i> {{ "findLogs" | translate }}
<span class="d-none d-md-inline ms-1">{{ "dateFilter" | translate }}</span>
</button> </button>
</div>
</form>
</div>
</div>
</div>
<!-- TABLE -->
<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">
{{ "transactionLogDetails" | translate }}
<div class="d-flex gap-2 align-items-center" *ngIf="allItems.length">
<div class="search-box"> <div class="search-box">
<input <input
type="text" type="text"
class="form-control form-control-sm" class="form-control form-control-sm"
[(ngModel)]="searchText"
(ngModelChange)="onSearch($event)"
placeholder="{{ 'search' | translate }}" placeholder="{{ 'search' | translate }}"
[disabled]="isLoading" [(ngModel)]="searchText"
(ngModelChange)="applySearch()"
/> />
<i class="fas fa-search search-icon"></i> <i class="fas fa-search search-icon"></i>
</div> </div>
@ -47,7 +103,7 @@
<button <button
class="btn btn-sm btn-outline-success" class="btn btn-sm btn-outline-success"
(click)="exportDataInExcel()" (click)="exportDataInExcel()"
[disabled]="isLoading || transactionLog.length === 0" [disabled]="!filteredItems.length"
title="Export to Excel" title="Export to Excel"
> >
<i class="fa fa-download"></i> <i class="fa fa-download"></i>
@ -57,124 +113,25 @@
<button <button
class="btn btn-sm btn-outline-secondary" class="btn btn-sm btn-outline-secondary"
(click)="toggleTableCard()" (click)="toggleTableCard()"
[title]="(transactionDataExpanded ? 'collapse' : 'expand') | translate" [title]="(logsDataExpanded ? 'collapse' : 'expand') | translate"
>
<i *ngIf="transactionDataExpanded" class="dripicons-chevron-up"></i>
<i *ngIf="!transactionDataExpanded" class="dripicons-chevron-down"></i>
</button>
</div>
</div>
<!-- Date Filter Section -->
<div class="card-body border-bottom bg-light" *ngIf="showDateFilters">
<div class="row g-3">
<div class="col-md-3">
<label class="form-label small">{{ "fromDate" | translate }}</label>
<input
type="date"
class="form-control form-control-sm"
[(ngModel)]="fromDate"
[max]="maxDate"
(change)="onDateFilterChange()"
[disabled]="isLoading"
/>
</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 }} <i *ngIf="logsDataExpanded" class="dripicons-chevron-up"></i>
</button> <i *ngIf="!logsDataExpanded" class="dripicons-chevron-down"></i>
</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> </button>
</div> </div>
</div> </div>
</div> </div>
<!-- Active Filter Badge --> <div class="card-body" *ngIf="logsDataExpanded">
<div class="mt-2" *ngIf="fromDate || toDate"> <!-- NO RECORDS -->
<span class="badge bg-info"> <div *ngIf="!filteredItems.length" class="text-center text-muted py-4">
<i class="fas fa-filter me-1"></i> <i class="fas fa-search fa-2x mb-2 opacity-50"></i>
{{ "activeFilter" | translate }}: <p>{{ "noTransactionLogsFound" | translate }}</p>
<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
class="card-body"
*ngIf="transactionDataExpanded; else collapsedTable"
>
<!-- 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>
<small *ngIf="fromDate || toDate" class="text-info">
{{ "filteringByDate" | translate }}
</small>
</div> </div>
<!-- Error Message --> <!-- TABLE -->
<div *ngIf="!isLoading && errorMessage" class="alert alert-danger alert-dismissible fade show" role="alert"> <div *ngIf="filteredItems.length" class="table-responsive">
{{ errorMessage }} <table class="table table-bordered mb-0">
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close" (click)="errorMessage = ''"></button>
</div>
<!-- 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>
<p *ngIf="fromDate || toDate" class="small">
{{ "tryAdjustingFilters" | translate }}
</p>
</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 scope="col">{{ "logID" | translate }}</th> <th scope="col">{{ "logID" | translate }}</th>
@ -192,6 +149,7 @@
<th scope="col">{{ "createdAt" | translate }}</th> <th scope="col">{{ "createdAt" | translate }}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr *ngFor="let log of pagedItems | tableFilter: searchText: [ <tr *ngFor="let log of pagedItems | tableFilter: searchText: [
'logId', 'logId',
@ -225,7 +183,7 @@
</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">
@ -240,7 +198,6 @@
[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 }}
@ -258,10 +215,7 @@
{{ "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 *ngIf="fromDate || toDate" class="badge bg-info ms-2">
<i class="fas fa-filter"></i>
</span> </span>
</div> </div>
@ -270,7 +224,7 @@
<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 }}
@ -278,7 +232,7 @@
<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>
@ -292,19 +246,3 @@
</div> </div>
</div> </div>
</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 {
transition: all 0.3s ease;
.date-input-group {
position: relative; position: relative;
.form-control { input[type="date"] {
padding-right: 2.5rem; cursor: pointer;
} padding-right: 35px;
.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 .calendar-icon {
.search-box {
position: relative;
min-width: 200px;
.search-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;
isLoading = false; maxDate: string;
errorMessage: string = ''; searchText = '';
searchText: string = '';
allItems: TransactionLog[] = [];
transactionDataExpanded: boolean = true;
// Date range properties logsDataExpanded = true;
fromDate: string = ''; isLoading = false;
toDate: string = '';
maxDate: string = '';
showDateFilters: boolean = false;
// Search subject for debouncing /** DATA LAYERS (do not mix these) */
private searchSubject = new Subject<string>(); allItems: TransactionLog[] = []; // raw API data
private destroy$ = new Subject<void>(); filteredItems: TransactionLog[] = []; // after search
pagedItems: TransactionLog[] = []; // table view
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 = '';
// Validate date range before making API call
if (!this.validateDateRange()) {
this.isLoading = false;
return;
}
// 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) { const { fromDate, toDate } = this.logsSearchForm.value;
params = params.set('toDate', this.toDate);
}
// Add search filter if provided const params = new HttpParams()
if (this.searchText && this.searchText.trim() !== '') { .set('fromDate', this.datePipe.transform(fromDate, 'dd-MM-yyyy')!)
params = params.set('search', this.searchText.trim()); .set('toDate', this.datePipe.transform(toDate, 'dd-MM-yyyy')!);
}
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 (from > to) {
this.errorMessage = 'From date cannot be after To date';
return false;
}
// Optional: Validate date range is not too wide
const diffTime = Math.abs(to.getTime() - from.getTime());
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
if (diffDays > 365) {
this.errorMessage = 'Date range cannot exceed 1 year';
return false;
}
}
return true;
}
// Date filter change handler
onDateFilterChange(): void {
this.currentPage = 1;
this.loadTransactionLogs();
}
// Clear date filters
clearDateFilters(): void {
this.fromDate = '';
this.toDate = '';
this.currentPage = 1;
this.loadTransactionLogs();
}
// Toggle date filter visibility if (!value) {
toggleDateFilters(): void { this.filteredItems = [...this.allItems];
this.showDateFilters = !this.showDateFilters; } else {
} this.filteredItems = this.allItems.filter((item) =>
[
// Apply default date range (Last 7 days) 'logId',
applyLast7Days(): void { 'porOrgacode',
const today = new Date(); 'transactionID',
const lastWeek = new Date(); 'drMbmbkmsnumber',
lastWeek.setDate(today.getDate() - 7); 'crMbmbkmsnumber',
'crPcaglacode',
this.fromDate = formatDate(lastWeek, 'yyyy-MM-dd', 'en-US'); 'drPcaGlacode',
this.toDate = formatDate(today, 'yyyy-MM-dd', 'en-US'); 'amount',
this.onDateFilterChange(); 'paymentMode',
} 'ppmPymdcode',
'sgtGntrdate',
// Apply default date range (Last 30 days) 'channelCode',
applyLast30Days(): void { 'createdAt',
const today = new Date(); 'transactionUri',
const lastMonth = new Date(); 'transactionCode',
lastMonth.setDate(today.getDate() - 30); ].some((key) =>
String((item as any)[key] ?? '')
this.fromDate = formatDate(lastMonth, 'yyyy-MM-dd', 'en-US'); .toLowerCase()
this.toDate = formatDate(today, 'yyyy-MM-dd', 'en-US'); .includes(value),
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.currentPage = 1;
this.loadTransactionLogs(); this.updatePagedItems();
} }
onSearch(value: string): void { updatePagedItems(): void {
this.searchSubject.next(value); const start = (this.currentPage - 1) * this.itemsPerPage;
const end = start + this.itemsPerPage;
this.pagedItems = this.filteredItems.slice(start, end);
} }
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 { itemsPerPageChanged(): void {
// Build export parameters this.currentPage = 1;
let params = new HttpParams(); this.updatePagedItems();
// 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)
get filteredItems(): TransactionLog[] {
return this.allItems;
} }
// 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