[object Object]

← back to Vendor Agents Viewer

viewer standard: add Grid/List toggle + per-field on/off menu to internal record-grid viewer

c19c33c606bc9b2068560cca348fcc55c7e3ea18 · 2026-08-12 08:53:19 -0700 · Steve

Files touched

Diff

commit c19c33c606bc9b2068560cca348fcc55c7e3ea18
Author: Steve <steve@designerwallcoverings.com>
Date:   Wed Aug 12 08:53:19 2026 -0700

    viewer standard: add Grid/List toggle + per-field on/off menu to internal record-grid viewer
---
 public/index.html | 81 ++++++++++++++++++++++++++++++++++++++++++++++++++-----
 1 file changed, 74 insertions(+), 7 deletions(-)

diff --git a/public/index.html b/public/index.html
index 6ae9e76..3077e0a 100644
--- a/public/index.html
+++ b/public/index.html
@@ -15,7 +15,35 @@
   .controls label{color:var(--mut);font-size:12px;display:flex;gap:6px;align-items:center}
   select,input[type=search]{background:#12141a;color:var(--txt);border:1px solid var(--line);border-radius:7px;padding:6px 9px;font-size:13px}
   input[type=range]{accent-color:var(--accent)}
+  /* view toggle */
+  .vav-vseg{display:flex;border:1px solid var(--line);border-radius:7px;overflow:hidden}
+  .vav-vseg button{padding:5px 11px;background:#12141a;border:none;font-size:12px;color:var(--mut);cursor:pointer;font-family:inherit}
+  .vav-vseg button.on{background:var(--accent);color:#0f1115}
+  /* fields menu */
+  .vav-fieldmenu{position:relative}
+  .vav-fbtn{background:#12141a;color:var(--mut);border:1px solid var(--line);border-radius:7px;padding:6px 10px;font-size:12px;cursor:pointer}
+  .vav-fbtn:hover{border-color:var(--accent);color:var(--txt)}
+  .vav-fpop{position:absolute;top:calc(100% + 6px);right:0;background:#12141a;border:1px solid var(--line);border-radius:9px;box-shadow:0 10px 28px rgba(0,0,0,.6);padding:8px 4px;z-index:30;min-width:175px}
+  .vav-fpop[hidden]{display:none}
+  .vav-fpop label{display:flex;align-items:center;gap:8px;padding:6px 12px;font-size:12px;color:var(--mut);cursor:pointer;white-space:nowrap}
+  .vav-fpop label:hover{color:var(--txt)}
+  .vav-fpop input{accent-color:var(--accent);width:13px;height:13px;margin:0;padding:0}
+  /* grid view */
   .grid{display:grid;grid-template-columns:repeat(var(--cols),1fr);gap:14px;padding:18px 22px}
+  /* list view */
+  .grid.list{display:flex;flex-direction:column;gap:6px;padding:14px 22px}
+  .grid.list .card{flex-direction:row;align-items:flex-start;border-radius:8px;padding:10px 14px;gap:14px;flex-wrap:wrap}
+  .grid.list .card .row1{flex:1;min-width:180px}
+  .grid.list .card .desc{flex:2;min-width:160px;min-height:0}
+  .grid.list .card .kv{flex:2;min-width:180px}
+  .grid.list .card .btns{flex:1;min-width:120px;margin-top:0}
+  /* field visibility */
+  body.vav-hf-desc [data-f="vav-desc"]{display:none!important}
+  body.vav-hf-kv [data-f="vav-kv"]{display:none!important}
+  body.vav-hf-when [data-f="vav-when"]{display:none!important}
+  body.vav-hf-scraper [data-f="vav-scraper"]{display:none!important}
+  body.vav-hf-btns [data-f="vav-btns"]{display:none!important}
+  body.vav-hf-badge [data-f="vav-badge"]{display:none!important}
   .card{background:var(--card);border:1px solid var(--line);border-radius:12px;padding:14px;display:flex;flex-direction:column;gap:8px}
   .card.on{border-left:3px solid var(--on)} .card.off{border-left:3px solid var(--off)} .card.conflict{border-left:3px solid var(--conf)}
   .row1{display:flex;justify-content:space-between;align-items:flex-start;gap:8px}
@@ -52,6 +80,14 @@
     </label>
     <label>Show <select id="filter"><option value="all">All</option><option value="online">Online</option><option value="offline">Offline</option><option value="conflict">Port Conflict</option></select></label>
     <label>Density <input type="range" id="dens" min="2" max="7" value="4"></label>
+    <div class="vav-vseg">
+      <button id="vavVGrid" class="on" title="Grid view">⊞ Grid</button>
+      <button id="vavVList" title="List view">☰ List</button>
+    </div>
+    <div class="vav-fieldmenu">
+      <button class="vav-fbtn" id="vavFieldsBtn" aria-haspopup="true" aria-expanded="false">Fields ▾</button>
+      <div class="vav-fpop" id="vavFpop" hidden role="menu"></div>
+    </div>
     <button class="act" onclick="load()">↻ Refresh</button>
   </div>
 </header>
@@ -96,19 +132,19 @@ function render(){
     <div class="card ${x.status}">
       <div class="row1">
         <div><div class="nm">${x.agent_name||'(unnamed)'}</div><div class="ven">${x.vendor_name?drill('vendor',x.vendor_name,x.vendor_name):''}${x.agent_port?' · :'+x.agent_port:''}</div></div>
-        ${drill('status',x.status,x.status,'badge '+x.status)}
+        <span data-f="vav-badge">${drill('status',x.status,x.status,'badge '+x.status)}</span>
       </div>
-      <div class="desc">${x.description||''}</div>
-      <div class="kv">
+      <div class="desc" data-f="vav-desc">${x.description||''}</div>
+      <div class="kv" data-f="vav-kv">
         <span>Catalog <b>${(x.catalog_count||0).toLocaleString()}</b></span>
         <span>Shopify <b>${(x.shopify_count||0).toLocaleString()}</b></span>
         <span>Images <b>${(x.catalog_with_images||0).toLocaleString()}</b></span>
         ${x.website_score!=null?`<span>Site <b>${x.website_score}</b></span>`:''}
       </div>
-      ${x.conflict_owner?`<div class="when" style="color:var(--conf)">\u26a0 port :${x.agent_port} reused by <b>${x.conflict_owner}</b> \u2014 agent not running</div>`:""}
-      <div class="when" title="${x.updated_at||''}">🕓 updated ${fmtDate(x.updated_at)}</div>
-      <div class="kv"><span>Scraper <b style="color:${x.launchable?'var(--on)':'var(--off)'}">${x.scraper_id||(x.skip_catalog_scrape?(x.is_private_label?'n/a — private label':'n/a — no source'):'none built')}</b></span></div>
-      <div class="btns">
+      ${x.conflict_owner?`<div class="when" data-f="vav-when" style="color:var(--conf)">\u26a0 port :${x.agent_port} reused by <b>${x.conflict_owner}</b> \u2014 agent not running</div>`:""}
+      <div class="when" data-f="vav-when" title="${x.updated_at||''}">🕓 updated ${fmtDate(x.updated_at)}</div>
+      <div class="kv" data-f="vav-scraper"><span>Scraper <b style="color:${x.launchable?'var(--on)':'var(--off)'}">${x.scraper_id||(x.skip_catalog_scrape?(x.is_private_label?'n/a — private label':'n/a — no source'):'none built')}</b></span></div>
+      <div class="btns" data-f="vav-btns">
         ${x.url?`<a class="act go" href="${x.url}" target="_blank">Open</a>`:''}
         ${x.status==='online'
           ? `<button class="act" onclick='act("restart",{vendor_code:${JSON.stringify(x.vendor_code)}},"restart ${x.agent_name}")'>Restart</button>
@@ -124,6 +160,37 @@ async function load(){
   $('#stats').innerHTML=`<span><b>${s.total}</b> agents</span><span><b style="color:var(--on)">${s.online}</b> online</span><span><b>${s.offline}</b> offline</span><span><b style="color:#f59e0b">${s.conflict||0}</b> port-conflict</span><span><b>${s.totalCatalog.toLocaleString()}</b> catalog SKUs</span><span><b>${s.totalShopify.toLocaleString()}</b> on Shopify</span>`;
   render();
 }
+// ── Grid / List toggle + Fields menu ──
+const VAV_FIELDS=[['desc','Description'],['kv','Counts (Catalog/Shopify)'],['when','Updated Date'],['scraper','Scraper ID'],['btns','Action Buttons'],['badge','Status Badge']];
+const VAV_FIELDS_DEF=Object.fromEntries(VAV_FIELDS.map(([k])=>[k,true]));
+let vavFieldVis=Object.assign({},VAV_FIELDS_DEF,JSON.parse(localStorage.getItem('vav_fields')||'{}'));
+function applyVavFields(){
+  VAV_FIELDS.forEach(([k])=>document.body.classList.toggle('vav-hf-'+k,!vavFieldVis[k]));
+  document.querySelectorAll('#vavFpop input[data-field]').forEach(cb=>cb.checked=!!vavFieldVis[cb.dataset.field]);
+  localStorage.setItem('vav_fields',JSON.stringify(vavFieldVis));
+}
+function buildVavFieldMenu(){
+  const fp=document.getElementById('vavFpop');
+  fp.innerHTML=VAV_FIELDS.map(([k,lbl])=>`<label><input type="checkbox" data-field="${k}"${vavFieldVis[k]?' checked':''}> ${lbl}</label>`).join('');
+  fp.querySelectorAll('input').forEach(cb=>cb.addEventListener('change',()=>{vavFieldVis[cb.dataset.field]=cb.checked;localStorage.setItem('vav_fields',JSON.stringify(vavFieldVis));applyVavFields();}));
+}
+function setVavView(v){
+  document.getElementById('grid').classList.toggle('list',v==='list');
+  document.getElementById('vavVGrid').classList.toggle('on',v==='grid');
+  document.getElementById('vavVList').classList.toggle('on',v==='list');
+  localStorage.setItem('vav_view',v);
+}
+document.getElementById('vavVGrid').onclick=()=>setVavView('grid');
+document.getElementById('vavVList').onclick=()=>setVavView('list');
+setVavView(localStorage.getItem('vav_view')||'grid');
+buildVavFieldMenu();
+applyVavFields();
+(function(){
+  const btn=document.getElementById('vavFieldsBtn'), fp=document.getElementById('vavFpop');
+  btn.addEventListener('click',e=>{e.stopPropagation();const h=fp.hidden;fp.hidden=!h;btn.setAttribute('aria-expanded',String(h));});
+  document.addEventListener('click',()=>{if(!fp.hidden){fp.hidden=true;btn.setAttribute('aria-expanded','false');}});
+  fp.addEventListener('click',e=>e.stopPropagation());
+})();
 // persist controls
 ['sort','filter','dens'].forEach(id=>{const el=$('#'+id);const k='vav_'+id;if(localStorage[k])el.value=localStorage[k];el.addEventListener('input',()=>{localStorage[k]=el.value;if(id==='dens')document.documentElement.style.setProperty('--cols',el.value);else render();});});
 document.documentElement.style.setProperty('--cols',$('#dens').value);

← c4e0072 vendor-agents-viewer: href-drill — data points now filter-li  ·  back to Vendor Agents Viewer  ·  TK-11438: migrate postgres pool to unix socket on darwin f19f192 →