[object Object]

← back to Dw Pitch Followup

auto-save: 2026-07-14T11:00:40 (1 files) — public/index.html

d4a8d30ed088a3bb449cdc8c85669c09326278d8 · 2026-07-14 11:00:42 -0700 · Steve Abrams

Files touched

Diff

commit d4a8d30ed088a3bb449cdc8c85669c09326278d8
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Tue Jul 14 11:00:42 2026 -0700

    auto-save: 2026-07-14T11:00:40 (1 files) — public/index.html
---
 public/index.html | 31 +++++++++++++++++++++++++++----
 1 file changed, 27 insertions(+), 4 deletions(-)

diff --git a/public/index.html b/public/index.html
index 7a39192..988571a 100644
--- a/public/index.html
+++ b/public/index.html
@@ -116,7 +116,7 @@
   <span id="bucketWrap"><label>Show</label>
     <select id="bucket">
       <option value="all">All types</option><option value="list1">Samples Sent ≥90%</option>
-      <option value="list2">Invoiced · No Order</option><option value="list3">Vendor Memos Overdue</option>
+      <option value="list2">Invoiced · No Order</option>
     </select>
   </span>
   <span><label>Size</label><input type="range" id="density" min="1" max="5" step="1" /></span>
@@ -152,7 +152,11 @@
 </aside>
 
 <script>
-const LISTS = ['list1','list2','list3'];
+// PARKED lists are hidden from the board + bucket dropdown but still built server-side.
+// Un-park by removing the id from PARKED (one-liner). Vendor Memos Overdue = list3.
+const ALL_LISTS = ['list1','list2','list3'];
+const PARKED = new Set(['list3']);
+const LISTS = ALL_LISTS.filter(l=>!PARKED.has(l));
 const API = p => `${location.protocol}//${location.host}${p}`;
 const FM_HOST = 'designerwallcoverings.account.filemaker-cloud.com';
 const USE_FM_SCRIPT = false; // true once a "GotoInvoice" FM script exists (jumps to record)
@@ -177,6 +181,20 @@ let active=null; // {card,row,list,seed}
 
 const LS=(k,d)=>{try{return JSON.parse(localStorage.getItem('dwpf:'+k))??d}catch{return d}};
 const setLS=(k,v)=>localStorage.setItem('dwpf:'+k,JSON.stringify(v));
