// Tenney North Park — Dashboard component. // FRAMEWORK: EB's CX_DASH document (cards + drawer + full record + data sheets), // injected exactly like ConstructionDashboard. CONTENT: window.TN_DASH // (tabs/tenney_dashboard_content.js). This file wires content into framework; // it defines NO framework behavior of its own (Playbook §2). // // The drawer/full-record chrome is injected ONCE into a persistent body-level // container so openDrawer() works from ANY tab (Library rows open records too), // not just while the Dashboard component is mounted. function ensureTnCxGlobal() { if (document.getElementById('tn-cx-global')) return true; const d = window.CX_DASH; const tn = window.TN_DASH; if (!d || !tn) return false; // Framework CSS (scoped under .cx-root), once. if (!document.getElementById('cx-dash-style')) { const st = document.createElement('style'); st.id = 'cx-dash-style'; st.textContent = d.css; document.head.appendChild(st); } // Reuse EB's drawer + full-record chrome verbatim; only the crumb text // (hub name) is content and gets swapped. const chromeStart = d.html.indexOf('
Data' ).join( '' + '' + '' ); const holder = document.createElement('div'); holder.id = 'tn-cx-global'; holder.innerHTML = // Hidden copy of the framework document's non-chrome sections (nav rail, // section hosts). The framework's boot scripts expect these ids to exist. '' + '
' + chrome + '
'; document.body.appendChild(holder); // Boot the framework scripts exactly once (globals: openDrawer, openFull, // recordContent, sheetData, …). if (!window.__CX_DASH_BOOTED) { window.__CX_DASH_BOOTED = true; d.scripts.forEach((code) => { const s = document.createElement('script'); s.textContent = code; holder.appendChild(s); }); } // Register TN content into the framework's registries (classic scripts // share top-level lexical scope, so recordContent/ARTIFACT_ID/sheetData // are reachable from a new classic script). if (!window.__TN_DASH_REGISTERED) { window.__TN_DASH_REGISTERED = true; const reg = document.createElement('script'); reg.textContent = "Object.assign(recordContent, window.TN_DASH.records);" + "Object.assign(ARTIFACT_ID, window.TN_DASH.artifactIds);" + "Object.assign(sheetData, window.TN_DASH.sheets);" + // Extra views: extend the framework's switchView for the TN record's // 'control' (.control-view) and 'about'/Context (.about-view) states. "(function () {" + " var _sv = switchView;" + " var EXTRA = { about: '.about-view', control: '.control-view' };" + " switchView = function (view) {" + " var root = activeRecordRoot();" + " if (!root) return _sv(view);" + " var hasData = !!root.querySelector('.data-view');" + " Object.keys(EXTRA).forEach(function (k) {" + " var el = root.querySelector(EXTRA[k]);" + " if (el) el.classList.toggle('active', view === k);" + " });" + " if (EXTRA[view]) {" + " if (!root.querySelector(EXTRA[view])) return;" + " var r = root.querySelector('.report-view'); if (r) r.classList.add('hidden');" + " var dv = root.querySelector('.data-view'); if (dv) dv.classList.remove('active');" + " document.querySelectorAll('.view-toggle').forEach(function (t) {" + " t.querySelectorAll('button').forEach(function (b) { b.classList.toggle('active', b.dataset.view === view); });" + " });" + " return;" + " }" + // Records without a Data view (e.g. source PDFs): 'data' is a no-op, // and 'report' must recover the report-view ourselves since the // original switchView bails when .data-view is missing. " if (!hasData) {" + " if (view === 'data') { view = 'report'; }" + " var rv = root.querySelector('.report-view'); if (rv) rv.classList.remove('hidden');" + " document.querySelectorAll('.view-toggle').forEach(function (t) {" + " t.querySelectorAll('button').forEach(function (b) { b.classList.toggle('active', b.dataset.view === 'report'); });" + " });" + " return;" + " }" + " _sv(view);" + " };" + "})();" + // Sheet formatting pass (content-level): right-align currency strings, // flag VACANT red, bold Total/Summary label rows. Runs after each render. "(function () {" + " var _rs = renderSheet;" + " renderSheet = function (idx) {" + " _rs(idx);" + " var wrap = activeRecordRoot().querySelector('.sheet-wrap');" + " if (!wrap) return;" + " wrap.querySelectorAll('td').forEach(function (td) {" + " var t = td.textContent;" + " if (t.charAt(0) === '$' || /^[0-9.]+%$/.test(t)) td.classList.add('num');" + " if (t === 'VACANT') td.classList.add('tn-vacant');" + " if (t === 'Total / Average' || t === 'Summary') td.classList.add('tn-sheet-label');" + " });" + " wrap.querySelectorAll('th').forEach(function (th) {" + " var sp = th.querySelector('span');" + " if (sp) th.textContent = sp.textContent;" + " });" + " };" + "})();"; holder.appendChild(reg); } return true; } function TenneyDashboard() { const hostRef = React.useRef(null); React.useEffect(() => { const tn = window.TN_DASH; const host = hostRef.current; if (!ensureTnCxGlobal() || !tn || !host) return; host.innerHTML = '
' + '
' + tn.cardsHtml + '
' + '
'; return () => { try { if (window.closeFull) window.closeFull(); } catch (e) {} try { if (window.closeDrawer) window.closeDrawer(); } catch (e) {} document.body.style.overflow = ''; host.innerHTML = ''; }; }, []); return
; } // Chrome available from any tab (Library rows call openDrawer too). ensureTnCxGlobal(); window.TenneyDashboard = TenneyDashboard; window.ensureTnCxGlobal = ensureTnCxGlobal;