[object Object]

← back to Rentv

desk UX streamline: scope-contextual views (kill dead Map/Command combos), remove redundant Command + fake-geo Map, add live result count

e17a5f0d7a3a64843f525d6ca77cf06f5c2589f0 · 2026-07-22 20:14:14 -0700 · Steve

Files touched

Diff

commit e17a5f0d7a3a64843f525d6ca77cf06f5c2589f0
Author: Steve <steve@designerwallcoverings.com>
Date:   Wed Jul 22 20:14:14 2026 -0700

    desk UX streamline: scope-contextual views (kill dead Map/Command combos), remove redundant Command + fake-geo Map, add live result count
---
 public/desk.html | 43 ++++++++++++++++++-------------------------
 1 file changed, 18 insertions(+), 25 deletions(-)

diff --git a/public/desk.html b/public/desk.html
index f72b7794..1703f7a9 100644
--- a/public/desk.html
+++ b/public/desk.html
@@ -74,9 +74,7 @@
   <nav class="rail" id="rail">
     <div class="h">View</div>
     <button data-v="directory" class="on"><span class="i">▤</span>Directory</button>
-    <button data-v="map"><span class="i">◈</span>Map</button>
     <button data-v="analytics"><span class="i">▮</span>Analytics</button>
-    <button data-v="command"><span class="i">⌘</span>Command</button>
     <div class="h" style="margin-top:10px">Scope</div>
     <button data-s="brokers" class="on scope"><span class="i">👤</span>Brokers <span class="c" id="cB">0</span></button>
     <button data-s="firms" class="scope"><span class="i">🏢</span>Firms <span class="c" id="cF">0</span></button>
@@ -86,9 +84,7 @@
   </nav>
   <div class="content">
     <div class="view on" id="v-directory"></div>
-    <div class="view" id="v-map"></div>
     <div class="view" id="v-analytics"></div>
-    <div class="view" id="v-command"></div>
   </div>
 </div>
 <script>
@@ -149,15 +145,25 @@ async function liveRows(scope,term){
   return scopeData().filter(x=>JSON.stringify(x).toLowerCase().includes(term.toLowerCase()));
 }
 // rail
