printing modification are done
This commit is contained in:
+274
-26
@@ -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(`
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Current Account Passbook - ${this.accountNo}</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { font-family: 'Segoe UI', Arial, sans-serif; padding: 20px; color: #333; }
|
||||
.passbook-header { text-align: center; margin-bottom: 20px; border-bottom: 2px solid #1976d2; padding-bottom: 12px; }
|
||||
.passbook-header h2 { color: #1976d2; font-size: 18px; margin-bottom: 4px; }
|
||||
.passbook-header h3 { font-size: 14px; font-weight: 400; color: #666; }
|
||||
.passbook-info-grid { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 8px 16px; margin-bottom: 16px; padding: 10px; background: #f5f5f5; border-radius: 4px; }
|
||||
.passbook-info-item { font-size: 12px; }
|
||||
.passbook-info-item .label { font-weight: 600; color: #666; text-transform: uppercase; font-size: 10px; }
|
||||
.passbook-info-item .value { color: #333; }
|
||||
.passbook-balance-row { display: flex; justify-content: space-between; margin-bottom: 16px; padding: 8px 10px; background: #e3f2fd; border-radius: 4px; font-size: 13px; }
|
||||
.passbook-balance-row .balance-item b { color: #1976d2; }
|
||||
table { width: 100%; border-collapse: collapse; font-size: 11px; }
|
||||
th { background: #1976d2; color: #fff; padding: 6px 8px; text-align: left; font-weight: 600; }
|
||||
td { padding: 5px 8px; border-bottom: 1px solid #e0e0e0; }
|
||||
tr:nth-child(even) { background: #fafafa; }
|
||||
.text-right { text-align: right; }
|
||||
.passbook-footer { margin-top: 16px; font-size: 10px; color: #999; text-align: center; border-top: 1px solid #e0e0e0; padding-top: 8px; }
|
||||
.reversed { text-decoration: line-through; color: #999; }
|
||||
</style>
|
||||
<style>${PassbookTabComponent.PRINT_STYLES}</style>
|
||||
</head>
|
||||
<body>
|
||||
${content}
|
||||
<div class="statement-print-page">${clone.outerHTML}</div>
|
||||
<script>
|
||||
window.onafterprint = function () { window.close(); };
|
||||
window.addEventListener('load', function () { window.focus(); window.print(); });
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
`);
|
||||
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 = `
|
||||
<svg class="bank-logo-svg" viewBox="0 0 24 24" width="26" height="26" aria-hidden="true">
|
||||
<path d="M4 10h3v7H4zM10.5 10h3v7h-3zM2 19h20v3H2zM17 10h3v7h-3zM12 1L2 6v2h20V6z" />
|
||||
</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();
|
||||
|
||||
@@ -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(`
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Savings Account Passbook - ${this.accountNo}</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { font-family: 'Segoe UI', Arial, sans-serif; padding: 20px; color: #333; }
|
||||
.passbook-header { text-align: center; margin-bottom: 20px; border-bottom: 2px solid #1976d2; padding-bottom: 12px; }
|
||||
.passbook-header h2 { color: #1976d2; font-size: 18px; margin-bottom: 4px; }
|
||||
.passbook-header h3 { font-size: 14px; font-weight: 400; color: #666; }
|
||||
.passbook-info-grid { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 8px 16px; margin-bottom: 16px; padding: 10px; background: #f5f5f5; border-radius: 4px; }
|
||||
.passbook-info-item { font-size: 12px; }
|
||||
.passbook-info-item .label { font-weight: 600; color: #666; text-transform: uppercase; font-size: 10px; }
|
||||
.passbook-info-item .value { color: #333; }
|
||||
.passbook-balance-row { display: flex; justify-content: space-between; margin-bottom: 16px; padding: 8px 10px; background: #e3f2fd; border-radius: 4px; font-size: 13px; }
|
||||
.passbook-balance-row .balance-item b { color: #1976d2; }
|
||||
table { width: 100%; border-collapse: collapse; font-size: 11px; }
|
||||
th { background: #1976d2; color: #fff; padding: 6px 8px; text-align: left; font-weight: 600; }
|
||||
td { padding: 5px 8px; border-bottom: 1px solid #e0e0e0; }
|
||||
tr:nth-child(even) { background: #fafafa; }
|
||||
.text-right { text-align: right; }
|
||||
.passbook-footer { margin-top: 16px; font-size: 10px; color: #999; text-align: center; border-top: 1px solid #e0e0e0; padding-top: 8px; }
|
||||
.reversed { text-decoration: line-through; color: #999; }
|
||||
</style>
|
||||
<style>${PassbookTabComponent.PRINT_STYLES}</style>
|
||||
</head>
|
||||
<body>
|
||||
${content}
|
||||
<div class="statement-print-page">${clone.outerHTML}</div>
|
||||
<script>
|
||||
window.onafterprint = function () { window.close(); };
|
||||
window.addEventListener('load', function () { window.focus(); window.print(); });
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
`);
|
||||
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 = `
|
||||
<svg class="bank-logo-svg" viewBox="0 0 24 24" width="26" height="26" aria-hidden="true">
|
||||
<path d="M4 10h3v7H4zM10.5 10h3v7h-3zM2 19h20v3H2zM17 10h3v7h-3zM12 1L2 6v2h20V6z" />
|
||||
</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) => {
|
||||
|
||||
@@ -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(`
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Share Account Passbook - ${this.accountNo}</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { font-family: 'Segoe UI', Arial, sans-serif; padding: 20px; color: #333; }
|
||||
.passbook-header { text-align: center; margin-bottom: 20px; border-bottom: 2px solid #1a3a6b; padding-bottom: 12px; }
|
||||
table { width: 100%; border-collapse: collapse; font-size: 11px; }
|
||||
th { background: #1a3a6b; color: #fff; padding: 6px 8px; text-align: left; font-weight: 600; }
|
||||
td { padding: 5px 8px; border-bottom: 1px solid #e0e0e0; }
|
||||
tr:nth-child(even) { background: #fafafa; }
|
||||
.text-right { text-align: right; }
|
||||
</style>
|
||||
<style>${SharesPassbookTabComponent.PRINT_STYLES}</style>
|
||||
</head>
|
||||
<body>
|
||||
${content}
|
||||
<div class="statement-print-page">${clone.outerHTML}</div>
|
||||
<script>
|
||||
window.onafterprint = function () { window.close(); };
|
||||
window.addEventListener('load', function () { window.focus(); window.print(); });
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
`);
|
||||
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 = `
|
||||
<svg class="bank-logo-svg" viewBox="0 0 24 24" width="26" height="26" aria-hidden="true">
|
||||
<path d="M4 10h3v7H4zM10.5 10h3v7h-3zM2 19h20v3H2zM17 10h3v7h-3zM12 1L2 6v2h20V6z" />
|
||||
</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) => {
|
||||
|
||||
Reference in New Issue
Block a user