feat: recurring deposit enhancements, dashboard, chart, and account view updates

This commit is contained in:
2026-10-12 10:23:10 +05:30
parent 6b0f218b70
commit 2d2eac8d03
69 changed files with 3619 additions and 595 deletions
@@ -0,0 +1,256 @@
<!--
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="rdd-container">
<!-- Header -->
<div class="rdd-header">
<div class="rdd-header__left">
<div class="rdd-header__icon">
<fa-icon icon="piggy-bank"></fa-icon>
</div>
<div>
<h2 class="rdd-header__title">RD Dashboard</h2>
<p class="rdd-header__sub">
All running Recurring Deposits as of <strong>{{ businessDate || '—' }}</strong> (business date). Click any
card to see those RDs in the list below.
</p>
</div>
</div>
<button class="rdd-btn rdd-btn--slim" (click)="load()" [disabled]="loading">
<fa-icon icon="sync-alt" [class.fa-spin]="loading"></fa-icon>
<span>{{ loading ? 'Loading...' : 'Reload' }}</span>
</button>
</div>
@if (errorMessage) {
<div class="rdd-alert">
<fa-icon icon="exclamation-triangle"></fa-icon>
<span>{{ errorMessage }}</span>
</div>
}
<!-- 1. Summary -->
<section class="rdd-section">
<h3 class="rdd-section__title">RD Book at a Glance</h3>
<div class="rdd-cards">
<button class="rdd-card rdd-card--success" [class.rdd-card--active]="stageFilter === 'REGULAR'" (click)="showStage('REGULAR')">
<span class="rdd-card__label"><fa-icon icon="chart-line"></fa-icon> Active RDs</span>
<span class="rdd-card__val">{{ summary.activeCount }}</span>
<span class="rdd-card__sub">Balance ₹{{ summary.activeBalance | number: '1.2-2' }}</span>
</button>
<button class="rdd-card rdd-card--info" [class.rdd-card--active]="stageFilter === 'MATURED'" (click)="showStage('MATURED')">
<span class="rdd-card__label"><fa-icon icon="calendar-check"></fa-icon> Matured – Close Pending</span>
<span class="rdd-card__val">{{ summary.maturedCount }}</span>
<span class="rdd-card__sub">To pay ₹{{ summary.maturedAmount | number: '1.2-2' }}</span>
</button>
<div class="rdd-card rdd-card--neutral rdd-card--static">
<span class="rdd-card__label"><fa-icon icon="check-circle"></fa-icon> Closed This Month</span>
<span class="rdd-card__val">{{ maturedClosedThisMonth + prematureClosedThisMonth }}</span>
<span class="rdd-card__sub">On maturity {{ maturedClosedThisMonth }} · Premature {{ prematureClosedThisMonth }}</span>
</div>
<div class="rdd-card rdd-card--neutral rdd-card--static">
<span class="rdd-card__label"><fa-icon icon="hand-holding-usd"></fa-icon> This Month's Installments</span>
<span class="rdd-card__val">{{ summary.collectionPercent }}%</span>
<div class="rdd-progress">
<div class="rdd-progress__bar" [style.width.%]="summary.collectionPercent"></div>
</div>
<span class="rdd-card__sub"
>Received ₹{{ summary.receivedThisMonth | number: '1.0-0' }} of ₹{{
summary.dueThisMonth | number: '1.0-0'
}}</span
>
</div>
<button class="rdd-card rdd-card--danger" [class.rdd-card--active]="extraFilter === 'overdue'" (click)="showExtra('overdue')">
<span class="rdd-card__label"><fa-icon icon="exclamation-circle"></fa-icon> Overdue RDs</span>
<span class="rdd-card__val">{{ summary.overdueCount }}</span>
<span class="rdd-card__sub">Pending ₹{{ summary.overdueAmount | number: '1.2-2' }}</span>
</button>
<button class="rdd-card rdd-card--warn" [class.rdd-card--active]="extraFilter === 'lien'" (click)="showExtra('lien')">
<span class="rdd-card__label"><fa-icon icon="lock"></fa-icon> Under Lien (Loan)</span>
<span class="rdd-card__val">{{ summary.lienCount }}</span>
<span class="rdd-card__sub">Lien ₹{{ summary.lienAmount | number: '1.2-2' }}</span>
</button>
</div>
</section>
<!-- 2. Installment health -->
<section class="rdd-section">
<h3 class="rdd-section__title">Installment Health</h3>
<div class="rdd-health">
<div class="rdd-tiles">
<button class="rdd-tile" [class.rdd-tile--active]="stageFilter === 'DUE_TODAY'" (click)="showStage('DUE_TODAY')">
<span class="rdd-tile__dot rdd-tile__dot--neutral"></span>
<span class="rdd-tile__label">Due today</span>
<span class="rdd-tile__val">{{ summary.dueTodayCount }}</span>
<span class="rdd-tile__sub">{{ summary.dueTodayPaidCount }} paid · {{ summary.dueTodayCount - summary.dueTodayPaidCount }} pending</span>
</button>
<button class="rdd-tile" [class.rdd-tile--active]="stageFilter === 'IN_GRACE'" (click)="showStage('IN_GRACE')">
<span class="rdd-tile__dot rdd-tile__dot--warn"></span>
<span class="rdd-tile__label">Late, in grace</span>
<span class="rdd-tile__val">{{ summary.inGraceCount }}</span>
<span class="rdd-tile__sub">₹{{ summary.inGraceAmount | number: '1.0-0' }} · no penalty yet</span>
</button>
<button class="rdd-tile" [class.rdd-tile--active]="stageFilter === 'PAST_GRACE'" (click)="showStage('PAST_GRACE')">
<span class="rdd-tile__dot rdd-tile__dot--danger"></span>
<span class="rdd-tile__label">Past grace</span>
<span class="rdd-tile__val">{{ summary.pastGraceCount }}</span>
<span class="rdd-tile__sub">₹{{ summary.pastGraceAmount | number: '1.0-0' }} · penalty applies</span>
</button>
<div class="rdd-tile rdd-tile--static">
<span class="rdd-tile__dot rdd-tile__dot--success"></span>
<span class="rdd-tile__label">Penalty income</span>
<span class="rdd-tile__val">₹{{ penaltyIncomeThisMonth | number: '1.0-0' }}</span>
<span class="rdd-tile__sub">This month · ₹{{ penaltyIncomeTotal | number: '1.0-0' }} in all</span>
</div>
</div>
<div class="rdd-ageing">
<div class="rdd-ageing__title">How late are overdue RDs?</div>
@for (b of ageingBuckets; track b.key) {
<button class="rdd-ageing__row" [class.rdd-ageing__row--active]="extraFilter === b.key" (click)="showExtra(b.key)">
<span class="rdd-ageing__label">{{ b.label }}</span>
<span class="rdd-ageing__track">
<span class="rdd-ageing__bar" [style.width.%]="ageingWidth(b.key)"></span>
</span>
<span class="rdd-ageing__count">{{ summary.ageing[b.key] || 0 }}</span>
</button>
}
</div>
</div>
</section>
<!-- 3. Action list -->
<section class="rdd-section">
<h3 class="rdd-section__title">RD List – What to Do Next</h3>
<div class="rdd-toolbar">
<div class="rdd-search">
<fa-icon icon="search" class="rdd-search__icon"></fa-icon>
<input class="rdd-search__input" type="text" placeholder="Account no. or client name" [(ngModel)]="search" />
</div>
<select class="rdd-select" [(ngModel)]="officeFilter">
<option [ngValue]="''">All branches</option>
@for (o of offices; track o.id) {
<option [ngValue]="o.id">{{ o.name }}</option>
}
</select>
<select class="rdd-select" [(ngModel)]="productFilter">
<option [ngValue]="''">All products</option>
@for (p of products; track p.id) {
<option [ngValue]="p.id">{{ p.name }}</option>
}
</select>
<select class="rdd-select" [(ngModel)]="stageFilter" (ngModelChange)="extraFilter = ''">
<option [ngValue]="''">All statuses</option>
@for (k of stageKeys; track k) {
<option [ngValue]="k">{{ stages[k].label }}</option>
}
</select>
@if (anyFilter) {
<button class="rdd-btn rdd-btn--slim" (click)="resetFilters()"><fa-icon icon="undo"></fa-icon> Clear</button>
}
<span class="rdd-toolbar__spacer"></span>
<span class="rdd-count"
><strong>{{ visibleRows.length }}</strong> of {{ rows.length }} RDs</span
>
</div>
@if (loading) {
<div class="rdd-state"><fa-icon icon="spinner" [class.fa-spin]="true"></fa-icon> Loading RDs...</div>
} @else if (visibleRows.length === 0) {
<div class="rdd-state">
<fa-icon icon="inbox"></fa-icon>
{{ rows.length ? 'No RDs match these filters.' : 'No running Recurring Deposits.' }}
</div>
} @else {
<div class="rdd-table-card">
<table class="rdd-table">
<thead>
<tr>
<th>Account</th>
<th>Client</th>
<th>Product</th>
<th class="text-right">Installment</th>
<th>Next Due</th>
<th class="text-right">Overdue</th>
<th>Maturity</th>
<th>Status</th>
<th class="text-center">Auto Debit</th>
<th class="text-right">Lien</th>
<th class="text-center">Action</th>
</tr>
</thead>
<tbody>
@for (r of visibleRows; track r.id) {
<tr class="rdd-row" (click)="openAccount(r)">
<td>
<span class="rdd-mono">{{ r.accountNo }}</span>
<div class="rdd-sub">{{ r.officeName }}</div>
</td>
<td class="rdd-strong">{{ r.clientName }}</td>
<td class="rdd-muted">{{ r.productName }}</td>
<td class="text-right">
<span class="rdd-mono">₹{{ r.installmentAmount | number: '1.2-2' }}</span>
<div class="rdd-sub">{{ r.paidInstallments }} of {{ r.totalInstallments }} paid</div>
</td>
<td>
<span class="rdd-mono">{{ r.nextDueDate || '—' }}</span>
<div class="rdd-sub" [class.rdd-sub--danger]="r.daysLate > 0">{{ dueText(r) }}</div>
</td>
<td class="text-right">
@if (r.overdueInstallments > 0) {
<span class="rdd-mono rdd-danger">₹{{ r.overdueAmount | number: '1.2-2' }}</span>
<div class="rdd-sub">
{{ r.overdueInstallments }} installment{{ r.overdueInstallments === 1 ? '' : 's' }}
</div>
} @else {
<span class="rdd-muted">—</span>
}
</td>
<td>
<span class="rdd-mono">{{ r.maturityDate || '—' }}</span>
<div class="rdd-sub">{{ maturityText(r) }}</div>
</td>
<td>
<span [class]="stageClass(r.stage)" [title]="r.meta.hint">{{ r.meta.label }}</span>
@if (r.penaltyCount > 0) {
<div class="rdd-sub">Penalty ₹{{ r.penaltyAmount | number: '1.0-0' }} ({{ r.penaltyCount }}x)</div>
}
</td>
<td class="text-center">
@if (r.autoDebit) {
<span class="rdd-yes" [title]="'From ' + (r.linkedAccountNo || 'linked savings')">On</span>
} @else {
<span class="rdd-muted">Off</span>
}
</td>
<td class="text-right">
@if (r.lienAmount > 0) {
<span class="rdd-mono">₹{{ r.lienAmount | number: '1.0-0' }}</span>
} @else {
<span class="rdd-muted">—</span>
}
</td>
<td class="text-center rdd-actions" (click)="$event.stopPropagation()">
@if (r.stage === 'MATURED') {
<button class="rdd-btn rdd-btn--primary rdd-btn--xs" (click)="closeAccount(r)">Close</button>
}
<button class="rdd-btn rdd-btn--xs" (click)="openAccount(r)">View</button>
</td>
</tr>
}
</tbody>
</table>
</div>
}
</section>
</div>