[object Object]

← 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

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 →