[object Object]

← back to Marketing Command Center

clients panel: per-group CSV export (respects filter; includes emails + LinkedIn/IG URLs; local download, not CC)

c1ee3c8888e449826346dd8c43202b408d6f6cb8 · 2026-07-16 11:01:23 -0700 · steve

Files touched

Diff

commit c1ee3c8888e449826346dd8c43202b408d6f6cb8
Author: steve <steve@designerwallcoverings.com>
Date:   Thu Jul 16 11:01:23 2026 -0700

    clients panel: per-group CSV export (respects filter; includes emails + LinkedIn/IG URLs; local download, not CC)
---
 public/panels/clients.html |  1 +
 public/panels/clients.js   | 35 +++++++++++++++++++++++++++++++----
 2 files changed, 32 insertions(+), 4 deletions(-)

diff --git a/public/panels/clients.html b/public/panels/clients.html
index 2f408ae..2218459 100644
--- a/public/panels/clients.html
+++ b/public/panels/clients.html
@@ -17,6 +17,7 @@
       <span class="pill" id="cl-progress">0 / 0 contacted</span>
       <span class="pill" id="cl-emailcount"></span>
       <span style="flex:1;"></span>
+      <button class="btn gold" id="cl-export" style="font-size:11px;padding:5px 9px;">⬇ Export CSV</button>
       <button class="btn ghost" id="cl-reset" style="font-size:11px;padding:5px 9px;">Reset checks</button>
     </div>
     <input id="cl-search" type="text" placeholder="Filter by name / company / city…" style="margin-top:12px;width:100%;">
diff --git a/public/panels/clients.js b/public/panels/clients.js
index 293cf0f..37755ff 100644
--- a/public/panels/clients.js
+++ b/public/panels/clients.js
@@ -20,13 +20,13 @@ window.MCC_PANELS['clients'] = {
     function norm(rec, kind) {
       if (kind === 'prospects') {
         return { id: (rec.website || rec.title || '').toLowerCase(), label: rec.title || '(business)', sub: [rec.city].filter(Boolean).join(' · '),
-                 email: rec.email || '', website: rec.website || '', phone: rec.phone || '', q: [rec.title, rec.city].filter(Boolean).join(' ') };
+                 email: rec.email || '', website: rec.website || '', phone: rec.phone || '', q: [rec.title, rec.city].filter(Boolean).join(' '), _raw: rec };
       }
       const label = rec.name || rec.company || '(client)';
       const sub = rec.name ? [rec.company, [rec.city, rec.state].filter(Boolean).join(', ')].filter(Boolean).join(' · ')
                            : [rec.city, rec.state].filter(Boolean).join(', ');
       return { id: rec.account || (rec.company + '|' + rec.name).toLowerCase(), label, sub, email: rec.email || '', website: '', phone: rec.phone || '',
-               q: [rec.name, rec.company, rec.city].filter(Boolean).join(' ') };
+               q: [rec.name, rec.company, rec.city].filter(Boolean).join(' '), _raw: rec };
     }
     const liURL = q => 'https://www.linkedin.com/search/results/all/?keywords=' + encodeURIComponent(q);
     const igURL = q => 'https://www.google.com/search?q=' + encodeURIComponent(q + ' instagram');
@@ -61,13 +61,40 @@ window.MCC_PANELS['clients'] = {
       $('#cl-search').value = '';
       $('#cl-search').oninput = render;
       $('#cl-reset').onclick = () => { if (confirm('Clear contacted checks for ' + d.label + '?')) { save(d.id, new Set()); render(); } };
+      $('#cl-export').onclick = () => exportCSV(d);
       render();
     }
 
+    function currentMatches() {
+      const q = ($('#cl-search').value || '').trim().toLowerCase();
+      return q ? RECS.filter(r => r.q.toLowerCase().includes(q)) : RECS;
+    }
+
+    // Review-oriented CSV export of the current group (respects the filter).
+    // Includes emails + the LinkedIn/Instagram discovery URLs. Local download —
+    // this does NOT send anything to Constant Contact.
+    function exportCSV(d) {
+      const rows = currentMatches();
+      const cell = v => { v = String(v == null ? '' : v); return /[",\n]/.test(v) ? '"' + v.replace(/"/g, '""') + '"' : v; };
+      let headers, line;
+      if (d.kind === 'prospects') {
+        headers = ['Business', 'City', 'Phone', 'Website', 'Email', 'LinkedIn Search', 'Instagram Search'];
+        line = r => [r._raw.title, r._raw.city, r._raw.phone, r._raw.website, r._raw.email, liURL(r.q), igURL(r.q)];
+      } else {
+        headers = ['Account', 'Company', 'Name', 'City', 'State', 'Email', 'Phone', 'LinkedIn Search', 'Instagram Search'];
+        line = r => [r._raw.account, r._raw.company, r._raw.name, r._raw.city, r._raw.state, r._raw.email, r._raw.phone, liURL(r.q), igURL(r.q)];
+      }
+      const csv = [headers.join(','), ...rows.map(r => line(r).map(cell).join(','))].join('\r\n');
+      const blob = new Blob(['' + csv], { type: 'text/csv;charset=utf-8;' });
+      const url = URL.createObjectURL(blob);
+      const a = document.createElement('a');
+      a.href = url; a.download = d.file.replace(/\.json$/, '') + '.csv';
+      document.body.appendChild(a); a.click(); a.remove(); URL.revokeObjectURL(url);
+    }
+
     function render() {
       const d = current; const set = done(d.id);
-      const q = ($('#cl-search').value || '').trim().toLowerCase();
-      const matches = q ? RECS.filter(r => r.q.toLowerCase().includes(q)) : RECS;
+      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 => {

← b50489a refresh-clients.sh: mkdir lock (macOS has no flock)  ·  back to Marketing Command Center  ·  calendars: decouple launched layer so it stays live where bu 87dc441 →