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">
@ -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