/* ============================================================ page.js — Shared helpers for pages running inside iframes ============================================================ */ // Compute app root from current URL — works for pages/ and pages/reports/ const _pIdx = window.location.pathname.split('/').indexOf('pages'); const BASE_URL = _pIdx > 0 ? window.location.pathname.split('/').slice(0, _pIdx).join('/') : ''; // Bridge to parent frame — only used for toast + nav const bridge = window.parent?.AppBridge || { navigate: (l, u) => window.location.href = u, toast: (m, t) => alert(m), getToken: () => localStorage.getItem('pa_token') || '', getUser: () => JSON.parse(localStorage.getItem('pa_user') || '{}'), }; const token = () => bridge.getToken(); const user = () => bridge.getUser(); // Toast — shows in parent frame (has the toast container) const toast = (msg, type = 'info') => bridge.toast(msg, type); // Navigate — opens new tab in parent shell const nav = (label, url) => bridge.navigate(label, BASE_URL + '/' + url); // ── LOCAL Modal (renders inside iframe so onclick refs work) ── const modal = (title, bodyHtml, footerHtml = '') => { closeModal(); // remove any existing // Inject minimal modal CSS if not already present if (!document.getElementById('_modal-styles')) { const s = document.createElement('style'); s.id = '_modal-styles'; s.textContent = ` #_modal-overlay{position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:1000;display:flex;align-items:center;justify-content:center;padding:20px;backdrop-filter:blur(3px);animation:_mfade .18s ease} @keyframes _mfade{from{opacity:0}to{opacity:1}} #_modal-box{background:var(--bg-card,#fff);border:1px solid var(--border,#e5e7eb);border-radius:10px;width:100%;max-width:640px;max-height:90vh;overflow-y:auto;box-shadow:0 8px 40px rgba(0,0,0,.18);animation:_mup .2s ease} @keyframes _mup{from{transform:translateY(16px);opacity:0}to{transform:translateY(0);opacity:1}} #_modal-header{display:flex;align-items:center;justify-content:space-between;padding:16px 20px;border-bottom:1px solid var(--border,#e5e7eb)} #_modal-title{font-size:15px;font-weight:600;color:var(--text-primary,#1a1d23)} #_modal-close{width:28px;height:28px;background:var(--bg-raised,#f8f9fa);border:1px solid var(--border,#e5e7eb);border-radius:50%;cursor:pointer;display:grid;place-items:center;color:var(--text-secondary,#5c636e);font-size:14px;flex-shrink:0;transition:.15s} #_modal-close:hover{background:#fee2e2;color:#dc2626;border-color:#fca5a5} #_modal-body{padding:20px} #_modal-footer{display:flex;gap:10px;justify-content:flex-end;padding:14px 20px;border-top:1px solid var(--border,#e5e7eb)} `; document.head.appendChild(s); } const overlay = document.createElement('div'); overlay.id = '_modal-overlay'; overlay.innerHTML = `
${msg}