/* ui.js — Helper tampilan: escape, format, badge, modal, toast, confirm */ (function (global) { 'use strict'; function escape(v) { if (v == null) return ''; return String(v) .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); } function formatDate(iso) { return global.WA ? global.WA.tanggalPanjang(iso) : (iso || ''); } function formatDateTime(iso) { if (!iso) return ''; var d = new Date(iso); if (isNaN(d.getTime())) return ''; var jam = String(d.getHours()).padStart(2, '0') + ':' + String(d.getMinutes()).padStart(2, '0'); return formatDate(iso) + ' ' + jam; } function formatBytes(bytes) { if (!bytes && bytes !== 0) return ''; var units = ['B', 'KB', 'MB', 'GB']; var i = 0; var n = bytes; while (n >= 1024 && i < units.length - 1) { n /= 1024; i++; } return (i === 0 ? n : n.toFixed(1)) + ' ' + units[i]; } function badge(status) { if (!status) return ''; return '' + escape(status.nama) + ''; } function escapeAttr(v) { return escape(v); } /* ---------- Toast ---------- */ function toastBox() { var box = document.getElementById('toastBox'); if (!box) { box = document.createElement('div'); box.id = 'toastBox'; document.body.appendChild(box); } return box; } function toast(message, type) { var el = document.createElement('div'); el.className = 'toast' + (type === 'ok' ? ' ok' : type === 'err' ? ' err' : ''); el.textContent = message; toastBox().appendChild(el); setTimeout(function () { el.style.transition = 'opacity .25s'; el.style.opacity = '0'; setTimeout(function () { el.remove(); }, 260); }, 3200); } /* ---------- Modal ---------- */ var modalEl = null; function ensureModal() { if (modalEl) return modalEl; modalEl = document.createElement('div'); modalEl.className = 'modal-overlay'; modalEl.innerHTML = ''; document.body.appendChild(modalEl); modalEl.addEventListener('click', function (e) { if (e.target === modalEl) closeModal(); }); modalEl.querySelector('.modal-x').addEventListener('click', closeModal); document.addEventListener('keydown', function (e) { if (e.key === 'Escape' && modalEl && modalEl.parentNode) closeModal(); }); return modalEl; } function openModal(opts) { var el = ensureModal(); opts = opts || {}; var modal = el.querySelector('.modal'); modal.className = 'modal' + (opts.size === 'lg' ? ' lg' : ''); el.querySelector('.modal-head h3').textContent = opts.title || ''; el.querySelector('.modal-body').innerHTML = opts.body || ''; el.querySelector('.modal-foot').innerHTML = opts.footer || ''; el.querySelector('.modal-foot').style.display = opts.footer ? 'flex' : 'none'; el.style.display = 'flex'; if (typeof opts.onMount === 'function') opts.onMount(el); var firstInput = el.querySelector('.modal-body input, .modal-body select, .modal-body textarea'); if (firstInput) setTimeout(function () { firstInput.focus(); }, 50); return el; } function closeModal() { if (modalEl) modalEl.style.display = 'none'; } function isModalOpen() { return !!(modalEl && modalEl.style.display !== 'none'); } function confirmDialog(message, title) { return global.confirm((title ? title + '\n\n' : '') + message); } /* ---------- Banner peringatan ---------- */ function banner(message, type) { if (!document.body) return; var host = document.querySelector('main.container') || document.body; var existing = document.getElementById('appBanner'); if (existing) existing.remove(); var el = document.createElement('div'); el.id = 'appBanner'; var base = 'padding:10px 14px;border-radius:10px;margin-bottom:14px;font-size:13px;line-height:1.5;'; if (type === 'warn') { el.style.cssText = base + 'background:#fffbeb;border:1px solid #fde68a;color:#92400e;'; } else { el.style.cssText = base + 'background:#fef2f2;border:1px solid #fecaca;color:#991b1b;'; } el.textContent = message; host.insertBefore(el, host.firstChild); } // Tampilkan error apa pun sebagai banner, bukan halaman kosong. global.addEventListener('error', function (e) { var msg = (e && (e.message || (e.error && e.error.message))) || 'Kesalahan tidak diketahui'; try { banner('Terjadi kesalahan: ' + msg, 'err'); } catch (_) { /* abaikan */ } }); global.addEventListener('unhandledrejection', function (e) { var r = e && e.reason; var msg = (r && (r.message || r)) || 'Operasi gagal'; try { banner('Terjadi kesalahan: ' + msg, 'err'); } catch (_) { /* abaikan */ } }); global.UI = { escape: escape, escapeAttr: escapeAttr, formatDate: formatDate, formatDateTime: formatDateTime, formatBytes: formatBytes, badge: badge, toast: toast, banner: banner, openModal: openModal, closeModal: closeModal, isModalOpen: isModalOpen, confirmDialog: confirmDialog }; })(window);