feat: non-individual entity UI improvements — hide individual-only sections, fix legal form display, send extended fields to API

- Hide Family Members tab/section for non-individual entities in client view, create flow, and preview
- Hide Financial & Classification section for non-individual entities in personal data and preview
- Fix legal form column in clients list — fetch legalForm from client API for correct display
- Add person/business icon before client name in list rows
- Remove unsupportedNpd filter — send authorizedSignatory, additionalInfo, dateOfRegistration to API
- Add registration number format hint (mat-hint) based on constitution type
- Add spacing between verification row and constitution type dropdowns
- Reduce table cell padding and prevent action buttons from being clipped
- Add missing MPL-2.0 license headers

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Aditya
2026-04-04 15:10:51 +05:30
co-authored by Claude Opus 4.6
parent ab01111424
commit b890d24bb7
522 changed files with 51991 additions and 21885 deletions
@@ -7,118 +7,83 @@
-->
<div class="nom-container mat-typography">
<!-- Header -->
<!-- ═══ FAMILY MEMBERS SECTION ═══ -->
<div class="nom-header">
<div class="nom-header__left">
<i class="fa fa-users nom-header__icon"></i>
<h3 class="nom-header__title">{{ 'labels.heading.Family Members' | translate }}</h3>
<span class="nom-header__badge">{{ clientFamilyMembers?.length || 0 }}</span>
<mat-icon class="nom-header__mat-icon">people</mat-icon>
<h3 class="nom-header__title">Family Members</h3>
<span class="nom-header__badge">{{ familyMembers.length }}</span>
</div>
<button mat-flat-button color="primary" class="nom-header__btn" (click)="addNominee()">
<fa-icon icon="plus" class="m-r-10"></fa-icon>{{ 'labels.buttons.Add' | translate }}
<button mat-flat-button color="primary" class="nom-header__btn" (click)="addFamilyMember()">
<mat-icon>add</mat-icon> Add Member
</button>
</div>
<!-- Empty State -->
@if (!clientFamilyMembers || clientFamilyMembers.length === 0) {
@if (familyMembers.length === 0) {
<div class="nom-empty">
<i class="fa fa-user-o nom-empty__icon"></i>
<p class="nom-empty__text">{{ 'labels.text.No nominees added yet' | translate }}</p>
<mat-icon class="nom-empty__mat-icon">people_outline</mat-icon>
<p class="nom-empty__text">No family members added yet.</p>
</div>
}
<!-- Nominee Cards -->
<mat-accordion multi>
@for (member of clientFamilyMembers; track member.id; let i = $index) {
@for (member of familyMembers; track member.id; let i = $index) {
<mat-expansion-panel class="nom-panel">
<mat-expansion-panel-header class="nom-panel__header">
<mat-panel-title class="nom-panel__title">
<i class="fa fa-user nom-panel__type-icon"></i>
<mat-icon class="nom-panel__mat-icon">person</mat-icon>
<span class="nom-panel__name">{{ displayName(member) }}</span>
@if (member.relationship) {
<span class="nom-panel__badge nom-panel__badge--relation">{{ member.relationship }}</span>
}
@if (member.nominationType) {
<span class="nom-panel__badge nom-panel__badge--type">{{ member.nominationType }}</span>
@if (getRelationship(member)) {
<span class="nom-panel__badge nom-panel__badge--relation">{{ getRelationship(member) }}</span>
}
</mat-panel-title>
<mat-panel-description class="nom-panel__desc">
@if (member.percentageShare != null) {
{{ member.percentageShare }}%
@if (member.dateOfBirth) {
{{ member.dateOfBirth | dateFormat }}
}
</mat-panel-description>
</mat-expansion-panel-header>
<!-- Actions Bar -->
<div class="nom-actions">
<div class="nom-actions__buttons">
<button mat-stroked-button color="primary" class="nom-actions__edit" (click)="editNominee(member, i)">
<fa-icon icon="edit" class="m-r-10"></fa-icon>{{ 'labels.buttons.Edit' | translate }}
<button mat-stroked-button color="primary" class="nom-actions__edit" (click)="editFamilyMember(member, i)">
<fa-icon icon="edit" class="m-r-10"></fa-icon> Edit
</button>
<button
mat-stroked-button
color="warn"
class="nom-actions__delete"
(click)="deleteFamilyMember(member.clientId, member.id, member.firstName, i)"
>
<fa-icon icon="trash" class="m-r-10"></fa-icon>{{ 'labels.buttons.Delete' | translate }}
<button mat-stroked-button color="warn" class="nom-actions__delete" (click)="deleteMember(member, 'Family Member')">
<fa-icon icon="trash" class="m-r-10"></fa-icon> Delete
</button>
</div>
</div>
<!-- Details Grid -->
<div class="nom-grid">
@if (member.nominationType) {
<div class="nom-field">
<div class="nom-field__label">{{ 'labels.inputs.Nomination Type' | translate }}</div>
<div class="nom-field__value">{{ member.nominationType }}</div>
</div>
}
@if (member.firstName) {
<div class="nom-field">
<div class="nom-field__label">{{ 'labels.inputs.Nominee Name' | translate }}</div>
<div class="nom-field__label">Name</div>
<div class="nom-field__value">{{ member.firstName }}</div>
</div>
}
@if (member.relationship) {
@if (getRelationship(member)) {
<div class="nom-field">
<div class="nom-field__label">{{ 'labels.inputs.Relationship' | translate }}</div>
<div class="nom-field__value">{{ member.relationship }}</div>
<div class="nom-field__label">Relationship</div>
<div class="nom-field__value">{{ getRelationship(member) }}</div>
</div>
}
@if (member.dateOfBirth) {
<div class="nom-field">
<div class="nom-field__label">{{ 'labels.inputs.Date Of Birth' | translate }}</div>
<div class="nom-field__label">Date of Birth</div>
<div class="nom-field__value">{{ member.dateOfBirth | dateFormat }}</div>
</div>
}
@if (member.percentageShare != null) {
<div class="nom-field">
<div class="nom-field__label">{{ 'labels.inputs.Percentage Share' | translate }}</div>
<div class="nom-field__value nom-field__value--mono">{{ member.percentageShare }}%</div>
</div>
}
@if (member.isDependent != null) {
<div class="nom-field">
<div class="nom-field__label">{{ 'labels.inputs.Nominee is Minor' | translate }}</div>
<div class="nom-field__label">Dependent</div>
<div class="nom-field__value">{{ member.isDependent | yesNo }}</div>
</div>
}
@if (member.guardianName) {
<div class="nom-field">
<div class="nom-field__label">{{ 'labels.inputs.Guardian Name' | translate }}</div>
<div class="nom-field__value">{{ member.guardianName }}</div>
</div>
}
@if (member.guardianRelationship) {
<div class="nom-field">
<div class="nom-field__label">{{ 'labels.inputs.Relationship with Minor' | translate }}</div>
<div class="nom-field__value">{{ member.guardianRelationship }}</div>
</div>
}
@if (member.remarks) {
<div class="nom-field">
<div class="nom-field__label">{{ 'labels.inputs.Remarks' | translate }}</div>
<div class="nom-field__label">Remarks</div>
<div class="nom-field__value">{{ member.remarks }}</div>
</div>
}
@@ -126,4 +91,5 @@
</mat-expansion-panel>
}
</mat-accordion>
</div>
@@ -28,6 +28,10 @@ $orange-light: #fff3e0;
padding: 20px 24px;
}
.section-divider {
margin: 28px 0 20px;
}
// Header
.nom-header {
display: flex;
@@ -46,6 +50,11 @@ $orange-light: #fff3e0;
color: $primary;
}
&__mat-icon {
color: $primary;
font-size: 22px;
}
&__title {
margin: 0;
font-size: 16px;
@@ -84,6 +93,14 @@ $orange-light: #fff3e0;
margin-bottom: 10px;
}
&__mat-icon {
font-size: 36px;
width: 36px;
height: 36px;
color: $gray-500;
margin-bottom: 10px;
}
&__text {
color: $gray-500;
font-size: 14px;
@@ -127,6 +144,23 @@ $orange-light: #fff3e0;
border-radius: 6px;
}
&__mat-icon {
font-size: 18px;
width: 28px;
height: 28px;
display: flex;
align-items: center;
justify-content: center;
background: $primary-light;
color: $primary;
border-radius: 6px;
&--nominee {
background: $green-light;
color: $green;
}
}
&__name {
font-size: 14px;
font-weight: 600;
@@ -22,6 +22,7 @@ import { SettingsService } from 'app/settings/settings.service';
import { Dates } from 'app/core/utils/dates';
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
import { MatIcon } from '@angular/material/icon';
import {
MatAccordion,
MatExpansionPanel,
@@ -35,7 +36,7 @@ import { YesnoPipe } from '../../../pipes/yesno.pipe';
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
/**
* Client Family Members Tab
* Client Family Members & Nominees Tab
*/
@Component({
selector: 'mifosx-family-members-tab',
@@ -44,6 +45,7 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
imports: [
...STANDALONE_SHARED_IMPORTS,
FaIconComponent,
MatIcon,
MatAccordion,
MatExpansionPanel,
MatExpansionPanelHeader,
@@ -62,37 +64,56 @@ export class FamilyMembersTabComponent {
private settingsService = inject(SettingsService);
private dateUtils = inject(Dates);
/** Client Family Members */
clientFamilyMembers: any;
/** All family members from API */
clientFamilyMembers: any[] = [];
/** Client ID */
clientId: string;
/** Family member template options (loaded on first dialog open) */
/** Family member template options */
familyMemberOptions: any;
constructor() {
this.route.data.subscribe((data: { clientFamilyMembers: any }) => {
this.clientFamilyMembers = data.clientFamilyMembers;
this.clientFamilyMembers = data.clientFamilyMembers || [];
});
this.clientId = this.route.parent.snapshot.params['clientId'];
}
/**
* Opens the nominee dialog in Add mode.
*/
addNominee() {
/** Family members (non-nominee entries) */
get familyMembers(): any[] {
return this.clientFamilyMembers.filter(m => !m.nominationType || m.nominationType === '');
}
/** Nominees (entries with nominationType set) */
get nominees(): any[] {
return this.clientFamilyMembers.filter(m => m.nominationType && m.nominationType !== '');
}
/** Get relationship display name */
getRelationship(member: any): string {
if (member.relationship) return member.relationship;
if (typeof member.relationshipId === 'object' && member.relationshipId?.name) return member.relationshipId.name;
return '';
}
displayName(member: any): string {
return member.firstName || '';
}
// ═══ Family Member Actions ═══
addFamilyMember() {
this.ensureTemplateLoaded().then(() => {
const dialogRef = this.dialog.open(NomineeDialogComponent, {
width: '700px',
data: {
title: this.translateService.instant('labels.buttons.Add') + ' ' + this.translateService.instant('labels.heading.Nominee'),
familyMemberOptions: this.familyMemberOptions
title: 'Add Family Member',
familyMemberOptions: this.familyMemberOptions,
isFamilyMember: true
}
});
dialogRef.afterClosed().subscribe((result: any) => {
if (result) {
this.clientsService.addFamilyMember(this.clientId, result).subscribe((res: any) => {
// Refresh the list by fetching the member details
this.clientsService.getClientFamilyMember(this.clientId, res.resourceId).subscribe((member: any) => {
this.clientFamilyMembers.push(member);
});
@@ -102,25 +123,23 @@ export class FamilyMembersTabComponent {
});
}
/**
* Opens the nominee dialog in Edit mode, pre-filled with member data.
*/
editNominee(member: any, index: number) {
editFamilyMember(member: any, index: number) {
const realIndex = this.clientFamilyMembers.indexOf(member);
this.ensureTemplateLoaded().then(() => {
const dialogRef = this.dialog.open(NomineeDialogComponent, {
width: '700px',
data: {
title: this.translateService.instant('labels.buttons.Edit') + ' ' + this.translateService.instant('labels.heading.Nominee'),
title: 'Edit Family Member',
familyMemberOptions: this.familyMemberOptions,
member
member,
isFamilyMember: true
}
});
dialogRef.afterClosed().subscribe((result: any) => {
if (result) {
this.clientsService.editFamilyMember(member.clientId, member.id, result).subscribe(() => {
this.clientsService.getClientFamilyMember(this.clientId, member.id).subscribe((updated: any) => {
this.clientFamilyMembers[index] = updated;
this.clientFamilyMembers[realIndex] = updated;
});
});
}
@@ -128,29 +147,68 @@ export class FamilyMembersTabComponent {
});
}
/**
* Deletes the family member.
*/
deleteFamilyMember(clientId: string, id: string, name: string, index: number) {
// ═══ Nominee Actions ═══
addNominee() {
this.ensureTemplateLoaded().then(() => {
const dialogRef = this.dialog.open(NomineeDialogComponent, {
width: '700px',
data: {
title: 'Add Nominee',
familyMemberOptions: this.familyMemberOptions
}
});
dialogRef.afterClosed().subscribe((result: any) => {
if (result) {
this.clientsService.addFamilyMember(this.clientId, result).subscribe((res: any) => {
this.clientsService.getClientFamilyMember(this.clientId, res.resourceId).subscribe((member: any) => {
this.clientFamilyMembers.push(member);
});
});
}
});
});
}
editNominee(member: any, index: number) {
const realIndex = this.clientFamilyMembers.indexOf(member);
this.ensureTemplateLoaded().then(() => {
const dialogRef = this.dialog.open(NomineeDialogComponent, {
width: '700px',
data: {
title: 'Edit Nominee',
familyMemberOptions: this.familyMemberOptions,
member
}
});
dialogRef.afterClosed().subscribe((result: any) => {
if (result) {
this.clientsService.editFamilyMember(member.clientId, member.id, result).subscribe(() => {
this.clientsService.getClientFamilyMember(this.clientId, member.id).subscribe((updated: any) => {
this.clientFamilyMembers[realIndex] = updated;
});
});
}
});
});
}
// ═══ Common ═══
deleteMember(member: any, label: string) {
const realIndex = this.clientFamilyMembers.indexOf(member);
const deleteFamilyMemberDialogRef = this.dialog.open(DeleteDialogComponent, {
data: { deleteContext: `Nominee id:${id} name : ${name}` }
data: { deleteContext: `${label} ${member.firstName || ''}` }
});
deleteFamilyMemberDialogRef.afterClosed().subscribe((response: any) => {
if (response.delete) {
this.clientsService.deleteFamilyMember(clientId, id).subscribe(() => {
this.clientFamilyMembers.splice(index, 1);
if (response?.delete) {
this.clientsService.deleteFamilyMember(member.clientId || this.clientId, member.id).subscribe(() => {
this.clientFamilyMembers.splice(realIndex, 1);
});
}
});
}
displayName(member: any): string {
return member.firstName || '';
}
/**
* Lazily loads family member template options from the API.
*/
private ensureTemplateLoaded(): Promise<void> {
if (this.familyMemberOptions) {
return Promise.resolve();