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