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
@@ -0,0 +1,49 @@
<!--
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/.
-->
<div class="ec-view">
@if (loading) {
<div class="ec-loading">
<mat-progress-spinner mode="indeterminate" diameter="40"></mat-progress-spinner>
<p>Loading classifications...</p>
</div>
}
@if (!loading) {
<div class="ec-header">
<h3><fa-icon icon="building"></fa-icon> Scheme &amp; Sector Classification</h3>
<span class="ec-count">{{ enabledCategories.length }} active</span>
</div>
@if (enabledCategories.length === 0) {
<div class="ec-empty">
<mat-icon>category</mat-icon>
<span>No classifications enabled for this entity.</span>
</div>
}
@for (cat of enabledCategories; track cat.key) {
<div class="ec-card">
<div class="ec-card__header">
<span class="ec-card__tag" [style.background]="cat.tagBg" [style.color]="cat.tagColor">{{ cat.tag }}</span>
<span class="ec-card__title">{{ cat.label }}</span>
<span class="ec-card__badge">Enabled</span>
</div>
<div class="ec-card__body">
<div class="ec-card__grid">
@for (field of getFieldPairs(getCategoryData(cat.key)); track field.label) {
<div class="ec-card__field">
<span class="ec-card__label">{{ field.label }}</span>
<span class="ec-card__value">{{ field.value }}</span>
</div>
}
</div>
</div>
</div>
}
}
</div>
@@ -0,0 +1,60 @@
.ec-view { padding: 16px 0; }
.ec-loading {
display: flex; flex-direction: column; align-items: center; gap: 12px;
padding: 48px; color: #64748b;
}
.ec-header {
display: flex; align-items: center; justify-content: space-between;
margin-bottom: 16px;
h3 { font-size: 16px; font-weight: 600; color: #1e293b; display: flex; align-items: center; gap: 8px;
fa-icon { color: #1565c0; }
}
}
.ec-count {
font-size: 12px; font-weight: 600; color: #059669;
background: #ecfdf5; padding: 3px 10px; border-radius: 12px;
}
.ec-empty {
display: flex; flex-direction: column; align-items: center; gap: 8px;
padding: 40px; color: #94a3b8; font-size: 14px;
background: #f8fafc; border: 2px dashed #e2e8f0; border-radius: 10px;
mat-icon { font-size: 36px; width: 36px; height: 36px; }
}
.ec-card {
border: 1px solid #e2e8f0; border-radius: 10px; margin-bottom: 12px; overflow: hidden;
}
.ec-card__header {
display: flex; align-items: center; gap: 10px;
padding: 12px 16px; background: #f8fafc; border-bottom: 1px solid #f1f5f9;
}
.ec-card__tag {
font-size: 11px; font-weight: 600; padding: 2px 8px; border-radius: 12px;
}
.ec-card__title { font-size: 14px; font-weight: 600; color: #1e293b; flex: 1; }
.ec-card__badge {
font-size: 10px; font-weight: 700; color: #059669;
background: #ecfdf5; padding: 2px 8px; border-radius: 10px; border: 1px solid #a7f3d0;
}
.ec-card__body { padding: 14px 16px; }
.ec-card__grid {
display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px 16px;
}
.ec-card__field { display: flex; flex-direction: column; gap: 2px; }
.ec-card__label { font-size: 10px; text-transform: uppercase; letter-spacing: 0.5px; color: #94a3b8; font-weight: 500; }
.ec-card__value { font-size: 13px; font-weight: 500; color: #1e293b; }
@media (max-width: 768px) {
.ec-card__grid { grid-template-columns: repeat(2, 1fr); }
}
@@ -0,0 +1,87 @@
import { Component, OnInit, inject } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
import { MatIcon } from '@angular/material/icon';
import { MatProgressSpinner } from '@angular/material/progress-spinner';
import { ClientsService } from '../../clients.service';
@Component({
selector: 'mifosx-entity-classification-tab',
templateUrl: './entity-classification-tab.component.html',
styleUrls: ['./entity-classification-tab.component.scss'],
imports: [
...STANDALONE_SHARED_IMPORTS,
FaIconComponent,
MatIcon,
MatProgressSpinner
]
})
export class EntityClassificationTabComponent implements OnInit {
private route = inject(ActivatedRoute);
private clientsService = inject(ClientsService);
clientId: string = '';
loading = true;
data: any = null;
/** Category metadata for display */
categories = [
{ key: 'msme', label: 'MSME', tag: 'Udyam / MSME', tagColor: '#0A5C6B', tagBg: '#E6F4F6', icon: 'cogs' },
{ key: 'startup', label: 'Startup India', tag: 'DPIIT', tagColor: '#1A5FAB', tagBg: '#EAF2FB', icon: 'rocket' },
{ key: 'mudra', label: 'Mudra Borrower', tag: 'PMMY', tagColor: '#8A5A00', tagBg: '#FEF3DC', icon: 'hand-holding-usd' },
{ key: 'eou', label: 'Export Oriented Unit', tag: 'EOU', tagColor: '#1B7A4A', tagBg: '#E8F5EE', icon: 'ship' },
{ key: 'importexport', label: 'Import Export', tag: 'DGFT / IEC', tagColor: '#5B3A8A', tagBg: '#F0EBF8', icon: 'exchange-alt' },
{ key: 'psl', label: 'Priority Sector', tag: 'RBI PSL', tagColor: '#7B1F1F', tagBg: '#FCEAEA', icon: 'bullseye' },
{ key: 'nps', label: 'Non-Priority Sector', tag: 'General', tagColor: '#444441', tagBg: '#F1EFE8', icon: 'building' }
];
ngOnInit() {
this.clientId = this.route.parent?.snapshot.paramMap.get('clientId') || '';
if (this.clientId) {
this.clientsService.getEntityClassifications(this.clientId).subscribe({
next: (res: any) => { this.data = res; this.loading = false; },
error: () => { this.loading = false; }
});
} else {
this.loading = false;
}
}
get enabledCategories(): any[] {
if (!this.data) return [];
return this.categories.filter(c => this.data[c.key]?.enabled);
}
getCategoryData(key: string): any {
return this.data?.[key] || {};
}
getFieldPairs(catData: any): { label: string, value: any }[] {
if (!catData) return [];
const skip = ['id', 'clientId', 'enabled', 'locale', 'dateFormat'];
return Object.keys(catData)
.filter(k => !skip.includes(k) && catData[k] != null && catData[k] !== '' && catData[k] !== false)
.map(k => ({
label: this.formatLabel(k),
value: this.formatValue(catData[k])
}));
}
private formatLabel(key: string): string {
return key
.replace(/([A-Z])/g, ' $1')
.replace(/^./, s => s.toUpperCase())
.replace(/Id$/, '')
.trim();
}
private formatValue(val: any): string {
if (Array.isArray(val) && val.length === 3) {
return `${val[2]}/${val[1]}/${val[0]}`;
}
if (val === true) return 'Yes';
if (typeof val === 'number') return val.toLocaleString('en-IN');
return String(val);
}
}