/* @jsx React.createElement */ const { useState, useEffect, useRef, useMemo } = React; /* ============================================================ Yappari — PRODUK (CRUD) ============================================================ */ const CATEGORIES = ['Minuman', 'Makanan', 'Snack']; const CAT_COLOR = { Minuman: 'var(--st-maint)', Makanan: 'var(--accent)', Snack: 'var(--st-warn)' }; function ProductsView() { const Y = window.YAPPARI; const [products, setProducts] = useState(() => Y.PRODUCTS.map(p => ({ ...p, active: true, slots: Y.productSlots(p.id).join(', ') }))); const [query, setQuery] = useState(''); const [page, setPage] = useState(0); const [editing, setEditing] = useState(null); // product or 'new' const PER = 8; const filtered = products.filter(p => (p.name + ' ' + p.cat).toLowerCase().includes(query.toLowerCase())); const pages = Math.max(1, Math.ceil(filtered.length / PER)); const pageItems = filtered.slice(page * PER, page * PER + PER); useEffect(() => { if (page >= pages) setPage(0); }, [pages]); const save = (prod) => { if (prod.id) setProducts(ps => ps.map(p => p.id === prod.id ? prod : p)); else setProducts(ps => [{ ...prod, id: 'p' + Date.now() }, ...ps]); setEditing(null); }; const remove = (id) => setProducts(ps => ps.filter(p => p.id !== id)); const toggle = (id) => setProducts(ps => ps.map(p => p.id === id ? { ...p, active: !p.active } : p)); return React.createElement('div', { className: 'view' }, React.createElement('div', { className: 'card card-pad', style: { marginBottom: 'var(--sp-5)', display: 'flex', gap: 'var(--sp-4)', alignItems: 'center', flexWrap: 'wrap' } }, React.createElement('div', { style: { position: 'relative', flex: 1, minWidth: 220 } }, React.createElement(Icon, { name: 'search', size: 17, style: { position: 'absolute', left: 12, top: 11, color: 'var(--fg-3)' } }), React.createElement('input', { className: 'input', style: { paddingLeft: 38 }, placeholder: 'Cari produk atau kategori…', value: query, onChange: e => { setQuery(e.target.value); setPage(0); } }), ), React.createElement('button', { className: 'btn btn-ghost' }, React.createElement(Icon, { name: 'upload', size: 16 }), 'Import CSV'), React.createElement('button', { className: 'btn btn-primary btn-stamp', onClick: () => setEditing('new') }, React.createElement(Icon, { name: 'plus', size: 16 }), 'Tambah produk'), ), React.createElement(SectionCard, { title: 'Katalog produk', sub: filtered.length + ' produk', motif: true, bodyStyle: { overflowX: 'auto' } }, React.createElement('table', { className: 'tbl' }, React.createElement('thead', null, React.createElement('tr', null, React.createElement('th', null, 'Produk'), React.createElement('th', null, 'Kategori'), React.createElement('th', { style: { textAlign: 'right' } }, 'Harga'), React.createElement('th', null, 'Slot ter-assign'), React.createElement('th', { style: { textAlign: 'center' } }, 'Aktif'), React.createElement('th', { style: { textAlign: 'right', width: 100 } }, 'Aksi'), )), React.createElement('tbody', null, pageItems.map(p => React.createElement('tr', { key: p.id, style: { opacity: p.active ? 1 : 0.5 } }, React.createElement('td', null, React.createElement('div', { className: 'row gap-3' }, p.photo ? React.createElement('img', { src: p.photo, style: { width: 38, height: 38, borderRadius: 8, objectFit: 'cover' } }) : React.createElement(ProductGlyph, { glyph: p.glyph, color: p.color, size: 38 }), React.createElement('span', { className: 'strong' }, p.name))), React.createElement('td', null, React.createElement('span', { className: 'badge', style: { color: CAT_COLOR[p.cat], background: 'var(--bg-sunken)' } }, React.createElement('span', { className: 'b-dot' }), p.cat)), React.createElement('td', { className: 'num strong', style: { textAlign: 'right' } }, fmtRp(p.price)), React.createElement('td', null, React.createElement('span', { className: 'mono', style: { fontSize: 12.5, color: 'var(--fg-3)' } }, p.slots || '—')), React.createElement('td', { style: { textAlign: 'center' } }, React.createElement('div', { style: { display: 'inline-flex' } }, React.createElement('button', { className: 'toggle ' + (p.active ? 'on' : ''), onClick: () => toggle(p.id) }))), React.createElement('td', { style: { textAlign: 'right' } }, React.createElement('div', { className: 'row gap-2', style: { justifyContent: 'flex-end' } }, React.createElement('button', { className: 'icon-btn', style: { width: 32, height: 32 }, onClick: () => setEditing(p) }, React.createElement(Icon, { name: 'pencil', size: 15 })), React.createElement('button', { className: 'icon-btn', style: { width: 32, height: 32, color: 'var(--st-error)' }, onClick: () => remove(p.id) }, React.createElement(Icon, { name: 'trash', size: 15 })), )), )), ), ), ), React.createElement('div', { className: 'row', style: { justifyContent: 'space-between', marginTop: 'var(--sp-4)' } }, React.createElement('span', { className: 'muted', style: { fontSize: 13 } }, 'Menampilkan ' + (pageItems.length ? page * PER + 1 : 0) + '–' + (page * PER + pageItems.length) + ' dari ' + filtered.length), React.createElement('div', { className: 'row gap-2' }, React.createElement('button', { className: 'icon-btn', disabled: page === 0, onClick: () => setPage(p => Math.max(0, p - 1)) }, React.createElement(Icon, { name: 'chevronLeft', size: 16 })), Array.from({ length: pages }).map((_, i) => React.createElement('button', { key: i, className: 'chip ' + (i === page ? 'active' : ''), style: { minWidth: 34, justifyContent: 'center' }, onClick: () => setPage(i) }, i + 1)), React.createElement('button', { className: 'icon-btn', disabled: page >= pages - 1, onClick: () => setPage(p => Math.min(pages - 1, p + 1)) }, React.createElement(Icon, { name: 'chevronRight', size: 16 })), ), ), editing && React.createElement(ProductModal, { product: editing === 'new' ? null : editing, onSave: save, onClose: () => setEditing(null) }), ); } function ProductModal({ product, onSave, onClose }) { const [form, setForm] = useState(product || { name: '', cat: 'Minuman', price: 5000, glyph: 'bottle', color: '#00A0E0', slots: '', active: true, photo: null }); const set = (k, v) => setForm(f => ({ ...f, [k]: v })); const onFile = (e) => { const f = e.target.files[0]; if (f) set('photo', URL.createObjectURL(f)); }; const valid = form.name.trim() && form.price > 0; return React.createElement(Modal, { eyebrow: product ? 'Edit produk' : 'Produk baru', title: product ? product.name : 'Tambah produk', lg: true, onClose, footer: React.createElement('div', { className: 'row gap-3' }, React.createElement('button', { className: 'btn btn-ghost', onClick: onClose }, 'Batal'), React.createElement('button', { className: 'btn btn-primary', disabled: !valid, onClick: () => onSave(form) }, React.createElement(Icon, { name: 'check', size: 16 }), 'Simpan')), }, React.createElement('div', { style: { display: 'grid', gridTemplateColumns: '120px 1fr', gap: 'var(--sp-5)' } }, React.createElement('div', null, React.createElement('label', { className: 'field-label' }, 'Foto'), React.createElement('label', { style: { display: 'block', cursor: 'pointer' } }, form.photo ? React.createElement('img', { src: form.photo, style: { width: 120, height: 120, borderRadius: 12, objectFit: 'cover' } }) : React.createElement('div', { style: { width: 120, height: 120, borderRadius: 12, border: '2px dashed var(--border)', display: 'grid', placeItems: 'center', color: 'var(--fg-3)', background: 'var(--bg-sunken)' } }, React.createElement('div', { className: 'col gap-1', style: { alignItems: 'center' } }, React.createElement(ProductGlyph, { glyph: form.glyph, color: form.color, size: 56 }), React.createElement('span', { style: { fontSize: 11 } }, 'Unggah foto'))), React.createElement('input', { type: 'file', accept: 'image/*', onChange: onFile, style: { display: 'none' } })), ), React.createElement('div', { className: 'col gap-4' }, React.createElement('div', null, React.createElement('label', { className: 'field-label' }, 'Nama produk'), React.createElement('input', { className: 'input', value: form.name, onChange: e => set('name', e.target.value), placeholder: 'mis. Aqua 600ml', autoFocus: true })), React.createElement('div', { style: { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 'var(--sp-4)' } }, React.createElement('div', null, React.createElement('label', { className: 'field-label' }, 'Kategori'), React.createElement('select', { className: 'select-native', value: form.cat, onChange: e => set('cat', e.target.value) }, CATEGORIES.map(c => React.createElement('option', { key: c, value: c }, c)))), React.createElement('div', null, React.createElement('label', { className: 'field-label' }, 'Harga (Rp)'), React.createElement('input', { className: 'input', type: 'number', step: 500, value: form.price, onChange: e => set('price', +e.target.value) }))), React.createElement('div', null, React.createElement('label', { className: 'field-label' }, 'Assign slot (pisahkan koma)'), React.createElement('input', { className: 'input mono', value: form.slots, onChange: e => set('slots', e.target.value), placeholder: 'A1, A2, B5' })), ), ), ); } Object.assign(window, { ProductsView, ProductModal, CATEGORIES, CAT_COLOR });