← back to Dw Pitch Followup
auto-save: 2026-07-13T11:24:39 (2 files) — public/index.html scripts/build-lists.js
a22a58af1d80cb4109deca1f6cf6f7410abb5e21 · 2026-07-13 11:24:41 -0700 · Steve Abrams
Files touched
M public/index.htmlM scripts/build-lists.js
Diff
commit a22a58af1d80cb4109deca1f6cf6f7410abb5e21
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Mon Jul 13 11:24:41 2026 -0700
auto-save: 2026-07-13T11:24:39 (2 files) — public/index.html scripts/build-lists.js
---
public/index.html | 30 +++++++++++++++++++++++++++---
scripts/build-lists.js | 6 +++++-
2 files changed, 32 insertions(+), 4 deletions(-)
diff --git a/public/index.html b/public/index.html
index 4085cd9..7bd0101 100644
--- a/public/index.html
+++ b/public/index.html
@@ -69,6 +69,12 @@
.proj{font-size:calc(11px*var(--scale));color:var(--gold)}
.postflag{background:#3a2a12;color:var(--amber);border:1px solid var(--amber);border-radius:6px;padding:4px 9px;font-size:calc(11px*var(--scale));font-weight:600;margin:3px 0;line-height:1.4}
.postflag a{color:var(--amber)}
+ /* Discontinued-sample warning: sampled item(s) carry a FileMaker Date Discontinued → dead stock. */
+ .disco-ban{background:#3a1512;color:var(--red);border:1px solid var(--red);border-radius:6px;padding:5px 9px;font-size:calc(11px*var(--scale));font-weight:700;margin:4px 0;line-height:1.4}
+ .disco-ban .sub{display:block;font-weight:600;color:#e79a8d;font-size:calc(10px*var(--scale));margin-top:2px}
+ .disco-ban.all{animation:discoblink 1.05s steps(1,end) infinite}
+ @keyframes discoblink{50%{background:var(--red);color:#2a0d0a;border-color:#fff}}
+ .dtag{display:inline-block;background:#3a1512;color:var(--red);border:1px solid var(--red);border-radius:4px;padding:0 5px;margin-left:6px;font-size:calc(9.5px*var(--scale));font-weight:700;letter-spacing:.03em;vertical-align:middle}
.acts{display:flex;gap:7px;margin-top:2px;flex-wrap:wrap}
/* RIGHT PANEL */
@@ -178,6 +184,23 @@ const dateChip=r=>r.when_iso?`<span class="chip" title="${esc(r.when_iso)}">🕓
const money=n=>'$'+(Number(n)||0).toLocaleString(undefined,{minimumFractionDigits:2,maximumFractionDigits:2});
// Nothing shipped yet: samples are pending but ZERO have been sent → block the letter, prompt a reorder.
const needsReorder=r=>Array.isArray(r.samples_pending)&&r.samples_pending.length>0&&Array.isArray(r.samples_sent)&&r.samples_sent.length===0;
+// Samples were sent, but the item carries a FileMaker Date Discontinued → it's really just
+// discontinued. `all` = EVERY shipped sample is dead stock → the blanket "how did the samples go /
+// ready to order?" pitch is inappropriate; card + panel blink a red DO-NOT-SEND banner. (Steve 2026-07-13)
+function discoState(r){
+ const sent=Array.isArray(r.samples_sent)?r.samples_sent:[];
+ const dead=sent.filter(s=>s&&s.disco);
+ if(!sent.length||!dead.length)return null;
+ return {count:dead.length,total:sent.length,all:dead.length===sent.length,items:dead};
+}
+function discoBanner(r){
+ const d=discoState(r); if(!d)return'';
+ const dates=[...new Set(d.items.map(s=>fmtDay(s.disco)).filter(Boolean))].slice(0,3).join(', ');
+ const msg=d.all
+ ? `⛔ DO NOT send a blanket follow-up — all ${d.total} sampled item(s) are DISCONTINUED.`
+ : `⚠️ ${d.count} of ${d.total} sampled item(s) DISCONTINUED — don't blanket-pitch “how did the samples go”.`;
+ return `<div class="disco-ban ${d.all?'all':''}">${msg}<span class="sub">Discontinued ${esc(dates||'—')} — offer alternatives, don't chase the sale on dead stock.</span></div>`;
+}
// Compact card flag: any invoice placed since we sent samples (possible conversion).
function postFlag(r){
const p=Array.isArray(r.post_sample_invoices)?r.post_sample_invoices:[]; if(!p.length) return '';
@@ -238,7 +261,7 @@ function cardHTML(list,r){
}
return `<div class="card ${sel?'sel':''}" data-k="${esc(k)}" data-list="${list}">
<div class="top"><input type="checkbox" ${sel?'checked':''} data-sel/>${head}</div>
- ${facts}${projLine}${postFlag(r)}
+ ${facts}${projLine}${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>
@@ -272,7 +295,8 @@ function samplesBlock(r){
const w=(withDate&&s&&s.date)?` · ${esc(fmtDay(s.date))}`:'';
const img=(s&&s.image)?`<img src="${esc(s.image)}" style="width:22px;height:22px;object-fit:cover;border-radius:4px;vertical-align:middle;margin-right:6px;border:1px solid var(--line)">`:`<span class="dot ${cls}"></span>`;
const name=(s&&s.url)?`<a href="${esc(s.url)}" target="_blank">${esc(nm)}</a>`:esc(nm);
- return `<li>${img}${name}${w}</li>`;
+ const dead=(s&&s.disco)?`<span class="dtag" title="Discontinued ${esc(s.disco)}">DISCONTINUED · ${esc(fmtDay(s.disco))}</span>`:'';
+ return `<li>${img}${name}${w}${dead}</li>`;
}).join('');
let out='';
if(sent.length)out+=`<div class="drow"><b>Samples sent (${sent.length})</b><ul class="slist">${li(sent,'ok',true)}</ul></div>`;
@@ -290,7 +314,7 @@ function merchBlock(r){
return out;
}
function panelContextHTML(list,r){
- let html=postSampleBlock(r);
+ let html=discoBanner(r)+postSampleBlock(r);
if(r.project) html+=`<div class="drow">🏠 <b>Current project:</b> ${esc(lbl(r.project))}</div>`;
if(list!=='list3') html+=merchBlock(r);
html+=samplesBlock(r);
diff --git a/scripts/build-lists.js b/scripts/build-lists.js
index 81a1b08..fa91853 100644
--- a/scripts/build-lists.js
+++ b/scripts/build-lists.js
@@ -236,8 +236,12 @@ async function enrichSamples(accounts) {
const sku = clean(fd['combo sku'] || '');
const label = sku || clean(fd['Mfr Pattern'] || '') || 'sample';
const sent = fmToISO(fd['Date WP Sample Sent']);
+ // `Date Discontinued` is a WALLPAPER product-level field: if present, this sampled item is
+ // DEAD STOCK — a "how did the samples go / ready to order?" follow-up can't convert on it.
+ // Carry it through so the viewer can red-flag "don't send a blanket pitch." (Steve 2026-07-13)
+ const disco = fmToISO(fd['Date Discontinued']);
const g = map.get(acct) || { sent: [], pending: [] };
- if (sent) g.sent.push({ label, sku, date: sent }); else g.pending.push({ label, sku });
+ if (sent) g.sent.push({ label, sku, date: sent, disco }); else g.pending.push({ label, sku, disco });
map.set(acct, g);
}
for (const g of map.values()) {
← 93dde31 chore: v0.3.0 (session close — public tunnel + auth hardenin
·
back to Dw Pitch Followup
·
auto-save: 2026-07-13T11:54:48 (2 files) — public/index.html 0b34230 →