PluginProbe
Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor / 2.0.7
Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor v2.0.7
2.0.13 2.0.12 2.0.11 2.0.10 2.0.9 trunk 2.0.2 2.0.3 2.0.4 2.0.5 2.0.6 2.0.7 2.0.8
blockenberg / blocks / loan-calculator / frontend.js

frontend.js in Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor 2.0.7, at blocks/loan-calculator/frontend.js

190 lines 8.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 (function () {
2 'use strict';
3
4 var CHART_CDN = 'https://cdn.jsdelivr.net/npm/chart.js@4.4.3/dist/chart.umd.min.js';
5
6 function calcLoan(amount, annualRate, years) {
7 if (!amount || !annualRate || !years) return { monthly: 0, totalPayment: 0, totalInterest: 0 };
8 var r = annualRate / 1200;
9 var n = years * 12;
10 var monthly = amount * (r * Math.pow(1 + r, n)) / (Math.pow(1 + r, n) - 1);
11 var totalPayment = monthly * n;
12 var totalInterest = totalPayment - amount;
13 return {
14 monthly: Math.round(monthly * 100) / 100,
15 totalPayment: Math.round(totalPayment * 100) / 100,
16 totalInterest: Math.round(totalInterest * 100) / 100
17 };
18 }
19
20 function fmtMoney(val, currency, pos) {
21 var s = Math.round(val).toLocaleString('en-US');
22 return pos === 'after' ? s + currency : currency + s;
23 }
24
25 function buildAmort(amount, annualRate, years, maxRows) {
26 var r = annualRate / 1200;
27 var n = years * 12;
28 var monthly = amount * (r * Math.pow(1 + r, n)) / (Math.pow(1 + r, n) - 1);
29 var balance = amount;
30 var rows = [];
31 for (var i = 1; i <= n && i <= maxRows; i++) {
32 var interest = balance * r;
33 var principal = monthly - interest;
34 balance = Math.max(0, balance - principal);
35 rows.push({ month: i, payment: monthly, principal: principal, interest: interest, balance: balance });
36 }
37 return rows;
38 }
39
40 document.querySelectorAll('.bkbg-lc-app').forEach(function (app) {
41 var opts = {};
42 try { opts = JSON.parse(app.dataset.opts || '{}'); } catch (e) {}
43
44 var amt = opts.loanAmountDefault || 300000;
45 var rate = opts.interestDefault || 5.5;
46 var yrs = opts.termDefault || 25;
47 var cur = opts.currency || '$';
48 var pos = opts.currencyPos || 'before';
49
50 // Find sliders and bind them
51 var sliderGroups = app.querySelectorAll('.bkbg-lc-slider-group');
52 var amountGroup, interestGroup, termGroup;
53
54 sliderGroups.forEach(function (g) {
55 var type = g.dataset.slider;
56 if (type === 'amount') amountGroup = g;
57 if (type === 'interest') interestGroup = g;
58 if (type === 'term') termGroup = g;
59 });
60
61 function initSlider(group, min, max, step, value, format) {
62 if (!group) return;
63 var input = group.querySelector('input[type="range"]');
64 var valEl = group.querySelector('.bkbg-lc-slider-val');
65 var minEl = group.querySelector('.bkbg-lc-range-min');
66 var maxEl = group.querySelector('.bkbg-lc-range-max');
67 if (input) {
68 input.min = min; input.max = max; input.step = step; input.value = value;
69 input.style.accentColor = opts.accentColor || '#6c3fb5';
70 }
71 if (valEl) valEl.textContent = format(value);
72 if (minEl) minEl.textContent = format(min);
73 if (maxEl) maxEl.textContent = format(max);
74 return input;
75 }
76
77 var amtInput = initSlider(amountGroup, opts.loanAmountMin || 10000, opts.loanAmountMax || 2000000, opts.loanAmountStep || 5000, amt, function (v) { return fmtMoney(v, cur, pos); });
78 var rateInput = initSlider(interestGroup, opts.interestMin || 0.5, opts.interestMax || 20, opts.interestStep || 0.1, rate, function (v) { return v + '%'; });
79 var yrsInput = initSlider(termGroup, opts.termMin || 1, opts.termMax || 30, opts.termStep || 1, yrs, function (v) { return v + ' yr'; });
80
81 // Result elements
82 var monthlyEl = app.querySelector('.bkbg-lc-monthly');
83 var principalEl = app.querySelector('[data-result="principal"] .bkbg-lc-res-val');
84 var interestEl = app.querySelector('[data-result="interest"] .bkbg-lc-res-val');
85 var totalEl = app.querySelector('[data-result="total"] .bkbg-lc-res-val');
86 var amortWrap = app.querySelector('.bkbg-lc-amort-wrap');
87
88 var chartInstance = null;
89
90 function updateAll() {
91 var a = parseFloat(amtInput ? amtInput.value : amt);
92 var r = parseFloat(rateInput ? rateInput.value : rate);
93 var y = parseFloat(yrsInput ? yrsInput.value : yrs);
94
95 // Update displayed values
96 if (amountGroup) {
97 var v = amountGroup.querySelector('.bkbg-lc-slider-val');
98 if (v) v.textContent = fmtMoney(a, cur, pos);
99 }
100 if (interestGroup) {
101 var v2 = interestGroup.querySelector('.bkbg-lc-slider-val');
102 if (v2) v2.textContent = r + '%';
103 }
104 if (termGroup) {
105 var v3 = termGroup.querySelector('.bkbg-lc-slider-val');
106 if (v3) v3.textContent = y + ' yr';
107 }
108
109 var result = calcLoan(a, r, y);
110
111 if (monthlyEl) monthlyEl.textContent = fmtMoney(result.monthly, cur, pos);
112 if (principalEl) principalEl.textContent = fmtMoney(a, cur, pos);
113 if (interestEl) interestEl.textContent = fmtMoney(result.totalInterest, cur, pos);
114 if (totalEl) totalEl.textContent = fmtMoney(result.totalPayment, cur, pos);
115
116 // Update chart
117 if (chartInstance) {
118 chartInstance.data.datasets[0].data = [a, result.totalInterest];
119 chartInstance.update();
120 }
121
122 // Amortization table
123 if (opts.showAmortization && amortWrap) {
124 var rows = buildAmort(a, r, y, opts.amortizationRows || 12);
125 var table = '<table class="bkbg-lc-amort-table"><thead><tr><th>#</th><th>Payment</th><th>Principal</th><th>Interest</th><th>Balance</th></tr></thead><tbody>';
126 rows.forEach(function (row) {
127 table += '<tr><td>' + row.month + '</td><td>' + fmtMoney(row.payment, cur, pos) + '</td><td>' + fmtMoney(row.principal, cur, pos) + '</td><td>' + fmtMoney(row.interest, cur, pos) + '</td><td>' + fmtMoney(row.balance, cur, pos) + '</td></tr>';
128 });
129 table += '</tbody></table>';
130 amortWrap.innerHTML = table;
131 }
132 }
133
134 function bindSlider(input) {
135 if (!input) return;
136 input.addEventListener('input', updateAll);
137 }
138 bindSlider(amtInput);
139 bindSlider(rateInput);
140 bindSlider(yrsInput);
141
142 // Initial render
143 updateAll();
144
145 // Load Chart.js and render donut
146 if (opts.showChart) {
147 function renderChart() {
148 var canvas = app.querySelector('.bkbg-lc-chart');
149 if (!canvas || typeof Chart === 'undefined') return;
150 var result = calcLoan(
151 parseFloat(amtInput ? amtInput.value : amt),
152 parseFloat(rateInput ? rateInput.value : rate),
153 parseFloat(yrsInput ? yrsInput.value : yrs)
154 );
155 chartInstance = new Chart(canvas, {
156 type: 'doughnut',
157 data: {
158 labels: ['Principal', 'Interest'],
159 datasets: [{
160 data: [parseFloat(amtInput ? amtInput.value : amt), result.totalInterest],
161 backgroundColor: [opts.principalColor || '#6c3fb5', opts.interestColor || '#f59e0b'],
162 borderWidth: 0
163 }]
164 },
165 options: {
166 cutout: '70%',
167 plugins: { legend: { position: 'bottom', labels: { padding: 16 } } }
168 }
169 });
170 }
171
172 if (typeof Chart !== 'undefined') {
173 renderChart();
174 } else {
175 if (!document.getElementById('bkbg-chartjs-cdn')) {
176 var s = document.createElement('script');
177 s.id = 'bkbg-chartjs-cdn';
178 s.src = CHART_CDN;
179 s.onload = renderChart;
180 document.head.appendChild(s);
181 } else {
182 var wait = setInterval(function () {
183 if (typeof Chart !== 'undefined') { clearInterval(wait); renderChart(); }
184 }, 100);
185 }
186 }
187 }
188 });
189 })();
190