feat: implement cashier dashboard, dividend dashboards, share accounts views, and checker tasks

This commit is contained in:
2026-08-25 11:20:42 +05:30
parent 7ef75c902d
commit 0b79b0b0d3
77 changed files with 7396 additions and 426 deletions
@@ -103,6 +103,12 @@
<div class="detail-value">{{ resolutionNumber }}</div>
</div>
}
@if (resolutionDate) {
<div class="detail-block">
<div class="detail-label">Resolution Date</div>
<div class="detail-value">{{ resolutionDate | dateFormat }}</div>
</div>
}
</div>
</div>
@@ -133,64 +139,71 @@
<!-- Transactions Table -->
@if (filteredTransactions.length > 0) {
<table class="statement-table">
<thead>
<tr>
<th class="col-sr">#</th>
<th class="col-date">Date</th>
<th class="col-desc">Description</th>
<th class="col-ref">Ref No.</th>
<th class="col-amount text-right">Shares Purchased</th>
<th class="col-amount text-right">Shares Redeemed</th>
<th class="col-balance text-right">Share Balance</th>
<th class="col-amount text-right">Amount (&#8377;)</th>
<th class="col-desc">Resolution No. / Meeting</th>
</tr>
</thead>
<tbody>
@for (txn of filteredTransactions; track txn.id; let i = $index) {
<tr [class.debit-row]="isRedeem(txn)" [class.credit-row]="!isRedeem(txn)">
<td class="col-sr text-center">{{ i + 1 }}</td>
<td class="col-date">{{ txn.purchasedDate | dateFormat }}</td>
<td class="col-desc">
<span class="txn-desc">{{ txn.type.value }} ({{ txn.status.value }})</span>
</td>
<td class="col-ref text-muted mono">{{ txn.id }}</td>
<td class="col-amount text-right credit-amount">
@if (!isRedeem(txn)) {
{{ txn.numberOfShares }}
}
</td>
<td class="col-amount text-right debit-amount">
@if (isRedeem(txn)) {
{{ txn.numberOfShares }}
}
</td>
<td class="col-balance text-right balance-amount">{{ txn.runningBalance }}</td>
<td class="col-amount text-right">
{{ txn.amount | currency: currency?.code : 'symbol-narrow' : '1.2-2' }}
</td>
<td class="col-desc">
@if (isRedeem(txn)) {
{{ txn.resolutionNumber || '—' }}
@if (txn.meetingDate) {
({{ txn.meetingDate | dateFormat }})
}
} @else {
—
}
</td>
<div class="statement-table-scroll">
<table class="statement-table">
<thead>
<tr>
<th class="col-sr text-center">#</th>
<th class="col-date text-left">Date</th>
<th class="col-desc text-left">Description</th>
<th class="col-ref text-center">Ref No.</th>
<th class="col-amount text-right">Shares Purchased</th>
<th class="col-amount text-right">Shares Redeemed</th>
<th class="col-balance text-right">Share Balance</th>
<th class="col-amount text-right">Amount (&#8377;)</th>
<th class="col-resolution text-left">Resolution No. / Meeting</th>
</tr>
}
</tbody>
<tfoot>
<tr class="total-row">
<td colspan="6" class="text-right total-label">Closing Share Balance</td>
<td class="text-right closing-balance">{{ totalApprovedShares }}</td>
<td colspan="2"></td>
</tr>
</tfoot>
</table>
</thead>
<tbody>
@for (txn of filteredTransactions; track txn.id; let i = $index) {
<tr [class.debit-row]="isRedeem(txn)" [class.credit-row]="!isRedeem(txn)">
<td class="col-sr text-center">{{ i + 1 }}</td>
<td class="col-date text-left">{{ txn.purchasedDate | dateFormat }}</td>
<td class="col-desc text-left">
<span class="txn-desc">{{ txn.type.value }} ({{ txn.status.value }})</span>
</td>
<td class="col-ref text-center text-muted mono">{{ txn.id }}</td>
<td class="col-amount text-right credit-amount">
@if (!isRedeem(txn)) {
{{ txn.numberOfShares }}
}
</td>
<td class="col-amount text-right debit-amount">
@if (isRedeem(txn)) {
{{ txn.numberOfShares }}
}
</td>
<td class="col-balance text-right balance-amount">{{ txn.runningBalance }}</td>
<td class="col-amount text-right">
{{ txn.amount | currency: currency?.code : 'symbol-narrow' : '1.2-2' }}
</td>
<td class="col-resolution text-left">
@if (isRedeem(txn)) {
{{ txn.resolutionNumber || '—' }}
@if (txn.meetingDate) {
({{ txn.meetingDate | dateFormat }})
}
} @else {
<!-- Purchases are authorised by the share account's own board resolution, which is
held on the account rather than the transaction. -->
{{ resolutionNumber || '—' }}
@if (resolutionDate) {
({{ resolutionDate | dateFormat }})
}
}
</td>
</tr>
}
</tbody>
<tfoot>
<tr class="total-row">
<td colspan="6" class="text-right total-label">Closing Share Balance</td>
<td class="text-right closing-balance">{{ totalApprovedShares }}</td>
<td colspan="2"></td>
</tr>
</tfoot>
</table>
</div>
} @else {
<div class="no-data-block">
<mat-icon>inbox</mat-icon>
@@ -214,6 +214,14 @@ $row-alt: #f4f7fb;
}
// ─── Transactions Table ───────────────────────────────────────────────────────
// The columns below are fixed-width so each cell stays under its own header. On a narrow
// viewport that total exceeds the card, so the table scrolls here rather than being clipped
// by the wrapper's overflow:hidden.
.statement-table-scroll {
width: 100%;
overflow-x: auto;
}
.statement-table {
width: 100%;
border-collapse: collapse;
@@ -225,6 +233,10 @@ $row-alt: #f4f7fb;
border-bottom: 2px solid $bank-primary;
}
// Headers default to left so each one starts exactly where its cell's text starts; the
// numeric columns opt into right alignment via .text-right below. Without this the browser
// (and Material's table styles) can centre the heading while the cell stays left, which
// reads as the whole column being out of position.
thead th {
padding: 9px 10px;
font-size: 11px;
@@ -233,6 +245,25 @@ $row-alt: #f4f7fb;
text-transform: uppercase;
letter-spacing: 0.4px;
white-space: nowrap;
text-align: left;
}
// Alignment helpers must win over the thead default above, for header and cell alike, so
// a column's heading and its values always share one alignment.
thead th,
tbody td,
tfoot td {
&.text-right {
text-align: right;
}
&.text-center {
text-align: center;
}
&.text-left {
text-align: left;
}
}
tbody tr {
@@ -274,20 +305,36 @@ $row-alt: #f4f7fb;
white-space: nowrap;
}
// Description is the only flexible column: it absorbs the leftover width so every other
// column keeps the fixed size its header needs and stays aligned with its cells.
.col-desc {
min-width: 140px;
width: auto;
min-width: 160px;
}
// Board Resolution No. / Meeting Date — wide enough to hold "00RK2 (14 August 2026)"
// on one line so it does not crowd the right-aligned Amount column beside it.
.col-resolution {
width: 190px;
white-space: nowrap;
padding-left: 16px;
}
.col-ref {
width: 70px;
width: 80px;
white-space: nowrap;
}
// Sized to the widest header label ("Shares Purchased") so the numeric cells sit directly
// under their own heading rather than drifting toward the neighbouring column.
.col-amount {
width: 100px;
width: 128px;
white-space: nowrap;
}
.col-balance {
width: 90px;
width: 112px;
white-space: nowrap;
}
// ─── Amount colors ────────────────────────────────────────────────────────────
@@ -335,6 +382,10 @@ $row-alt: #f4f7fb;
text-align: center;
}
.text-left {
text-align: left;
}
.text-muted {
color: #999;
}
@@ -450,6 +501,12 @@ $row-alt: #f4f7fb;
border-radius: 0;
}
// On paper there is nothing to scroll — let the table lay out at full width so no
// column is cut off at the page edge.
.statement-table-scroll {
overflow-x: visible;
}
.bank-header {
-webkit-print-color-adjust: exact;
print-color-adjust: exact;
@@ -47,6 +47,7 @@ export class SharesPassbookTabComponent implements OnInit {
status = '';
activatedOnDate: any = null;
resolutionNumber = '';
resolutionDate: any = null;
savingsAccountNumber = '';
totalApprovedShares = 0;
currentMarketPrice = 0;
@@ -75,6 +76,7 @@ export class SharesPassbookTabComponent implements OnInit {
this.status = account.status?.value || '';
this.activatedOnDate = account.timeline?.activatedDate || null;
this.resolutionNumber = account.resolutionNumber || '';
this.resolutionDate = account.resolutionDate || null;
this.savingsAccountNumber = account.savingsAccountNumber || '';
this.totalApprovedShares = account.summary?.totalApprovedShares || 0;
this.currentMarketPrice = account.currentMarketPrice || 0;