mazdak/UI-transcation-form #66

Merged
naeem.ullah merged 3 commits from mazdak/UI-transcation-form into FMFI-PRE-PRODUCTION-2026 1 month ago

@ -1,67 +1,299 @@
<div class="page-content">
<div id="layout-wrapper">
<div class="inner-pg-sp">
<div class="container-fluid">
<!-- User Selection Card -->
<div class="card shadow-sm mb-4">
<div class="row">
<div class="col-12">
<div class="d-sm-flex align-items-center justify-content-between navbar-header p-0"></div>
</div>
</div>
<div class="container-fluid">
<div class="col-xl-12 mt-4">
<!-- CARD 1: User Selection + Permissions Table -->
<div class="card border mb-4">
<div class="card-body">
<form [formGroup]="permission" class="row align-items-center">
<label class="col-md-2 col-form-label fw-semibold">
{{ "userCode" | translate }}
</label>
<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]="false" [dropdownPosition]="'auto'" [virtualScroll]="true"
[bufferAmount]="20" appendTo="body">
<!-- Custom template for dropdown options -->
<ng-template ng-option-tmp let-item="item">
<div class="d-flex flex-column">
<span class="fw-medium">{{ item.userName }}</span>
<small class="text-muted">{{ item.userId }}</small>
<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 mb-3">
{{ "userPermission" | translate }}
</div>
<div class="card-body">
<!-- User Selection -->
<div class="card shadow-sm mb-4">
<div class="card-body">
<form [formGroup]="permission" class="row align-items-center">
<label class="col-md-2 col-form-label fw-semibold">
{{ "userCode" | translate }}
</label>
<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]="false" [dropdownPosition]="'auto'"
[virtualScroll]="true" [bufferAmount]="20" appendTo="body">
<ng-template ng-option-tmp let-item="item">
<div class="d-flex flex-column">
<span class="fw-medium">{{ item.userName }}</span>
<small class="text-muted">{{ item.userId }}</small>
</div>
</ng-template>
<ng-template ng-label-tmp let-item="item">
<span>{{ item.userName }} ({{ item.userId }})</span>
</ng-template>
</ng-select>
</div>
</ng-template>
</form>
</div>
</div>
<!-- Permissions Table -->
<div class="card shadow-sm" *ngIf="showPermissions">
<div class="card-body">
<h4 class="card-title mb-3">{{ "menu" | translate }}</h4>
<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: 50px">#</th>
<th>{{ "type" | translate }}</th>
<th style="width: 100px;" class="text-center">{{ "allow" | translate }}</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let item of menuItems; let i = index">
<td class="text-muted">{{ i + 1 }}</td>
<td>{{ item.endpoint | translate }}</td>
<td class="text-center">
<input type="checkbox" [(ngModel)]="item.checked" />
</td>
</tr>
</tbody>
</table>
</div>
<div class="text-end mt-3">
<button class="btn btn-primary btn-sm px-4" (click)="savePermissions()">
{{ "save" | translate }}
</button>
</div>
</div>
</div>
<!-- Optional: Custom template for selected item -->
<ng-template ng-label-tmp let-item="item">
<span>{{ item.userName }} ({{ item.userId }})</span>
</ng-template>
</ng-select>
</div>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="card shadow-sm" *ngIf="showPermissions">
<!-- CARD 2: Create Third Party User Form -->
<div class="card border">
<div class="card-body">
<h4 class="card-title mb-3">{{ "menu" | translate }}</h4>
<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 mb-3">
{{ "createThirdPartyUser" | translate }}
</div>
<div class="card-body">
<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: 50px">#</th>
<th>{{ "type" | translate }}</th>
<th style="width: 100px;" class="text-center">{{ "allow" | translate }}</th>
</tr>
</thead>
<form [formGroup]="thirdPartyForm" autocomplete="off">
<tbody>
<div class="row g-3 mb-3">
<!-- SUS_USERCODE -->
<div class="col-md-6">
<div class="d-flex align-items-center gap-2">
<label for="SUS_USERCODE" class="text-nowrap">
{{ "SUS_USERCODE" | translate }}<span class="mandatory">*</span>
</label>
<div class="position-relative w-100">
<input type="text" id="SUS_USERCODE" class="form-control"
formControlName="SUS_USERCODE"
placeholder="{{ 'SUS_USERCODE' | translate }}"
/>
<div class="text-danger"
*ngIf="thirdPartyForm.get('SUS_USERCODE')?.touched && thirdPartyForm.get('SUS_USERCODE')?.invalid">
<div *ngIf="thirdPartyForm.get('SUS_USERCODE')?.errors?.['required']">
{{ "fieldRequired" | translate }}
</div>
</div>
</div>
</div>
</div>
<tr *ngFor="let item of menuItems; let i = index">
<td class="text-muted">{{ i + 1 }}</td>
<td>{{ item.endpoint | translate }}</td>
<td class="text-center"><input type="checkbox"
[(ngModel)]="item.checked" /></td>
</tr>
<!-- SUS_EMPCODE -->
<div class="col-md-6">
<div class="d-flex align-items-center gap-2">
<label for="SUS_EMPCODE" class="text-nowrap">
{{ "SUS_EMPCODE" | translate }}<span class="mandatory">*</span>
</label>
<div class="position-relative w-100">
<input type="text" id="SUS_EMPCODE" class="form-control"
formControlName="SUS_EMPCODE"
placeholder="{{ 'SUS_EMPCODE' | translate }}"
appNoWhitespaces />
<div class="text-danger"
*ngIf="thirdPartyForm.get('SUS_EMPCODE')?.touched && thirdPartyForm.get('SUS_EMPCODE')?.invalid">
<div *ngIf="thirdPartyForm.get('SUS_EMPCODE')?.errors?.['required']">
{{ "fieldRequired" | translate }}
</div>
</div>
</div>
</div>
</div>
</div>
</tbody>
</table>
</div>
<div class="row g-3 mb-3">
<!-- SUS_NAME -->
<div class="col-md-6">
<div class="d-flex align-items-center gap-2">
<label for="SUS_NAME" class="text-nowrap">
{{ "SUS_NAME" | translate }}<span class="mandatory">*</span>
</label>
<div class="position-relative w-100">
<input type="text" id="SUS_NAME" class="form-control"
formControlName="SUS_NAME"
placeholder="{{ 'SUS_NAME' | translate }}"
appNoWhitespaces />
<div class="text-danger"
*ngIf="thirdPartyForm.get('SUS_NAME')?.touched && thirdPartyForm.get('SUS_NAME')?.invalid">
<div *ngIf="thirdPartyForm.get('SUS_NAME')?.errors?.['required']">
{{ "fieldRequired" | translate }}
</div>
</div>
</div>
</div>
</div>
<!-- SUS_PASSWORD -->
<div class="col-md-6">
<div class="d-flex align-items-start gap-2">
<label for="SUS_PASSWORD" class="text-nowrap">
{{ "SUS_PASSWORD" | translate }}<span class="mandatory">*</span>
</label>
<div class="w-100">
<div class="password-wrapper">
<input [type]="passwordType" id="SUS_PASSWORD" class="form-control" formControlName="SUS_PASSWORD"
placeholder="{{ 'SUS_PASSWORD' | translate }}" appNoWhitespaces />
<app-password-hide-show #passShowMenu class="password-eye align-items" [showPassword]="true"
(onEyeClick)="toggleSetupPass()"></app-password-hide-show>
</div>
<div class="text-danger"
*ngIf="thirdPartyForm.get('SUS_PASSWORD')?.touched && thirdPartyForm.get('SUS_PASSWORD')?.invalid">
<div *ngIf="thirdPartyForm.get('SUS_PASSWORD')?.errors?.['required']">
{{ "fieldRequired" | translate }}
</div>
<div *ngIf="thirdPartyForm.get('SUS_PASSWORD')?.errors?.['pattern']">
{{ "passwordPattern" | translate }}
</div>
</div>
</div>
</div>
</div>
</div>
<div class="row g-3 mb-3">
<!-- SUS_EMAIL -->
<div class="col-md-6">
<div class="d-flex align-items-center gap-2">
<label for="SUS_EMAIL" class="text-nowrap">
{{ "SUS_EMAIL" | translate }}<span class="mandatory">*</span>
</label>
<div class="position-relative w-100">
<input type="email" id="SUS_EMAIL" class="form-control"
formControlName="SUS_EMAIL"
placeholder="{{ 'SUS_EMAIL' | translate }}"
appNoWhitespaces />
<div class="text-danger"
*ngIf="thirdPartyForm.get('SUS_EMAIL')?.touched && thirdPartyForm.get('SUS_EMAIL')?.invalid">
<div *ngIf="thirdPartyForm.get('SUS_EMAIL')?.errors?.['required']">
{{ "fieldRequired" | translate }}
</div>
<div *ngIf="thirdPartyForm.get('SUS_EMAIL')?.errors?.['email']">
{{ "invalidEmail" | translate }}
</div>
</div>
</div>
</div>
</div>
<!-- SUS_USERCELLNO -->
<div class="col-md-6">
<div class="d-flex align-items-center gap-2">
<label for="SUS_USERCELLNO" class="text-nowrap">
{{ "SUS_USERCELLNO" | translate }}<span class="mandatory">*</span>
</label>
<div class="position-relative w-100">
<input type="tel" id="SUS_USERCELLNO" class="form-control"
formControlName="SUS_USERCELLNO"
placeholder="{{ 'SUS_USERCELLNO' | translate }}"
(keypress)="onlyNumbers($event)"
maxlength="10"
appNoWhitespaces />
<div class="text-danger"
*ngIf="thirdPartyForm.get('SUS_USERCELLNO')?.touched && thirdPartyForm.get('SUS_USERCELLNO')?.invalid">
<div *ngIf="thirdPartyForm.get('SUS_USERCELLNO')?.errors?.['required']">
{{ "fieldRequired" | translate }}
</div>
</div>
</div>
</div>
</div>
</div>
<div class="row g-3 mb-3">
<!-- SUS_ACTIVE -->
<div class="col-md-6">
<div class="d-flex align-items-center gap-2">
<input type="checkbox" id="SUS_ACTIVE" class="form-check-input"
formControlName="SUS_ACTIVE" />
<label for="SUS_ACTIVE" class="form-check-label">
{{ "SUS_ACTIVE" | translate }}
</label>
</div>
</div>
<div class="text-end mt-3">
<button class="btn btn-primary btn-sm px-4" (click)="savePermissions()">
{{ "save" | translate }}
</button>
<!-- SUS_THIRDPARTY -->
<div class="col-md-6">
<div class="d-flex align-items-center gap-2">
<input type="checkbox" id="SUS_THIRDPARTY" class="form-check-input"
formControlName="SUS_THIRDPARTY" />
<label for="SUS_THIRDPARTY" class="form-check-label">
{{ "SUS_THIRDPARTY" | translate }}
</label>
</div>
</div>
</div>
<div class="text-end mt-3">
<button class="btn btn-primary btn-sm px-4" (click)="submitForm()">
{{ "save" | translate }}
</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>

