diff --git a/src/app/current-accounts/current-account-view/passbook-tab/passbook-tab.component.ts b/src/app/current-accounts/current-account-view/passbook-tab/passbook-tab.component.ts index 71517bdfd..697d53a5c 100644 --- a/src/app/current-accounts/current-account-view/passbook-tab/passbook-tab.component.ts +++ b/src/app/current-accounts/current-account-view/passbook-tab/passbook-tab.component.ts @@ -152,46 +152,294 @@ export class PassbookTabComponent implements OnInit, OnDestroy { } printPassbook(): void { - const content = this.passbookContent?.nativeElement?.innerHTML; - if (!content) return; + const source = this.passbookContent?.nativeElement as HTMLElement; + if (!source) return; + + /** Work on a copy so the on-screen statement is untouched */ + const clone = source.cloneNode(true) as HTMLElement; + clone.querySelectorAll('.no-print').forEach((el: Element) => el.remove()); + + /** Material icon fonts are not available in the popup, so swap them for inline SVG */ + clone.querySelectorAll('mat-icon, .mat-icon').forEach((el: Element) => { + const insideLogo = !!el.closest('.bank-logo-circle'); + if (insideLogo) { + el.outerHTML = PassbookTabComponent.BANK_LOGO_SVG; + } else { + el.remove(); + } + }); + + const printWindow = window.open('', 'Passbook', 'height=800,width=1100'); + if (!printWindow) return; - const printWindow = window.open('', 'Passbook', 'height=700,width=900'); printWindow.document.write(` + Current Account Passbook - ${this.accountNo} - + - ${content} +
${clone.outerHTML}
+ `); printWindow.document.close(); printWindow.focus(); - printWindow.print(); - printWindow.close(); } + /** Material "account_balance" glyph, inlined so the print window needs no icon font */ + private static readonly BANK_LOGO_SVG = ` + `; + + /** + * Mirrors passbook-tab.component.scss so the printed statement looks like the + * on-screen one. Kept in sync manually - component styles are scoped and are + * not reachable from the popup window. + */ + 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-print-page { padding: 0; } + + .statement-wrapper { + background: #fff; + border: 1px solid #d0d7e3; + border-radius: 6px; + overflow: hidden; + } + + /* Bank header */ + .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-name-block .bank-title { + color: #fff; + font-size: 16px; + font-weight: 700; + letter-spacing: 0.3px; + } + + .bank-name-block .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; } + + .statement-label .stmt-label-text { + color: #c8a951; + font-size: 12px; + font-weight: 700; + letter-spacing: 2px; + text-transform: uppercase; + } + + .statement-label .stmt-period { + color: rgba(255, 255, 255, 0.75); + font-size: 10px; + margin-top: 3px; + } + + /* Account details band */ + .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-block .detail-label { + font-size: 8.5px; + font-weight: 700; + color: #7a8aaa; + text-transform: uppercase; + letter-spacing: 0.8px; + margin-bottom: 2px; + } + + .detail-block .detail-value { font-size: 12px; color: #222; } + .detail-block .detail-value.bold { font-weight: 600; } + .detail-block .detail-value.mono { font-family: 'Courier New', monospace; letter-spacing: 1px; } + + .detail-block .detail-value.status-badge { + display: inline-block; + padding: 1px 8px; + border-radius: 10px; + font-size: 10px; + font-weight: 600; + background: #eee; + color: #555; + } + + .detail-block .detail-value.status-badge.active { background: #d4edda; color: #155724; } + + /* Balance summary strip */ + .balance-summary-strip { + display: flex; + align-items: stretch; + background: #1a3a6b; + } + + .balance-box { flex: 1; padding: 10px 18px; text-align: center; } + + .balance-box .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 .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; } + + /* Transactions table */ + .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 tr.reversed-row { opacity: 0.55; text-decoration: line-through; } + .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; } + + /* Column widths */ + .col-sr { width: 34px; } + .col-date { width: 100px; white-space: nowrap; } + .col-desc { min-width: 150px; } + .col-ref { width: 74px; } + .col-amount { width: 100px; } + .col-balance { width: 110px; } + + /* Amount colors */ + .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; + } + + /* Badges & misc */ + .txn-desc { font-weight: 500; } + + .reversed-badge { + display: inline-block; + margin-left: 6px; + font-size: 8px; + font-weight: 700; + color: #fff; + background: #e74c3c; + padding: 1px 5px; + border-radius: 3px; + letter-spacing: 0.5px; + } + + .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 */ + .no-data-block { padding: 40px; text-align: center; color: #aaa; font-size: 13px; } + + /* Footer */ + .statement-footer { + background: #f0f3f9; + border-top: 1px solid #d0d7e3; + padding: 10px 20px; + text-align: center; + } + + .statement-footer .footer-note { + font-size: 9px; + color: #888; + margin-bottom: 3px; + font-style: italic; + } + + .statement-footer .footer-meta { font-size: 9px; color: #aaa; letter-spacing: 0.3px; } + `; + ngOnDestroy(): void { this.destroy$.next(); this.destroy$.complete(); diff --git a/src/app/savings/savings-account-view/passbook-tab/passbook-tab.component.ts b/src/app/savings/savings-account-view/passbook-tab/passbook-tab.component.ts index 6be1eca86..be2127a28 100644 --- a/src/app/savings/savings-account-view/passbook-tab/passbook-tab.component.ts +++ b/src/app/savings/savings-account-view/passbook-tab/passbook-tab.component.ts @@ -142,46 +142,294 @@ export class PassbookTabComponent implements OnInit, OnDestroy { } printPassbook(): void { - const content = this.passbookContent?.nativeElement?.innerHTML; - if (!content) return; + const source = this.passbookContent?.nativeElement as HTMLElement; + if (!source) return; + + /** Work on a copy so the on-screen statement is untouched */ + const clone = source.cloneNode(true) as HTMLElement; + clone.querySelectorAll('.no-print').forEach((el: Element) => el.remove()); + + /** Material icon fonts are not available in the popup, so swap them for inline SVG */ + clone.querySelectorAll('mat-icon, .mat-icon').forEach((el: Element) => { + const insideLogo = !!el.closest('.bank-logo-circle'); + if (insideLogo) { + el.outerHTML = PassbookTabComponent.BANK_LOGO_SVG; + } else { + el.remove(); + } + }); + + const printWindow = window.open('', 'Passbook', 'height=800,width=1100'); + if (!printWindow) return; - const printWindow = window.open('', 'Passbook', 'height=700,width=900'); printWindow.document.write(` + Savings Account Passbook - ${this.accountNo} - + - ${content} +
${clone.outerHTML}
+ `); printWindow.document.close(); printWindow.focus(); - printWindow.print(); - printWindow.close(); } + /** Material "account_balance" glyph, inlined so the print window needs no icon font */ + private static readonly BANK_LOGO_SVG = ` + `; + + /** + * Mirrors passbook-tab.component.scss so the printed statement looks like the + * on-screen one. Kept in sync manually - component styles are scoped and are + * not reachable from the popup window. + */ + 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-print-page { padding: 0; } + + .statement-wrapper { + background: #fff; + border: 1px solid #d0d7e3; + border-radius: 6px; + overflow: hidden; + } + + /* Bank header */ + .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-name-block .bank-title { + color: #fff; + font-size: 16px; + font-weight: 700; + letter-spacing: 0.3px; + } + + .bank-name-block .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; } + + .statement-label .stmt-label-text { + color: #c8a951; + font-size: 12px; + font-weight: 700; + letter-spacing: 2px; + text-transform: uppercase; + } + + .statement-label .stmt-period { + color: rgba(255, 255, 255, 0.75); + font-size: 10px; + margin-top: 3px; + } + + /* Account details band */ + .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-block .detail-label { + font-size: 8.5px; + font-weight: 700; + color: #7a8aaa; + text-transform: uppercase; + letter-spacing: 0.8px; + margin-bottom: 2px; + } + + .detail-block .detail-value { font-size: 12px; color: #222; } + .detail-block .detail-value.bold { font-weight: 600; } + .detail-block .detail-value.mono { font-family: 'Courier New', monospace; letter-spacing: 1px; } + + .detail-block .detail-value.status-badge { + display: inline-block; + padding: 1px 8px; + border-radius: 10px; + font-size: 10px; + font-weight: 600; + background: #eee; + color: #555; + } + + .detail-block .detail-value.status-badge.active { background: #d4edda; color: #155724; } + + /* Balance summary strip */ + .balance-summary-strip { + display: flex; + align-items: stretch; + background: #1a3a6b; + } + + .balance-box { flex: 1; padding: 10px 18px; text-align: center; } + + .balance-box .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 .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; } + + /* Transactions table */ + .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 tr.reversed-row { opacity: 0.55; text-decoration: line-through; } + .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; } + + /* Column widths */ + .col-sr { width: 34px; } + .col-date { width: 100px; white-space: nowrap; } + .col-desc { min-width: 150px; } + .col-ref { width: 74px; } + .col-amount { width: 100px; } + .col-balance { width: 110px; } + + /* Amount colors */ + .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; + } + + /* Badges & misc */ + .txn-desc { font-weight: 500; } + + .reversed-badge { + display: inline-block; + margin-left: 6px; + font-size: 8px; + font-weight: 700; + color: #fff; + background: #e74c3c; + padding: 1px 5px; + border-radius: 3px; + letter-spacing: 0.5px; + } + + .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 */ + .no-data-block { padding: 40px; text-align: center; color: #aaa; font-size: 13px; } + + /* Footer */ + .statement-footer { + background: #f0f3f9; + border-top: 1px solid #d0d7e3; + padding: 10px 20px; + text-align: center; + } + + .statement-footer .footer-note { + font-size: 9px; + color: #888; + margin-bottom: 3px; + font-style: italic; + } + + .statement-footer .footer-meta { font-size: 9px; color: #aaa; letter-spacing: 0.3px; } + `; + private recalculateRunningBalances(transactions: SavingsAccountTransaction[]): SavingsAccountTransaction[] { let balance = 0; return transactions.map((txn) => { diff --git a/src/app/shares/shares-account-view/passbook-tab/passbook-tab.component.ts b/src/app/shares/shares-account-view/passbook-tab/passbook-tab.component.ts index da843b90a..e2aa7545e 100644 --- a/src/app/shares/shares-account-view/passbook-tab/passbook-tab.component.ts +++ b/src/app/shares/shares-account-view/passbook-tab/passbook-tab.component.ts @@ -133,36 +133,284 @@ export class SharesPassbookTabComponent implements OnInit { } printPassbook(): void { - const content = this.passbookContent?.nativeElement?.innerHTML; - if (!content) return; + const source = this.passbookContent?.nativeElement as HTMLElement; + if (!source) return; + + /** Work on a copy so the on-screen passbook is untouched */ + const clone = source.cloneNode(true) as HTMLElement; + clone.querySelectorAll('.no-print').forEach((el: Element) => el.remove()); + + /** Material icon fonts are not available in the popup, so swap them for inline SVG */ + clone.querySelectorAll('mat-icon, .mat-icon').forEach((el: Element) => { + const insideLogo = !!el.closest('.bank-logo-circle'); + if (insideLogo) { + el.outerHTML = SharesPassbookTabComponent.BANK_LOGO_SVG; + } else { + el.remove(); + } + }); + + const printWindow = window.open('', 'SharePassbook', 'height=800,width=1200'); + if (!printWindow) return; - const printWindow = window.open('', 'SharePassbook', 'height=700,width=900'); printWindow.document.write(` + Share Account Passbook - ${this.accountNo} - + - ${content} +
${clone.outerHTML}
+ `); printWindow.document.close(); printWindow.focus(); - printWindow.print(); - printWindow.close(); } + /** Material "account_balance" glyph, inlined so the print window needs no icon font */ + private static readonly BANK_LOGO_SVG = ` + `; + + /** + * Mirrors passbook-tab.component.scss so the printed passbook looks like the + * on-screen one. Kept in sync manually - component styles are scoped and are + * not reachable from the popup window. Landscape, because the share ledger + * carries nine columns including the Resolution No. / Meeting column. + */ + private static readonly PRINT_STYLES = ` + @page { size: A4 landscape; margin: 8mm; } + + * { 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-print-page { padding: 0; } + + .statement-wrapper { + background: #fff; + border: 1px solid #d0d7e3; + border-radius: 6px; + overflow: hidden; + } + + /* Bank header */ + .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-name-block .bank-title { + color: #fff; + font-size: 16px; + font-weight: 700; + letter-spacing: 0.3px; + } + + .bank-name-block .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; } + + .statement-label .stmt-label-text { + color: #c8a951; + font-size: 12px; + font-weight: 700; + letter-spacing: 2px; + text-transform: uppercase; + } + + .statement-label .stmt-period { + color: rgba(255, 255, 255, 0.75); + font-size: 10px; + margin-top: 3px; + } + + /* Account details band */ + .account-details-band { + background: #f0f3f9; + border-bottom: 1px solid #d0d7e3; + padding: 12px 20px; + } + + .account-details-grid { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 10px 24px; + } + + .detail-block .detail-label { + font-size: 8.5px; + font-weight: 700; + color: #7a8aaa; + text-transform: uppercase; + letter-spacing: 0.8px; + margin-bottom: 2px; + } + + .detail-block .detail-value { font-size: 12px; color: #222; } + .detail-block .detail-value.bold { font-weight: 600; } + .detail-block .detail-value.mono { font-family: 'Courier New', monospace; letter-spacing: 1px; } + + .detail-block .detail-value.status-badge { + display: inline-block; + padding: 1px 8px; + border-radius: 10px; + font-size: 10px; + font-weight: 600; + background: #eee; + color: #555; + } + + .detail-block .detail-value.status-badge.active { background: #d4edda; color: #155724; } + + /* Share summary strip */ + .balance-summary-strip { + display: flex; + align-items: stretch; + background: #1a3a6b; + } + + .balance-box { flex: 1; padding: 10px 18px; text-align: center; } + + .balance-box .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 .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; } + + /* Ledger table - no horizontal scroll on paper, the landscape page holds every column */ + .statement-table-scroll { width: 100%; overflow: visible; } + + .statement-table { width: 100%; border-collapse: collapse; font-size: 10.5px; } + + .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: 9.5px; + font-weight: 700; + color: #1a3a6b; + text-transform: uppercase; + letter-spacing: 0.4px; + white-space: nowrap; + } + + .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; } + + /* Column widths */ + .col-sr { width: 32px; } + .col-date { width: 96px; white-space: nowrap; } + .col-desc { width: auto; min-width: 140px; } + .col-ref { width: 70px; white-space: nowrap; } + .col-amount { width: 108px; white-space: nowrap; } + .col-balance { width: 96px; white-space: nowrap; } + .col-resolution { width: 170px; white-space: nowrap; padding-left: 14px; } + + /* Share movement colors */ + .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: 10.5px; + font-weight: 700; + color: #1a3a6b; + text-transform: uppercase; + letter-spacing: 0.5px; + } + + .closing-balance { + font-size: 12.5px; + font-weight: 700; + color: #1a3a6b; + font-family: 'Courier New', monospace; + } + + /* Misc */ + .txn-desc { font-weight: 500; } + .text-right { text-align: right; } + .text-center { text-align: center; } + .text-left { text-align: left; } + .text-muted { color: #999; } + .mono { font-family: 'Courier New', monospace; font-size: 10px; } + + /* No data */ + .no-data-block { padding: 40px; text-align: center; color: #aaa; font-size: 13px; } + + /* Footer */ + .statement-footer { + background: #f0f3f9; + border-top: 1px solid #d0d7e3; + padding: 10px 20px; + text-align: center; + } + + .statement-footer .footer-note { + font-size: 9px; + color: #888; + margin-bottom: 3px; + font-style: italic; + } + + .statement-footer .footer-meta { font-size: 9px; color: #aaa; letter-spacing: 0.3px; } + `; + private recalculateRunningBalances(transactions: any[]): any[] { let balance = 0; return transactions.map((txn) => {