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 / invoice-calculator / frontend.js

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

301 lines 15.5 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 function fmt(sym, n) { return sym + Number(n || 0).toFixed(2); }
5 function uid() { return '_' + Math.random().toString(36).slice(2); }
6
7 /* ── typography CSS-var helper ──────────────────────── */
8 var _typoKeys = {
9 family:'font-family', weight:'font-weight', style:'font-style',
10 decoration:'text-decoration', transform:'text-transform',
11 sizeDesktop:'font-size-d', sizeTablet:'font-size-t', sizeMobile:'font-size-m',
12 lineHeightDesktop:'line-height-d', lineHeightTablet:'line-height-t', lineHeightMobile:'line-height-m',
13 letterSpacingDesktop:'letter-spacing-d', letterSpacingTablet:'letter-spacing-t', letterSpacingMobile:'letter-spacing-m',
14 wordSpacingDesktop:'word-spacing-d', wordSpacingTablet:'word-spacing-t', wordSpacingMobile:'word-spacing-m'
15 };
16 function typoCssVarsForEl(el, obj, prefix) {
17 if (!obj || typeof obj !== 'object') return;
18 Object.keys(_typoKeys).forEach(function (k) {
19 var v = obj[k];
20 if (v === undefined || v === '' || v === null) return;
21 if (k === 'sizeDesktop' || k === 'sizeTablet' || k === 'sizeMobile') v = v + (obj.sizeUnit || 'px');
22 else if (k === 'lineHeightDesktop' || k === 'lineHeightTablet' || k === 'lineHeightMobile') v = v + (obj.lineHeightUnit || '');
23 else if (k === 'letterSpacingDesktop' || k === 'letterSpacingTablet' || k === 'letterSpacingMobile') v = v + (obj.letterSpacingUnit || 'px');
24 else if (k === 'wordSpacingDesktop' || k === 'wordSpacingTablet' || k === 'wordSpacingMobile') v = v + (obj.wordSpacingUnit || 'px');
25 el.style.setProperty(prefix + _typoKeys[k], String(v));
26 });
27 }
28
29 function initInvoice(app) {
30 var opts = {};
31 try { opts = JSON.parse(app.getAttribute('data-opts') || '{}'); } catch(e) {}
32
33 var accent = opts.accentColor || '#6c3fb5';
34 var cardBg = opts.cardBg || '#ffffff';
35 var headerBg = opts.headerBg || '#f5f3ff';
36 var headerColor = opts.headerColor || '#6c3fb5';
37 var rowOddBg = opts.rowOddBg || '#ffffff';
38 var rowEvenBg = opts.rowEvenBg || '#f9fafb';
39 var totalBg = opts.totalBg || '#6c3fb5';
40 var totalColor = opts.totalColor || '#ffffff';
41 var borderColor = opts.borderColor || '#e5e7eb';
42 var labelColor = opts.labelColor || '#374151';
43 var titleColor = opts.titleColor || '#1f2937';
44 var subtitleColor= opts.subtitleColor || '#6b7280';
45 var sectionBg = opts.sectionBg || '';
46 var maxWidth = opts.maxWidth || 640;
47 var cardRadius = opts.cardRadius || 16;
48 var inputRadius = opts.inputRadius || 6;
49 var titleSize = opts.titleSize || 28;
50 var padTop = opts.paddingTop || 60;
51 var padBot = opts.paddingBottom || 60;
52 var sym = opts.currencySymbol || '$';
53 var showTitle = opts.showTitle !== false;
54 var showSub = opts.showSubtitle !== false;
55 var showFromTo = opts.showFromTo || false;
56 var showInvNo = opts.showInvoiceNo || false;
57
58 var items = [
59 { id: uid(), desc: 'Website Design', qty: 1, price: 1200 },
60 { id: uid(), desc: 'SEO Optimization', qty: 3, price: 150 },
61 { id: uid(), desc: 'Monthly Support', qty: 12, price: 80 }
62 ];
63 var taxRate = parseFloat(opts.defaultTax) || 0;
64 var discRate = parseFloat(opts.defaultDiscount) || 0;
65
66 // Layout
67 app.style.fontFamily = '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';
68 if (sectionBg) app.style.background = sectionBg;
69 app.style.paddingTop = padTop + 'px';
70 app.style.paddingBottom = padBot + 'px';
71
72 typoCssVarsForEl(app, opts.titleTypo, '--bkbg-inv-tt-');
73
74 var card = document.createElement('div');
75 card.className = 'bkbg-inv-card';
76 card.style.cssText = 'background:' + cardBg + ';border-radius:' + cardRadius + 'px;padding:36px 32px;max-width:' + maxWidth + 'px;margin:0 auto;';
77 app.appendChild(card);
78
79 // Header
80 var topRow = document.createElement('div');
81 topRow.style.cssText = 'display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:24px;flex-wrap:wrap;gap:12px;';
82 card.appendChild(topRow);
83
84 var titleWrap = document.createElement('div');
85 if (showTitle && opts.title) {
86 var titleEl = document.createElement('div');
87 titleEl.className = 'bkbg-inv-title';
88 titleEl.style.cssText = 'color:' + titleColor + ';margin-bottom:4px;';
89 titleEl.textContent = opts.title;
90 titleWrap.appendChild(titleEl);
91 }
92 if (showSub && opts.subtitle) {
93 var subEl = document.createElement('div');
94 subEl.style.cssText = 'font-size:14px;color:' + subtitleColor + ';';
95 subEl.textContent = opts.subtitle;
96 titleWrap.appendChild(subEl);
97 }
98 topRow.appendChild(titleWrap);
99
100 if (showInvNo) {
101 var invNoWrap = document.createElement('div');
102 invNoWrap.style.textAlign = 'right';
103 var invLbl = document.createElement('div');
104 invLbl.style.cssText = 'font-size:12px;font-weight:600;color:' + labelColor + ';margin-bottom:4px;';
105 invLbl.textContent = 'Invoice #';
106 var invInp = document.createElement('input');
107 invInp.className = 'bkbg-inv-input';
108 invInp.value = 'INV-001';
109 invInp.style.cssText = 'width:120px;text-align:right;border-radius:' + inputRadius + 'px;border:1.5px solid ' + borderColor + ';border-color:' + borderColor + ';font-size:14px;';
110 invNoWrap.appendChild(invLbl); invNoWrap.appendChild(invInp);
111 topRow.appendChild(invNoWrap);
112 }
113
114 if (showFromTo) {
115 var ftGrid = document.createElement('div');
116 ftGrid.className = 'bkbg-inv-from-to';
117 ftGrid.style.marginBottom = '24px';
118 [['From', opts.fromName || ''], ['To', opts.toName || '']].forEach(function(pair) {
119 var box = document.createElement('div');
120 box.style.cssText = 'padding:14px 16px;background:' + headerBg + ';border-radius:' + inputRadius + 'px;';
121 var lbl = document.createElement('div');
122 lbl.style.cssText = 'font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.04em;color:' + headerColor + ';margin-bottom:6px;';
123 lbl.textContent = pair[0];
124 var val = document.createElement('div');
125 val.style.cssText = 'font-size:14px;font-weight:600;color:' + labelColor + ';';
126 val.textContent = pair[1];
127 box.appendChild(lbl); box.appendChild(val);
128 ftGrid.appendChild(box);
129 });
130 card.appendChild(ftGrid);
131 }
132
133 // Table
134 var tableWrap = document.createElement('div');
135 tableWrap.className = 'bkbg-inv-table-wrap';
136 tableWrap.style.cssText = 'overflow-x:auto;border-radius:' + inputRadius + 'px;border:1.5px solid ' + borderColor + ';margin-bottom:14px;';
137 card.appendChild(tableWrap);
138
139 var table = document.createElement('table');
140 table.className = 'bkbg-inv-table';
141 tableWrap.appendChild(table);
142
143 var thead = document.createElement('thead');
144 table.appendChild(thead);
145 var thRow = document.createElement('tr');
146 thead.appendChild(thRow);
147 [['Description','44%','left'],['Qty','12%','center'],['Price','20%','right'],['Total','18%','right'],['','6%','center']].forEach(function(col) {
148 var th = document.createElement('th');
149 th.style.cssText = 'width:' + col[1] + ';text-align:' + col[2] + ';background:' + headerBg + ';color:' + headerColor + ';padding:10px 12px;font-size:12px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;';
150 th.textContent = col[0];
151 thRow.appendChild(th);
152 });
153
154 var tbody = document.createElement('tbody');
155 table.appendChild(tbody);
156
157 // Totals
158 var totalsWrap = document.createElement('div');
159 totalsWrap.className = 'bkbg-inv-totals';
160 card.appendChild(totalsWrap);
161
162 var subtotalRow = makeTotalRow('Subtotal', '');
163 var discRow = makeTotalRow('Discount', '');
164 var taxRow = makeTotalRow('Tax', '');
165 var grandRow = document.createElement('div');
166 grandRow.className = 'bkbg-inv-grand-total';
167 grandRow.style.cssText = 'display:flex;justify-content:space-between;padding:14px 16px;background:' + totalBg + ';border-radius:' + inputRadius + 'px;margin-top:8px;color:' + totalColor + ';';
168 var grandLbl = document.createElement('span'); grandLbl.style.cssText = 'font-size:15px;font-weight:700;'; grandLbl.textContent = 'Total';
169 var grandVal = document.createElement('span'); grandVal.style.cssText = 'font-size:20px;font-weight:800;';
170 grandRow.appendChild(grandLbl); grandRow.appendChild(grandVal);
171
172 // Tax/Disc inputs
173 var discContainer = document.createElement('div');
174 discContainer.style.cssText = 'display:flex;align-items:center;gap:8px;';
175 var discInp = makeNumInp(discRate);
176 var discAmt = document.createElement('span'); discAmt.style.cssText = 'font-weight:600;color:#ef4444;font-size:14px;';
177 discContainer.appendChild(discInp); discContainer.appendChild(discAmt);
178 discRow.querySelector('.bkbg-inv-row-val').appendChild(discContainer);
179
180 var taxContainer = document.createElement('div');
181 taxContainer.style.cssText = 'display:flex;align-items:center;gap:8px;';
182 var taxInp = makeNumInp(taxRate);
183 var taxAmt = document.createElement('span'); taxAmt.style.cssText = 'font-weight:600;font-size:14px;color:' + labelColor + ';';
184 taxContainer.appendChild(taxInp); taxContainer.appendChild(taxAmt);
185 taxRow.querySelector('.bkbg-inv-row-val').appendChild(taxContainer);
186
187 totalsWrap.appendChild(subtotalRow);
188 totalsWrap.appendChild(discRow);
189 totalsWrap.appendChild(taxRow);
190 totalsWrap.appendChild(grandRow);
191
192 function makeTotalRow(lbl, val) {
193 var div = document.createElement('div');
194 div.style.cssText = 'display:flex;justify-content:space-between;align-items:center;padding:8px 0;border-bottom:1px solid ' + borderColor + ';font-size:14px;color:' + labelColor + ';';
195 var l = document.createElement('span'); l.textContent = lbl;
196 var v = document.createElement('span'); v.className = 'bkbg-inv-row-val'; v.style.fontWeight = '600';
197 if (val) v.textContent = val;
198 div.appendChild(l); div.appendChild(v);
199 return div;
200 }
201
202 function makeNumInp(defaultVal) {
203 var inp = document.createElement('input');
204 inp.className = 'bkbg-inv-input';
205 inp.type = 'number'; inp.min = '0'; inp.max = '100'; inp.value = defaultVal;
206 inp.style.cssText = 'width:60px;border-radius:' + inputRadius + 'px;border:1.5px solid ' + borderColor + ';font-size:13px;';
207 return inp;
208 }
209
210 function recalc() {
211 var sub = items.reduce(function(s,i){ return s + (parseFloat(i.qty)||0)*(parseFloat(i.price)||0); }, 0);
212 var disc2 = parseFloat(discInp.value) || 0;
213 var tax2 = parseFloat(taxInp.value) || 0;
214 var damt = sub * disc2 / 100;
215 var tamt = (sub - damt) * tax2 / 100;
216 var tot = sub - damt + tamt;
217 subtotalRow.querySelector('.bkbg-inv-row-val').textContent = fmt(sym, sub);
218 discAmt.textContent = '-' + fmt(sym, damt);
219 taxAmt.textContent = '+' + fmt(sym, tamt);
220 grandVal.textContent = fmt(sym, tot);
221 }
222 discInp.addEventListener('input', recalc);
223 taxInp.addEventListener('input', recalc);
224
225 // Add line item button
226 var addBtn = document.createElement('button');
227 addBtn.className = 'bkbg-inv-add-btn';
228 addBtn.style.cssText = 'color:' + accent + ';border-color:' + borderColor + ';border-radius:' + inputRadius + 'px;';
229 addBtn.textContent = '+ Add Line Item';
230 addBtn.addEventListener('click', function() {
231 items.push({ id: uid(), desc: '', qty: 1, price: 0 });
232 renderRows();
233 recalc();
234 });
235 card.insertBefore(addBtn, totalsWrap);
236
237 function makeInpCell(val, type, width) {
238 var inp = document.createElement('input');
239 inp.className = 'bkbg-inv-input';
240 inp.type = type || 'text'; inp.value = val;
241 inp.style.cssText = 'width:' + (width || '100%') + ';border-radius:' + inputRadius + 'px;border:1.5px solid ' + borderColor + ';';
242 return inp;
243 }
244
245 function renderRows() {
246 tbody.innerHTML = '';
247 items.forEach(function(item, idx) {
248 var tr = document.createElement('tr');
249 tr.style.background = idx % 2 === 0 ? rowOddBg : rowEvenBg;
250
251 // Desc
252 var tdDesc = document.createElement('td'); tdDesc.style.padding = '8px 10px';
253 var descInp = makeInpCell(item.desc, 'text', '100%');
254 descInp.addEventListener('input', function(){ item.desc = descInp.value; });
255 tdDesc.appendChild(descInp); tr.appendChild(tdDesc);
256
257 // Qty
258 var tdQty = document.createElement('td'); tdQty.style.cssText = 'padding:8px 10px;text-align:center;';
259 var qtyInp = makeInpCell(item.qty, 'number', '60px');
260 qtyInp.addEventListener('input', function(){ item.qty = qtyInp.value; updateLineTotal(tdTotal, item); recalc(); });
261 tdQty.appendChild(qtyInp); tr.appendChild(tdQty);
262
263 // Price
264 var tdPrice = document.createElement('td'); tdPrice.style.cssText = 'padding:8px 10px;text-align:right;';
265 var priceInp = makeInpCell(item.price, 'number', '80px');
266 priceInp.style.textAlign = 'right';
267 priceInp.addEventListener('input', function(){ item.price = priceInp.value; updateLineTotal(tdTotal, item); recalc(); });
268 tdPrice.appendChild(priceInp); tr.appendChild(tdPrice);
269
270 // Line total
271 var tdTotal = document.createElement('td'); tdTotal.style.cssText = 'padding:8px 10px;text-align:right;font-weight:600;color:' + labelColor + ';font-size:14px;';
272 updateLineTotal(tdTotal, item);
273 tr.appendChild(tdTotal);
274
275 // Delete
276 var tdDel = document.createElement('td'); tdDel.style.cssText = 'padding:8px 6px;text-align:center;';
277 var delBtn = document.createElement('button');
278 delBtn.className = 'bkbg-inv-del-btn';
279 delBtn.textContent = '×';
280 delBtn.addEventListener('click', function() {
281 items = items.filter(function(i){ return i.id !== item.id; });
282 renderRows(); recalc();
283 });
284 tdDel.appendChild(delBtn); tr.appendChild(tdDel);
285
286 tbody.appendChild(tr);
287 });
288 recalc();
289 }
290
291 function updateLineTotal(td, item) {
292 td.textContent = fmt(sym, (parseFloat(item.qty)||0) * (parseFloat(item.price)||0));
293 }
294
295 renderRows();
296 recalc();
297 }
298
299 document.querySelectorAll('.bkbg-inv-app').forEach(initInvoice);
300 })();
301