[object Object]

← back to Ticket System

board: on/off toggles for all four field groups — columns (▦), stat pills (◍), expanded-row detail fields by kind (⤢), and the DM panel (✉); all persist to localStorage + Reset clears them (TK-10429)

c65f1c1b8402ea34ff43449c780e20e586c08019 · 2026-08-10 15:52:23 -0700 · steve

Files touched

Diff

commit c65f1c1b8402ea34ff43449c780e20e586c08019
Author: steve <steve@designerwallcoverings.com>
Date:   Mon Aug 10 15:52:23 2026 -0700

    board: on/off toggles for all four field groups — columns (▦), stat pills (◍), expanded-row detail fields by kind (⤢), and the DM panel (✉); all persist to localStorage + Reset clears them (TK-10429)
---
 board.html | 38 ++++++++++++++++++++++++++++----------
 1 file changed, 28 insertions(+), 10 deletions(-)

diff --git a/board.html b/board.html
index 2a07b772..9c6e7152 100644
--- a/board.html
+++ b/board.html
@@ -54,11 +54,14 @@
   <input type="range" id="dens" min="4" max="14" step="1" title="row density">
   <button class="btn" id="colsBtn">▦ Columns</button>
   <button class="btn" id="pillsBtn" title="Toggle stat pills">◍ Pills</button>
-  <button class="btn" id="resetBtn" title="Reset columns/sort/density/pills">↺ Reset</button>
+  <button class="btn" id="detBtn" title="Toggle expanded-row detail fields">⤢ Detail</button>
+  <button class="btn" id="dmBtn" title="Toggle the DM panel">✉ DMs</button>
+  <button class="btn" id="resetBtn" title="Reset all view settings">↺ Reset</button>
   <a class="btn" href="/kanban">Kanban</a><a class="btn" href="/office">Office</a>
 </header>
 <div id="colmenu"></div>
 <div id="pillmenu"></div>
+<div id="detmenu"></div>
 <div class="tblwrap"><table class="g"><thead><tr id="thead"></tr></thead><tbody id="tbody"></tbody></table></div>
 <div id="dmwrap"></div>
 <script>
@@ -90,6 +93,10 @@ let WID=LS('tkWid',{}), DENS=LS('tkDens',7);
 // toggleable stat pills — mirrors the column-visibility pattern (persists to localStorage)
 const PILLS=[{k:'open',l:'open'},{k:'doing',l:'doing'},{k:'blocked',l:'blocked'},{k:'done',l:'done'},{k:'stale',l:'stale'},{k:'totals',l:'total / shown'}];
 let PILL_VIS=LS('tkPills',null)||Object.fromEntries(PILLS.map(p=>[p.k,1]));
+// toggleable expanded-row detail fields (by kind) + DM-panel show/hide — both persist
+const DET=[{k:'comment',l:'comments'},{k:'note',l:'notes'},{k:'action',l:'actions'},{k:'win',l:'wins'},{k:'challenge',l:'challenges'},{k:'cody',l:'cody dissents'}];
+let DET_VIS=LS('tkDet',null)||Object.fromEntries(DET.map(d=>[d.k,1]));
+let DM_ON=LS('tkDM',1), LASTMSG=[];
 document.documentElement.style.setProperty('--rowpad',DENS+'px'); $('#dens').value=DENS;
 const ordered=()=>ORDER.map(k=>COLS.find(c=>c.k===k)).filter(c=>c&&VIS[c.k]);
 
@@ -128,9 +135,11 @@ function render(){
     tr.innerHTML=cols.map(c=>`<td>${c.cell(t)}</td>`).join('');
     tr.onclick=e=>{if(e.target.closest('a,.who,.proj'))return;const nx=tr.nextElementSibling;if(nx&&nx.classList.contains('det')){nx.remove();return;}
       const d=document.createElement('tr');d.className='det';
-      const thread=(t.comments||[]).map(c=>`<div class="c k-${c.kind}"><b>${esc(c.agent)}</b> <i>${esc(c.kind)}</i> ${esc(c.text)}<span class="cts">${dt(c.ts)}</span></div>`).join('')
-        +(t.actions||[]).slice(-6).map(a=>`<div class="c k-action"><b>${esc(a.agent)}</b> <i>action</i> ${esc(a.text)}<span class="cts">${dt(a.ts)}</span></div>`).join('')
-        +(!(t.comments||[]).length&&!(t.actions||[]).length?'<div class="c none">no comments yet</div>':'');
+      const cShown=(t.comments||[]).filter(c=>DET_VIS[c.kind]!==0);
+      const aShown=DET_VIS.action!==0?(t.actions||[]).slice(-6):[];
+      const thread=cShown.map(c=>`<div class="c k-${c.kind}"><b>${esc(c.agent)}</b> <i>${esc(c.kind)}</i> ${esc(c.text)}<span class="cts">${dt(c.ts)}</span></div>`).join('')
+        +aShown.map(a=>`<div class="c k-action"><b>${esc(a.agent)}</b> <i>action</i> ${esc(a.text)}<span class="cts">${dt(a.ts)}</span></div>`).join('')
+        +(!cShown.length&&!aShown.length?'<div class="c none">no visible detail (toggle via ⤢ Detail)</div>':'');
       d.innerHTML=`<td colspan="${cols.length}"><div class="thread">${thread}</div></td>`;tr.after(d);};
     tb.appendChild(tr);
   }
