[object Object]

← back to Marketing Command Center

clients panel: search across ALL fields + sort dropdown (name/city/state/has-email/IG/LinkedIn), persisted

1a674419cad5591d8ddfb2da290295f74416d592 · 2026-07-16 11:30:24 -0700 · steve

Files touched

Diff

commit 1a674419cad5591d8ddfb2da290295f74416d592
Author: steve <steve@designerwallcoverings.com>
Date:   Thu Jul 16 11:30:24 2026 -0700

    clients panel: search across ALL fields + sort dropdown (name/city/state/has-email/IG/LinkedIn), persisted
---
 public/panels/clients.html | 14 +++++++++++++-
 public/panels/clients.js   | 26 +++++++++++++++++++++++---
 2 files changed, 36 insertions(+), 4 deletions(-)

diff --git a/public/panels/clients.html b/public/panels/clients.html
index 2218459..73c3539 100644
--- a/public/panels/clients.html
+++ b/public/panels/clients.html
@@ -20,7 +20,19 @@
       <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%;">
+    <div class="row" style="gap:8px;margin-top:12px;align-items:center;flex-wrap:wrap;">
+      <input id="cl-search" type="text" placeholder="Search all fields — name, company, email, city, state, phone, website…" style="flex:1;min-width:220px;">
+      <label class="muted" for="cl-sort" style="font-size:11.5px;">Sort</label>
+      <select id="cl-sort" style="font-size:12px;">
+        <option value="default">Default order</option>
+        <option value="name">Name / Company A→Z</option>
+        <option value="city">City A→Z</option>
+        <option value="state">State A→Z</option>
+        <option value="email">Has email first</option>
+        <option value="instagram">Has Instagram first</option>
+        <option value="linkedin">Has LinkedIn first</option>
+      </select>
+    </div>
     <div class="muted" id="cl-groupnote" style="font-size:11.5px;margin-top:8px;"></div>
   </div>
 
diff --git a/public/panels/clients.js b/public/panels/clients.js
index 3890113..ede787f 100644
--- a/public/panels/clients.js
+++ b/public/panels/clients.js
@@ -20,13 +20,15 @@ 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(' '), _raw: rec };
+                 email: rec.email || '', website: rec.website || '', phone: rec.phone || '', q: [rec.title, rec.city].filter(Boolean).join(' '),
+                 _raw: rec, _all: Object.values(rec).filter(v => v && typeof v === 'string').join(' ').toLowerCase() };
       }
       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(' '), _raw: rec };
+               q: [rec.name, rec.company, rec.city].filter(Boolean).join(' '),
+               _raw: rec, _all: Object.values(rec).filter(v => v && typeof v === 'string').join(' ').toLowerCase() };
     }
     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');
@@ -60,14 +62,32 @@ window.MCC_PANELS['clients'] = {
       $('#cl-groupnote').textContent = `Source: ${data.source || d.file}${data.updated ? ' · updated ' + data.updated : ''}. LinkedIn opens a people/company search; IG opens a Google “<name> instagram” lookup — open the right match and follow.`;
       $('#cl-search').value = '';
       $('#cl-search').oninput = render;
+      $('#cl-sort').value = sortMode();
+      $('#cl-sort').onchange = () => { try { localStorage.setItem(SK, $('#cl-sort').value); } catch {} render(); };
       $('#cl-reset').onclick = () => { if (confirm('Clear contacted checks for ' + d.label + '?')) { save(d.id, new Set()); render(); } };
       $('#cl-export').onclick = () => exportCSV(d);
       render();
     }
 
+    const SK = 'mcc_cl_sort';
+    const sortMode = () => { try { return localStorage.getItem(SK) || 'default'; } catch { return 'default'; } };
+    function sortRecs(list) {
+      const az = (a, b) => a.label.localeCompare(b.label);
+      const has = f => (a, b) => ((b._raw[f] ? 1 : 0) - (a._raw[f] ? 1 : 0)) || az(a, b);
+      const cmp = {
+        name: az,
+        city: (a, b) => (a._raw.city || '').localeCompare(b._raw.city || '') || az(a, b),
+        state: (a, b) => (a._raw.state || '').localeCompare(b._raw.state || '') || az(a, b),
+        email: (a, b) => ((b.email ? 1 : 0) - (a.email ? 1 : 0)) || az(a, b),
+        instagram: has('instagram'),
+        linkedin: has('linkedin'),
+      }[sortMode()];
+      return cmp ? list.slice().sort(cmp) : list;
+    }
     function currentMatches() {
       const q = ($('#cl-search').value || '').trim().toLowerCase();
-      return q ? RECS.filter(r => r.q.toLowerCase().includes(q)) : RECS;
+      const list = q ? RECS.filter(r => r._all.includes(q)) : RECS;
+      return sortRecs(list);
     }
 
     // Review-oriented CSV export of the current group (respects the filter).

← e0ae6f5 clients: browser+local-model contact enrichment (direct Inst  ·  back to Marketing Command Center  ·  auto-save: 2026-07-16T11:43:05 (1 files) — public/data/clien d885131 →