← back to Vendor Price Requests
public/index.html
93 lines
<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>Vendor Price Requests — Designer Wallcoverings</title>
<style>
:root{--ink:#26221c;--cream:#f6f3ec;--paper:#fffdf8;--line:#e3ddcf;--gold:#9a7b3f;--muted:#8a8273;--green:#2c6b4f;--red:#a3322a}
*{box-sizing:border-box;margin:0;padding:0}
body{font:14px/1.5 -apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;background:var(--cream);color:var(--ink);padding:22px;max-width:1100px;margin:0 auto}
h1{font:600 22px Georgia,serif} .sub{color:var(--muted);font-size:13px;margin:4px 0 16px}
.layout{display:grid;grid-template-columns:300px 1fr;gap:18px}
.vlist{background:var(--paper);border:1px solid var(--line);border-radius:10px;overflow:hidden;max-height:80vh;overflow-y:auto}
.v{padding:10px 13px;border-bottom:1px solid var(--line);cursor:pointer;display:flex;justify-content:space-between;gap:8px}
.v:hover{background:var(--cream)} .v.active{background:var(--cream);box-shadow:inset 3px 0 0 var(--gold)}
.v .n{font-weight:600;font-size:13px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.v .c{font-size:11px;color:var(--muted);white-space:nowrap}
.v .sent{color:var(--green);font-size:11px;font-weight:700}
.panel{background:var(--paper);border:1px solid var(--line);border-radius:10px;padding:18px;min-height:60vh}
.row{display:flex;gap:10px;align-items:center;margin-bottom:10px;flex-wrap:wrap}
input,textarea{font:inherit;border:1px solid var(--line);border-radius:6px;padding:8px 10px;background:#fff;width:100%}
textarea{min-height:330px;font:13px/1.55 ui-monospace,Menlo,monospace}
label{font-size:11px;text-transform:uppercase;letter-spacing:.6px;color:var(--muted);font-weight:700}
button{font:inherit;cursor:pointer;border:1px solid var(--line);background:var(--paper);border-radius:7px;padding:9px 16px;font-weight:600}
.send{background:var(--accent,#2c4a3e);background:#2c4a3e;color:#fff;border-color:#2c4a3e}
.send:disabled{opacity:.5;cursor:not-allowed}
.empty{color:var(--muted);text-align:center;padding:60px 20px}
.stat{display:inline-block;background:var(--cream);border:1px solid var(--line);border-radius:8px;padding:6px 12px;font-size:13px;margin-right:8px}
.stat b{color:var(--gold);font-size:17px}
.toast{position:fixed;bottom:18px;right:18px;background:var(--ink);color:var(--cream);padding:11px 16px;border-radius:8px;opacity:0;transition:.2s}
.toast.show{opacity:1}
.warn{background:#f6ecd6;border:1px solid #e3d2a0;border-radius:8px;padding:8px 12px;font-size:12.5px;color:#7a5b1e;margin-bottom:12px}
.fbtn{font-size:12px;padding:6px 12px}.fbtn.active{background:var(--ink);color:var(--cream);border-color:var(--ink)}
.badge-d{color:var(--gold);font-size:11px;font-weight:700}
.save{background:var(--paper);border:1px solid var(--gold);color:var(--gold);font-weight:600}
</style></head><body>
<h1>Vendor Price Requests</h1>
<div class="sub">Per-vendor letters asking for current trade pricing on showroom SKUs we have no cost for. Review · set recipient · <b>Send is gated — nothing goes out until you click Send.</b></div>
<div id="stats"></div>
<div class="row" style="margin-top:10px;gap:6px" id="filters">
<button class="fbtn active" data-f="all">All</button>
<button class="fbtn" data-f="todo">To draft</button>
<button class="fbtn" data-f="drafted">✎ Drafts</button>
<button class="fbtn" data-f="sent">✓ Sent</button>
</div>
<div class="layout" style="margin-top:14px">
<div class="vlist" id="vlist"></div>
<div class="panel" id="panel"><div class="empty">Select a vendor to review its letter.</div></div>
</div>
<div class="toast" id="toast"></div>
<script>
const $=s=>document.querySelector(s); let VEND=[], cur=null, FILTER="all";
function toast(m){const t=$("#toast");t.textContent=m;t.classList.add("show");setTimeout(()=>t.classList.remove("show"),2600);}
function statusOf(v){ if(v.letter_status==='sent')return 'sent'; if(v.has_draft||v.letter_status==='drafted')return 'drafted'; return 'todo'; }
async function load(){
const d=await (await fetch("/api/vendors")).json(); VEND=d.vendors||[];
const totalSkus=VEND.reduce((a,v)=>a+v.gap_count,0), sent=VEND.filter(v=>statusOf(v)==='sent').length, drafted=VEND.filter(v=>statusOf(v)==='drafted').length;
$("#stats").innerHTML=`<span class="stat"><b>${VEND.length}</b> vendors</span><span class="stat"><b>${totalSkus.toLocaleString()}</b> SKUs needing price</span><span class="stat"><b>${drafted}</b> drafts</span><span class="stat"><b>${sent}</b> sent</span>`;
const rows=VEND.filter(v=>FILTER==='all'||statusOf(v)===FILTER);
$("#vlist").innerHTML=rows.length?rows.map(v=>{const st=statusOf(v);const tag=st==='sent'?'<span class=sent>✓ sent</span>':st==='drafted'?'<span class=badge-d>✎ draft</span>':v.gap_count+' skus';
return `<div class="v ${cur===v.vendor?'active':''}" onclick="pick('${v.vendor.replace(/'/g,"\\'")}')"><span class="n">${esc(v.vendor)}</span><span class="c">${tag}</span></div>`;}).join(""):'<div class="empty" style="padding:30px">none</div>';
}
document.querySelectorAll(".fbtn").forEach(b=>b.onclick=()=>{FILTER=b.dataset.f;document.querySelectorAll(".fbtn").forEach(x=>x.classList.toggle("active",x===b));load();});
function esc(s){return String(s).replace(/[&<>"]/g,c=>({'&':'&','<':'<','>':'>','"':'"'}[c]));}
async function pick(v){
cur=v; load();
$("#panel").innerHTML='<div class="empty">Loading letter…</div>';
const d=await (await fetch("/api/letter/"+encodeURIComponent(v))).json();
$("#panel").innerHTML=`
<div class="row"><div style="flex:1"><label>Vendor</label><div style="font:600 17px Georgia,serif">${esc(d.vendor)}</div></div>
<div class="stat"><b>${d.count}</b> SKUs</div></div>
${!d.email?'<div class="warn">No saved recipient email for this vendor — enter one below before sending.</div>':''}
<div class="row"><div style="flex:1"><label>Recipient email</label><input id="to" value="${esc(d.email||'')}" placeholder="purchasing@vendor.com"></div></div>
<div class="row" style="flex-direction:column;align-items:stretch"><label>Subject</label><input id="subj" value="${esc(d.subject)}"></div>
<div class="row" style="flex-direction:column;align-items:stretch"><label>Letter (editable)${d.hasDraft?' <span class="badge-d">• saved draft'+(d.draftSavedAt?' '+new Date(d.draftSavedAt).toLocaleString(undefined,{month:'short',day:'numeric',hour:'numeric',minute:'2-digit'}):'')+'</span>':''}</label><textarea id="body">${esc(d.body)}</textarea></div>
${d.sheetUrl?`<div class="row"><label>Price-list sheet</label> <a href="${esc(d.sheetUrl)}" target="_blank" rel="noopener">${esc(d.sheetUrl)}</a></div>`:''}
<div class="row"><button onclick="saveEmail()">Save email</button>
<button class="save" id="sheetBtn" onclick="makeSheet()">📄 ${d.sheetUrl?'Recreate':'Create'} price-list sheet</button>
<button class="save" onclick="saveDraft()">✎ Save draft</button>
<button class="send" id="sendBtn" onclick="send()">✉ Approve & Send to vendor</button>
<span style="font-size:12px;color:var(--muted)">Sends from info@designerwallcoverings.com via George</span></div>`;
}
async function saveEmail(){ await fetch("/api/contact/"+encodeURIComponent(cur),{method:"POST",headers:{'Content-Type':'application/json'},body:JSON.stringify({email:$("#to").value})}); toast("Email saved"); load(); }
async function saveDraft(){ await fetch("/api/draft/"+encodeURIComponent(cur),{method:"POST",headers:{'Content-Type':'application/json'},body:JSON.stringify({subject:$("#subj").value,body:$("#body").value,email:$("#to").value})}); toast("Draft saved"); load(); }
async function makeSheet(){ const b=$("#sheetBtn"); b.disabled=true; b.textContent="📄 Creating sheet…"; try{ const r=await fetch("/api/sheet/"+encodeURIComponent(cur),{method:"POST"}); const d=await r.json(); if(d.ok){ toast("✓ Sheet created ("+d.rows+" rows)"); pick(cur); } else { toast("⚠ "+(d.error||"failed")); b.disabled=false; b.textContent="📄 Create price-list sheet"; } }catch(e){ toast("⚠ "+e.message); b.disabled=false; } }
async function send(){
const to=$("#to").value.trim(); if(!/.+@.+\..+/.test(to)){toast("Enter a valid recipient email");return;}
if(!confirm("Send this pricing request to "+to+" ?")) return;
$("#sendBtn").disabled=true; $("#sendBtn").textContent="Sending…";
const r=await fetch("/api/send/"+encodeURIComponent(cur),{method:"POST",headers:{'Content-Type':'application/json'},body:JSON.stringify({to,subject:$("#subj").value,body:$("#body").value})});
const d=await r.json();
if(d.ok){ toast("✓ Sent to "+to); cur=null; $("#panel").innerHTML='<div class="empty">Sent. Pick another vendor.</div>'; load(); }
else { toast("⚠ "+(d.error||"send failed")); $("#sendBtn").disabled=false; $("#sendBtn").textContent="✉ Approve & Send to vendor"; }
}
load();
</script></body></html>