Refactor logging component UI and logic

Simplified and modernized the logging.component.html layout, replacing nested cards and redundant markup with a cleaner structure. Refactored logging.component.ts to improve data flow: search, filtering, and pagination are now handled in the component instead of the template pipe. Updated page size options in app.constants.ts for better usability. Minor cleanup in setup-user.component.ts.
dev-pending-20-01-2026-v1
Naeem Ullah 2 weeks ago
parent efbf56adbc
commit 6df0195ed8

@ -1,224 +1,164 @@
<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"> <!-- SEARCH FORM -->
<div class="col-12"> <div class="col-xl-12 mt-4">
<div class="d-sm-flex align-items-center justify-content-between navbar-header p-0"> <div class="card border">
<div class="card-body">
</div> <div class="card-header font-edit-13-child">
</div> {{ "loggerManager" | translate }}
</div> </div>
<div class="container-fluid">
<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">
{{'loggerManager' | translate}}
</div>
<div class="card-body">
<form [formGroup]="logsSearchForm">
<div class="row g-3 mb-3">
<div class="col-md-6">
<div class="d-flex align-items-center gap-2">
<label for="fromDate" class="text-nowrap">
{{ 'fromDate' | translate }}<span
class="mandatory">*</span>
</label>
<div
class="password-wrapper position-relative w-100">
<div
class="d-flex flex-row align-items-stretch">
<input formControlName="fromDate"
type="date" id="fromDate"
class="form-control" appNoWhitespaces />
</div>
<div class="text-danger"
*ngIf="logsSearchForm.get('fromDate')?.touched && logsSearchForm.get('fromDate')?.invalid">
{{ 'fieldRequired' | translate }}
</div>
</div>
</div>
</div>
<div class="col-md-6">
<div class="d-flex align-items-start gap-2">
<label for="toDate" class="text-nowrap mt-2">
{{ 'toDate' | translate }}<span
class="mandatory">*</span>
</label>
<div
class="password-wrapper position-relative w-100">
<input formControlName="toDate" id="toDate"
type="date" class="form-control"
maxlength="500" appNoWhitespaces rows="3" />
<div class="text-danger" *ngIf="
logsSearchForm.get('toDate')?.touched &&
(logsSearchForm.get('toDate')?.invalid || logsSearchForm.errors?.['toDateInvalid'] || logsSearchForm.errors?.['toDateGreaterThanToday'])
">
<div
*ngIf="logsSearchForm.get('toDate')?.hasError('required')">
{{ 'fieldRequired' | translate }}
</div>
<div
*ngIf="logsSearchForm.errors?.['toDateInvalid']">
{{ 'toDateInvalidError' | translate }}
</div>
<div
*ngIf="logsSearchForm.errors?.['toDateGreaterThanToday']">
{{ 'toDateGreaterThanToday' | translate }}
</div>
</div>
</div>
</div>
</div>
</div>
<div class="row g-3 mb-3">
<div class="col-md-6 ms-auto text-end">
<button [disabled]="logsSearchForm.invalid"
(click)="getlogsData()"
class="btn btn-primary waves-effect waves-light">{{'findLogs'
| translate}}</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>
<input
type="date"
formControlName="fromDate"
class="form-control"
/>
</div>
</form> <div class="col-md-6">
</div> <label>
</div> {{ "toDate" | translate }} <span class="mandatory">*</span>
</div> </label>
</div> <input
</div> type="date"
</div> formControlName="toDate"
</div> class="form-control"
</div> />
</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>
<!-- TABLE -->
<div class="col-xl-12 mt-4">
<div class="card border">
<div
class="card-header d-flex justify-content-between align-items-center"
>
{{ "loggerManagerDetails" | translate }}
<div class="d-flex gap-2" *ngIf="allItems.length">
<input
type="text"
class="form-control form-control-sm"
placeholder="{{ 'search' | translate }}"
[(ngModel)]="searchText"
(ngModelChange)="applySearch()"
/>
<i
class="fa fa-download cursor-pointer"
(click)="exportDataInExcel()"
></i>
<i class="cursor-pointer" (click)="toggleTableCard()">
<i
*ngIf="logsDataExpanded; else down"
class="dripicons-chevron-up"
></i>
<ng-template #down>
<i class="dripicons-chevron-down"></i>
</ng-template>
</i>
</div> </div>
<div class="container-fluid"> </div>
<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">
{{'loggerManagerDetails' | translate}}
<div class="d-flex align-items-center gap-2" *ngIf="allItems.length">
<div class="search-box">
<input type="text" class="form-control form-control-sm" [(ngModel)]="searchText"
placeholder="{{ 'search' | translate }}">
<i class="fas fa-search search-icon"></i>
</div>
<i (click)="exportDataInExcel()" id="downloadReport" class="fa fa-download"></i> <div class="card-body" *ngIf="logsDataExpanded">
<!-- NO RECORDS -->
<i class="materialdesignicons" (click)="toggleTableCard()"> <div *ngIf="!filteredItems.length" class="text-center text-muted">
<ng-container *ngIf="logsDataExpanded; else collapsedIcon"> {{ "noLoggingDetailsFound" | translate }}
<i class="dripicons-chevron-up float-end"></i> </div>
</ng-container>
<ng-template #collapsedIcon>
<i class="dripicons-chevron-down float-end"></i>
</ng-template>
</i>
</div>
</div>
<div class="card-body" *ngIf="logsDataExpanded && allItems.length; else noRecordsFound">
<div class="table-responsive">
<table class="table mb-0 border">
<thead class="table-light">
<tr>
<th>{{'loggingID' | translate}}</th>
<th>{{'loggingRequestUri' | translate}}</th>
<th>{{'loggingResponseCode' | translate}}</th>
<th>{{'loggingRemoteIP' | translate}}</th>
<th>{{'loggingDateTime' | translate}}</th>
<th>{{'loggingMethod' | translate}}</th>
</tr>
</thead>
<tbody>
<tr
*ngFor="let logs of (allItems | tableFilter: searchText: ['id','method','remoteIp','requestBody','requestUri','responseCode','userId','dateTime']).slice((currentPage-1)*itemsPerPage, currentPage*itemsPerPage)">
<td>{{logs?.id}}</td>
<td>{{logs?.requestUri}}</td>
<td>{{logs?.responseCode}}</td>
<td>{{logs?.remoteIp}}</td>
<td>{{logs?.dateTime | date:'dd-MM-yyyy, hh:mm a'}}</td>
<td>{{logs?.method}}</td>
</tr>
</tbody>
</table>
<div
class="d-flex justify-content-between align-items-center mt-3">
<div class="form-group mb-0">
<ng-select class="form-select-sm"
[items]="pageSizeOptions" bindLabel="label"
bindValue="value" [(ngModel)]="itemsPerPage"
[searchable]="false"
(change)="itemsPerPageChanged()" [clearable]="false"
[dropdownPosition]="'top'">
</ng-select>
</div>
<div class="text-muted" *ngIf="allItems.length > 1"> <!-- TABLE -->
{{'page' | translate}} {{currentPage}} {{'of' | <div *ngIf="filteredItems.length" class="table-responsive">
translate}} {{totalPages()}} ({{allItems.length}} <table class="table table-bordered mb-0">
{{'totalItems' | translate}}) <thead class="table-light">
</div> <tr>
<div class="text-muted" *ngIf="allItems.length === 0"> <th>{{ "loggingID" | translate }}</th>
{{'no_record' | translate}} <th>{{ "loggingRequestUri" | translate }}</th>
</div> <th>{{ "loggingResponseCode" | translate }}</th>
<div class="text-muted" *ngIf="allItems.length === 1"> <th>{{ "loggingRemoteIP" | translate }}</th>
{{'page' | translate}} {{currentPage}} {{'of' | <th>{{ "loggingDateTime" | translate }}</th>
translate}} {{totalPages()}} ({{allItems.length}} <th>{{ "loggingMethod" | translate }}</th>
{{'record' | translate}}) </tr>
</div> </thead>
<tbody>
<tr *ngFor="let logs of pagedItems">
<td>{{ logs.id }}</td>
<td>{{ logs.requestUri }}</td>
<td>{{ logs.responseCode }}</td>
<td>{{ logs.remoteIp }}</td>
<td>{{ logs.dateTime | date: "dd-MM-yyyy, hh:mm a" }}</td>
<td>{{ logs.method }}</td>
</tr>
</tbody>
</table>
<!-- FOOTER -->
<div
class="d-flex justify-content-between align-items-center mt-3"
>
<ng-select
[items]="pageSizeOptions"
bindLabel="label"
bindValue="value"
[(ngModel)]="itemsPerPage"
(change)="itemsPerPageChanged()"
[searchable]="false"
[clearable]="false">
</ng-select>
<div class="text-muted">
{{ "page" | translate }} {{ currentPage }}
{{ "of" | translate }} {{ totalPages() }} ({{
filteredItems.length
}}
{{ "totalItems" | translate }})
</div>
<div class="btn-group"> <div class="btn-group">
<button class="btn btn-primary waves-effect waves-light" <button
(click)="previousPage()" class="btn btn-primary"
[disabled]="currentPage === 1"> (click)="previousPage()"
{{ 'previous' | translate }} [disabled]="currentPage === 1"
</button> >
<button class="btn btn-primary waves-effect waves-light" {{ "previous" | translate }}
(click)="nextPage()" </button>
[disabled]="currentPage >= totalPages()">
{{ 'next' | translate }} <button
</button> class="btn btn-primary"
</div> (click)="nextPage()"
</div> [disabled]="currentPage >= totalPages()"
</div> >
</div> {{ "next" | translate }}
</div> </button>
</div>
</div>
</div>
</div>
</div>
</div>
</div> </div>
</div>
</div> </div>
</div>
</div> </div>
</div>
</div> </div>
</div>
</div> </div>
<ng-template #noRecordsFound>
<div *ngIf="!isLoading && logsList.length === 0" class="text-center text-muted mt-3">
<p>{{'noLoggingDetailsFound' | translate}}</p>
</div>
</ng-template>

