// 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.
'
' + d.html.slice(0, chromeStart) + '
' +
'
' + 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 =
'