Files
cbsnexora/src/app/clients/client-stepper/client-family-members-step/client-family-member-dialog/client-family-member-dialog.component.html
T
2026-08-06 10:41:28 +05:30

499 lines
21 KiB
HTML

<!--
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
-->
<div class="fm-dialog">
<div class="fm-dialog__header">
<mat-icon class="fm-dialog__icon">people</mat-icon>
<span>{{ data.context }} Family Member</span>
</div>
<mat-dialog-content>
<form [formGroup]="familyMemberForm">
<!-- ═══ VIEW MODE ═══ -->
@if (isViewMode) {
<!-- Linked client card -->
@if (entryMode === 'linked' && selectedClient) {
<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')"
/>
} @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
>
</div>
@if (linkedSignatureImage) {
<div class="fm-linked-card__sig">
<img
[src]="linkedSignatureImage"
alt="Signature"
class="zoomable"
(click)="zoomImage(linkedSignatureImage, 'Specimen Signature')"
/>
<span>Signature</span>
</div>
}
</div>
}
@if (fetchingLinkedData) {
<div class="fm-fetching-hint">
<mat-progress-spinner diameter="14" mode="indeterminate"></mat-progress-spinner>
<span>Fetching client details...</span>
</div>
}
<!-- All details in read-only grid -->
<div class="fm-readonly-grid">
<div class="fm-readonly-item">
<span class="fm-readonly-label">Full Name</span>
<span class="fm-readonly-value">{{ familyMemberForm.get('firstName')?.value || '—' }}</span>
</div>
<div class="fm-readonly-item">
<span class="fm-readonly-label">Relationship</span>
<span class="fm-readonly-value">{{ relationshipName }}</span>
</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>
</div>
<div class="fm-readonly-item">
<span class="fm-readonly-label">Nationality</span>
<span class="fm-readonly-value">{{ familyMemberForm.get('nationality')?.value || '—' }}</span>
</div>
<div class="fm-readonly-item">
<span class="fm-readonly-label">Aadhaar</span>
<span class="fm-readonly-value">
{{ familyMemberForm.get('aadhaarNumber')?.value || '—' }}
@if (linkedKycStatus?.aadhaarVerified) {
<span class="fm-kyc-badge verified"><mat-icon>check_circle</mat-icon> Verified</span>
} @else if (familyMemberForm.get('aadhaarNumber')?.value) {
<span class="fm-kyc-badge not-verified"><mat-icon>cancel</mat-icon> Not Verified</span>
}
</span>
</div>
<div class="fm-readonly-item">
<span class="fm-readonly-label">PAN</span>
<span class="fm-readonly-value">
{{ familyMemberForm.get('panNumber')?.value || '—' }}
@if (linkedKycStatus?.panVerified) {
<span class="fm-kyc-badge verified"><mat-icon>check_circle</mat-icon> Verified</span>
} @else if (familyMemberForm.get('panNumber')?.value) {
<span class="fm-kyc-badge not-verified"><mat-icon>cancel</mat-icon> Not Verified</span>
}
</span>
</div>
<div class="fm-readonly-item">
<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
}
</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>
</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>
</div>
}
</div>
@if (familyMemberForm.get('remarks')?.value) {
<div class="fm-readonly-remarks">
<span class="fm-readonly-label">Remarks</span>
<span class="fm-readonly-value">{{ familyMemberForm.get('remarks')?.value }}</span>
</div>
}
@if (isSpouse && familyMemberForm.get('children')?.value?.length > 0) {
<div class="fm-readonly-remarks">
<span class="fm-readonly-label">Children</span>
<div class="fm-children-list">
@for (child of familyMemberForm.get('children')?.value; track $index) {
<div class="fm-child-item" style="font-size: 13px; color: #555; margin-top: 4px;">
<span>{{ child.fullName }}</span>
@if (child.dateOfBirth) {
<span> · DOB: {{ child.dateOfBirth | date: 'dd MMM yyyy' }}</span>
}
</div>
}
</div>
</div>
}
}
<!-- ═══ ADD / EDIT MODE ═══ -->
@if (!isViewMode) {
<!-- Mode Toggle -->
<div class="fm-mode-toggle">
<button type="button" class="fm-mode-btn" [class.active]="entryMode === 'manual'" (click)="setMode('manual')">
<mat-icon>edit</mat-icon> Enter Manually
</button>
<button type="button" class="fm-mode-btn" [class.active]="entryMode === 'linked'" (click)="setMode('linked')">
<mat-icon>search</mat-icon> Link Existing Client
</button>
</div>
<!-- Linked Client Search -->
@if (entryMode === 'linked') {
@if (!selectedClient) {
<div class="fm-search-wrapper">
<mat-form-field appearance="outline" class="fm-search-field">
<mat-label>Search by Name or Client ID</mat-label>
<input matInput [value]="searchText" (input)="onSearch($any($event.target).value)" autocomplete="off" />
<mat-icon matSuffix>search</mat-icon>
</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>
}
@if (linkedSelectError) {
<div class="fm-linked-error">
<mat-icon>error_outline</mat-icon>
<span>{{ linkedSelectError }}</span>
</div>
}
@if (!searching && searchResults.length > 0) {
<div class="fm-search-results">
@for (r of searchResults; track r.id) {
<div
class="fm-search-item"
[class.is-disabled]="isClientAlreadyLinked(r.id)"
(click)="selectClient(r)"
>
<mat-icon class="fm-search-item__icon">person</mat-icon>
<div class="fm-search-item__info">
<span class="fm-search-item__name">
{{ r.displayName }}
@if (isClientAlreadyLinked(r.id)) {
<span class="fm-search-item__taken">· already linked</span>
}
</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>
}
</div>
}
@if (!searching && searchText.length >= 2 && searchResults.length === 0) {
<div class="search-no-results-hint">No clients found</div>
}
</div>
}
@if (selectedClient) {
<!-- Linked Client Card with Profile & Signature -->
<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')"
/>
} @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
>
</div>
@if (linkedSignatureImage) {
<div class="fm-linked-card__sig">
<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>
</div>
@if (fetchingLinkedData) {
<div class="fm-fetching-hint">
<mat-progress-spinner diameter="14" mode="indeterminate"></mat-progress-spinner>
<span>Fetching client details...</span>
</div>
}
<!-- Read-only fetched details -->
@if (!fetchingLinkedData) {
<div class="fm-readonly-grid">
<div class="fm-readonly-item">
<span class="fm-readonly-label">Full Name</span>
<span class="fm-readonly-value">{{ familyMemberForm.get('firstName')?.value || '—' }}</span>
</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>
</div>
<div class="fm-readonly-item">
<span class="fm-readonly-label">Aadhaar</span>
<span class="fm-readonly-value">
{{ familyMemberForm.get('aadhaarNumber')?.value || '—' }}
@if (linkedKycStatus?.aadhaarVerified) {
<span class="fm-kyc-badge verified"><mat-icon>check_circle</mat-icon> Verified</span>
} @else if (familyMemberForm.get('aadhaarNumber')?.value) {
<span class="fm-kyc-badge not-verified"><mat-icon>cancel</mat-icon> Not Verified</span>
}
</span>
</div>
<div class="fm-readonly-item">
<span class="fm-readonly-label">PAN</span>
<span class="fm-readonly-value">
{{ familyMemberForm.get('panNumber')?.value || '—' }}
@if (linkedKycStatus?.panVerified) {
<span class="fm-kyc-badge verified"><mat-icon>check_circle</mat-icon> Verified</span>
} @else if (familyMemberForm.get('panNumber')?.value) {
<span class="fm-kyc-badge not-verified"><mat-icon>cancel</mat-icon> Not Verified</span>
}
</span>
</div>
</div>
}
}
}
<!-- Core Fields -->
<div class="fm-grid">
@if (entryMode === 'manual') {
<mat-form-field appearance="outline">
<mat-label>Full Name</mat-label>
<input matInput required formControlName="firstName" placeholder="Full name of family member" />
</mat-form-field>
}
<mat-form-field appearance="outline">
<mat-label>Relationship</mat-label>
<mat-select required formControlName="relationshipId">
@for (rel of data.options.relationshipIdOptions; track rel) {
<mat-option [value]="rel.id" [disabled]="disabledRelationshipIds.has(rel.id)">
{{ rel.name }}
@if (disabledRelationshipIds.has(rel.id)) {
<span class="fm-rel-taken">· already added</span>
}
</mat-option>
}
</mat-select>
<mat-icon
matSuffix
class="fm-rel-hint-icon"
matTooltip="Father, Mother and Spouse can only be added once."
matTooltipPosition="left"
>
info_outline
</mat-icon>
</mat-form-field>
@if (entryMode === 'manual') {
<mat-form-field appearance="outline" (click)="dobPicker.open()">
<mat-label>Date of Birth</mat-label>
<input matInput [max]="maxDate" [matDatepicker]="dobPicker" formControlName="dateOfBirth" />
<mat-datepicker-toggle matSuffix [for]="dobPicker"></mat-datepicker-toggle>
<mat-datepicker #dobPicker></mat-datepicker>
</mat-form-field>
}
<mat-form-field appearance="outline">
<mat-label>Nationality</mat-label>
<mat-select formControlName="nationality">
<mat-option value="INDIAN">Indian</mat-option>
<mat-option value="NRI">NRI</mat-option>
<mat-option value="FOREIGN_NATIONAL">Foreign National</mat-option>
<mat-option value="OTHER">Other</mat-option>
</mat-select>
</mat-form-field>
</div>
<!-- Marriage Details (Spouse only) -->
@if (isSpouse) {
<div class="fm-section-label">Marriage Details</div>
<div class="fm-grid">
<mat-form-field appearance="outline" (click)="marriagePicker.open()">
<mat-label>Marriage Date</mat-label>
<input matInput [max]="maxDate" [matDatepicker]="marriagePicker" formControlName="marriageDate" />
<mat-datepicker-toggle matSuffix [for]="marriagePicker"></mat-datepicker-toggle>
<mat-datepicker #marriagePicker></mat-datepicker>
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>Issuing Authority</mat-label>
<mat-select formControlName="marriageAuthority">
@for (auth of marriageAuthorityOptions; track auth.value) {
<mat-option [value]="auth.value">{{ auth.label }}</mat-option>
}
</mat-select>
</mat-form-field>
</div>
<!-- Marriage Certificate Upload -->
<div class="fm-file-upload">
@if (!marriageCertFile) {
<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)"
/>
<span class="fm-file-hint">PDF, JPG or PNG (max 5 MB)</span>
} @else {
<div class="fm-file-attached">
<mat-icon class="fm-file-attached__icon">description</mat-icon>
<span class="fm-file-attached__name">{{ marriageCertFileName }}</span>
<button mat-icon-button type="button" (click)="removeMarriageCert()" class="fm-file-attached__remove">
<mat-icon>close</mat-icon>
</button>
</div>
}
</div>
}
<!-- ID Verification (manual mode only) -->
@if (entryMode === 'manual') {
<div class="fm-section-label">ID Verification</div>
<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()"
/>
</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
) {
<mat-error>Invalid PAN format</mat-error>
}
</mat-form-field>
</div>
}
<!-- Status -->
<div class="fm-checkbox-row">
<mat-checkbox formControlName="isAlive">Is Alive</mat-checkbox>
<mat-checkbox formControlName="isDependent">Is Dependent</mat-checkbox>
</div>
<!-- Remarks -->
<mat-form-field appearance="outline" class="fm-full-width">
<mat-label>Remarks</mat-label>
<textarea matInput formControlName="remarks" rows="2"></textarea>
</mat-form-field>
<!-- Children Section -->
@if (isSpouse) {
<div class="fm-section-label" style="margin-top: 16px;">Children</div>
<div class="fm-checkbox-row" style="display: flex; align-items: center; justify-content: space-between;">
<mat-checkbox formControlName="hasChildren">Does this family member have a child/children?</mat-checkbox>
@if (familyMemberForm.get('hasChildren')?.value) {
<button mat-stroked-button type="button" color="primary" (click)="openChildrenDialog()" style="margin-left: 16px;">
<mat-icon>edit</mat-icon> Edit Children Details ({{ children.length }})
</button>
}
</div>
}
}
</form>
</mat-dialog-content>
<mat-dialog-actions align="end">
@if (isViewMode) {
<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"
[disabled]="familyMemberForm.invalid"
[mat-dialog-close]="{ member: familyMember }"
>
{{ data.context === 'Edit' ? 'Update' : 'Add Member' }}
</button>
}
</mat-dialog-actions>
</div>