-$('rail').addEventListener('click',e=>{const btn=e.target.closest('button');if(!btn)return;
+// Views are contextual to the scope — a scope only exposes the views that actually render ITS data,
+// so you can never land on a mismatched combo (e.g. Sublease + a firm-only chart).
+const SCOPE_VIEWS={brokers:['directory','analytics'],firms:['directory','analytics'],owners:['directory'],sublease:['directory']};
+function applyScopeViews(){
+  const ok=SCOPE_VIEWS[D.scope]||['directory'];
+  document.querySelectorAll('#rail [data-v]').forEach(b=>{const good=ok.includes(b.dataset.v);b.disabled=!good;b.style.opacity=good?'':'.32';b.style.cursor=good?'':'not-allowed';});
+  if(!ok.includes(D.view)){D.view='directory';
+    document.querySelectorAll('#rail [data-v]').forEach(x=>x.classList.toggle('on',x.dataset.v==='directory'));
+    document.querySelectorAll('.view').forEach(v=>v.classList.toggle('on',v.id==='v-directory'));}
+}
+$('rail').addEventListener('click',e=>{const btn=e.target.closest('button');if(!btn||btn.disabled)return;
   if(btn.dataset.v){document.querySelectorAll('#rail [data-v]').forEach(x=>x.classList.remove('on'));btn.classList.add('on');D.view=btn.dataset.v;document.querySelectorAll('.view').forEach(v=>v.classList.remove('on'));$('v-'+D.view).classList.add('on');}
   if(btn.dataset.s){document.querySelectorAll('#rail .scope').forEach(x=>x.classList.remove('on'));btn.classList.add('on');D.scope=btn.dataset.s;D.selDir=0;renderAll();}
 });
-function renderAll(){directory();map();analytics();command();}
+function renderAll(){directory();analytics();applyScopeViews();}
 // ── Directory (master-detail) ──
 function directory(){
   const rows=scopeData();
-  $('v-directory').innerHTML=`<div class="md"><div class="list"><div class="search"><input id="dq" placeholder="Search ${D.scope}…"></div><div class="rows" id="drows"></div></div><div class="detail" id="ddetail"></div></div>`;
+  $('v-directory').innerHTML=`<div class="md"><div class="list"><div class="search"><input id="dq" placeholder="Search ${D.scope}…"><div class="fresh" id="dcount" style="padding:7px 2px 0"></div></div><div class="rows" id="drows"></div></div><div class="detail" id="ddetail"></div></div>`;
   const q='';
   function list(){let r=rows.slice();const v=($('dq').value||'').toLowerCase();if(v)r=r.filter(x=>JSON.stringify(x).toLowerCase().includes(v));return r;}
   function detail(x){if(!x){$('ddetail').innerHTML='';return;}
@@ -168,20 +174,15 @@ function directory(){
   const emptyMsg=D.scope==='sublease'
     ? '<div class="fresh" style="padding:20px 16px;line-height:1.6">No sublease listings yet.<br>Inventory is entered by the RENTV desk from live broker deals — <a class="field-link" href="/desk-admin">add a listing →</a></div>'
     : '<div class="fresh">No matches in the registry.</div>';
-  async function draw(){const r=await liveRows(D.scope,$('dq').value);$('drows').innerHTML=r.map((x,i)=>`<div class="row${i===D.selDir?' sel':''}" data-i="${i}"><div class="nm">${esc(nm(x))}</div><div class="f">${esc(sub(D.scope,x))}</div></div>`).join('')||emptyMsg;
+  async function draw(){const r=await liveRows(D.scope,$('dq').value);
+    const tot=D.scope==='brokers'?D.totalB:D.scope==='firms'?D.totalF:D.scope==='sublease'?D.totalS:D.owners.length;
+    const term=($('dq').value||'').trim();
+    $('dcount').textContent=r.length?(term?`${r.length} match${r.length===1?'':'es'}`:`Showing ${r.length.toLocaleString()} of ${Number(tot).toLocaleString()}`):'';
+    $('drows').innerHTML=r.map((x,i)=>`<div class="row${i===D.selDir?' sel':''}" data-i="${i}"><div class="nm">${esc(nm(x))}</div><div class="f">${esc(sub(D.scope,x))}</div></div>`).join('')||emptyMsg;
     if(!r.length&&D.scope==='sublease'){$('ddetail').innerHTML='<div class="fresh" style="padding:30px 36px;line-height:1.7;max-width:520px">This desk surfaces <b style="color:#e7ecf1">real sublease space</b> — sqft, asking rate, term, the sublandlord, and the listing broker, each with click-outs to the listing, map, and broker. It fills as the RENTV team logs deals from their broker network.</div>';return;}
     $('drows').querySelectorAll('.row').forEach(row=>row.onclick=()=>{D.selDir=+row.dataset.i;$('drows').querySelectorAll('.row').forEach(x=>x.classList.remove('sel'));row.classList.add('sel');detail(r[D.selDir]);});detail(r[D.selDir]||r[0]);}
   $('dq').oninput=debounce(()=>{D.selDir=0;draw();},300);draw();
 }
-// ── Map ──
-function map(){
-  const mq=D.owners.slice(0,10);
-  $('v-map').innerHTML=`<div class="mapv"><div class="map" id="mapc"><div class="g"></div></div><div class="mside" id="mside"></div></div>`;
-  mq.forEach((m,i)=>{const x=12+((i*41)%76),y=18+((i*31)%62);const p=document.createElement('div');p.className='pin'+(i===0?' sel':'');p.style.left=x+'%';p.style.top=y+'%';p.dataset.i=i;p.innerHTML=`<div class="d"></div><div class="l">${shortUsd(m.assessed_total)}</div>`;$('mapc').appendChild(p);});
-  function show(m){const ad=[m.address,m.city].filter(Boolean).join(', ');$('mside').innerHTML=`<div class="k">${esc(m.type_label||'Asset')} · ${esc(m.use_desc||'')}</div><h2>${esc(m.address||m.ain)}</h2><div style="color:#9aa4ad;margin-bottom:12px">${esc(ad)}</div><div class="acts">${actions('owners',m)}</div><div style="margin-top:16px"><div class="r"><span>Assessed</span><b>${shortUsd(m.assessed_total)}</b></div><div class="r"><span>SqFt</span><b>${(m.sqft||0).toLocaleString()}</b></div><div class="r"><span>Built</span><b>${m.year_built||'—'}</b></div><div class="r"><span>Recorded</span><b>${dstr(m.recording_date)}</b></div></div>`;}
-  if(mq[0])show(mq[0]);
-  $('mapc').querySelectorAll('.pin').forEach(p=>p.onclick=()=>{$('mapc').querySelectorAll('.pin').forEach(x=>x.classList.remove('sel'));p.classList.add('sel');show(mq[+p.dataset.i]);});
-}
 // ── Analytics ──
 function analytics(){
   const top=D.firms.slice().sort((a,b)=>(b.agent_count||0)-(a.agent_count||0)).slice(0,9);const max=top[0]?.agent_count||1;
@@ -191,13 +192,5 @@ function analytics(){
   }</div></div></div>`;
   $('bars').querySelectorAll('.barwrap').forEach(w=>w.querySelector('.bar').onclick=e=>{if(e.target.closest('a'))return;w.classList.toggle('open');});
 }
-// ── Command ──
-function command(){
-  $('v-command').innerHTML=`<div class="cmd"><div class="box"><div class="in"><span style="color:#8b939b;font-size:18px">⌘</span><input id="cq" placeholder="Search ${D.scope}…"></div><div class="results" id="cres"></div></div></div>`;
-  async function draw(){let r=(await liveRows(D.scope,$('cq').value)).slice(0,40);
-    $('cres').innerHTML=r.map((x,i)=>`<div class="res${i===0?' hot':''}"><div class="nm">${esc(nm(x))}</div><div class="f">${esc(sub(D.scope,x))}${loc(x)?' · '+esc(loc(x)):''}</div><div class="exp"><div class="acts">${actions(D.scope,x)}</div></div></div>`).join('')||'<div class="res"><div class="f">No matches.</div></div>';
-    $('cres').querySelectorAll('.res').forEach(el=>el.onclick=e=>{if(e.target.closest('a'))return;el.classList.toggle('open');});}
-  $('cq').oninput=debounce(draw,300);draw();
-}
 </script>
 </body></html>

← 9e29c2bb auto-save: 2026-07-22T19:46:22 (3 files) — data/markets.json  ·  back to Rentv  ·  auto-save: 2026-07-22T20:16:30 (3 files) — data/markets.json 85dbfc62 →