// ============================================================ // Print Studio + History + Settings (live / API-backed) // ============================================================ (function () { const { Icon, Badge, BrandDot, Btn, Field, Modal, fmtNum } = window; const { useState, useMemo } = React; // ---------------- PRINT STUDIO ---------------- function PrintStudio({ products, queue, setQueue, printer, onPrint, toast, user }) { const [q, setQ] = useState(''); const [brand, setBrand] = useState('All'); const [focus, setFocus] = useState(0); const [defM, setDefM] = useState('05/2026'); const [defE, setDefE] = useState('04/2028'); const [defB, setDefB] = useState('34011542'); const [printing, setPrinting] = useState(null); const catalog = useMemo(() => products.filter(p => (brand === 'All' || p.brand === brand) && (!q || (p.title + p.sku + p.barcode).toLowerCase().includes(q.toLowerCase())) ).slice(0, 60), [products, q, brand]); const add = (p) => { if (queue.find(x => x.sku === p.sku)) { toast('Already in queue', 'info'); return; } setQueue(qq => [...qq, { ...p, qty: 50, mfg: defM, exp: defE, batch: defB }]); setFocus(queue.length); }; const upd = (i, k, v) => setQueue(qq => qq.map((x, j) => j === i ? { ...x, [k]: v } : x)); const rm = (i) => { setQueue(qq => qq.filter((_, j) => j !== i)); setFocus(f => Math.max(0, Math.min(f, queue.length - 2))); }; const applyAll = () => { setQueue(qq => qq.map(x => ({ ...x, mfg: defM, exp: defE, batch: defB }))); toast('Applied dates to all ' + queue.length + ' items'); }; const totalLabels = queue.reduce((s, x) => s + (+x.qty || 0), 0); const cur = queue[focus] || queue[0]; const send = async () => { if (!queue.length) return; const total = totalLabels; setPrinting({ done: 0, total }); let done = 0; const iv = setInterval(() => { done = Math.min(total * 0.9, done + Math.max(4, total / 20)); setPrinting({ done: Math.round(done), total }); }, 90); try { const res = await onPrint(queue, 'printed'); clearInterval(iv); setPrinting({ done: total, total }); setTimeout(async () => { setPrinting(null); setQueue([]); if (res && res.fallback_pdf) { const n = window.generateLabelsPDF(queue.map(x => ({ ...x })), { name: 'labels-' + new Date().toISOString().slice(0, 10), labelW: printer.labelW, labelH: printer.labelH, gap: printer.gap }); toast((n || total) + ' labels saved as PDF (printer unavailable)'); } else { toast(fmtNum(total) + ' labels sent to ' + printer.model); } }, 460); } catch (e) { clearInterval(iv); setPrinting(null); toast(e.message || 'Print failed', 'err'); } }; const downloadPdf = async () => { if (!queue.length) return; const n = window.generateLabelsPDF(queue.map(x => ({ ...x })), { name: 'labels-' + new Date().toISOString().slice(0, 10), labelW: printer.labelW, labelH: printer.labelH, gap: printer.gap }); toast((n || totalLabels) + ' labels exported to PDF'); try { await onPrint(queue, 'pdf'); } catch (e) { /* log best-effort */ } }; const tspl = cur ? window.buildTSPL({ product: cur, mfg: cur.mfg, exp: cur.exp, batch: cur.batch, qty: cur.qty, density: printer.density, speed: printer.speed, labelW: printer.labelW, labelH: printer.labelH, gap: printer.gap }) : ''; return React.createElement('div', { className: 'screen' }, React.createElement('div', { className: 'screen-head' }, React.createElement('div', null, React.createElement('h1', null, 'Print Studio'), React.createElement('p', { className: 'screen-sub' }, queue.length, ' products queued · ', React.createElement('b', { style: { color: 'var(--amber)' } }, fmtNum(totalLabels)), ' labels') ), React.createElement('div', { className: 'printer-pill ' + (printer.connected ? 'on' : 'off') }, React.createElement('span', { className: 'pulse-dot ' + (printer.connected ? 'live' : 'off') }), printer.model, ' · ', React.createElement('span', { className: 'mono' }, printer.mode === 'usb' ? printer.share : printer.ip) ) ), React.createElement('div', { className: 'studio' }, React.createElement('div', { className: 'panel catalog' }, React.createElement('div', { className: 'panel-head' }, React.createElement('h3', null, 'Catalog')), React.createElement('div', { className: 'search sm' }, React.createElement(Icon, { name: 'search', size: 15, color: 'var(--txt-mut)' }), React.createElement('input', { value: q, onChange: e => setQ(e.target.value), placeholder: 'Search to add…' }) ), React.createElement('div', { className: 'seg sm' }, ['All', 'LUXURIATE', 'VITMINVEDA'].map(b => React.createElement('button', { key: b, className: 'seg-btn' + (brand === b ? ' on' : ''), onClick: () => setBrand(b) }, b === 'All' ? 'All' : b === 'LUXURIATE' ? 'LUX' : 'VMV')) ), React.createElement('div', { className: 'cat-list' }, catalog.map(p => React.createElement('button', { key: p.sku, className: 'cat-item' + (queue.find(x => x.sku === p.sku) ? ' added' : ''), onClick: () => add(p) }, React.createElement(BrandDot, { brand: p.brand }), React.createElement('div', { className: 'cat-main' }, React.createElement('span', { className: 'cat-title' }, p.title.replace(p.brand, '').trim()), React.createElement('span', { className: 'cat-sku mono' }, p.sku) ), React.createElement(Icon, { name: queue.find(x => x.sku === p.sku) ? 'check' : 'plus', size: 16, stroke: 2.2 }) )) ) ), React.createElement('div', { className: 'queue-col' }, React.createElement('div', { className: 'panel' }, React.createElement('div', { className: 'defaults-bar' }, React.createElement(Field, { label: 'MFG' }, React.createElement('input', { className: 'input mono sm', value: defM, onChange: e => setDefM(e.target.value), placeholder: 'MM/YYYY' })), React.createElement(Field, { label: 'EXP' }, React.createElement('input', { className: 'input mono sm', value: defE, onChange: e => setDefE(e.target.value), placeholder: 'MM/YYYY' })), React.createElement(Field, { label: 'Batch No.' }, React.createElement('input', { className: 'input mono sm', value: defB, onChange: e => setDefB(e.target.value) })), React.createElement(Btn, { variant: 'ghost', size: 'sm', icon: 'layers', onClick: applyAll, disabled: !queue.length }, 'Apply to all') ) ), React.createElement('div', { className: 'panel queue-panel' }, queue.length === 0 ? React.createElement('div', { className: 'empty tall' }, React.createElement(Icon, { name: 'print', size: 34, color: 'var(--txt-mut)' }), React.createElement('p', null, 'Add products from the catalog to start a print job')) : React.createElement('div', { className: 'queue-list' }, queue.map((x, i) => React.createElement('div', { key: x.sku, className: 'q-item' + (i === focus ? ' focus' : ''), onClick: () => setFocus(i) }, React.createElement('div', { className: 'q-head' }, React.createElement(BrandDot, { brand: x.brand }), React.createElement('div', { className: 'q-titles' }, React.createElement('span', { className: 'q-title' }, x.title.replace(x.brand, '').trim()), React.createElement('span', { className: 'q-sku mono' }, x.sku, ' · ', x.barcode) ), React.createElement('button', { className: 'icon-btn danger', onClick: (e) => { e.stopPropagation(); rm(i); } }, React.createElement(Icon, { name: 'x', size: 15 })) ), React.createElement('div', { className: 'q-controls', onClick: e => e.stopPropagation() }, React.createElement('label', { className: 'mini-field' }, React.createElement('span', null, 'MFG'), React.createElement('input', { className: 'input mono xs', value: x.mfg, onChange: e => upd(i, 'mfg', e.target.value) })), React.createElement('label', { className: 'mini-field' }, React.createElement('span', null, 'EXP'), React.createElement('input', { className: 'input mono xs', value: x.exp, onChange: e => upd(i, 'exp', e.target.value) })), React.createElement('label', { className: 'mini-field' }, React.createElement('span', null, 'Batch'), React.createElement('input', { className: 'input mono xs', value: x.batch, onChange: e => upd(i, 'batch', e.target.value) })), React.createElement('div', { className: 'stepper' }, React.createElement('span', null, 'Qty'), React.createElement('button', { onClick: () => upd(i, 'qty', Math.max(1, (+x.qty || 1) - 10)) }, React.createElement(Icon, { name: 'minus', size: 14, stroke: 2.4 })), React.createElement('input', { className: 'input mono qty', value: x.qty, onChange: e => upd(i, 'qty', e.target.value.replace(/\D/g, '') || '') }), React.createElement('button', { onClick: () => upd(i, 'qty', (+x.qty || 0) + 10) }, React.createElement(Icon, { name: 'plus', size: 14, stroke: 2.4 })) ) ) )) ) ) ), React.createElement('div', { className: 'preview-col' }, React.createElement('div', { className: 'panel' }, React.createElement('div', { className: 'panel-head' }, React.createElement('h3', null, 'Label preview'), cur && React.createElement('span', { className: 'panel-meta mono' }, '×', cur.qty)), React.createElement('div', { className: 'label-stage' }, cur ? React.createElement(window.ThermalLabel, { product: cur, mfg: cur.mfg, exp: cur.exp, batch: cur.batch, labelW: printer.labelW, labelH: printer.labelH, scale: Math.min(6, Math.max(2.5, 240 / printer.labelW)) }) : React.createElement('div', { className: 'label-ghost' }, 'No product selected') ), React.createElement('span', { className: 'prev-note' }, printer.labelW + ' × ' + printer.labelH + ' mm · 203 dpi · EAN-13') ), React.createElement('div', { className: 'panel tspl-panel' }, React.createElement('div', { className: 'panel-head' }, React.createElement('h3', null, React.createElement(Icon, { name: 'barcode', size: 14, style: { marginRight: 6, verticalAlign: -2 } }), 'TSPL output'), React.createElement('button', { className: 'link-btn', onClick: () => { navigator.clipboard && navigator.clipboard.writeText(tspl); toast('TSPL copied'); } }, React.createElement(Icon, { name: 'copy', size: 13 }), 'Copy') ), React.createElement('pre', { className: 'tspl' }, tspl || '// queue empty') ), React.createElement('div', { className: 'send-bar' }, React.createElement('div', { className: 'send-stat' }, React.createElement('span', { className: 'send-num' }, fmtNum(totalLabels)), React.createElement('span', { className: 'send-lbl' }, 'labels · ', queue.length, ' SKUs · ~', Math.ceil(totalLabels / 60), ' min') ), React.createElement(Btn, { variant: 'primary', icon: 'send', full: true, disabled: !queue.length, onClick: send }, 'Send to ' + printer.model), React.createElement(Btn, { variant: 'ghost', icon: 'download', full: true, disabled: !queue.length, onClick: downloadPdf }, 'Download as PDF') ) ) ), printing && React.createElement(PrintProgress, printing) ); } function PrintProgress({ done, total }) { const pct = Math.round(done / total * 100); return React.createElement('div', { className: 'modal-backdrop' }, React.createElement('div', { className: 'print-modal' }, React.createElement('div', { className: 'print-spinner' }, React.createElement(Icon, { name: 'print', size: 30, color: 'var(--amber)' })), React.createElement('h3', null, 'Printing over TCP…'), React.createElement('p', { className: 'mono' }, fmtNum(done), ' / ', fmtNum(total), ' labels'), React.createElement('div', { className: 'prog-track' }, React.createElement('div', { className: 'prog-fill', style: { width: pct + '%' } })), React.createElement('span', { className: 'prog-pct' }, pct, '%') ) ); } // ---------------- HISTORY ---------------- function History({ history, products, addToQueue, go, toast }) { const [q, setQ] = useState(''); const [brand, setBrand] = useState('All'); const filtered = history.filter(h => (brand === 'All' || h.brand === brand) && (!q || (h.title + h.sku + h.batch).toLowerCase().includes(q.toLowerCase()))); const totalLabels = filtered.reduce((s, h) => s + h.qty, 0); const reprint = (h) => { const p = products.find(x => x.sku === h.sku) || { sku: h.sku, brand: h.brand, title: h.title, barcode: h.barcode, netwt: h.netwt, mrp: h.mrp, category: 'Other' }; addToQueue({ ...p, qty: h.qty, mfg: h.mfg, exp: h.exp, batch: h.batch }); go('print'); toast('Loaded batch for re-print'); }; return React.createElement('div', { className: 'screen' }, React.createElement('div', { className: 'screen-head' }, React.createElement('div', null, React.createElement('h1', null, 'Print History'), React.createElement('p', { className: 'screen-sub' }, fmtNum(filtered.length), ' batches · ', fmtNum(totalLabels), ' labels') ), React.createElement(Btn, { variant: 'ghost', icon: 'download', onClick: () => exportCsv(filtered, toast) }, 'Export CSV') ), React.createElement('div', { className: 'toolbar' }, React.createElement('div', { className: 'search' }, React.createElement(Icon, { name: 'search', size: 17, color: 'var(--txt-mut)' }), React.createElement('input', { value: q, onChange: e => setQ(e.target.value), placeholder: 'Search batch, SKU, product…' }) ), React.createElement('div', { className: 'seg' }, ['All', 'LUXURIATE', 'VITMINVEDA'].map(b => React.createElement('button', { key: b, className: 'seg-btn' + (brand === b ? ' on' : ''), onClick: () => setBrand(b) }, b !== 'All' && React.createElement(BrandDot, { brand: b }), b === 'All' ? 'All' : b === 'LUXURIATE' ? 'LUX' : 'VMV')) ) ), React.createElement('div', { className: 'panel table-panel' }, React.createElement('div', { className: 'table hist' }, React.createElement('div', { className: 'tr th' }, React.createElement('span', null, 'When'), React.createElement('span', null, 'Product'), React.createElement('span', { className: 'mono-h' }, 'Batch'), React.createElement('span', { className: 'mono-h' }, 'MFG / EXP'), React.createElement('span', null, 'Qty'), React.createElement('span', null, 'Operator'), React.createElement('span', null, 'Status'), React.createElement('span', null, '') ), filtered.length === 0 && React.createElement('div', { className: 'empty' }, React.createElement(Icon, { name: 'history', size: 28, color: 'var(--txt-mut)' }), React.createElement('p', null, 'No print history yet')), filtered.map(h => React.createElement('div', { key: h.id, className: 'tr' }, React.createElement('span', { className: 'h-when' }, React.createElement('b', null, h.today ? 'Today' : h.date), React.createElement('em', null, h.time)), React.createElement('span', { className: 'h-prod' }, React.createElement(BrandDot, { brand: h.brand, size: 9 }), React.createElement('div', null, React.createElement('b', null, (h.title || '').replace(h.brand, '').trim()), React.createElement('i', { className: 'mono' }, h.sku))), React.createElement('span', { className: 'mono' }, h.batch), React.createElement('span', { className: 'mono' }, h.mfg, ' → ', h.exp), React.createElement('span', null, React.createElement('b', { className: 'qty-pill' }, '×', h.qty)), React.createElement('span', { className: 'h-op' }, h.operator), React.createElement('span', null, h.status === 'pdf' ? React.createElement(Badge, { tone: 'neutral' }, React.createElement(Icon, { name: 'download', size: 11, stroke: 2.5 }), 'PDF') : h.status === 'failed' ? React.createElement(Badge, { tone: 'amber' }, React.createElement(Icon, { name: 'alert', size: 11, stroke: 2.5 }), 'Failed') : React.createElement(Badge, { tone: 'ok' }, React.createElement(Icon, { name: 'check', size: 11, stroke: 3 }), 'Printed')), React.createElement('span', null, React.createElement('button', { className: 'reprint-btn', onClick: () => reprint(h) }, React.createElement(Icon, { name: 'refresh', size: 13, stroke: 2 }), 'Re-print')) )) ) ) ); } function exportCsv(rows, toast) { const head = ['Date', 'Time', 'Brand', 'SKU', 'Product', 'Barcode', 'Batch', 'MFG', 'EXP', 'Qty', 'Operator', 'Status']; const lines = [head.join(',')]; rows.forEach(h => lines.push([h.date, h.time, h.brand, h.sku, '"' + (h.title || '').replace(/"/g, '""') + '"', h.barcode, h.batch, h.mfg, h.exp, h.qty, '"' + h.operator + '"', h.status].join(','))); const blob = new Blob([lines.join('\n')], { type: 'text/csv' }); const a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = 'print-history.csv'; a.click(); toast('Exported print-history.csv'); } // ---------------- SETTINGS ---------------- function Settings({ printer, onSaveSettings, onTestPrinter, onDetect, onTestPrint, users, user, toast }) { const [p, setP] = useState(printer); const [testing, setTesting] = useState(false); const [detecting, setDetecting] = useState(false); const [result, setResult] = useState(null); // {ok, message} const set = (k, v) => setP(o => ({ ...o, [k]: v })); const save = async () => { try { await onSaveSettings(p); toast('Settings saved'); } catch (e) { toast(e.message, 'err'); } }; const test = async () => { setTesting(true); setResult(null); try { const r = await onTestPrinter(p); setP(o => ({ ...o, connected: r.connected })); setResult({ ok: r.connected, message: r.message }); } catch (e) { setResult({ ok: false, message: e.message }); } setTesting(false); }; const detect = async () => { setDetecting(true); setResult(null); try { const r = await onDetect(p); if (r.found && r.found.length) { if (p.mode === 'network' && r.found[0].ip) { setP(o => ({ ...o, ip: r.found[0].ip, port: String(r.found[0].port) })); setResult({ ok: true, message: 'Found a printer at ' + r.found[0].ip + ' — saved. Now run Test connection.' }); } else if (p.mode === 'usb' && r.found[0].share) { setP(o => ({ ...o, share: r.found[0].share })); setResult({ ok: true, message: 'Found a Windows shared printer at ' + r.found[0].share + ' — saved. Now run Test connection.' }); } else { setResult({ ok: false, message: 'Auto-detect returned no usable printer information.' }); } } else { const scans = r.scanned || 0; setResult({ ok: false, message: p.mode === 'network' ? 'No printer found on the network (' + scans + ' addresses scanned). Enter the IP manually.' : 'No shared printer found automatically. Enter the share path manually or install and share the TSC printer.' }); } } catch (e) { setResult({ ok: false, message: e.message }); } setDetecting(false); }; const testprint = async () => { try { const r = await onTestPrint(); toast(r.message || 'Test label sent'); } catch (e) { toast(e.message, 'err'); } }; const sampleCur = { brand: 'LUXURIATE', title: 'LUXURIATE Aloe Vera 100gm', mrp: '₹749', netwt: '100gm', batch: '34011542', barcode: '8989238877813', mfg: '05/2026', exp: '04/2028', sku: 'LU-ALGL_100GM' }; const sample = window.buildTSPL({ product: sampleCur, mfg: '05/2026', exp: '04/2028', batch: '34011542', qty: 50, density: p.density, speed: p.speed, labelW: p.labelW, labelH: p.labelH, gap: p.gap }); return React.createElement('div', { className: 'screen' }, React.createElement('div', { className: 'screen-head' }, React.createElement('div', null, React.createElement('h1', null, 'Settings'), React.createElement('p', { className: 'screen-sub' }, 'Printer, labels & users')), React.createElement(Btn, { variant: 'primary', icon: 'check', onClick: save }, 'Save settings') ), React.createElement('div', { className: 'settings-grid' }, // --- Printer connection wizard --- React.createElement('div', { className: 'panel set-panel' }, React.createElement('div', { className: 'panel-head' }, React.createElement('h3', null, 'Printer connection'), React.createElement('span', { className: 'conn-pill ' + (p.connected ? 'on' : 'off') }, React.createElement('span', { className: 'pulse-dot ' + (p.connected ? 'live' : 'off') }), p.connected ? 'Connected' : 'Not tested')), React.createElement(Field, { label: 'Connection type' }, React.createElement('div', { className: 'seg', style: { alignSelf: 'flex-start' } }, [['network', 'Network (TCP) · no driver'], ['usb', 'USB (shared printer)']].map(([m, lbl]) => React.createElement('button', { key: m, className: 'seg-btn' + (p.mode === m ? ' on' : ''), onClick: () => { set('mode', m); setResult(null); set('connected', false); } }, lbl)) ) ), React.createElement(Field, { label: 'Printer model' }, React.createElement('select', { className: 'select', value: p.model, onChange: e => set('model', e.target.value) }, ['TSC TE244', 'TSC TE344', 'TSC TTP-244 Pro', 'TSC TDP-225', 'TSC DA210', 'TSC TX200'].map(m => React.createElement('option', { key: m }, m)))), p.mode === 'network' ? React.createElement(React.Fragment, null, React.createElement('div', { className: 'frow' }, React.createElement(Field, { label: 'Printer IP address' }, React.createElement('input', { className: 'input mono', value: p.ip, onChange: e => set('ip', e.target.value), placeholder: '192.168.1.50' })), React.createElement(Field, { label: 'Port' }, React.createElement('input', { className: 'input mono', value: p.port, onChange: e => set('port', e.target.value.replace(/\D/g, '')), placeholder: '9100' })) ), React.createElement('div', { className: 'btn-row' }, React.createElement(Btn, { variant: 'ghost', size: 'sm', icon: detecting ? 'refresh' : 'signal', onClick: detect, disabled: detecting }, detecting ? 'Scanning…' : 'Auto-detect'), React.createElement(Btn, { size: 'sm', icon: testing ? 'refresh' : 'signal', onClick: test, disabled: testing }, testing ? 'Testing…' : 'Test connection'), React.createElement(Btn, { size: 'sm', icon: 'print', onClick: testprint }, 'Test print') ) ) : React.createElement(React.Fragment, null, React.createElement(Field, { label: 'Windows shared-printer path', hint: 'Install the TSC driver. Auto-detect can attempt to locate a shared printer path automatically.' }, React.createElement('input', { className: 'input mono', value: p.share, onChange: e => set('share', e.target.value), placeholder: '\\\\localhost\\TSC' })), React.createElement('div', { className: 'btn-row' }, React.createElement(Btn, { variant: 'ghost', size: 'sm', icon: detecting ? 'refresh' : 'signal', onClick: detect, disabled: detecting }, detecting ? 'Detecting…' : 'Auto-detect'), React.createElement(Btn, { size: 'sm', icon: testing ? 'refresh' : 'signal', onClick: test, disabled: testing }, testing ? 'Testing…' : 'Test connection'), React.createElement(Btn, { size: 'sm', icon: 'print', onClick: testprint }, 'Test print') ) ), result && React.createElement('div', { className: 'test-result ' + (result.ok ? 'ok' : 'bad') }, React.createElement(Icon, { name: result.ok ? 'check' : 'alert', size: 15, stroke: 2.2 }), React.createElement('span', null, result.message)), React.createElement('div', { className: 'hint-box' }, React.createElement('b', null, 'Tip: '), p.mode === 'network' ? 'Network mode needs NO driver. Give the TSC printer a static IP (printer menu or its config tool), plug it into your router/switch, then Auto-detect or enter the IP.' : 'USB mode prints through a Windows shared printer. Install the TSC driver once, then use Auto-detect or enter a shared path like \\localhost\\TSC.') ), // --- Label & media --- React.createElement('div', { className: 'panel set-panel' }, React.createElement('div', { className: 'panel-head' }, React.createElement('h3', null, 'Label & media')), React.createElement('div', { className: 'frow' }, React.createElement(Field, { label: 'Width (mm)' }, React.createElement('input', { className: 'input mono', value: p.labelW, onChange: e => set('labelW', +e.target.value || 0) })), React.createElement(Field, { label: 'Height (mm)' }, React.createElement('input', { className: 'input mono', value: p.labelH, onChange: e => set('labelH', +e.target.value || 0) })), React.createElement(Field, { label: 'Gap (mm)' }, React.createElement('input', { className: 'input mono', value: p.gap, onChange: e => set('gap', +e.target.value || 0) })) ), React.createElement('div', { className: 'label-stage sm', style: { marginTop: 4 } }, React.createElement(window.ThermalLabel, { product: sampleCur, labelW: p.labelW, labelH: p.labelH, scale: Math.min(8, Math.max(4, 280 / p.labelW)) })), React.createElement(Field, { label: 'Darkness / density · ' + p.density }, React.createElement('input', { type: 'range', min: 1, max: 15, value: p.density, onChange: e => set('density', +e.target.value), className: 'range' })), React.createElement(Field, { label: 'Print speed (ips) · ' + p.speed }, React.createElement('input', { type: 'range', min: 1, max: 8, value: p.speed, onChange: e => set('speed', +e.target.value), className: 'range' })) ), // --- Sample TSPL --- React.createElement('div', { className: 'panel set-panel tspl-set' }, React.createElement('div', { className: 'panel-head' }, React.createElement('h3', null, 'Sample TSPL command'), React.createElement('button', { className: 'link-btn', onClick: () => { navigator.clipboard && navigator.clipboard.writeText(sample); toast('Copied'); } }, React.createElement(Icon, { name: 'copy', size: 13 }), 'Copy')), React.createElement('pre', { className: 'tspl' }, sample) ), // --- Users --- React.createElement('div', { className: 'panel set-panel' }, React.createElement('div', { className: 'panel-head' }, React.createElement('h3', null, 'Users & roles'), user.role === 'admin' && React.createElement('button', { className: 'link-btn', onClick: () => toast('Add users in the database — see README', 'info') }, React.createElement(Icon, { name: 'plus', size: 13 }), 'Invite')), React.createElement('div', { className: 'user-list' }, users.map(u => React.createElement('div', { key: u.email, className: 'user-row' }, React.createElement('div', { className: 'avatar', style: { background: u.color } }, u.name.split(' ').map(s => s[0]).join('').slice(0, 2)), React.createElement('div', { className: 'user-main' }, React.createElement('b', null, u.name), React.createElement('span', { className: 'mono' }, u.email)), React.createElement(Badge, { tone: u.role === 'admin' ? 'amber' : 'neutral' }, u.role) )) ) ) ) ); } Object.assign(window, { PrintStudio, History, Settings }); })();