Merge pull request 'aconnect-UX/1576' (#31) from aconnect-UX/1576 into dev-pending-01-01-2026

Reviewed-on: https://ct.mfsys.com.pk/aConnect/aConnect-UX/pulls/31
aconnect-UX/1942
Naeem Ullah 3 weeks ago
commit 273d3e98be

@ -22,7 +22,7 @@
{{'loggerManager' | translate}}
</div>
<div class="card-body">
<form>
<form [formGroup]="logsSearchForm">
<div class="row g-3 mb-3">
<div class="col-md-6">
<div class="d-flex align-items-center gap-2">
@ -34,14 +34,15 @@
class="password-wrapper position-relative w-100">
<div
class="d-flex flex-row align-items-stretch">
<input type="date" id="fromDate"
class="form-control"
appNoWhitespaces />
<input formControlName="fromDate"
type="date" id="fromDate"
class="form-control" appNoWhitespaces />
</div>
<!-- <div class="text-danger">
{{ 'requiredField' | translate }}
</div> -->
<div class="text-danger"
*ngIf="logsSearchForm.get('fromDate')?.touched && logsSearchForm.get('fromDate')?.invalid">
{{ 'fieldRequired' | translate }}
</div>
</div>
</div>
</div>
@ -54,9 +55,23 @@
<div
class="password-wrapper position-relative w-100">
<input id="toDate" type="date" class="form-control"
maxlength="500"
appNoWhitespaces rows="3" />
<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'])
">
<div
*ngIf="logsSearchForm.get('toDate')?.hasError('required')">
{{ 'fieldRequired' | translate }}
</div>
<div
*ngIf="logsSearchForm.errors?.['toDateInvalid']">
{{ 'toDateInvalidError' | translate }}
</div>
</div>
@ -68,7 +83,8 @@
<div class="row g-3 mb-3">
<div class="col-md-6 ms-auto text-end">
<button
<button [disabled]="logsSearchForm.invalid"
(click)="getlogsData()"
class="btn btn-primary waves-effect waves-light">{{'findLogs'
| translate}}</button>
@ -100,14 +116,14 @@
<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">
<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"
<input type="text" class="form-control form-control-sm" [(ngModel)]="searchText"
placeholder="{{ 'search' | translate }}">
<i class="fas fa-search search-icon"></i>
</div>
<i class="materialdesignicons">
<ng-container *ngIf="renewalDataExpanded; else collapsedIcon">
<i class="materialdesignicons" (click)="toggleTableCard()">
<ng-container *ngIf="logsDataExpanded; else collapsedIcon">
<i class="dripicons-chevron-up float-end"></i>
</ng-container>
<ng-template #collapsedIcon>
@ -116,7 +132,7 @@
</i>
</div>
</div>
<div class="card-body">
<div class="card-body" *ngIf="logsDataExpanded && allItems.length; else noRecordsFound">
<div class="table-responsive">
<table class="table mb-0 border">
<thead class="table-light">
@ -125,43 +141,19 @@
<th>{{'loggingRequestUri' | translate}}</th>
<th>{{'loggingResponseCode' | translate}}</th>
<th>{{'loggingRemoteIP' | translate}}</th>
<th>{{'loggingTimeTaken' | translate}}</th>
<th>{{'loggingDateTime' | translate}}</th>
<th>{{'loggingMethod' | translate}}</th>
<!-- <th>{{'action' | translate}}</th> -->
</tr>
</thead>
<tbody>
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<!-- <td>
<div
class="d-flex justify-content-center gap-2">
<button class="btn btn-info btn-sm"
title="View">
<i class="mdi mdi-eye-outline"></i>
</button>
<button class="btn btn-secondary btn-sm"
title="Edit">
<i class="fas fa-pen"></i>
</button>
<button class="btn btn-danger btn-sm"
title="Delete">
<i class="fas fa-trash-alt"></i>
</button>
</div>
</td> -->
<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>
@ -171,28 +163,39 @@
<ng-select class="form-select-sm"
[items]="pageSizeOptions" bindLabel="label"
bindValue="value" [(ngModel)]="itemsPerPage"
[searchable]="false" [clearable]="false"
[searchable]="false"
(change)="itemsPerPageChanged()" [clearable]="false"
[dropdownPosition]="'top'">
</ng-select>
</div>
<div class="text-muted">
{{ 'page' | translate }} {{ 'of' | translate }} ({{
'totalItems' | translate }})
<div class="text-muted" *ngIf="allItems.length > 1">
{{'page' | translate}} {{currentPage}} {{'of' |
translate}} {{totalPages()}} ({{allItems.length}}
{{'totalItems' | translate}})
</div>
<div class="text-muted" *ngIf="allItems.length === 0">
{{'no_record' | translate}}
</div>
<div class="text-muted" *ngIf="allItems.length === 1">
{{'page' | translate}} {{currentPage}} {{'of' |
translate}} {{totalPages()}} ({{allItems.length}}
{{'record' | translate}})
</div>
<div class="btn-group">
<button
class="btn btn-primary waves-effect waves-light">
<button class="btn btn-primary waves-effect waves-light"
(click)="previousPage()"
[disabled]="currentPage === 1">
{{ 'previous' | translate }}
</button>
<button
class="btn btn-primary waves-effect waves-light">
<button class="btn btn-primary waves-effect waves-light"
(click)="nextPage()"
[disabled]="currentPage >= totalPages()">
{{ 'next' | translate }}
</button>
</div>
</div>
</div>
</div>
</div>
@ -207,3 +210,8 @@
</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,28 +1,116 @@
import { Component } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, FormsModule, ReactiveFormsModule, Validators } from '@angular/forms';
import { NgSelectModule } from '@ng-select/ng-select';
import { TranslateModule } from '@ngx-translate/core';
import { pageSizeOptions } from '../utils/app.constants';
import { CommonModule } from '@angular/common';
import { pageSizeOptions, toDateAfterFromDateValidator } from '../utils/app.constants';
import { CommonModule, DatePipe } from '@angular/common';
import { HttpParams } from '@angular/common/http';
import { URIKey } from '../utils/uri-enums';
import { HttpURIService } from '../app.http.uri.service';
import { LogsManagementResponse } from '../utils/app.interfaces';
import { TableFilterPipe } from '../shared/pipes/table-filter.pipe';
@Component({
selector: 'app-logging',
imports: [TranslateModule, FormsModule, NgSelectModule, CommonModule, ReactiveFormsModule],
imports: [ TranslateModule, FormsModule, NgSelectModule, CommonModule, ReactiveFormsModule,
TableFilterPipe
],
providers: [ DatePipe ],
templateUrl: './logging.component.html',
styleUrl: './logging.component.scss'
})
export class LoggingComponent {
export class LoggingComponent implements OnInit {
currentPage: number = 1;
pageSizeOptions = pageSizeOptions
renewalDataExpanded: boolean = true
itemsPerPage: number = 5;
searchText: any;
toggleCard(arg0: string) {
throw new Error('Method not implemented.');
}
nextPage() {
throw new Error('Method not implemented.');
}
logsForm: any;
pageSizeOptions = pageSizeOptions
logsDataExpanded: boolean = true
itemsPerPage: number = 5;
searchText: any = '';
allItems: any[] = [];
pagedItems: any[] = [];
logsList: LogsManagementResponse[] = [];
logsSearchForm!: FormGroup;
isLoading: boolean = false;
constructor(
private fb: FormBuilder,
private httpService: HttpURIService,
private datePipe: DatePipe
) { }
ngOnInit() {
this.initializeLogsSearchForm();
}
initializeLogsSearchForm() {
this.logsSearchForm = this.fb.group({
fromDate: ['', Validators.required],
toDate: ['', Validators.required]
},
{
validators: toDateAfterFromDateValidator
})
}
getlogsData(){
if(this.logsSearchForm.invalid)
return;
let formValues = this.logsSearchForm.value;
this.isLoading = true;
let fromDateTransformed = this.datePipe.transform(formValues.fromDate, "dd-MM-yyyy");
let toDateTransformed = this.datePipe.transform(formValues.toDate, "dd-MM-yyyy");
const params = new HttpParams()
.set('fromDate', fromDateTransformed!)
.set('toDate', toDateTransformed!);
this.httpService.requestGET<LogsManagementResponse[]>(URIKey.LOGGER_MANAGER_URI, params).subscribe({
next: (response) => {
this.logsList = response;
this.allItems = [...this.logsList];
this.updatePagedItems();
this.isLoading = false;
},
error: (err) => {
console.error('Error fetching BOD data:', err);
this.logsList = [];
this.isLoading = false;
}
});
}
updatePagedItems(): void {
const startIndex = (this.currentPage - 1) * this.itemsPerPage;
const endIndex = startIndex + this.itemsPerPage;
this.pagedItems = this.allItems.slice(startIndex, endIndex);
}
totalPages(): number {
return Math.ceil(this.allItems.length / this.itemsPerPage);
}
previousPage(): void {
if (this.currentPage > 1) {
this.currentPage--;
this.updatePagedItems();
}
}
nextPage(): void {
if (this.currentPage < this.totalPages()) {
this.currentPage++;
this.updatePagedItems();
}
}
itemsPerPageChanged(): void {
this.currentPage = 1;
this.updatePagedItems();
}
toggleTableCard(){
this.logsDataExpanded = !this.logsDataExpanded;
}
}

@ -1,6 +1,6 @@
import { Injectable } from "@angular/core";
import { Observable } from "rxjs";
import { PermissionNode } from "../utils/app.constants";
import { PermissionNode } from "../utils/app.interfaces";
import { HttpURIService } from "../app.http.uri.service";
import { StorageService } from "../shared/services/storage.service";

@ -0,0 +1,25 @@
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'tableFilter',
standalone: true
})
export class TableFilterPipe implements PipeTransform {
transform(items: any[], searchText: string, filterProperties: string[]): any[] {
if (!items) return [];
if (!searchText.trim()) return items;
searchText = searchText.toLowerCase().trim();
return items.filter(item => {
return filterProperties.some(prop => {
const value = this.getNestedProperty(item, prop)?.toString().toLowerCase();
return value?.includes(searchText);
});
});
}
private getNestedProperty(obj: any, path: string): any {
return path.split('.').reduce((o, p) => o?.[p], obj);
}
}

