From cdec4e63ec8110eaa292d45a34d739930d70ecb5 Mon Sep 17 00:00:00 2001 From: Mazdak Gibran <141390141+mazdakgibran@users.noreply.github.com> Date: Fri, 30 Jan 2026 13:14:14 +0500 Subject: [PATCH] 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. --- .../transaction-logs.component.html | 414 ++++++++---------- .../transaction-logs.component.scss | 144 +----- .../transaction-logs.component.ts | 385 +++++----------- src/assets/i18n/English.json | 3 +- 4 files changed, 303 insertions(+), 643 deletions(-) diff --git a/src/app/transaction-logs/transaction-logs.component.html b/src/app/transaction-logs/transaction-logs.component.html index cd98bc0..e769eea 100644 --- a/src/app/transaction-logs/transaction-logs.component.html +++ b/src/app/transaction-logs/transaction-logs.component.html @@ -1,5 +1,6 @@
+
@@ -7,177 +8,133 @@
- +
+
-
-
-
-
-
-
- {{ "transactionLogs" | translate }} +
+ {{ "transactionLogs" | translate }} +
-
- - - - - -
- -
+
+
+
+ +
+ + +
+
+ {{ 'fieldRequired' | translate }} +
+
- -
-
+
+ +
+ + +
+
+ {{ 'fieldRequired' | translate }} +
+
+ {{ 'toDateInvalidError' | translate }} +
+
+
- -
-
-
- - -
-
- - -
-
-
- -
- - - -
- - - -
-
-
- - -
- - - {{ "activeFilter" | translate }}: - {{ "from" | translate }} {{ fromDate | date }} - {{ "to" | translate }} - {{ toDate | date }} - -
-
+
+ +
+ +
+
+
-
- -
-
- Loading... -
-

{{ "loadingTransactionLogs" | translate }}

- - {{ "filteringByDate" | translate }} - -
+ +
+
+
+
+ {{ "transactionLogDetails" | translate }} - - +
+ - -
- -
{{ "noTransactionLogsFound" | translate }}
-

- {{ "tryAdjustingFilters" | translate }} -

-
+
+ +
+ + +
+
+
+ +
+ +
+ +

{{ "noTransactionLogsFound" | translate }}

+
- -
- - - - + +
+
{{ "logID" | translate }}
+ + + @@ -190,10 +147,11 @@ - - - - {{ log.channelCode || '-' }} - -
{{ "logID" | translate }} {{ "organization" | translate }} {{ "transactionID" | translate }} {{ "drAccount" | translate }}{{ "date" | translate }} {{ "channel" | translate }} {{ "createdAt" | translate }}
{{ log.createdAt | date: 'MMM dd, yyyy HH:mm' }}
+ + - -
- -
- {{ "show" | translate }} -
- - - {{ item.value }} {{ "entries" | translate }} - + +
+ +
+ {{ "show" | translate }} +
+ + + {{ item.value }} {{ "entries" | translate }} + - - {{ item.value }} {{ "entries" | translate }} - - -
-
+ + {{ item.value }} {{ "entries" | translate }} + + +
+
- -
- {{ "page" | translate }} {{ currentPage }} - {{ "of" | translate }} {{ totalPages() }} - - ({{ totalCount }} {{ "totalItems" | translate }}) - - - - -
+ +
+ {{ "page" | translate }} {{ currentPage }} + {{ "of" | translate }} {{ totalPages() }} + + ({{ filteredItems.length }} {{ "totalItems" | translate }}) + +
- -
- - -
-
-
-
-
-
+ +
+ +
@@ -297,14 +245,4 @@
-
- - -
- -

{{ "tableCollapsed" | translate }}

- -
-
\ No newline at end of file +
\ No newline at end of file diff --git a/src/app/transaction-logs/transaction-logs.component.scss b/src/app/transaction-logs/transaction-logs.component.scss index 7767147..a4a46b7 100644 --- a/src/app/transaction-logs/transaction-logs.component.scss +++ b/src/app/transaction-logs/transaction-logs.component.scss @@ -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; } } \ No newline at end of file diff --git a/src/app/transaction-logs/transaction-logs.component.ts b/src/app/transaction-logs/transaction-logs.component.ts index 319a873..665d877 100644 --- a/src/app/transaction-logs/transaction-logs.component.ts +++ b/src/app/transaction-logs/transaction-logs.component.ts @@ -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(); - private destroy$ = new Subject(); - + + /** 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(URIKey.TRANSACTION_LOGS, params) + .requestGET(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(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); -} } \ No newline at end of file diff --git a/src/assets/i18n/English.json b/src/assets/i18n/English.json index 7acb14a..ea317e0 100644 --- a/src/assets/i18n/English.json +++ b/src/assets/i18n/English.json @@ -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" } -- 2.32.0