+// COMPLETED cards: dismissed by the operator, persisted, never shown again on this pitch system.
+const doneKey=(list,r)=>'done:'+list+':'+rowKey(list,r);
+const isDone=(list,r)=>!!LS(doneKey(list,r),null);
+function markDone(card){
+  const list=card.dataset.list, r=card._row; if(!r)return;
+  setLS(doneKey(list,r),{at:Date.now()});
+  const bodyEl=card.closest('.col-body'); const cid=bodyEl?bodyEl.id.replace('body-',''):list;
+  // drop from the in-memory VIEW so a scroll-append can't bring it back this session
+  const arr=VIEW[cid]||[]; const idx=arr.findIndex(x=>x.r===r);
+  if(idx>=0){arr.splice(idx,1); if(idx<RENDERED[cid])RENDERED[cid]--;}
+  if(active&&active.card===card)closePanel();
+  card.remove();
+  const n=document.getElementById('n-'+cid); if(n)n.textContent=Math.max(0,(+n.textContent||1)-1);
+}
 const esc=s=>String(s??'').replace(/[&<>"]/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;'}[c]));
 const fmtWhen=iso=>{if(!iso)return'';const d=new Date(iso);return isNaN(d)?'':d.toLocaleString(undefined,{year:'numeric',month:'short',day:'numeric',hour:'numeric',minute:'2-digit'})};
 // Date-only ISO (YYYY-MM-DD) must be parsed as LOCAL, not UTC — else `new Date('2026-07-01')`
@@ -218,6 +236,9 @@ function postSampleBlock(r){
 function stripMfr(s){return String(s||'').replace(/[A-Za-z0-9]+(?:-[A-Za-z0-9]+)*/g,t=>/\d/.test(t)?'':t).replace(/\s*[|·•—–-]+\s*/g,' · ').replace(/(?:^[\s·]+|[\s·]+$)/g,'').replace(/(?:·\s*){2,}/g,'· ').replace(/\bSample\b\s*$/i,'').replace(/\s{2,}/g,' ').trim().replace(/[·\s]+$/,'')}
 function showMfr(){return !!document.getElementById('showMfr')?.checked}
 const lbl=s=>showMfr()?String(s||'').trim():stripMfr(s);
+// goodLbl: drop item strings that stripMfr reduced to punctuation junk (e.g. "/ · .") —
+// needs >=2 alphabetic chars to be a real, showable detail; else return '' (caller filters).
+const goodLbl=s=>{const t=lbl(s);return t.replace(/[^a-z]/gi,'').length>=2?t:''};
 
 function rowKey(list,r){return list==='list3'?`${r.account}|${r.sku}`:r.account}
 function selSet(list){return new Set(LS('sel:'+list,[]))}
@@ -229,6 +250,7 @@ function filterSort(list){
   const rows=(DATA[list]||[]).slice();
   const q=document.getElementById('q').value.trim().toLowerCase();
   let v=q?rows.filter(r=>JSON.stringify(r).toLowerCase().includes(q)):rows;
+  v=v.filter(r=>!isDone(list,r)); // hide completed
   const [f,dir]=(LS('sort:'+list,SORTS[list][0][1])).split(':');
   v.sort((a,b)=>{let x=a[f],y=b[f]; if(f==='lifetime_sales'||typeof x==='number'||typeof y==='number'){x=+x||0;y=+y||0;return dir==='asc'?x-y:y-x;} x=String(x||'');y=String(y||'');return dir==='asc'?x.localeCompare(y):y.localeCompare(x)});
   return v.map(r=>({r,list}));
@@ -236,7 +258,7 @@ function filterSort(list){
 function flatRows(){
   const bucket=LS('bucket','all'); const buckets=bucket==='all'?LISTS:[bucket];
   const q=document.getElementById('q').value.trim().toLowerCase();
-  let all=[]; for(const list of buckets){for(const r of (DATA[list]||[]))all.push({r,list})}
+  let all=[]; for(const list of buckets){for(const r of (DATA[list]||[])){if(isDone(list,r))continue;all.push({r,list})}}
   if(q) all=all.filter(x=>JSON.stringify(x.r).toLowerCase().includes(q));
   const [f,dir]=(LS('sort:flat','lifetime_sales:desc')).split(':');
   all.sort((a,b)=>{let x=a.r[f],y=b.r[f]; if(f==='lifetime_sales'||typeof x==='number'||typeof y==='number'){x=+x||0;y=+y||0;return dir==='asc'?x-y:y-x;} x=String(x||'');y=String(y||'');return dir==='asc'?x.localeCompare(y):y.localeCompare(x)});
@@ -268,7 +290,7 @@ function cardHTML(list,r){
     ${facts}${projLine}${quoteItems}${postFlag(r)}${discoBanner(r)}
     <div class="acts">${needsReorder(r)
       ? `<button class="btn reorder" data-view>Reorder Samples??</button>`
-      : `<button class="btn gold" data-gen>Generate letter</button><button class="btn" data-view>Details ▸</button>`}</div>
+      : `<button class="btn gold" data-gen>Generate letter</button><button class="btn" data-view>Details ▸</button>`}<button class="btn done" data-done title="Mark completed — removes it from this pitch system">✓ Done</button></div>
   </div>`;
 }
 
@@ -462,6 +484,7 @@ document.getElementById('board').addEventListener('change',e=>{
 });
 document.getElementById('board').addEventListener('click',e=>{
   const card=e.target.closest('.card'); if(!card)return;
+  if(e.target.matches('[data-done]')) return markDone(card);
   if(e.target.matches('[data-gen]')) return openPanelFor(card,{generate:true});
   if(e.target.matches('[data-view]')) return openPanelFor(card,{generate:false});
 });

← a59c63b pitch: redefine "Invoiced · No Order" list as real quotes ne  ·  back to Dw Pitch Followup  ·  pitch: card/UX batch — dismiss-completed, park vendor memos, c6d5609 →