@ -1,6 +1,6 @@
import { Component } from '@angular/core';
import { FormBuilder, FormGroup, ReactiveFormsModule } from '@angular/forms';
import { PermissionNode } from '../utils/app.constants';
import { PermissionNode } from '../utils/app.interfaces';
import { Observable } from 'rxjs';
import { CredentialService } from '../services/credential.service';
import { I18NService } from '../services/i18n.service';

@ -1,3 +1,5 @@
import { AbstractControl, ValidationErrors, ValidatorFn } from "@angular/forms";
export const CONSTANTS = {
POR_ORGACODE: '0005',
};
@ -8,10 +10,17 @@ export const pageSizeOptions = [
{ label: '20 items', value: 20 }
];
export interface PermissionNode {
name: string;
checked: boolean;
expanded: boolean;
children?: PermissionNode[];
buttons?: PermissionNode[];
export const toDateAfterFromDateValidator: ValidatorFn = (
group: AbstractControl
): ValidationErrors | null => {
const fromDate = group.get('fromDate')?.value;
const toDate = group.get('toDate')?.value;
if (!fromDate || !toDate) {
return null;
}
return toDate >= fromDate
? null
: { toDateInvalid: true };
};

@ -0,0 +1,18 @@
export interface PermissionNode {
name: string;
checked: boolean;
expanded: boolean;
children?: PermissionNode[];
buttons?: PermissionNode[];
}
export interface LogsManagementResponse {
id: number,
method: string,
remoteIp: string,
requestBody: string,
requestUri: string,
responseCode: number,
userId: string,
dateTime: string
}

@ -12,5 +12,6 @@ export enum URIKey {
RESET_PASSWORD_URI = "RESET_PASSWORD_URI",
CHANGE_PASSWORD_URI = "CHANGE_PASSWORD_URI",
THIRD_PARTY_REGISTER_URI = "THIRD_PARTY_REGISTER_URI",
TRANSACTION_LOGS = "TRANSACTION_LOGS"
TRANSACTION_LOGS = "TRANSACTION_LOGS",
LOGGER_MANAGER_URI = "LOGGER_MANAGER_URI"
}

@ -71,6 +71,11 @@
"Id": "ENTITY_TRANSACTION_LOGS",
"URI": "/transaction/logs",
"UUID": "TRANSACTION_LOGS"
},
{
"Id": "ENTITY_LOGGER_MANAGER_URI",
"URI": "/logs/getByDate",
"UUID": "LOGGER_MANAGER_URI"
}
]
}

@ -247,6 +247,8 @@
"userCode": "مستخدم",
"choose" : "يختار",
"allow": "يسمح",
"toDateInvalidError": "يجب أن يكون تاريخ اليوم أكبر من أو يساوي تاريخ البداية",
"noLoggingDetailsFound": "لم يتم العثور على تفاصيل التسجيل",
"ERR_SEC_0001": "البريد الإلكتروني موجود بالفعل",
"ERR_SEC_0002": "اسم المستخدم موجود بالفعل",
"ERR_SEC_0003": "كلمة المرور القديمة غير صحيحة"

@ -247,6 +247,8 @@
"userCode": "User",
"choose" : "Choose",
"allow": "Allow",
"toDateInvalidError": "To Date must be greater than or equal to From Date",
"noLoggingDetailsFound": "No Logging Details found",
"ERR_SEC_0001": "Email already exists",
"ERR_SEC_0002": "Username already exists",
"ERR_SEC_0003": "Old Password is not correct"

Loading…
Cancel
Save