[object Object]

← back to Marketing Command Center

Vendor IG Reporting: render last 3 posts inline per vendor (was 10-photo grid)

075ec0c8b25e882f9c28443b51e2f1d5dff353c4 · 2026-07-17 08:54:50 -0700 · Steve Abrams

Files touched

Diff

commit 075ec0c8b25e882f9c28443b51e2f1d5dff353c4
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Fri Jul 17 08:54:50 2026 -0700

    Vendor IG Reporting: render last 3 posts inline per vendor (was 10-photo grid)
---
 public/panels/vendors.js | 54 +++++++++++++++++++++++++++---------------------
 1 file changed, 31 insertions(+), 23 deletions(-)

diff --git a/public/panels/vendors.js b/public/panels/vendors.js
index 1c6cec8..d85f370 100644
--- a/public/panels/vendors.js
+++ b/public/panels/vendors.js
@@ -17,18 +17,26 @@ window.MCC_PANELS['vendors'] = {
     };
     setStats();
 
-    const postGrid = (r) => {
+    // Last 3 posts rendered INLINE as a stacked listing directly below each
+    // vendor row (not a photo grid) — thumb + caption ↗ + date · likes · comments.
+    const postList = (r) => {
       if (!r.hasIG) return '';
       if (r.posts && r.posts.length) {
-        const cells = r.posts.map(p => {
-          const cap = esc((p.caption || '').replace(/\s+/g, ' ').slice(0, 140));
-          const meta = `♥ ${fmt(p.likes)}  ·  💬 ${fmt(p.comments)}`;
-          const vid = p.media_type === 'VIDEO' ? '<span class="ig-vid">▶</span>' : '';
-          return `<a class="ig-cell" href="${esc(p.permalink)}" target="_blank" rel="noopener noreferrer" title="${esc(p.timestamp ? p.timestamp.slice(0,10) : '')}  ${meta}\n${cap}">
-            <img loading="lazy" src="${esc(p.image || '')}" alt="" onerror="this.parentNode.classList.add('ig-broken')">
-            ${vid}<span class="ig-meta">${meta}</span></a>`;
+        const items = r.posts.slice(0, 3).map(p => {
+          const cap = esc((p.caption || '').replace(/\s+/g, ' ').slice(0, 120)) || '(no caption)';
+          const when = p.timestamp ? p.timestamp.slice(0, 10) : '';
+          const vid = p.media_type === 'VIDEO' ? '▶ ' : '';
+          const thumb = p.image
+            ? `<img class="ig-li-thumb" loading="lazy" src="${esc(p.image)}" alt="" onerror="this.style.visibility='hidden'">`
+            : `<span class="ig-li-thumb"></span>`;
+          return `<a class="ig-li" href="${esc(p.permalink)}" target="_blank" rel="noopener noreferrer">
+            ${thumb}
+            <span class="ig-li-body">
+              <span class="ig-li-cap">${vid}${cap} <span class="ig-li-ext">↗</span></span>
+              <span class="ig-li-meta">${when ? when + '  ·  ' : ''}♥ ${fmt(p.likes)}  ·  💬 ${fmt(p.comments)}</span>
+            </span></a>`;
         }).join('');
-        return `<div class="ig-grid">${cells}</div>`;
+        return `<div class="ig-list">${items}</div>`;
       }
       const why = r.postsError ? `posts unavailable — ${esc(r.postsError).slice(0, 90)}` : 'no posts cached yet — click “Refresh posts”';
       return `<div class="muted" style="font-size:11px;padding:4px 2px 8px">${why}</div>`;
@@ -50,7 +58,7 @@ window.MCC_PANELS['vendors'] = {
             <div style="flex:1">${handle}</div>
             <div style="min-width:70px;text-align:right;font-variant-numeric:tabular-nums">${r.hasIG ? fmt(r.followersNum) : ''}</div>
           </div>
-          ${postGrid(r)}
+          ${postList(r)}
         </div>`;
       }).join('');
     };
@@ -66,7 +74,7 @@ window.MCC_PANELS['vendors'] = {
         <small class="muted" id="vp-when">${data.stats.postsFetchedAt ? 'posts updated ' + ago(data.stats.postsFetchedAt) : 'posts not fetched yet'} · ${data.stats.withPosts || 0}/${data.stats.withIG || 0} with posts</small>`;
       bar.appendChild(wrap);
       wrap.querySelector('#vp-refresh').onclick = async (e) => {
-        const btn = e.target; btn.disabled = true; btn.textContent = '↻ Fetching last 10 posts…';
+        const btn = e.target; btn.disabled = true; btn.textContent = '↻ Fetching latest posts…';
         try {
           const res = await (await fetch('/api/vendors/posts/refresh', { method: 'POST' })).json();
           if (!res.ok) { btn.textContent = '⚠ ' + (res.error || 'refresh failed'); btn.disabled = false; return; }
@@ -79,23 +87,23 @@ window.MCC_PANELS['vendors'] = {
       };
     }
 
-    // Grid styling (idempotent)
-    if (!document.getElementById('vend-ig-css')) {
-      const st = document.createElement('style'); st.id = 'vend-ig-css';
+    // Inline last-3 listing styling (idempotent; id bumped so it re-injects over old grid CSS)
+    if (!document.getElementById('vend-ig-css2')) {
+      const st = document.createElement('style'); st.id = 'vend-ig-css2';
       st.textContent = `
-      .ig-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(84px,1fr));gap:6px;margin-top:9px}
-      .ig-cell{position:relative;aspect-ratio:1/1;border-radius:7px;overflow:hidden;background:#ece7dd;display:block;border:1px solid var(--line)}
-      .ig-cell img{width:100%;height:100%;object-fit:cover;display:block;transition:transform .25s}
-      .ig-cell:hover img{transform:scale(1.06)}
-      .ig-cell.ig-broken{background:#ddd6c8}
-      .ig-cell .ig-vid{position:absolute;top:4px;right:5px;color:#fff;font-size:11px;text-shadow:0 1px 3px #000}
-      .ig-cell .ig-meta{position:absolute;left:0;right:0;bottom:0;padding:3px 5px;font-size:9.5px;color:#fff;background:linear-gradient(transparent,rgba(0,0,0,.72));opacity:0;transition:opacity .2s;font-variant-numeric:tabular-nums;white-space:nowrap}
-      .ig-cell:hover .ig-meta{opacity:1}`;
+      .ig-list{display:flex;flex-direction:column;gap:4px;margin-top:8px}
+      .ig-li{display:flex;gap:9px;align-items:center;padding:5px 7px;border:1px solid var(--line);border-radius:8px;background:#fbf9f4;text-decoration:none;color:inherit}
+      .ig-li:hover{background:#f3eee2}
+      .ig-li-thumb{width:40px;height:40px;flex:0 0 40px;border-radius:6px;object-fit:cover;background:#ece7dd;display:inline-block}
+      .ig-li-body{display:flex;flex-direction:column;min-width:0;gap:1px}
+      .ig-li-cap{font-size:12px;line-height:1.35;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:min(70vw,640px)}
+      .ig-li-ext{color:var(--muted,#8a8372);font-size:11px}
+      .ig-li-meta{font-size:10.5px;color:var(--muted,#8a8372);font-variant-numeric:tabular-nums}`;
       document.head.appendChild(st);
     }
 
     const s = data.stats || {};
     root.querySelector('#vend-banner').innerHTML = s.missing
-      ? `<div class="muted-banner">${s.withIG} of ${s.total} brands have an official Instagram — ${s.missing} have none found (Atomic50, Bespoke, Folia, Naturale 54). Combined vendor reach ≈ ${fmt(s.totalReach)} followers. Last-10 posts via Instagram Business Discovery (click Refresh).</div>` : '';
+      ? `<div class="muted-banner">${s.withIG} of ${s.total} brands have an official Instagram — ${s.missing} have none found (Atomic50, Bespoke, Folia, Naturale 54). Combined vendor reach ≈ ${fmt(s.totalReach)} followers. Last 3 posts shown inline per vendor via Instagram Business Discovery (click Refresh).</div>` : '';
   },
 };

← 922c32f clients panel: chip clicks open target in a right-side drawe  ·  back to Marketing Command Center  ·  board UX: add 'Clear staged' — dismisses stale staged/pendin 2888aa4 →