@ -1,5 +1,5 @@
import { Component } from '@angular/core';
import { FormBuilder, FormGroup, FormsModule, ReactiveFormsModule } from '@angular/forms';
import { Component, ViewChild } from '@angular/core';
import { FormBuilder, FormGroup, FormsModule, ReactiveFormsModule, Validators } from '@angular/forms';
import { PermissionNode } from '../utils/app.interfaces';
import { CredentialService } from '../services/credential.service';
import { I18NService } from '../services/i18n.service';
@ -10,22 +10,27 @@ import { HttpErrorResponse, HttpParams } from '@angular/common/http';
import { CommonModule } from '@angular/common';
import { NgSelectModule } from '@ng-select/ng-select';
import { filter, Observable, take } from 'rxjs';
import { SuccessMessages } from '../utils/enums';
import { ErrorMessages, SuccessMessages } from '../utils/enums';
import { URIService } from '../app.uri';
import { PasswordHideShowComponent } from '../shared/components/password-hide-show/password-hide-show.component';
import { error } from 'console';
@Component({
selector: 'app-menu',
imports: [TranslateModule, ReactiveFormsModule, CommonModule, TranslateModule, NgSelectModule, FormsModule],
imports: [TranslateModule, ReactiveFormsModule, ReactiveFormsModule, CommonModule, TranslateModule, NgSelectModule, FormsModule, PasswordHideShowComponent],
templateUrl: './menu.component.html',
styleUrl: './menu.component.scss'
})
export class MenuComponent {
users: any[] = [];
thirdPartyForm!: FormGroup;
permission: FormGroup;
showPermissions = false;
permissions: PermissionNode[] = [];
saving = false;
passwordType: string = 'password';
menuItems: { endpoint: string; checked: boolean }[] = [];
@ViewChild('passShowMenu') passwordHideShow?: PasswordHideShowComponent;
constructor(
private credentialService: CredentialService,
@ -38,13 +43,104 @@ export class MenuComponent {
this.defaultPermissions().subscribe((data: PermissionNode[]) => {
this.permissions = data;
});
}
ngOnInit() {
this.getAllUsers();
this.loadTransactionEndpoints();
this.createThirdPartyUserForm();
}
toggleSetupPass() {
this.passwordType = this.passwordHideShow?.showPassword ? 'password' : 'text';
}
createThirdPartyUserForm(): void{
this.thirdPartyForm = this.fb.group({
SUS_USERCODE: ["", [Validators.required]],
SUS_EMPCODE: ["", [Validators.required]],
SUS_NAME: ["", [Validators.required]],
SUS_PASSWORD: ['', [
Validators.required,
Validators.minLength(8),
Validators.maxLength(20),
Validators.pattern(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]+$/)
]],
SUS_EMAIL: ["", [Validators.required, Validators.email]],
SUS_USERCELLNO: ["", [
Validators.required,
]],
POR_ORGACODE: [this.credentialService.getPorOrgacode()],
SUS_ACTIVE: [true],
SUS_THIRDPARTY: [true],
})
}
onlyNumbers(event: KeyboardEvent) {
const charCode = event.which ? event.which : event.keyCode;
if (charCode < 48 || charCode > 57) {
event.preventDefault();
}
}
submitForm() {
if (this.thirdPartyForm.invalid) {
this.thirdPartyForm.markAllAsTouched();
return;
}
const f = this.thirdPartyForm.value;
const payload = {
formId: "SH_SM_US_USER",
postProcessFormId: "SH_SM_US_USER",
workFlowId: null,
operation: "nonwizard",
porOrgacode: this.credentialService.getPorOrgacode(),
usercode: this.credentialService.getUserId(),
uniqueConstraints: [["SUS_USERCODE", "POR_ORGACODE"]],
formCounters: [],
susUsercode: f.SUS_USERCODE,
susName: f.SUS_NAME,
susEmpcode: f.SUS_EMPCODE,
susPassword: f.SUS_PASSWORD,
susEmail: f.SUS_EMAIL,
susUsercellno: f.SUS_USERCELLNO,
susActive: f.SUS_ACTIVE,
susThirdparty: f.SUS_THIRDPARTY,
}
console.log("payload",payload )
this.httpService.requestPOST(URIKey.CREATE_THIRD_PARTY_USER, payload)
.subscribe({
next: (response: any) => {
if (!(response instanceof HttpErrorResponse)) {
this.i18nService.success(SuccessMessages.SAVED_SUCCESSFULLY, []);
this.createThirdPartyUserForm();
this.getAllUsers();
this.permission.reset();
this.showPermissions = false;
}
},
error: (error: any) => {
const errorCode = error?.error?.errorCode || error?.errorCode;
switch (errorCode) {
case 'ERR_SEC_0002':
this.i18nService.error(ErrorMessages.USERNAME_ALREADY_EXISTS, []);
break;
case 'ERR_SEC_0001':
this.i18nService.error(ErrorMessages.EMAIL_ALREADY_EXISTS, []);
break;
default:
this.i18nService.error('Unexpected error', []);
}
}
});
}
loadTransactionEndpoints() {
this.httpService.requestGET<string[]>(URIKey.TRANSACTION_ENDPOINTS).subscribe((response) => {
if (!(response instanceof HttpErrorResponse)) {
@ -127,7 +223,9 @@ savePermissions() {
next: (response: any) => {
if (!(response instanceof HttpErrorResponse)) {
this.i18nService.success(SuccessMessages.SAVED_SUCCESSFULLY, []);
this.onUserChange(); // this triggers one GET — expected
this.permission.reset();
this.showPermissions = false;
this.menuItems.forEach(item => item.checked = false);
}
},
complete: () => {

@ -73,7 +73,7 @@
</div>
<div class="row g-3 mb-3">
<div class="col-md-6">
<div class="d-flex align-items-center gap-2">
<div class="d-flex align-items-start gap-2">
<label for="confirmPassword" class="text-nowrap">
{{ 'confirmPassword' | translate }}<span
class="mandatory">*</span>

@ -189,11 +189,12 @@
<!-- Password -->
<!-- Password field (only show in create mode) -->
<div class="col-md-6" *ngIf="mode === 'create'">
<div class="d-flex align-items-center gap-2">
<div class="d-flex align-items-start gap-2">
<label for="defaultPassword" class="text-nowrap">
{{ "password" | translate }}<span class="mandatory">*</span>
</label>
<div class="password-wrapper position-relative w-100">
<div class="w-100">
<div class="password-wrapper">
<input
id="defaultPassword"
[type]="passwordType"
@ -209,6 +210,7 @@
[showPassword]="true"
(onEyeClick)="toggleSetupPass()"
></app-password-hide-show>
</div>
<div
class="text-danger"
*ngIf="userForm.get('defaultPassword')?.touched && userForm.get('defaultPassword')?.invalid"

@ -12,6 +12,8 @@ export enum ErrorMessages{
USER_FETCH_FAILED = "USER_FETCH_FAILED",
RESET_PASSWORD_FAILED = "RESET_PASSWORD_FAILED",
CHANGE_PASSWORD_FAILED = "ERR_SEC_0007",
EMAIL_ALREADY_EXISTS = "ERR_SEC_0001",
USERNAME_ALREADY_EXISTS = "ERR_SEC_0002"
}

@ -22,7 +22,8 @@ export enum URIKey {
GL_TO_ACCOUNT = "GL_TO_ACCOUNT",
TRANSACTION_PERMISSIONS_ASSIGN = "TRANSACTION_PERMISSIONS_ASSIGN",
TRANSACTION_PERMISSIONS = "TRANSACTION_PERMISSIONS",
TRANSACTION_ENDPOINTS = "TRANSACTION_ENDPOINTS"
TRANSACTION_ENDPOINTS = "TRANSACTION_ENDPOINTS",
CREATE_THIRD_PARTY_USER = "CREATE_THIRD_PARTY_USER"
}

@ -121,6 +121,11 @@
"Id": "ENTITY_TRANSACTION_ENDPOINTS",
"URI": "/transaction-permissions/endpoints",
"UUID": "TRANSACTION_ENDPOINTS"
},
{
"Id": "ENTITY_CREATE_THIRD_PARTY_USER",
"URI": "/createThirdPartyUser",
"UUID": "CREATE_THIRD_PARTY_USER"
}
]
}

@ -339,5 +339,15 @@
"seventeen": "١٧",
"eighteen": "١٨",
"nineteen": "١٩",
"twenty": "٢٠"
"twenty": "٢٠",
"SUS_USERCODE": "رمز المستخدم",
"SUS_EMPCODE": "رمز الموظف",
"SUS_NAME": "الاسم الكامل",
"SUS_PASSWORD": "كلمة المرور",
"SUS_EMAIL": "البريد الإلكتروني",
"SUS_USERCELLNO": "رقم الجوال",
"SUS_ACTIVE": "الحساب نشط",
"SUS_THIRDPARTY": "طرف ثالث",
"createThirdPartyUser": "إنشاء مستخدم طرف ثالث",
"userPermission": "صلاحية المستخدم"
}

@ -254,7 +254,7 @@
"noThirdPartyRegFound": "No Third Party Registration Details Found",
"ERR_SEC_0001": "Email already exists",
"ERR_SEC_0007": "New password cannot be same as old password",
"ERR_SEC_0002": "User ID already exists",
"ERR_SEC_0002": "Username already exists",
"ERR_SEC_0003": "Old Password is not correct",
"ERR_SEC_0004": "Invalid credentials",
"ERR_SEC_0005": "User not found",
@ -321,7 +321,7 @@
"GL_TO_GL": "General Ledger to General Ledger",
"ACCOUNT_TO_GL": "Account to General Ledger",
"GL_TO_ACCOUNT": "General Ledger to Account",
"one": "1",
"one": "1",
"two": "2",
"three": "3",
"four": "4",
@ -340,5 +340,15 @@
"seventeen": "17",
"eighteen": "18",
"nineteen": "19",
"twenty": "20"
"twenty": "20",
"SUS_USERCODE": "User Code",
"SUS_EMPCODE": "Employee Code",
"SUS_NAME": "Full Name",
"SUS_PASSWORD": "Password",
"SUS_EMAIL": "Email",
"SUS_USERCELLNO": "Cell Number",
"SUS_ACTIVE": "Account Active",
"SUS_THIRDPARTY": "Third Party",
"createThirdPartyUser": "Create Third Party User",
"userPermission": "User Permission"
}
Loading…
Cancel
Save