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 / index.js

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

216 lines 16.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 ( function () {
2 var el = wp.element.createElement;
3 var useState = wp.element.useState;
4 var Fragment = wp.element.Fragment;
5 var registerBlockType = wp.blocks.registerBlockType;
6 var __ = wp.i18n.__;
7 var InspectorControls = wp.blockEditor.InspectorControls;
8 var PanelColorSettings = wp.blockEditor.PanelColorSettings;
9 var useBlockProps = wp.blockEditor.useBlockProps;
10 var PanelBody = wp.components.PanelBody;
11 var RangeControl = wp.components.RangeControl;
12 var SelectControl = wp.components.SelectControl;
13 var TextControl = wp.components.TextControl;
14 var ToggleControl = wp.components.ToggleControl;
15 var Button = wp.components.Button;
16
17 var _TypographyControl, _typoCssVars;
18 function getTypographyControl() { return _TypographyControl || (_TypographyControl = window.bkbgTypographyControl); }
19 function getTypoCssVars() { return _typoCssVars || (_typoCssVars = window.bkbgTypoCssVars); }
20
21 var DEFAULT_ITEMS = [
22 { id: 1, desc: 'Website Design', qty: 1, price: 1200 },
23 { id: 2, desc: 'SEO Optimization', qty: 3, price: 150 },
24 { id: 3, desc: 'Monthly Support', qty: 12, price: 80 }
25 ];
26
27 function uid() { return Date.now() + Math.random(); }
28 function fmt(sym, n) { return sym + Number(n || 0).toFixed(2); }
29
30 function InvoicePreview(props) {
31 var a = props.attributes;
32 var sa = props.setAttributes;
33 var sym = a.currencySymbol || '$';
34 var accent = a.accentColor || '#6c3fb5';
35
36 var _items = useState(DEFAULT_ITEMS.map(function(i){ return Object.assign({},i); }));
37 var items = _items[0]; var setItems = _items[1];
38 var _tax = useState(a.defaultTax || 10); var tax = _tax[0]; var setTax = _tax[1];
39 var _disc = useState(a.defaultDiscount || 0); var disc = _disc[0]; var setDisc = _disc[1];
40 var _invNo = useState('INV-001'); var invNo = _invNo[0]; var setInvNo = _invNo[1];
41
42 var subtotal = items.reduce(function(s, i){ return s + (parseFloat(i.qty)||0) * (parseFloat(i.price)||0); }, 0);
43 var discAmt = subtotal * (parseFloat(disc)||0) / 100;
44 var taxAmt = (subtotal - discAmt) * (parseFloat(tax)||0) / 100;
45 var total = subtotal - discAmt + taxAmt;
46
47 function updateItem(id, field, val) {
48 setItems(items.map(function(it){ return it.id === id ? Object.assign({}, it, { [field]: val }) : it; }));
49 }
50 function addItem() {
51 setItems([...items, { id: uid(), desc: '', qty: 1, price: 0 }]);
52 }
53 function removeItem(id) {
54 setItems(items.filter(function(it){ return it.id !== id; }));
55 }
56
57 var thStyle = { padding: '10px 12px', textAlign: 'left', fontWeight: 700, fontSize: '12px', textTransform: 'uppercase', letterSpacing: '.04em', color: a.headerColor || '#6c3fb5', background: a.headerBg || '#f5f3ff', border: 'none' };
58 var tdStyle = { padding: '10px 12px', fontSize: '14px', color: a.labelColor, border: 'none', verticalAlign: 'middle' };
59
60 function cellInput(val, onChange, type, width) {
61 return el('input', { type: type || 'text', value: val, style: { width: width || '100%', padding: '6px 8px', border: '1.5px solid ' + (a.borderColor||'#e5e7eb'), borderRadius: (a.inputRadius||6) + 'px', fontSize: '13px', fontFamily: 'inherit', outline: 'none', boxSizing: 'border-box' }, onChange: function(e){ onChange(e.target.value); } });
62 }
63
64 return el('div', { style: { paddingTop: a.paddingTop+'px', paddingBottom: a.paddingBottom+'px', background: a.sectionBg||undefined } },
65 el('div', { style: { background: a.cardBg, borderRadius: a.cardRadius+'px', padding: '36px 32px', maxWidth: a.maxWidth+'px', margin: '0 auto', boxShadow:'0 4px 24px rgba(0,0,0,0.09)' } },
66
67 // Header row
68 el('div', { style:{ display:'flex', justifyContent:'space-between', alignItems:'flex-start', marginBottom:'24px', flexWrap:'wrap', gap:'12px' }},
69 el('div', null,
70 a.showTitle && el('div', { className: 'bkbg-inv-title', style:{ color:a.titleColor, marginBottom:'4px' }}, a.title),
71 a.showSubtitle && el('div', { style:{ fontSize:'14px', color:a.subtitleColor }}, a.subtitle)
72 ),
73 a.showInvoiceNo && el('div', { style:{ textAlign:'right' }},
74 el('div', { style:{ fontSize:'12px', color:a.labelColor, fontWeight:600, marginBottom:'4px' }}, 'Invoice #'),
75 el('input', { value: invNo, style:{ border:'1.5px solid '+(a.borderColor||'#e5e7eb'), borderRadius:(a.inputRadius||6)+'px', padding:'5px 10px', fontSize:'14px', fontFamily:'inherit', outline:'none', width:'120px', textAlign:'right' }, onChange: function(e){ setInvNo(e.target.value); } })
76 )
77 ),
78
79 // From/To
80 a.showFromTo && el('div', { style:{ display:'grid', gridTemplateColumns:'1fr 1fr', gap:'16px', marginBottom:'24px' }},
81 ['From', 'To'].map(function(lbl, i) {
82 return el('div', { key:lbl, style:{ padding:'14px 16px', background: a.headerBg || '#f5f3ff', borderRadius:(a.inputRadius||6)+'px' }},
83 el('div', { style:{ fontSize:'11px', fontWeight:600, textTransform:'uppercase', letterSpacing:'.04em', color:a.headerColor||accent, marginBottom:'6px' }}, lbl),
84 el('div', { style:{ fontSize:'14px', fontWeight:600, color:a.labelColor }}, i===0 ? a.fromName : a.toName)
85 );
86 })
87 ),
88
89 // Items table
90 el('div', { style:{ overflowX:'auto', borderRadius:(a.inputRadius||6)+'px', border:'1.5px solid '+(a.borderColor||'#e5e7eb'), marginBottom:'16px' }},
91 el('table', { style:{ width:'100%', borderCollapse:'collapse' }},
92 el('thead', null, el('tr', null,
93 el('th', { style: Object.assign({}, thStyle, { width:'44%' }) }, 'Description'),
94 el('th', { style: Object.assign({}, thStyle, { width:'14%', textAlign:'center' }) }, 'Qty'),
95 el('th', { style: Object.assign({}, thStyle, { width:'20%', textAlign:'right' }) }, 'Price'),
96 el('th', { style: Object.assign({}, thStyle, { width:'18%', textAlign:'right' }) }, 'Total'),
97 el('th', { style: Object.assign({}, thStyle, { width:'4%' }) }, '')
98 )),
99 el('tbody', null,
100 items.map(function(it, idx) {
101 var rowBg = idx % 2 === 0 ? (a.rowOddBg||'#ffffff') : (a.rowEvenBg||'#f9fafb');
102 var lineTotal = (parseFloat(it.qty)||0) * (parseFloat(it.price)||0);
103 return el('tr', { key: String(it.id), style:{ background: rowBg }},
104 el('td', { style: tdStyle }, cellInput(it.desc, function(v){ updateItem(it.id,'desc',v); })),
105 el('td', { style: Object.assign({}, tdStyle, {textAlign:'center'}) }, cellInput(it.qty, function(v){ updateItem(it.id,'qty',v); }, 'number', '60px')),
106 el('td', { style: Object.assign({}, tdStyle, {textAlign:'right'}) }, cellInput(it.price, function(v){ updateItem(it.id,'price',v); }, 'number', '80px')),
107 el('td', { style: Object.assign({}, tdStyle, {textAlign:'right', fontWeight:600}) }, fmt(sym, lineTotal)),
108 el('td', { style: Object.assign({}, tdStyle, {textAlign:'center'}) },
109 el('button', { onClick: function(){ removeItem(it.id); }, style:{ background:'none', border:'none', color:'#ef4444', cursor:'pointer', fontSize:'16px', lineHeight:1, padding:'2px 4px' }}, '×')
110 )
111 );
112 })
113 )
114 )
115 ),
116
117 // Add row button
118 el('button', { onClick: addItem, style:{ display:'flex', alignItems:'center', gap:'6px', background:'none', border:'1.5px dashed '+(a.borderColor||'#e5e7eb'), borderRadius:(a.inputRadius||6)+'px', padding:'8px 14px', color:accent, fontWeight:600, fontSize:'13px', cursor:'pointer', fontFamily:'inherit', width:'100%', justifyContent:'center', marginBottom:'20px' }},
119 '+ Add Line Item'
120 ),
121
122 // Totals panel
123 el('div', { style:{ marginLeft:'auto', maxWidth:'280px' }},
124 el('div', { style:{ display:'flex', justifyContent:'space-between', padding:'8px 0', borderBottom:'1px solid '+(a.borderColor||'#e5e7eb'), fontSize:'14px', color:a.labelColor }},
125 el('span', null, 'Subtotal'), el('span', { style:{fontWeight:600} }, fmt(sym, subtotal))
126 ),
127 el('div', { style:{ display:'flex', justifyContent:'space-between', alignItems:'center', padding:'8px 0', borderBottom:'1px solid '+(a.borderColor||'#e5e7eb'), fontSize:'14px', color:a.labelColor }},
128 el('span', null, 'Discount (%)'),
129 el('div', { style:{display:'flex', alignItems:'center', gap:'8px'}},
130 el('input', { type:'number',min:0,max:100,value:disc, onChange:function(e){setDisc(e.target.value);}, style:{width:'60px',padding:'4px 8px',border:'1.5px solid '+(a.borderColor||'#e5e7eb'),borderRadius:(a.inputRadius||6)+'px',fontSize:'13px',outline:'none',fontFamily:'inherit'} }),
131 el('span', { style:{fontWeight:600, color:'#ef4444'} }, '-' + fmt(sym, discAmt))
132 )
133 ),
134 el('div', { style:{ display:'flex', justifyContent:'space-between', alignItems:'center', padding:'8px 0', borderBottom:'1px solid '+(a.borderColor||'#e5e7eb'), fontSize:'14px', color:a.labelColor }},
135 el('span', null, 'Tax (%)'),
136 el('div', { style:{display:'flex', alignItems:'center', gap:'8px'}},
137 el('input', { type:'number',min:0,max:100,value:tax, onChange:function(e){setTax(e.target.value);}, style:{width:'60px',padding:'4px 8px',border:'1.5px solid '+(a.borderColor||'#e5e7eb'),borderRadius:(a.inputRadius||6)+'px',fontSize:'13px',outline:'none',fontFamily:'inherit'} }),
138 el('span', { style:{fontWeight:600} }, '+' + fmt(sym, taxAmt))
139 )
140 ),
141 el('div', { style:{ display:'flex', justifyContent:'space-between', padding:'14px 16px', background: a.totalBg||accent, borderRadius:(a.inputRadius||6)+'px', marginTop:'8px', color:a.totalColor||'#fff' }},
142 el('span', { style:{fontSize:'15px', fontWeight:700} }, 'Total'),
143 el('span', { style:{fontSize:'20px', fontWeight:800} }, fmt(sym, total))
144 )
145 )
146 )
147 );
148 }
149
150 registerBlockType('blockenberg/invoice-calculator', {
151 edit: function(props) {
152 var a = props.attributes; var set = props.setAttributes;
153 var blockProps = useBlockProps((function () {
154 var _tv = getTypoCssVars();
155 var s = {};
156 Object.assign(s, _tv(a.titleTypo, '--bkbg-inv-tt-'));
157 return { style: s };
158 })());
159 var colorSettings = [
160 { value: a.accentColor, onChange: function(v){ set({accentColor:v}); }, label: 'Accent Color' },
161 { value: a.cardBg, onChange: function(v){ set({cardBg:v}); }, label: 'Card Background' },
162 { value: a.headerBg, onChange: function(v){ set({headerBg:v}); }, label: 'Table Header Background' },
163 { value: a.headerColor, onChange: function(v){ set({headerColor:v}); }, label: 'Table Header Text' },
164 { value: a.rowOddBg, onChange: function(v){ set({rowOddBg:v}); }, label: 'Row (Odd) Background' },
165 { value: a.rowEvenBg, onChange: function(v){ set({rowEvenBg:v}); }, label: 'Row (Even) Background' },
166 { value: a.totalBg, onChange: function(v){ set({totalBg:v}); }, label: 'Total Row Background' },
167 { value: a.totalColor, onChange: function(v){ set({totalColor:v}); }, label: 'Total Row Text' },
168 { value: a.borderColor, onChange: function(v){ set({borderColor:v}); }, label: 'Border Color' },
169 { value: a.labelColor, onChange: function(v){ set({labelColor:v}); }, label: 'Label Color' },
170 { value: a.titleColor, onChange: function(v){ set({titleColor:v}); }, label: 'Title Color' },
171 { value: a.subtitleColor, onChange: function(v){ set({subtitleColor:v}); }, label: 'Subtitle Color' },
172 { value: a.sectionBg, onChange: function(v){ set({sectionBg:v}); }, label: 'Section Background' }
173 ];
174 return el(Fragment, null,
175 el(InspectorControls, null,
176 el(PanelBody, { title: 'Header', initialOpen: false },
177 el(ToggleControl, { __nextHasNoMarginBottom: true, label: 'Show Title', checked: a.showTitle, onChange: function(v){ set({showTitle:v}); } }),
178 el(ToggleControl, { __nextHasNoMarginBottom: true, label: 'Show Subtitle', checked: a.showSubtitle, onChange: function(v){ set({showSubtitle:v}); } }),
179 el(TextControl, { label: 'Title', value: a.title, onChange: function(v){ set({title:v}); } }),
180 el(TextControl, { label: 'Subtitle', value: a.subtitle, onChange: function(v){ set({subtitle:v}); } })
181 ),
182 el(PanelBody, { title: 'Invoice Settings', initialOpen: true },
183 el(TextControl, { label: 'Currency Symbol', value: a.currencySymbol, onChange: function(v){ set({currencySymbol:v}); } }),
184 el(TextControl, { label: 'Default Tax (%)', value: String(a.defaultTax), onChange: function(v){ set({defaultTax:parseFloat(v)||0}); } }),
185 el(TextControl, { label: 'Default Discount (%)', value: String(a.defaultDiscount), onChange: function(v){ set({defaultDiscount:parseFloat(v)||0}); } }),
186 el(ToggleControl, { __nextHasNoMarginBottom: true, label: 'Show From / To Info', checked: a.showFromTo, onChange: function(v){ set({showFromTo:v}); } }),
187 a.showFromTo && el(TextControl, { label: 'From Name', value: a.fromName, onChange: function(v){ set({fromName:v}); } }),
188 a.showFromTo && el(TextControl, { label: 'To Name', value: a.toName, onChange: function(v){ set({toName:v}); } }),
189 el(ToggleControl, { __nextHasNoMarginBottom: true, label: 'Show Invoice Number', checked: a.showInvoiceNo, onChange: function(v){ set({showInvoiceNo:v}); } })
190 ),
191
192 el( PanelBody, { title: __( 'Typography', 'blockenberg' ), initialOpen: false },
193 el(getTypographyControl(), { label: 'Title', value: a.titleTypo, onChange: function(v){ set({ titleTypo: v }); } })
194 ),
195 el(PanelColorSettings, { title: 'Colors', initialOpen: false, colorSettings: colorSettings }),
196 el(PanelBody, { title: 'Sizing & Layout', initialOpen: false },
197 el(RangeControl, { label: 'Card Border Radius', value: a.cardRadius, min:0, max:40, step:1, onChange: function(v){ set({cardRadius:v}); } }),
198 el(RangeControl, { label: 'Input Border Radius',value: a.inputRadius, min:0, max:20, step:1, onChange: function(v){ set({inputRadius:v}); } }),
199 el(RangeControl, { label: 'Max Width (px)', value: a.maxWidth, min:320,max:900, step:10, onChange: function(v){ set({maxWidth:v}); } }),
200 el(RangeControl, { label: 'Padding Top (px)', value: a.paddingTop, min:0, max:160, step:4, onChange: function(v){ set({paddingTop:v}); } }),
201 el(RangeControl, { label: 'Padding Bottom (px)',value: a.paddingBottom, min:0, max:160, step:4, onChange: function(v){ set({paddingBottom:v}); } })
202 )
203 ),
204 el('div', blockProps, el(InvoicePreview, { attributes: a, setAttributes: set }))
205 );
206 },
207 save: function(props) {
208 var a = props.attributes;
209 var _tv = getTypoCssVars();
210 var s = {};
211 Object.assign(s, _tv(a.titleTypo, '--bkbg-inv-tt-'));
212 return el('div', wp.blockEditor.useBlockProps.save({ style: s }), el('div', { className: 'bkbg-inv-app', 'data-opts': JSON.stringify(a) }));
213 }
214 });
215 }() );
216