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 / expense-tracker / frontend.js

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

388 lines 16.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 typoCssVarsForEl(typo, prefix, el) {
5 if (!typo || typeof typo !== 'object') return;
6 var map = {
7 family: 'font-family', weight: 'font-weight', transform: 'text-transform',
8 style: 'font-style', decoration: 'text-decoration',
9 sizeDesktop: 'font-size-d', sizeTablet: 'font-size-t', sizeMobile: 'font-size-m', sizeUnit: 'font-size-unit',
10 lineHeightDesktop: 'line-height-d', lineHeightTablet: 'line-height-t', lineHeightMobile: 'line-height-m', lineHeightUnit: 'line-height-unit',
11 letterSpacingDesktop: 'letter-spacing-d', letterSpacingTablet: 'letter-spacing-t', letterSpacingMobile: 'letter-spacing-m', letterSpacingUnit: 'letter-spacing-unit',
12 wordSpacingDesktop: 'word-spacing-d', wordSpacingTablet: 'word-spacing-t', wordSpacingMobile: 'word-spacing-m', wordSpacingUnit: 'word-spacing-unit'
13 };
14 Object.keys(map).forEach(function (k) {
15 if (typo[k] !== undefined && typo[k] !== '') {
16 var v = typo[k];
17 if (typeof v === 'number') {
18 var css = map[k];
19 var unit = '';
20 if (css.indexOf('font-size') === 0 && css !== 'font-size-unit') unit = typo.sizeUnit || 'px';
21 else if (css.indexOf('line-height') === 0 && css !== 'line-height-unit') unit = typo.lineHeightUnit || '';
22 else if (css.indexOf('letter-spacing') === 0 && css !== 'letter-spacing-unit') unit = typo.letterSpacingUnit || 'px';
23 else if (css.indexOf('word-spacing') === 0 && css !== 'word-spacing-unit') unit = typo.wordSpacingUnit || 'px';
24 v = v + unit;
25 }
26 el.style.setProperty(prefix + map[k], v);
27 }
28 });
29 }
30
31 var CAT_COLORS = {
32 Housing: '#6366f1',
33 Food: '#f59e0b',
34 Transport: '#10b981',
35 Entertainment: '#ec4899',
36 Health: '#ef4444',
37 Shopping: '#3b82f6',
38 Utilities: '#14b8a6',
39 Other: '#8b5cf6'
40 };
41 var CATEGORIES = ['Housing','Food','Transport','Entertainment','Health','Shopping','Utilities','Other'];
42
43 var STORAGE_KEY = 'bkbg_expense_tracker';
44
45 function loadData(uid) {
46 try {
47 var raw = localStorage.getItem(STORAGE_KEY + '_' + uid);
48 return raw ? JSON.parse(raw) : { expenses: [], budget: 1000 };
49 } catch (e) { return { expenses: [], budget: 1000 }; }
50 }
51
52 function saveData(uid, data) {
53 try { localStorage.setItem(STORAGE_KEY + '_' + uid, JSON.stringify(data)); } catch (e) {}
54 }
55
56 function formatDate(d) {
57 var dt = new Date(d);
58 return dt.toLocaleDateString(undefined, { month: 'short', day: 'numeric' });
59 }
60
61 function initBlock(root) {
62 var opts;
63 try { opts = JSON.parse(root.getAttribute('data-opts')); } catch (e) { return; }
64 var a = opts;
65 var uid = root.getAttribute('data-uid') || ('expt' + Math.random().toString(36).slice(2));
66 root.setAttribute('data-uid', uid);
67
68 var stored = loadData(uid);
69 var expenses = stored.expenses || [];
70 var budget = stored.budget || a.defaultBudget;
71 var filterCat = 'all';
72 var nextId = (expenses.length ? Math.max.apply(null, expenses.map(function (e) { return e.id; })) + 1 : 1);
73
74 root.innerHTML = '';
75
76 var wrap = document.createElement('div');
77 wrap.className = 'bkbg-expt-wrap';
78 wrap.style.cssText = 'background:' + a.sectionBg + ';max-width:' + a.contentMaxWidth + 'px;margin:0 auto;';
79 root.appendChild(wrap);
80
81 typoCssVarsForEl(a.typoTitle, '--bkbg-expt-ttl-', wrap);
82 typoCssVarsForEl(a.typoSubtitle, '--bkbg-expt-sub-', wrap);
83
84 if (a.showTitle) {
85 var h = document.createElement('div');
86 h.className = 'bkbg-expt-title';
87 h.style.cssText = 'color:' + a.titleColor + ';';
88 h.textContent = a.title;
89 wrap.appendChild(h);
90 }
91 if (a.showSubtitle) {
92 var sub = document.createElement('div');
93 sub.className = 'bkbg-expt-subtitle';
94 sub.style.color = a.subtitleColor;
95 sub.textContent = a.subtitle;
96 wrap.appendChild(sub);
97 }
98
99 /* Summary */
100 var summaryEl = document.createElement('div');
101 summaryEl.className = 'bkbg-expt-summary';
102 wrap.appendChild(summaryEl);
103
104 /* Form */
105 var form = document.createElement('div');
106 form.className = 'bkbg-expt-form';
107 form.style.cssText = 'background:' + a.cardBg + ';border-color:' + a.borderColor + ';';
108 wrap.appendChild(form);
109
110 function makeFormGroup(labelText) {
111 var g = document.createElement('div');
112 g.className = 'bkbg-expt-form-group';
113 var lbl = document.createElement('label');
114 lbl.className = 'bkbg-expt-label';
115 lbl.style.color = a.labelColor;
116 lbl.textContent = labelText;
117 g.appendChild(lbl);
118 return g;
119 }
120
121 var gName = makeFormGroup('Description');
122 var iName = document.createElement('input');
123 iName.className = 'bkbg-expt-input';
124 iName.placeholder = 'e.g. Coffee';
125 gName.appendChild(iName);
126 form.appendChild(gName);
127
128 var gAmt = makeFormGroup('Amount');
129 var iAmt = document.createElement('input');
130 iAmt.className = 'bkbg-expt-input';
131 iAmt.type = 'number';
132 iAmt.placeholder = '0.00';
133 iAmt.min = '0';
134 iAmt.step = '0.01';
135 gAmt.appendChild(iAmt);
136 form.appendChild(gAmt);
137
138 var gCat = makeFormGroup('Category');
139 var iCat = document.createElement('select');
140 iCat.className = 'bkbg-expt-select';
141 CATEGORIES.forEach(function (c) {
142 var o = document.createElement('option');
143 o.value = c;
144 o.textContent = c;
145 iCat.appendChild(o);
146 });
147 gCat.appendChild(iCat);
148 form.appendChild(gCat);
149
150 var gDate = makeFormGroup('Date');
151 var iDate = document.createElement('input');
152 iDate.className = 'bkbg-expt-input';
153 iDate.type = 'date';
154 iDate.value = new Date().toISOString().slice(0, 10);
155 gDate.appendChild(iDate);
156 form.appendChild(gDate);
157
158 var addBtn = document.createElement('button');
159 addBtn.className = 'bkbg-expt-add-btn';
160 addBtn.style.background = a.accentColor;
161 addBtn.textContent = '+ Add';
162 form.appendChild(addBtn);
163
164 /* Chart */
165 var chartEl = null;
166 if (a.showChart) {
167 chartEl = document.createElement('div');
168 chartEl.className = 'bkbg-expt-chart';
169 chartEl.style.cssText = 'background:' + a.cardBg + ';border-color:' + a.borderColor + ';';
170 wrap.appendChild(chartEl);
171 }
172
173 /* List */
174 var listEl = document.createElement('div');
175 listEl.className = 'bkbg-expt-list';
176 listEl.style.cssText = 'background:' + a.cardBg + ';border-color:' + a.borderColor + ';';
177 wrap.appendChild(listEl);
178
179 var listHeader = document.createElement('div');
180 listHeader.className = 'bkbg-expt-list-header';
181 listHeader.style.cssText = 'color:' + a.labelColor + ';border-color:' + a.borderColor + ';';
182 listHeader.appendChild(document.createTextNode('Expenses'));
183 var filterSel = document.createElement('select');
184 filterSel.className = 'bkbg-expt-filter';
185 var allOpt = document.createElement('option');
186 allOpt.value = 'all';
187 allOpt.textContent = 'All Categories';
188 filterSel.appendChild(allOpt);
189 CATEGORIES.forEach(function (c) {
190 var o = document.createElement('option');
191 o.value = c;
192 o.textContent = c;
193 filterSel.appendChild(o);
194 });
195 listHeader.appendChild(filterSel);
196 listEl.appendChild(listHeader);
197
198 var listBody = document.createElement('div');
199 listEl.appendChild(listBody);
200
201 function persist() { saveData(uid, { expenses: expenses, budget: budget }); }
202
203 function render() {
204 var total = expenses.reduce(function (s, e) { return s + e.amount; }, 0);
205
206 /* Summary */
207 summaryEl.innerHTML = '';
208 function makeCard(val, lbl, color) {
209 var c = document.createElement('div');
210 c.className = 'bkbg-expt-card';
211 c.style.cssText = 'background:' + a.cardBg + ';border-color:' + a.borderColor + ';';
212 var v = document.createElement('div');
213 v.className = 'bkbg-expt-card-val';
214 v.style.color = color;
215 v.textContent = a.currency + val;
216 var l = document.createElement('div');
217 l.className = 'bkbg-expt-card-lbl';
218 l.style.color = a.subtitleColor;
219 l.textContent = lbl;
220 c.appendChild(v);
221 c.appendChild(l);
222 return c;
223 }
224
225 summaryEl.appendChild(makeCard(total.toFixed(2), 'Total Spent', a.accentColor));
226
227 if (a.showBudget) {
228 var rem = budget - total;
229 var remColor = rem < 0 ? a.dangerColor : a.labelColor;
230 var budCard = makeCard(Math.abs(rem).toFixed(2), rem < 0 ? 'Over Budget' : 'Remaining', remColor);
231 var barWrap = document.createElement('div');
232 barWrap.className = 'bkbg-expt-budget-bar-wrap';
233 var barFill = document.createElement('div');
234 barFill.className = 'bkbg-expt-budget-bar';
235 var pct = Math.min((total / budget) * 100, 100);
236 barFill.style.cssText = 'width:' + pct + '%;background:' + (rem < 0 ? a.dangerColor : a.accentColor) + ';';
237 barWrap.appendChild(barFill);
238 budCard.appendChild(barWrap);
239 summaryEl.appendChild(budCard);
240 }
241
242 summaryEl.appendChild(makeCard(expenses.length.toString(), 'Transactions', a.labelColor));
243
244 /* Chart */
245 if (chartEl) {
246 chartEl.innerHTML = '';
247 var ct = document.createElement('div');
248 ct.className = 'bkbg-expt-chart-title';
249 ct.style.color = a.labelColor;
250 ct.textContent = 'Spending by Category';
251 chartEl.appendChild(ct);
252
253 var catTotals = {};
254 expenses.forEach(function (e) {
255 catTotals[e.category] = (catTotals[e.category] || 0) + e.amount;
256 });
257 var maxCat = Math.max.apply(null, Object.values(catTotals).concat([0]));
258
259 Object.keys(catTotals).sort(function (x, y) { return catTotals[y] - catTotals[x]; }).forEach(function (cat) {
260 var pct = maxCat > 0 ? Math.round((catTotals[cat] / maxCat) * 100) : 0;
261 var row = document.createElement('div');
262 row.className = 'bkbg-expt-bar-row';
263 var lbls = document.createElement('div');
264 lbls.className = 'bkbg-expt-bar-labels';
265 var catLbl = document.createElement('span');
266 catLbl.style.color = a.labelColor;
267 catLbl.textContent = cat;
268 var amtLbl = document.createElement('span');
269 amtLbl.style.color = a.subtitleColor;
270 amtLbl.textContent = a.currency + catTotals[cat].toFixed(2);
271 lbls.appendChild(catLbl);
272 lbls.appendChild(amtLbl);
273 var track = document.createElement('div');
274 track.className = 'bkbg-expt-bar-track';
275 var fill = document.createElement('div');
276 fill.className = 'bkbg-expt-bar-fill';
277 fill.style.cssText = 'width:' + pct + '%;background:' + (CAT_COLORS[cat] || a.accentColor) + ';';
278 track.appendChild(fill);
279 row.appendChild(lbls);
280 row.appendChild(track);
281 chartEl.appendChild(row);
282 });
283
284 if (!Object.keys(catTotals).length) {
285 var empty = document.createElement('div');
286 empty.style.cssText = 'text-align:center;padding:16px;font-size:13px;color:' + a.subtitleColor + ';';
287 empty.textContent = 'No expenses yet';
288 chartEl.appendChild(empty);
289 }
290 }
291
292 /* List */
293 listBody.innerHTML = '';
294 var filtered = filterCat === 'all' ? expenses : expenses.filter(function (e) { return e.category === filterCat; });
295 var sorted = filtered.slice().sort(function (x, y) { return new Date(y.date) - new Date(x.date); });
296
297 if (!sorted.length) {
298 var empt = document.createElement('div');
299 empt.className = 'bkbg-expt-empty';
300 empt.style.color = a.subtitleColor;
301 empt.textContent = filterCat === 'all' ? 'No expenses yet. Add your first one above!' : 'No expenses in this category.';
302 listBody.appendChild(empt);
303 return;
304 }
305
306 sorted.forEach(function (exp) {
307 var item = document.createElement('div');
308 item.className = 'bkbg-expt-item';
309 item.style.borderColor = a.borderColor;
310 var left = document.createElement('div');
311 left.className = 'bkbg-expt-item-left';
312 var dot = document.createElement('div');
313 dot.className = 'bkbg-expt-dot';
314 dot.style.background = CAT_COLORS[exp.category] || a.accentColor;
315 var info = document.createElement('div');
316 var name = document.createElement('div');
317 name.className = 'bkbg-expt-item-name';
318 name.style.color = a.labelColor;
319 name.textContent = exp.name;
320 var meta = document.createElement('div');
321 meta.className = 'bkbg-expt-item-meta';
322 meta.style.color = a.subtitleColor;
323 meta.textContent = exp.category + ' · ' + formatDate(exp.date);
324 info.appendChild(name);
325 info.appendChild(meta);
326 left.appendChild(dot);
327 left.appendChild(info);
328 var right = document.createElement('div');
329 right.className = 'bkbg-expt-item-right';
330 var amt = document.createElement('div');
331 amt.className = 'bkbg-expt-item-amount';
332 amt.style.color = a.labelColor;
333 amt.textContent = a.currency + exp.amount.toFixed(2);
334 var del = document.createElement('button');
335 del.className = 'bkbg-expt-del-btn';
336 del.textContent = '×';
337 del.setAttribute('aria-label', 'Delete');
338 del.addEventListener('click', function () {
339 expenses = expenses.filter(function (e) { return e.id !== exp.id; });
340 persist();
341 render();
342 });
343 right.appendChild(amt);
344 right.appendChild(del);
345 item.appendChild(left);
346 item.appendChild(right);
347 listBody.appendChild(item);
348 });
349 }
350
351 addBtn.addEventListener('click', function () {
352 var name = iName.value.trim();
353 var amt = parseFloat(iAmt.value);
354 if (!name || isNaN(amt) || amt <= 0) { return; }
355 expenses.push({ id: nextId++, name: name, amount: amt, category: iCat.value, date: iDate.value || new Date().toISOString().slice(0, 10) });
356 persist();
357 render();
358 iName.value = '';
359 iAmt.value = '';
360 iName.focus();
361 });
362
363 iName.addEventListener('keydown', function (e) {
364 if (e.key === 'Enter') { iAmt.focus(); }
365 });
366 iAmt.addEventListener('keydown', function (e) {
367 if (e.key === 'Enter') { addBtn.click(); }
368 });
369
370 filterSel.addEventListener('change', function () {
371 filterCat = filterSel.value;
372 render();
373 });
374
375 render();
376 }
377
378 function init() {
379 document.querySelectorAll('.bkbg-expt-app').forEach(initBlock);
380 }
381
382 if (document.readyState === 'loading') {
383 document.addEventListener('DOMContentLoaded', init);
384 } else {
385 init();
386 }
387 })();
388