Merge branch 'mazdak/UX-transactionLogs' into PRE-PRODUCTION-2026

PRE-PRODUCTION-2026
Naeem Ullah 5 days ago
commit 16be190777

@ -1,5 +1,6 @@
<div id="layout-wrapper">
<div class="inner-pg-sp">
<div class="container-fluid">
<div class="row">
<div class="col-12">
@ -7,177 +8,133 @@
</div>
</div>
</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="card-header font-edit-13-child mb-3">
{{ "transactionLogs" | translate }}
</div>
<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>
<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>
<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>
<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>
<!-- 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>
<!-- 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 class="text-end">
<button
class="btn btn-primary"
[disabled]="logsSearchForm.invalid"
(click)="getlogsData()"
>
{{ "findLogs" | translate }}
</button>
</div>
</form>
</div>
</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>
<!-- 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 }}
<!-- 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>
<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>
<!-- 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="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>
<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 -->
<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>
</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>
<!-- 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>
<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>
@ -297,14 +245,4 @@
</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>
</div>

@ -1,139 +1,29 @@
// Date filter section
.date-filter-section {
transition: all 0.3s ease;
.date-input-wrapper {
position: relative;
.date-input-group {
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[] = [];
itemsPerPage = 10;
currentPage = 1;
maxDate: string;
searchText = '';
logsDataExpanded = true;
isLoading = false;
errorMessage: string = '';
searchText: string = '';
allItems: TransactionLog[] = [];
transactionDataExpanded: boolean = true;
// 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>();
/** 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();
// 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 { fromDate, toDate } = this.logsSearchForm.value;
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();
this.updatePagedItems();
}
// 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();
}
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);
}
}

@ -313,4 +313,4 @@
"noDataInRange": "No transactions found in the selected date range",
"dateRangeError": "Please select a valid date range",
"exportAllData": "Export All Data"
}
}

Loading…
Cancel
Save