Merge branch 'dev-pending-20-01-2026-naeem' into dev-pending-20-01-2026

mazdak/UX-2123
Naeem Ullah 2 weeks ago
commit 772653b51c

@ -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": {

@ -36,7 +36,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