Revert demo to pre-2026-08-06 state (rollback today's push)
Reverts all commits pushed to demo today (a5124ca3,858c277a,a1a50ab8,f72abb09,fcf66657) back tof8c532f7as a single forward commit, without rewriting history. All reverted content (Share Withdrawal/Closure feature, TDS reports, KYC exception tracking, client-edit-email-dialog, form121 tab, search-select) remains intact on dev and was verified identical before this revert.
This commit is contained in:
-77
@@ -1,77 +0,0 @@
|
||||
<!--
|
||||
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/.
|
||||
-->
|
||||
|
||||
<h1 mat-dialog-title class="ced-title">
|
||||
<span class="ced-title__icon">✉</span>
|
||||
Approval Email
|
||||
|
||||
<div class="ced-status-row">
|
||||
@if (data.emailSent) {
|
||||
<span class="ced-chip ced-chip--sent"> <span class="ced-chip__dot"></span>Sent </span>
|
||||
} @else {
|
||||
<span class="ced-chip ced-chip--notsent"> <span class="ced-chip__dot"></span>Not sent </span>
|
||||
}
|
||||
|
||||
@if (data.emailOpenedAt) {
|
||||
<span class="ced-chip ced-chip--seen"> <fa-icon icon="eye" class="ced-chip__icon"></fa-icon>Seen </span>
|
||||
} @else {
|
||||
<span class="ced-chip ced-chip--notseen">
|
||||
<fa-icon icon="eye-slash" class="ced-chip__icon"></fa-icon>Not yet seen
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
</h1>
|
||||
|
||||
<div mat-dialog-content class="ced-content">
|
||||
<!-- Classic Email Header (Gmail Style) -->
|
||||
<div class="ced-gmail-header">
|
||||
<h2 class="ced-gmail-subject">{{ data.emailSubject ?? '(No Subject)' }}</h2>
|
||||
|
||||
<div class="ced-gmail-meta">
|
||||
<div class="ced-gmail-avatar">
|
||||
<div class="ced-gmail-avatar-letter">C</div>
|
||||
</div>
|
||||
|
||||
<div class="ced-gmail-meta-content">
|
||||
<div class="ced-gmail-meta-top">
|
||||
<span class="ced-gmail-sender-name">CBSNexora</span>
|
||||
<span class="ced-gmail-sender-email"><{{ data.emailFrom ?? 'noreply@cbsnexora.in' }}></span>
|
||||
<span class="ced-gmail-spacer"></span>
|
||||
<span class="ced-gmail-date">{{ data.emailSentAt ? formatDate(data.emailSentAt) : '—' }}</span>
|
||||
</div>
|
||||
<div class="ced-gmail-meta-bottom">
|
||||
<span class="ced-gmail-to">
|
||||
to
|
||||
@if (data.emailTo) {
|
||||
{{ data.emailTo }}
|
||||
} @else {
|
||||
<span style="font-style: italic; color: #d32f2f">No email on file</span>
|
||||
}
|
||||
</span>
|
||||
<fa-icon icon="caret-down" class="ced-gmail-caret"></fa-icon>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- HTML email body -->
|
||||
@if (data.emailBody) {
|
||||
<div class="ced-email-body" [innerHTML]="safeBody"></div>
|
||||
} @else {
|
||||
<div class="ced-empty">
|
||||
<p>No email content available.</p>
|
||||
@if (!data.emailSent) {
|
||||
<p class="ced-empty__hint">The email has not been sent yet for this edit.</p>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<mat-dialog-actions align="end">
|
||||
<button mat-raised-button color="primary" mat-dialog-close>Close</button>
|
||||
</mat-dialog-actions>
|
||||
-234
@@ -1,234 +0,0 @@
|
||||
/**
|
||||
* 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/.
|
||||
*/
|
||||
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.ced-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
font-size: 1.15rem;
|
||||
font-weight: 600;
|
||||
color: #1a237e;
|
||||
position: relative; /* For aligning badges to the right */
|
||||
|
||||
&__icon {
|
||||
font-size: 1.3rem;
|
||||
}
|
||||
}
|
||||
|
||||
.ced-content {
|
||||
min-width: 560px;
|
||||
max-width: 760px;
|
||||
max-height: 72vh;
|
||||
overflow-y: auto;
|
||||
padding: 0 4px;
|
||||
}
|
||||
|
||||
/* ── Gmail-Style Email Header ─────────────────────────────────────────────────── */
|
||||
.ced-gmail-header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-bottom: 30px;
|
||||
padding: 0 4px;
|
||||
font-family: Roboto, Inter, sans-serif; /* Gmail-like typography */
|
||||
}
|
||||
|
||||
.ced-gmail-subject {
|
||||
font-size: 1.35rem;
|
||||
font-weight: 400;
|
||||
color: #202124;
|
||||
margin: 0 0 24px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.ced-gmail-meta {
|
||||
display: flex;
|
||||
align-items: center; /* Avatar vertically centered with text */
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.ced-gmail-avatar {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
background-color: #f06292;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.ced-gmail-avatar-letter {
|
||||
color: white;
|
||||
font-size: 1.25rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.ced-gmail-meta-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
justify-content: center;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.ced-gmail-meta-top {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.ced-gmail-spacer {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.ced-gmail-sender-name {
|
||||
font-size: 0.95rem;
|
||||
font-weight: 600;
|
||||
color: #202124;
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
.ced-gmail-sender-email {
|
||||
color: #5f6368;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.ced-gmail-date {
|
||||
color: #5f6368;
|
||||
font-size: 0.75rem;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.ced-gmail-meta-bottom {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.ced-gmail-to {
|
||||
font-size: 0.75rem;
|
||||
color: #5f6368;
|
||||
}
|
||||
|
||||
.ced-gmail-caret {
|
||||
font-size: 0.75rem;
|
||||
color: #5f6368;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.ced-status-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
margin-left: auto; /* Pushes badges to the right in the flex container */
|
||||
}
|
||||
|
||||
/* ── Status chips ───────────────────────────────────────────────────────── */
|
||||
.ced-chip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 4px 12px 4px 10px;
|
||||
border-radius: 20px;
|
||||
font-size: 0.82rem;
|
||||
font-weight: 500;
|
||||
|
||||
&__dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
&__icon {
|
||||
font-size: 0.82rem;
|
||||
}
|
||||
|
||||
&--sent {
|
||||
background: #e8f5e9;
|
||||
color: #1b5e20;
|
||||
|
||||
.ced-chip__dot {
|
||||
background: #43a047;
|
||||
}
|
||||
}
|
||||
|
||||
&--notsent {
|
||||
background: #f5f5f5;
|
||||
color: #757575;
|
||||
|
||||
.ced-chip__dot {
|
||||
background: #bdbdbd;
|
||||
}
|
||||
}
|
||||
|
||||
&--seen {
|
||||
background: #e3f2fd;
|
||||
color: #0d47a1;
|
||||
}
|
||||
|
||||
&--notseen {
|
||||
background: #f5f5f5;
|
||||
color: #9e9e9e;
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Divider ────────────────────────────────────────────────────────────── */
|
||||
.ced-divider {
|
||||
border: none;
|
||||
border-top: 1px solid #eee;
|
||||
margin: 12px 0;
|
||||
}
|
||||
|
||||
/* ── Email body iframe-like container ───────────────────────────────────── */
|
||||
.ced-email-body {
|
||||
background: #fafafa;
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 8px;
|
||||
padding: 20px 24px;
|
||||
font-family: Arial, sans-serif;
|
||||
font-size: 0.93rem;
|
||||
line-height: 1.6;
|
||||
color: #333;
|
||||
|
||||
// Reset any Angular Material overrides inside the rendered HTML
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
td,
|
||||
th {
|
||||
padding: 6px 12px;
|
||||
border: 1px solid #e0e0e0;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0 0 10px;
|
||||
}
|
||||
|
||||
ul {
|
||||
padding-left: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Empty state ────────────────────────────────────────────────────────── */
|
||||
.ced-empty {
|
||||
text-align: center;
|
||||
padding: 32px 0;
|
||||
color: #9e9e9e;
|
||||
|
||||
&__hint {
|
||||
font-size: 0.85rem;
|
||||
margin-top: 6px;
|
||||
}
|
||||
}
|
||||
-69
@@ -1,69 +0,0 @@
|
||||
/**
|
||||
* 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, inject } from '@angular/core';
|
||||
import {
|
||||
MAT_DIALOG_DATA,
|
||||
MatDialogTitle,
|
||||
MatDialogContent,
|
||||
MatDialogActions,
|
||||
MatDialogClose
|
||||
} from '@angular/material/dialog';
|
||||
import { CdkScrollable } from '@angular/cdk/scrolling';
|
||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
|
||||
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
||||
|
||||
export interface ClientEditEmailDialogData {
|
||||
emailSubject: string | null;
|
||||
emailBody: string | null;
|
||||
emailSent: boolean;
|
||||
emailSentAt: string | null;
|
||||
emailOpenedAt: string | null;
|
||||
emailFrom: string | null;
|
||||
emailTo: string | null;
|
||||
}
|
||||
|
||||
/** Displays the exact confirmation email sent to the client when their pending edit was approved.
|
||||
* Shows subject, HTML body (rendered), sent timestamp, and read/seen status. */
|
||||
@Component({
|
||||
selector: 'mifosx-client-edit-email-dialog',
|
||||
templateUrl: './client-edit-email-dialog.component.html',
|
||||
styleUrls: ['./client-edit-email-dialog.component.scss'],
|
||||
standalone: true,
|
||||
imports: [
|
||||
...STANDALONE_SHARED_IMPORTS,
|
||||
MatDialogTitle,
|
||||
CdkScrollable,
|
||||
MatDialogContent,
|
||||
MatDialogActions,
|
||||
MatDialogClose,
|
||||
FaIconComponent
|
||||
]
|
||||
})
|
||||
export class ClientEditEmailDialogComponent {
|
||||
data = inject<ClientEditEmailDialogData>(MAT_DIALOG_DATA);
|
||||
private sanitizer = inject(DomSanitizer);
|
||||
|
||||
get safeBody(): SafeHtml {
|
||||
return this.sanitizer.bypassSecurityTrustHtml(this.data.emailBody ?? '');
|
||||
}
|
||||
|
||||
formatDate(isoDate: string | null | undefined): string {
|
||||
if (!isoDate) return '—';
|
||||
const d = new Date(isoDate);
|
||||
return d.toLocaleString('en-IN', {
|
||||
day: '2-digit',
|
||||
month: 'short',
|
||||
year: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
hour12: true
|
||||
});
|
||||
}
|
||||
}
|
||||
-33
@@ -126,29 +126,6 @@
|
||||
<ng-container matColumnDef="actions">
|
||||
<th mat-header-cell *matHeaderCellDef>Actions</th>
|
||||
<td mat-cell *matCellDef="let edit">
|
||||
<!-- ── Email status badges (compact, always visible) ── -->
|
||||
<span
|
||||
class="pce-email-badge"
|
||||
[class.pce-email-badge--sent]="edit.emailSent"
|
||||
[class.pce-email-badge--nosent]="!edit.emailSent"
|
||||
[title]="edit.emailSent ? 'Email sent on ' + formatDate(edit.emailSentAt) : 'No confirmation email sent'"
|
||||
>
|
||||
<fa-icon icon="envelope"></fa-icon>
|
||||
{{ edit.emailSent ? 'Sent' : 'Not Sent' }}
|
||||
</span>
|
||||
@if (edit.emailSent) {
|
||||
<span
|
||||
class="pce-email-badge"
|
||||
[class.pce-email-badge--seen]="edit.emailOpenedAt"
|
||||
[class.pce-email-badge--notseen]="!edit.emailOpenedAt"
|
||||
[title]="edit.emailOpenedAt ? 'Opened on ' + formatDate(edit.emailOpenedAt) : 'Not yet opened'"
|
||||
>
|
||||
<fa-icon [icon]="edit.emailOpenedAt ? 'eye' : 'eye-slash'"></fa-icon>
|
||||
{{ edit.emailOpenedAt ? 'Seen' : 'Not Seen' }}
|
||||
</span>
|
||||
}
|
||||
|
||||
<!-- ── Action buttons ── -->
|
||||
<button mat-stroked-button class="pce-btn pce-btn-view" (click)="viewCompare(edit)">
|
||||
<fa-icon icon="eye" class="m-r-10"></fa-icon>Compare
|
||||
</button>
|
||||
@@ -160,16 +137,6 @@
|
||||
<fa-icon icon="ban" class="m-r-10"></fa-icon>Reject
|
||||
</button>
|
||||
}
|
||||
@if (edit.emailSent) {
|
||||
<button
|
||||
mat-stroked-button
|
||||
class="pce-btn pce-btn-email"
|
||||
(click)="viewEmail(edit)"
|
||||
title="View the exact confirmation email sent to the client"
|
||||
>
|
||||
<fa-icon icon="envelope" class="m-r-10"></fa-icon>View Email
|
||||
</button>
|
||||
}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
|
||||
-42
@@ -346,45 +346,3 @@ $text-muted: #64748b;
|
||||
line-height: 1.5;
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Email badges ───────────────────────────────────────────────────────── */
|
||||
.pce-email-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
font-size: 0.72rem;
|
||||
font-weight: 600;
|
||||
margin-right: 6px;
|
||||
white-space: nowrap;
|
||||
vertical-align: middle;
|
||||
|
||||
fa-icon {
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
|
||||
&--sent {
|
||||
background: #e8f5e9;
|
||||
color: #1b5e20;
|
||||
border: 1px solid #c8e6c9;
|
||||
}
|
||||
|
||||
&--nosent {
|
||||
background: #f5f5f5;
|
||||
color: #9e9e9e;
|
||||
border: 1px solid #e0e0e0;
|
||||
}
|
||||
|
||||
&--seen {
|
||||
background: #e3f2fd;
|
||||
color: #0d47a1;
|
||||
border: 1px solid #bbdefb;
|
||||
}
|
||||
|
||||
&--notseen {
|
||||
background: #fff3e0;
|
||||
color: #e65100;
|
||||
border: 1px solid #ffe0b2;
|
||||
}
|
||||
}
|
||||
|
||||
+1
-49
@@ -34,7 +34,6 @@ import { InputBase } from 'app/shared/form-dialog/formfield/model/input-base';
|
||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
import { NON_COMPARABLE_KEYS, PENDING_EDIT_FIELD_LABELS } from 'app/clients/shared/client-edit-diff.util';
|
||||
import { ClientEditCompareDialogComponent } from './client-edit-compare-dialog/client-edit-compare-dialog.component';
|
||||
import { ClientEditEmailDialogComponent } from './client-edit-email-dialog/client-edit-email-dialog.component';
|
||||
|
||||
/** Maps a payload key that holds a CodeValue/template id to the options array it should be
|
||||
* resolved against — populated from /clients/template plus the same per-code-value lookups the
|
||||
@@ -98,8 +97,7 @@ const NATIONALITY_LABELS: Record<string, string> = {
|
||||
MatRowDef,
|
||||
MatRow,
|
||||
MatIcon,
|
||||
MatPaginatorModule,
|
||||
ClientEditEmailDialogComponent
|
||||
MatPaginatorModule
|
||||
]
|
||||
})
|
||||
export class PendingClientEditsComponent implements OnInit {
|
||||
@@ -425,52 +423,6 @@ export class PendingClientEditsComponent implements OnInit {
|
||||
});
|
||||
}
|
||||
|
||||
/** Opens the email preview dialog for an edit row. Fetches the email content first (subject,
|
||||
* body, sent/opened timestamps) via the email-preview endpoint, then opens the dialog. */
|
||||
viewEmail(edit: any): void {
|
||||
this.clientsService.getClientEditEmailPreview(edit.clientId, edit.id).subscribe({
|
||||
next: (preview) => {
|
||||
try {
|
||||
this.dialog.open(ClientEditEmailDialogComponent, {
|
||||
width: '800px',
|
||||
maxWidth: '96vw',
|
||||
data: {
|
||||
emailSubject: preview?.emailSubject || null,
|
||||
emailBody: preview?.emailBody || null,
|
||||
emailSent: preview?.emailSent || false,
|
||||
emailSentAt: preview?.emailSentAt || null,
|
||||
emailOpenedAt: preview?.emailOpenedAt || null,
|
||||
emailFrom: preview?.emailFrom || null,
|
||||
emailTo: preview?.emailTo || null
|
||||
}
|
||||
});
|
||||
} catch (e: any) {
|
||||
console.error('Dialog open failed (next): ', e);
|
||||
}
|
||||
},
|
||||
error: () => {
|
||||
try {
|
||||
// Fallback: open dialog with no-content state
|
||||
this.dialog.open(ClientEditEmailDialogComponent, {
|
||||
width: '800px',
|
||||
maxWidth: '96vw',
|
||||
data: {
|
||||
emailSubject: null,
|
||||
emailBody: null,
|
||||
emailSent: false,
|
||||
emailSentAt: null,
|
||||
emailOpenedAt: null,
|
||||
emailFrom: null,
|
||||
emailTo: null
|
||||
}
|
||||
});
|
||||
} catch (e: any) {
|
||||
console.error('Dialog open failed (error): ', e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
reload(): void {
|
||||
this.load();
|
||||
}
|
||||
|
||||
-86
@@ -1,86 +0,0 @@
|
||||
<!--
|
||||
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/.
|
||||
-->
|
||||
|
||||
<div class="pg-card">
|
||||
<div class="pos-section-label">Share Withdrawal Requests — Review, Approve or Reject</div>
|
||||
<div class="inv-form-grid" style="padding: 12px 16px">
|
||||
<label class="inv-form-field">
|
||||
<span>Status</span>
|
||||
<select [(ngModel)]="filterStatus" (change)="onFilterChange()">
|
||||
@for (opt of statusOptions; track opt.value) {
|
||||
<option [value]="opt.value">{{ opt.label }}</option>
|
||||
}
|
||||
</select>
|
||||
</label>
|
||||
<label class="inv-form-field" style="align-self: end">
|
||||
<button type="button" class="pg-btn pg-btn--ghost" [disabled]="loading" (click)="load()">Refresh</button>
|
||||
</label>
|
||||
</div>
|
||||
<table class="pg-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Account No</th>
|
||||
<th>Client</th>
|
||||
<th class="num">Shares</th>
|
||||
<th>Resolution No.</th>
|
||||
<th>Meeting Date</th>
|
||||
<th>Clerk</th>
|
||||
<th>Manager</th>
|
||||
<th>Submitted At</th>
|
||||
<th>Status</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@if (loading) {
|
||||
<tr>
|
||||
<td colspan="10" class="pg-empty">Loading…</td>
|
||||
</tr>
|
||||
} @else if (loaded && requests.length === 0) {
|
||||
<tr>
|
||||
<td colspan="10" class="pg-empty">No {{ filterStatus | lowercase }} share withdrawal requests.</td>
|
||||
</tr>
|
||||
} @else {
|
||||
@for (r of requests; track r.id) {
|
||||
<tr>
|
||||
<td>
|
||||
<span class="pg-chip">{{ r.account_no }}</span>
|
||||
</td>
|
||||
<td>{{ r.client_name }}</td>
|
||||
<td class="num">{{ r.requested_shares }}</td>
|
||||
<td>{{ r.resolution_number }}</td>
|
||||
<td>{{ formatDateTime(r.meeting_date) }}</td>
|
||||
<td>{{ r.clerk_name }}</td>
|
||||
<td>{{ r.manager_name || r.manager_username || '—' }}</td>
|
||||
<td>{{ formatDateTime(r.submitted_at) }}</td>
|
||||
<td>
|
||||
<span [class]="statusClass(r.status)">{{ statusLabel(r.status) }}</span>
|
||||
</td>
|
||||
<td>
|
||||
<button type="button" class="pg-btn pg-btn--ghost" (click)="review(r)">Review</button>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
@if (showReviewModal && reviewRequestId) {
|
||||
<div class="swr-list-popup-backdrop" (click)="closeReviewModal()">
|
||||
<div class="swr-list-popup-panel" (click)="$event.stopPropagation()">
|
||||
<mifosx-share-withdrawal-request
|
||||
[embedded]="true"
|
||||
[overrideMode]="'review'"
|
||||
[overrideRequestId]="reviewRequestId"
|
||||
(closeRequested)="closeReviewModal()"
|
||||
(actionCompleted)="onReviewActionCompleted()"
|
||||
></mifosx-share-withdrawal-request>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
-64
@@ -1,64 +0,0 @@
|
||||
/**
|
||||
* 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/.
|
||||
*/
|
||||
|
||||
@keyframes swr-list-popup-fade {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes swr-list-popup-zoom {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: scale(0.97) translateY(10px);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: scale(1) translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.swr-list-popup-backdrop {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
// No backdrop-filter here: filter/backdrop-filter on an ancestor makes browsers treat it as a new
|
||||
// containing block for `position: fixed` descendants — the embedded review screen inside this popup
|
||||
// renders its OWN fixed-position dialogs (Validate, Reject, Confirm-Submit), which would then be
|
||||
// confined to this backdrop's box instead of the real viewport. Extra opacity on the background
|
||||
// color compensates for the loss of the blur look.
|
||||
background: rgb(15 25 45 / 65%);
|
||||
z-index: 1100;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: center;
|
||||
padding: 32px 20px;
|
||||
overflow-y: auto;
|
||||
animation: swr-list-popup-fade 0.2s ease-out;
|
||||
}
|
||||
|
||||
.swr-list-popup-panel {
|
||||
background: #f7f9fc;
|
||||
border-radius: 16px;
|
||||
width: 100%;
|
||||
max-width: 1100px;
|
||||
box-shadow: 0 24px 60px rgb(0 0 0 / 25%);
|
||||
animation: swr-list-popup-zoom 0.25s ease-out;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.swr-list-popup-backdrop,
|
||||
.swr-list-popup-panel {
|
||||
animation: none;
|
||||
}
|
||||
}
|
||||
-128
@@ -1,128 +0,0 @@
|
||||
/**
|
||||
* 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 { CommonModule } from '@angular/common';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { catchError } from 'rxjs/operators';
|
||||
import { of } from 'rxjs';
|
||||
|
||||
import { ClerkShareWithdrawalService } from '../../../cashier/clerk-share-withdrawal.service';
|
||||
import { ShareWithdrawalRequestComponent } from '../../../clerk/share-withdrawal-request/share-withdrawal-request.component';
|
||||
|
||||
/** Approval Center — Share Withdrawal Requests tab. A Bank Manager (checker) reviews, approves
|
||||
* or rejects share withdrawal requests submitted from the Clerk Dashboard. Mirrors
|
||||
* WithdrawalRequestsComponent (cash withdrawal), adapted for shares. Clicking "Review" opens the
|
||||
* review screen in a popup on top of this list, rather than navigating away, so approving/rejecting
|
||||
* drops the manager straight back into the (now refreshed) list. */
|
||||
@Component({
|
||||
selector: 'mifosx-share-withdrawal-requests',
|
||||
templateUrl: './share-withdrawal-requests.component.html',
|
||||
styleUrls: [
|
||||
'../../../cashier/cashier-shared.scss',
|
||||
'../../../clerk/clerk-shared.scss',
|
||||
'./share-withdrawal-requests.component.scss'
|
||||
],
|
||||
standalone: true,
|
||||
imports: [
|
||||
CommonModule,
|
||||
FormsModule,
|
||||
ShareWithdrawalRequestComponent
|
||||
]
|
||||
})
|
||||
export class ShareWithdrawalRequestsComponent implements OnInit {
|
||||
private service = inject(ClerkShareWithdrawalService);
|
||||
|
||||
requests: any[] = [];
|
||||
totalRecords = 0;
|
||||
loading = false;
|
||||
loaded = false;
|
||||
|
||||
filterStatus = 'PENDING';
|
||||
|
||||
showReviewModal = false;
|
||||
reviewRequestId: number | null = null;
|
||||
|
||||
get statusOptions(): { value: string; label: string }[] {
|
||||
return [
|
||||
{ value: 'PENDING', label: 'Pending' },
|
||||
{ value: 'APPROVED', label: 'Approved' },
|
||||
{ value: 'REJECTED', label: 'Rejected' }
|
||||
];
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.load();
|
||||
}
|
||||
|
||||
load(): void {
|
||||
this.loading = true;
|
||||
this.service
|
||||
.getPendingRequests(this.filterStatus, 0, 50)
|
||||
.pipe(catchError(() => of({ pageItems: [], totalFilteredRecords: 0 })))
|
||||
.subscribe((res: any) => {
|
||||
this.requests = res?.pageItems ?? [];
|
||||
this.totalRecords = Number(res?.totalFilteredRecords ?? 0);
|
||||
this.loading = false;
|
||||
this.loaded = true;
|
||||
});
|
||||
}
|
||||
|
||||
onFilterChange(): void {
|
||||
this.load();
|
||||
}
|
||||
|
||||
review(req: any): void {
|
||||
this.reviewRequestId = req.id;
|
||||
this.showReviewModal = true;
|
||||
}
|
||||
|
||||
closeReviewModal(): void {
|
||||
this.showReviewModal = false;
|
||||
this.reviewRequestId = null;
|
||||
}
|
||||
|
||||
onReviewActionCompleted(): void {
|
||||
this.closeReviewModal();
|
||||
this.load();
|
||||
}
|
||||
|
||||
statusClass(status: string): string {
|
||||
const map: Record<string, string> = {
|
||||
PENDING: 'inv-badge--active',
|
||||
APPROVED: 'inv-badge--matured',
|
||||
REJECTED: 'inv-badge--written_off'
|
||||
};
|
||||
return `inv-badge ${map[status] ?? ''}`;
|
||||
}
|
||||
|
||||
statusLabel(status: string): string {
|
||||
const map: Record<string, string> = {
|
||||
PENDING: 'Pending',
|
||||
APPROVED: 'Approved',
|
||||
REJECTED: 'Rejected'
|
||||
};
|
||||
return map[status] ?? status;
|
||||
}
|
||||
|
||||
formatDateTime(val: any): string {
|
||||
if (!val) return '—';
|
||||
try {
|
||||
const d = new Date(val);
|
||||
return d.toLocaleString('en-IN', {
|
||||
day: '2-digit',
|
||||
month: 'short',
|
||||
year: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
});
|
||||
} catch {
|
||||
return String(val);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,207 +20,143 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Pill-style tab navigation -->
|
||||
<nav mat-tab-nav-bar class="ac-tabs" [tabPanel]="tabPanel">
|
||||
<!-- Pill-style tab navigation — wraps onto multiple rows so every section stays visible and
|
||||
clickable at once, instead of being hidden behind prev/next scroll arrows. -->
|
||||
<nav class="ac-tabs">
|
||||
<a
|
||||
mat-tab-link
|
||||
class="ac-tab"
|
||||
[routerLink]="['./checker-inbox']"
|
||||
routerLinkActive
|
||||
#checkerInbox="routerLinkActive"
|
||||
[active]="checkerInbox.isActive"
|
||||
routerLink="./checker-inbox"
|
||||
routerLinkActive="ac-tab--active"
|
||||
*mifosxHasPermission="'ALL_FUNCTIONS_READ'"
|
||||
>
|
||||
<fa-icon icon="inbox" class="ac-tab__ic"></fa-icon>
|
||||
<span>Checker Inbox</span>
|
||||
</a>
|
||||
<a
|
||||
mat-tab-link
|
||||
class="ac-tab"
|
||||
[routerLink]="['./client-approval']"
|
||||
routerLinkActive
|
||||
#clientApproval="routerLinkActive"
|
||||
[active]="clientApproval.isActive"
|
||||
routerLink="./client-approval"
|
||||
routerLinkActive="ac-tab--active"
|
||||
*mifosxHasPermission="'READ_CLIENT'"
|
||||
>
|
||||
<fa-icon icon="user-check" class="ac-tab__ic"></fa-icon>
|
||||
<span>Client Approval</span>
|
||||
</a>
|
||||
<a
|
||||
mat-tab-link
|
||||
class="ac-tab"
|
||||
[routerLink]="['./loan-approval']"
|
||||
routerLinkActive
|
||||
#loanApproval="routerLinkActive"
|
||||
[active]="loanApproval.isActive"
|
||||
*mifosxHasPermission="'READ_LOAN'"
|
||||
>
|
||||
<a class="ac-tab" routerLink="./loan-approval" routerLinkActive="ac-tab--active" *mifosxHasPermission="'READ_LOAN'">
|
||||
<fa-icon icon="hand-holding-usd" class="ac-tab__ic"></fa-icon>
|
||||
<span>Loan Approval</span>
|
||||
</a>
|
||||
<a
|
||||
mat-tab-link
|
||||
class="ac-tab"
|
||||
[routerLink]="['./loan-disbursal']"
|
||||
routerLinkActive
|
||||
#loanDisbursal="routerLinkActive"
|
||||
[active]="loanDisbursal.isActive"
|
||||
routerLink="./loan-disbursal"
|
||||
routerLinkActive="ac-tab--active"
|
||||
*mifosxHasPermission="'READ_LOAN'"
|
||||
>
|
||||
<fa-icon icon="money-bill-wave" class="ac-tab__ic"></fa-icon>
|
||||
<span>Loan Disbursal</span>
|
||||
</a>
|
||||
<a
|
||||
mat-tab-link
|
||||
class="ac-tab"
|
||||
[routerLink]="['./reschedule-loan']"
|
||||
routerLinkActive
|
||||
#rescheduleLoan="routerLinkActive"
|
||||
[active]="rescheduleLoan.isActive"
|
||||
routerLink="./reschedule-loan"
|
||||
routerLinkActive="ac-tab--active"
|
||||
*mifosxHasPermission="'RESCHEDULE_LOAN'"
|
||||
>
|
||||
<fa-icon icon="calendar-alt" class="ac-tab__ic"></fa-icon>
|
||||
<span>Reschedule Loan</span>
|
||||
</a>
|
||||
<a
|
||||
mat-tab-link
|
||||
class="ac-tab"
|
||||
[routerLink]="['./pending-account']"
|
||||
routerLinkActive
|
||||
#pendingAccount="routerLinkActive"
|
||||
[active]="pendingAccount.isActive"
|
||||
routerLink="./pending-account"
|
||||
routerLinkActive="ac-tab--active"
|
||||
*mifosxHasPermission="'ALL_FUNCTIONS_READ'"
|
||||
>
|
||||
<fa-icon icon="hourglass-half" class="ac-tab__ic"></fa-icon>
|
||||
<span>Pending Account</span>
|
||||
</a>
|
||||
<a
|
||||
mat-tab-link
|
||||
class="ac-tab"
|
||||
[routerLink]="['./pending-savings-products']"
|
||||
routerLinkActive
|
||||
#pendingSavingsProducts="routerLinkActive"
|
||||
[active]="pendingSavingsProducts.isActive"
|
||||
routerLink="./pending-savings-products"
|
||||
routerLinkActive="ac-tab--active"
|
||||
*mifosxHasPermission="'ALL_FUNCTIONS_READ'"
|
||||
>
|
||||
<fa-icon icon="piggy-bank" class="ac-tab__ic"></fa-icon>
|
||||
<span>Pending Product Edits</span>
|
||||
</a>
|
||||
<a
|
||||
mat-tab-link
|
||||
class="ac-tab"
|
||||
[routerLink]="['./pending-savings-account-edits']"
|
||||
routerLinkActive
|
||||
#pendingSavingsAccountEdits="routerLinkActive"
|
||||
[active]="pendingSavingsAccountEdits.isActive"
|
||||
routerLink="./pending-savings-account-edits"
|
||||
routerLinkActive="ac-tab--active"
|
||||
*mifosxHasPermission="'ALL_FUNCTIONS_READ'"
|
||||
>
|
||||
<fa-icon icon="edit" class="ac-tab__ic"></fa-icon>
|
||||
<span>Pending Account Edits</span>
|
||||
</a>
|
||||
<a
|
||||
mat-tab-link
|
||||
class="ac-tab"
|
||||
[routerLink]="['./pending-share-account-edits']"
|
||||
routerLinkActive
|
||||
#pendingShareAccountEdits="routerLinkActive"
|
||||
[active]="pendingShareAccountEdits.isActive"
|
||||
routerLink="./pending-share-account-edits"
|
||||
routerLinkActive="ac-tab--active"
|
||||
*mifosxHasPermission="'ALL_FUNCTIONS_READ'"
|
||||
>
|
||||
<fa-icon icon="edit" class="ac-tab__ic"></fa-icon>
|
||||
<span>Pending Share Account Edits</span>
|
||||
</a>
|
||||
<a
|
||||
mat-tab-link
|
||||
class="ac-tab"
|
||||
[routerLink]="['./pending-client-edits']"
|
||||
routerLinkActive
|
||||
#pendingClientEdits="routerLinkActive"
|
||||
[active]="pendingClientEdits.isActive"
|
||||
routerLink="./pending-client-edits"
|
||||
routerLinkActive="ac-tab--active"
|
||||
*mifosxHasPermission="'ALL_FUNCTIONS_READ'"
|
||||
>
|
||||
<fa-icon icon="edit" class="ac-tab__ic"></fa-icon>
|
||||
<span>Pending Client Edits</span>
|
||||
</a>
|
||||
<a
|
||||
mat-tab-link
|
||||
class="ac-tab"
|
||||
[routerLink]="['./rejected-account']"
|
||||
routerLinkActive
|
||||
#rejectedAccount="routerLinkActive"
|
||||
[active]="rejectedAccount.isActive"
|
||||
routerLink="./rejected-account"
|
||||
routerLinkActive="ac-tab--active"
|
||||
*mifosxHasPermission="'ALL_FUNCTIONS_READ'"
|
||||
>
|
||||
<fa-icon icon="ban" class="ac-tab__ic"></fa-icon>
|
||||
<span>Reject Account</span>
|
||||
</a>
|
||||
<a
|
||||
mat-tab-link
|
||||
class="ac-tab"
|
||||
[routerLink]="['./terminated-account']"
|
||||
routerLinkActive
|
||||
#terminatedAccount="routerLinkActive"
|
||||
[active]="terminatedAccount.isActive"
|
||||
routerLink="./terminated-account"
|
||||
routerLinkActive="ac-tab--active"
|
||||
*mifosxHasPermission="'ALL_FUNCTIONS_READ'"
|
||||
>
|
||||
<fa-icon icon="times-circle" class="ac-tab__ic"></fa-icon>
|
||||
<span>Terminated Account</span>
|
||||
</a>
|
||||
<a
|
||||
mat-tab-link
|
||||
class="ac-tab"
|
||||
[routerLink]="['./withdrawal-requests']"
|
||||
routerLinkActive
|
||||
#withdrawalRequests="routerLinkActive"
|
||||
[active]="withdrawalRequests.isActive"
|
||||
routerLink="./withdrawal-requests"
|
||||
routerLinkActive="ac-tab--active"
|
||||
*mifosxHasPermission="'ALL_FUNCTIONS_READ'"
|
||||
>
|
||||
<fa-icon icon="money-bill-wave" class="ac-tab__ic"></fa-icon>
|
||||
<span>Withdrawal Requests</span>
|
||||
</a>
|
||||
<a
|
||||
mat-tab-link
|
||||
class="ac-tab"
|
||||
[routerLink]="['./share-withdrawal-requests']"
|
||||
routerLinkActive
|
||||
#shareWithdrawalRequests="routerLinkActive"
|
||||
[active]="shareWithdrawalRequests.isActive"
|
||||
*mifosxHasPermission="'ALL_FUNCTIONS_READ'"
|
||||
>
|
||||
<fa-icon icon="hand-holding-usd" class="ac-tab__ic"></fa-icon>
|
||||
<span>Share Withdrawal Requests</span>
|
||||
</a>
|
||||
<a
|
||||
mat-tab-link
|
||||
class="ac-tab"
|
||||
[routerLink]="['./deposit-requests']"
|
||||
routerLinkActive
|
||||
#depositRequests="routerLinkActive"
|
||||
[active]="depositRequests.isActive"
|
||||
routerLink="./deposit-requests"
|
||||
routerLinkActive="ac-tab--active"
|
||||
*mifosxHasPermission="'ALL_FUNCTIONS_READ'"
|
||||
>
|
||||
<fa-icon icon="hand-holding-usd" class="ac-tab__ic"></fa-icon>
|
||||
<span>Deposit Requests</span>
|
||||
</a>
|
||||
<a
|
||||
mat-tab-link
|
||||
class="ac-tab"
|
||||
[routerLink]="['./disbursement-requests']"
|
||||
routerLinkActive
|
||||
#disbursementRequests="routerLinkActive"
|
||||
[active]="disbursementRequests.isActive"
|
||||
routerLink="./disbursement-requests"
|
||||
routerLinkActive="ac-tab--active"
|
||||
*mifosxHasPermission="'ALL_FUNCTIONS_READ'"
|
||||
>
|
||||
<fa-icon icon="money-bill-wave" class="ac-tab__ic"></fa-icon>
|
||||
<span>Disbursement & Transfers</span>
|
||||
</a>
|
||||
<a
|
||||
mat-tab-link
|
||||
class="ac-tab"
|
||||
[routerLink]="['./chequebook-requests']"
|
||||
routerLinkActive
|
||||
#chequebookRequests="routerLinkActive"
|
||||
[active]="chequebookRequests.isActive"
|
||||
routerLink="./chequebook-requests"
|
||||
routerLinkActive="ac-tab--active"
|
||||
*mifosxHasPermission="'READ_CHEQUEBOOKPENDINGREQUEST'"
|
||||
>
|
||||
<fa-icon icon="money-check" class="ac-tab__ic"></fa-icon>
|
||||
@@ -228,7 +164,7 @@
|
||||
</a>
|
||||
</nav>
|
||||
|
||||
<mat-tab-nav-panel #tabPanel>
|
||||
<div class="ac-tab-panel">
|
||||
<router-outlet></router-outlet>
|
||||
</mat-tab-nav-panel>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -17,7 +17,6 @@ import { Route } from '../core/route/route.service';
|
||||
import { CheckerInboxAndTasksComponent } from './checker-inbox-and-tasks/checker-inbox-and-tasks.component';
|
||||
import { CheckerInboxComponent } from './checker-inbox-and-tasks-tabs/checker-inbox/checker-inbox.component';
|
||||
import { WithdrawalRequestsComponent } from './checker-inbox-and-tasks-tabs/withdrawal-requests/withdrawal-requests.component';
|
||||
import { ShareWithdrawalRequestsComponent } from './checker-inbox-and-tasks-tabs/share-withdrawal-requests/share-withdrawal-requests.component';
|
||||
import { DepositRequestsComponent } from './checker-inbox-and-tasks-tabs/deposit-requests/deposit-requests.component';
|
||||
import { DisbursementRequestsComponent } from './checker-inbox-and-tasks-tabs/disbursement-requests/disbursement-requests.component';
|
||||
import { ClientApprovalComponent } from './checker-inbox-and-tasks-tabs/client-approval/client-approval.component';
|
||||
@@ -160,11 +159,6 @@ const routes: Routes = [
|
||||
component: WithdrawalRequestsComponent,
|
||||
data: { title: 'Withdrawal Requests' }
|
||||
},
|
||||
{
|
||||
path: 'share-withdrawal-requests',
|
||||
component: ShareWithdrawalRequestsComponent,
|
||||
data: { title: 'Share Withdrawal Requests' }
|
||||
},
|
||||
{
|
||||
path: 'deposit-requests',
|
||||
component: DepositRequestsComponent,
|
||||
@@ -206,19 +200,6 @@ const routes: Routes = [
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'checker-inbox-and-tasks/share-withdrawal-requests',
|
||||
children: [
|
||||
{
|
||||
path: ':id/review',
|
||||
loadComponent: () =>
|
||||
import('../clerk/share-withdrawal-request/share-withdrawal-request.component').then(
|
||||
(m) => m.ShareWithdrawalRequestComponent
|
||||
),
|
||||
data: { title: 'Review Share Withdrawal Request', breadcrumb: 'Review Share Withdrawal', mode: 'review' }
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'checker-inbox-and-tasks/disbursement-requests',
|
||||
children: [
|
||||
|
||||
Reference in New Issue
Block a user