← back to Prestige Car Wash
Growth admin: tab-aware sort control on all data grids (sort+density rule; dtd:A, contrarian-gated)
2c10e9878b4417fa373beca78dfec7e1d981aa8f · 2026-07-26 07:55:32 -0700 · steve@designerwallcoverings.com
Files touched
M public/admin/index.html
Diff
commit 2c10e9878b4417fa373beca78dfec7e1d981aa8f
Author: steve@designerwallcoverings.com <steve@designerwallcoverings.com>
Date: Sun Jul 26 07:55:32 2026 -0700
Growth admin: tab-aware sort control on all data grids (sort+density rule; dtd:A, contrarian-gated)
---
public/admin/index.html | 39 ++++++++++++++++++++++++++++++++++++++-
1 file changed, 38 insertions(+), 1 deletion(-)
diff --git a/public/admin/index.html b/public/admin/index.html
index f1df69e..a7885af 100644
--- a/public/admin/index.html
+++ b/public/admin/index.html
@@ -89,6 +89,7 @@
<div class="controls" id="ctl" style="margin-top:16px">
<input type="search" id="q" placeholder="Search…">
+ <label>Sort <select id="sort"></select></label>
<label>Density <input type="range" id="density" min="2" max="6" step="1"></label>
<span id="count" style="color:var(--mut);font-size:13px;font-weight:600"></span>
</div>
@@ -110,6 +111,7 @@ function setDensity(v){v=Number(v);document.documentElement.style.setProperty('-
document.documentElement.style.setProperty('--fs',Math.max(.85,Math.min(1.2,1+(3-v)*0.05)).toFixed(3));}
$('#density').oninput=e=>{setDensity(e.target.value);localStorage.setItem('pcwadmin.density',e.target.value)};
let t;$('#q').oninput=()=>{clearTimeout(t);t=setTimeout(render,200)};
+$('#sort').onchange=e=>{localStorage.setItem('pcwadmin.sort.'+CUR,e.target.value);render()};
// ---- tab definitions -------------------------------------------------------
const TABS=[
@@ -166,7 +168,7 @@ let CMP={sel:null,plats:new Set(),media:[],tpl:{platforms:[],hashtag_sets:{},tem
$('#tabs').innerHTML=TABS.map(t=>`<button data-id="${t.id}" class="${t.id===CUR?'on':''}" onclick="go('${t.id}')">${t.label}</button>`).join('');
window.go=id=>{CUR=id;document.querySelectorAll('#tabs button').forEach(b=>b.classList.toggle('on',b.dataset.id===id));
- $('#q').value='';render();};
+ $('#q').value='';$('#sort').dataset.tab='';render();};
function cardWrap(inner){return `<div class="card"><div class="body">${inner}</div></div>`;}
// Expandable SWOT · costs · payback panel for an idea card. Renders only the
@@ -208,6 +210,31 @@ function analysisPanel(s){
return `<details class="analysis"><summary>${summary}</summary><div class="analysis-body">${html}</div></details>`;
}
const gridWrap=cards=>`<div class="grid">${cards}</div>`;
+// --- tab-aware sort (dtd:A, 2026-07-26) — every api grid gets Newest/Oldest +
+// domain-specific keys + Title A–Z; selection persists per-tab in localStorage.
+const SORTKEYS={
+ suggestions:[{label:'Impact ↓',field:'impact',numeric:true,dir:-1},{label:'Effort ↑',field:'effort',numeric:true,dir:1},{label:'Priority ↓',calc:r=>(+r.impact||0)*2-(+r.effort||0),numeric:true,dir:-1}],
+ competitors:[{label:'Rating ↓',field:'rating',numeric:true,dir:-1},{label:'Reviews ↓',field:'reviews',numeric:true,dir:-1}],
+ services:[{label:'Price ↑',field:'price',numeric:true,dir:1},{label:'Price ↓',field:'price',numeric:true,dir:-1}],
+ ads:[{label:'Budget ↓',field:'budget_month',numeric:true,dir:-1}],
+ holidays:[{label:'Date ↑',field:'date',dir:1}],
+ directories:[{label:'Priority',field:'priority',dir:1}]
+};
+const TITLEFIELD={suggestions:'title',competitors:'name',services:'name',ads:'headline',holidays:'name',directories:'name',leads:'name'};
+function sortOptsFor(id){
+ const opts=[{label:'Newest',field:'created_at',dir:-1},{label:'Oldest',field:'created_at',dir:1},...(SORTKEYS[id]||[])];
+ if(TITLEFIELD[id]) opts.push({label:'Title A–Z',field:TITLEFIELD[id],dir:1});
+ return opts;
+}
+function cmpBy(opt){
+ const get=opt.calc?opt.calc:(r=>r[opt.field]);
+ return (a,b)=>{
+ let x=get(a),y=get(b);
+ if(opt.numeric){x=Number(x)||0;y=Number(y)||0;return (x-y)*opt.dir;}
+ x=String(x==null?'':x).toLowerCase();y=String(y==null?'':y).toLowerCase();
+ return (x<y?-1:x>y?1:0)*opt.dir;
+ };
+}
function render(){
const tab=TABS.find(t=>t.id===CUR);
@@ -224,6 +251,16 @@ function render(){
const q=$('#q').value.trim().toLowerCase();
let list=rows;
if(q&&tab.search) list=rows.filter(r=>tab.search.some(f=>String(r[f]||'').toLowerCase().includes(q)));
+ // tab-aware sort: (re)build the sort select on tab change, then apply the chosen key
+ const opts=sortOptsFor(tab.id), sortEl=$('#sort');
+ if(sortEl.dataset.tab!==tab.id){
+ sortEl.innerHTML=opts.map((o,i)=>`<option value="${i}">${enc(o.label)}</option>`).join('');
+ const saved=localStorage.getItem('pcwadmin.sort.'+tab.id);
+ sortEl.value=(saved!=null&&Number(saved)<opts.length)?saved:'0';
+ sortEl.dataset.tab=tab.id;
+ }
+ const opt=opts[Number(sortEl.value)||0]||opts[0];
+ list=[...list].sort(cmpBy(opt));
$('#count').textContent=list.length+' item'+(list.length!==1?'s':'');
$('#view').innerHTML=list.length?gridWrap(list.map(tab.card).join('')):'<p style="color:var(--mut)">Nothing here yet.</p>';
});
← 6edbd5d auto-save: 2026-07-26T07:41:30 (2 files) — public/admin/inde
·
back to Prestige Car Wash
·
Growth admin: per-idea Action Brief (printable/copyable), dt 060a5b2 →