New screen (Deposits menu, repurposing the dead "FD Maturity Report" shell button): every active Fixed Deposit positioned against its own maturity date (Matured / Due Soon / Upcoming / On Track), computed client-side from maturityDate so it can never drift out of step with the account. Reads the new lean maturity-dashboard backend endpoint. A matured, renewable row (no maturity instruction chosen at account-opening, or an explicit Reinvest instruction still waiting on its product's grace period) gets a Renew action wired to the account's own Payment dialog -- see the FD account-view commit for that side of it. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
241 lines
8.2 KiB
HTML
241 lines
8.2 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/.
|
|
-->
|
|
|
|
<div class="fdd-container">
|
|
<!-- Compact Light Header -->
|
|
<div class="fdd-header">
|
|
<div class="fdd-header__left">
|
|
<div class="fdd-header__icon">
|
|
<fa-icon icon="piggy-bank"></fa-icon>
|
|
</div>
|
|
<div>
|
|
<div class="fdd-header__title-row">
|
|
<h2 class="fdd-header__title">FD Dashboard & Maturity Ladder</h2>
|
|
<span class="fdd-header__badge">Core Banking</span>
|
|
</div>
|
|
<p class="fdd-header__sub">
|
|
Active Fixed Deposits positioned against maturity dates. Press <kbd>ESC</kbd> to toggle header & footer.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<div class="fdd-header__right">
|
|
<button class="fdd-btn fdd-btn--slim" (click)="load()" [disabled]="loading" title="Refresh maturity ladder data">
|
|
<fa-icon icon="sync-alt" [class.fa-spin]="loading"></fa-icon>
|
|
<span>{{ loading ? 'Syncing...' : 'Reload' }}</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Alerts -->
|
|
@if (errorMessage) {
|
|
<div class="fdd-alert fdd-alert--danger">
|
|
<fa-icon icon="exclamation-triangle"></fa-icon>
|
|
<span>{{ errorMessage }}</span>
|
|
</div>
|
|
}
|
|
|
|
<!-- Compact Stat Cards Grid -->
|
|
<div class="fdd-stats-grid">
|
|
@for (key of stageKeys; track key) {
|
|
<button
|
|
[class]="'fdd-stat-card fdd-stat-card--' + stageMeta[key].tone"
|
|
[class.fdd-stat-card--active]="stageFilter === key"
|
|
(click)="toggleStage(key)"
|
|
>
|
|
<div class="fdd-stat-card__header">
|
|
<span class="fdd-stat-card__label">{{ stageMeta[key].label }}</span>
|
|
<div class="fdd-stat-card__icon">
|
|
@switch (key) {
|
|
@case ('MATURED') {
|
|
<fa-icon icon="exclamation-circle"></fa-icon>
|
|
}
|
|
@case ('DUE_SOON') {
|
|
<fa-icon icon="clock"></fa-icon>
|
|
}
|
|
@case ('UPCOMING') {
|
|
<fa-icon icon="calendar-alt"></fa-icon>
|
|
}
|
|
@default {
|
|
<fa-icon icon="check-circle"></fa-icon>
|
|
}
|
|
}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="fdd-stat-card__val">
|
|
@switch (key) {
|
|
@case ('MATURED') {
|
|
{{ summary.matured }}
|
|
}
|
|
@case ('DUE_SOON') {
|
|
{{ summary.dueSoon }}
|
|
}
|
|
@case ('UPCOMING') {
|
|
{{ summary.upcoming }}
|
|
}
|
|
@default {
|
|
{{ summary.onTrack }}
|
|
}
|
|
}
|
|
</div>
|
|
<div class="fdd-stat-card__sub">{{ stageMeta[key].summary }}</div>
|
|
</button>
|
|
}
|
|
</div>
|
|
|
|
<!-- Compact Filter & Search Toolbar -->
|
|
<div class="fdd-toolbar">
|
|
<div class="fdd-search-box">
|
|
<fa-icon icon="search" class="fdd-search-box__icon"></fa-icon>
|
|
<input
|
|
class="fdd-search-box__input"
|
|
type="text"
|
|
placeholder="Search account no. or client name..."
|
|
[(ngModel)]="search"
|
|
/>
|
|
@if (search) {
|
|
<button class="fdd-search-box__clear" (click)="search = ''" title="Clear search">
|
|
<fa-icon icon="times"></fa-icon>
|
|
</button>
|
|
}
|
|
</div>
|
|
|
|
@if (stageFilter || search) {
|
|
<button class="fdd-btn fdd-btn--reset fdd-btn--slim" (click)="resetFilters()">
|
|
<fa-icon icon="undo"></fa-icon> Reset
|
|
</button>
|
|
}
|
|
|
|
<div class="fdd-toolbar__spacer"></div>
|
|
|
|
<div class="fdd-count-chip">
|
|
<fa-icon icon="layer-group"></fa-icon>
|
|
<span
|
|
><strong>{{ visibleRows.length }}</strong> of {{ summary.total }} active FDs</span
|
|
>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Worklist Table / Empty State -->
|
|
@if (loading) {
|
|
<div class="fdd-card fdd-state-card">
|
|
<fa-icon icon="spinner" [class.fa-spin]="true" class="fdd-spinner"></fa-icon>
|
|
<p>Loading FD maturity list...</p>
|
|
</div>
|
|
} @else if (visibleRows.length === 0) {
|
|
<div class="fdd-card fdd-state-card">
|
|
<fa-icon icon="inbox" class="fdd-state-icon"></fa-icon>
|
|
<p class="fdd-state-msg">
|
|
@if (!stageFilter && summary.total > 0) {
|
|
No FDs are matured or maturing within {{ policy.upcomingDays }} days. Click "On Track" to see all active FDs.
|
|
} @else {
|
|
No FDs match these filters.
|
|
}
|
|
</p>
|
|
@if (stageFilter || search) {
|
|
<button class="fdd-btn fdd-btn--primary fdd-btn--slim" (click)="resetFilters()">
|
|
<fa-icon icon="undo"></fa-icon> Clear Filters
|
|
</button>
|
|
}
|
|
</div>
|
|
} @else {
|
|
<div class="fdd-table-card">
|
|
<div class="fdd-table-wrapper">
|
|
<table class="fdd-table">
|
|
<thead>
|
|
<tr>
|
|
<th>Account</th>
|
|
<th>Client</th>
|
|
<th>Product</th>
|
|
<th>Maturity Date</th>
|
|
<th class="text-right">Deposit Amount</th>
|
|
<th class="text-right">Maturity Amount</th>
|
|
<th>Status</th>
|
|
<th class="text-center">Action</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
@for (r of visibleRows; track r.id) {
|
|
<tr class="fdd-row" (click)="openAccount(r)">
|
|
<td>
|
|
<span class="fdd-acc-num">{{ r.accountNo || '—' }}</span>
|
|
</td>
|
|
|
|
<td>
|
|
<div class="fdd-client-cell">
|
|
<span class="fdd-client-name">{{ r.clientName }}</span>
|
|
</div>
|
|
</td>
|
|
|
|
<td>
|
|
<span class="fdd-product-tag">{{ r.productName }}</span>
|
|
</td>
|
|
|
|
<td>
|
|
<div class="fdd-date-cell">
|
|
<span class="fdd-date-val">{{ r.maturityDate }}</span>
|
|
<span class="fdd-date-sub" [class.fdd-date-sub--overdue]="r.daysOverdue > 0">
|
|
{{ maturityText(r) }}
|
|
</span>
|
|
</div>
|
|
</td>
|
|
|
|
<td class="text-right">
|
|
<span class="fdd-amount fdd-amount--deposit">
|
|
{{ r.depositAmount != null ? '₹' + (r.depositAmount | number: '1.2-2') : '—' }}
|
|
</span>
|
|
</td>
|
|
|
|
<td class="text-right">
|
|
<span class="fdd-amount fdd-amount--maturity">
|
|
{{ r.maturityAmount != null ? '₹' + (r.maturityAmount | number: '1.2-2') : '—' }}
|
|
</span>
|
|
</td>
|
|
|
|
<td>
|
|
<div class="fdd-status-cell">
|
|
<span [class]="stageClass(r.stage)">
|
|
{{ r.meta.label }}
|
|
</span>
|
|
@if (r.renewable) {
|
|
<div
|
|
class="fdd-grace-sub"
|
|
[class.fdd-grace-sub--warning]="r.renewalGracePeriodDays > 0 && !r.withinGraceWindow"
|
|
>
|
|
{{ graceStatusText(r) }}
|
|
</div>
|
|
} @else if (r.configuredInstructionLabel) {
|
|
<div class="fdd-grace-sub">Configured: {{ r.configuredInstructionLabel }} (auto-processes)</div>
|
|
}
|
|
</div>
|
|
</td>
|
|
|
|
<td class="fdd-act-cell text-center" (click)="$event.stopPropagation()">
|
|
@if (r.renewable) {
|
|
<button class="fdd-btn fdd-btn--action" [disabled]="renewing === r.id" (click)="openRenewDialog(r)">
|
|
@if (renewing === r.id) {
|
|
<fa-icon icon="spinner" [class.fa-spin]="true"></fa-icon> Renewing...
|
|
} @else {
|
|
<fa-icon icon="sync-alt"></fa-icon> Renew
|
|
}
|
|
</button>
|
|
} @else {
|
|
<button class="fdd-btn-icon" (click)="openAccount(r)" title="View Account Details">
|
|
<fa-icon icon="chevron-right"></fa-icon>
|
|
</button>
|
|
}
|
|
</td>
|
|
</tr>
|
|
}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
}
|
|
</div>
|