Files
cbsnexora/src/app/tasks/checker-inbox-and-tasks-tabs/pending-user-edits/pending-user-edits.component.ts
T

323 lines
8.7 KiB
TypeScript

/**
* Copyright since 2025 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
import { Component, OnInit, inject } from '@angular/core';
import { FormsModule } from '@angular/forms';
import {
MatTable,
MatColumnDef,
MatHeaderCellDef,
MatHeaderCell,
MatCellDef,
MatCell,
MatHeaderRowDef,
MatHeaderRow,
MatRowDef,
MatRow
} from '@angular/material/table';
import { MatPaginatorModule, PageEvent } from '@angular/material/paginator';
import { MatDialog } from '@angular/material/dialog';
import { MatIcon } from '@angular/material/icon';
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
import { UsersService } from 'app/users/users.service';
import { SettingsService } from 'app/settings/settings.service';
import { ConfirmationDialogComponent } from 'app/shared/confirmation-dialog/confirmation-dialog.component';
import { FormDialogComponent } from 'app/shared/form-dialog/form-dialog.component';
import { FormfieldBase } from 'app/shared/form-dialog/formfield/model/formfield-base';
import { InputBase } from 'app/shared/form-dialog/formfield/model/input-base';
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
import {
UserEditCompareDialogComponent,
UserEditCompareRow,
UserEditCompareDialogData
} from './user-edit-compare-dialog/user-edit-compare-dialog.component';
const USER_FIELD_LABELS: Record<string, string> = {
username: 'Username',
firstname: 'First Name',
lastname: 'Last Name',
email: 'Email Address',
mobileNo: 'Mobile Number',
officeId: 'Office',
officeName: 'Office Name',
staffId: 'Staff Member',
roles: 'Assigned Roles',
otpDeliveryMethod: '2FA / OTP Channel',
sendPasswordToEmail: 'Send Password To Email',
passwordNeverExpires: 'Password Never Expires',
password: 'Password'
};
@Component({
selector: 'mifosx-pending-user-edits',
templateUrl: './pending-user-edits.component.html',
styleUrls: ['./pending-user-edits.component.scss'],
imports: [
...STANDALONE_SHARED_IMPORTS,
FormsModule,
FaIconComponent,
MatTable,
MatColumnDef,
MatHeaderCellDef,
MatHeaderCell,
MatCellDef,
MatCell,
MatHeaderRowDef,
MatHeaderRow,
MatRowDef,
MatRow,
MatIcon,
MatPaginatorModule
]
})
export class PendingUserEditsComponent implements OnInit {
private usersService = inject(UsersService);
private settingsService = inject(SettingsService);
private dialog = inject(MatDialog);
requests: any[] = [];
loading = false;
loaded = false;
filterStatus = 'PENDING';
filterUsername = '';
fromDate: Date | null = null;
toDate: Date | null = null;
maxDate = new Date();
activePreset = 'all';
pageIndex = 0;
pageSize = 20;
totalRecords = 0;
displayedColumns = [
'id',
'requestType',
'username',
'fullname',
'email',
'office',
'makerUsername',
'submittedDate',
'status',
'actions'
];
statusOptions = [
{ value: 'PENDING', label: 'Pending Approval' },
{ value: 'APPROVED', label: 'Approved' },
{ value: 'REJECTED', label: 'Rejected' },
{ value: '', label: 'All Statuses' }
];
datePresets = [
{ key: 'today', label: 'Today' },
{ key: '7d', label: 'Last 7 Days' },
{ key: '30d', label: 'Last 30 Days' },
{ key: 'all', label: 'All Dates' }
];
ngOnInit(): void {
this.load();
}
load(): void {
this.loading = true;
const fromStr = this.fromDate ? this.formatIsoDate(this.fromDate) : undefined;
const toStr = this.toDate ? this.formatIsoDate(this.toDate) : undefined;
const offset = this.pageIndex * this.pageSize;
this.usersService
.getPendingUsers(this.filterStatus, this.filterUsername, fromStr, toStr, offset, this.pageSize)
.subscribe({
next: (response: any) => {
this.requests = response.pageItems || [];
this.totalRecords = response.totalFilteredRecords || this.requests.length;
this.loading = false;
this.loaded = true;
},
error: () => {
this.requests = [];
this.loading = false;
this.loaded = true;
}
});
}
onFilterChange(): void {
this.pageIndex = 0;
this.load();
}
applyFilter(query: string): void {
this.filterUsername = query;
this.pageIndex = 0;
this.load();
}
applyPreset(key: string): void {
this.activePreset = key;
const now = new Date();
if (key === 'today') {
this.fromDate = new Date(now.getFullYear(), now.getMonth(), now.getDate());
this.toDate = new Date(now.getFullYear(), now.getMonth(), now.getDate());
} else if (key === '7d') {
const past = new Date();
past.setDate(past.getDate() - 7);
this.fromDate = past;
this.toDate = now;
} else if (key === '30d') {
const past = new Date();
past.setDate(past.getDate() - 30);
this.fromDate = past;
this.toDate = now;
} else {
this.fromDate = null;
this.toDate = null;
}
this.pageIndex = 0;
this.load();
}
onPageChange(event: PageEvent): void {
this.pageIndex = event.pageIndex;
this.pageSize = event.pageSize;
this.load();
}
formatDate(val: any): string {
if (!val) {
return '—';
}
const d = new Date(val);
return isNaN(d.getTime()) ? String(val) : d.toLocaleString();
}
private formatIsoDate(d: Date): string {
const yyyy = d.getFullYear();
const mm = String(d.getMonth() + 1).padStart(2, '0');
const dd = String(d.getDate()).padStart(2, '0');
return `${yyyy}-${mm}-${dd}`;
}
statusClass(status: string): string {
if (status === 'PENDING') {
return 'status-badge--pending';
}
if (status === 'APPROVED') {
return 'status-badge--approved';
}
if (status === 'REJECTED') {
return 'status-badge--rejected';
}
return '';
}
compare(req: any): void {
this.usersService.getPendingUser(req.id).subscribe((detail: any) => {
const rows: UserEditCompareRow[] = [];
const proposed = detail.proposedValues || {};
const original = detail.originalValues || {};
const allKeys = Array.from(
new Set([
...Object.keys(original),
...Object.keys(proposed)
])
);
for (const k of allKeys) {
if (k === 'repeatPassword' || k === 'pendingLockKey' || k === 'id') {
continue;
}
const label = USER_FIELD_LABELS[k] || k;
let curVal = original[k];
let propVal = proposed[k];
if (Array.isArray(curVal)) {
curVal = curVal.join(', ');
}
if (Array.isArray(propVal)) {
propVal = propVal.join(', ');
}
rows.push({
key: k,
label,
current: curVal,
value: propVal
});
}
const data: UserEditCompareDialogData = {
requestId: detail.id,
requestType: detail.requestType,
username: detail.username || req.username,
rows,
status: detail.status,
makerUsername: detail.makerUsername,
submittedDate: this.formatDate(detail.submittedDate),
checkerUsername: detail.checkerUsername,
checkedDate: this.formatDate(detail.checkedDate),
rejectReason: detail.rejectReason
};
this.dialog.open(UserEditCompareDialogComponent, {
data,
width: '650px'
});
});
}
approve(req: any): void {
const actionLabel = req.requestType === 'CREATE_USER' ? 'creation' : 'update';
const dialogRef = this.dialog.open(ConfirmationDialogComponent, {
data: {
heading: `Approve User ${actionLabel}`,
dialogContext: `Are you sure you want to approve this user ${actionLabel} for '${req.username}'?`
}
});
dialogRef.afterClosed().subscribe((confirmed: boolean) => {
if (confirmed) {
this.usersService.approvePendingUser(req.id).subscribe(() => {
this.load();
});
}
});
}
reject(req: any): void {
const formfields: FormfieldBase[] = [
new InputBase({
controlType: 'input',
key: 'reason',
label: 'Rejection Reason',
required: true,
order: 1
})
];
const dialogRef = this.dialog.open(FormDialogComponent, {
data: {
title: `Reject User Request`,
layout: { addButtonText: 'Reject' },
formfields
}
});
dialogRef.afterClosed().subscribe((result: any) => {
if (result && result.data && result.data.reason) {
this.usersService.rejectPendingUser(req.id, result.data.reason).subscribe(() => {
this.load();
});
}
});
}
}