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

@ -32,7 +32,6 @@
</div>
<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?.['pattern']">{{"userNamePattterenError" | translate}}</small>
</div>
</div>
<div class="mb-3">

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

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

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

@ -14,7 +14,7 @@ import { HttpErrorResponse, HttpParams } from '@angular/common/http';
import { HttpURIService } from '../../app.http.uri.service';
import { I18NService } from '../../services/i18n.service';
import { SuccessMessages } from '../../utils/enums';
import { ToastrService, ActiveToast, ToastRef } from 'ngx-toastr';
@Component({
@ -125,6 +125,13 @@ export class SetupUserComponent implements OnInit {
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 {
this.getButtonPermissions();
@ -138,8 +145,7 @@ ngOnInit(): void {
],
userFullname: ['', [
Validators.required,
Validators.minLength(5),
Validators.pattern(/^\S+$/)
Validators.minLength(5)
]
],
defaultPassword: ['', [

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

@ -1,18 +1,58 @@
.permission-table td {
vertical-align: middle;
/* Card styling */
.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 {
cursor: pointer;
color: #1e40af;
color: #3b82f6;
transition: transform 0.2s;
}
.expand-icon:hover {
transform: scale(1.2);
}
/* Recursive permission cells */
.permission-cell {
position: relative;
padding-left: calc(var(--level) * 22px + 14px);
font-weight: 500;
background-color: rgba(30, 64, 175, calc(var(--level) * 0.08));
color: #1f2937;
background-color: rgba(59, 130, 246, calc(var(--level) * 0.08));
color: #111827;
font-size: calc(14px - var(--level) * 0.5px);
transition: background-color 0.2s;
}
.permission-cell::before {
@ -25,19 +65,34 @@
background-color: #9ca3af;
}
.permission-cell {
font-size: calc(14px - var(--level) * 0.5px);
/* Scrollable ng-select dropdown */
::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 {
background-color: #e5e7eb;
/* Button styling */
.btn-primary {
background-color: #3b82f6;
border-color: #3b82f6;
font-weight: 500;
transition: background-color 0.2s;
}
.permission-table thead th {
background-color: #e5e7eb;
color: #111827;
.btn-primary:hover {
background-color: #2563eb;
border-color: #2563eb;
}
.permission-table td:last-child {
text-align: center;
/* Responsive adjustments */
@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",
"drAccount": "DR Account",
"crAccount": "CR Account",
"crPcaglacode": "CR GL Account",
"drPcaGlacode": "DR GL Account",
"paymentMode": "Payment Mode",
"channel": "Channel",
"createdAt": "Created At",

Loading…
Cancel
Save