-
+
+
+
+
+
Account Holder
+
{{ account.clientName || account.partyName | uppercase }}
+
+
+
Account Number
+
{{ account.accountNo }}
+
+
+
Party Code
+
{{ account.partyCode }}
+
+
+
Product
+
{{ account.subType }}
+
+
+
Account Opening Date
+
{{ account.openingDate | dateFormat }}
+
+
+
Status
+
+ {{ account.status }}
+
+
+
+
Agent
+
+ {{ account.agentCode }}{{ account.agentName ? ' · ' + account.agentName : '' }}
+
+
+
+
Interest Rate (p.a.)
+
{{ account.interestRate }}%
+
+
+
Maturity Date
+
{{ account.maturityDate | dateFormat }}
+
+
+
- @if (shownTransactions.length === 0) {
-
-
- {{
- hasDateFilters
- ? 'Nothing moved on this account in these dates'
- : 'Nothing has been credited to this account yet'
- }}
-
- } @else {
-
-
-
-
- #
- ID
- Transaction Date
- Transaction Type
- Voucher
- Description
- Debit
- Credit
- Balance
-
-
-
- @for (txn of shownTransactions; track txn.id; let i = $index) {
+
+
+
+
Current Balance
+
+ {{ account.currentBalance || 0 | currency: 'INR' : 'symbol-narrow' : '1.2-2' }}
+
+
+
+
+
Maturity Amount
+
+ {{ account.maturityAmount || 0 | currency: 'INR' : 'symbol-narrow' : '1.2-2' }}
+
+
+
+
+
Total Transactions
+
{{ passbookEntries.length }}
+
+
+
+
+ @if (passbookEntries.length > 0) {
+
+
- {{ i + 1 }}
- {{ txn.id }}
- {{ txn.transactionDate | date: 'dd MMMM yyyy' }}
- {{ label(txn.transactionType) }}
- {{ txn.voucherNo || '—' }}
- {{ txn.description }}
-
- {{ txn.direction === 'DEBIT' ? '₹' + (txn.amount | formatNumber) : 'N/A' }}
+ #
+ Date
+ Description
+ Ref No.
+ Debit (Dr)
+ Credit (Cr)
+ Balance (₹)
+
+
+
+ @for (txn of passbookEntries; track txn.id; let i = $index) {
+
+ {{ i + 1 }}
+ {{ txn.transactionDate | dateFormat }}
+
+ {{ label(txn.transactionType) }}
+
+ {{ txn.refNo || txn.id }}
+
+ @if (txn.direction === 'DEBIT') {
+ {{ txn.amount | currency: 'INR' : 'symbol-narrow' : '1.2-2' }}
+ }
+
+
+ @if (txn.direction === 'CREDIT') {
+ {{ txn.amount | currency: 'INR' : 'symbol-narrow' : '1.2-2' }}
+ }
+
+
+ {{ txn.balanceAfterTransaction || 0 | currency: 'INR' : 'symbol-narrow' : '1.2-2' }}
+
+
+ }
+
+
+
+ Total / Closing Balance
+
+ {{ totalDebits | currency: 'INR' : 'symbol-narrow' : '1.2-2' }}
-
- {{ txn.direction === 'CREDIT' ? '₹' + (txn.amount | formatNumber) : 'N/A' }}
+
+ {{ totalCredits | currency: 'INR' : 'symbol-narrow' : '1.2-2' }}
-
- ₹{{ txn.balanceAfterTransaction || 0 | formatNumber }}
+
+ {{ passbookClosingBalance | currency: 'INR' : 'symbol-narrow' : '1.2-2' }}
- }
-
-
-
- }
-
- }
+
+
+ } @else {
+
+
inbox
+
{{ hasDateFilters ? 'No transactions found for the selected date range.' : 'No entries yet.' }}
+
+ }
-
- @if (tab === 'passbook') {
-
-
+ }
-
-
-
-
DDS Passbook
-
{{ account.officeName }}
-
-
- Account No {{ account.accountNo }} Party Code {{ account.partyCode }} Name {{ account.clientName || account.partyName }} Product {{ account.subType }} · {{ account.interestRate }} % Opened On {{ account.openingDate | date: 'dd/MM/yyyy' }} Agent {{ account.agentCode }}
-
-
-
- @if (passbookEntries.length === 0) {
+
+ @if (tab === 'nominees') {
+
+ @if (!account.nominees || account.nominees.length === 0) {
- {{ hasDateFilters ? 'No entries in these dates' : 'No entries yet' }}
+
+ No nominee on this account
} @else {
-
-
-
- Date
- Voucher
- Particulars
- Withdrawals
- Deposits
- Balance
-
-
-
- @for (txn of passbookEntries; track txn.id) {
+
+
+
- {{ txn.transactionDate | date: 'dd/MM/yyyy' }}
- {{ txn.voucherNo || '' }}
-
- {{ label(txn.transactionType) }}
- @if (txn.description) {
- {{ txn.description }}
- }
-
- {{ txn.direction === 'DEBIT' ? (txn.amount | formatNumber) : '' }}
- {{ txn.direction === 'CREDIT' ? (txn.amount | formatNumber) : '' }}
-
- {{ txn.balanceAfterTransaction || 0 | formatNumber }}
-
+ #
+ Name
+ Relation
+ Gender
+ Mobile
+ Birth Date
+ Nominated On
+ Guardian
+ {{ account.nomineeSplitMode === 'Sequence' ? 'Order' : 'Share %' }}
- }
-
-
-
- Total
-
- {{ totalDebits | formatNumber }}
-
-
- {{ totalCredits | formatNumber }}
-
-
- {{ passbookClosingBalance | formatNumber }}
-
-
-
-
+
+
+ @for (nominee of account.nominees; track nominee.id; let i = $index) {
+
+ {{ i + 1 }}
+
+ {{ nominee.name }}
+
+ {{ nominee.relation }}
+ {{ nominee.gender }}
+ {{ nominee.mobileNo || '—' }}
+ {{ nominee.birthDate ? (nominee.birthDate | date: 'dd MMM yyyy') : '—' }}
+ {{ nominee.nominationDate ? (nominee.nominationDate | date: 'dd MMM yyyy') : '—' }}
+ {{ nominee.guardianName || '—' }}
+
+ {{
+ account.nomineeSplitMode === 'Sequence' ? nominee.sequenceNo : (nominee.sharePercent ?? '—')
+ }}
+
+
+ }
+
+
+
}
-
- }
-
-
- @if (tab === 'nominees') {
-
- @if (!account.nominees || account.nominees.length === 0) {
-
-
- No nominee on this account
-
- } @else {
-
-
-
-
- #
- Name
- Relation
- Gender
- Mobile
- Birth Date
- Nominated On
- Guardian
- {{ account.nomineeSplitMode === 'Sequence' ? 'Order' : 'Share %' }}
-
-
-
- @for (nominee of account.nominees; track nominee.id; let i = $index) {
-
- {{ i + 1 }}
-
- {{ nominee.name }}
-
- {{ nominee.relation }}
- {{ nominee.gender }}
- {{ nominee.mobileNo || '—' }}
- {{ nominee.birthDate ? (nominee.birthDate | date: 'dd MMM yyyy') : '—' }}
- {{ nominee.nominationDate ? (nominee.nominationDate | date: 'dd MMM yyyy') : '—' }}
- {{ nominee.guardianName || '—' }}
-
- {{ account.nomineeSplitMode === 'Sequence' ? nominee.sequenceNo : (nominee.sharePercent ?? '—') }}
-
-
- }
-
-
-
- }
-
- }
+ }
+
}
diff --git a/src/app/deposits/dds-accounts/dds-account-view/dds-account-view.component.scss b/src/app/deposits/dds-accounts/dds-account-view/dds-account-view.component.scss
index d0da71aa8..4b5f16eae 100644
--- a/src/app/deposits/dds-accounts/dds-account-view/dds-account-view.component.scss
+++ b/src/app/deposits/dds-accounts/dds-account-view/dds-account-view.component.scss
@@ -157,45 +157,89 @@
}
}
-/* ── Tabs ─────────────────────────────────────────────────────────────── */
+/* ── Tabs: the savings account page's pill tabs ───────────────────────── */
+
+$tab-navy: #0d1b2a;
+$tab-blue: #1e88e5;
+$tab-blue-d: #1e40af;
+$tab-border: #e2e8f0;
.dav-tabs {
- display: flex;
- gap: 8px;
- padding: 8px;
- border-radius: 14px;
background: #fff;
- border: 1px solid rgb(0 0 0 / 7%);
+ border: 1px solid $tab-border;
+ border-radius: 14px;
+ padding: 6px;
+ box-shadow: 0 1px 3px rgb(15 23 42 / 4%);
+ overflow: hidden;
+
+ ::ng-deep {
+ .mat-mdc-tab-link-container {
+ border-bottom: none !important;
+ }
+
+ .mat-mdc-tab-links {
+ gap: 4px;
+ flex-wrap: wrap;
+ }
+
+ .mdc-tab-indicator__content--underline,
+ .mat-mdc-tab-header-pagination-controls-enabled .mdc-tab-indicator__content--underline {
+ display: none !important;
+ }
+ }
}
.dav-tab {
- flex: 1;
- display: flex;
+ display: inline-flex !important;
align-items: center;
- justify-content: center;
- gap: 6px;
- padding: 10px 16px;
- border: 0;
- border-radius: 10px;
- background: transparent;
- font-size: 0.95rem;
- font-weight: 600;
- color: #475569;
+ gap: 8px;
+ height: 38px !important;
+ min-width: 0 !important;
+ padding: 0 16px !important;
+ border-radius: 10px !important;
+ font-family: Inter, 'DM Sans', Roboto, sans-serif !important;
+ font-size: 13px !important;
+ font-weight: 600 !important;
+ letter-spacing: 0.01em !important;
+ color: #64748b !important;
+ background: transparent !important;
+ transition: all 0.15s ease;
+ white-space: nowrap;
+ border: 1px solid transparent !important;
+ opacity: 1 !important;
cursor: pointer;
- &:hover {
- background: #f1f5f9;
+ &__ic {
+ font-size: 12px;
+ color: inherit;
+ }
+
+ &:hover:not(.mdc-tab--active) {
+ background: #f1f5f9 !important;
+ color: $tab-navy !important;
+ }
+
+ &.mdc-tab--active,
+ &.mat-mdc-tab-link-active {
+ background: linear-gradient(135deg, $tab-blue 0%, $tab-blue-d 100%) !important;
+ color: #fff !important;
+ box-shadow: 0 2px 6px -1px rgb(30 64 175 / 35%);
+ border-color: $tab-blue-d !important;
+
+ ::ng-deep .mdc-tab__text-label,
+ ::ng-deep .mat-mdc-tab-link-content,
+ ::ng-deep span,
+ .dav-tab__ic {
+ color: #fff !important;
+ opacity: 1 !important;
+ }
}
}
-.dav-tab--on {
- color: #fff;
- background: linear-gradient(90deg, #1d4ed8, #2563eb);
- box-shadow: 0 4px 12px rgb(37 99 235 / 30%);
-
- &:hover {
- background: linear-gradient(90deg, #1d4ed8, #2563eb);
- }
+.dav-body {
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
}
/* ── Cards ────────────────────────────────────────────────────────────── */
@@ -269,38 +313,7 @@
color: rgb(0 0 0 / 50%);
}
-/* ── Transactions ─────────────────────────────────────────────────────── */
-
-.dav-toolbar {
- display: flex;
- align-items: center;
- flex-wrap: wrap;
- gap: 12px;
- padding: 14px 20px;
- background: #f8fafc;
- border-bottom: 1px solid rgb(0 0 0 / 7%);
-}
-
-.dav-datepick {
- display: flex;
- flex-direction: column;
- gap: 2px;
-
- span {
- font-size: 0.68rem;
- font-weight: 700;
- letter-spacing: 0.06em;
- text-transform: uppercase;
- color: rgb(0 0 0 / 45%);
- }
-
- input {
- padding: 6px 10px;
- border-radius: 8px;
- border: 1px solid rgb(0 0 0 / 15%);
- font: inherit;
- }
-}
+/* ── Shared tables and buttons (nominees, not-found state) ─────────── */
.dav-btn {
display: inline-flex;
@@ -319,21 +332,6 @@
}
}
-.dav-btn--primary {
- color: #fff;
- border-color: transparent;
- background: linear-gradient(90deg, #1d4ed8, #2563eb);
-}
-
-.dav-totals {
- margin-left: auto;
- display: flex;
- gap: 16px;
- font-size: 0.88rem;
- font-weight: 700;
- font-variant-numeric: tabular-nums;
-}
-
.dav-table-wrap {
overflow-x: auto;
}
@@ -366,11 +364,6 @@
}
}
-.dav-desc {
- max-width: 320px;
- color: rgb(0 0 0 / 65%);
-}
-
@media (width <= 720px) {
.dav-page {
padding: 16px;
@@ -382,66 +375,11 @@
}
.dav-tab {
- font-size: 0.85rem;
- padding: 8px;
+ padding: 0 12px !important;
+ font-size: 12px !important;
}
}
-/* ── Passbook ─────────────────────────────────────────────────────────── */
-
-.dav-push {
- margin-left: auto;
-}
-
-.dav-passbook {
- padding: 20px;
-}
-
-.dav-pb-head {
- display: flex;
- justify-content: space-between;
- flex-wrap: wrap;
- gap: 16px 32px;
- margin-bottom: 16px;
- padding-bottom: 16px;
- border-bottom: 2px solid #1e3a8a;
-}
-
-.dav-pb-title {
- font-size: 1.2rem;
- font-weight: 700;
- color: #1e3a8a;
-}
-
-.dav-pb-sub {
- font-size: 0.85rem;
- color: rgb(0 0 0 / 55%);
-}
-
-.dav-pb-grid {
- display: grid;
- grid-template-columns: auto auto;
- gap: 4px 16px;
- font-size: 0.88rem;
-
- span {
- color: rgb(0 0 0 / 55%);
- }
-}
-
-.dav-pb-table {
- tfoot td {
- padding: 14px 16px;
- border-top: 2px solid rgb(0 0 0 / 15%);
- }
-}
-
-.dav-pb-note {
- display: block;
- font-size: 0.78rem;
- color: rgb(0 0 0 / 50%);
-}
-
/* Closure tab */
.dav-closure-tag {
margin-left: 8px;
@@ -457,3 +395,629 @@
border-top: 1px solid rgb(0 0 0 / 10%);
font-weight: 600;
}
+
+/* ── Transactions: the savings account's Transactions tab ─────────────── */
+
+$txn-navy: #0d1b2a;
+$txn-slate: #475569;
+$txn-muted: #64748b;
+$txn-border: #e2e8f0;
+$txn-soft: #f8fafc;
+$txn-blue: #1e40af;
+
+.txn-tab {
+ width: 100%;
+
+ table {
+ width: 100%;
+ }
+}
+
+.txn-tab__card {
+ background: #fff;
+ border: 1px solid $txn-border;
+ border-radius: 14px;
+ box-shadow: 0 1px 3px rgb(15 23 42 / 4%);
+ overflow: hidden;
+}
+
+.txn-tab__toolbar {
+ display: flex;
+ align-items: center;
+ gap: 18px;
+ padding: 12px 18px;
+ background: $txn-soft;
+ border-bottom: 1px solid $txn-border;
+ flex-wrap: wrap;
+}
+
+.txn-tab__date {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ font-size: 13px;
+ color: $txn-slate;
+ font-weight: 500;
+
+ input {
+ height: 34px;
+ padding: 0 10px;
+ border-radius: 8px;
+ border: 1px solid $txn-border;
+ background: #fff;
+ font: inherit;
+ color: $txn-navy;
+ }
+}
+
+.txn-tab__btn {
+ height: 34px !important;
+ border-radius: 8px !important;
+ font-size: 13px !important;
+ font-weight: 600 !important;
+
+ fa-icon {
+ margin-right: 4px;
+ }
+}
+
+.txn-tab__spacer {
+ flex: 1;
+}
+
+.txn-tab__total {
+ font-size: 13px;
+ font-weight: 700;
+ font-variant-numeric: tabular-nums;
+}
+
+.txn-tab__total--cr {
+ color: #1a7a3f;
+}
+
+.txn-tab__total--dr {
+ color: #c0392b;
+}
+
+.txn-tab__export {
+ height: 36px !important;
+ border-radius: 8px !important;
+ font-weight: 600 !important;
+ font-size: 13px !important;
+ padding: 0 16px !important;
+ background: linear-gradient(135deg, #1e88e5 0%, $txn-blue 100%) !important;
+ color: #fff !important;
+
+ &:disabled {
+ opacity: 0.5;
+ }
+}
+
+::ng-deep .txn-tab__card {
+ .mat-mdc-header-row {
+ background: $txn-soft;
+ }
+
+ .mat-mdc-header-cell {
+ font-size: 11.5px;
+ font-weight: 800;
+ color: $txn-slate;
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+ padding: 12px 14px;
+ border-bottom: 1px solid $txn-border;
+ }
+
+ .mat-mdc-cell {
+ font-size: 13px;
+ color: $txn-navy;
+ padding: 11px 14px;
+ border-bottom: 1px solid #f1f5f9;
+ }
+
+ .mat-mdc-row:hover {
+ background: #f8fafc;
+ }
+
+ .mat-column-debit,
+ .mat-column-credit,
+ .mat-column-balance {
+ text-align: right;
+ font-variant-numeric: tabular-nums;
+ }
+
+ .mat-mdc-paginator {
+ border-top: 1px solid $txn-border;
+ background: $txn-soft;
+ min-height: 48px;
+ }
+
+ .transfer {
+ color: #6d28d9;
+ }
+
+ .txn-tab__nodata {
+ padding: 32px 14px;
+ text-align: center;
+ color: $txn-muted;
+ }
+}
+
+.txn-tab__empty {
+ background: #fff;
+ border: 1px solid $txn-border;
+ border-radius: 14px;
+ box-shadow: 0 1px 3px rgb(15 23 42 / 4%);
+ padding: 56px 24px;
+ text-align: center;
+ color: $txn-muted;
+}
+
+.txn-tab__empty-ico {
+ font-size: 34px;
+ color: #cbd5e1;
+ margin-bottom: 8px;
+}
+
+.txn-tab__empty-title {
+ font-size: 14.5px;
+ font-weight: 700;
+ color: $txn-slate;
+ margin-top: 6px;
+}
+
+.txn-tab__empty-sub {
+ font-size: 12.5px;
+ color: $txn-muted;
+ margin-top: 2px;
+}
+
+/* ── Passbook: the savings account's Passbook tab ─────────────────────── */
+
+$bank-primary: #1a3a6b;
+$bank-accent: #c8a951;
+$debit-color: #c0392b;
+$credit-color: #1a7a3f;
+$balance-color: #1a3a6b;
+$border-color: #d0d7e3;
+$row-alt: #f4f7fb;
+
+.passbook-container {
+ padding: 16px;
+ background: #eef1f7;
+ border-radius: 14px;
+ min-height: 100%;
+}
+
+.passbook-filter-bar {
+ margin-bottom: 16px;
+ background: #fff;
+ border-radius: 8px;
+ padding: 12px 16px;
+ border: 1px solid $border-color;
+}
+
+.passbook-filter-form {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ flex-wrap: wrap;
+}
+
+.passbook-date-field {
+ width: 180px;
+}
+
+.passbook-filter-spacer {
+ flex: 1;
+}
+
+.statement-wrapper {
+ background: #fff;
+ border: 1px solid $border-color;
+ border-radius: 8px;
+ overflow: hidden;
+ box-shadow: 0 2px 12px rgb(0 0 0 / 8%);
+ font-family: 'Segoe UI', Arial, sans-serif;
+}
+
+.bank-header {
+ background: $bank-primary;
+ padding: 18px 24px;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ border-bottom: 4px solid $bank-accent;
+}
+
+.bank-logo-area {
+ display: flex;
+ align-items: center;
+ gap: 14px;
+}
+
+.bank-logo-circle {
+ width: 52px;
+ height: 52px;
+ border-radius: 50%;
+ background: $bank-accent;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+
+ mat-icon {
+ color: $bank-primary;
+ font-size: 28px;
+ width: 28px;
+ height: 28px;
+ }
+}
+
+.bank-name-block {
+ .bank-title {
+ color: #fff;
+ font-size: 18px;
+ font-weight: 700;
+ letter-spacing: 0.3px;
+ }
+
+ .bank-subtitle {
+ color: $bank-accent;
+ font-size: 11px;
+ font-weight: 500;
+ letter-spacing: 1.5px;
+ text-transform: uppercase;
+ margin-top: 2px;
+ }
+}
+
+.statement-label {
+ text-align: right;
+
+ .stmt-label-text {
+ color: $bank-accent;
+ font-size: 13px;
+ font-weight: 700;
+ letter-spacing: 2px;
+ text-transform: uppercase;
+ }
+
+ .stmt-period {
+ color: rgb(255 255 255 / 75%);
+ font-size: 11px;
+ margin-top: 4px;
+ }
+}
+
+.branch-line {
+ background: #e8edf5;
+ border-bottom: 1px solid $border-color;
+ padding: 6px 24px;
+ display: flex;
+ flex-wrap: wrap;
+ gap: 4px 18px;
+ font-size: 11px;
+ color: #4a5a7a;
+}
+
+.account-details-band {
+ background: #f0f3f9;
+ border-bottom: 1px solid $border-color;
+ padding: 14px 24px;
+}
+
+.account-details-grid {
+ display: grid;
+ grid-template-columns: repeat(3, 1fr);
+ gap: 12px 24px;
+}
+
+.detail-block {
+ .detail-label {
+ font-size: 9px;
+ font-weight: 700;
+ color: #7a8aaa;
+ text-transform: uppercase;
+ letter-spacing: 0.8px;
+ margin-bottom: 3px;
+ }
+
+ .detail-value {
+ font-size: 13px;
+ color: #222;
+
+ &.bold {
+ font-weight: 600;
+ }
+
+ &.mono {
+ font-family: 'Courier New', monospace;
+ letter-spacing: 1px;
+ }
+
+ &.status-badge {
+ display: inline-block;
+ padding: 1px 8px;
+ border-radius: 10px;
+ font-size: 11px;
+ font-weight: 600;
+ background: #eee;
+ color: #555;
+
+ &.active {
+ background: #d4edda;
+ color: #155724;
+ }
+ }
+ }
+}
+
+.balance-summary-strip {
+ display: flex;
+ align-items: stretch;
+ background: $bank-primary;
+ padding: 0;
+}
+
+.balance-box {
+ flex: 1;
+ padding: 12px 20px;
+ text-align: center;
+
+ .balance-box-label {
+ font-size: 10px;
+ font-weight: 600;
+ color: rgb(255 255 255 / 65%);
+ text-transform: uppercase;
+ letter-spacing: 1px;
+ margin-bottom: 4px;
+ }
+
+ .balance-box-value {
+ font-size: 16px;
+ font-weight: 700;
+ color: $bank-accent;
+ }
+}
+
+.balance-divider {
+ width: 1px;
+ background: rgb(255 255 255 / 15%);
+ margin: 8px 0;
+}
+
+.statement-table {
+ width: 100%;
+ border-collapse: collapse;
+ font-size: 12.5px;
+
+ thead tr {
+ background: #e8edf5;
+ border-top: 1px solid $border-color;
+ border-bottom: 2px solid $bank-primary;
+ }
+
+ thead th {
+ padding: 9px 10px;
+ font-size: 11px;
+ font-weight: 700;
+ color: $bank-primary;
+ text-transform: uppercase;
+ letter-spacing: 0.4px;
+ white-space: nowrap;
+ }
+
+ tbody tr {
+ border-bottom: 1px solid #e8ecf3;
+ transition: background 0.1s;
+
+ &:nth-child(even) {
+ background: $row-alt;
+ }
+
+ &:hover {
+ background: #dce6f5;
+ }
+ }
+
+ tbody td {
+ padding: 7px 10px;
+ vertical-align: middle;
+ color: #333;
+ }
+
+ tfoot .total-row {
+ background: #e8edf5;
+ border-top: 2px solid $bank-primary;
+
+ td {
+ padding: 9px 10px;
+ }
+ }
+}
+
+.col-sr {
+ width: 38px;
+}
+
+.col-date {
+ width: 110px;
+ white-space: nowrap;
+}
+
+.col-desc {
+ min-width: 160px;
+}
+
+.col-ref {
+ width: 100px;
+}
+
+.col-amount {
+ width: 110px;
+}
+
+.col-balance {
+ width: 120px;
+}
+
+.debit-amount {
+ color: $debit-color;
+ font-weight: 500;
+}
+
+.credit-amount {
+ color: $credit-color;
+ font-weight: 500;
+}
+
+.balance-amount {
+ color: $balance-color;
+ font-weight: 600;
+ font-family: 'Courier New', monospace;
+}
+
+.total-label {
+ font-size: 12px;
+ font-weight: 700;
+ color: $bank-primary;
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+}
+
+.closing-balance {
+ font-size: 14px;
+ font-weight: 700;
+ color: $bank-primary;
+ font-family: 'Courier New', monospace;
+}
+
+.txn-desc {
+ font-weight: 500;
+}
+
+.text-right {
+ text-align: right;
+}
+
+.text-center {
+ text-align: center;
+}
+
+.text-muted {
+ color: #999;
+}
+
+.mono {
+ font-family: 'Courier New', monospace;
+ font-size: 11px;
+}
+
+.no-data-block {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ padding: 48px;
+ color: #aaa;
+
+ mat-icon {
+ font-size: 48px;
+ width: 48px;
+ height: 48px;
+ margin-bottom: 12px;
+ }
+
+ p {
+ font-size: 14px;
+ margin: 0;
+ }
+}
+
+.statement-footer {
+ background: #f0f3f9;
+ border-top: 1px solid $border-color;
+ padding: 12px 24px;
+ text-align: center;
+
+ .footer-note {
+ font-size: 10px;
+ color: #888;
+ margin-bottom: 4px;
+ font-style: italic;
+ }
+
+ .footer-meta {
+ font-size: 10px;
+ color: #aaa;
+ letter-spacing: 0.3px;
+ }
+}
+
+@media (width <= 768px) {
+ .account-details-grid {
+ grid-template-columns: 1fr 1fr;
+ }
+
+ .bank-header {
+ flex-direction: column;
+ gap: 12px;
+ align-items: flex-start;
+ }
+
+ .statement-label {
+ text-align: left;
+ }
+
+ .balance-summary-strip {
+ flex-direction: column;
+ }
+
+ .balance-divider {
+ width: auto;
+ height: 1px;
+ margin: 0 20px;
+ }
+
+ .passbook-filter-form {
+ flex-direction: column;
+ align-items: stretch;
+ }
+
+ .passbook-date-field {
+ width: 100%;
+ }
+
+ .passbook-filter-spacer {
+ display: none;
+ }
+}
+
+@media (width <= 480px) {
+ .account-details-grid {
+ grid-template-columns: 1fr;
+ }
+}
+
+@media print {
+ .no-print {
+ display: none !important;
+ }
+
+ .passbook-container {
+ background: #fff;
+ padding: 0;
+ }
+
+ .statement-wrapper {
+ border: none;
+ box-shadow: none;
+ border-radius: 0;
+ }
+
+ .bank-header,
+ .balance-summary-strip {
+ -webkit-print-color-adjust: exact;
+ print-color-adjust: exact;
+ }
+}
diff --git a/src/app/deposits/dds-accounts/dds-account-view/dds-account-view.component.ts b/src/app/deposits/dds-accounts/dds-account-view/dds-account-view.component.ts
index 33c3c817b..5ecaab665 100644
--- a/src/app/deposits/dds-accounts/dds-account-view/dds-account-view.component.ts
+++ b/src/app/deposits/dds-accounts/dds-account-view/dds-account-view.component.ts
@@ -6,14 +6,37 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
-import { Component, OnInit, inject } from '@angular/core';
+import { Component, DestroyRef, OnInit, ViewChild, inject } from '@angular/core';
+import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { ActivatedRoute, Router } from '@angular/router';
import { FormControl, ReactiveFormsModule } from '@angular/forms';
-import { NgClass } from '@angular/common';
+import { CurrencyPipe, NgClass } from '@angular/common';
import { MatTooltipModule } from '@angular/material/tooltip';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
+import { MatTabLink, MatTabNav, MatTabNavPanel } from '@angular/material/tabs';
+import {
+ MatCell,
+ MatCellDef,
+ MatColumnDef,
+ MatHeaderCell,
+ MatHeaderCellDef,
+ MatHeaderRow,
+ MatHeaderRowDef,
+ MatNoDataRow,
+ MatRow,
+ MatRowDef,
+ MatTable,
+ MatTableDataSource
+} from '@angular/material/table';
+import { MatPaginator } from '@angular/material/paginator';
+import { MatSort, MatSortHeader } from '@angular/material/sort';
+import { MatIcon } from '@angular/material/icon';
+import { MatIconButton } from '@angular/material/button';
+import { MatMenu, MatMenuItem, MatMenuTrigger } from '@angular/material/menu';
+import { MatDialog } from '@angular/material/dialog';
+import { DdsTransactionViewDialogComponent } from './dds-transaction-view-dialog/dds-transaction-view-dialog.component';
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
-import { forkJoin, of } from 'rxjs';
+import { forkJoin, merge, of } from 'rxjs';
import { catchError } from 'rxjs/operators';
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
import { FormatNumberPipe } from 'app/pipes/format-number.pipe';
@@ -52,6 +75,29 @@ const TRANSACTION_LABELS: Record
= {
FaIconComponent,
MatTooltipModule,
MatProgressSpinnerModule,
+ MatTabNav,
+ MatTabLink,
+ MatTabNavPanel,
+ MatTable,
+ MatColumnDef,
+ MatHeaderCellDef,
+ MatHeaderCell,
+ MatCellDef,
+ MatCell,
+ MatHeaderRowDef,
+ MatHeaderRow,
+ MatRowDef,
+ MatRow,
+ MatNoDataRow,
+ MatSort,
+ MatSortHeader,
+ MatPaginator,
+ MatIcon,
+ MatIconButton,
+ MatMenu,
+ MatMenuItem,
+ MatMenuTrigger,
+ CurrencyPipe,
FormatNumberPipe
]
})
@@ -59,6 +105,8 @@ export class DdsAccountViewComponent implements OnInit {
private route = inject(ActivatedRoute);
private router = inject(Router);
private organizationService = inject(OrganizationService);
+ private destroyRef = inject(DestroyRef);
+ private dialog = inject(MatDialog);
isLoading = true;
account: any = null;
@@ -79,6 +127,40 @@ export class DdsAccountViewComponent implements OnInit {
CLOSED_NIL_BALANCE: 'Closed with nil balance'
};
+ /** Transactions tab table, laid out as the savings account's: oldest first, 50 to a page, sortable. */
+ readonly txnColumns: string[] = [
+ 'row',
+ 'id',
+ 'date',
+ 'transactionType',
+ 'debit',
+ 'credit',
+ 'balance',
+ 'actions'
+ ];
+
+ readonly txnDataSource = new MatTableDataSource([]);
+
+ /** The paginator and sort only exist while the Transactions tab is open, so they are re-attached each time. */
+ @ViewChild(MatPaginator) set txnPaginator(paginator: MatPaginator | undefined) {
+ this.txnDataSource.paginator = paginator ?? null;
+ }
+
+ @ViewChild(MatSort) set txnSort(sort: MatSort | undefined) {
+ this.txnDataSource.sort = sort ?? null;
+ }
+
+ /**
+ * Passbook letterhead - the bank from Institution Profile and the issuing branch from Branch Profile,
+ * as the savings passbook takes them. Falls back to a generic label when the profile was never filled in.
+ */
+ bankName = 'Bank';
+ bankSubtitle = 'Account Statement';
+ branchName = '';
+ branchAddress = '';
+ ifscCode = '';
+ micrCode = '';
+
/** The passbook reads oldest first, as it is written up. */
get passbookEntries(): any[] {
return [...this.shownTransactions].reverse();
@@ -92,37 +174,140 @@ export class DdsAccountViewComponent implements OnInit {
/**
* Prints the passbook alone. It is copied into a window of its own with only its own styles, so the
- * page's menus, banner and tabs do not come out on the paper.
+ * page's menus, banner and tabs do not come out on the paper - the same way the savings passbook prints.
*/
printPassbook(): void {
const passbook = document.getElementById('dds-passbook');
- const popup = window.open('', '_blank', 'width=900,height=700');
- if (!passbook || !popup) {
+ if (!passbook) {
return;
}
- popup.document.write(`${this.account?.accountNo || 'DDS'} Passbook ${passbook.innerHTML}`);
+ const clone = passbook.cloneNode(true) as HTMLElement;
+ clone.querySelectorAll('.no-print').forEach((el: Element) => el.remove());
+ // The icon font is not available in the popup: the bank logo becomes an inline SVG, other icons go.
+ clone.querySelectorAll('mat-icon, .mat-icon').forEach((el: Element) => {
+ if (el.closest('.bank-logo-circle')) {
+ el.outerHTML = DdsAccountViewComponent.BANK_LOGO_SVG;
+ } else {
+ el.remove();
+ }
+ });
+ const popup = window.open('', '_blank', 'width=1100,height=800');
+ if (!popup) {
+ return;
+ }
+ popup.document.write(`
+ ${this.account?.accountNo || 'DDS'} Passbook
+
+ ${clone.outerHTML}`);
popup.document.close();
popup.focus();
popup.print();
}
+ /** Material "account_balance" glyph, inlined so the print window needs no icon font. */
+ private static readonly BANK_LOGO_SVG = `
+
+
+ `;
+
+ /** Mirrors the passbook styles in the component's SCSS - scoped styles do not reach the popup. */
+ private static readonly PRINT_STYLES = `
+ @page { size: A4 portrait; margin: 10mm; }
+ * { margin: 0; padding: 0; box-sizing: border-box; -webkit-print-color-adjust: exact; print-color-adjust: exact; }
+ body { font-family: 'Segoe UI', Arial, sans-serif; color: #333; background: #fff; }
+ .statement-wrapper { background: #fff; border: 1px solid #d0d7e3; border-radius: 6px; overflow: hidden; }
+ .bank-header { background: #1a3a6b; padding: 14px 20px; display: flex; justify-content: space-between;
+ align-items: center; border-bottom: 4px solid #c8a951; }
+ .bank-logo-area { display: flex; align-items: center; gap: 12px; }
+ .bank-logo-circle { width: 44px; height: 44px; border-radius: 50%; background: #c8a951; display: flex;
+ align-items: center; justify-content: center; }
+ .bank-logo-svg { fill: #1a3a6b; display: block; }
+ .bank-title { color: #fff; font-size: 16px; font-weight: 700; letter-spacing: 0.3px; }
+ .bank-subtitle { color: #c8a951; font-size: 10px; font-weight: 500; letter-spacing: 1.5px;
+ text-transform: uppercase; margin-top: 2px; }
+ .statement-label { text-align: right; }
+ .stmt-label-text { color: #c8a951; font-size: 12px; font-weight: 700; letter-spacing: 2px; text-transform: uppercase; }
+ .stmt-period { color: rgba(255, 255, 255, 0.75); font-size: 10px; margin-top: 3px; }
+ .branch-line { background: #e8edf5; border-bottom: 1px solid #d0d7e3; padding: 5px 20px; display: flex;
+ flex-wrap: wrap; gap: 4px 16px; font-size: 9.5px; color: #4a5a7a; }
+ .account-details-band { background: #f0f3f9; border-bottom: 1px solid #d0d7e3; padding: 12px 20px; }
+ .account-details-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px 24px; }
+ .detail-label { font-size: 8.5px; font-weight: 700; color: #7a8aaa; text-transform: uppercase;
+ letter-spacing: 0.8px; margin-bottom: 2px; }
+ .detail-value { font-size: 12px; color: #222; }
+ .detail-value.bold { font-weight: 600; }
+ .detail-value.mono { font-family: 'Courier New', monospace; letter-spacing: 1px; }
+ .detail-value.status-badge { display: inline-block; padding: 1px 8px; border-radius: 10px; font-size: 10px;
+ font-weight: 600; background: #eee; color: #555; }
+ .detail-value.status-badge.active { background: #d4edda; color: #155724; }
+ .balance-summary-strip { display: flex; align-items: stretch; background: #1a3a6b; }
+ .balance-box { flex: 1; padding: 10px 18px; text-align: center; }
+ .balance-box-label { font-size: 9px; font-weight: 600; color: rgba(255, 255, 255, 0.65);
+ text-transform: uppercase; letter-spacing: 1px; margin-bottom: 3px; }
+ .balance-box-value { font-size: 15px; font-weight: 700; color: #c8a951; }
+ .balance-divider { width: 1px; background: rgba(255, 255, 255, 0.15); margin: 6px 0; }
+ .statement-table { width: 100%; border-collapse: collapse; font-size: 11px; }
+ .statement-table thead { display: table-header-group; }
+ .statement-table tfoot { display: table-row-group; }
+ .statement-table tr { page-break-inside: avoid; break-inside: avoid; }
+ .statement-table thead tr { background: #e8edf5; border-top: 1px solid #d0d7e3; border-bottom: 2px solid #1a3a6b; }
+ .statement-table thead th { padding: 8px 9px; font-size: 10px; font-weight: 700; color: #1a3a6b;
+ text-transform: uppercase; letter-spacing: 0.4px; white-space: nowrap; text-align: left; }
+ .statement-table tbody tr { border-bottom: 1px solid #e8ecf3; }
+ .statement-table tbody tr:nth-child(even) { background: #f4f7fb; }
+ .statement-table tbody td { padding: 6px 9px; vertical-align: middle; color: #333; }
+ .statement-table tfoot .total-row { background: #e8edf5; border-top: 2px solid #1a3a6b; }
+ .statement-table tfoot .total-row td { padding: 8px 9px; }
+ .col-sr { width: 34px; }
+ .col-date { width: 100px; white-space: nowrap; }
+ .col-desc { min-width: 150px; }
+ .col-ref { width: 90px; }
+ .col-amount { width: 100px; }
+ .col-balance { width: 110px; }
+ .debit-amount { color: #c0392b; font-weight: 500; }
+ .credit-amount { color: #1a7a3f; font-weight: 500; }
+ .balance-amount { color: #1a3a6b; font-weight: 600; font-family: 'Courier New', monospace; }
+ .total-label { font-size: 11px; font-weight: 700; color: #1a3a6b; text-transform: uppercase; letter-spacing: 0.5px; }
+ .closing-balance { font-size: 13px; font-weight: 700; color: #1a3a6b; font-family: 'Courier New', monospace; }
+ .txn-desc { font-weight: 500; }
+ .txn-note { display: block; font-size: 9.5px; color: #777; }
+ .text-right { text-align: right; }
+ .text-center { text-align: center; }
+ .text-muted { color: #999; }
+ .mono { font-family: 'Courier New', monospace; font-size: 10px; }
+ .no-data-block { padding: 40px; text-align: center; color: #aaa; font-size: 13px; }
+ .statement-footer { background: #f0f3f9; border-top: 1px solid #d0d7e3; padding: 10px 20px; text-align: center; }
+ .footer-note { font-size: 9px; color: #888; margin-bottom: 3px; font-style: italic; }
+ .footer-meta { font-size: 9px; color: #aaa; letter-spacing: 0.3px; }
+ `;
+
fromDateControl = new FormControl('');
toDateControl = new FormControl('');
readonly today = new Date().toISOString().split('T')[0];
ngOnInit(): void {
+ this.txnDataSource.sortingDataAccessor = (txn: any, column: string) => {
+ switch (column) {
+ case 'date':
+ return `${txn.transactionDate || ''}#${String(txn.id).padStart(12, '0')}`;
+ case 'transactionType':
+ return this.label(txn.transactionType);
+ case 'voucher':
+ return txn.voucherNo || '';
+ case 'debit':
+ case 'credit':
+ return Number(txn.amount || 0);
+ case 'balance':
+ return Number(txn.balanceAfterTransaction || 0);
+ default:
+ return Number(txn.id || 0);
+ }
+ };
+ merge(this.fromDateControl.valueChanges, this.toDateControl.valueChanges)
+ .pipe(takeUntilDestroyed(this.destroyRef))
+ .subscribe(() => this.refreshTransactionTable());
+
const partyId = this.route.snapshot.paramMap.get('partyId');
if (!partyId) {
this.isLoading = false;
@@ -144,10 +329,39 @@ export class DdsAccountViewComponent implements OnInit {
(a: any, b: any) =>
String(b.transactionDate).localeCompare(String(a.transactionDate)) || Number(b.id) - Number(a.id)
);
+ this.refreshTransactionTable();
+ this.loadLetterhead(account);
this.isLoading = false;
});
}
+ /** The Transactions tab reads oldest first, as the savings account's does, within the chosen dates. */
+ private refreshTransactionTable(): void {
+ this.txnDataSource.data = this.passbookEntries;
+ }
+
+ /** Institution Profile for the bank; the account's own office's Branch Profile, else the first on record. */
+ private loadLetterhead(account: any): void {
+ this.branchName = account?.officeName || '';
+ forkJoin({
+ institution: this.organizationService.getInstitutionProfile().pipe(catchError(() => of(null))),
+ branches: this.organizationService.getBranchProfiles().pipe(catchError(() => of([])))
+ }).subscribe(({ institution, branches }: any) => {
+ if (institution) {
+ this.bankName = institution.passbookHeaderName || institution.bankName || institution.legalName || 'Bank';
+ this.bankSubtitle = institution.licenseNumber ? `Lic. No. ${institution.licenseNumber}` : 'Account Statement';
+ }
+ const list: any[] = Array.isArray(branches) ? branches : [];
+ const branch = list.find((b: any) => account?.officeId && b.officeId === account.officeId) || list[0];
+ if (branch) {
+ this.branchName = branch.officeName || this.branchName;
+ this.branchAddress = branch.address || '';
+ this.ifscCode = branch.ifscCode || '';
+ this.micrCode = branch.micrCode || '';
+ }
+ });
+ }
+
/** The transactions in the chosen range - all of them when no range is set. */
get shownTransactions(): any[] {
const from = this.fromDateControl.value;
@@ -164,6 +378,11 @@ export class DdsAccountViewComponent implements OnInit {
});
}
+ /** Whether the account has any transactions at all, whatever the date filter says. */
+ get hasTransactions(): boolean {
+ return this.transactions.length > 0;
+ }
+
get totalCredits(): number {
return this.sumOf('CREDIT');
}
@@ -209,6 +428,11 @@ export class DdsAccountViewComponent implements OnInit {
return this.transactions.length > 0 ? this.transactions[0].transactionDate : null;
}
+ /** Row tint, as the savings transactions table has it - money moved by transfer shows in violet. */
+ txnRowClass(txn: any): string {
+ return txn.transactionType === 'TRANSFER' || txn.transactionType === 'WITHDRAW_DEPOSITE' ? 'transfer' : '';
+ }
+
/** The rows on screen, as a spreadsheet the branch can keep or send on. */
exportCsv(): void {
const header = [
@@ -268,6 +492,35 @@ export class DdsAccountViewComponent implements OnInit {
this.toDateControl.setValue('');
}
+ /**
+ * A line in full - what it was, when, the payment details, voucher, reference, journal, branch and
+ * who keyed it - as a savings account's View Transaction shows one. From the Receipt menu item it
+ * prints straight away.
+ */
+ viewTransaction(txn: any, printReceipt = false): void {
+ this.dialog.open(DdsTransactionViewDialogComponent, {
+ width: '640px',
+ maxWidth: '95vw',
+ data: {
+ transaction: txn,
+ account: this.account,
+ typeLabel: this.label(txn.transactionType),
+ printOnOpen: printReceipt
+ }
+ });
+ }
+
+ viewJournalEntries(txn: any): void {
+ if (txn.journalTransactionId) {
+ this.router.navigate([
+ '/',
+ 'journal-entry',
+ 'view',
+ txn.journalTransactionId
+ ]);
+ }
+ }
+
label(type: string): string {
return TRANSACTION_LABELS[type] || String(type || '').replace(/_/g, ' ');
}
diff --git a/src/app/deposits/dds-accounts/dds-account-view/dds-transaction-view-dialog/dds-transaction-view-dialog.component.html b/src/app/deposits/dds-accounts/dds-account-view/dds-transaction-view-dialog/dds-transaction-view-dialog.component.html
new file mode 100644
index 000000000..b8273db1c
--- /dev/null
+++ b/src/app/deposits/dds-accounts/dds-account-view/dds-transaction-view-dialog/dds-transaction-view-dialog.component.html
@@ -0,0 +1,144 @@
+
+
+
+
+
+
+
+
+
DDS Transaction Receipt
+
+ {{ account.partyName }} · A/c {{ account.accountNo }} · {{ txn.officeName || account.officeName || '' }}
+
+
+ {{ isCredit ? 'Credit' : 'Debit' }} {{ txn.amount | currency: currency : 'symbol-narrow' : '1.2-2' }}
+
+
+
+
+ General
+
+
+ Id
+ {{ txn.id }}
+
+
+ Reference No.
+ {{ txn.refNo || '—' }}
+
+
+ Transaction Type
+ {{ data.typeLabel }} ({{ txn.direction }})
+
+
+ Transaction Date
+ {{ txn.transactionDate | date: 'dd MMM yyyy' }}
+
+
+ Submitted On
+ {{ txn.submittedOnDate ? (txn.submittedOnDate | date: 'dd MMM yyyy') : '—' }}
+
+
+ Currency
+ {{ currency }}
+
+
+ Amount
+ {{ txn.amount | currency: currency : 'symbol-narrow' : '1.2-2' }}
+
+
+ Running Balance
+
+ {{ txn.balanceAfterTransaction || 0 | currency: currency : 'symbol-narrow' : '1.2-2' }}
+
+
+
+ Note
+ {{ txn.description || '—' }}
+
+
+
+
+
+ Payment Details
+
+
+ Mode
+ {{ txn.transactionMode || '—' }}
+
+
+ Account Type / No.
+ {{ txn.accountType || '—' }}{{ txn.accountNo ? ' · ' + txn.accountNo : '' }}
+
+
+ Cheque No.
+ {{ txn.chequeNo || '—' }}
+
+
+ Token No.
+ {{ txn.tokenNo || '—' }}
+
+
+
+
+
+ Accounting & Audit
+
+
+ Voucher No.
+ {{ txn.voucherNo || '—' }}
+
+
+ Journal Entry
+ {{ txn.journalTransactionId || 'None - nothing moved on the books' }}
+
+
+ Branch
+ {{ txn.officeName || account.officeName || '—' }}
+
+
+ Agent
+ {{ txn.agentCode }}{{ txn.agentName ? ' · ' + txn.agentName : '' }}
+
+
+ Created By
+ {{ txn.createdByUsername || '—' }}
+
+
+ Created On
+ {{ txn.createdDate ? (txn.createdDate | date: 'dd MMM yyyy, hh:mm a') : '—' }}
+
+
+
+
+
+
+ Close
+
+
+ View Journal Entries
+
+
+ Print Receipt
+
+
+
diff --git a/src/app/deposits/dds-accounts/dds-account-view/dds-transaction-view-dialog/dds-transaction-view-dialog.component.scss b/src/app/deposits/dds-accounts/dds-account-view/dds-transaction-view-dialog/dds-transaction-view-dialog.component.scss
new file mode 100644
index 000000000..4dbddccc1
--- /dev/null
+++ b/src/app/deposits/dds-accounts/dds-account-view/dds-transaction-view-dialog/dds-transaction-view-dialog.component.scss
@@ -0,0 +1,167 @@
+/**
+ * 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/.
+ */
+
+$primary: #1353a0;
+$credit: #15803d;
+$debit: #b91c1c;
+$border: #e2e8f0;
+$text: #0f172a;
+$text-mid: #475569;
+
+:host {
+ display: block;
+}
+
+.dtv {
+ display: flex;
+ flex-direction: column;
+ max-height: 88vh;
+ color: $text;
+ font-size: 13px;
+}
+
+.dtv-head {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ padding: 16px 20px;
+ background: linear-gradient(135deg, #1e40af, #2563eb);
+ color: #fff;
+
+ &--debit {
+ background: linear-gradient(135deg, #7f1d1d, #b91c1c);
+ }
+
+ &__ic {
+ display: grid;
+ place-items: center;
+ flex: none;
+ width: 38px;
+ height: 38px;
+ border-radius: 10px;
+ background: rgb(255 255 255 / 18%);
+ }
+
+ &__text {
+ flex: 1;
+ min-width: 0;
+
+ h2 {
+ margin: 0;
+ font-size: 16px;
+ font-weight: 700;
+ }
+
+ p {
+ margin: 2px 0 0;
+ font-size: 12px;
+ opacity: 0.85;
+ }
+ }
+
+ &__amount {
+ display: flex;
+ flex-direction: column;
+ align-items: flex-end;
+
+ span {
+ font-size: 11px;
+ letter-spacing: 0.06em;
+ text-transform: uppercase;
+ opacity: 0.85;
+ }
+
+ strong {
+ font-size: 18px;
+ }
+ }
+}
+
+.dtv-close {
+ border: 0;
+ background: transparent;
+ color: #fff;
+ font-size: 16px;
+ cursor: pointer;
+}
+
+.dtv-body {
+ flex: 1;
+ min-height: 0;
+ padding: 14px 20px;
+ overflow-y: auto;
+}
+
+.dtv-print-only {
+ display: none;
+}
+
+.dtv-section {
+ & + & {
+ margin-top: 14px;
+ }
+
+ h3 {
+ margin: 0 0 6px;
+ font-size: 11px;
+ font-weight: 700;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ color: $primary;
+ }
+}
+
+.dtv-kv {
+ width: 100%;
+ border: 1px solid $border;
+ border-radius: 8px;
+ border-collapse: separate;
+ border-spacing: 0;
+ overflow: hidden;
+
+ td {
+ padding: 7px 12px;
+ border-bottom: 1px solid #eef2f6;
+ vertical-align: top;
+ }
+
+ tr:last-child td {
+ border-bottom: 0;
+ }
+
+ .k {
+ width: 38%;
+ background: #f8fafc;
+ color: $text-mid;
+ font-weight: 600;
+ }
+
+ .mono {
+ font-family: ui-monospace, 'Cascadia Mono', Consolas, monospace;
+ }
+
+ .strong {
+ font-weight: 700;
+ }
+}
+
+.dtv-actions {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ padding: 12px 20px;
+ border-top: 1px solid $border;
+
+ &__spacer {
+ flex: 1;
+ }
+
+ fa-icon {
+ margin-right: 6px;
+ }
+}
diff --git a/src/app/deposits/dds-accounts/dds-account-view/dds-transaction-view-dialog/dds-transaction-view-dialog.component.ts b/src/app/deposits/dds-accounts/dds-account-view/dds-transaction-view-dialog/dds-transaction-view-dialog.component.ts
new file mode 100644
index 000000000..0b7ad0e12
--- /dev/null
+++ b/src/app/deposits/dds-accounts/dds-account-view/dds-transaction-view-dialog/dds-transaction-view-dialog.component.ts
@@ -0,0 +1,109 @@
+/**
+ * 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/.
+ */
+
+import { Component, inject } from '@angular/core';
+import { CurrencyPipe, DatePipe } from '@angular/common';
+import { Router } from '@angular/router';
+import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
+import { MatButton } from '@angular/material/button';
+import { FaIconComponent } from '@fortawesome/angular-fontawesome';
+import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
+
+/** What the account view hands the dialog: the line, its account, and how to name its type. */
+export interface DdsTransactionViewData {
+ transaction: any;
+ account: any;
+ typeLabel: string;
+ /** Opened from the Receipt menu item: print the receipt straight away. */
+ printOnOpen?: boolean;
+}
+
+/**
+ * One DDS passbook line in full - everything the line was saved with - the way a savings account's
+ * View Transaction shows one: what it was, when, how much, and who keyed it, with the voucher, its
+ * own reference and the journal it was booked under. Prints as a receipt.
+ */
+@Component({
+ selector: 'mifosx-dds-transaction-view-dialog',
+ templateUrl: './dds-transaction-view-dialog.component.html',
+ styleUrls: ['./dds-transaction-view-dialog.component.scss'],
+ imports: [
+ ...STANDALONE_SHARED_IMPORTS,
+ CurrencyPipe,
+ DatePipe,
+ MatButton,
+ FaIconComponent
+ ]
+})
+export class DdsTransactionViewDialogComponent {
+ private dialogRef = inject(MatDialogRef);
+ private router = inject(Router);
+ data = inject(MAT_DIALOG_DATA);
+
+ get txn(): any {
+ return this.data.transaction || {};
+ }
+
+ get account(): any {
+ return this.data.account || {};
+ }
+
+ get isCredit(): boolean {
+ return this.txn.direction === 'CREDIT';
+ }
+
+ get currency(): string {
+ return this.account.currencyCode || 'INR';
+ }
+
+ constructor() {
+ if (this.data.printOnOpen) {
+ // After the view has drawn, so the receipt has something in it to print.
+ setTimeout(() => this.printReceipt(), 300);
+ }
+ }
+
+ viewJournalEntries(): void {
+ if (!this.txn.journalTransactionId) {
+ return;
+ }
+ this.dialogRef.close();
+ this.router.navigate([
+ '/',
+ 'journal-entry',
+ 'view',
+ this.txn.journalTransactionId
+ ]);
+ }
+
+ /** The receipt block alone, in a window of its own - as the savings receipt prints. */
+ printReceipt(): void {
+ const receipt = document.getElementById('dds-txn-receipt');
+ const popup = window.open('', '_blank', 'width=720,height=800');
+ if (!receipt || !popup) {
+ return;
+ }
+ popup.document.write(`${this.txn.refNo || 'DDS'} Receipt ${receipt.innerHTML}
+ Customer Authorised Signatory
+ `);
+ popup.document.close();
+ popup.focus();
+ popup.print();
+ }
+
+ close(): void {
+ this.dialogRef.close();
+ }
+}
diff --git a/src/app/deposits/party-master/create-party/create-party.component.html b/src/app/deposits/party-master/create-party/create-party.component.html
index 4c90eb218..a33a05b3f 100644
--- a/src/app/deposits/party-master/create-party/create-party.component.html
+++ b/src/app/deposits/party-master/create-party/create-party.component.html
@@ -24,6 +24,13 @@