Enhance transaction logs and user permissions UI

Added new fields (crPcaglacode, drPcaGlacode, amount, paymentMode) to the TransactionLog model and updated the transaction logs table to display these fields. Improved user permissions UI with better styling, scrollable tables, and enhanced ng-select options. Updated user setup to use a more descriptive placeholder and added a confirmation dialog for user deletion. Also improved i18n translations for new fields and cleaned up validation and error messages.
dev-pending-20-01-2026-naeem
Naeem Ullah 2 weeks ago
parent fd1b5b1607
commit 69cb52694e

@ -15,7 +15,7 @@
"watch:dev": "ng build --watch --configuration=development", "watch:dev": "ng build --watch --configuration=development",
"watch:test": "ng build --watch --configuration=test", "watch:test": "ng build --watch --configuration=test",
"test": "ng test", "test": "ng test",
"serve:ssr": "node dist/aconnect-ux/server/main.js" "serve:ssr": "node dist/aconnect-ux/server/main.js"
}, },
"private": true, "private": true,
"dependencies": { "dependencies": {

@ -32,7 +32,6 @@
</div> </div>
<div *ngIf="loginForm.get('USER_ID')?.invalid && loginForm.get('USER_ID')?.touched" class="text-danger"> <div *ngIf="loginForm.get('USER_ID')?.invalid && loginForm.get('USER_ID')?.touched" class="text-danger">
<small *ngIf="loginForm.get('USER_ID')?.errors?.['required']">{{"userNameRequired" | translate}}</small> <small *ngIf="loginForm.get('USER_ID')?.errors?.['required']">{{"userNameRequired" | translate}}</small>
<small *ngIf="loginForm.get('USER_ID')?.errors?.['pattern']">{{"userNamePattterenError" | translate}}</small>
</div> </div>
</div> </div>
<div class="mb-3"> <div class="mb-3">

@ -19,6 +19,10 @@ export interface TransactionLog {
transactionID: string; transactionID: string;
drMbmbkmsnumber: string; drMbmbkmsnumber: string;
crMbmbkmsnumber: string; crMbmbkmsnumber: string;
crPcaglacode: string;
drPcaGlacode: string;
amount: number;
paymentMode: string;
ppmPymdcode: string; ppmPymdcode: string;
sgtGntrdate: string; sgtGntrdate: string;
channelCode: string; channelCode: string;

@ -3,9 +3,9 @@
<div class="container-fluid"> <div class="container-fluid">
<div class="row"> <div class="row">
<div class="col-12"> <div class="col-12">
<div class="d-sm-flex align-items-center justify-content-between navbar-header p-0"> <div
class="d-sm-flex align-items-center justify-content-between navbar-header p-0"
</div> ></div>
</div> </div>
</div> </div>
</div> </div>
@ -19,21 +19,40 @@
<div class="col-lg-12"> <div class="col-lg-12">
<div class="card-body mt-2 p-0"> <div class="card-body mt-2 p-0">
<div class="card mb-0 mt-2"> <div class="card mb-0 mt-2">
<div class="card-header font-edit-13-child d-flex justify-content-between align-items-center"> <div
{{'transactionLogs' | translate}} class="card-header font-edit-13-child d-flex justify-content-between align-items-center"
>
{{ "transactionLogs" | translate }}
<div class="d-flex align-items-center gap-2"> <div class="d-flex align-items-center gap-2">
<div class="search-box"> <div class="search-box">
<input type="text" class="form-control form-control-sm" [(ngModel)]="searchText" <input
(ngModelChange)="onSearch($event)" placeholder="{{ 'search' | translate }}"> type="text"
class="form-control form-control-sm"
[(ngModel)]="searchText"
(ngModelChange)="onSearch($event)"
placeholder="{{ 'search' | translate }}"
/>
<i class="fas fa-search search-icon"></i> <i class="fas fa-search search-icon"></i>
</div> </div>
<div class="d-flex align-items-center gap-2"> <div class="d-flex align-items-center gap-2">
<i (click)="exportDataInExcel()" id="downloadReport" class="fa fa-download"></i> <i
</div> (click)="exportDataInExcel()"
id="downloadReport"
class="fa fa-download"
></i>
</div>
<i class="materialdesignicons" (click)="toggleTableCard()"> <i
<ng-container *ngIf="transactionDataExpanded; else collapsedIcon"> class="materialdesignicons"
(click)="toggleTableCard()"
>
<ng-container
*ngIf="
transactionDataExpanded;
else collapsedIcon
"
>
<i class="dripicons-chevron-up float-end"></i> <i class="dripicons-chevron-up float-end"></i>
</ng-container> </ng-container>
<ng-template #collapsedIcon> <ng-template #collapsedIcon>
@ -43,76 +62,136 @@
</div> </div>
</div> </div>
<div
<div class="card-body" *ngIf="transactionDataExpanded && allItems.length; else noRecordsFound"> class="card-body"
*ngIf="
transactionDataExpanded && allItems.length;
else noRecordsFound
"
>
<div *ngIf="isLoading" class="text-center text-muted"> <div *ngIf="isLoading" class="text-center text-muted">
<p>{{'loadingTransactionLogs' | translate}}</p> <p>{{ "loadingTransactionLogs" | translate }}</p>
</div> </div>
<div *ngIf="!isLoading && transactionLog.length === 0" class="text-center text-muted"> <div
<p>{{'noTransactionLogsFound' | translate}}</p> *ngIf="!isLoading && transactionLog.length === 0"
class="text-center text-muted"
>
<p>{{ "noTransactionLogsFound" | translate }}</p>
</div> </div>
<div *ngIf="errorMessage" class="alert alert-danger"> <div *ngIf="errorMessage" class="alert alert-danger">
{{ errorMessage }} {{ errorMessage }}
</div> </div>
<div *ngIf="!isLoading && transactionLog.length > 0" class="table-responsive"> <div
*ngIf="!isLoading && transactionLog.length > 0"
class="table-responsive"
>
<table class="table mb-0 border"> <table class="table mb-0 border">
<thead class="table-light"> <thead class="table-light">
<tr> <tr>
<th>{{'logID' | translate}}</th> <th>{{ "logID" | translate }}</th>
<th>{{'organization' | translate}}</th> <th>{{ "organization" | translate }}</th>
<th>{{'transactionID' | translate}}</th> <th>{{ "transactionID" | translate }}</th>
<th>{{'drAccount' | translate}}</th> <th>{{ "drAccount" | translate }}</th>
<th>{{'crAccount' | translate}}</th> <th>{{ "crAccount" | translate }}</th>
<th>{{'paymentMode' | translate}}</th> <th>{{ "drPcaGlacode" | translate }}</th>
<th>{{'date' | translate}}</th> <th>{{ "crPcaglacode" | translate }}</th>
<th>{{'channel' | translate}}</th> <th>{{ "paymentMode" | translate }}</th>
<th>{{'createdAt' | translate}}</th> <th>{{ "date" | translate }}</th>
<th>{{ "channel" | translate }}</th>
<th>{{ "createdAt" | translate }}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr <tr
*ngFor="let log of (allItems | tableFilter: searchText: ['logID','organization','transactionID','drAccount','crAccount','paymentMode','date','channel','createdAt']).slice((currentPage-1)*itemsPerPage, currentPage*itemsPerPage)"> *ngFor="
let log of (
allItems
| tableFilter
: searchText
: [
'logID',
'organization',
'transactionID',
'drAccount',
'drPcaGlacode',
'crPcaglacode',
'crAccount',
'paymentMode',
'date',
'channel',
'createdAt',
]
).slice(
(currentPage - 1) * itemsPerPage,
currentPage * itemsPerPage
)
"
>
<td>{{ log.logId }}</td> <td>{{ log.logId }}</td>
<td>{{ log.porOrgacode }}</td> <td>{{ log.porOrgacode }}</td>
<td>{{ log.transactionID }}</td> <td>{{ log.transactionID }}</td>
<td>{{ log.drMbmbkmsnumber || '-' }}</td> <td>{{ log.drMbmbkmsnumber || "-" }}</td>
<td>{{ log.crMbmbkmsnumber || '-' }}</td> <td>{{ log.crMbmbkmsnumber || "-" }}</td>
<td>{{ log.drPcaGlacode || "-" }}</td>
<td>{{ log.crPcaglacode || "-" }}</td>
<td>{{ log.ppmPymdcode }}</td> <td>{{ log.ppmPymdcode }}</td>
<td>{{ log.sgtGntrdate | date: 'MMM dd, yyyy' }}</td> <td>
{{ log.sgtGntrdate | date: "MMM dd, yyyy" }}
</td>
<td>{{ log.channelCode }}</td> <td>{{ log.channelCode }}</td>
<td>{{ log.createdAt | date: 'MMM dd, yyyy HH:mm' }}</td> <td>
{{
log.createdAt | date: "MMM dd, yyyy HH:mm"
}}
</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
<div <div
class="d-flex justify-content-between align-items-center mt-3"> 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" <div class="form-group mb-0">
[(ngModel)]="itemsPerPage" (change)="itemsPerPageChanged()" [searchable]="false" [clearable]="false" <ng-select
[dropdownPosition]="'top'"> class="form-select-sm"
</ng-select> [items]="pageSizeOptions"
</div> bindLabel="label"
bindValue="value"
[(ngModel)]="itemsPerPage"
(change)="itemsPerPageChanged()"
[searchable]="false"
[clearable]="false"
[dropdownPosition]="'top'"
>
</ng-select>
</div>
<div class="text-muted"> <div class="text-muted">
{{ 'page' | translate }} {{ currentPage }} {{ 'of' | translate }} {{ totalPages() }} ({{ totalCount }} {{ 'totalItems' | translate }}) {{ "page" | translate }} {{ currentPage }}
</div> {{ "of" | translate }} {{ totalPages() }} ({{
totalCount
}}
{{ "totalItems" | translate }})
</div>
<div class="btn-group"> <div class="btn-group">
<button <button
class="btn btn-primary waves-effect waves-light" (click)="previousPage()"> class="btn btn-primary waves-effect waves-light"
{{ 'previous' | translate }} (click)="previousPage()"
</button> >
<button {{ "previous" | translate }}
class="btn btn-primary waves-effect waves-light" (click)="nextPage()"> </button>
{{ 'next' | translate }} <button
</button> class="btn btn-primary waves-effect waves-light"
</div> (click)="nextPage()"
</div> >
{{ "next" | translate }}
</button>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -127,7 +206,10 @@
</div> </div>
</div> </div>
<ng-template #noRecordsFound> <ng-template #noRecordsFound>
<div *ngIf="!isLoading && allItems.length === 0" class="text-center text-muted mt-3"> <div
<p>{{'noTransactionLogsFound' | translate}}</p> *ngIf="!isLoading && allItems.length === 0"
</div> class="text-center text-muted mt-3"
>
<p>{{ "noTransactionLogsFound" | translate }}</p>
</div>
</ng-template> </ng-template>

@ -80,7 +80,7 @@
formControlName="userFullname" formControlName="userFullname"
name="userFullname" name="userFullname"
maxlength="500" maxlength="500"
placeholder="{{ 'userName' | translate }}" appNoWhitespaces placeholder="{{ 'Full Name' | translate }}"
rows="3" /> rows="3" />
<div class="text-danger" *ngIf="userForm.get('userFullname')?.touched && userForm.get('userFullname')?.invalid"> <div class="text-danger" *ngIf="userForm.get('userFullname')?.touched && userForm.get('userFullname')?.invalid">
@ -98,12 +98,6 @@
"> ">
{{'nameMinLength' | translate }} {{'nameMinLength' | translate }}
</div> </div>
<div class="text-danger" *ngIf="
userForm.get('userFullname')?.errors?.['pattern'] &&
userForm.get('userFullname')?.value
">
{{'emptySpaceRestriction' | translate}}
</div >
</div> </div>
</div> </div>
@ -259,11 +253,10 @@
<button class="btn btn-info btn-sm" title="View" (click)="onView(item.userId)"> <button class="btn btn-info btn-sm" title="View" (click)="onView(item.userId)">
<i class="mdi mdi-eye-outline"></i> <i class="mdi mdi-eye-outline"></i>
</button> </button>
<button *ngIf="buttonPermissions?.delete" class="btn btn-danger btn-sm" title="Delete"
<button *ngIf="buttonPermissions?.delete" class="btn btn-danger btn-sm" title="Delete" (click)="confirmDelete(item.userId)">
(click)="onDelete(item.userId)"> <i class="fas fa-trash-alt"></i>
<i class="fas fa-trash-alt"></i> </button>
</button>
</div> </div>
</td> </td>

@ -14,7 +14,7 @@ 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({
@ -125,6 +125,13 @@ export class SetupUserComponent implements OnInit {
this.userSetupDataExpanded = !this.userSetupDataExpanded; this.userSetupDataExpanded = !this.userSetupDataExpanded;
} }
confirmDelete(userId: string) {
const confirmed = window.confirm('Are you sure you want to delete this user?');
if (confirmed) {
this.onDelete(userId);
}
}
ngOnInit(): void { ngOnInit(): void {
this.getButtonPermissions(); this.getButtonPermissions();
@ -138,8 +145,7 @@ ngOnInit(): void {
], ],
userFullname: ['', [ userFullname: ['', [
Validators.required, Validators.required,
Validators.minLength(5), Validators.minLength(5)
Validators.pattern(/^\S+$/)
] ]
], ],
defaultPassword: ['', [ defaultPassword: ['', [

@ -1,59 +1,60 @@
<div class="page-content"> <div class="page-content">
<div class="container-fluid"> <div class="container-fluid">
<!-- User Selection --> <!-- User Selection Card -->
<div class="card mb-3"> <div class="card shadow-sm mb-4">
<div class="card-body"> <div class="card-body">
<form [formGroup]="permission"> <form [formGroup]="permission" class="row align-items-center">
<div class="row mb-2 align-items-center"> <label class="col-md-2 col-form-label fw-semibold">
<label class="col-sm-2 col-form-label pe-1"> {{ 'userCode' | translate }}
{{ 'userCode' | translate }} </label>
</label>
<div class="col-md-6">
<div class="col-sm-4"> <ng-select
<ng-select class="form-select" formControlName="userCode" [items]="users" bindLabel="userName" bindValue="userId" class="form-select"
placeholder="{{ 'choose' | translate }}" (change)="onUserChange()"> formControlName="userCode"
</ng-select> [items]="users"
bindLabel="userName"
</div> bindValue="userId"
placeholder="{{ 'choose' | translate }}"
(change)="onUserChange()"
[searchable]="true"
[clearable]="true"
[dropdownPosition]="'auto'"
[virtualScroll]="true"
[bufferAmount]="10">
</ng-select>
</div> </div>
</form> </form>
</div> </div>
</div> </div>
<!-- Permission Table --> <!-- Permissions Table Card -->
<div class="card" *ngIf="showPermissions"> <div class="card shadow-sm" *ngIf="showPermissions">
<div class="card-body"> <div class="card-body">
<h4 class="card-title"> <h4 class="card-title mb-3">{{ 'permissions' | translate }}</h4>
{{ 'permissions' | translate }}
</h4> <div class="table-responsive scrollable-table">
<hr> <table class="table table-hover table-bordered table-sm permission-table">
<thead class="table-light">
<table class="table table-bordered table-sm align-middle permission-table"> <tr>
<thead class="table-light"> <th style="width: 40px;"></th>
<tr> <th>{{ 'Permissions' | translate }}</th>
<th style="width: 40px;"></th> <th style="width: 120px;" class="text-center">{{ 'allow' | translate }}</th>
<th>{{ 'Permissions' | translate }}</th> </tr>
<th style="width: 120px;" class="text-center"> </thead>
{{ 'allow' | translate }}
</th> <tbody>
</tr> <ng-container *ngTemplateOutlet="permissionRow; context: { nodes: permissions, level: 0 }"></ng-container>
</thead> </tbody>
</table>
<tbody> </div>
<ng-container
*ngTemplateOutlet="permissionRow; context: { nodes: permissions, level: 0 }">
</ng-container>
</tbody>
</table>
<div class="text-end mt-3"> <div class="text-end mt-3">
<button <button class="btn btn-primary btn-sm px-4" (click)="savePermissions()">
class="btn btn-primary btn-sm px-4"
(click)="savePermissions()">
{{ 'save' | translate }} {{ 'save' | translate }}
</button> </button>
</div> </div>
@ -69,50 +70,30 @@
<ng-container *ngFor="let node of nodes"> <ng-container *ngFor="let node of nodes">
<tr> <tr>
<!-- Expand / Collapse -->
<td class="text-center"> <td class="text-center">
<span <span *ngIf="node.children?.length || node.buttons?.length"
*ngIf="node.children?.length || node.buttons?.length" class="expand-icon"
class="expand-icon" (click)="toggleExpand(node)">
(click)="toggleExpand(node)"> <i class="bx" [ngClass]="node.expanded ? 'bx-chevron-down' : 'bx-chevron-right'"></i>
<i
class="bx"
[ngClass]="node.expanded ? 'bx-chevron-down' : 'bx-chevron-right'">
</i>
</span> </span>
</td> </td>
<!-- Permission Name --> <td class="permission-cell" [style.--level]="level">
<td
class="permission-cell"
[style.--level]="level">
{{ node.name | translate }} {{ node.name | translate }}
</td> </td>
<!-- Checkbox -->
<td class="text-center"> <td class="text-center">
<input <input type="checkbox" [checked]="node.checked" (change)="toggleNode(node, $event)">
type="checkbox"
[checked]="node.checked"
(change)="toggleNode(node, $event)">
</td> </td>
</tr> </tr>
<!-- Children -->
<ng-container *ngIf="node.expanded"> <ng-container *ngIf="node.expanded">
<ng-container *ngIf="node.children?.length"> <ng-container *ngIf="node.children?.length">
<ng-container <ng-container *ngTemplateOutlet="permissionRow; context: { nodes: node.children, level: level + 1 }"></ng-container>
*ngTemplateOutlet="permissionRow; context: { nodes: node.children, level: level + 1 }">
</ng-container>
</ng-container> </ng-container>
<ng-container *ngIf="node.buttons?.length"> <ng-container *ngIf="node.buttons?.length">
<ng-container <ng-container *ngTemplateOutlet="permissionRow; context: { nodes: node.buttons, level: level + 1 }"></ng-container>
*ngTemplateOutlet="permissionRow; context: { nodes: node.buttons, level: level + 1 }">
</ng-container>
</ng-container> </ng-container>
</ng-container> </ng-container>
</ng-container> </ng-container>

@ -1,18 +1,58 @@
.permission-table td { /* Card styling */
vertical-align: middle; .card {
border-radius: 0.5rem;
border: none;
box-shadow: 0 2px 6px rgba(0,0,0,0.08);
} }
/* Scrollable table wrapper */
.scrollable-table {
max-height: 450px; /* adjustable */
overflow-y: auto;
border-radius: 0.5rem;
}
/* Table styles */
.permission-table {
min-width: 100%;
border-radius: 0.5rem;
}
.permission-table thead th {
background-color: #f3f4f6;
color: #1f2937;
font-weight: 600;
border-bottom: 2px solid #e5e7eb;
}
.permission-table tbody tr:hover {
background-color: #eef2ff;
}
.permission-table td:last-child {
text-align: center;
}
/* Expand / collapse icon */
.expand-icon { .expand-icon {
cursor: pointer; cursor: pointer;
color: #1e40af; color: #3b82f6;
transition: transform 0.2s;
}
.expand-icon:hover {
transform: scale(1.2);
} }
/* Recursive permission cells */
.permission-cell { .permission-cell {
position: relative; position: relative;
padding-left: calc(var(--level) * 22px + 14px); padding-left: calc(var(--level) * 22px + 14px);
font-weight: 500; font-weight: 500;
background-color: rgba(30, 64, 175, calc(var(--level) * 0.08)); background-color: rgba(59, 130, 246, calc(var(--level) * 0.08));
color: #1f2937; color: #111827;
font-size: calc(14px - var(--level) * 0.5px);
transition: background-color 0.2s;
} }
.permission-cell::before { .permission-cell::before {
@ -25,19 +65,34 @@
background-color: #9ca3af; background-color: #9ca3af;
} }
.permission-cell { /* Scrollable ng-select dropdown */
font-size: calc(14px - var(--level) * 0.5px); ::ng-deep ng-dropdown-panel {
max-height: 250px;
overflow-y: auto;
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
border-radius: 0.5rem;
} }
.permission-table tbody tr:hover { /* Button styling */
background-color: #e5e7eb; .btn-primary {
background-color: #3b82f6;
border-color: #3b82f6;
font-weight: 500;
transition: background-color 0.2s;
} }
.permission-table thead th { .btn-primary:hover {
background-color: #e5e7eb; background-color: #2563eb;
color: #111827; border-color: #2563eb;
} }
.permission-table td:last-child { /* Responsive adjustments */
text-align: center; @media (max-width: 768px) {
.col-md-6 {
width: 100%;
}
.permission-table td,
.permission-table th {
font-size: 13px;
}
} }

@ -197,6 +197,8 @@
"transactionID": "Transaction ID", "transactionID": "Transaction ID",
"drAccount": "DR Account", "drAccount": "DR Account",
"crAccount": "CR Account", "crAccount": "CR Account",
"crPcaglacode": "CR GL Account",
"drPcaGlacode": "DR GL Account",
"paymentMode": "Payment Mode", "paymentMode": "Payment Mode",
"channel": "Channel", "channel": "Channel",
"createdAt": "Created At", "createdAt": "Created At",

Loading…
Cancel
Save