@@ -158,14 +167,23 @@ function renderPillMenu(){
   m.innerHTML=`<div class="gh">Stat pills</div>`+PILLS.map(p=>`<label><input type="checkbox" data-k="${p.k}" ${PILL_VIS[p.k]?'checked':''}>${esc(p.l)}</label>`).join('');
   m.querySelectorAll('input').forEach(cb=>cb.onchange=()=>{PILL_VIS[cb.dataset.k]=cb.checked?1:0;save('tkPills',PILL_VIS);renderPills();});
 }
+function renderDetMenu(){
+  const m=$('#detmenu');
+  m.innerHTML=`<div class="gh">Expanded-row detail fields</div>`+DET.map(d=>`<label><input type="checkbox" data-k="${d.k}" ${DET_VIS[d.k]?'checked':''}>${esc(d.l)}</label>`).join('');
+  m.querySelectorAll('input').forEach(cb=>cb.onchange=()=>{DET_VIS[cb.dataset.k]=cb.checked?1:0;save('tkDet',DET_VIS);document.querySelectorAll('tr.det').forEach(r=>r.remove());});
+}
 window.setQ=v=>{$('#q').value=v;q=v;render();};
 $('#q').oninput=e=>{q=e.target.value;render();};
 $('#dens').oninput=e=>{DENS=+e.target.value;document.documentElement.style.setProperty('--rowpad',DENS+'px');save('tkDens',DENS);};
-$('#colsBtn').onclick=()=>{$('#pillmenu').classList.remove('show');$('#colmenu').classList.toggle('show');};
-$('#pillsBtn').onclick=()=>{$('#colmenu').classList.remove('show');$('#pillmenu').classList.toggle('show');};
-$('#resetBtn').onclick=()=>{['tkCols','tkOrder','tkWid','tkDens','tkPills'].forEach(k=>localStorage.removeItem(k));location.reload();};
-document.addEventListener('click',e=>{if(!e.target.closest('#colmenu,#colsBtn'))$('#colmenu').classList.remove('show');if(!e.target.closest('#pillmenu,#pillsBtn'))$('#pillmenu').classList.remove('show');});
+const closeMenus=except=>['#colmenu','#pillmenu','#detmenu'].forEach(id=>{if(id!==except)$(id).classList.remove('show');});
+$('#colsBtn').onclick=()=>{closeMenus('#colmenu');$('#colmenu').classList.toggle('show');};
+$('#pillsBtn').onclick=()=>{closeMenus('#pillmenu');$('#pillmenu').classList.toggle('show');};
+$('#detBtn').onclick=()=>{closeMenus('#detmenu');$('#detmenu').classList.toggle('show');};
+$('#dmBtn').onclick=()=>{DM_ON=DM_ON?0:1;save('tkDM',DM_ON);$('#dmBtn').classList.toggle('off',!DM_ON);dmPanel(LASTMSG);};
+$('#resetBtn').onclick=()=>{['tkCols','tkOrder','tkWid','tkDens','tkPills','tkDet','tkDM'].forEach(k=>localStorage.removeItem(k));location.reload();};
+document.addEventListener('click',e=>{if(!e.target.closest('#colmenu,#colsBtn'))$('#colmenu').classList.remove('show');if(!e.target.closest('#pillmenu,#pillsBtn'))$('#pillmenu').classList.remove('show');if(!e.target.closest('#detmenu,#detBtn'))$('#detmenu').classList.remove('show');});
 function dmPanel(mm){
+  if(!DM_ON){$('#dmwrap').innerHTML='';return;}
   const map=new Map(mm.map(m=>[m.mid,m]));const rootOf=mid=>{let id=mid,c=map.get(mid),seen=new Set([id]);while(c&&c.re&&map.get(c.re)){if(seen.has(c.re))break;seen.add(c.re);id=c.re;c=map.get(id);}return id;};
   const th=new Map();for(const m of mm){const r=rootOf(m.mid);(th.get(r)||th.set(r,[]).get(r)).push(m);}
   const convos=[...th.values()].map(a=>a.sort((x,y)=>x.ts<y.ts?-1:1)).sort((a,b)=>a[a.length-1].ts<b[b.length-1].ts?1:-1).slice(0,30);
@@ -177,8 +195,8 @@ function dmPanel(mm){
 async function load(){
   try{
     const [t,m]=await Promise.all([fetch('/api/tickets').then(r=>r.json()),fetch('/api/messages').then(r=>r.json())]);
-    DATA=t; render(); dmPanel(m);
+    DATA=t; LASTMSG=m; render(); dmPanel(m);
   }catch(e){$('#tbody').innerHTML=`<tr><td>load failed: ${esc(e.message)}</td></tr>`;}
 }
-renderColMenu(); renderPillMenu(); load(); setInterval(load,30000);
+renderColMenu(); renderPillMenu(); renderDetMenu(); $('#dmBtn').classList.toggle('off',!DM_ON); load(); setInterval(load,30000);
 </script></body></html>

← 79770148 board: toggle FLEET TICKETS stat pills on/off (persisted); f  ·  back to Ticket System  ·  board: fetch via location.origin — fixes empty table when op 6ab4daff →