/* utils.js — Global helper utilities for ShipSeg */ function htmlEsc(str) { return String(str ?? '') .replace(/&/g,'&') .replace(//g,'>') .replace(/"/g,'"') .replace(/'/g,'''); } function fmtDate(iso) { if (!iso) return '—'; try { let dateStr = iso; if (typeof dateStr === 'string' && !dateStr.endsWith('Z') && !dateStr.includes('+') && !/[-+]\d{2}:?\d{2}$/.test(dateStr)) { dateStr = dateStr + 'Z'; } return new Date(dateStr).toLocaleDateString('en-GB', { day:'numeric', month:'short', year:'2-digit', hour:'2-digit', minute:'2-digit' }); } catch { return iso; } } function catLabel(cat) { return { tonnage:'Tonnage', cargo_vc:'Cargo VC', cargo_tc:'Cargo TC', unknown:'Unknown' }[cat] || cat; } function debounce(fn, ms) { let t; return function(...args) { clearTimeout(t); t = setTimeout(() => fn.apply(this, args), ms); }; } // ── Toast ───────────────────────────────────────────────────────── const Toast = { show(msg, type = 'info', duration = 3500) { const icons = { success: '✓', error: '✕', info: 'ℹ', warning: '⚠' }; const el = document.createElement('div'); el.className = `toast ${type}`; el.innerHTML = `${htmlEsc(msg)}`; const container = document.getElementById('toast-container'); container.appendChild(el); setTimeout(() => { el.style.opacity = '0'; el.style.transition = 'opacity 0.3s'; setTimeout(() => el.remove(), 300); }, duration); }, }; // ── Shared edit modal ───────────────────────────────────────────── function showEditModal(title, fields, onSave) { const existing = document.getElementById('edit-modal-overlay'); if (existing) existing.remove(); const overlay = document.createElement('div'); overlay.id = 'edit-modal-overlay'; overlay.className = 'modal-overlay open'; overlay.innerHTML = `