feat: update changes

This commit is contained in:
Anshul Rathore
2026-08-06 10:41:28 +05:30
parent a982083b14
commit 983c613b8e
21 changed files with 1189 additions and 105 deletions
@@ -0,0 +1,84 @@
<!--
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">&lt;{{ data.emailFrom ?? 'noreply@cbsnexora.in' }}&gt;</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>
@@ -0,0 +1,219 @@
/**
* Copyright since 2025 Mifos Initiative
* 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 0;
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 #eeeeee;
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;
}
}
@@ -0,0 +1,65 @@
/**
* 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
});
}
}
@@ -126,6 +126,25 @@
<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>
@@ -137,6 +156,12 @@
<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>
@@ -346,3 +346,41 @@ $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;
}
}
@@ -34,6 +34,7 @@ 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
@@ -97,7 +98,8 @@ const NATIONALITY_LABELS: Record<string, string> = {
MatRowDef,
MatRow,
MatIcon,
MatPaginatorModule
MatPaginatorModule,
ClientEditEmailDialogComponent
]
})
export class PendingClientEditsComponent implements OnInit {
@@ -423,6 +425,45 @@ 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();
}