← back to Dw Photo Capture

public/vendor-photos.html

79 lines

<!doctype html>
<html lang="en"><head>
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>Vendor Photos · DW</title>
<style>
  :root{--bg:#0f0e0c;--card:#1b1916;--ink:#f3efe7;--muted:#9a9184;--line:#2e2a25;--gold:#c8a24a;--cardw:220px}
  *{box-sizing:border-box} body{margin:0;background:var(--bg);color:var(--ink);font:15px/1.4 -apple-system,system-ui,sans-serif}
  header{position:sticky;top:0;z-index:5;background:var(--bg);border-bottom:1px solid var(--line);padding:12px 16px}
  h1{margin:0 0 10px;font-size:18px} h1 a{color:var(--muted);font-size:13px;text-decoration:none;margin-left:8px}
  .controls{display:flex;flex-wrap:wrap;gap:10px;align-items:center}
  select,input[type=search]{background:var(--card);color:var(--ink);border:1px solid var(--line);border-radius:8px;padding:7px 9px;font-size:14px}
  .dens{color:var(--muted);font-size:13px;display:flex;align-items:center;gap:6px}
  .vendors{display:flex;flex-wrap:wrap;gap:6px;margin-top:10px}
  .vendors button{background:var(--card);color:var(--ink);border:1px solid var(--line);border-radius:999px;padding:5px 11px;font-size:13px;cursor:pointer}
  .vendors button.on{border-color:var(--gold);color:var(--gold)}
  .vendors b{color:var(--muted);font-weight:400;margin-left:4px}
  main{display:grid;grid-template-columns:repeat(auto-fill,minmax(var(--cardw),1fr));gap:10px;padding:14px 16px}
  .card{background:var(--card);border:1px solid var(--line);border-radius:10px;overflow:hidden}
  .card img{width:100%;aspect-ratio:1;object-fit:cover;display:block;background:#000}
  .card .meta{padding:8px 10px;font-size:12px;display:grid;gap:3px}
  .card .sku{font-weight:600;font-size:13px}
  .card .ven,.card .src{color:var(--muted)}
  .when{color:var(--gold)}
  .empty{padding:40px 16px;color:var(--muted);text-align:center}
</style></head>
<body>
<header>
  <h1>Vendor Photos <span id="count" style="color:var(--muted);font-weight:400;font-size:14px"></span><a href="/app">← capture</a></h1>
  <div class="controls">
    <input type="search" id="q" placeholder="Search SKU or vendor">
    <select id="sort">
      <option value="newest">Newest</option>
      <option value="oldest">Oldest</option>
      <option value="vendor">Vendor A→Z</option>
      <option value="sku">SKU A→Z</option>
    </select>
    <label class="dens">Size <input type="range" id="dens" min="120" max="420" step="20" value="220"></label>
  </div>
  <div class="vendors" id="vendors"></div>
</header>
<main id="grid"></main>
<script>
const $ = s => document.querySelector(s);
const ls = { get(k,d){ try{ return localStorage.getItem(k) ?? d; }catch(e){ return d; } }, set(k,v){ try{ localStorage.setItem(k,v); }catch(e){} } };
const esc = s => String(s ?? '').replace(/[&<>"]/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;'}[c]));
const fmtDate = iso => { const d = new Date(iso); return isNaN(d) ? '' : d.toLocaleString(undefined,{year:'numeric',month:'short',day:'numeric',hour:'numeric',minute:'2-digit'}); };
let all = [], vendor = ls.get('vp.vendor',''), sort = ls.get('vp.sort','newest');
$('#sort').value = sort; $('#dens').value = ls.get('vp.dens','220');
document.documentElement.style.setProperty('--cardw', $('#dens').value + 'px');
function render(){
  const q = $('#q').value.trim().toLowerCase();
  let rows = all.filter(r => (!vendor || r.vendor === vendor) && (!q || (r.dw_sku||'').toLowerCase().includes(q) || r.vendor.toLowerCase().includes(q)));
  const by = { newest:(a,b)=>b.at.localeCompare(a.at), oldest:(a,b)=>a.at.localeCompare(b.at),
    vendor:(a,b)=>a.vendor.localeCompare(b.vendor)||b.at.localeCompare(a.at), sku:(a,b)=>(a.dw_sku||'').localeCompare(b.dw_sku||'') };
  rows.sort(by[sort] || by.newest);
  $('#count').textContent = rows.length + ' of ' + all.length;
  $('#grid').innerHTML = rows.length ? rows.map(r => `<div class="card">
      <a href="${esc(r.path)}" target="_blank" rel="noopener noreferrer"><img loading="lazy" src="${esc(r.path)}" alt="${esc(r.dw_sku||'')}"></a>
      <div class="meta"><div class="sku">${esc(r.dw_sku || '—')}</div><div class="ven">${esc(r.vendor)}</div>
      <div class="when" title="${esc(r.at)}">🕓 ${esc(fmtDate(r.at))}</div><div class="src">${esc(r.source||'')}</div></div></div>`).join('')
    : '<div class="empty">No photos yet.</div>';
}
function renderVendors(counts){
  const names = Object.keys(counts).sort((a,b)=>a.localeCompare(b));
  if (vendor && !counts[vendor]) vendor = '';
  $('#vendors').innerHTML = `<button data-v="" class="${vendor?'':'on'}">All<b>${all.length}</b></button>` +
    names.map(v => `<button data-v="${esc(v)}" class="${v===vendor?'on':''}">${esc(v)}<b>${counts[v]}</b></button>`).join('');
}
$('#vendors').addEventListener('click', e => { const b = e.target.closest('button'); if (!b) return;
  vendor = b.dataset.v; ls.set('vp.vendor', vendor);
  document.querySelectorAll('#vendors button').forEach(x => x.classList.toggle('on', x === b)); render(); });
$('#sort').addEventListener('change', e => { sort = e.target.value; ls.set('vp.sort', sort); render(); });
$('#dens').addEventListener('input', e => { document.documentElement.style.setProperty('--cardw', e.target.value + 'px'); ls.set('vp.dens', e.target.value); });
$('#q').addEventListener('input', render);
fetch(location.origin + '/api/vendor-photos').then(r => r.json()).then(d => { all = d.photos || []; renderVendors(d.vendors || {}); render(); })
  .catch(() => { $('#grid').innerHTML = '<div class="empty">Could not load photos.</div>'; });
</script>
</body></html>