merging code

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Nitesh Yadav
2026-04-20 14:26:25 +05:30
co-authored by Claude Sonnet 4.6
322 changed files with 28779 additions and 4976 deletions
@@ -1,3 +1,11 @@
<!--
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/.
-->
<!--
Copyright since 2025 Mifos Initiative
-->
@@ -10,7 +18,6 @@
<mat-dialog-content>
<form [formGroup]="familyMemberForm">
<!-- ═══ VIEW MODE ═══ -->
@if (isViewMode) {
<!-- Linked client card -->
@@ -18,20 +25,31 @@
<div class="fm-linked-card">
<div class="fm-linked-card__left">
@if (linkedProfileImage) {
<img [src]="linkedProfileImage" class="fm-linked-card__photo zoomable" alt="Profile"
(click)="zoomImage(linkedProfileImage, 'Client Photograph')" />
<img
[src]="linkedProfileImage"
class="fm-linked-card__photo zoomable"
alt="Profile"
(click)="zoomImage(linkedProfileImage, 'Client Photograph')"
/>
} @else {
<mat-icon class="fm-linked-card__icon">person</mat-icon>
}
</div>
<div class="fm-linked-card__info">
<span class="fm-linked-card__name">{{ selectedClient.displayName }}</span>
<span class="fm-linked-card__meta">Client ID: {{ selectedClient.id }}{{ selectedClient.accountNumber ? ' · A/C: ' + selectedClient.accountNumber : '' }}</span>
<span class="fm-linked-card__meta"
>Client ID: {{ selectedClient.id
}}{{ selectedClient.accountNumber ? ' · A/C: ' + selectedClient.accountNumber : '' }}</span
>
</div>
@if (linkedSignatureImage) {
<div class="fm-linked-card__sig">
<img [src]="linkedSignatureImage" alt="Signature" class="zoomable"
(click)="zoomImage(linkedSignatureImage, 'Specimen Signature')" />
<img
[src]="linkedSignatureImage"
alt="Signature"
class="zoomable"
(click)="zoomImage(linkedSignatureImage, 'Specimen Signature')"
/>
<span>Signature</span>
</div>
}
@@ -57,7 +75,11 @@
</div>
<div class="fm-readonly-item">
<span class="fm-readonly-label">Date of Birth</span>
<span class="fm-readonly-value">{{ familyMemberForm.get('dateOfBirth')?.value ? (familyMemberForm.get('dateOfBirth')?.value | date:'dd MMM yyyy') : '—' }}</span>
<span class="fm-readonly-value">{{
familyMemberForm.get('dateOfBirth')?.value
? (familyMemberForm.get('dateOfBirth')?.value | date: 'dd MMM yyyy')
: '—'
}}</span>
</div>
<div class="fm-readonly-item">
<span class="fm-readonly-label">Nationality</span>
@@ -89,19 +111,25 @@
<span class="fm-readonly-label">Status</span>
<span class="fm-readonly-value">
{{ familyMemberForm.get('isAlive')?.value !== false ? 'Alive' : 'Deceased' }}
@if (familyMemberForm.get('isDependent')?.value) { · Dependent }
@if (familyMemberForm.get('isDependent')?.value) {
· Dependent
}
</span>
</div>
@if (isSpouse && familyMemberForm.get('marriageDate')?.value) {
<div class="fm-readonly-item">
<span class="fm-readonly-label">Marriage Date</span>
<span class="fm-readonly-value">{{ familyMemberForm.get('marriageDate')?.value | date:'dd MMM yyyy' }}</span>
<span class="fm-readonly-value">{{
familyMemberForm.get('marriageDate')?.value | date: 'dd MMM yyyy'
}}</span>
</div>
}
@if (isSpouse && familyMemberForm.get('marriageAuthority')?.value) {
<div class="fm-readonly-item">
<span class="fm-readonly-label">Issuing Authority</span>
<span class="fm-readonly-value">{{ getMarriageAuthorityLabel(familyMemberForm.get('marriageAuthority')?.value) }}</span>
<span class="fm-readonly-value">{{
getMarriageAuthorityLabel(familyMemberForm.get('marriageAuthority')?.value)
}}</span>
</div>
}
</div>
@@ -136,7 +164,9 @@
</mat-form-field>
@if (searching) {
<div class="fm-search-results">
<div class="fm-search-loading"><mat-progress-spinner diameter="18" mode="indeterminate"></mat-progress-spinner> Searching...</div>
<div class="fm-search-loading">
<mat-progress-spinner diameter="18" mode="indeterminate"></mat-progress-spinner> Searching...
</div>
</div>
}
@if (!searching && searchResults.length > 0) {
@@ -146,7 +176,18 @@
<mat-icon class="fm-search-item__icon">person</mat-icon>
<div class="fm-search-item__info">
<span class="fm-search-item__name">{{ r.displayName }}</span>
<span class="fm-search-item__meta">ID: {{ r.id }} · {{ r.officeName }}@if (r.dateOfBirth) { · DOB: {{ r.dateOfBirth | dateFormat }} }@if (r.fatherSpouseName) { · {{ r.fatherSpouseName }} }@if (r.mobileNo) { · {{ r.mobileNo }} }</span>
<span class="fm-search-item__meta"
>ID: {{ r.id }} · {{ r.officeName }}
@if (r.dateOfBirth) {
· DOB: {{ r.dateOfBirth | dateFormat }}
}
@if (r.fatherSpouseName) {
· {{ r.fatherSpouseName }}
}
@if (r.mobileNo) {
· {{ r.mobileNo }}
}
</span>
</div>
</div>
}
@@ -162,24 +203,37 @@
<div class="fm-linked-card">
<div class="fm-linked-card__left">
@if (linkedProfileImage) {
<img [src]="linkedProfileImage" class="fm-linked-card__photo zoomable" alt="Profile"
(click)="zoomImage(linkedProfileImage, 'Client Photograph')" />
<img
[src]="linkedProfileImage"
class="fm-linked-card__photo zoomable"
alt="Profile"
(click)="zoomImage(linkedProfileImage, 'Client Photograph')"
/>
} @else {
<mat-icon class="fm-linked-card__icon">person</mat-icon>
}
</div>
<div class="fm-linked-card__info">
<span class="fm-linked-card__name">{{ selectedClient.displayName }}</span>
<span class="fm-linked-card__meta">Client ID: {{ selectedClient.id }}{{ selectedClient.accountNumber ? ' · A/C: ' + selectedClient.accountNumber : '' }}</span>
<span class="fm-linked-card__meta"
>Client ID: {{ selectedClient.id
}}{{ selectedClient.accountNumber ? ' · A/C: ' + selectedClient.accountNumber : '' }}</span
>
</div>
@if (linkedSignatureImage) {
<div class="fm-linked-card__sig">
<img [src]="linkedSignatureImage" alt="Signature" class="zoomable"
(click)="zoomImage(linkedSignatureImage, 'Specimen Signature')" />
<img
[src]="linkedSignatureImage"
alt="Signature"
class="zoomable"
(click)="zoomImage(linkedSignatureImage, 'Specimen Signature')"
/>
<span>Signature</span>
</div>
}
<button mat-icon-button type="button" (click)="removeLinkedClient()" class="fm-linked-card__remove"><mat-icon>close</mat-icon></button>
<button mat-icon-button type="button" (click)="removeLinkedClient()" class="fm-linked-card__remove">
<mat-icon>close</mat-icon>
</button>
</div>
@if (fetchingLinkedData) {
@@ -198,7 +252,11 @@
</div>
<div class="fm-readonly-item">
<span class="fm-readonly-label">Date of Birth</span>
<span class="fm-readonly-value">{{ familyMemberForm.get('dateOfBirth')?.value ? (familyMemberForm.get('dateOfBirth')?.value | date:'dd MMM yyyy') : '—' }}</span>
<span class="fm-readonly-value">{{
familyMemberForm.get('dateOfBirth')?.value
? (familyMemberForm.get('dateOfBirth')?.value | date: 'dd MMM yyyy')
: '—'
}}</span>
</div>
<div class="fm-readonly-item">
<span class="fm-readonly-label">Aadhaar</span>
@@ -292,8 +350,13 @@
<button type="button" mat-stroked-button class="fm-file-btn" (click)="certInput.click()">
<mat-icon>upload_file</mat-icon> Upload Marriage Certificate
</button>
<input #certInput type="file" hidden accept=".pdf,.jpg,.jpeg,.png"
(change)="onMarriageCertSelected($event)" />
<input
#certInput
type="file"
hidden
accept=".pdf,.jpg,.jpeg,.png"
(change)="onMarriageCertSelected($event)"
/>
<span class="fm-file-hint">PDF, JPG or PNG (max 5 MB)</span>
} @else {
<div class="fm-file-attached">
@@ -313,15 +376,32 @@
<div class="fm-grid">
<mat-form-field appearance="outline">
<mat-label>Aadhaar Number</mat-label>
<input matInput formControlName="aadhaarNumber" maxlength="12" placeholder="12-digit Aadhaar"
(keypress)="$event.charCode >= 48 && $event.charCode <= 57 ? true : $event.preventDefault()" />
<input
matInput
formControlName="aadhaarNumber"
maxlength="12"
placeholder="12-digit Aadhaar"
(keypress)="$event.charCode >= 48 && $event.charCode <= 57 ? true : $event.preventDefault()"
/>
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>PAN Number</mat-label>
<input matInput formControlName="panNumber" maxlength="10" placeholder="ABCDE1234F" style="text-transform:uppercase"
(input)="familyMemberForm.get('panNumber')?.setValue($any($event.target).value.toUpperCase(), {emitEvent:false})" />
@if (familyMemberForm.get('panNumber')?.hasError('pattern') && familyMemberForm.get('panNumber')?.touched) {
<input
matInput
formControlName="panNumber"
maxlength="10"
placeholder="ABCDE1234F"
style="text-transform: uppercase"
(input)="
familyMemberForm
.get('panNumber')
?.setValue($any($event.target).value.toUpperCase(), { emitEvent: false })
"
/>
@if (
familyMemberForm.get('panNumber')?.hasError('pattern') && familyMemberForm.get('panNumber')?.touched
) {
<mat-error>Invalid PAN format</mat-error>
}
</mat-form-field>
@@ -348,9 +428,13 @@
<button mat-flat-button color="primary" mat-dialog-close type="button">Close</button>
} @else {
<button mat-stroked-button mat-dialog-close type="button">Cancel</button>
<button mat-flat-button color="primary" type="button"
<button
mat-flat-button
color="primary"
type="button"
[disabled]="familyMemberForm.invalid"
[mat-dialog-close]="{ member: familyMember }">
[mat-dialog-close]="{ member: familyMember }"
>
{{ data.context === 'Edit' ? 'Update' : 'Add Member' }}
</button>
}
@@ -1,3 +1,11 @@
/**
* 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/.
*/
.fm-dialog__header {
display: flex;
align-items: center;
@@ -9,7 +17,9 @@
font-weight: 600;
color: #1e293b;
.fm-dialog__icon { color: #1074b9; }
.fm-dialog__icon {
color: #1074b9;
}
}
.fm-mode-toggle {
@@ -32,7 +42,11 @@
cursor: pointer;
transition: all 0.15s;
mat-icon { font-size: 18px; width: 18px; height: 18px; }
mat-icon {
font-size: 18px;
width: 18px;
height: 18px;
}
&.active {
border-color: #1074b9;
@@ -40,11 +54,19 @@
color: #1074b9;
}
&:hover:not(.active) { border-color: #94a3b8; }
&:hover:not(.active) {
border-color: #94a3b8;
}
}
.fm-search-wrapper { position: relative; margin-bottom: 12px; }
.fm-search-field { width: 100%; }
.fm-search-wrapper {
position: relative;
margin-bottom: 12px;
}
.fm-search-field {
width: 100%;
}
.fm-search-results {
position: absolute;
@@ -55,7 +77,7 @@
background: #fff;
border: 1px solid #e2e8f0;
border-radius: 8px;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
box-shadow: 0 8px 24px rgb(0 0 0 / 12%);
max-height: 220px;
overflow-y: auto;
}
@@ -92,13 +114,34 @@
cursor: pointer;
border-bottom: 1px solid #f1f5f9;
&:last-child { border-bottom: none; }
&:hover { background: #f0f7ff; }
&:last-child {
border-bottom: none;
}
&__icon { color: #1074b9; font-size: 20px; }
&__info { display: flex; flex-direction: column; }
&__name { font-size: 13px; font-weight: 600; color: #1e293b; }
&__meta { font-size: 11px; color: #64748b; }
&:hover {
background: #f0f7ff;
}
&__icon {
color: #1074b9;
font-size: 20px;
}
&__info {
display: flex;
flex-direction: column;
}
&__name {
font-size: 13px;
font-weight: 600;
color: #1e293b;
}
&__meta {
font-size: 11px;
color: #64748b;
}
}
.fm-linked-card {
@@ -111,8 +154,14 @@
border-radius: 8px;
margin-bottom: 16px;
&__left { flex-shrink: 0; }
&__icon { color: #1074b9; }
&__left {
flex-shrink: 0;
}
&__icon {
color: #1074b9;
}
&__photo {
width: 40px;
height: 40px;
@@ -120,9 +169,25 @@
object-fit: cover;
border: 2px solid #e2e8f0;
}
&__info { flex: 1; display: flex; flex-direction: column; }
&__name { font-size: 14px; font-weight: 600; color: #1e293b; }
&__meta { font-size: 12px; color: #64748b; font-family: 'Roboto Mono', monospace; }
&__info {
flex: 1;
display: flex;
flex-direction: column;
}
&__name {
font-size: 14px;
font-weight: 600;
color: #1e293b;
}
&__meta {
font-size: 12px;
color: #64748b;
font-family: 'Roboto Mono', monospace;
}
&__sig {
display: flex;
flex-direction: column;
@@ -139,6 +204,7 @@
background: #fff;
padding: 2px 4px;
}
span {
font-size: 9px;
color: #94a3b8;
@@ -146,7 +212,14 @@
letter-spacing: 0.04em;
}
}
&__remove { color: #94a3b8 !important; &:hover { color: #ef4444 !important; } }
&__remove {
color: #94a3b8 !important;
&:hover {
color: #ef4444 !important;
}
}
}
.fm-fetching-hint {
@@ -201,8 +274,16 @@
padding: 10px 16px;
margin-bottom: 8px;
.fm-readonly-label { display: block; margin-bottom: 4px; }
.fm-readonly-value { font-size: 13px; color: #475569; line-height: 1.5; }
.fm-readonly-label {
display: block;
margin-bottom: 4px;
}
.fm-readonly-value {
font-size: 13px;
color: #475569;
line-height: 1.5;
}
}
.fm-kyc-badge {
@@ -225,6 +306,7 @@
background: #dcfce7;
color: #16a34a;
}
&.not-verified {
background: #fef3c7;
color: #d97706;
@@ -262,16 +344,22 @@
.zoomable {
cursor: zoom-in;
transition: transform 0.15s ease, box-shadow 0.15s ease;
transition:
transform 0.15s ease,
box-shadow 0.15s ease;
&:hover {
transform: scale(1.08);
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.18);
box-shadow: 0 4px 16px rgb(0 0 0 / 18%);
}
}
::ng-deep .image-zoom-panel {
.mat-mdc-dialog-container { padding: 0 !important; border-radius: 10px !important; overflow: hidden; }
.mat-mdc-dialog-container {
padding: 0 !important;
border-radius: 10px !important;
overflow: hidden;
}
}
.fm-file-upload {
@@ -290,7 +378,11 @@
color: #1074b9;
border-color: #cbd5e1;
mat-icon { font-size: 18px; width: 18px; height: 18px; }
mat-icon {
font-size: 18px;
width: 18px;
height: 18px;
}
}
.fm-file-hint {
@@ -308,7 +400,11 @@
border-radius: 6px;
width: 100%;
&__icon { color: #1074b9; font-size: 20px; }
&__icon {
color: #1074b9;
font-size: 20px;
}
&__name {
flex: 1;
font-size: 13px;
@@ -318,13 +414,21 @@
overflow: hidden;
text-overflow: ellipsis;
}
&__remove {
color: #94a3b8 !important;
width: 28px !important;
height: 28px !important;
line-height: 28px !important;
mat-icon { font-size: 16px; width: 16px; height: 16px; }
&:hover { color: #ef4444 !important; }
mat-icon {
font-size: 16px;
width: 16px;
height: 16px;
}
&:hover {
color: #ef4444 !important;
}
}
}
@@ -87,7 +87,9 @@ export class ClientFamilyMemberDialogComponent implements OnInit {
}
/** Whether dialog is in View-only mode */
get isViewMode(): boolean { return this.data.context === 'View'; }
get isViewMode(): boolean {
return this.data.context === 'View';
}
/** Get relationship name from ID */
get relationshipName(): string {
@@ -109,7 +111,7 @@ export class ClientFamilyMemberDialogComponent implements OnInit {
/** Get marriage authority display label */
getMarriageAuthorityLabel(value: string): string {
return this.marriageAuthorityOptions.find(a => a.value === value)?.label || value || '—';
return this.marriageAuthorityOptions.find((a) => a.value === value)?.label || value || '—';
}
/** Marriage certificate file */
@@ -150,7 +152,10 @@ export class ClientFamilyMemberDialogComponent implements OnInit {
});
if (m.linkedClientId) {
this.entryMode = 'linked';
this.selectedClient = { id: m.linkedClientId, displayName: m.linkedClientName || m.firstName || `Client #${m.linkedClientId}` };
this.selectedClient = {
id: m.linkedClientId,
displayName: m.linkedClientName || m.firstName || `Client #${m.linkedClientId}`
};
// Re-fetch linked client details (profile, signature, KYC status)
this.fetchLinkedClientData(m.linkedClientId.toString());
}
@@ -162,13 +167,22 @@ export class ClientFamilyMemberDialogComponent implements OnInit {
createFamilyMemberForm() {
this.familyMemberForm = this.formBuilder.group({
firstName: ['', Validators.required],
relationshipId: ['', Validators.required],
firstName: [
'',
Validators.required
],
relationshipId: [
'',
Validators.required
],
dateOfBirth: [''],
isDependent: [false],
remarks: [''],
aadhaarNumber: [''],
panNumber: ['', Validators.pattern(/^[A-Z]{5}[0-9]{4}[A-Z]{1}$/)],
panNumber: [
'',
Validators.pattern(/^[A-Z]{5}[0-9]{4}[A-Z]{1}$/)
],
nationality: ['INDIAN'],
isAlive: [true],
marriageDate: [''],
@@ -208,7 +222,10 @@ export class ClientFamilyMemberDialogComponent implements OnInit {
this.searching = false;
this.clientsService.enrichSearchResults(this.searchResults);
},
error: () => { this.searchResults = []; this.searching = false; }
error: () => {
this.searchResults = [];
this.searching = false;
}
});
}, 400);
}
@@ -242,7 +259,9 @@ export class ClientFamilyMemberDialogComponent implements OnInit {
this.setLinkedFieldsReadonly(true);
this.fetchingLinkedData = false;
},
error: () => { this.fetchingLinkedData = false; }
error: () => {
this.fetchingLinkedData = false;
}
});
// Fetch KYC (PAN, Aadhaar, verification status)
@@ -250,8 +269,12 @@ export class ClientFamilyMemberDialogComponent implements OnInit {
next: (kyc: any) => {
if (kyc) {
const patch: any = {};
if (kyc.panNumber) { patch.panNumber = kyc.panNumber; }
if (kyc.aadhaarNumberMasked) { patch.aadhaarNumber = kyc.aadhaarNumberMasked; }
if (kyc.panNumber) {
patch.panNumber = kyc.panNumber;
}
if (kyc.aadhaarNumberMasked) {
patch.aadhaarNumber = kyc.aadhaarNumberMasked;
}
this.familyMemberForm.patchValue(patch);
this.linkedKycStatus = {
panVerified: kyc.panVerified || false,
@@ -278,8 +301,8 @@ export class ClientFamilyMemberDialogComponent implements OnInit {
// Fetch signature image
this.clientsService.getClientDocuments(clientId).subscribe({
next: (docs: any[]) => {
const sigDoc = docs?.find((d: any) =>
d.name?.toLowerCase().includes('signature') || d.description?.toLowerCase().includes('signature')
const sigDoc = docs?.find(
(d: any) => d.name?.toLowerCase().includes('signature') || d.description?.toLowerCase().includes('signature')
);
if (sigDoc) {
this.clientsService.getClientSignatureImage(clientId, sigDoc.id.toString()).subscribe({
@@ -296,8 +319,13 @@ export class ClientFamilyMemberDialogComponent implements OnInit {
/** Disable/enable fields that are auto-populated from linked client */
private setLinkedFieldsReadonly(readonly: boolean): void {
const fields = ['firstName', 'dateOfBirth', 'aadhaarNumber', 'panNumber'];
fields.forEach(f => {
const fields = [
'firstName',
'dateOfBirth',
'aadhaarNumber',
'panNumber'
];
fields.forEach((f) => {
const ctrl = this.familyMemberForm.get(f);
if (ctrl) {
readonly ? ctrl.disable() : ctrl.enable();
@@ -313,7 +341,11 @@ export class ClientFamilyMemberDialogComponent implements OnInit {
this.linkedKycStatus = null;
this.setLinkedFieldsReadonly(false);
this.familyMemberForm.patchValue({
firstName: '', linkedClientId: '', dateOfBirth: '', aadhaarNumber: '', panNumber: ''
firstName: '',
linkedClientId: '',
dateOfBirth: '',
aadhaarNumber: '',
panNumber: ''
});
}
@@ -385,26 +417,49 @@ export class ClientFamilyMemberDialogComponent implements OnInit {
</div>
</div>
`,
styles: [`
.img-zoom-dialog { display: flex; flex-direction: column; }
.img-zoom-header {
display: flex; align-items: center; justify-content: space-between;
padding: 10px 16px; background: #f8fafc; border-bottom: 1px solid #e2e8f0;
font-size: 14px; font-weight: 600; color: #1e293b;
}
.img-zoom-body {
display: flex; align-items: center; justify-content: center;
padding: 20px; background: #fff; min-height: 200px;
}
.img-zoom-body img {
max-width: 100%; max-height: 70vh; object-fit: contain;
border-radius: 6px; box-shadow: 0 2px 12px rgba(0,0,0,0.1);
}
`],
imports: [MatIcon, STANDALONE_SHARED_IMPORTS]
styles: [
`
.img-zoom-dialog {
display: flex;
flex-direction: column;
}
.img-zoom-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px 16px;
background: #f8fafc;
border-bottom: 1px solid #e2e8f0;
font-size: 14px;
font-weight: 600;
color: #1e293b;
}
.img-zoom-body {
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
background: #fff;
min-height: 200px;
}
.img-zoom-body img {
max-width: 100%;
max-height: 70vh;
object-fit: contain;
border-radius: 6px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
}
`
],
imports: [
MatIcon,
STANDALONE_SHARED_IMPORTS
]
})
export class ImageZoomDialogComponent {
dialogRef = inject<MatDialogRef<ImageZoomDialogComponent>>(MatDialogRef);
data = inject(MAT_DIALOG_DATA);
close() { this.dialogRef.close(); }
close() {
this.dialogRef.close();
}
}
@@ -1,3 +1,11 @@
<!--
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/.
-->
<!--
Copyright since 2025 Mifos Initiative
-->
@@ -75,9 +83,13 @@
{{ member.dateOfBirth ? (member.dateOfBirth | dateFormat) : '—' }}
</span>
<span class="fm-table__col fm-table__col--id fm-mono">
@if (member.panNumber) { {{ member.panNumber }} }
@else if (member.aadhaarNumber) { {{ member.aadhaarNumber }} }
@else { — }
@if (member.panNumber) {
{{ member.panNumber }}
} @else if (member.aadhaarNumber) {
{{ member.aadhaarNumber }}
} @else {
—
}
</span>
<span class="fm-table__col fm-table__col--status">
@if (member.isAlive !== false) {
@@ -87,14 +99,32 @@
}
</span>
<span class="fm-table__col fm-table__col--actions">
<button mat-icon-button type="button" (click)="viewFamilyMember(member)" class="fm-action-btn" matTooltip="View">
<button
mat-icon-button
type="button"
(click)="viewFamilyMember(member)"
class="fm-action-btn"
matTooltip="View"
>
<mat-icon>visibility</mat-icon>
</button>
@if (!readOnly) {
<button mat-icon-button type="button" (click)="editFamilyMember(member, i)" class="fm-action-btn" matTooltip="Edit">
<button
mat-icon-button
type="button"
(click)="editFamilyMember(member, i)"
class="fm-action-btn"
matTooltip="Edit"
>
<mat-icon>edit</mat-icon>
</button>
<button mat-icon-button type="button" (click)="deleteFamilyMember(member.firstName, i)" class="fm-action-btn fm-action-btn--delete" matTooltip="Delete">
<button
mat-icon-button
type="button"
(click)="deleteFamilyMember(member.firstName, i)"
class="fm-action-btn fm-action-btn--delete"
matTooltip="Delete"
>
<mat-icon>delete</mat-icon>
</button>
}
@@ -103,5 +133,4 @@
}
</div>
}
</div>
@@ -1,11 +1,29 @@
/**
* 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/.
*/
.zoomable {
cursor: zoom-in;
transition: transform 0.15s ease, box-shadow 0.15s ease;
&:hover { transform: scale(1.08); box-shadow: 0 4px 16px rgba(0,0,0,0.18); }
transition:
transform 0.15s ease,
box-shadow 0.15s ease;
&:hover {
transform: scale(1.08);
box-shadow: 0 4px 16px rgb(0 0 0 / 18%);
}
}
::ng-deep .image-zoom-panel {
.mat-mdc-dialog-container { padding: 0 !important; border-radius: 10px !important; overflow: hidden; }
.mat-mdc-dialog-container {
padding: 0 !important;
border-radius: 10px !important;
overflow: hidden;
}
}
.search-no-results-hint {
@@ -42,7 +60,7 @@
&__sub {
font-size: 11px;
color: rgba(255, 255, 255, 0.5);
color: rgb(255 255 255 / 50%);
margin: 2px 0 0;
}
}
@@ -63,7 +81,10 @@
color: #1e293b;
}
.fm-step__icon { color: #1074b9; font-size: 14px; }
.fm-step__icon {
color: #1074b9;
font-size: 14px;
}
.fm-step__count {
background: #1074b9;
@@ -170,8 +191,13 @@
border-bottom: 1px solid #f1f5f9;
transition: background 0.1s;
&:last-child { border-bottom: none; }
&:hover { background: #f8fafc; }
&:last-child {
border-bottom: none;
}
&:hover {
background: #f8fafc;
}
}
.fm-table__col {
@@ -186,11 +212,31 @@
gap: 8px;
min-width: 0;
}
&--rel { flex: 0 0 130px; }
&--dob { flex: 0 0 120px; color: #64748b; font-size: 12px; }
&--id { flex: 0 0 140px; }
&--status { flex: 0 0 60px; text-align: center; }
&--actions { flex: 0 0 80px; display: flex; gap: 2px; }
&--rel {
flex: 0 0 130px;
}
&--dob {
flex: 0 0 120px;
color: #64748b;
font-size: 12px;
}
&--id {
flex: 0 0 140px;
}
&--status {
flex: 0 0 60px;
text-align: center;
}
&--actions {
flex: 0 0 80px;
display: flex;
gap: 2px;
}
}
.fm-table__avatar {
@@ -232,8 +278,15 @@
padding: 2px 8px;
border-radius: 10px;
&--yes { background: #ecfdf5; color: #059669; }
&--no { background: #fef2f2; color: #dc2626; }
&--yes {
background: #ecfdf5;
color: #059669;
}
&--no {
background: #fef2f2;
color: #dc2626;
}
}
.fm-action-btn {
@@ -248,11 +301,16 @@
color: #64748b;
}
&:hover mat-icon { color: #1074b9; }
&--delete:hover mat-icon { color: #ef4444; }
&:hover mat-icon {
color: #1074b9;
}
&--delete:hover mat-icon {
color: #ef4444;
}
}
@media (max-width: 768px) {
@media (width <= 768px) {
.fm-step__header {
flex-direction: column;
align-items: flex-start;
@@ -260,7 +318,10 @@
}
.fm-table__col {
&--type, &--id { display: none; }
&--type,
&--id {
display: none;
}
}
}
@@ -284,11 +345,27 @@
gap: 10px;
}
.nom-header__icon { font-size: 16px; color: #1565c0; }
.nom-header__title { font-size: 16px; font-weight: 600; color: #1e293b; display: block; }
.nom-header__sub { font-size: 11px; color: #94a3b8; display: block; }
.nom-header__icon {
font-size: 16px;
color: #1565c0;
}
.nom-add-btn { font-size: 13px; }
.nom-header__title {
font-size: 16px;
font-weight: 600;
color: #1e293b;
display: block;
}
.nom-header__sub {
font-size: 11px;
color: #94a3b8;
display: block;
}
.nom-add-btn {
font-size: 13px;
}
/* Share bar */
.nom-share-bar {
@@ -302,7 +379,11 @@
border-radius: 6px;
}
.nom-share-bar__label { font-size: 12px; color: #64748b; white-space: nowrap; }
.nom-share-bar__label {
font-size: 12px;
color: #64748b;
white-space: nowrap;
}
.nom-share-bar__track {
flex: 1;
@@ -317,8 +398,14 @@
background: #1565c0;
border-radius: 4px;
transition: width 0.3s;
&.complete { background: #059669; }
&.over { background: #dc2626; }
&.complete {
background: #059669;
}
&.over {
background: #dc2626;
}
}
.nom-share-bar__value {
@@ -327,7 +414,10 @@
color: #1565c0;
min-width: 36px;
text-align: right;
&.over { color: #dc2626; }
&.over {
color: #dc2626;
}
}
/* Nominee card */
@@ -346,23 +436,62 @@
}
.nom-card__avatar {
width: 36px; height: 36px;
width: 36px;
height: 36px;
border-radius: 50%;
background: #dbeafe;
display: flex; align-items: center; justify-content: center;
display: flex;
align-items: center;
justify-content: center;
color: #1565c0;
mat-icon { font-size: 20px; }
mat-icon {
font-size: 20px;
}
}
.nom-card__photo {
width: 100%;
height: 100%;
object-fit: cover;
border-radius: 50%;
}
.nom-card__info {
flex: 1;
}
.nom-card__name {
font-weight: 600;
font-size: 14px;
color: #1e293b;
display: block;
}
.nom-card__meta {
font-size: 12px;
color: #64748b;
display: block;
margin-top: 2px;
}
.nom-card__photo { width: 100%; height: 100%; object-fit: cover; border-radius: 50%; }
.nom-card__info { flex: 1; }
.nom-card__name { font-weight: 600; font-size: 14px; color: #1e293b; display: block; }
.nom-card__meta { font-size: 12px; color: #64748b; display: block; margin-top: 2px; }
.nom-card__sig {
flex-shrink: 0;
img { height: 24px; max-width: 60px; object-fit: contain; border: 1px solid #e2e8f0; border-radius: 4px; padding: 2px; }
img {
height: 24px;
max-width: 60px;
object-fit: contain;
border: 1px solid #e2e8f0;
border-radius: 4px;
padding: 2px;
}
}
.nom-card__actions {
display: flex;
gap: 2px;
}
.nom-card__actions { display: flex; gap: 2px; }
.nom-card__edit {
padding: 12px 16px;
@@ -391,8 +520,18 @@
min-width: 50px;
}
.nom-card__share-value { font-size: 18px; font-weight: 700; color: #1565c0; display: block; }
.nom-card__share-label { font-size: 10px; color: #94a3b8; text-transform: uppercase; }
.nom-card__share-value {
font-size: 18px;
font-weight: 700;
color: #1565c0;
display: block;
}
.nom-card__share-label {
font-size: 10px;
color: #94a3b8;
text-transform: uppercase;
}
.nom-card__guardian {
display: flex;
@@ -403,7 +542,13 @@
border-top: 1px solid #e0f2fe;
font-size: 12px;
color: #0c4a6e;
mat-icon { font-size: 16px; width: 16px; height: 16px; color: #0ea5e9; }
mat-icon {
font-size: 16px;
width: 16px;
height: 16px;
color: #0ea5e9;
}
}
/* Empty */
@@ -420,8 +565,16 @@
font-size: 14px;
}
.nom-empty__icon { font-size: 36px; width: 36px; height: 36px; color: #cbd5e1; }
.nom-empty__sub { font-size: 12px; }
.nom-empty__icon {
font-size: 36px;
width: 36px;
height: 36px;
color: #cbd5e1;
}
.nom-empty__sub {
font-size: 12px;
}
/* Nominee form */
.nom-form {
@@ -443,33 +596,47 @@
}
/* Search */
.nom-search-wrapper { position: relative; }
.nom-search-field { width: 100%; }
.nom-search-wrapper {
position: relative;
}
.nom-search-field {
width: 100%;
}
.nom-search-prefix {
display: inline-flex;
align-items: center;
padding-right: 8px;
mat-icon { font-size: 20px; color: #94a3b8; }
mat-icon {
font-size: 20px;
color: #94a3b8;
}
}
.nom-search-results {
position: absolute;
top: 100%;
left: 0; right: 0;
left: 0;
right: 0;
z-index: 10;
background: #fff;
border: 1px solid #e2e8f0;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
box-shadow: 0 4px 12px rgb(0 0 0 / 10%);
max-height: 240px;
overflow-y: auto;
margin-top: -12px;
}
.nom-search-loading {
display: flex; align-items: center; gap: 8px;
display: flex;
align-items: center;
gap: 8px;
padding: 12px 16px;
font-size: 13px; color: #64748b;
font-size: 13px;
color: #64748b;
}
.nom-search-item {
@@ -478,14 +645,43 @@
gap: 10px;
padding: 10px 16px;
cursor: pointer;
&:hover { background: #f1f5f9; }
&:hover {
background: #f1f5f9;
}
}
.nom-search-item__icon { color: #94a3b8; font-size: 20px; }
.nom-search-item__info { flex: 1; }
.nom-search-item__name { font-weight: 500; font-size: 13px; color: #1e293b; display: block; }
.nom-search-item__meta { font-size: 11px; color: #94a3b8; display: block; }
.nom-search-item__status { font-size: 11px; font-weight: 600; color: #94a3b8; &.active { color: #22c55e; } }
.nom-search-item__icon {
color: #94a3b8;
font-size: 20px;
}
.nom-search-item__info {
flex: 1;
}
.nom-search-item__name {
font-weight: 500;
font-size: 13px;
color: #1e293b;
display: block;
}
.nom-search-item__meta {
font-size: 11px;
color: #94a3b8;
display: block;
}
.nom-search-item__status {
font-size: 11px;
font-weight: 600;
color: #94a3b8;
&.active {
color: #22c55e;
}
}
/* Selected nominee */
.nom-selected {
@@ -505,30 +701,92 @@
}
.nom-selected__avatar {
width: 40px; height: 40px;
width: 40px;
height: 40px;
border-radius: 50%;
background: #dbeafe;
display: flex; align-items: center; justify-content: center;
display: flex;
align-items: center;
justify-content: center;
color: #1565c0;
overflow: hidden;
}
.nom-selected__photo { width: 100%; height: 100%; object-fit: cover; }
.nom-selected__info { flex: 1; }
.nom-selected__name { font-weight: 600; font-size: 14px; color: #1e293b; display: block; }
.nom-selected__meta { font-size: 12px; color: #64748b; display: block; }
.nom-selected__photo {
width: 100%;
height: 100%;
object-fit: cover;
}
.nom-selected__info {
flex: 1;
}
.nom-selected__name {
font-weight: 600;
font-size: 14px;
color: #1e293b;
display: block;
}
.nom-selected__meta {
font-size: 12px;
color: #64748b;
display: block;
}
.nom-selected__sig {
display: flex; flex-direction: column; align-items: center; gap: 2px; flex-shrink: 0;
img { height: 28px; max-width: 70px; object-fit: contain; border: 1px solid #e2e8f0; border-radius: 4px; background: #fff; padding: 2px 4px; }
span { font-size: 9px; color: #94a3b8; text-transform: uppercase; letter-spacing: 0.04em; }
display: flex;
flex-direction: column;
align-items: center;
gap: 2px;
flex-shrink: 0;
img {
height: 28px;
max-width: 70px;
object-fit: contain;
border: 1px solid #e2e8f0;
border-radius: 4px;
background: #fff;
padding: 2px 4px;
}
span {
font-size: 9px;
color: #94a3b8;
text-transform: uppercase;
letter-spacing: 0.04em;
}
}
.nom-selected__remove {
color: #94a3b8;
}
.nom-selected__loading {
display: flex;
align-items: center;
gap: 8px;
padding: 12px 16px;
font-size: 12px;
color: #64748b;
}
.nom-selected__remove { color: #94a3b8; }
.nom-selected__loading { display: flex; align-items: center; gap: 8px; padding: 12px 16px; font-size: 12px; color: #64748b; }
.nom-kyc-badge {
display: inline-flex; align-items: center; margin-left: 4px;
mat-icon { font-size: 14px; width: 14px; height: 14px; }
&.verified { color: #16a34a; }
display: inline-flex;
align-items: center;
margin-left: 4px;
mat-icon {
font-size: 14px;
width: 14px;
height: 14px;
}
&.verified {
color: #16a34a;
}
}
.nom-selected__details {
@@ -539,10 +797,29 @@
flex-wrap: wrap;
}
.nom-detail { display: flex; flex-direction: column; gap: 2px; }
.nom-detail__label { font-size: 10px; text-transform: uppercase; color: #94a3b8; letter-spacing: 0.5px; }
.nom-detail__value { font-size: 13px; font-weight: 500; color: #1e293b; }
.nom-detail__value.mono { font-family: 'Roboto Mono', monospace; font-size: 12px; }
.nom-detail {
display: flex;
flex-direction: column;
gap: 2px;
}
.nom-detail__label {
font-size: 10px;
text-transform: uppercase;
color: #94a3b8;
letter-spacing: 0.5px;
}
.nom-detail__value {
font-size: 13px;
font-weight: 500;
color: #1e293b;
}
.nom-detail__value.mono {
font-family: 'Roboto Mono', monospace;
font-size: 12px;
}
/* Form fields */
.nom-form-fields {
@@ -553,8 +830,13 @@
align-items: center;
}
.nom-form-field { width: 100%; }
.nom-minor-toggle { padding-top: 8px; }
.nom-form-field {
width: 100%;
}
.nom-minor-toggle {
padding-top: 8px;
}
/* Guardian */
.nom-guardian-section {
@@ -570,7 +852,11 @@
font-weight: 600;
color: #0c4a6e;
margin-bottom: 10px;
mat-icon { font-size: 18px; color: #0ea5e9; }
mat-icon {
font-size: 18px;
color: #0ea5e9;
}
}
.nom-guardian-card {
@@ -581,13 +867,32 @@
background: #f0f9ff;
border: 1px solid #bae6fd;
border-radius: 8px;
mat-icon { color: #0ea5e9; }
mat-icon {
color: #0ea5e9;
}
}
.nom-guardian-card__info { flex: 1; }
.nom-guardian-card__name { font-weight: 600; font-size: 13px; color: #1e293b; display: block; }
.nom-guardian-card__meta { font-size: 11px; color: #64748b; display: block; }
.nom-guardian-rel { width: 160px; }
.nom-guardian-card__info {
flex: 1;
}
.nom-guardian-card__name {
font-weight: 600;
font-size: 13px;
color: #1e293b;
display: block;
}
.nom-guardian-card__meta {
font-size: 11px;
color: #64748b;
display: block;
}
.nom-guardian-rel {
width: 160px;
}
/* Actions */
.nom-form-actions {
@@ -596,11 +901,24 @@
gap: 10px;
padding: 12px 16px;
border-top: 1px solid #f1f5f9;
button { display: flex; align-items: center; gap: 6px; }
button {
display: flex;
align-items: center;
gap: 6px;
}
}
@media (max-width: 768px) {
.nom-form-fields { grid-template-columns: 1fr; }
.nom-guardian-card { flex-wrap: wrap; }
.nom-guardian-rel { width: 100%; }
@media (width <= 768px) {
.nom-form-fields {
grid-template-columns: 1fr;
}
.nom-guardian-card {
flex-wrap: wrap;
}
.nom-guardian-rel {
width: 100%;
}
}
@@ -1,5 +1,9 @@
/**
* 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, Input, OnInit, inject } from '@angular/core';
@@ -88,9 +92,20 @@ export class ClientFamilyMembersStepComponent implements OnInit {
/** Relationship options */
relationshipOptions = [
'Father', 'Mother', 'Spouse', 'Son', 'Daughter',
'Brother', 'Sister', 'Uncle', 'Aunt', 'Grandfather',
'Grandmother', 'Friend', 'Business Partner', 'Other'
'Father',
'Mother',
'Spouse',
'Son',
'Daughter',
'Brother',
'Sister',
'Uncle',
'Aunt',
'Grandfather',
'Grandmother',
'Friend',
'Business Partner',
'Other'
];
ngOnInit(): void {
@@ -109,14 +124,18 @@ export class ClientFamilyMembersStepComponent implements OnInit {
this.loaded = true;
this.clientFamilyMembers = Array.isArray(data) ? data : [];
},
error: () => { this.loaded = true; }
error: () => {
this.loaded = true;
}
});
}
addFamilyMember() {
const ref = this.dialog.open(ClientFamilyMemberDialogComponent, {
data: { context: 'Add', options: this.clientTemplate.familyMemberOptions },
width: '640px', maxWidth: '95vw', panelClass: 'fm-dialog-panel'
width: '640px',
maxWidth: '95vw',
panelClass: 'fm-dialog-panel'
});
ref.afterClosed().subscribe((response: any) => {
if (response?.member) {
@@ -128,14 +147,18 @@ export class ClientFamilyMembersStepComponent implements OnInit {
viewFamilyMember(member: any) {
this.dialog.open(ClientFamilyMemberDialogComponent, {
data: { context: 'View', member, options: this.clientTemplate.familyMemberOptions },
width: '640px', maxWidth: '95vw', panelClass: 'fm-dialog-panel'
width: '640px',
maxWidth: '95vw',
panelClass: 'fm-dialog-panel'
});
}
editFamilyMember(member: any, index: number) {
const ref = this.dialog.open(ClientFamilyMemberDialogComponent, {
data: { context: 'Edit', member, options: this.clientTemplate.familyMemberOptions },
width: '640px', maxWidth: '95vw', panelClass: 'fm-dialog-panel'
width: '640px',
maxWidth: '95vw',
panelClass: 'fm-dialog-panel'
});
ref.afterClosed().subscribe((response: any) => {
if (response?.member) {
@@ -175,8 +198,7 @@ export class ClientFamilyMembersStepComponent implements OnInit {
return this.clientFamilyMembers.some((m: any) => {
const rel = m.relationship || m.relationshipId;
const relName = typeof rel === 'string' ? rel : '';
return relName.toLowerCase() === 'spouse' ||
m.firstName?.toLowerCase().includes('spouse');
return relName.toLowerCase() === 'spouse' || m.firstName?.toLowerCase().includes('spouse');
});
}
@@ -191,14 +213,19 @@ export class ClientFamilyMembersStepComponent implements OnInit {
const allMembers = Array.isArray(data) ? data : [];
this.nominees = allMembers.filter((m: any) => m.nominationType && m.nominationType !== '');
},
error: () => { this.nomineesLoaded = true; }
error: () => {
this.nomineesLoaded = true;
}
});
}
onNomineeSearch(text: string): void {
this.nomineeSearchText = text;
clearTimeout(this.nomineeSearchTimer);
if (!text || text.length < 2) { this.nomineeSearchResults = []; return; }
if (!text || text.length < 2) {
this.nomineeSearchResults = [];
return;
}
this.nomineeSearching = true;
this.nomineeSearchTimer = setTimeout(() => {
this.clientsService.searchByText(text, 0, 10).subscribe({
@@ -207,7 +234,10 @@ export class ClientFamilyMembersStepComponent implements OnInit {
this.nomineeSearching = false;
this.clientsService.enrichSearchResults(this.nomineeSearchResults);
},
error: () => { this.nomineeSearchResults = []; this.nomineeSearching = false; }
error: () => {
this.nomineeSearchResults = [];
this.nomineeSearching = false;
}
});
}, 400);
}
@@ -232,7 +262,9 @@ export class ClientFamilyMembersStepComponent implements OnInit {
// Fetch client data for DOB
this.clientsService.getClientData(clientId.toString()).subscribe({
next: (data: any) => {
if (data?.dateOfBirth) { details.dateOfBirth = data.dateOfBirth; }
if (data?.dateOfBirth) {
details.dateOfBirth = data.dateOfBirth;
}
done();
},
error: () => done()
@@ -253,7 +285,9 @@ export class ClientFamilyMembersStepComponent implements OnInit {
// Fetch profile image
this.clientsService.getClientProfileImage(clientId.toString()).subscribe({
next: (img: any) => {
if (img) { details.profileImageUrl = this.sanitizer.bypassSecurityTrustResourceUrl(img); }
if (img) {
details.profileImageUrl = this.sanitizer.bypassSecurityTrustResourceUrl(img);
}
done();
},
error: () => done()
@@ -262,8 +296,8 @@ export class ClientFamilyMembersStepComponent implements OnInit {
// Fetch signature image
this.clientsService.getClientDocuments(clientId.toString()).subscribe({
next: (docs: any[]) => {
const sigDoc = docs?.find((d: any) =>
d.name?.toLowerCase().includes('signature') || d.description?.toLowerCase().includes('signature')
const sigDoc = docs?.find(
(d: any) => d.name?.toLowerCase().includes('signature') || d.description?.toLowerCase().includes('signature')
);
if (sigDoc) {
this.clientsService.getClientSignatureImage(clientId.toString(), sigDoc.id.toString()).subscribe({
@@ -273,7 +307,9 @@ export class ClientFamilyMembersStepComponent implements OnInit {
},
error: () => done()
});
} else { done(); }
} else {
done();
}
},
error: () => done()
});
@@ -282,7 +318,10 @@ export class ClientFamilyMembersStepComponent implements OnInit {
onGuardianSearch(text: string): void {
this.guardianSearchText = text;
clearTimeout(this.guardianSearchTimer);
if (!text || text.length < 2) { this.guardianSearchResults = []; return; }
if (!text || text.length < 2) {
this.guardianSearchResults = [];
return;
}
this.guardianSearching = true;
this.guardianSearchTimer = setTimeout(() => {
this.clientsService.searchByText(text, 0, 10).subscribe({
@@ -291,7 +330,10 @@ export class ClientFamilyMembersStepComponent implements OnInit {
this.guardianSearching = false;
this.clientsService.enrichSearchResults(this.guardianSearchResults);
},
error: () => { this.guardianSearchResults = []; this.guardianSearching = false; }
error: () => {
this.guardianSearchResults = [];
this.guardianSearching = false;
}
});
}, 400);
}
@@ -315,7 +357,8 @@ export class ClientFamilyMembersStepComponent implements OnInit {
}
saveNominee(): void {
if (!this.clientId || !this.selectedNomineeClient || !this.nomineeRelationshipId || !this.nomineePercentageShare) return;
if (!this.clientId || !this.selectedNomineeClient || !this.nomineeRelationshipId || !this.nomineePercentageShare)
return;
if (this.totalSharePercentage > 100) return;
this.nomineeSaving = true;
@@ -360,7 +403,9 @@ export class ClientFamilyMembersStepComponent implements OnInit {
});
this.resetNomineeForm();
},
error: () => { this.nomineeSaving = false; }
error: () => {
this.nomineeSaving = false;
}
});
}
@@ -383,13 +428,13 @@ export class ClientFamilyMembersStepComponent implements OnInit {
/** Max share allowed for the nominee being edited (excludes its own share) */
maxShareForEdit(index: number): number {
const otherTotal = this.nominees.reduce((sum, n, i) => i === index ? sum : sum + (n.percentageShare || 0), 0);
const otherTotal = this.nominees.reduce((sum, n, i) => (i === index ? sum : sum + (n.percentageShare || 0)), 0);
return 100 - otherTotal;
}
saveEditNominee(nominee: any, index: number): void {
if (!this.editNomineeRelId || !this.editNomineeShare) return;
const otherTotal = this.nominees.reduce((sum, n, i) => i === index ? sum : sum + (n.percentageShare || 0), 0);
const otherTotal = this.nominees.reduce((sum, n, i) => (i === index ? sum : sum + (n.percentageShare || 0)), 0);
if (otherTotal + this.editNomineeShare > 100) return;
const relOption = this.clientTemplate?.familyMemberOptions?.relationshipIdOptions?.find(
@@ -413,8 +458,12 @@ export class ClientFamilyMembersStepComponent implements OnInit {
dateFormat: 'dd MMMM yyyy',
locale: 'en'
};
if (nominee.guardianName) { payload.guardianName = nominee.guardianName; }
if (nominee.guardianRelationship) { payload.guardianRelationship = nominee.guardianRelationship; }
if (nominee.guardianName) {
payload.guardianName = nominee.guardianName;
}
if (nominee.guardianRelationship) {
payload.guardianRelationship = nominee.guardianRelationship;
}
this.clientsService.editFamilyMember(this.clientId, nominee.id, payload).subscribe({ error: () => {} });
}