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
@@ -6,7 +6,6 @@
file, You can obtain one at http://mozilla.org/MPL/2.0/.
-->
<div class="nom-container mat-typography">
<!-- Header -->
<div class="nom-page-header">
<div class="nom-page-header__left">
@@ -39,9 +38,12 @@
<div class="nom-type-chips">
@for (group of accountGroups; track group.type) {
@if (group.accounts.length > 0) {
<button type="button" class="nom-type-chip"
<button
type="button"
class="nom-type-chip"
[class.active]="selectedType === group.type"
(click)="selectedType = group.type; onTypeChange()">
(click)="selectedType = group.type; onTypeChange()"
>
<mat-icon>{{ group.icon }}</mat-icon>
{{ group.label }}
<span class="nom-type-chip__count">{{ group.accounts.length }}</span>
@@ -55,13 +57,23 @@
<mat-label>Account</mat-label>
<mat-select [(ngModel)]="selectedAccountId" (ngModelChange)="onAccountChange()">
@for (acc of selectedGroup.accounts; track acc.id) {
<mat-option [value]="acc.id">{{ acc.productName }} — {{ acc.accountNo }}@if (acc.accountBalance != null) { ({{ acc.accountBalance | number:'1.2-2' }}) }</mat-option>
<mat-option [value]="acc.id"
>{{ acc.productName }} — {{ acc.accountNo }}
@if (acc.accountBalance != null) {
({{ acc.accountBalance | number: '1.2-2' }})
}
</mat-option>
}
</mat-select>
</mat-form-field>
@if (selectedAccountId && !showAddForm) {
<button mat-flat-button color="primary" class="nom-add-btn"
[disabled]="totalShare >= 100" (click)="showAddForm = true">
<button
mat-flat-button
color="primary"
class="nom-add-btn"
[disabled]="totalShare >= 100"
(click)="showAddForm = true"
>
<mat-icon>add</mat-icon> Add Nominee
</button>
}
@@ -83,10 +95,12 @@
<div class="nom-share-bar">
<span class="nom-share-bar__label">Share</span>
<div class="nom-share-bar__track">
<div class="nom-share-bar__fill"
<div
class="nom-share-bar__fill"
[style.width.%]="totalShare > 100 ? 100 : totalShare"
[class.over]="totalShare > 100"
[class.complete]="totalShare === 100"></div>
[class.complete]="totalShare === 100"
></div>
</div>
<span class="nom-share-bar__value" [class.over]="totalShare > 100">{{ totalShare }}%</span>
</div>
@@ -97,8 +111,12 @@
<div class="nom-card__main">
<div class="nom-card__avatar">
@if (nominee.profileImageUrl) {
<img [src]="nominee.profileImageUrl" alt="" class="nom-card__photo zoomable"
(click)="zoomImage(nominee.profileImageUrl, 'Nominee Photo')" />
<img
[src]="nominee.profileImageUrl"
alt=""
class="nom-card__photo zoomable"
(click)="zoomImage(nominee.profileImageUrl, 'Nominee Photo')"
/>
} @else {
<mat-icon>person</mat-icon>
}
@@ -107,14 +125,22 @@
<span class="nom-card__name">{{ nominee.nomineeDisplayName || nominee.firstName || '—' }}</span>
<span class="nom-card__meta">
{{ getRelationship(nominee) }}
@if (nominee.nomineeDateOfBirth || nominee.dateOfBirth) { · DOB: {{ (nominee.nomineeDateOfBirth || nominee.dateOfBirth) | dateFormat }} }
@if (nominee.nomineeMobileNo || nominee.mobileNo) { · {{ nominee.nomineeMobileNo || nominee.mobileNo }} }
@if (nominee.nomineeDateOfBirth || nominee.dateOfBirth) {
· DOB: {{ nominee.nomineeDateOfBirth || nominee.dateOfBirth | dateFormat }}
}
@if (nominee.nomineeMobileNo || nominee.mobileNo) {
· {{ nominee.nomineeMobileNo || nominee.mobileNo }}
}
</span>
</div>
@if (nominee.signatureUrl) {
<div class="nom-card__sig">
<img [src]="nominee.signatureUrl" alt="Signature" class="zoomable"
(click)="zoomImage(nominee.signatureUrl, 'Nominee Signature')" />
<img
[src]="nominee.signatureUrl"
alt="Signature"
class="zoomable"
(click)="zoomImage(nominee.signatureUrl, 'Nominee Signature')"
/>
</div>
}
@if (nominee.nomineeIdentifier) {
@@ -124,16 +150,30 @@
<span class="nom-card__share-value">{{ nominee.percentageShare || 0 }}%</span>
</div>
<div class="nom-card__actions">
<button mat-icon-button type="button" matTooltip="View" (click)="viewNominee(nominee)"><mat-icon>visibility</mat-icon></button>
<button mat-icon-button type="button" matTooltip="Edit" (click)="editNominee(nominee, i)"><mat-icon>edit</mat-icon></button>
<button mat-icon-button type="button" matTooltip="Delete" class="nom-card__delete" (click)="deleteNominee(nominee, i)"><mat-icon>delete</mat-icon></button>
<button mat-icon-button type="button" matTooltip="View" (click)="viewNominee(nominee)">
<mat-icon>visibility</mat-icon>
</button>
<button mat-icon-button type="button" matTooltip="Edit" (click)="editNominee(nominee, i)">
<mat-icon>edit</mat-icon>
</button>
<button
mat-icon-button
type="button"
matTooltip="Delete"
class="nom-card__delete"
(click)="deleteNominee(nominee, i)"
>
<mat-icon>delete</mat-icon>
</button>
</div>
</div>
@if (nominee.nomineeIsMinor && (nominee.guardianDisplayName || nominee.guardianName)) {
<div class="nom-card__guardian">
<mat-icon>shield</mat-icon>
Guardian: <strong>{{ nominee.guardianDisplayName || nominee.guardianName }}</strong>
@if (nominee.guardianRelationshipName) { ({{ nominee.guardianRelationshipName }}) }
@if (nominee.guardianRelationshipName) {
({{ nominee.guardianRelationshipName }})
}
</div>
}
<!-- Inline Edit -->
@@ -143,24 +183,39 @@
<mat-form-field appearance="outline" class="nom-edit-field">
<mat-label>Type</mat-label>
<mat-select [(ngModel)]="editNomType">
@for (opt of nominationTypeOptions; track opt.value) { <mat-option [value]="opt.value">{{ opt.label }}</mat-option> }
@for (opt of nominationTypeOptions; track opt.value) {
<mat-option [value]="opt.value">{{ opt.label }}</mat-option>
}
</mat-select>
</mat-form-field>
<mat-form-field appearance="outline" class="nom-edit-field">
<mat-label>Relationship</mat-label>
<mat-select [(ngModel)]="editRelId">
@for (rel of relationshipOptions; track rel) { <mat-option [value]="rel.id">{{ rel.name }}</mat-option> }
@for (rel of relationshipOptions; track rel) {
<mat-option [value]="rel.id">{{ rel.name }}</mat-option>
}
</mat-select>
</mat-form-field>
<mat-form-field appearance="outline" class="nom-edit-field nom-edit-field--sm">
<mat-label>Share %</mat-label>
<input matInput type="number" [(ngModel)]="editShare" min="1" [max]="maxShareForEdit(i)" />
@if (editShare && editShare > maxShareForEdit(i)) { <mat-error>Max {{ maxShareForEdit(i) }}%</mat-error> }
@if (editShare && editShare > maxShareForEdit(i)) {
<mat-error>Max {{ maxShareForEdit(i) }}%</mat-error>
}
</mat-form-field>
<button mat-flat-button color="primary" type="button" class="nom-edit-save"
<button
mat-flat-button
color="primary"
type="button"
class="nom-edit-save"
[disabled]="!editRelId || !editShare || editShare > maxShareForEdit(i)"
(click)="saveEdit(nominee, i)"><mat-icon>check</mat-icon></button>
<button mat-stroked-button type="button" class="nom-edit-cancel" (click)="cancelEdit()"><mat-icon>close</mat-icon></button>
(click)="saveEdit(nominee, i)"
>
<mat-icon>check</mat-icon>
</button>
<button mat-stroked-button type="button" class="nom-edit-cancel" (click)="cancelEdit()">
<mat-icon>close</mat-icon>
</button>
</div>
</div>
}
@@ -185,17 +240,39 @@
<mat-form-field appearance="outline" class="nom-search-field">
<mat-label>Search by Name, Client ID or Mobile</mat-label>
<span matPrefix class="nom-search-prefix"><mat-icon>search</mat-icon></span>
<input matInput [value]="searchText" (input)="onSearch($any($event.target).value)" (blur)="dismissSearch()" placeholder="Type at least 2 characters..." autocomplete="off" />
<input
matInput
[value]="searchText"
(input)="onSearch($any($event.target).value)"
(blur)="dismissSearch()"
placeholder="Type at least 2 characters..."
autocomplete="off"
/>
</mat-form-field>
@if (searchResults.length > 0 || searching) {
<div class="nom-search-results">
@if (searching) { <div class="nom-search-loading"><mat-progress-spinner diameter="16" mode="indeterminate"></mat-progress-spinner> Searching...</div> }
@if (searching) {
<div class="nom-search-loading">
<mat-progress-spinner diameter="16" mode="indeterminate"></mat-progress-spinner> Searching...
</div>
}
@for (client of searchResults; track client.id) {
<div class="nom-search-item" (mousedown)="selectClient(client); $event.preventDefault()">
<mat-icon class="nom-search-item__icon">person</mat-icon>
<div class="nom-search-item__info">
<span class="nom-search-item__name">{{ client.displayName }}</span>
<span class="nom-search-item__meta">ID: {{ client.id }} · {{ client.officeName }}@if (client.dateOfBirth) { · DOB: {{ client.dateOfBirth | dateFormat }} }@if (client.fatherSpouseName) { · {{ client.fatherSpouseName }} }@if (client.mobileNo) { · {{ client.mobileNo }} }</span>
<span class="nom-search-item__meta"
>ID: {{ client.id }} · {{ client.officeName }}
@if (client.dateOfBirth) {
· DOB: {{ client.dateOfBirth | dateFormat }}
}
@if (client.fatherSpouseName) {
· {{ client.fatherSpouseName }}
}
@if (client.mobileNo) {
· {{ client.mobileNo }}
}
</span>
</div>
</div>
}
@@ -212,32 +289,68 @@
<div class="nom-linked-card">
<div class="nom-linked-card__left">
@if (linkedProfileImage) {
<img [src]="linkedProfileImage" class="nom-linked-card__photo zoomable" (click)="zoomImage(linkedProfileImage, 'Photo')" />
} @else { <mat-icon class="nom-linked-card__icon">person</mat-icon> }
<img
[src]="linkedProfileImage"
class="nom-linked-card__photo zoomable"
(click)="zoomImage(linkedProfileImage, 'Photo')"
/>
} @else {
<mat-icon class="nom-linked-card__icon">person</mat-icon>
}
</div>
<div class="nom-linked-card__info">
<span class="nom-linked-card__name">{{ selectedClient.displayName }}</span>
<span class="nom-linked-card__meta">ID: {{ selectedClient.id }}@if (selectedClient.mobileNo) { · {{ selectedClient.mobileNo }} }</span>
<span class="nom-linked-card__meta"
>ID: {{ selectedClient.id }}
@if (selectedClient.mobileNo) {
· {{ selectedClient.mobileNo }}
}
</span>
</div>
@if (linkedSignatureImage) {
<div class="nom-linked-card__sig">
<img [src]="linkedSignatureImage" alt="Signature" class="zoomable" (click)="zoomImage(linkedSignatureImage, 'Signature')" />
<img
[src]="linkedSignatureImage"
alt="Signature"
class="zoomable"
(click)="zoomImage(linkedSignatureImage, 'Signature')"
/>
</div>
}
<button mat-icon-button type="button" (click)="removeSelectedClient()"><mat-icon>close</mat-icon></button>
</div>
@if (fetchingDetails) {
<div class="nom-fetching-hint"><mat-progress-spinner diameter="14" mode="indeterminate"></mat-progress-spinner> Fetching details...</div>
<div class="nom-fetching-hint">
<mat-progress-spinner diameter="14" mode="indeterminate"></mat-progress-spinner> Fetching details...
</div>
}
@if (!fetchingDetails) {
<!-- KYC details -->
@if (selectedClient.dateOfBirth || selectedClient.panNumber || selectedClient.aadhaarMasked) {
<div class="nom-readonly-row">
@if (selectedClient.dateOfBirth) { <span class="nom-ro-item"><span class="nom-ro-label">DOB</span> {{ selectedClient.dateOfBirth | dateFormat }}</span> }
@if (selectedClient.panNumber) { <span class="nom-ro-item"><span class="nom-ro-label">PAN</span> {{ selectedClient.panNumber }} @if (linkedKycStatus?.panVerified) { <mat-icon class="nom-ro-ok">check_circle</mat-icon> }</span> }
@if (selectedClient.aadhaarMasked) { <span class="nom-ro-item"><span class="nom-ro-label">Aadhaar</span> {{ selectedClient.aadhaarMasked }} @if (linkedKycStatus?.aadhaarVerified) { <mat-icon class="nom-ro-ok">check_circle</mat-icon> }</span> }
@if (selectedClient.dateOfBirth) {
<span class="nom-ro-item"
><span class="nom-ro-label">DOB</span> {{ selectedClient.dateOfBirth | dateFormat }}</span
>
}
@if (selectedClient.panNumber) {
<span class="nom-ro-item"
><span class="nom-ro-label">PAN</span> {{ selectedClient.panNumber }}
@if (linkedKycStatus?.panVerified) {
<mat-icon class="nom-ro-ok">check_circle</mat-icon>
}
</span>
}
@if (selectedClient.aadhaarMasked) {
<span class="nom-ro-item"
><span class="nom-ro-label">Aadhaar</span> {{ selectedClient.aadhaarMasked }}
@if (linkedKycStatus?.aadhaarVerified) {
<mat-icon class="nom-ro-ok">check_circle</mat-icon>
}
</span>
}
</div>
}
@@ -246,19 +359,25 @@
<mat-form-field appearance="outline" class="nom-field-item">
<mat-label>Nomination Type</mat-label>
<mat-select [(ngModel)]="nomineeNomType">
@for (opt of nominationTypeOptions; track opt.value) { <mat-option [value]="opt.value">{{ opt.label }}</mat-option> }
@for (opt of nominationTypeOptions; track opt.value) {
<mat-option [value]="opt.value">{{ opt.label }}</mat-option>
}
</mat-select>
</mat-form-field>
<mat-form-field appearance="outline" class="nom-field-item">
<mat-label>Relationship</mat-label>
<mat-select [(ngModel)]="nomineeRelationshipId">
@for (rel of relationshipOptions; track rel) { <mat-option [value]="rel.id">{{ rel.name }}</mat-option> }
@for (rel of relationshipOptions; track rel) {
<mat-option [value]="rel.id">{{ rel.name }}</mat-option>
}
</mat-select>
</mat-form-field>
<mat-form-field appearance="outline" class="nom-field-item nom-field-item--sm">
<mat-label>Share %</mat-label>
<input matInput type="number" [(ngModel)]="nomineeShare" min="1" [max]="maxAllowedShare" />
@if (nomineeShare && nomineeShare > maxAllowedShare) { <mat-error>Max {{ maxAllowedShare }}%</mat-error> }
@if (nomineeShare && nomineeShare > maxAllowedShare) {
<mat-error>Max {{ maxAllowedShare }}%</mat-error>
}
</mat-form-field>
<div class="nom-field-item nom-field-item--toggle">
<mat-slide-toggle [(ngModel)]="nomineeIsMinor">Minor</mat-slide-toggle>
@@ -270,11 +389,21 @@
<span class="nom-guardian-label"><mat-icon>shield</mat-icon> Guardian</span>
@if (!selectedGuardian) {
<mat-form-field appearance="outline" class="nom-search-field">
<input matInput [ngModel]="guardianSearchText" (ngModelChange)="onGuardianSearch($event)" placeholder="Search guardian..." autocomplete="off" />
<input
matInput
[ngModel]="guardianSearchText"
(ngModelChange)="onGuardianSearch($event)"
placeholder="Search guardian..."
autocomplete="off"
/>
</mat-form-field>
@if (guardianSearchResults.length > 0 || guardianSearching) {
<div class="nom-search-results nom-search-results--inline">
@if (guardianSearching) { <div class="nom-search-loading"><mat-progress-spinner diameter="14" mode="indeterminate"></mat-progress-spinner></div> }
@if (guardianSearching) {
<div class="nom-search-loading">
<mat-progress-spinner diameter="14" mode="indeterminate"></mat-progress-spinner>
</div>
}
@for (client of guardianSearchResults; track client.id) {
<div class="nom-search-item" (click)="selectGuardian(client)">
<mat-icon class="nom-search-item__icon">person</mat-icon>
@@ -284,7 +413,12 @@
</div>
}
} @else {
<span class="nom-guardian-selected">{{ selectedGuardian.displayName }} <button mat-icon-button (click)="selectedGuardian = null"><mat-icon>close</mat-icon></button></span>
<span class="nom-guardian-selected"
>{{ selectedGuardian.displayName }}
<button mat-icon-button (click)="selectedGuardian = null">
<mat-icon>close</mat-icon>
</button></span
>
}
</div>
}
@@ -296,11 +430,24 @@
<div class="nom-form-actions">
<button mat-stroked-button type="button" (click)="resetAddForm()">Cancel</button>
<button mat-raised-button color="primary" type="button"
[disabled]="!nomineeRelationshipId || !nomineeShare || nomineeSaving || (nomineeShare > maxAllowedShare) || (nomineeIsMinor && !selectedGuardian)"
(click)="saveNominee()">
@if (nomineeSaving) { <mat-progress-spinner diameter="16" mode="indeterminate"></mat-progress-spinner> }
@else { <mat-icon>check</mat-icon> Save }
<button
mat-raised-button
color="primary"
type="button"
[disabled]="
!nomineeRelationshipId ||
!nomineeShare ||
nomineeSaving ||
nomineeShare > maxAllowedShare ||
(nomineeIsMinor && !selectedGuardian)
"
(click)="saveNominee()"
>
@if (nomineeSaving) {
<mat-progress-spinner diameter="16" mode="indeterminate"></mat-progress-spinner>
} @else {
<mat-icon>check</mat-icon> Save
}
</button>
</div>
}
@@ -8,176 +8,708 @@
$primary: #1074b9;
$primary-light: #e8f0fe;
.nom-container { padding: 16px 24px; }
.nom-container {
padding: 16px 24px;
}
/* Header */
.nom-page-header {
background: #0f172a; padding: 14px 20px; border-radius: 10px; margin-bottom: 16px;
&__left { display: flex; align-items: center; gap: 10px; }
&__icon { color: #60a5fa; }
&__title { font-size: 15px; font-weight: 600; color: #fff; margin: 0; }
&__sub { font-size: 10px; color: rgba(255,255,255,0.45); margin: 1px 0 0; }
background: #0f172a;
padding: 14px 20px;
border-radius: 10px;
margin-bottom: 16px;
&__left {
display: flex;
align-items: center;
gap: 10px;
}
&__icon {
color: #60a5fa;
}
&__title {
font-size: 15px;
font-weight: 600;
color: #fff;
margin: 0;
}
&__sub {
font-size: 10px;
color: rgb(255 255 255 / 45%);
margin: 1px 0 0;
}
}
.nom-loading { display: flex; align-items: center; gap: 8px; padding: 24px 0; justify-content: center; font-size: 13px; color: #64748b;
&--sm { padding: 12px 0; }
.nom-loading {
display: flex;
align-items: center;
gap: 8px;
padding: 24px 0;
justify-content: center;
font-size: 13px;
color: #64748b;
&--sm {
padding: 12px 0;
}
}
.nom-no-accounts {
text-align: center; padding: 36px 20px; background: #f8fafc; border-radius: 8px; border: 1px dashed #e2e8f0;
&__icon { font-size: 40px; width: 40px; height: 40px; color: #94a3b8; margin-bottom: 8px; }
p { color: #64748b; font-size: 13px; margin: 2px 0; }
&__sub { font-size: 11px; color: #94a3b8; }
text-align: center;
padding: 36px 20px;
background: #f8fafc;
border-radius: 8px;
border: 1px dashed #e2e8f0;
&__icon {
font-size: 40px;
width: 40px;
height: 40px;
color: #94a3b8;
margin-bottom: 8px;
}
p {
color: #64748b;
font-size: 13px;
margin: 2px 0;
}
&__sub {
font-size: 11px;
color: #94a3b8;
}
}
/* Toolbar: chips on top, account + add below */
.nom-toolbar {
display: flex; flex-direction: column; gap: 10px; margin-bottom: 12px;
&__right { display: flex; align-items: center; gap: 10px; }
}
.nom-type-chips { display: flex; gap: 6px; flex-wrap: wrap; }
.nom-type-chip {
display: inline-flex; align-items: center; gap: 5px; padding: 6px 12px;
border: 1.5px solid #e2e8f0; border-radius: 6px; background: #fff;
font-size: 12px; font-weight: 500; color: #64748b; cursor: pointer; transition: all 0.15s;
mat-icon { font-size: 16px; width: 16px; height: 16px; }
&__count { font-size: 9px; font-weight: 700; background: #f1f5f9; color: #64748b; padding: 0 5px; border-radius: 6px; }
&.active { border-color: $primary; background: $primary-light; color: $primary;
.nom-type-chip__count { background: $primary; color: #fff; }
display: flex;
flex-direction: column;
gap: 10px;
margin-bottom: 12px;
&__right {
display: flex;
align-items: center;
gap: 10px;
}
&:hover:not(.active) { border-color: #94a3b8; }
}
.nom-account-select { flex: 1; min-width: 280px; }
.nom-add-btn { border-radius: 6px !important; font-size: 12px !important; height: 36px !important; white-space: nowrap; }
.nom-type-chips {
display: flex;
gap: 6px;
flex-wrap: wrap;
}
.nom-type-chip {
display: inline-flex;
align-items: center;
gap: 5px;
padding: 6px 12px;
border: 1.5px solid #e2e8f0;
border-radius: 6px;
background: #fff;
font-size: 12px;
font-weight: 500;
color: #64748b;
cursor: pointer;
transition: all 0.15s;
mat-icon {
font-size: 16px;
width: 16px;
height: 16px;
}
&__count {
font-size: 9px;
font-weight: 700;
background: #f1f5f9;
color: #64748b;
padding: 0 5px;
border-radius: 6px;
}
&.active {
border-color: $primary;
background: $primary-light;
color: $primary;
.nom-type-chip__count {
background: $primary;
color: #fff;
}
}
&:hover:not(.active) {
border-color: #94a3b8;
}
}
.nom-account-select {
flex: 1;
min-width: 280px;
}
.nom-add-btn {
border-radius: 6px !important;
font-size: 12px !important;
height: 36px !important;
white-space: nowrap;
}
/* Share bar */
.nom-share-bar {
display: flex; align-items: center; gap: 8px; padding: 6px 12px;
background: #f8fafc; border: 1px solid #e2e8f0; border-radius: 6px; margin-bottom: 8px;
&__label { font-size: 11px; font-weight: 600; color: #64748b; white-space: nowrap; }
&__track { flex: 1; height: 6px; background: #e2e8f0; border-radius: 3px; overflow: hidden; }
display: flex;
align-items: center;
gap: 8px;
padding: 6px 12px;
background: #f8fafc;
border: 1px solid #e2e8f0;
border-radius: 6px;
margin-bottom: 8px;
&__label {
font-size: 11px;
font-weight: 600;
color: #64748b;
white-space: nowrap;
}
&__track {
flex: 1;
height: 6px;
background: #e2e8f0;
border-radius: 3px;
overflow: hidden;
}
&__fill {
height: 100%; border-radius: 3px; transition: width 0.3s;
height: 100%;
border-radius: 3px;
transition: width 0.3s;
background: linear-gradient(90deg, #3b82f6, #10b981);
&.over { background: #ef4444; } &.complete { background: linear-gradient(90deg, #10b981, #059669); }
&.over {
background: #ef4444;
}
&.complete {
background: linear-gradient(90deg, #10b981, #059669);
}
}
&__value {
font-size: 13px;
font-weight: 700;
color: #10b981;
min-width: 35px;
text-align: right;
&.over {
color: #ef4444;
}
}
&__value { font-size: 13px; font-weight: 700; color: #10b981; min-width: 35px; text-align: right; &.over { color: #ef4444; } }
}
/* Nominee card — compact */
.nom-card { border: 1px solid #e2e8f0; border-radius: 6px; margin-bottom: 6px; overflow: hidden; }
.nom-card__main { display: flex; align-items: center; gap: 10px; padding: 8px 12px; }
.nom-card {
border: 1px solid #e2e8f0;
border-radius: 6px;
margin-bottom: 6px;
overflow: hidden;
}
.nom-card__main {
display: flex;
align-items: center;
gap: 10px;
padding: 8px 12px;
}
.nom-card__avatar {
width: 32px; height: 32px; border-radius: 50%; background: #dbeafe;
display: flex; align-items: center; justify-content: center; color: #1565c0; flex-shrink: 0;
mat-icon { font-size: 18px; }
}
.nom-card__photo { width: 100%; height: 100%; object-fit: cover; border-radius: 50%; }
.nom-card__sig {
width: 32px;
height: 32px;
border-radius: 50%;
background: #dbeafe;
display: flex;
align-items: center;
justify-content: center;
color: #1565c0;
flex-shrink: 0;
img { height: 24px; max-width: 60px; object-fit: contain; border: 1px solid #e2e8f0; border-radius: 3px; background: #fff; padding: 1px 3px; }
}
.nom-card__info { flex: 1; min-width: 0; }
.nom-card__name { font-weight: 600; font-size: 13px; color: #1e293b; display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.nom-card__meta { font-size: 11px; color: #64748b; display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.nom-card__id {
font-size: 10px; font-weight: 600; color: #1d4ed8; background: #eff6ff;
padding: 2px 8px; border-radius: 4px; white-space: nowrap;
font-family: 'Roboto Mono', monospace; letter-spacing: 0.3px; flex-shrink: 0;
}
.nom-card__share { text-align: center; min-width: 40px; flex-shrink: 0; }
.nom-card__share-value { font-size: 15px; font-weight: 700; color: #1565c0; }
.nom-card__actions { display: flex; gap: 0; flex-shrink: 0;
button { width: 32px !important; height: 32px !important; line-height: 32px !important;
mat-icon { font-size: 18px; }
mat-icon {
font-size: 18px;
}
}
.nom-card__delete { color: #94a3b8 !important; &:hover { color: #ef4444 !important; } }
.nom-card__photo {
width: 100%;
height: 100%;
object-fit: cover;
border-radius: 50%;
}
.nom-card__sig {
flex-shrink: 0;
img {
height: 24px;
max-width: 60px;
object-fit: contain;
border: 1px solid #e2e8f0;
border-radius: 3px;
background: #fff;
padding: 1px 3px;
}
}
.nom-card__info {
flex: 1;
min-width: 0;
}
.nom-card__name {
font-weight: 600;
font-size: 13px;
color: #1e293b;
display: block;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.nom-card__meta {
font-size: 11px;
color: #64748b;
display: block;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.nom-card__id {
font-size: 10px;
font-weight: 600;
color: #1d4ed8;
background: #eff6ff;
padding: 2px 8px;
border-radius: 4px;
white-space: nowrap;
font-family: 'Roboto Mono', monospace;
letter-spacing: 0.3px;
flex-shrink: 0;
}
.nom-card__share {
text-align: center;
min-width: 40px;
flex-shrink: 0;
}
.nom-card__share-value {
font-size: 15px;
font-weight: 700;
color: #1565c0;
}
.nom-card__actions {
display: flex;
gap: 0;
flex-shrink: 0;
button {
width: 32px !important;
height: 32px !important;
line-height: 32px !important;
mat-icon {
font-size: 18px;
}
}
}
.nom-card__delete {
color: #94a3b8 !important;
&:hover {
color: #ef4444 !important;
}
}
.nom-card__guardian {
display: flex; align-items: center; gap: 5px; padding: 5px 12px; background: #fff7ed;
font-size: 11px; color: #92400e; border-top: 1px solid #fde68a;
mat-icon { font-size: 14px; width: 14px; height: 14px; }
display: flex;
align-items: center;
gap: 5px;
padding: 5px 12px;
background: #fff7ed;
font-size: 11px;
color: #92400e;
border-top: 1px solid #fde68a;
mat-icon {
font-size: 14px;
width: 14px;
height: 14px;
}
}
/* Inline edit */
.nom-card__edit { padding: 8px 12px; border-top: 1px solid #e2e8f0; background: #f8fafc; }
.nom-edit-row { display: flex; align-items: center; gap: 8px; }
.nom-edit-field { flex: 1; &--sm { max-width: 100px; } }
.nom-edit-save, .nom-edit-cancel { min-width: 36px !important; height: 36px !important; padding: 0 !important; }
.nom-card__edit {
padding: 8px 12px;
border-top: 1px solid #e2e8f0;
background: #f8fafc;
}
.nom-edit-row {
display: flex;
align-items: center;
gap: 8px;
}
.nom-edit-field {
flex: 1;
&--sm {
max-width: 100px;
}
}
.nom-edit-save,
.nom-edit-cancel {
min-width: 36px !important;
height: 36px !important;
padding: 0 !important;
}
/* Empty */
.nom-empty {
text-align: center; padding: 24px; background: #f8fafc; border-radius: 6px; border: 1px dashed #e2e8f0;
mat-icon { font-size: 28px; width: 28px; height: 28px; color: #94a3b8; margin-bottom: 4px; display: block; margin: 0 auto 4px; }
span { font-size: 12px; color: #94a3b8; }
text-align: center;
padding: 24px;
background: #f8fafc;
border-radius: 6px;
border: 1px dashed #e2e8f0;
mat-icon {
font-size: 28px;
width: 28px;
height: 28px;
color: #94a3b8;
display: block;
margin: 0 auto 4px;
}
span {
font-size: 12px;
color: #94a3b8;
}
}
/* ═══ Add Form ═══ */
.nom-add-form {
border: 1px solid #e2e8f0; border-radius: 8px; padding: 14px; margin-top: 8px; background: #fafbfc;
&__title { display: flex; align-items: center; gap: 6px; font-size: 13px; font-weight: 600; color: #1e293b; margin-bottom: 12px; }
border: 1px solid #e2e8f0;
border-radius: 8px;
padding: 14px;
margin-top: 8px;
background: #fafbfc;
&__title {
display: flex;
align-items: center;
gap: 6px;
font-size: 13px;
font-weight: 600;
color: #1e293b;
margin-bottom: 12px;
}
}
/* Search */
.nom-search-wrapper { position: relative; margin-bottom: 8px; }
.nom-search-field { width: 100%; }
.nom-search-prefix { color: #94a3b8; }
.nom-search-wrapper {
position: relative;
margin-bottom: 8px;
}
.nom-search-field {
width: 100%;
}
.nom-search-prefix {
color: #94a3b8;
}
.nom-search-results {
position: absolute; top: 56px; left: 0; right: 0; z-index: 100;
background: #fff; border: 1px solid #e2e8f0; border-radius: 6px;
box-shadow: 0 6px 20px rgba(0,0,0,0.1); max-height: 220px; overflow-y: auto;
&--inline { position: relative; top: 0; margin-top: 4px; }
position: absolute;
top: 56px;
left: 0;
right: 0;
z-index: 100;
background: #fff;
border: 1px solid #e2e8f0;
border-radius: 6px;
box-shadow: 0 6px 20px rgb(0 0 0 / 10%);
max-height: 220px;
overflow-y: auto;
&--inline {
position: relative;
top: 0;
margin-top: 4px;
}
}
.nom-search-loading { display: flex; align-items: center; gap: 6px; padding: 10px 12px; font-size: 12px; color: #64748b; }
.nom-search-loading {
display: flex;
align-items: center;
gap: 6px;
padding: 10px 12px;
font-size: 12px;
color: #64748b;
}
.nom-search-item {
display: flex; align-items: center; gap: 8px; padding: 8px 12px; cursor: pointer; border-bottom: 1px solid #f1f5f9;
&:last-child { border-bottom: none; } &:hover { background: #f0f7ff; }
&__icon { color: $primary; font-size: 18px; }
&__info { display: flex; flex-direction: column; flex: 1; }
&__name { font-size: 13px; font-weight: 600; color: #1e293b; }
&__meta { font-size: 10px; color: #64748b; }
display: flex;
align-items: center;
gap: 8px;
padding: 8px 12px;
cursor: pointer;
border-bottom: 1px solid #f1f5f9;
&:last-child {
border-bottom: none;
}
&:hover {
background: #f0f7ff;
}
&__icon {
color: $primary;
font-size: 18px;
}
&__info {
display: flex;
flex-direction: column;
flex: 1;
}
&__name {
font-size: 13px;
font-weight: 600;
color: #1e293b;
}
&__meta {
font-size: 10px;
color: #64748b;
}
}
.search-no-results-hint {
font-size: 11px;
color: #94a3b8;
text-align: center;
padding: 4px 0;
}
.search-no-results-hint { font-size: 11px; color: #94a3b8; text-align: center; padding: 4px 0; }
/* Linked card */
.nom-linked-card {
display: flex; align-items: center; gap: 8px; padding: 8px 12px;
background: #f8fafc; border: 1px solid #e2e8f0; border-radius: 6px; margin-bottom: 8px;
&__left { flex-shrink: 0; }
&__icon { color: $primary; }
&__photo { width: 36px; height: 36px; border-radius: 50%; object-fit: cover; border: 2px solid #e2e8f0; }
&__info { flex: 1; display: flex; flex-direction: column; }
&__name { font-size: 13px; font-weight: 600; color: #1e293b; }
&__meta { font-size: 11px; color: #64748b; font-family: 'Roboto Mono', monospace; }
display: flex;
align-items: center;
gap: 8px;
padding: 8px 12px;
background: #f8fafc;
border: 1px solid #e2e8f0;
border-radius: 6px;
margin-bottom: 8px;
&__left {
flex-shrink: 0;
}
&__icon {
color: $primary;
}
&__photo {
width: 36px;
height: 36px;
border-radius: 50%;
object-fit: cover;
border: 2px solid #e2e8f0;
}
&__info {
flex: 1;
display: flex;
flex-direction: column;
}
&__name {
font-size: 13px;
font-weight: 600;
color: #1e293b;
}
&__meta {
font-size: 11px;
color: #64748b;
font-family: 'Roboto Mono', monospace;
}
&__sig {
flex-shrink: 0;
img { height: 24px; max-width: 60px; object-fit: contain; border: 1px solid #e2e8f0; border-radius: 3px; background: #fff; padding: 1px 3px; }
img {
height: 24px;
max-width: 60px;
object-fit: contain;
border: 1px solid #e2e8f0;
border-radius: 3px;
background: #fff;
padding: 1px 3px;
}
}
}
.nom-fetching-hint { display: flex; align-items: center; gap: 6px; font-size: 11px; color: #64748b; margin: -4px 0 8px; }
.nom-fetching-hint {
display: flex;
align-items: center;
gap: 6px;
font-size: 11px;
color: #64748b;
margin: -4px 0 8px;
}
/* Read-only KYC row */
.nom-readonly-row {
display: flex; flex-wrap: wrap; gap: 16px; padding: 8px 12px;
background: #f8fafc; border: 1px solid #e2e8f0; border-radius: 6px; margin-bottom: 10px;
display: flex;
flex-wrap: wrap;
gap: 16px;
padding: 8px 12px;
background: #f8fafc;
border: 1px solid #e2e8f0;
border-radius: 6px;
margin-bottom: 10px;
}
.nom-ro-item {
font-size: 12px;
color: #1e293b;
display: inline-flex;
align-items: center;
gap: 4px;
}
.nom-ro-label {
font-size: 10px;
font-weight: 600;
color: #94a3b8;
text-transform: uppercase;
margin-right: 2px;
}
.nom-ro-ok {
font-size: 14px !important;
width: 14px !important;
height: 14px !important;
color: #16a34a;
}
.nom-ro-item { font-size: 12px; color: #1e293b; display: inline-flex; align-items: center; gap: 4px; }
.nom-ro-label { font-size: 10px; font-weight: 600; color: #94a3b8; text-transform: uppercase; margin-right: 2px; }
.nom-ro-ok { font-size: 14px !important; width: 14px !important; height: 14px !important; color: #16a34a; }
/* Nominee fields */
.nom-fields-row { display: flex; flex-wrap: wrap; gap: 0 12px; align-items: center; margin-bottom: 4px; }
.nom-field-item { flex: 1; min-width: 160px; &--sm { max-width: 100px; min-width: 80px; } &--toggle { padding-top: 8px; } }
.nom-remarks-field { width: 100%; }
.nom-form-actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 4px; }
.nom-fields-row {
display: flex;
flex-wrap: wrap;
gap: 0 12px;
align-items: center;
margin-bottom: 4px;
}
.nom-field-item {
flex: 1;
min-width: 160px;
&--sm {
max-width: 100px;
min-width: 80px;
}
&--toggle {
padding-top: 8px;
}
}
.nom-remarks-field {
width: 100%;
}
.nom-form-actions {
display: flex;
justify-content: flex-end;
gap: 8px;
margin-top: 4px;
}
/* Guardian */
.nom-guardian-section { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 8px; }
.nom-guardian-label { display: flex; align-items: center; gap: 4px; font-size: 12px; font-weight: 600; color: #92400e; mat-icon { font-size: 16px; } }
.nom-guardian-selected { display: flex; align-items: center; gap: 4px; font-size: 13px; font-weight: 500; color: #1e293b;
button { width: 24px !important; height: 24px !important; line-height: 24px !important; mat-icon { font-size: 14px; } }
.nom-guardian-section {
display: flex;
align-items: center;
gap: 8px;
flex-wrap: wrap;
margin-bottom: 8px;
}
.nom-guardian-label {
display: flex;
align-items: center;
gap: 4px;
font-size: 12px;
font-weight: 600;
color: #92400e;
mat-icon {
font-size: 16px;
}
}
.nom-guardian-selected {
display: flex;
align-items: center;
gap: 4px;
font-size: 13px;
font-weight: 500;
color: #1e293b;
button {
width: 24px !important;
height: 24px !important;
line-height: 24px !important;
mat-icon {
font-size: 14px;
}
}
}
/* Zoom */
.zoomable { cursor: zoom-in; transition: transform 0.15s; &:hover { transform: scale(1.08); box-shadow: 0 4px 12px rgba(0,0,0,0.15); } }
::ng-deep .image-zoom-panel { .mat-mdc-dialog-container { padding: 0 !important; border-radius: 8px !important; overflow: hidden; } }
.zoomable {
cursor: zoom-in;
transition: transform 0.15s;
&:hover {
transform: scale(1.08);
box-shadow: 0 4px 12px rgb(0 0 0 / 15%);
}
}
::ng-deep .image-zoom-panel {
.mat-mdc-dialog-container {
padding: 0 !important;
border-radius: 8px !important;
overflow: hidden;
}
}
@@ -109,11 +109,11 @@ export class NomineeTabComponent implements OnInit {
editNomType: string = 'DA1';
get hasAccounts(): boolean {
return this.accountGroups.some(g => g.accounts.length > 0);
return this.accountGroups.some((g) => g.accounts.length > 0);
}
get selectedGroup(): AccountGroup | undefined {
return this.accountGroups.find(g => g.type === this.selectedType);
return this.accountGroups.find((g) => g.type === this.selectedType);
}
get selectedAccount(): any {
@@ -154,13 +154,43 @@ export class NomineeTabComponent implements OnInit {
next: (data: any) => {
this.loading = false;
this.accountGroups = [
{ type: 'savings', apiType: 'SB', label: 'Savings Account', icon: 'savings', accounts: this.filterActive(data.savingsAccounts) },
{ type: 'current', apiType: 'CB', label: 'Current Account', icon: 'account_balance', accounts: this.filterActive(data.currentAccounts) },
{ type: 'fd', apiType: 'FD', label: 'Fixed Deposit', icon: 'lock', accounts: this.filterActive(data.fixedDepositAccounts) },
{ type: 'rd', apiType: 'RD', label: 'Recurring Deposit', icon: 'autorenew', accounts: this.filterActive(data.recurringDepositAccounts) },
{ type: 'loan', apiType: 'LA', label: 'Loan Account', icon: 'credit_score', accounts: this.filterActive(data.loanAccounts) }
{
type: 'savings',
apiType: 'SB',
label: 'Savings Account',
icon: 'savings',
accounts: this.filterActive(data.savingsAccounts)
},
{
type: 'current',
apiType: 'CB',
label: 'Current Account',
icon: 'account_balance',
accounts: this.filterActive(data.currentAccounts)
},
{
type: 'fd',
apiType: 'FD',
label: 'Fixed Deposit',
icon: 'lock',
accounts: this.filterActive(data.fixedDepositAccounts)
},
{
type: 'rd',
apiType: 'RD',
label: 'Recurring Deposit',
icon: 'autorenew',
accounts: this.filterActive(data.recurringDepositAccounts)
},
{
type: 'loan',
apiType: 'LA',
label: 'Loan Account',
icon: 'credit_score',
accounts: this.filterActive(data.loanAccounts)
}
];
const firstWithAccounts = this.accountGroups.find(g => g.accounts.length > 0);
const firstWithAccounts = this.accountGroups.find((g) => g.accounts.length > 0);
if (firstWithAccounts) {
this.selectedType = firstWithAccounts.type;
// Auto-select if only 1 account in the group
@@ -170,18 +200,16 @@ export class NomineeTabComponent implements OnInit {
}
}
},
error: () => { this.loading = false; }
error: () => {
this.loading = false;
}
});
}
private filterActive(accounts: any[]): any[] {
if (!accounts) return [];
// Include all non-closed accounts (active, disbursed, pending, etc.)
return accounts.filter((a: any) =>
a.status?.active ||
a.status?.value === 'Active' ||
!a.status?.closed
);
return accounts.filter((a: any) => a.status?.active || a.status?.value === 'Active' || !a.status?.closed);
}
onTypeChange(): void {
@@ -198,7 +226,9 @@ export class NomineeTabComponent implements OnInit {
onAccountChange(): void {
this.nominees = [];
this.resetAddForm();
if (this.selectedAccountId) { this.loadNominees(); }
if (this.selectedAccountId) {
this.loadNominees();
}
}
private loadNominees(): void {
@@ -210,9 +240,12 @@ export class NomineeTabComponent implements OnInit {
this.nomineesLoading = false;
this.nominees = Array.isArray(data) ? data : [];
// Enrich each nominee with profile image & signature
this.nominees.forEach(n => this.enrichNomineeImages(n));
this.nominees.forEach((n) => this.enrichNomineeImages(n));
},
error: () => { this.nomineesLoading = false; this.nominees = []; }
error: () => {
this.nomineesLoading = false;
this.nominees = [];
}
});
}
@@ -224,7 +257,9 @@ export class NomineeTabComponent implements OnInit {
// Profile image
this.clientsService.getClientProfileImage(cid).subscribe({
next: (img: any) => {
if (img) { nominee.profileImageUrl = this.sanitizer.bypassSecurityTrustResourceUrl(img); }
if (img) {
nominee.profileImageUrl = this.sanitizer.bypassSecurityTrustResourceUrl(img);
}
},
error: () => {}
});
@@ -232,8 +267,8 @@ export class NomineeTabComponent implements OnInit {
// Signature
this.clientsService.getClientDocuments(cid).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(cid, sigDoc.id.toString()).subscribe({
@@ -253,7 +288,10 @@ export class NomineeTabComponent implements OnInit {
onSearch(text: string): void {
this.searchText = text;
clearTimeout(this.searchTimer);
if (!text || text.length < 2) { this.searchResults = []; return; }
if (!text || text.length < 2) {
this.searchResults = [];
return;
}
this.searching = true;
this.searchTimer = setTimeout(() => {
this.clientsService.searchByText(text, 0, 8).subscribe({
@@ -262,13 +300,19 @@ export class NomineeTabComponent implements OnInit {
this.searching = false;
this.clientsService.enrichSearchResults(this.searchResults);
},
error: () => { this.searchResults = []; this.searching = false; }
error: () => {
this.searchResults = [];
this.searching = false;
}
});
}, 400);
}
dismissSearch(): void {
setTimeout(() => { this.searchResults = []; this.searching = false; }, 200);
setTimeout(() => {
this.searchResults = [];
this.searching = false;
}, 200);
}
selectClient(client: any): void {
@@ -291,11 +335,18 @@ export class NomineeTabComponent implements OnInit {
this.linkedSignatureImage = null;
this.linkedKycStatus = null;
let pending = 4;
const done = () => { pending--; if (pending <= 0) { this.fetchingDetails = false; } };
const done = () => {
pending--;
if (pending <= 0) {
this.fetchingDetails = false;
}
};
this.clientsService.getClientData(clientId.toString()).subscribe({
next: (data: any) => {
if (data?.dateOfBirth) { this.selectedClient = { ...this.selectedClient, dateOfBirth: data.dateOfBirth }; }
if (data?.dateOfBirth) {
this.selectedClient = { ...this.selectedClient, dateOfBirth: data.dateOfBirth };
}
done();
},
error: () => done()
@@ -304,8 +355,15 @@ export class NomineeTabComponent implements OnInit {
this.clientsService.getClientKyc(clientId.toString()).subscribe({
next: (kyc: any) => {
if (kyc) {
this.selectedClient = { ...this.selectedClient, panNumber: kyc.panNumber, aadhaarMasked: kyc.aadhaarNumberMasked };
this.linkedKycStatus = { panVerified: kyc.panVerified || false, aadhaarVerified: kyc.aadhaarVerified || false };
this.selectedClient = {
...this.selectedClient,
panNumber: kyc.panNumber,
aadhaarMasked: kyc.aadhaarNumberMasked
};
this.linkedKycStatus = {
panVerified: kyc.panVerified || false,
aadhaarVerified: kyc.aadhaarVerified || false
};
}
done();
},
@@ -313,19 +371,31 @@ export class NomineeTabComponent implements OnInit {
});
this.clientsService.getClientProfileImage(clientId.toString()).subscribe({
next: (img: any) => { if (img) { this.linkedProfileImage = this.sanitizer.bypassSecurityTrustResourceUrl(img); } done(); },
next: (img: any) => {
if (img) {
this.linkedProfileImage = this.sanitizer.bypassSecurityTrustResourceUrl(img);
}
done();
},
error: () => done()
});
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({
next: (blob: Blob) => { this.linkedSignatureImage = this.sanitizer.bypassSecurityTrustUrl(URL.createObjectURL(blob)); done(); },
next: (blob: Blob) => {
this.linkedSignatureImage = this.sanitizer.bypassSecurityTrustUrl(URL.createObjectURL(blob));
done();
},
error: () => done()
});
} else { done(); }
} else {
done();
}
},
error: () => done()
});
@@ -336,12 +406,22 @@ export class NomineeTabComponent 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, 8).subscribe({
next: (data: any) => { this.guardianSearchResults = data?.content || []; this.guardianSearching = false; this.clientsService.enrichSearchResults(this.guardianSearchResults); },
error: () => { this.guardianSearchResults = []; this.guardianSearching = false; }
next: (data: any) => {
this.guardianSearchResults = data?.content || [];
this.guardianSearching = false;
this.clientsService.enrichSearchResults(this.guardianSearchResults);
},
error: () => {
this.guardianSearchResults = [];
this.guardianSearching = false;
}
});
}, 400);
}
@@ -385,7 +465,9 @@ export class NomineeTabComponent implements OnInit {
this.loadNominees();
this.resetAddForm();
},
error: () => { this.nomineeSaving = false; }
error: () => {
this.nomineeSaving = false;
}
});
}
@@ -395,26 +477,30 @@ export class NomineeTabComponent implements OnInit {
// Resolve the linked client ID — could be nomineeClientId or nested
const clientId = nominee.nomineeClientId || nominee.linkedClientId || nominee.clientId;
const displayName = nominee.nomineeDisplayName || nominee.firstName || '';
import('../../client-stepper/client-family-members-step/client-family-member-dialog/client-family-member-dialog.component').then(m => {
this.dialog.open(m.ClientFamilyMemberDialogComponent, {
data: {
context: 'View',
member: {
firstName: displayName,
relationshipId: nominee.relationshipId,
dateOfBirth: nominee.nomineeDateOfBirth || nominee.dateOfBirth,
linkedClientId: clientId,
linkedClientName: displayName,
isAlive: true,
isDependent: nominee.nomineeIsMinor,
panNumber: nominee.panNumber || '',
aadhaarNumber: nominee.aadhaarMasked || ''
import('../../client-stepper/client-family-members-step/client-family-member-dialog/client-family-member-dialog.component').then(
(m) => {
this.dialog.open(m.ClientFamilyMemberDialogComponent, {
data: {
context: 'View',
member: {
firstName: displayName,
relationshipId: nominee.relationshipId,
dateOfBirth: nominee.nomineeDateOfBirth || nominee.dateOfBirth,
linkedClientId: clientId,
linkedClientName: displayName,
isAlive: true,
isDependent: nominee.nomineeIsMinor,
panNumber: nominee.panNumber || '',
aadhaarNumber: nominee.aadhaarMasked || ''
},
options: { relationshipIdOptions: this.relationshipOptions }
},
options: { relationshipIdOptions: this.relationshipOptions }
},
width: '640px', maxWidth: '95vw', panelClass: 'fm-dialog-panel'
});
});
width: '640px',
maxWidth: '95vw',
panelClass: 'fm-dialog-panel'
});
}
);
}
editNominee(nominee: any, index: number): void {
@@ -424,10 +510,12 @@ export class NomineeTabComponent implements OnInit {
this.editNomType = nominee.nominationType || 'DA1';
}
cancelEdit(): void { this.editingIndex = null; }
cancelEdit(): void {
this.editingIndex = null;
}
maxShareForEdit(index: number): number {
return 100 - this.nominees.reduce((sum, n, i) => i === index ? sum : sum + (n.percentageShare || 0), 0);
return 100 - this.nominees.reduce((sum, n, i) => (i === index ? sum : sum + (n.percentageShare || 0)), 0);
}
saveEdit(nominee: any, index: number): void {
@@ -468,7 +556,8 @@ export class NomineeTabComponent implements OnInit {
this.dialog.open(ImageZoomDialogComponent, {
data: { imageSrc, title },
panelClass: 'image-zoom-panel',
maxWidth: '90vw', maxHeight: '90vh'
maxWidth: '90vw',
maxHeight: '90vh'
});
}