← back to Marketing Command Center
clients panel: inline LinkedIn/Instagram Follow chips under each name + date-time stamp on Follow/message click (persisted per contact/channel)
88eb92d0cb4390a61297fb7de30d1c7d13f67c6d · 2026-07-16 17:35:19 -0700 · steve
Files touched
M public/panels/clients.htmlM public/panels/clients.js
Diff
commit 88eb92d0cb4390a61297fb7de30d1c7d13f67c6d
Author: steve <steve@designerwallcoverings.com>
Date: Thu Jul 16 17:35:19 2026 -0700
clients panel: inline LinkedIn/Instagram Follow chips under each name + date-time stamp on Follow/message click (persisted per contact/channel)
---
public/panels/clients.html | 13 +++++++++++--
public/panels/clients.js | 33 +++++++++++++++++++++++++--------
2 files changed, 36 insertions(+), 10 deletions(-)
diff --git a/public/panels/clients.html b/public/panels/clients.html
index f7dc591..da7b2e9 100644
--- a/public/panels/clients.html
+++ b/public/panels/clients.html
@@ -1,8 +1,17 @@
<style>
- #cl-list.cl-compact .cl-row { padding: 2px 0 !important; }
+ #cl-list.cl-compact .cl-row { padding: 3px 0 !important; }
#cl-list.cl-compact .cl-row b { font-size: 12px !important; }
- #cl-list.cl-compact .cl-row span { line-height: 1.12 !important; }
#cl-filters button.gold { color: #1a1a1a; }
+ .cl-chip { display: inline-flex; align-items: center; gap: 3px; font-size: 10.5px; padding: 2px 8px;
+ border-radius: 12px; border: 1px solid var(--line, #dcdcdc); text-decoration: none; color: #333; white-space: nowrap; }
+ .cl-chip:hover { filter: brightness(0.96); }
+ .cl-chip.mail { color: #1a1a1a; }
+ .cl-chip.in.direct { background: #0a66c2; color: #fff; border-color: #0a66c2; }
+ .cl-chip.in.search { color: #0a66c2; border-color: #9dc3e6; }
+ .cl-chip.ig.direct { background: linear-gradient(45deg,#f09433,#dc2743,#bc1888); color: #fff; border-color: transparent; }
+ .cl-chip.ig.search { color: #c13584; border-color: #e3a9c9; }
+ .cl-acted { font-size: 9.5px; color: #7a7a7a; white-space: nowrap; align-self: center; }
+ #cl-list.cl-compact .cl-chip { font-size: 9.5px; padding: 1px 6px; }
</style>
<div id="cl-root" style="max-width:960px;">
<div class="muted-banner" id="cl-banner">Loading client & prospect groups…</div>
diff --git a/public/panels/clients.js b/public/panels/clients.js
index 4b443f9..55a14af 100644
--- a/public/panels/clients.js
+++ b/public/panels/clients.js
@@ -15,6 +15,11 @@ window.MCC_PANELS['clients'] = {
const CK = id => 'mcc_cl_' + id; // localStorage key per group
const done = id => { try { return new Set(JSON.parse(localStorage.getItem(CK(id)) || '[]')); } catch { return new Set(); } };
const save = (id, set) => { try { localStorage.setItem(CK(id), JSON.stringify([...set])); } catch {} };
+ // Per-channel action log: when you Follow/message a contact on LinkedIn/IG, stamp the date+time.
+ const AK = id => 'mcc_cl_acted_' + id;
+ const acts = id => { try { return JSON.parse(localStorage.getItem(AK(id)) || '{}'); } catch { return {}; } };
+ const saveActs = (id, a) => { try { localStorage.setItem(AK(id), JSON.stringify(a)); } catch {} };
+ const fmtWhen = ts => { try { return new Date(ts).toLocaleString(undefined, { month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit' }); } catch { return ''; } };
// Normalize a record from either a "clients" (FM) or "prospects" (CSV) dataset.
function norm(rec, kind) {
@@ -189,22 +194,34 @@ window.MCC_PANELS['clients'] = {
const matches = currentMatches();
const shown = matches.slice(0, CAP);
const more = matches.length - shown.length;
- $('#cl-list').innerHTML = `<div class="card"><div>${shown.map(r => {
+ const A = acts(d.id);
+ $('#cl-list').innerHTML = `<div class="card"><div>${shown.map((r, i) => {
const on = set.has(r.id);
- const mail = r.email ? `<a href="mailto:${esc(r.email)}" style="font-size:11.5px;" title="${esc(r.email)}">✉︎ ${esc(r.email)}</a>` : '<span class="muted" style="font-size:11px;">no email</span>';
- const web = r.website ? `<a href="${esc(r.website)}" target="_blank" rel="noopener" style="font-size:11px;">site ↗</a>` : '';
+ const liDir = !!r._raw.linkedin, igDir = !!r._raw.instagram;
+ const a = A[r.id] || {};
const sub = r.sub ? `<span class="muted" style="font-size:11px;">${esc(r.sub)}</span>` : '';
- return `<label class="cl-row" style="display:flex;align-items:center;gap:9px;padding:7px 0;border-bottom:1px solid var(--line,#eee);${on ? 'opacity:.5;' : ''}">
- <input type="checkbox" data-id="${esc(r.id)}" ${on ? 'checked' : ''} style="width:16px;height:16px;flex:none;">
+ const stamp = (ch) => `<span class="cl-acted" id="acted-${ch}-${i}">${a[ch] ? '🕓 ' + esc(fmtWhen(a[ch])) : ''}</span>`;
+ const chips =
+ (r.email ? `<a class="cl-chip mail" href="mailto:${esc(r.email)}" title="${esc(r.email)}" onclick="event.stopPropagation()">✉︎ ${esc(r.email)}</a>` : '') +
+ (r.website ? `<a class="cl-chip" href="${esc(r.website)}" target="_blank" rel="noopener" onclick="event.stopPropagation()">🌐 site ↗</a>` : '') +
+ `<a class="cl-chip in ${liDir ? 'direct' : 'search'}" data-ch="in" data-idx="${i}" href="${esc(r._raw.linkedin || liURL(r.q))}" target="_blank" rel="noopener" title="${liDir ? 'LinkedIn profile' : 'LinkedIn search'}">in · ${liDir ? 'Follow' : 'Find'} ↗</a>` + stamp('in') +
+ `<a class="cl-chip ig ${igDir ? 'direct' : 'search'}" data-ch="ig" data-idx="${i}" href="${esc(r._raw.instagram || igURL(r.q))}" target="_blank" rel="noopener" title="${igDir ? 'Instagram profile' : 'Instagram search'}">IG · ${igDir ? 'Follow' : 'Find'} ↗</a>` + stamp('ig');
+ return `<label class="cl-row" style="display:flex;align-items:flex-start;gap:9px;padding:8px 0;border-bottom:1px solid var(--line,#eee);${on ? 'opacity:.5;' : ''}">
+ <input type="checkbox" data-id="${esc(r.id)}" ${on ? 'checked' : ''} style="width:16px;height:16px;flex:none;margin-top:2px;">
<span style="flex:1;display:flex;flex-direction:column;line-height:1.3;min-width:0;">
<b style="font-size:13px;${on ? 'text-decoration:line-through;' : ''}">${esc(r.label)}</b>
${sub}
- <span style="display:flex;gap:12px;flex-wrap:wrap;margin-top:2px;">${mail} ${web}</span>
+ <span class="cl-chips" style="display:flex;gap:6px;flex-wrap:wrap;align-items:center;margin-top:5px;">${chips}</span>
</span>
- <a class="btn ${r._raw.linkedin ? 'gold' : 'ghost'}" href="${esc(r._raw.linkedin || liURL(r.q))}" target="_blank" rel="noopener" title="${r._raw.linkedin ? 'direct LinkedIn' : 'LinkedIn search'}" style="font-size:10.5px;padding:4px 8px;" onclick="event.stopPropagation()">in ${r._raw.linkedin ? '↗' : '⌕'}</a>
- <a class="btn ${r._raw.instagram ? 'gold' : 'ghost'}" href="${esc(r._raw.instagram || igURL(r.q))}" target="_blank" rel="noopener" title="${r._raw.instagram ? 'direct Instagram' : 'Instagram search'}" style="font-size:10.5px;padding:4px 8px;" onclick="event.stopPropagation()">IG ${r._raw.instagram ? '↗' : '⌕'}</a>
</label>`;
}).join('')}</div>${more > 0 ? `<div class="muted" style="font-size:11px;margin-top:8px;">Showing ${shown.length} of ${matches.length.toLocaleString()} — type in the filter to narrow.</div>` : ''}</div>`;
+ // Stamp the date+time when a LinkedIn/Instagram Follow/message chip is clicked.
+ $('#cl-list').querySelectorAll('.cl-chip.in, .cl-chip.ig').forEach(el => el.addEventListener('click', ev => {
+ ev.stopPropagation();
+ const idx = +el.dataset.idx, ch = el.dataset.ch, rec = shown[idx];
+ const A2 = acts(d.id); (A2[rec.id] = A2[rec.id] || {})[ch] = Date.now(); saveActs(d.id, A2);
+ const span = document.getElementById(`acted-${ch}-${idx}`); if (span) span.textContent = '🕓 ' + fmtWhen(A2[rec.id][ch]);
+ }));
$('#cl-progress').textContent = `${set.size.toLocaleString()} / ${RECS.length.toLocaleString()} contacted`;
$('#cl-list').querySelectorAll('input[type=checkbox]').forEach(cb => cb.onchange = () => {
const s = done(d.id); cb.checked ? s.add(cb.dataset.id) : s.delete(cb.dataset.id); save(d.id, s);
← bb5f304 docs: IG fleet connect checklist (35 live + convert/link ste
·
back to Marketing Command Center
·
clients panel: 'not actioned' filter + LinkedIn/Instagram ac 05fccc4 →