-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcontacts.html
More file actions
151 lines (146 loc) · 15.5 KB
/
contacts.html
File metadata and controls
151 lines (146 loc) · 15.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
<!doctype html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Contacts — RAG Command Center</title>
<link href="https://fonts.googleapis.com/css2?family=DM+Mono:wght@400;500&family=Syne:wght@400;600;700;800&family=Fraunces:opsz,wght@9..144,300;9..144,600;9..144,700&display=swap" rel="stylesheet">
<style>
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
:root{--bg:#0c0d10;--panel:#131417;--panel2:#191b20;--border:#22252d;--border2:#2a2e38;--accent:#d4a843;--hot:#e8445a;--warm:#d4a843;--cold:#4b8fcc;--green:#2ec97a;--ink:#eaecf0;--ink2:#8e97a8;--ink3:#50586a;--mono:'DM Mono',monospace;--serif:'Fraunces',Georgia,serif;--display:'Syne',sans-serif;--radius:10px}
html{font-family:var(--display);background:var(--bg);color:var(--ink)}body{min-height:100vh}button{font-family:inherit;cursor:pointer}input,select,textarea{font-family:inherit}a{color:inherit;text-decoration:none}
.topbar{height:52px;background:var(--panel);border-bottom:1px solid var(--border);display:flex;align-items:center;justify-content:space-between;padding:0 24px;position:sticky;top:0;z-index:200}
.topbar-logo{display:flex;align-items:center;gap:10px}.logo-hex{width:28px;height:28px;background:var(--accent);clip-path:polygon(50% 0%,100% 25%,100% 75%,50% 100%,0% 75%,0% 25%);display:flex;align-items:center;justify-content:center;font-weight:800;font-size:8px;color:#000}
.topbar-name{font-weight:700;font-size:14px}.topbar-sub{font-family:var(--mono);font-size:9px;color:var(--ink3);letter-spacing:.1em;text-transform:uppercase}
.topbar-tabs{display:flex;gap:2px}.tab-btn{padding:6px 16px;border-radius:6px;background:transparent;border:1px solid transparent;font-size:12px;font-weight:600;color:var(--ink3);transition:all .15s}.tab-btn:hover{color:var(--ink);background:var(--panel2)}.tab-btn.active{color:var(--accent);background:rgba(212,168,67,.08);border-color:rgba(212,168,67,.2)}
.main{padding:28px;max-width:1360px;margin:0 auto}
.page-hd{margin-bottom:20px}.page-hd h1{font-family:var(--serif);font-size:28px;font-weight:600;margin-bottom:3px}.page-hd p{font-family:var(--mono);font-size:10px;color:var(--ink3);letter-spacing:.1em;text-transform:uppercase}
.filters-bar{display:flex;gap:10px;align-items:center;flex-wrap:wrap;margin-bottom:20px}
.fi{padding:8px 12px;border:1px solid var(--border2);border-radius:7px;background:var(--panel);color:var(--ink);font-family:var(--mono);font-size:11px;outline:none}.fi:focus{border-color:var(--accent)}
.btn-p{padding:10px 20px;background:var(--accent);border:none;border-radius:7px;color:#000;font-weight:700;font-size:13px;transition:all .15s}.btn-p:hover{background:#c49a35}
.btn-s{padding:9px 16px;background:var(--panel2);border:1px solid var(--border2);border-radius:7px;color:var(--ink2);font-size:12px;font-weight:600;transition:all .15s}.btn-s:hover{border-color:var(--accent);color:var(--accent)}
table{width:100%;border-collapse:collapse}th{font-family:var(--mono);font-size:9px;color:var(--ink3);letter-spacing:.08em;text-transform:uppercase;padding:10px 14px;text-align:left;border-bottom:1px solid var(--border);font-weight:500}td{padding:12px 14px;border-bottom:1px solid var(--border);font-size:13px;vertical-align:middle}tbody tr{transition:background .12s;cursor:pointer}tbody tr:hover{background:var(--panel2)}
.tag{font-family:var(--mono);font-size:9px;padding:2px 7px;border-radius:4px;border:1px solid transparent;white-space:nowrap}.tag.hot{background:rgba(232,68,90,.08);color:var(--hot);border-color:rgba(232,68,90,.2)}.tag.warm{background:rgba(212,168,67,.08);color:var(--warm);border-color:rgba(212,168,67,.2)}.tag.cold{background:rgba(75,143,204,.08);color:var(--cold);border-color:rgba(75,143,204,.2)}.tag.green{background:rgba(46,201,122,.08);color:var(--green);border-color:rgba(46,201,122,.2)}
.card{background:var(--panel);border:1px solid var(--border);border-radius:var(--radius);overflow:hidden}
.card-hd{display:flex;align-items:center;justify-content:space-between;padding:14px 18px;border-bottom:1px solid var(--border)}.card-hd h2{font-size:13px;font-weight:600}
.modal-overlay{display:none;position:fixed;inset:0;background:rgba(0,0,0,.65);backdrop-filter:blur(6px);z-index:900;align-items:center;justify-content:center}.modal-overlay.open{display:flex}
.modal{background:var(--panel);border:1px solid var(--border2);border-radius:14px;width:100%;max-width:560px;margin:20px;max-height:90vh;overflow-y:auto;box-shadow:0 32px 80px rgba(0,0,0,.5)}
.modal-hd{display:flex;align-items:center;justify-content:space-between;padding:20px 24px;border-bottom:1px solid var(--border)}.modal-hd h2{font-family:var(--serif);font-size:20px;font-weight:600}
.modal-body{padding:24px}.fgroup{display:flex;flex-direction:column;gap:5px;margin-bottom:12px}.flabel{font-family:var(--mono);font-size:9px;color:var(--ink3);letter-spacing:.08em;text-transform:uppercase}
.empty{text-align:center;padding:48px;font-family:var(--mono);font-size:11px;color:var(--ink3)}
.stats-row{display:flex;gap:12px;margin-bottom:20px}.stat-mini{background:var(--panel);border:1px solid var(--border);border-radius:var(--radius);padding:14px 18px;flex:1}.stat-mini .lbl{font-family:var(--mono);font-size:9px;color:var(--ink3);letter-spacing:.1em;text-transform:uppercase;margin-bottom:6px}.stat-mini .val{font-family:var(--serif);font-size:28px;font-weight:600}
</style>
<script src="data/bootstrap.js"></script><script src="assets/js/utils.js"></script></head><body>
<header class="topbar"><div class="topbar-logo"><div class="logo-hex">RAG</div><div><div class="topbar-name">RAG Command Center</div><div class="topbar-sub">CRM · Contacts</div></div></div>
<nav class="topbar-tabs"><a class="tab-btn" href="command-center.html">Dashboard</a><a class="tab-btn active" href="contacts.html">Contacts</a><a class="tab-btn" href="pipeline.html">Pipeline</a><a class="tab-btn" href="signals.html">Signals</a><a class="tab-btn" href="commission.html">Commissions</a><a class="tab-btn" href="settings.html">Settings</a></nav></header>
<div class="main">
<div class="page-hd"><h1 style="font-family:var(--serif)">Contacts</h1><p>CRM · manage clients, leads, and relationships</p></div>
<div class="stats-row"><div class="stat-mini"><div class="lbl">Total Contacts</div><div class="val" id="s-total">0</div></div><div class="stat-mini"><div class="lbl">Buyers</div><div class="val" id="s-buyers">0</div></div><div class="stat-mini"><div class="lbl">Sellers</div><div class="val" id="s-sellers">0</div></div><div class="stat-mini"><div class="lbl">Investors</div><div class="val" id="s-investors">0</div></div></div>
<div class="filters-bar"><input class="fi" id="c-search" placeholder="Search name, email, phone…" style="width:240px" oninput="renderContacts()"><select class="fi" id="c-type" onchange="renderContacts()"><option value="">All Types</option><option value="buyer">Buyer</option><option value="seller">Seller</option><option value="investor">Investor</option><option value="referral">Referral</option></select><select class="fi" id="c-agent" onchange="renderContacts()"><option value="">All Agents</option><option value="Ricki Kohli">Ricki</option><option value="Amit Khatkar">Amit</option><option value="Gary Doman">Gary</option></select><select class="fi" id="c-score" onchange="renderContacts()"><option value="">All Scores</option><option value="hot">Hot</option><option value="warm">Warm</option><option value="cold">Cold</option></select><div style="margin-left:auto"><button class="btn-p" onclick="openAddContact()">+ Add Contact</button></div></div>
<div class="card"><div class="card-hd"><h2>Contact List</h2><span style="font-family:var(--mono);font-size:10px;color:var(--ink3)" id="c-count">0 contacts</span></div><table><thead><tr><th>Name</th><th>Type</th><th>Phone</th><th>Email</th><th>Agent</th><th>Score</th><th>Last Contact</th><th>Tags</th></tr></thead><tbody id="c-tbody"></tbody></table></div></div>
<div class="modal-overlay" id="addModal" onclick="if(event.target===this)closeAddContact()"><div class="modal"><div class="modal-hd"><h2>Add Contact</h2><button class="btn-s" onclick="closeAddContact()">✕</button></div><div class="modal-body">
<div class="fgroup"><label class="flabel">Full Name *</label><input class="fi" id="ac-name" style="width:100%" placeholder="Jane Smith"></div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:12px">
<div class="fgroup"><label class="flabel">Phone</label><input class="fi" id="ac-phone" style="width:100%" placeholder="(604) 555-0100"></div>
<div class="fgroup"><label class="flabel">Email</label><input class="fi" id="ac-email" style="width:100%" type="email" placeholder="jane@email.com"></div>
<div class="fgroup"><label class="flabel">Type</label><select class="fi" id="ac-type" style="width:100%"><option value="buyer">Buyer</option><option value="seller">Seller</option><option value="investor">Investor</option><option value="referral">Referral</option></select></div>
<div class="fgroup"><label class="flabel">Assigned Agent</label><select class="fi" id="ac-agent" style="width:100%"><option value="">Unassigned</option><option value="Ricki Kohli">Ricki Kohli</option><option value="Amit Khatkar">Amit Khatkar</option><option value="Gary Doman">Gary Doman</option></select></div>
<div class="fgroup"><label class="flabel">Budget Max ($)</label><input class="fi" id="ac-budget" style="width:100%" type="number" placeholder="650000"></div>
<div class="fgroup"><label class="flabel">Source</label><select class="fi" id="ac-source" style="width:100%"><option value="website">Website</option><option value="referral">Referral</option><option value="social">Social Media</option><option value="open_house">Open House</option><option value="cold_call">Cold Call</option><option value="other">Other</option></select></div>
</div>
<div class="fgroup"><label class="flabel">Tags (comma-separated)</label><input class="fi" id="ac-tags" style="width:100%" placeholder="pre-approved, Victoria, first-time"></div>
<div class="fgroup"><label class="flabel">Notes</label><textarea class="fi" id="ac-notes" style="width:100%;min-height:72px;resize:vertical" placeholder="Initial notes about this contact…"></textarea></div>
<div style="display:flex;gap:10px;margin-top:16px"><button class="btn-p" onclick="saveContact()">Save Contact</button><button class="btn-s" onclick="closeAddContact()">Cancel</button></div>
</div></div></div>
<script>
/* === localStorage-backed CRM contacts === */
const CRM_KEY = 'rag_crm_contacts';
function loadContacts(){ try{ return JSON.parse(localStorage.getItem(CRM_KEY)||'[]'); }catch{ return []; } }
function saveContacts(arr){ localStorage.setItem(CRM_KEY, JSON.stringify(arr)); }
function openAddContact(){ document.getElementById('addModal').classList.add('open'); }
function closeAddContact(){ document.getElementById('addModal').classList.remove('open'); }
function saveContact(){
const name = document.getElementById('ac-name').value.trim();
if(!name){ alert('Name is required.'); return; }
const contacts = loadContacts();
contacts.unshift({
id: 'c_'+Date.now().toString(36)+Math.random().toString(36).slice(2,6),
name, phone: document.getElementById('ac-phone').value.trim(),
email: document.getElementById('ac-email').value.trim(),
type: document.getElementById('ac-type').value,
agent: document.getElementById('ac-agent').value,
budget: Number(document.getElementById('ac-budget').value)||null,
source: document.getElementById('ac-source').value,
tags: document.getElementById('ac-tags').value.split(',').map(t=>t.trim()).filter(Boolean),
notes: document.getElementById('ac-notes').value.trim(),
score: 'warm', interactions: [],
created_at: new Date().toISOString(), last_contact: null
});
saveContacts(contacts);
closeAddContact();
['ac-name','ac-phone','ac-email','ac-budget','ac-tags','ac-notes'].forEach(id=>{ const el=document.getElementById(id); if(el) el.value=''; });
renderContacts();
}
function renderContacts(){
const contacts = loadContacts();
const q = (document.getElementById('c-search').value||'').toLowerCase();
const type = document.getElementById('c-type').value;
const agent = document.getElementById('c-agent').value;
const score = document.getElementById('c-score').value;
let rows = contacts.filter(c=>{
if(q && !`${c.name} ${c.email} ${c.phone} ${(c.tags||[]).join(' ')}`.toLowerCase().includes(q)) return false;
if(type && c.type !== type) return false;
if(agent && c.agent !== agent) return false;
if(score && c.score !== score) return false;
return true;
});
document.getElementById('s-total').textContent = contacts.length;
document.getElementById('s-buyers').textContent = contacts.filter(c=>c.type==='buyer').length;
document.getElementById('s-sellers').textContent = contacts.filter(c=>c.type==='seller').length;
document.getElementById('s-investors').textContent = contacts.filter(c=>c.type==='investor').length;
document.getElementById('c-count').textContent = rows.length + ' contacts';
const tbody = document.getElementById('c-tbody');
if(!rows.length){ tbody.innerHTML = '<tr><td colspan="8" class="empty">No contacts yet. Click + Add Contact to get started.</td></tr>'; return; }
tbody.innerHTML = rows.map(c => `<tr>
<td style="font-weight:700">${escapeHtml(c.name)}</td>
<td>${escapeHtml(c.type||'—')}</td>
<td style="font-family:var(--mono);font-size:11px">${escapeHtml(c.phone||'—')}</td>
<td style="font-family:var(--mono);font-size:11px">${escapeHtml(c.email||'—')}</td>
<td>${escapeHtml(c.agent||'Unassigned')}</td>
<td><span class="tag ${c.score||'warm'}">${escapeHtml(c.score||'warm')}</span></td>
<td style="font-family:var(--mono);font-size:10px;color:var(--ink3)">${c.last_contact ? new Date(c.last_contact).toLocaleDateString() : 'Never'}</td>
<td>${(c.tags||[]).map(t=>`<span class="tag green">${escapeHtml(t)}</span>`).join(' ')}</td>
</tr>`).join('');
}
/* Auto-import pipeline leads into CRM contacts */
(async function(){
try {
const data = await GRR.loadData();
const leads = data.internal.leads || [];
const state = GRR.loadState();
const inquiries = state.inquiries || [];
const all = [...leads, ...inquiries.map(i=>({name:i.name,email:i.email,phone:i.phone||'',intent:i.intent||'inquiry',market:i.market||'',budget:i.budget||'',notes:i.notes||'',source:i.source||'website',created_at:i.created_at||'',score_band:i.score_band||'warm'}))];
if(!all.length){ renderContacts(); return; }
const contacts = loadContacts();
const existingEmails = new Set(contacts.map(c=>(c.email||'').toLowerCase()).filter(Boolean));
const existingNames = new Set(contacts.map(c=>(c.name||'').toLowerCase()));
let imported = 0;
all.forEach(l => {
const email = (l.email||'').toLowerCase();
const name = (l.name||l.full_name||'').trim();
if(!name) return;
if(email && existingEmails.has(email)) return;
if(existingNames.has(name.toLowerCase())) return;
contacts.push({
id: 'c_'+Date.now().toString(36)+Math.random().toString(36).slice(2,6),
name, phone: l.phone||'', email: l.email||'',
type: (l.intent||'').toLowerCase().includes('sell') ? 'seller' : 'buyer',
agent: '', budget: l.budget_max||l.budget||null,
source: l.source||'pipeline', tags: (l.target_areas||[]).concat(l.market?[l.market]:[]),
notes: l.notes||l.message||'', score: l.score_band||l.score||'warm',
interactions: [], created_at: l.created_at||new Date().toISOString(), last_contact: null
});
if(email) existingEmails.add(email);
existingNames.add(name.toLowerCase());
imported++;
});
if(imported) saveContacts(contacts);
} catch(e){ console.warn('Pipeline import:', e); }
renderContacts();
})();
</script>