← 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
M public/panels/vendors.js
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 →