← back to Majilite Onboard
viewer standard: add Grid/List toggle + per-field on/off menu to internal record-grid viewer
f5257e6597534d461a25d5d6cbc136d154d0a8e8 · 2026-08-12 08:53:18 -0700 · Steve
Files touched
Diff
commit f5257e6597534d461a25d5d6cbc136d154d0a8e8
Author: Steve <steve@designerwallcoverings.com>
Date: Wed Aug 12 08:53:18 2026 -0700
viewer standard: add Grid/List toggle + per-field on/off menu to internal record-grid viewer
---
public/index.html | 85 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 84 insertions(+), 1 deletion(-)
diff --git a/public/index.html b/public/index.html
index c54378a..4ea8f97 100644
--- a/public/index.html
+++ b/public/index.html
@@ -59,6 +59,38 @@
.when{font-size:10px;color:var(--dim);margin-top:6px}
.spec-badge{position:absolute;top:6px;right:6px;font-size:9px;background:#0b0d10cc;border:1px solid var(--line);color:var(--teal);border-radius:6px;padding:1px 6px}
.imgonly .meta{display:none}
+ /* ── List view ── */
+ .grid.list{display:flex;flex-direction:column;gap:6px}
+ .grid.list .card{display:grid;grid-template-columns:52px 1fr auto;align-items:center;border-radius:8px;gap:10px;padding:6px 12px 6px 6px}
+ .grid.list .thumb{width:52px;height:52px;min-width:52px;border-radius:6px;overflow:hidden}
+ .grid.list .thumb img{width:100%;height:100%;object-fit:cover}
+ .grid.list .meta{display:flex;flex-direction:column;gap:2px;padding:0}
+ .grid.list .meta .t{font-size:13px}
+ .grid.list .meta .sku{font-size:11px}
+ .grid.list .meta .row{margin-top:4px}
+ .grid.list .meta .when{margin-top:0}
+ .grid.list .spec-badge{position:static;font-size:9px}
+ /* ── View toggle buttons ── */
+ .view-btn{background:var(--panel);border:1px solid var(--line);color:var(--dim);border-radius:7px;padding:6px 11px;cursor:pointer;font-size:12px;transition:border-color .12s,color .12s}
+ .view-btn:hover{border-color:var(--teal);color:var(--ink)}
+ .view-btn.active{border-color:var(--teal);color:var(--teal)}
+ /* ── Fields ▾ popover ── */
+ .fieldmenu{position:relative}
+ .fbtn{background:var(--panel);border:1px solid var(--line);color:var(--dim);border-radius:7px;padding:6px 11px;cursor:pointer;font-size:12px;white-space:nowrap}
+ .fbtn:hover{border-color:var(--teal);color:var(--ink)}
+ .fpop{position:absolute;top:calc(100% + 6px);left:0;background:#0f1216;border:1px solid var(--line);border-radius:8px;box-shadow:0 10px 30px #0008;padding:8px 4px;z-index:30;min-width:180px}
+ .fpop[hidden]{display:none}
+ .fpop label{display:flex;align-items:center;gap:9px;padding:6px 12px;font-size:12.5px;color:var(--dim);cursor:pointer;border-radius:6px;white-space:nowrap}
+ .fpop label:hover{background:var(--panel);color:var(--ink)}
+ .fpop input{accent-color:var(--teal);margin:0}
+ /* ── field hide classes (grid only) ── */
+ body.hf-thumb .grid:not(.list) .thumb{display:none}
+ body.hf-title .grid:not(.list) .meta .t{display:none}
+ body.hf-sku .grid:not(.list) .meta .sku{display:none}
+ body.hf-color .grid:not(.list) .meta .row .dot,
+ body.hf-color .grid:not(.list) .meta .row .chip[data-f="family"]{display:none}
+ body.hf-pattern .grid:not(.list) .meta .row .chip[data-f="pattern"]{display:none}
+ body.hf-when .grid:not(.list) .meta .when{display:none}
/* drawer */
#drawer{position:fixed;top:0;right:0;height:100vh;width:420px;max-width:92vw;background:var(--panel);border-left:1px solid var(--line);transform:translateX(100%);transition:transform .18s;overflow:auto;z-index:20}
#drawer.open{transform:none}
@@ -112,10 +144,25 @@
<option value="pattern">Pattern A→Z</option>
<option value="title">Title A→Z</option>
<option value="sku">SKU A→Z</option>
+ <option value="color">Color A→Z</option>
+ <option value="vendor">Vendor A→Z</option>
</select>
</label>
<label>Density <input type="range" id="dens" min="3" max="9" value="5"/></label>
<label><input type="checkbox" id="imgonly"/> image-only</label>
+ <button class="view-btn" id="viewgrid" title="Grid view">⊞ Grid</button>
+ <button class="view-btn" id="viewlist" title="List view">☰ List</button>
+ <div class="fieldmenu">
+ <button class="fbtn" id="fieldbtn">Fields ▾</button>
+ <div class="fpop" id="fpop" hidden>
+ <label><input type="checkbox" data-mf="thumb" checked> Image</label>
+ <label><input type="checkbox" data-mf="title" checked> Title / Color</label>
+ <label><input type="checkbox" data-mf="sku" checked> SKU</label>
+ <label><input type="checkbox" data-mf="color" checked> Color swatch</label>
+ <label><input type="checkbox" data-mf="pattern" checked> Pattern chip</label>
+ <label><input type="checkbox" data-mf="when" checked> Created date</label>
+ </div>
+ </div>
<button class="btn" id="reset">Reset</button>
</div>
<div class="grid" id="grid"></div>
@@ -219,7 +266,9 @@ function apply(){
pattern:(a,b)=>a.pattern.localeCompare(b.pattern)||a.color.localeCompare(b.color),
title:(a,b)=>a.title.localeCompare(b.title),
family:(a,b)=>a.color_family.localeCompare(b.color_family)||a.title.localeCompare(b.title),
- light:(a,b)=>hexLum(b.color_hex)-hexLum(a.color_hex), dark:(a,b)=>hexLum(a.color_hex)-hexLum(b.color_hex) }[s];
+ light:(a,b)=>hexLum(b.color_hex)-hexLum(a.color_hex), dark:(a,b)=>hexLum(a.color_hex)-hexLum(b.color_hex),
+ color:(a,b)=>String(a.color||'').localeCompare(String(b.color||'')),
+ vendor:(a,b)=>String(a.vendor||'').localeCompare(String(b.vendor||'')) }[s];
VIEW.sort(cmp);
$('#grid').innerHTML=VIEW.map(p=>cardHTML({...p,_new:false})).join('');
}
@@ -258,6 +307,40 @@ $('#reset').addEventListener('click',()=>{FILTER={family:null,pattern:null,statu
$('#run').addEventListener('click',()=>runImport(45));
$('#replay').addEventListener('click',()=>runImport(20));
+// ── VIEW TOGGLE ────────────────────────────────────────────────────
+const GRID_EL=$('#grid');
+function setView(v){
+ GRID_EL.classList.toggle('list',v==='list');
+ $('#viewgrid').classList.toggle('active',v==='grid');
+ $('#viewlist').classList.toggle('active',v==='list');
+ // hide density slider in list mode (columns irrelevant)
+ $('#dens').parentElement.style.opacity=v==='list'?'.35':'1';
+ $('#dens').disabled=v==='list';
+ try{localStorage.setItem(LS('view'),v);}catch(_){}
+}
+$('#viewgrid').addEventListener('click',()=>setView('grid'));
+$('#viewlist').addEventListener('click',()=>setView('list'));
+setView(localStorage.getItem(LS('view'))||'grid');
+
+// ── FIELDS MENU ───────────────────────────────────────────────────
+const MFIELDS_DEFAULT={thumb:true,title:true,sku:true,color:true,pattern:true,when:true};
+let MFIELDS=Object.assign({},MFIELDS_DEFAULT);
+try{const s=localStorage.getItem(LS('fields'));if(s)MFIELDS=Object.assign({},MFIELDS_DEFAULT,JSON.parse(s));}catch(_){}
+function applyMFields(){
+ for(const k of Object.keys(MFIELDS_DEFAULT)){
+ document.body.classList.toggle('hf-'+k,!MFIELDS[k]);
+ }
+ document.querySelectorAll('#fpop input[data-mf]').forEach(cb=>{cb.checked=!!MFIELDS[cb.dataset.mf];});
+ try{localStorage.setItem(LS('fields'),JSON.stringify(MFIELDS));}catch(_){}
+}
+document.querySelectorAll('#fpop input[data-mf]').forEach(cb=>{
+ cb.addEventListener('change',()=>{MFIELDS[cb.dataset.mf]=cb.checked;applyMFields();});
+});
+applyMFields();
+// popover open/close
+$('#fieldbtn').addEventListener('click',e=>{e.stopPropagation();$('#fpop').hidden=!$('#fpop').hidden;});
+document.addEventListener('click',e=>{if(!e.target.closest('.fieldmenu'))$('#fpop').hidden=true;});
+
// ---- boot ----
function seedColl(c){COLL=c;$('#pfx').textContent=c.sku_prefix;$('#pmode').textContent=c.onboard_as==='sample'?('$'+c.sample_price+' sample'):(c.display_prices?'priced':'quote-only');}
if(window.__BOOT__&&window.__BOOT__.collection){seedColl(window.__BOOT__.collection);} else {fetch('/api/collection',{credentials:'same-origin'}).then(r=>r.json()).then(seedColl);}
← cca8690 chore: v-patch (session close — DWCC viewer regen)
·
back to Majilite Onboard
·
nav-agent: universal grid-controls drop-in on internal viewe a9e93ff →