155 lines
5.4 KiB
JavaScript
155 lines
5.4 KiB
JavaScript
/* 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, '"')
|
|
.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 '<span class="badge" style="background:' + escape(status.warna || '#64748b') + '">' +
|
|
escape(status.nama) + '</span>';
|
|
}
|
|
|
|
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 =
|
|
'<div class="modal" role="dialog" aria-modal="true">' +
|
|
'<div class="modal-head"><h3></h3><button class="modal-x" type="button" aria-label="Tutup">×</button></div>' +
|
|
'<div class="modal-body"></div>' +
|
|
'<div class="modal-foot"></div>' +
|
|
'</div>';
|
|
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);
|