@ -1,95 +1,139 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, FormsModule, ReactiveFormsModule, Validators } from '@angular/forms'; import {
FormBuilder,
FormGroup,
FormsModule,
ReactiveFormsModule,
Validators,
} from '@angular/forms';
import { NgSelectModule } from '@ng-select/ng-select'; import { NgSelectModule } from '@ng-select/ng-select';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
import { LOGGING_DETAILS_FILE_NAME, pageSizeOptions, toDateAfterFromDateValidator } from '../utils/app.constants'; import {
LOGGING_DETAILS_FILE_NAME,
pageSizeOptions,
toDateAfterFromDateValidator,
} from '../utils/app.constants';
import { CommonModule, DatePipe } from '@angular/common'; import { CommonModule, DatePipe } from '@angular/common';
import { HttpParams } from '@angular/common/http'; import { HttpParams } from '@angular/common/http';
import { URIKey } from '../utils/uri-enums'; import { URIKey } from '../utils/uri-enums';
import { HttpURIService } from '../app.http.uri.service'; import { HttpURIService } from '../app.http.uri.service';
import { LogsManagementResponse } from '../utils/app.interfaces'; import { LogsManagementResponse } from '../utils/app.interfaces';
import { TableFilterPipe } from '../shared/pipes/table-filter.pipe';
import { ExcelExportService } from '../shared/services/excel-export.service'; import { ExcelExportService } from '../shared/services/excel-export.service';
@Component({ @Component({
selector: 'app-logging', selector: 'app-logging',
imports: [ TranslateModule, FormsModule, NgSelectModule, CommonModule, ReactiveFormsModule, standalone: true,
TableFilterPipe imports: [
], TranslateModule,
providers: [ DatePipe ], FormsModule,
NgSelectModule,
CommonModule,
ReactiveFormsModule,
],
providers: [DatePipe],
templateUrl: './logging.component.html', templateUrl: './logging.component.html',
styleUrl: './logging.component.scss' styleUrl: './logging.component.scss',
}) })
export class LoggingComponent implements OnInit { export class LoggingComponent implements OnInit {
currentPage: number = 1;
pageSizeOptions = pageSizeOptions
logsDataExpanded: boolean = true
itemsPerPage: number = 5;
searchText: any = '';
allItems: any[] = [];
pagedItems: any[] = [];
logsList: LogsManagementResponse[] = [];
logsSearchForm!: FormGroup; logsSearchForm!: FormGroup;
isLoading: boolean = false;
pageSizeOptions = pageSizeOptions;
itemsPerPage = 10;
currentPage = 1;
searchText = '';
logsDataExpanded = true;
isLoading = false;
/** DATA LAYERS (do not mix these) */
allItems: LogsManagementResponse[] = []; // raw API data
filteredItems: LogsManagementResponse[] = []; // after search
pagedItems: LogsManagementResponse[] = []; // table view
constructor( constructor(
private fb: FormBuilder, private fb: FormBuilder,
private httpService: HttpURIService, private httpService: HttpURIService,
private datePipe: DatePipe, private datePipe: DatePipe,
private excelExportServic: ExcelExportService private excelExportService: ExcelExportService,
) { } ) {}
ngOnInit() { ngOnInit(): void {
this.initializeLogsSearchForm(); this.logsSearchForm = this.fb.group(
}
initializeLogsSearchForm() {
this.logsSearchForm = this.fb.group({
fromDate: ['', Validators.required],
toDate: ['', Validators.required]
},
{ {
validators: toDateAfterFromDateValidator fromDate: ['', Validators.required],
}) toDate: ['', Validators.required],
},
{ validators: toDateAfterFromDateValidator },
);
} }
getlogsData(){ getlogsData(): void {
if(this.logsSearchForm.invalid) if (this.logsSearchForm.invalid) return;
return;
let formValues = this.logsSearchForm.value;
this.isLoading = true; this.isLoading = true;
let fromDateTransformed = this.datePipe.transform(formValues.fromDate, "dd-MM-yyyy"); const { fromDate, toDate } = this.logsSearchForm.value;
let toDateTransformed = this.datePipe.transform(formValues.toDate, "dd-MM-yyyy");
const params = new HttpParams() const params = new HttpParams()
.set('fromDate', fromDateTransformed!) .set('fromDate', this.datePipe.transform(fromDate, 'dd-MM-yyyy')!)
.set('toDate', toDateTransformed!); .set('toDate', this.datePipe.transform(toDate, 'dd-MM-yyyy')!);
this.httpService.requestGET<LogsManagementResponse[]>(URIKey.LOGGER_MANAGER_URI, params).subscribe({ this.httpService
next: (response) => { .requestGET<LogsManagementResponse[]>(URIKey.LOGGER_MANAGER_URI, params)
this.logsList = response; .subscribe({
this.allItems = [...this.logsList]; next: (response) => {
this.updatePagedItems(); this.allItems = response ?? [];
this.isLoading = false; this.filteredItems = [...this.allItems];
}, this.currentPage = 1;
error: (err) => { this.updatePagedItems();
console.error('Error fetching logging details data:', err); this.isLoading = false;
this.logsList = []; },
this.isLoading = false; error: () => {
} this.allItems = [];
}); this.filteredItems = [];
this.pagedItems = [];
this.isLoading = 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) =>
[
'id',
'method',
'remoteIp',
'requestUri',
'responseCode',
'userId',
'dateTime',
].some((key) =>
String((item as any)[key] ?? '')
.toLowerCase()
.includes(value),
),
);
}
this.currentPage = 1;
this.updatePagedItems();
} }
updatePagedItems(): void { updatePagedItems(): void {
const startIndex = (this.currentPage - 1) * this.itemsPerPage; const start = (this.currentPage - 1) * this.itemsPerPage;
const endIndex = startIndex + this.itemsPerPage; const end = start + this.itemsPerPage;
this.pagedItems = this.allItems.slice(startIndex, endIndex); this.pagedItems = this.filteredItems.slice(start, end);
} }
totalPages(): number { totalPages(): number {
return Math.ceil(this.allItems.length / this.itemsPerPage); return Math.ceil(this.filteredItems.length / this.itemsPerPage);
} }
previousPage(): void { previousPage(): void {
@ -111,13 +155,14 @@ export class LoggingComponent implements OnInit {
this.updatePagedItems(); this.updatePagedItems();
} }
toggleTableCard(){ toggleTableCard(): void {
this.logsDataExpanded = !this.logsDataExpanded; this.logsDataExpanded = !this.logsDataExpanded;
} }
exportDataInExcel(){ exportDataInExcel(): void {
this.excelExportServic.exportExcel(this.logsList, LOGGING_DETAILS_FILE_NAME) this.excelExportService.exportExcel(
this.filteredItems,
LOGGING_DETAILS_FILE_NAME,
);
} }
} }

@ -14,7 +14,6 @@ import { HttpErrorResponse, HttpParams } from '@angular/common/http';
import { HttpURIService } from '../../app.http.uri.service'; import { HttpURIService } from '../../app.http.uri.service';
import { I18NService } from '../../services/i18n.service'; import { I18NService } from '../../services/i18n.service';
import { SuccessMessages } from '../../utils/enums'; import { SuccessMessages } from '../../utils/enums';
import { ToastrService, ActiveToast, ToastRef } from 'ngx-toastr';
@Component({ @Component({

@ -5,9 +5,9 @@ export const CONSTANTS = {
}; };
export const pageSizeOptions = [ export const pageSizeOptions = [
{ label: '5 items', value: 5 }, { value: 10, label: '10 items' },
{ label: '10 items', value: 10 }, { value: 20, label: '20 items' },
{ label: '20 items', value: 20 } { value: 50, label: '50 items' },
]; ];
export const toDateAfterFromDateValidator: ValidatorFn = ( export const toDateAfterFromDateValidator: ValidatorFn = (

Loading…
Cancel
Save