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 class="inner-pg-sp">
<div class="container-fluid">
<div class="row">
<div class="col-12">
@ -9,175 +10,131 @@
</div>
<div class="container-fluid">
<!-- SEARCH FORM -->
<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">
{{ "transactionLogs" | translate }}
<div class="d-flex align-items-center gap-2">
<!-- Date Filter Toggle Button -->
<button
class="btn btn-sm btn-outline-info"
(click)="toggleDateFilters()"
[title]="(showDateFilters ? 'hideDateFilters' : 'showDateFilters') | translate"
>
<i class="fas fa-calendar-alt"></i>
<span class="d-none d-md-inline ms-1">{{ "dateFilter" | translate }}</span>
</button>
<div class="search-box">
<input
type="text"
class="form-control form-control-sm"
[(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">
<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>
<div class="card-header font-edit-13-child mb-3">
{{ "transactionLogs" | translate }}
</div>
<button
class="btn btn-sm btn-outline-secondary"
(click)="toggleTableCard()"
[title]="(transactionDataExpanded ? 'collapse' : 'expand') | translate"
>
<i *ngIf="transactionDataExpanded" class="dripicons-chevron-up"></i>
<i *ngIf="!transactionDataExpanded" class="dripicons-chevron-down"></i>
</button>
</div>
</div>
<form [formGroup]="logsSearchForm">
<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>
<!-- 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 }}
</button>
</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>
<!-- 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>
<div class="text-end">
<button
class="btn btn-primary"
[disabled]="logsSearchForm.invalid"
(click)="getlogsData()"
>
{{ "findLogs" | translate }}
</button>
</div>
</form>
</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>
<!-- 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">
<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="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 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>
<!-- 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>
<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>
<!-- 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>
<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 -->
<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 scope="col">{{ "logID" | translate }}</th>
<!-- TABLE -->
<div *ngIf="filteredItems.length" class="table-responsive">
<table class="table table-bordered mb-0">
<thead class="table-light">
<tr>
<th scope="col">{{ "logID" | translate }}</th>
<th scope="col">{{ "organization" | translate }}</th>
<th scope="col">{{ "transactionID" | translate }}</th>
<th scope="col">{{ "drAccount" | translate }}</th>
@ -190,10 +147,11 @@
<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 pagedItems | tableFilter: searchText: [
</tr>
</thead>
<tbody>
<tr *ngFor="let log of pagedItems | tableFilter: searchText: [
'logId',
'porOrgacode',
'transactionID',
@ -222,73 +180,63 @@
<td>{{ log.channelCode || '-' }}</td>
<td>{{ log.createdAt | date: 'MMM dd, yyyy HH:mm' }}</td>
</tr>
</tbody>
</table>
<!-- 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>
</tbody>
</table>
<!-- 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() }}
<span class="d-none d-md-inline">
({{ totalCount }} {{ "totalItems" | translate }})
</span>
<span *ngIf="fromDate || toDate" class="badge bg-info ms-2">
<i class="fas fa-filter"></i>
</span>
</div>
<!-- Page info -->
<div class="text-muted mb-2 mb-md-0">
{{ "page" | translate }} {{ currentPage }}
{{ "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-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-outline-primary btn-sm"
(click)="nextPage()"
[disabled]="currentPage === totalPages() || isLoading"
>
{{ "next" | translate }}
<i class="fas fa-chevron-right ms-1"></i>
</button>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Pagination buttons -->
<div class="btn-group">
<button
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-outline-primary btn-sm"
(click)="nextPage()"
[disabled]="currentPage >= totalPages()"
>
{{ "next" | translate }}
<i class="fas fa-chevron-right ms-1"></i>
</button>
</div>
</div>
</div>
@ -298,13 +246,3 @@
</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-filter-section {
transition: all 0.3s ease;
.date-input-group {
position: relative;
.date-input-wrapper {
position: relative;
.form-control {
padding-right: 2.5rem;
}
input[type="date"] {
cursor: pointer;
padding-right: 35px;
.date-icon {
&::-webkit-calendar-picker-indicator {
position: absolute;
right: 0.75rem;
top: 50%;
transform: translateY(-50%);
color: #6c757d;
}
}
.quick-filter-btn {
&.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;
}
left: 0;
top: 0;
width: 100%;
height: 100%;
margin: 0;
padding: 0;
cursor: pointer;
opacity: 0;
}
}
}
// Loading spinner
.spinner-border {
width: 3rem;
height: 3rem;
}
// Search box
.search-box {
position: relative;
min-width: 200px;
.search-icon {
.calendar-icon {
position: absolute;
right: 0.75rem;
right: 10px;
top: 50%;
transform: translateY(-50%);
color: #6c757d;
pointer-events: none;
}
.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;
color: #6c757d;
}
}

@ -1,340 +1,171 @@
import { Component, OnInit, OnDestroy } from '@angular/core';
import { CommonModule } from '@angular/common';
import { Component, OnInit } from '@angular/core';
import { CommonModule, DatePipe } from '@angular/common';
import { TranslateModule } from '@ngx-translate/core';
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 { 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 { 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';
import { Subject } from 'rxjs';
import { takeUntil, debounceTime } from 'rxjs/operators';
@Component({
selector: 'app-transaction-logs',
templateUrl: './transaction-logs.component.html',
styleUrls: ['./transaction-logs.component.scss'],
imports: [CommonModule, TranslateModule, NgSelectModule, FormsModule, ReactiveFormsModule, TableFilterPipe]
})
export class TransactionLogsComponent implements OnInit, OnDestroy {
currentPage: number = 1;
totalCount: number = 0;
renewalDataExpanded: boolean = true;
imports: [
TranslateModule,
FormsModule,
NgSelectModule,
CommonModule,
ReactiveFormsModule,
TableFilterPipe
],
providers: [DatePipe]})
export class TransactionLogsComponent implements OnInit {
logsSearchForm!: FormGroup;
pageSizeOptions = pageSizeOptions;
itemsPerPage: number = 10;
transactionLog: TransactionLog[] = [];
isLoading = false;
errorMessage: string = '';
searchText: string = '';
allItems: TransactionLog[] = [];
transactionDataExpanded: boolean = true;
itemsPerPage = 10;
currentPage = 1;
maxDate: string;
searchText = '';
// Date range properties
fromDate: string = '';
toDate: string = '';
maxDate: string = '';
showDateFilters: boolean = false;
logsDataExpanded = true;
isLoading = false;
// Search subject for debouncing
private searchSubject = new Subject<string>();
private destroy$ = new Subject<void>();
/** DATA LAYERS (do not mix these) */
allItems: TransactionLog[] = []; // raw API data
filteredItems: TransactionLog[] = []; // after search
pagedItems: TransactionLog[] = []; // table view
constructor(
private excelExportService: ExcelExportService,
private fb: FormBuilder,
private httpService: HttpURIService,
) {}
ngOnInit(): void {
// Set max date to today
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();
private datePipe: DatePipe,
private excelExportService: ExcelExportService,
) {
this.maxDate = new Date().toISOString().split('T')[0];
}
ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
this.searchSubject.complete();
ngOnInit(): void {
this.logsSearchForm = this.fb.group(
{
fromDate: ['', Validators.required],
toDate: ['', Validators.required],
},
{ validators: toDateAfterFromDateValidator },
);
}
private setupSearchDebounce(): void {
this.searchSubject.pipe(
takeUntil(this.destroy$),
debounceTime(300)
).subscribe((searchValue: string) => {
this.searchText = searchValue;
this.currentPage = 1;
this.loadTransactionLogs();
});
}
getlogsData(): void {
if (this.logsSearchForm.invalid) return;
loadTransactionLogs(): void {
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();
const { fromDate, toDate } = this.logsSearchForm.value;
// 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());
}
const params = new HttpParams()
.set('fromDate', this.datePipe.transform(fromDate, 'dd-MM-yyyy')!)
.set('toDate', this.datePipe.transform(toDate, 'dd-MM-yyyy')!);
this.httpService
.requestGET<any>(URIKey.TRANSACTION_LOGS, params)
.requestGET<TransactionLog[]>(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;
}
next: (response) => {
this.allItems = response ?? [];
this.filteredItems = [...this.allItems];
this.currentPage = 1;
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 = [];
error: () => {
this.allItems = [];
this.totalCount = 0;
this.filteredItems = [];
this.pagedItems = [];
this.isLoading = false;
},
complete: () => {
this.isLoading = false;
}
});
}
private validateDateRange(): boolean {
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 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;
}
/** SEARCH — filters DATA, not DOM */
applySearch(): void {
const value = this.searchText.toLowerCase().trim();
if (!value) {
this.filteredItems = [...this.allItems];
} else {
this.filteredItems = this.allItems.filter((item) =>
[
'logId',
'porOrgacode',
'transactionID',
'drMbmbkmsnumber',
'crMbmbkmsnumber',
'crPcaglacode',
'drPcaGlacode',
'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.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();
}
// 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();
this.updatePagedItems();
}
onSearch(value: string): void {
this.searchSubject.next(value);
updatePagedItems(): void {
const start = (this.currentPage - 1) * this.itemsPerPage;
const end = start + this.itemsPerPage;
this.pagedItems = this.filteredItems.slice(start, end);
}
totalPages(): number {
if (this.totalCount === 0) return 1;
return Math.ceil(this.totalCount / this.itemsPerPage);
}
return Math.ceil(this.filteredItems.length / this.itemsPerPage);
}
previousPage(): void {
if (this.currentPage > 1) {
this.currentPage--;
this.loadTransactionLogs();
this.updatePagedItems();
}
}
nextPage(): void {
if (this.currentPage < this.totalPages()) {
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)
get filteredItems(): TransactionLog[] {
return this.allItems;
itemsPerPageChanged(): void {
this.currentPage = 1;
this.updatePagedItems();
}
// Format date for display
formatDateDisplay(dateString: string): string {
if (!dateString) return '';
return formatDate(new Date(dateString), 'MMM dd, yyyy', 'en-US');
toggleTableCard(): void {
this.logsDataExpanded = !this.logsDataExpanded;
}
// Check if date filter is active
get isDateFilterActive(): boolean {
return !!this.fromDate || !!this.toDate;
exportDataInExcel(): void {
this.excelExportService.exportExcel(
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",
"dateFilter": "Date Filter",
"showDateFilters":"Show Date Filter",
"hideDateFilters":"Hide Date Filter"
"hideDateFilters":"Hide Date Filter",
"transactionLogDetails": "Transaction Logs Details"
}

Loading…
Cancel
Save