/** * AxoRep — relatórios de compra: coleta por hover (+/−) + revisão/aprovação. */ (function (global) { 'use strict'; const LS_KEY_LEGACY = 'alm-rep-reports'; const LS_SIDEBAR_KEY = 'alm-rep-sidebar-collapsed'; let deps = null; let currentUser = null; let initWired = false; let store = { activeId: null, reports: [] }; let ui = { view: 'work', segFilter: 'all', tableSearch: '', sidebarSearch: '', saveMode: 'local', serverAvailable: false, sidebarCollapsed: false, collecting: false, lastWorkActiveId: null, archivePreviewId: null, tableSortK: null, tableSortAsc: true, }; let pickPending = null; let deletePendingId = null; let storeRev = 0; let serverTimer = null; let saveInFlight = false; let saveQueued = false; let saveStatus = 'idle'; const COLS = 10; const ICON_ARCHIVE = ''; const ICON_TRASH = ''; const ICON_RESTORE = ''; function val(k) { const v = deps[k]; return typeof v === 'function' ? v() : v; } function esc(s) { return deps.escHtml(String(s ?? '')); } function attrEsc(s) { return String(s ?? '').replace(/&/g, '&').replace(/"/g, '"'); } function migrateItem(ri) { if (!ri.decision) { if (ri.status === 'ok') ri.decision = 'approved'; else if (ri.status === 'rejected') ri.decision = 'rejected'; else ri.decision = 'pending'; } delete ri.status; delete ri.alignMsg; delete ri.checkedAt; return ri; } function migrateReport(r) { if (!r.status) r.status = r.savedAt ? 'saved' : 'draft'; if (r.loaded === undefined) r.loaded = !!(r.items && r.items.length); for (const it of r.items || []) migrateItem(it); return r; } function migrateStore() { for (const r of store.reports) migrateReport(r); } function lsKey(user) { const u = (user || currentUser || '').trim().toLowerCase(); return u ? `alm-rep-reports-${u}` : LS_KEY_LEGACY; } function apiFetchOpts(extra) { const base = extra || {}; const headers = global.almAuthHeaders ? global.almAuthHeaders(base.headers || {}) : { ...(base.headers || {}) }; return { credentials: 'include', ...base, headers }; } function loadLocal(user) { const u = (user || currentUser || '').trim().toLowerCase(); if (!u) return; currentUser = u; try { const raw = localStorage.getItem(lsKey(u)); if (raw) { const parsed = JSON.parse(raw); if (parsed && Array.isArray(parsed.reports)) { store = { activeId: null, reports: parsed.reports }; migrateStore(); } } } catch (_) {} } function saveLocal() { if (!currentUser) return; try { localStorage.setItem( lsKey(currentUser), JSON.stringify({ activeId: null, reports: store.reports }) ); } catch (_) {} } function maybeImportLegacyLocal() { if (store.reports.length) return; try { const raw = localStorage.getItem(LS_KEY_LEGACY); if (!raw) return; const parsed = JSON.parse(raw); if (!parsed?.reports?.length) return; store.reports = parsed.reports; migrateStore(); localStorage.removeItem(LS_KEY_LEGACY); } catch (_) {} } function activeReport() { return store.reports.find((r) => r.id === store.activeId) || null; } function activeDraftReport() { const r = activeReport(); return r && r.status !== 'saved' ? r : null; } function isCollectPage() { const pg = val('curPage'); return pg === 'explorar' || pg === 'arvore' || pg === 'sugestao'; } function showReportHeader() { const pg = val('curPage'); return pg === 'explorar' || pg === 'arvore' || pg === 'sugestao' || pg === 'relatorio'; } function draftReportsSorted() { return [...store.reports.filter((r) => r.status !== 'saved')].sort((a, b) => { const ta = new Date(a.updatedAt || a.createdAt || 0).getTime(); const tb = new Date(b.updatedAt || b.createdAt || 0).getTime(); return tb - ta; }); } function resolveWorkActiveId() { const drafts = draftReportsSorted(); if (ui.lastWorkActiveId && drafts.some((r) => r.id === ui.lastWorkActiveId)) return ui.lastWorkActiveId; if (store.activeId && drafts.some((r) => r.id === store.activeId)) return store.activeId; return null; } function reportsForSidebar() { let list; if (ui.view === 'archive') list = store.reports.filter((r) => r.status === 'saved'); else list = draftReportsSorted(); const q = (ui.sidebarSearch || '').trim().toLowerCase(); if (q) list = list.filter((r) => (r.name || '').toLowerCase().includes(q)); return list; } function reportsForHeaderPicker() { return draftReportsSorted(); } function scopeSnapshot() { return { sel: deps.selectionToParam(val('curSelection')), cob: deps.filterParamFromSet(deps.cobSel), sup: deps.filterParamFromSet(deps.supSel), rop: deps.filterParamFromSet(deps.ropSel), st: deps.filterParamFromSet(deps.stSel), rupt: deps.ruptSel.has('pipe') ? 'pipe' : deps.ruptSel.has('liq') ? 'liq' : deps.ruptSel.has('1') ? '1' : '', q: document.getElementById('busca')?.value?.trim() || '', sort: val('sortK'), asc: !!val('sortAsc'), }; } function candidateRows() { const out = []; for (const x of deps.getFilteredList()) { const s = deps.computeSugestao(x); if (s) out.push({ x, s }); } return out; } function candidateCount() { return candidateRows().length; } function findLive(p) { const f = deps.findItem(p); if (!f) return null; const x = f.it; return x._seg ? x : Object.assign({ _seg: f.seg }, x); } function projectedCob(x, orderQty) { const dem = deps.demandM(x) || x.dem || 0; if (dem <= 0) return null; const onh = (x.o || 0) + (x.po_qp || 0) + (x.pr_q || 0) + (orderQty || 0); return onh / dem; } function persistStore() { storeRev += 1; saveLocal(); scheduleServerSave(); } function touchReport(r) { r.updatedAt = new Date().toISOString(); persistStore(); } function wireTitleInput(el, r) { if (!el) return; let nameTimer = null; const applyName = (raw) => { r.name = String(raw || '').trim() || r.name; clearTimeout(nameTimer); nameTimer = setTimeout(() => { touchReport(r); renderSidebar(); renderHeaderBanner(); }, 400); }; el.addEventListener('input', (e) => applyName(e.target.value)); el.addEventListener('change', (e) => applyName(e.target.value)); } function newId() { return 'r' + Date.now().toString(36) + Math.random().toString(36).slice(2, 7); } function createReport(name, opts = {}) { const enterCollect = opts.enterCollect !== false; const r = { id: newId(), name: (name || '').trim() || defaultReportName(), createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), status: 'draft', scope: {}, items: [], loaded: false, }; store.reports.unshift(r); store.activeId = r.id; ui.lastWorkActiveId = r.id; ui.archivePreviewId = null; ui.view = 'work'; persistStore(); if (enterCollect) enterCollectMode(r.id); deps.syncUrl(); render(); return r; } function performDeleteReport(id) { store.reports = store.reports.filter((r) => r.id !== id); if (ui.archivePreviewId === id) ui.archivePreviewId = null; if (ui.lastWorkActiveId === id) ui.lastWorkActiveId = null; if (store.activeId === id) { store.activeId = ui.view === 'work' ? resolveWorkActiveId() : null; if (ui.collecting) exitCollectMode(); } persistStore(); deps.syncUrl(); render(); } function openDeleteModal(id) { const r = store.reports.find((x) => x.id === id); if (!r) return; deletePendingId = id; const m = document.getElementById('rep-delete-modal'); const msg = document.getElementById('rep-delete-msg'); if (msg) msg.textContent = `Excluir relatório «${r.name}»? Esta ação não pode ser desfeita.`; if (m) { m.classList.add('open'); m.setAttribute('aria-hidden', 'false'); document.getElementById('rep-delete-cancel')?.focus(); } } function closeDeleteModal() { deletePendingId = null; const m = document.getElementById('rep-delete-modal'); if (!m) return; m.classList.remove('open'); m.setAttribute('aria-hidden', 'true'); } function confirmDeleteReport() { const id = deletePendingId; if (!id) return; closeDeleteModal(); performDeleteReport(id); } function deleteReport(id) { openDeleteModal(id); } function archiveReport(id) { const r = store.reports.find((x) => x.id === id); if (!r || r.status === 'saved') return; if (!r.items.length) { alert('Adicione itens antes de arquivar.'); return; } r.status = 'saved'; r.savedAt = new Date().toISOString(); touchReport(r); if (store.activeId === id) { if (ui.lastWorkActiveId === id) ui.lastWorkActiveId = null; store.activeId = null; if (ui.collecting) exitCollectMode(); } ui.view = 'archive'; ui.archivePreviewId = id; persistStore(); deps.syncUrl(); render(); } function restoreReport(id) { const r = store.reports.find((x) => x.id === id); if (!r || r.status !== 'saved') return; r.status = 'draft'; delete r.savedAt; touchReport(r); store.activeId = id; ui.lastWorkActiveId = id; ui.archivePreviewId = null; ui.view = 'work'; persistStore(); deps.syncUrl(); render(); } function selectReport(id) { const r = store.reports.find((x) => x.id === id); if (!r) return; if (ui.view === 'archive' || r.status === 'saved') { ui.archivePreviewId = id; render(); return; } store.activeId = id; ui.lastWorkActiveId = id; ui.archivePreviewId = null; persistStore(); deps.syncUrl?.(); if (isCollectPage()) { enterCollectMode(id); return; } renderHeaderBanner(); refreshPickCells(); render(); } function setView(view) { ui.view = view === 'archive' ? 'archive' : 'work'; if (ui.view === 'archive') { const cur = activeReport(); if (cur && cur.status !== 'saved') ui.lastWorkActiveId = store.activeId; store.activeId = null; if (ui.collecting) exitCollectMode(); if ( ui.archivePreviewId && !store.reports.some((x) => x.id === ui.archivePreviewId && x.status === 'saved') ) { ui.archivePreviewId = null; } } else { store.activeId = resolveWorkActiveId(); ui.archivePreviewId = null; } persistStore(); deps.syncUrl(); render(); } function getView() { return ui.view; } function loadSuggestions() { const r = activeReport(); if (!r) return; const rows = candidateRows(); const existing = new Set(r.items.map((i) => i.p)); for (const { x, s } of rows) { if (existing.has(x.p)) continue; r.items.push({ p: x.p, seg: x._seg || x.seg || '', qty: s.qty, qtySug: s.qty, decision: 'pending', note: '', }); existing.add(x.p); } r.loaded = true; r.scope = scopeSnapshot(); touchReport(r); render(); } function reportRowEntries(r) { const out = []; for (const ri of r.items || []) { const x = findLive(ri.p); if (!x) continue; const s = deps.computeSugestao(x) || null; out.push({ x, s, ri }); } return out; } function isCollecting() { return !!ui.collecting; } function getActiveReport() { return activeReport(); } function isInReport(part) { const r = activeReport(); if (!r) return false; const code = String(part || '').trim(); return (r.items || []).some((i) => i.p === code); } function enterCollectMode(reportId) { if (reportId) { store.activeId = reportId; ui.lastWorkActiveId = reportId; } ui.collecting = true; persistStore(); renderHeaderBanner(); renderSidebar(); refreshPickCells(); deps.syncUrl?.(); if (val('curPage') === 'relatorio') goPage('explorar'); } function exitCollectMode() { ui.collecting = false; renderHeaderBanner(); renderSidebar(); refreshPickCells(); } let headerReportMenuOpen = false; function setHeaderReportMenuOpen(open) { headerReportMenuOpen = !!open; const btn = document.getElementById('header-report-name-btn'); const menu = document.getElementById('header-report-menu'); if (btn) btn.setAttribute('aria-expanded', headerReportMenuOpen ? 'true' : 'false'); if (menu) menu.hidden = !headerReportMenuOpen; } function renderHeaderReportMenu() { const menu = document.getElementById('header-report-menu'); if (!menu) return; const list = reportsForHeaderPicker(); if (!list.length) { menu.innerHTML = '
Nenhum rascunho em andamento.
'; return; } menu.innerHTML = list .map((rep) => { const on = rep.id === store.activeId ? ' on' : ''; const { secondary } = sidebarCardMeta(rep); return ``; }) .join(''); menu.querySelectorAll('[data-header-rep-id]').forEach((btn) => { btn.onclick = () => { selectReport(btn.dataset.headerRepId); setHeaderReportMenuOpen(false); }; }); } function renderHeaderBanner() { const wrap = document.getElementById('header-report-mode'); const nameEl = document.getElementById('header-report-name'); const nameBtn = document.getElementById('header-report-name-btn'); const metaEl = document.getElementById('header-report-meta'); const exitBtn = document.getElementById('header-report-exit'); const pill = document.getElementById('header-report-pill'); if (!wrap) return; if (!showReportHeader()) { wrap.hidden = true; setHeaderReportMenuOpen(false); return; } wrap.hidden = false; const draft = activeDraftReport(); const empty = !draft; if (nameBtn) nameBtn.classList.toggle('is-empty', empty); if (pill) pill.classList.toggle('is-collecting', !!ui.collecting); if (empty) { if (nameEl) nameEl.textContent = 'Nenhum selecionado'; if (metaEl) { metaEl.textContent = ''; metaEl.hidden = true; } if (exitBtn) exitBtn.hidden = true; } else { if (nameEl) nameEl.textContent = draft.name || '—'; const n = reportItemCount(draft); if (metaEl) { metaEl.textContent = n ? `${n} itens` : 'Vazio'; metaEl.hidden = false; } if (exitBtn) { exitBtn.hidden = false; if (ui.collecting) { exitBtn.textContent = 'Sair do modo'; exitBtn.title = 'Parar de adicionar itens'; } else { exitBtn.textContent = 'Coletar itens'; exitBtn.title = 'Ativar modo coleta neste relatório'; } } } renderHeaderReportMenu(); } function refreshPickCells(root) { const scope = root || document; const draft = activeDraftReport(); scope.querySelectorAll('tr[data-p]').forEach((tr) => { const p = tr.getAttribute('data-p'); if (!p) return; tr.classList.toggle('rep-in-report', !!draft && isInReport(p)); const cell = tr.querySelector('.rep-pick-cell'); if (!cell) return; const seg = tr.getAttribute('data-seg') || ''; cell.innerHTML = pickButtonInner(p, seg); }); } function pickButtonInner(p, seg) { if (!isCollectPage()) return ''; const pe = attrEsc(p); const se = attrEsc(seg); if (activeDraftReport() && isInReport(p)) { return ``; } return ``; } function pickCellHtml(p, seg) { return `${pickButtonInner(p, seg)}`; } function defaultReportName() { return ( 'Relatório · ' + new Date().toLocaleString('pt-BR', { day: '2-digit', month: 'short', hour: '2-digit', minute: '2-digit' }) ); } function addItem(part, seg) { const r = activeReport(); if (!r) return false; const code = String(part || '').trim(); if (!code || isInReport(code)) return false; const live = findLive(code); const x = live || { p: code, _seg: seg || '' }; const s = deps.computeSugestao(x); const qty = s ? s.qty : 0; r.items.push({ p: code, seg: seg || itemSeg(x) || '', qty, qtySug: qty, decision: 'pending', note: '', }); r.loaded = true; touchReport(r); renderHeaderBanner(); refreshPickCells(); if (val('curPage') === 'relatorio') render(); else { renderSidebar(); renderTableOnly(); } return true; } function removeItem(part) { const r = activeReport(); if (!r) return false; const code = String(part || '').trim(); const before = r.items.length; r.items = (r.items || []).filter((i) => i.p !== code); if (r.items.length === before) return false; touchReport(r); refreshPickCells(); if (val('curPage') === 'relatorio') render(); else { renderSidebar(); renderTableOnly(); } return true; } function draftsForPick() { return store.reports .filter((r) => r.status !== 'saved') .sort((a, b) => new Date(b.updatedAt || b.createdAt) - new Date(a.updatedAt || a.createdAt)); } function openPickModal(onPick) { pickPending = onPick || null; const m = document.getElementById('rep-pick-modal'); const list = document.getElementById('rep-pick-list'); if (!m || !list) { openNewModal(true, pickPending); return; } const drafts = draftsForPick(); if (!drafts.length) { openNewModal(true, pickPending); return; } list.innerHTML = drafts .map((r) => { const n = (r.items || []).length; return ``; }) .join(''); list.querySelectorAll('[data-rep-pick-id]').forEach((btn) => { btn.onclick = () => { const fn = pickPending; pickPending = null; selectReport(btn.dataset.repPickId); closePickModal(); if (fn) fn(); }; }); m.classList.add('open'); m.setAttribute('aria-hidden', 'false'); } function closePickModal() { const m = document.getElementById('rep-pick-modal'); if (!m) return; m.classList.remove('open'); m.setAttribute('aria-hidden', 'true'); } function cancelPickPending() { pickPending = null; closePickModal(); openNewModal(false); } function promptPickOrCreateReport(thenAdd) { openPickModal(thenAdd); } function toggleItem(part, seg) { const code = String(part || '').trim(); if (!code) return; if (isInReport(code)) { removeItem(code); return; } const doAdd = () => addItem(code, seg); if (!ui.collecting || !activeReport()) { promptPickOrCreateReport(doAdd); return; } doAdd(); } function itemSeg(x) { return x._seg || x.seg || ''; } function matchesSegFilter(entry) { const f = ui.segFilter; if (!f || f === 'all') return true; const { x, s, ri } = entry; if (f === 'prime') return !!(s && s.primeRep); if (f === 'rop') { return deps.itemUsesRop(x) && ((x.rop || 0) > 0 || (ri.qtySug || 0) > 0 || (ri.qty || 0) > 0); } if (f === 'po') return supplyGroupKey(x) === 'po'; if (f === 'pr') return supplyGroupKey(x) === 'pr'; return itemSeg(x) === f; } function filterTableRows(rows) { let list = rows.filter(matchesSegFilter); const q = ui.tableSearch.trim().toLowerCase(); if (!q) return list; return list.filter( (o) => o.x.p.toLowerCase().includes(q) || (o.x.d || '').toLowerCase().includes(q) || (o.x.fam || '').toLowerCase().includes(q) ); } function reportSortCobValue(x) { const m = deps.cobMetrics?.(x); if (!m) return null; if (ui.tableSortAsc && m.pct > 1) return 1 + m.pct; return m.pct; } function reportSortValue(entry) { const { x, s, ri } = entry; const k = ui.tableSortK; const r = activeReport(); if (!k || k === 'ord') { const idx = (r?.items || []).findIndex((i) => i.p === x.p); return idx < 0 ? Infinity : idx; } if (k === '_seg') return itemSeg(x); if (k === 'p') return x.p; if (k === 'd') return x.d || ''; if (k === 'fam') return x.fam || ''; if (k === 'o') return x.o; if (k === 'cob') return reportSortCobValue(x); if (k === 'qty') return ri.qty; if (k === 'proj') return projectedCob(x, ri.qty); if (k === 'alvo') return s?.alvoRef ?? null; return x.p; } function sortReportRows(rows) { if (!ui.tableSortK) return rows; const asc = ui.tableSortAsc; const nullV = asc ? Infinity : -Infinity; return [...rows].sort((a, b) => { const av = reportSortValue(a); const bv = reportSortValue(b); const aN = av == null ? nullV : av; const bN = bv == null ? nullV : bv; if (typeof aN === 'string' || typeof bN === 'string') { return asc ? ('' + aN).localeCompare(bN, 'pt-BR') : ('' + bN).localeCompare(aN, 'pt-BR'); } if (aN !== bN) return asc ? aN - bN : bN - aN; return (a.x.p || '').localeCompare(b.x.p || '', 'pt-BR'); }); } function defaultRepSortAsc(k) { return k === 'ord' || k === 'p' || k === 'd' || k === 'fam' || k === '_seg' || k === 'cob'; } function updateRepSortHeaders() { document.querySelectorAll('#rep-app .rep-table-single thead th[data-rep-k]').forEach((th) => { const on = th.dataset.repK === ui.tableSortK; th.classList.toggle('on', on); th.setAttribute('aria-sort', on ? (ui.tableSortAsc ? 'ascending' : 'descending') : 'none'); if (on) th.dataset.dir = ui.tableSortAsc ? 'asc' : 'desc'; else delete th.dataset.dir; }); } function countForFilter(rows, filterKey) { const prev = ui.segFilter; ui.segFilter = filterKey; const n = rows.filter((e) => matchesSegFilter(e)).length; ui.segFilter = prev; return n; } function reportItemCount(r) { return (r.items || []).length; } function reportValueRs(r) { let total = 0; for (const ri of r.items || []) { const x = findLive(ri.p); if (!x) continue; total += (ri.qty || 0) * (x.uc || 0); } return total; } function reportSummaryText(r) { const n = reportItemCount(r); const val = reportValueRs(r); const valTxt = val > 0 && deps.fmtBRL ? ' · ' + deps.fmtBRL(val) : ''; return `${n.toLocaleString('pt-BR')} itens${valTxt}`; } function formatRelativeTime(iso) { if (!iso) return ''; const then = new Date(iso).getTime(); if (Number.isNaN(then)) return ''; const diff = Date.now() - then; const mins = Math.floor(diff / 60000); if (mins < 1) return 'agora'; if (mins < 60) return `há ${mins} min`; const hrs = Math.floor(mins / 60); if (hrs < 24) return `há ${hrs}h`; const days = Math.floor(hrs / 24); if (days < 7) return `há ${days}d`; return new Date(iso).toLocaleDateString('pt-BR', { day: '2-digit', month: 'short' }); } function sidebarCardMeta(r) { const n = reportItemCount(r); const val = reportValueRs(r); const valTxt = val > 0 && deps.fmtBRL ? deps.fmtBRL(val) : ''; const countTxt = n === 0 ? 'Aguardando itens' : `${n.toLocaleString('pt-BR')} itens`; const secondary = valTxt ? `${countTxt} · ${valTxt}` : countTxt; const when = formatRelativeTime(r.updatedAt || r.createdAt); return { secondary, when, empty: n === 0 }; } function goPage(page) { const fn = global.navPage; if (typeof fn === 'function') fn(page); } function wireEmptyCtas(el, r) { el.querySelector('#rep-cta-collect')?.addEventListener('click', () => { enterCollectMode(r.id); goPage('explorar'); }); el.querySelectorAll('[data-rep-cta-page]').forEach((btn) => { btn.addEventListener('click', () => goPage(btn.dataset.repCtaPage)); }); } function alvoText(s) { return s.modo === 'rop' ? 'ROP ' + deps.fmtN(s.alvoRef) : deps.fmtAlvo(s.alvoRef) + ' m'; } function projText(x, qty) { const proj = projectedCob(x, qty); return proj == null ? '—' : proj.toFixed(1) + ' m'; } function supplyGroupKey(x) { const s = x.sup || 'none'; if (s === 'po') return 'po'; if (s === 'pr') return 'pr'; return 'none'; } function repPreviewRowHtml({ x, s, ri }) { const cobMain = deps.cobMainText(x); const bar = deps.cobBar ? deps.cobBar(x) : ''; const saldo = (x.o || 0) <= 0 ? '0' : deps.fmtN(x.o); const qtyTxt = `${deps.fmtN ? deps.fmtN(ri.qty) : ri.qty} ${esc(x.u || '')}`.trim(); const seg = itemSeg(x); const fam = (x.fam || '').trim(); const badges = deps.itemBadgesHtml ? deps.itemBadgesHtml(x) : ''; const badgeHtml = badges ? `${badges}` : ''; return ` ${esc(seg)} ${esc(x.p)}
${badgeHtml}${esc(x.d || '')}
${esc(fam || '—')} ${saldo} ${bar}${cobMain} ${esc(qtyTxt)} ${projText(x, ri.qty)} ${s ? alvoText(s) : '—'} `; } function previewTableColgroup() { return ` `; } function previewTableHead() { return `${previewTableColgroup()} SegCódigoDescriçãoFamíliaSaldoCob.QtyCob.+ped.Alvo `; } function repRowHtml({ x, s, ri }) { const cobMain = deps.cobMainText(x); const bar = deps.cobBar ? deps.cobBar(x) : ''; const saldo = (x.o || 0) <= 0 ? '0' : deps.fmtN(x.o); const qtyCell = ` ${esc(x.u || '')}`; const actions = ` `; const seg = itemSeg(x); const fam = (x.fam || '').trim(); const badges = deps.itemBadgesHtml ? deps.itemBadgesHtml(x) : ''; const badgeHtml = badges ? `${badges}` : ''; return ` ${actions} ${esc(seg)} ${esc(x.p)}
${badgeHtml}${esc(x.d || '')}
${esc(fam || '—')} ${saldo} ${bar}${cobMain} ${qtyCell} ${projText(x, ri.qty)} ${s ? alvoText(s) : '—'} `; } function tableColgroup() { return ` `; } function repTh(k, label, cls = '', title = '') { const t = title ? ` title="${attrEsc(title)}"` : ''; const c = cls ? ` class="${cls}"` : ''; return `${label}`; } function tableHead() { return `${tableColgroup()} ${repTh('ord', '', 'rep-actions-hd', 'Ordem no relatório')} ${repTh('_seg', 'Seg', 'seg')} ${repTh('p', 'Código', 'l')} ${repTh('d', 'Descrição', 'l')} ${repTh('fam', 'Família', 'l')} ${repTh('o', 'Saldo')} ${repTh('cob', 'Cob.', '', 'Cobertura vs alvo')} ${repTh('qty', 'Qty')} ${repTh('proj', 'Cob.+ped.', '', 'Cobertura projetada com pedido')} ${repTh('alvo', 'Alvo')} `; } function setSegFilter(f) { const segs = deps.SEGS || []; const nkeys = deps.NKEYS || []; const extra = ['rop', 'po', 'pr', 'prime', ...nkeys]; let next = f; if (next !== 'all' && !extra.includes(next) && !segs.includes(next)) next = 'all'; ui.segFilter = next; saveLocal(); deps.syncUrl(); renderSegNav(); renderTableOnly(); } function getSegFilter() { return ui.segFilter || 'all'; } function pushSegSep(pills) { if (pills.length) pills.push(''); } function renderSegNav() { const nav = document.getElementById('rep-seg-nav'); const r = activeReport(); if (!nav || !r || !r.items.length) return; const entries = reportRowEntries(r); const segs = deps.SEGS || []; const pills = []; const allN = countForFilter(entries, 'all'); pills.push( `` ); let lastAbc = ''; let abcHadPill = false; for (const seg of segs) { const n = countForFilter(entries, seg); if (n <= 0 && ui.segFilter !== seg) continue; const abc = seg[0]; if (abc !== lastAbc) { if (lastAbc && abcHadPill) pushSegSep(pills); lastAbc = abc; abcHadPill = false; } pills.push( `` ); abcHadPill = true; } const poN = countForFilter(entries, 'po'); if (poN > 0 || ui.segFilter === 'po') { pushSegSep(pills); pills.push( `` ); } const prN = countForFilter(entries, 'pr'); if (prN > 0 || ui.segFilter === 'pr') { pills.push( `` ); } const ni0N = countForFilter(entries, 'NI0'); const primeN = countForFilter(entries, 'prime'); if (ni0N > 0 || ui.segFilter === 'NI0' || entries.some((e) => itemSeg(e.x) === 'NI0')) { pushSegSep(pills); pills.push( `` ); if (primeN > 0 || ui.segFilter === 'prime') { pills.push( `` ); } } const ropN = countForFilter(entries, 'rop'); if (ropN > 0 || ui.segFilter === 'rop') { pushSegSep(pills); pills.push( `` ); } nav.setAttribute('aria-label', 'Segmento e pedidos abertos'); nav.innerHTML = pills.join(''); nav.querySelectorAll('[data-rep-seg]').forEach((btn) => { btn.onclick = () => setSegFilter(btn.dataset.repSeg); }); } function updateQty(p, qty) { const r = activeReport(); if (!r) return; const ri = r.items.find((i) => i.p === p); if (!ri) return; ri.qty = Number.isFinite(qty) ? qty : 0; touchReport(r); const entry = reportRowEntries(r).find((e) => e.ri.p === p); if (!entry) return; document.querySelectorAll(`[data-proj-for="${CSS.escape(p)}"]`).forEach((el) => { el.textContent = projText(entry.x, ri.qty); }); } function preserveLocalDecisions(serverReports) { const byId = new Map(store.reports.map((r) => [r.id, r])); const serverIds = new Set((serverReports || []).map((r) => r.id)); const out = (serverReports || []).map((sr) => { const local = byId.get(sr.id); if (!local) return migrateReport({ ...sr }); const localItems = new Map(local.items.map((i) => [i.p, i])); const mergedItems = (sr.items || []).map((it) => { const li = localItems.get(it.p); if (!li) return migrateItem({ ...it }); return migrateItem({ ...it, decision: li.decision || it.decision, qty: li.qty ?? it.qty, qtySug: li.qtySug ?? it.qtySug, note: li.note != null ? li.note : it.note, approvedAt: li.approvedAt || it.approvedAt, rejectedAt: li.rejectedAt || it.rejectedAt, }); }); const serverCodes = new Set((sr.items || []).map((i) => i.p)); for (const li of local.items || []) { if (!serverCodes.has(li.p)) mergedItems.push(migrateItem({ ...li })); } return migrateReport({ ...sr, items: mergedItems }); }); for (const local of store.reports) { if (!serverIds.has(local.id)) out.push(migrateReport({ ...local })); } return out; } function applyServerStore(serverReports, revAtSend) { if (storeRev > revAtSend) return false; store.reports = preserveLocalDecisions(serverReports); migrateStore(); return true; } function updateSaveBadge() { const badge = document.getElementById('rep-save-badge'); if (!badge) return; if (!ui.serverAvailable) { badge.textContent = 'Autosave · local'; return; } if (saveInFlight) badge.textContent = 'Salvando…'; else if (saveQueued || saveStatus === 'pending') badge.textContent = 'Pendente'; else if (saveStatus === 'saved') badge.textContent = 'Salvo'; else badge.textContent = 'Autosave · servidor'; } function setSaveStatus(status) { saveStatus = status; updateSaveBadge(); } function wireRepTableSort() { const root = document.getElementById('rep-app'); if (!root || root._repSortWired) return; root._repSortWired = true; root.addEventListener('click', (e) => { const th = e.target.closest('.rep-table-single thead th[data-rep-k]'); if (!th || !root.contains(th)) return; const k = th.dataset.repK; if (!k) return; if (ui.tableSortK === k) ui.tableSortAsc = !ui.tableSortAsc; else { ui.tableSortK = k; ui.tableSortAsc = defaultRepSortAsc(k); } renderTableOnly(); }); } function wireRepTableClicks() { const root = document.getElementById('rep-app'); if (!root || root._repClickWired) return; root._repClickWired = true; root.addEventListener( 'click', (ev) => { const btn = ev.target.closest('button[data-rep-act]'); if (!btn || !root.contains(btn)) return; ev.preventDefault(); ev.stopPropagation(); const act = btn.getAttribute('data-rep-act'); const p = btn.getAttribute('data-rep-part'); if (!p || !act) return; if (act === 'remove') removeItem(p); }, true ); root.addEventListener('change', (ev) => { const inp = ev.target.closest('.rep-qty-in'); if (!inp || !root.contains(inp)) return; const p = inp.getAttribute('data-rep-part'); if (p) updateQty(p, parseFloat(inp.value)); }); root.addEventListener('click', (ev) => { if (ev.target.closest('button[data-rep-act], .rep-qty-in, input, textarea, label')) return; const row = ev.target.closest('tr.rep-data-row'); if (!row || !root.contains(row)) return; const p = row.getAttribute('data-rep-part'); if (!p || !deps.openItemInModal) return; document.querySelectorAll('#rep-app tr.rep-data-row').forEach((t) => { t.classList.toggle('sel', t.getAttribute('data-rep-part') === p); }); deps.openItemInModal(null, p); }); } function renderTableOnly() { const r = activeReport(); const body = document.getElementById('rep-tbody'); if (!r || !body) return; const rows = sortReportRows(filterTableRows(reportRowEntries(r))); const segLblMap = { rop: ' (sem giro ROP)', po: ' (com OC)', pr: ' (com PR)', prime: ' (1ª rep.)' }; const segLbl = ui.segFilter === 'all' || !ui.segFilter ? '' : segLblMap[ui.segFilter] || ` (${ui.segFilter})`; body.innerHTML = rows.length ? rows.map((e) => repRowHtml(e)).join('') : `Nenhum item${esc(segLbl)} neste filtro.`; const prog = document.getElementById('rep-counts'); if (prog) prog.textContent = reportSummaryText(r); renderSegNav(); updateRepSortHeaders(); } function openNewModal(show, afterCreate) { if (show && afterCreate) pickPending = afterCreate; const m = document.getElementById('rep-new-modal'); if (!m) return; m.classList.toggle('open', !!show); m.setAttribute('aria-hidden', show ? 'false' : 'true'); if (show) { const inp = document.getElementById('rep-new-name'); if (inp) { inp.value = ''; inp.placeholder = 'Ex.: CZ ruptura jun/26'; inp.focus(); } } } function renderSidebar() { const ul = document.getElementById('rep-report-list'); const searchInp = document.getElementById('rep-sidebar-search'); if (searchInp && searchInp.value !== ui.sidebarSearch) searchInp.value = ui.sidebarSearch; if (!ul) return; const allList = ui.view === 'archive' ? store.reports.filter((r) => r.status === 'saved') : store.reports.filter((r) => r.status !== 'saved'); const list = reportsForSidebar(); if (!list.length) { const filtered = !!(ui.sidebarSearch || '').trim(); ul.innerHTML = ui.view === 'archive' ? filtered ? '
  • Nenhum relatório salvo corresponde ao filtro.
  • ' : '
  • Nenhum relatório salvo ainda.
  • ' : allList.length && filtered ? '
  • Nenhum rascunho corresponde ao filtro.
  • ' : '
  • Nenhum rascunho ativo.
    Veja Arquivados ou use + Novo.
  • '; return; } ul.innerHTML = list .map((r) => { const on = ui.view === 'archive' ? r.id === ui.archivePreviewId ? ' on' : '' : r.id === store.activeId ? ' on' : ''; const collecting = ui.collecting && store.activeId === r.id; const statusCls = r.status === 'saved' ? 'rep-status-saved' : 'rep-status-draft'; const statusLbl = r.status === 'saved' ? 'Arquivado' : 'Ativo'; const { secondary, when, empty } = sidebarCardMeta(r); const archiveBtn = r.status !== 'saved' ? `` : ''; const restoreBtn = r.status === 'saved' ? `` : ''; return `
  • ${archiveBtn}${restoreBtn}
  • `; }) .join(''); ul.querySelectorAll('[data-rep-id]').forEach((btn) => { btn.onclick = () => selectReport(btn.dataset.repId); }); ul.querySelectorAll('[data-rep-del]').forEach((btn) => { btn.onclick = (ev) => { ev.stopPropagation(); deleteReport(btn.dataset.repDel); }; }); ul.querySelectorAll('[data-rep-archive]').forEach((btn) => { btn.onclick = (ev) => { ev.stopPropagation(); archiveReport(btn.dataset.repArchive); }; }); ul.querySelectorAll('[data-rep-restore]').forEach((btn) => { btn.onclick = (ev) => { ev.stopPropagation(); restoreReport(btn.dataset.repRestore); }; }); } function closeArchivePreview() { ui.archivePreviewId = null; render(); } function openArchivePreview(id) { const r = store.reports.find((x) => x.id === id && x.status === 'saved'); if (!r) return; ui.archivePreviewId = id; render(); } function renderArchivePreview(r) { const el = document.getElementById('rep-main'); if (!el) return; const summary = reportSummaryText(r); const savedAt = r.savedAt || r.updatedAt; const savedLbl = savedAt ? new Date(savedAt).toLocaleString('pt-BR', { day: '2-digit', month: 'short', hour: '2-digit', minute: '2-digit', }) : ''; const rows = reportRowEntries(r); el.innerHTML = `

    ${esc(r.name)}

    Salvo ${esc(summary)}${savedLbl ? ' · ' + esc(savedLbl) : ''}

    Modo leitura — restaure para editar em Ativos.

    ${previewTableHead()}${rows.length ? rows.map((e) => repPreviewRowHtml(e)).join('') : ''}
    Sem itens.
    `; el.querySelector('#rep-preview-back')?.addEventListener('click', closeArchivePreview); el.querySelector('#rep-preview-restore')?.addEventListener('click', () => restoreReport(r.id)); } function renderArchiveMain() { const el = document.getElementById('rep-main'); if (!el) return; if (ui.archivePreviewId) { const pr = store.reports.find((x) => x.id === ui.archivePreviewId && x.status === 'saved'); if (pr) { renderArchivePreview(pr); return; } ui.archivePreviewId = null; } const saved = [...store.reports.filter((r) => r.status === 'saved')].sort((a, b) => { const ta = new Date(a.savedAt || a.updatedAt || 0).getTime(); const tb = new Date(b.savedAt || b.updatedAt || 0).getTime(); return tb - ta; }); if (!saved.length) { el.innerHTML = '

    Nenhum relatório arquivado.

    Use o ícone de arquivo na sidebar de Ativos para arquivar um relatório.

    '; return; } el.innerHTML = `

    ${saved.length === 1 ? '1 relatório arquivado' : `${saved.length} relatórios arquivados`} — revise em modo leitura ou restaure para editar em Ativos.

    ${saved .map((r) => { const n = reportItemCount(r); const val = reportValueRs(r); const dt = new Date(r.savedAt || r.updatedAt).toLocaleString('pt-BR', { day: '2-digit', month: 'short', hour: '2-digit', minute: '2-digit', }); return ``; }) .join('')}
    NomeSalvo emItensValor est.Ações
    ${esc(r.name)} ${esc(dt)} ${n} ${val > 0 && deps.fmtBRL ? esc(deps.fmtBRL(val)) : '—'}
    `; el.querySelectorAll('[data-rep-open]').forEach((btn) => { btn.onclick = () => openArchivePreview(btn.dataset.repOpen); }); el.querySelectorAll('[data-rep-restore]').forEach((btn) => { btn.onclick = () => restoreReport(btn.dataset.repRestore); }); el.querySelectorAll('[data-rep-del-archive]').forEach((btn) => { btn.onclick = () => deleteReport(btn.dataset.repDelArchive); }); } function renderWorkMain() { const el = document.getElementById('rep-main'); if (!el) return; const r = activeReport(); if (!r || r.status === 'saved') { const archN = store.reports.filter((x) => x.status === 'saved').length; const hint = archN > 0 ? '

    Há relatórios em Arquivados — restaure para editar ou crie um novo rascunho.

    ' : '

    Use + Novo relatório ou colete itens em Segmentação/Árvore.

    '; el.innerHTML = `

    Nenhum rascunho ativo.

    ${hint}
    `; return; } const savedBadge = ''; if (!r.items.length) { el.innerHTML = `
    ${savedBadge} Autosave

    Monte a lista em Segmentação ou Árvore.

    `; wireTitleInput(el.querySelector('#rep-title-in'), r); wireEmptyCtas(el, r); return; } const summary = reportSummaryText(r); el.innerHTML = `
    ${savedBadge} ${esc(summary)}
    Autosave
    ${tableHead()}
    `; wireTitleInput(el.querySelector('#rep-title-in'), r); el.querySelector('#rep-export-csv')?.addEventListener('click', exportCsv); el.querySelector('#rep-table-search')?.addEventListener('input', (e) => { ui.tableSearch = e.target.value; renderTableOnly(); }); renderSegNav(); renderTableOnly(); } function renderMain() { if (ui.view === 'archive') renderArchiveMain(); else renderWorkMain(); } function exportCsv() { const r = activeReport(); if (!r) return; const rows = reportRowEntries(r); const head = [ 'codigo', 'descricao', 'seg', 'familia', 'rop', 'qty_pedido', 'qty_sugerida', 'cob_atual', 'cob_projetada', 'alvo', 'valor_rs', 'nota', ]; const lines = [head.join(';')]; for (const { x, s, ri } of rows) { const proj = projectedCob(x, ri.qty); lines.push( [ x.p, (x.d || '').replace(/[;\n]/g, ' '), itemSeg(x), (x.fam || '').replace(/[;\n]/g, ' '), x.rop ?? '', ri.qty, ri.qtySug, x.cob ?? '', proj == null ? '' : proj.toFixed(2), s ? s.alvoRef : '', Math.round(ri.qty * (x.uc || 0)), (ri.note || '').replace(/[;\n]/g, ' '), ].join(';') ); } const blob = new Blob(['\ufeff' + lines.join('\n')], { type: 'text/csv;charset=utf-8' }); const a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = (r.name || 'relatorio').replace(/[^\w\-]+/g, '_').slice(0, 40) + '.csv'; a.click(); URL.revokeObjectURL(a.href); } function scheduleServerSave() { if (!ui.serverAvailable) return; setSaveStatus('pending'); clearTimeout(serverTimer); serverTimer = setTimeout(() => flushServerSave(false), 800); } async function flushServerSave(manual) { if (!currentUser || !ui.serverAvailable) { if (manual) alert('Servidor indisponível — dados só no navegador.'); return; } if (saveInFlight) { saveQueued = true; updateSaveBadge(); return; } saveInFlight = true; saveQueued = false; const revAtSend = storeRev; setSaveStatus('saving'); const payload = { version: 1, activeId: store.activeId, reports: store.reports }; let needsRetry = false; try { const res = await fetch( '/api/rep-reports', apiFetchOpts({ method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }) ); const data = await res.json().catch(() => ({})); if (res.status === 401) { ui.serverAvailable = false; setSaveStatus('idle'); if (manual) alert('Sessão expirada — faça login novamente.'); return; } if (res.ok && data.reports) { if (storeRev > revAtSend) { needsRetry = true; } else if (applyServerStore(data.reports, revAtSend)) { ui.saveMode = 'server'; saveLocal(); setSaveStatus('saved'); if (manual) render(); else if (val('curPage') === 'relatorio') renderTableOnly(); } return; } setSaveStatus('idle'); if (manual) alert(data.error || data.detail || 'Falha ao salvar no servidor'); } catch (_) { setSaveStatus('idle'); if (manual) alert('Servidor indisponível'); } finally { saveInFlight = false; if (needsRetry || saveQueued || storeRev > revAtSend) { saveQueued = false; clearTimeout(serverTimer); serverTimer = setTimeout(() => flushServerSave(false), 0); } else { updateSaveBadge(); } } } function pushToServer(manual) { return flushServerSave(manual); } async function pullFromServer() { if (!currentUser) return false; try { const res = await fetch('/api/rep-reports', apiFetchOpts()); if (res.status === 401) { ui.serverAvailable = false; return false; } if (!res.ok) return false; const data = await res.json(); if (!Array.isArray(data.reports)) return false; const keepActive = store.activeId; store = { activeId: keepActive, reports: preserveLocalDecisions(data.reports), }; if (keepActive && !store.reports.some((r) => r.id === keepActive)) store.activeId = null; migrateStore(); saveLocal(); ui.serverAvailable = true; ui.saveMode = 'server'; return true; } catch (_) { return false; } } async function detectServer() { if (!currentUser) { ui.serverAvailable = false; return; } try { const res = await fetch('/api/status', apiFetchOpts()); ui.serverAvailable = res.ok; } catch (_) { ui.serverAvailable = false; } } async function onUserReady(username) { const user = (username || '').trim().toLowerCase(); if (!user) return; clearTimeout(serverTimer); storeRev = 0; saveInFlight = false; saveQueued = false; saveStatus = 'idle'; currentUser = user; store = { activeId: null, reports: [] }; ui.collecting = false; ui.lastWorkActiveId = null; ui.archivePreviewId = null; ui.view = 'work'; loadLocal(user); await detectServer(); const pulled = await pullFromServer(); if (!pulled && !store.reports.length) { maybeImportLegacyLocal(); if (store.reports.length) { saveLocal(); scheduleServerSave(); } } render(); renderHeaderBanner(); refreshPickCells(); } function onUserLogout() { clearTimeout(serverTimer); storeRev = 0; saveInFlight = false; saveQueued = false; saveStatus = 'idle'; pickPending = null; currentUser = null; store = { activeId: null, reports: [] }; ui.serverAvailable = false; ui.collecting = false; ui.saveMode = 'local'; } function loadSidebarCollapsed() { try { return localStorage.getItem(LS_SIDEBAR_KEY) === '1'; } catch (_) { return false; } } function applySidebarCollapsed() { const app = document.getElementById('rep-app'); const btn = document.getElementById('rep-sidebar-toggle'); if (app) app.classList.toggle('rep-sidebar-collapsed', !!ui.sidebarCollapsed); if (btn) { btn.textContent = ui.sidebarCollapsed ? '»' : '«'; btn.title = ui.sidebarCollapsed ? 'Expandir rascunhos' : 'Recolher rascunhos'; btn.setAttribute('aria-expanded', ui.sidebarCollapsed ? 'false' : 'true'); } } function toggleSidebarCollapsed() { ui.sidebarCollapsed = !ui.sidebarCollapsed; try { localStorage.setItem(LS_SIDEBAR_KEY, ui.sidebarCollapsed ? '1' : '0'); } catch (_) {} applySidebarCollapsed(); } function updateViewTabs() { const draftN = store.reports.filter((r) => r.status !== 'saved').length; const archN = store.reports.filter((r) => r.status === 'saved').length; document.querySelectorAll('[data-rep-view]').forEach((btn) => { const v = btn.dataset.repView; btn.classList.toggle('on', v === ui.view); const full = btn.querySelector('.rep-view-full'); if (full) { const n = v === 'archive' ? archN : draftN; full.innerHTML = v === 'archive' ? `Arquivados(${n})` : `Ativos(${n})`; } }); const title = document.getElementById('rep-sidebar-title'); if (title) title.textContent = ui.view === 'archive' ? 'Arquivados' : 'Relatórios'; const newBtn = document.getElementById('rep-new-report'); if (newBtn) newBtn.hidden = ui.view === 'archive'; const sidebar = document.getElementById('rep-sidebar'); if (sidebar) sidebar.classList.toggle('rep-sidebar-archive-mode', ui.view === 'archive'); applySidebarCollapsed(); } function render() { updateViewTabs(); renderSidebar(); renderMain(); renderHeaderBanner(); const page = document.getElementById('page-relatorio'); if (page) page.classList.toggle('rep-view-archive', ui.view === 'archive'); updateSaveBadge(); } function onFilterChange() { if (val('curPage') === 'relatorio') render(); } function setActiveId(id) { if (!id) return; const r = store.reports.find((x) => x.id === id); if (!r) return; if (r.status === 'saved') { ui.view = 'archive'; ui.archivePreviewId = id; store.activeId = null; } else { store.activeId = id; ui.lastWorkActiveId = id; ui.archivePreviewId = null; if (ui.view === 'archive') ui.view = 'work'; if (isCollectPage()) { enterCollectMode(id); return; } } persistStore(); deps.syncUrl?.(); renderHeaderBanner(); refreshPickCells(); } function getActiveId() { return store.activeId; } function init(initDeps) { deps = initDeps; ui.sidebarCollapsed = loadSidebarCollapsed(); if (initWired) { renderHeaderBanner(); return; } initWired = true; ui.collecting = false; ui.lastWorkActiveId = null; wireRepTableClicks(); wireRepTableSort(); renderHeaderBanner(); document.getElementById('rep-sidebar-toggle')?.addEventListener('click', toggleSidebarCollapsed); document.getElementById('rep-new-report')?.addEventListener('click', () => openNewModal(true)); document.getElementById('rep-new-cancel')?.addEventListener('click', () => cancelPickPending()); document.getElementById('rep-new-save')?.addEventListener('click', () => { const name = document.getElementById('rep-new-name')?.value || ''; const fn = pickPending; pickPending = null; createReport(name); openNewModal(false); closePickModal(); if (fn) fn(); }); document.getElementById('rep-new-modal')?.addEventListener('click', (e) => { if (e.target.id === 'rep-new-modal') cancelPickPending(); }); document.getElementById('rep-pick-cancel')?.addEventListener('click', () => cancelPickPending()); document.getElementById('rep-pick-new')?.addEventListener('click', () => { closePickModal(); openNewModal(true, pickPending); }); document.getElementById('rep-pick-modal')?.addEventListener('click', (e) => { if (e.target.id === 'rep-pick-modal') cancelPickPending(); }); document.getElementById('rep-delete-cancel')?.addEventListener('click', () => closeDeleteModal()); document.getElementById('rep-delete-confirm')?.addEventListener('click', () => confirmDeleteReport()); document.getElementById('rep-delete-modal')?.addEventListener('click', (e) => { if (e.target.id === 'rep-delete-modal') closeDeleteModal(); }); document.getElementById('header-report-exit')?.addEventListener('click', () => { setHeaderReportMenuOpen(false); if (ui.collecting) { store.activeId = null; ui.lastWorkActiveId = null; persistStore(); deps.syncUrl?.(); exitCollectMode(); } else { const d = activeDraftReport(); if (d) enterCollectMode(d.id); else if (val('curPage') === 'relatorio') goPage('explorar'); } }); const headerRepBtn = document.getElementById('header-report-name-btn'); if (headerRepBtn && !headerRepBtn._repWired) { headerRepBtn._repWired = true; headerRepBtn.addEventListener('click', (e) => { e.stopPropagation(); setHeaderReportMenuOpen(!headerReportMenuOpen); }); } if (!document._headerRepMenuWired) { document._headerRepMenuWired = true; document.addEventListener('click', (e) => { if (!headerReportMenuOpen) return; const sw = document.querySelector('.header-report-switcher'); if (sw && !sw.contains(e.target)) setHeaderReportMenuOpen(false); }); document.addEventListener('keydown', (e) => { if (e.key === 'Escape' && headerReportMenuOpen) setHeaderReportMenuOpen(false); }); } document.querySelectorAll('[data-rep-view]').forEach((btn) => { btn.onclick = () => setView(btn.dataset.repView); }); const sidebarSearch = document.getElementById('rep-sidebar-search'); if (sidebarSearch && !sidebarSearch._repWired) { sidebarSearch._repWired = true; sidebarSearch.addEventListener('input', (e) => { ui.sidebarSearch = e.target.value; renderSidebar(); }); } } global.AxoRep = { init, onUserReady, onUserLogout, render, onFilterChange, setActiveId, getActiveId, getView, setView, getSegFilter, setSegFilter, createReport, isCollecting, getActiveReport, isInReport, enterCollectMode, exitCollectMode, addItem, removeItem, toggleItem, pickCellHtml, refreshPickCells, refreshHeaderBanner: renderHeaderBanner, promptPickOrCreateReport, }; })(typeof window !== 'undefined' ? window : globalThis);