done
This commit is contained in:
@@ -46,14 +46,69 @@
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="pg-card">
|
||||
<!-- One list at a time. The three answer unrelated questions and a clerk is only ever in one of
|
||||
them; stacked, the one they came for was usually below the fold. Counts ride on the tabs so a
|
||||
list worth opening — a blocked member, a lock nearing forfeiture — is visible without opening
|
||||
it. -->
|
||||
<div class="dd-tabs" role="tablist">
|
||||
<button
|
||||
type="button"
|
||||
role="tab"
|
||||
class="dd-tab"
|
||||
[class.dd-tab--active]="activeTab === 'declarations'"
|
||||
[attr.aria-selected]="activeTab === 'declarations'"
|
||||
(click)="activeTab = 'declarations'"
|
||||
>
|
||||
Declarations
|
||||
<span class="dd-tab__count">{{ declarations.length }}</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
role="tab"
|
||||
class="dd-tab"
|
||||
[class.dd-tab--active]="activeTab === 'overdue'"
|
||||
[attr.aria-selected]="activeTab === 'overdue'"
|
||||
(click)="activeTab = 'overdue'"
|
||||
>
|
||||
Overdue EMI
|
||||
<span class="dd-tab__count" [class.dd-tab__count--alert]="rows.length > 0">{{ rows.length }}</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
role="tab"
|
||||
class="dd-tab"
|
||||
[class.dd-tab--active]="activeTab === 'locks'"
|
||||
[attr.aria-selected]="activeTab === 'locks'"
|
||||
(click)="activeTab = 'locks'"
|
||||
>
|
||||
Lock History
|
||||
<span class="dd-tab__count">{{ locks.length }}</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="pg-card" [hidden]="activeTab !== 'declarations'">
|
||||
<div class="pos-section-label">Dividend Declarations — Every Dividend Ever Declared, Tracked to Completion</div>
|
||||
<div class="inv-form-grid" style="padding: 12px 16px">
|
||||
<label class="inv-form-field" style="align-self: end">
|
||||
<button type="button" class="pg-btn" [disabled]="declarationsLoading" (click)="loadDeclarations()">
|
||||
Refresh
|
||||
</button>
|
||||
</label>
|
||||
<div class="dd-toolbar">
|
||||
<div class="dd-search">
|
||||
<input
|
||||
type="search"
|
||||
class="dd-search__input"
|
||||
placeholder="Search product, period or status…"
|
||||
[(ngModel)]="declarationSearch"
|
||||
name="declarationSearch"
|
||||
/>
|
||||
@if (declarationSearch) {
|
||||
<button type="button" class="dd-search__clear" (click)="declarationSearch = ''" aria-label="Clear search">
|
||||
×
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
<span class="dd-toolbar__count">
|
||||
{{ visibleDeclarations.length }} of {{ declarations.length }} declaration{{
|
||||
declarations.length === 1 ? '' : 's'
|
||||
}}
|
||||
</span>
|
||||
<button type="button" class="pg-btn" [disabled]="declarationsLoading" (click)="loadDeclarations()">Refresh</button>
|
||||
</div>
|
||||
<table class="pg-table">
|
||||
<thead>
|
||||
@@ -75,8 +130,12 @@
|
||||
<tr>
|
||||
<td colspan="6" class="pg-empty">No dividends have been declared yet.</td>
|
||||
</tr>
|
||||
} @else if (visibleDeclarations.length === 0) {
|
||||
<tr>
|
||||
<td colspan="6" class="pg-empty">No declaration matches “{{ declarationSearch }}”.</td>
|
||||
</tr>
|
||||
} @else {
|
||||
@for (d of declarations; track d.declarationId) {
|
||||
@for (d of visibleDeclarations; track d.declarationId) {
|
||||
<tr>
|
||||
<td>{{ d.productName }}</td>
|
||||
<td>{{ d.periodStartDate }} → {{ d.periodEndDate }}</td>
|
||||
@@ -95,12 +154,27 @@
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="pg-card" style="margin-top: 16px">
|
||||
<div class="pg-card" [hidden]="activeTab !== 'overdue'">
|
||||
<div class="pos-section-label">Dividend Dashboard — Locked Dividends for Members with Overdue EMI</div>
|
||||
<div class="inv-form-grid" style="padding: 12px 16px">
|
||||
<label class="inv-form-field" style="align-self: end">
|
||||
<button type="button" class="pg-btn" [disabled]="loading" (click)="load()">Refresh</button>
|
||||
</label>
|
||||
<div class="dd-toolbar">
|
||||
<div class="dd-search">
|
||||
<input
|
||||
type="search"
|
||||
class="dd-search__input"
|
||||
placeholder="Search member name or share account no…"
|
||||
[(ngModel)]="overdueSearch"
|
||||
name="overdueSearch"
|
||||
/>
|
||||
@if (overdueSearch) {
|
||||
<button type="button" class="dd-search__clear" (click)="overdueSearch = ''" aria-label="Clear search">
|
||||
×
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
<span class="dd-toolbar__count">
|
||||
{{ visibleOverdueRows.length }} of {{ rows.length }} member{{ rows.length === 1 ? '' : 's' }}
|
||||
</span>
|
||||
<button type="button" class="pg-btn" [disabled]="loading" (click)="load()">Refresh</button>
|
||||
</div>
|
||||
<table class="pg-table">
|
||||
<thead>
|
||||
@@ -122,10 +196,19 @@
|
||||
<tr>
|
||||
<td colspan="6" class="pg-empty">No members with a locked dividend and an overdue EMI right now.</td>
|
||||
</tr>
|
||||
} @else if (visibleOverdueRows.length === 0) {
|
||||
<tr>
|
||||
<td colspan="6" class="pg-empty">No member matches “{{ overdueSearch }}”.</td>
|
||||
</tr>
|
||||
} @else {
|
||||
@for (row of rows; track row.lockId) {
|
||||
@for (row of visibleOverdueRows; track row.lockId) {
|
||||
<tr>
|
||||
<td>{{ row.clientName }}</td>
|
||||
<td>
|
||||
{{ row.clientName }}
|
||||
@if (row.shareAccountNo) {
|
||||
<div class="dd-account-no">{{ row.shareAccountNo }}</div>
|
||||
}
|
||||
</td>
|
||||
<td class="num">{{ row.lockedAmount | number: '1.2-2' }}</td>
|
||||
<td>{{ row.lockedDate }}</td>
|
||||
<td>{{ row.releaseDueDate }}</td>
|
||||
@@ -181,7 +264,7 @@
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="pg-card" style="margin-top: 16px">
|
||||
<div class="pg-card" [hidden]="activeTab !== 'locks'">
|
||||
<div class="pos-section-label">All Dividend Locks — Full 3-Year Lock History</div>
|
||||
<div class="inv-form-grid" style="padding: 12px 16px; gap: 8px">
|
||||
<div class="dd-filter-group">
|
||||
@@ -196,9 +279,21 @@
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
<label class="inv-form-field" style="align-self: end; margin-left: auto">
|
||||
<button type="button" class="pg-btn" [disabled]="locksLoading" (click)="loadLocks()">Refresh</button>
|
||||
</label>
|
||||
<div class="dd-search" style="margin-left: auto">
|
||||
<input
|
||||
type="search"
|
||||
class="dd-search__input"
|
||||
placeholder="Search member name or share account no…"
|
||||
[(ngModel)]="lockSearch"
|
||||
name="lockSearch"
|
||||
/>
|
||||
@if (lockSearch) {
|
||||
<button type="button" class="dd-search__clear" (click)="lockSearch = ''" aria-label="Clear search">
|
||||
×
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
<button type="button" class="pg-btn" [disabled]="locksLoading" (click)="loadLocks()">Refresh</button>
|
||||
</div>
|
||||
|
||||
<!-- Financial-year chips, built from the locks themselves: a new declaration adds its year here
|
||||
@@ -246,7 +341,7 @@
|
||||
<th>Release Due</th>
|
||||
<th>Status</th>
|
||||
<th>Applied Loan</th>
|
||||
<th></th>
|
||||
<th>Demand Draft</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -256,12 +351,23 @@
|
||||
</tr>
|
||||
} @else if (locksLoaded && visibleLocks.length === 0) {
|
||||
<tr>
|
||||
<td colspan="8" class="pg-empty">No dividend locks match this filter.</td>
|
||||
<td colspan="8" class="pg-empty">
|
||||
@if (lockSearch) {
|
||||
No dividend lock matches “{{ lockSearch }}”.
|
||||
} @else {
|
||||
No dividend locks match this filter.
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
} @else {
|
||||
@for (l of visibleLocks; track l.lockId) {
|
||||
<tr>
|
||||
<td>{{ l.clientName }}</td>
|
||||
<td>
|
||||
{{ l.clientName }}
|
||||
@if (l.shareAccountNo) {
|
||||
<div class="dd-account-no">{{ l.shareAccountNo }}</div>
|
||||
}
|
||||
</td>
|
||||
<td>
|
||||
@if (l.financialYear) {
|
||||
<div class="dd-dividend-year">FY {{ l.financialYear }}</div>
|
||||
@@ -279,170 +385,21 @@
|
||||
<span class="dd-badge" [ngClass]="lockStatusClass(l.status)">{{ l.status | titlecase }}</span>
|
||||
</td>
|
||||
<td>{{ l.appliedLoanAccountNo || '—' }}</td>
|
||||
<!-- Once a draft has been raised the clerk needs its number, not another Claim button:
|
||||
it is what goes on the counterfoil and what the member asks about when they return.
|
||||
Until then, the button hands off to the DD inventory to raise one. -->
|
||||
<td>
|
||||
@if (l.status === 'UNCLAIMED') {
|
||||
<button type="button" class="pg-btn" (click)="toggleClaim(l)">
|
||||
{{ expandedClaimLockId === l.lockId ? 'Cancel' : 'Claim' }}
|
||||
@if (l.claimDdNumber && l.claimStatus === 'APPROVED') {
|
||||
<span class="dd-dd-number">{{ l.claimDdNumber }}</span>
|
||||
} @else if (l.claimStatus === 'PENDING') {
|
||||
<span class="dd-badge dd-badge--pending">DD {{ l.claimDdNumber }} — awaiting approval</span>
|
||||
} @else if (l.status === 'UNCLAIMED') {
|
||||
<button type="button" class="pg-btn dd-apply-confirm" (click)="claimAsDemandDraft(l)">
|
||||
Claim by DD
|
||||
</button>
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
@if (expandedClaimLockId === l.lockId) {
|
||||
<tr class="dd-apply-row">
|
||||
<td colspan="8">
|
||||
<div class="dd-apply-panel dd-apply-panel--wrap">
|
||||
<div class="dd-apply-panel-title">
|
||||
Unclaimed amount: {{ l.amount | number: '1.2-2' }}
|
||||
@if (l.financialYear) {
|
||||
<span> (FY {{ l.financialYear }} dividend)</span>
|
||||
}
|
||||
— issue as a Demand Draft
|
||||
</div>
|
||||
<!-- A dividend is collected only as a Demand Draft. Amount defaults to the full
|
||||
unclaimed balance; anything left unclaimed stays on the lock for a later visit. -->
|
||||
<label class="inv-form-field">
|
||||
<span>DD Amount</span>
|
||||
<input type="number" [(ngModel)]="claimDdAmount[l.lockId]" min="0" step="0.01" />
|
||||
</label>
|
||||
<label class="inv-form-field">
|
||||
<span>Beneficiary Name</span>
|
||||
<input type="text" [(ngModel)]="claimDdBeneficiary[l.lockId]" />
|
||||
</label>
|
||||
<label class="inv-form-field">
|
||||
<span>Payable At — Branch</span>
|
||||
<input type="text" [(ngModel)]="claimDdPayableAtBranch[l.lockId]" />
|
||||
</label>
|
||||
<label class="inv-form-field">
|
||||
<span>Payable At — City</span>
|
||||
<input type="text" [(ngModel)]="claimDdPayableAtCity[l.lockId]" />
|
||||
</label>
|
||||
<div class="dd-apply-panel-total">
|
||||
Claiming: {{ claimDdAmount[l.lockId] || 0 | number: '1.2-2' }} /
|
||||
{{ l.amount | number: '1.2-2' }}
|
||||
</div>
|
||||
@if (claimError) {
|
||||
<div class="dd-apply-error">{{ claimError }}</div>
|
||||
}
|
||||
<button
|
||||
type="button"
|
||||
class="pg-btn dd-apply-confirm"
|
||||
[disabled]="claimSubmitting"
|
||||
(click)="submitClaim(l)"
|
||||
>
|
||||
{{ claimSubmitting ? 'Submitting…' : 'Submit Claim Request' }}
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
}
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="pg-card" style="margin-top: 16px">
|
||||
<div class="pos-section-label">Dividend Claim Requests — Pending Review</div>
|
||||
|
||||
<!-- The draft just drawn. Approving refreshes the list, so the row it came from is gone by the
|
||||
time the clerk looks — and the DD number is exactly what they need to write on the
|
||||
counterfoil and hand over. -->
|
||||
@if (lastIssuedDd) {
|
||||
<div class="dd-issued-banner">
|
||||
<div class="dd-issued-banner__main">
|
||||
Demand Draft <strong>{{ lastIssuedDd.ddNumber }}</strong> issued to {{ lastIssuedDd.clientName }} for
|
||||
{{ lastIssuedDd.amount | number: '1.2-2' }}
|
||||
</div>
|
||||
<button type="button" class="dd-issued-banner__close" (click)="lastIssuedDd = null" aria-label="Dismiss">
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
<div class="inv-form-grid" style="padding: 12px 16px">
|
||||
<label class="inv-form-field" style="align-self: end">
|
||||
<button type="button" class="pg-btn" [disabled]="claimRequestsLoading" (click)="loadClaimRequests()">
|
||||
Refresh
|
||||
</button>
|
||||
</label>
|
||||
</div>
|
||||
<table class="pg-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Client</th>
|
||||
<!-- A dividend is collected only as a Demand Draft, so what the checker needs to see is the
|
||||
draft about to be drawn: for how much, in whose favour, payable where. -->
|
||||
<th class="num">DD Amount</th>
|
||||
<th>Beneficiary</th>
|
||||
<th>Payable At</th>
|
||||
<th class="num">Unclaimed Balance</th>
|
||||
<th>Requested By</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@if (claimRequestsLoading) {
|
||||
<tr>
|
||||
<td colspan="7" class="pg-empty">Loading…</td>
|
||||
</tr>
|
||||
} @else if (claimRequestsLoaded && claimRequests.length === 0) {
|
||||
<tr>
|
||||
<td colspan="7" class="pg-empty">No dividend claim requests pending review.</td>
|
||||
</tr>
|
||||
} @else {
|
||||
@for (req of claimRequests; track req.id) {
|
||||
<tr>
|
||||
<td>{{ req.client_name }}</td>
|
||||
<td class="num">{{ req.dd_apply_amount | number: '1.2-2' }}</td>
|
||||
<td>{{ req.dd_beneficiary_name || '—' }}</td>
|
||||
<td>
|
||||
@if (req.dd_payable_at_branch || req.dd_payable_at_city) {
|
||||
{{ req.dd_payable_at_branch }}
|
||||
@if (req.dd_payable_at_branch && req.dd_payable_at_city) {
|
||||
,
|
||||
}
|
||||
{{ req.dd_payable_at_city }}
|
||||
} @else {
|
||||
—
|
||||
}
|
||||
</td>
|
||||
<td class="num">{{ req.locked_amount | number: '1.2-2' }}</td>
|
||||
<td>{{ req.clerk_name }}</td>
|
||||
<td>
|
||||
<button type="button" class="pg-btn" [disabled]="claimActioningId === req.id" (click)="approveClaim(req)">
|
||||
{{ claimActioningId === req.id ? 'Approving…' : 'Approve' }}
|
||||
</button>
|
||||
<button type="button" class="pg-btn" (click)="toggleRejectClaim(req)">
|
||||
{{ expandedRejectClaimId === req.id ? 'Cancel' : 'Reject' }}
|
||||
</button>
|
||||
@if (claimActionError[req.id]) {
|
||||
<div class="dd-retry-error">{{ claimActionError[req.id] }}</div>
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
@if (expandedRejectClaimId === req.id) {
|
||||
<tr class="dd-apply-row">
|
||||
<td colspan="7">
|
||||
<div class="dd-apply-panel">
|
||||
<label class="inv-form-field">
|
||||
<span>Rejection Reason</span>
|
||||
<input
|
||||
type="text"
|
||||
[(ngModel)]="rejectReason[req.id]"
|
||||
placeholder="Why is this claim being rejected?"
|
||||
/>
|
||||
</label>
|
||||
<button
|
||||
type="button"
|
||||
class="pg-btn dd-apply-confirm"
|
||||
[disabled]="claimActioningId === req.id"
|
||||
(click)="rejectClaim(req)"
|
||||
>
|
||||
{{ claimActioningId === req.id ? 'Rejecting…' : 'Confirm Reject' }}
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
}
|
||||
}
|
||||
</tbody>
|
||||
|
||||
Reference in New Issue
Block a user