Unification polish: LinkedIn in the grid inline contact editor (v0.1.0:54)

The fundraising grid's per-contact editor now has a LinkedIn URL field next to
name, email, title, and location. It threads through the grid contact object and
sanitize (which preserves contact-object fields), and _upsert_contact_from_fundraising
now reads and persists linkedin_url on both the update and insert paths — so a
LinkedIn entered in the grid lands on the linked contact record.

Test: test_grid_contact_link.py extended to assert LinkedIn entered in the grid
persists to the contact (idempotent). Frontend html.parser clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Keysat
2026-06-05 15:24:50 -05:00
parent 49d384a0fb
commit 300041a7ec
6 changed files with 50 additions and 14 deletions
+15 -4
View File
@@ -6946,9 +6946,9 @@
const contacts = Array.isArray(value) ? [...value] : [];
const updateContacts = (next) => updateCell(row.id, col.id, next);
return (
<div style={{ minWidth: '380px', background: '#0b1118', border: '1px solid #263548', borderRadius: '8px', padding: '8px' }}>
<div style={{ minWidth: '480px', background: '#0b1118', border: '1px solid #263548', borderRadius: '8px', padding: '8px' }}>
{contacts.map((c, idx) => (
<div key={idx} style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr auto', gap: '6px', marginBottom: '6px' }}>
<div key={idx} style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr 1fr auto', gap: '6px', marginBottom: '6px' }}>
<input className="text-input" placeholder="Name" value={c.name || ''} onChange={(e) => {
const next = [...contacts];
next[idx] = { ...next[idx], name: e.target.value };
@@ -6982,6 +6982,17 @@
moveByKey(rowIndex, colIndex, 'Enter');
}
}} />
<input className="text-input" placeholder="LinkedIn URL" value={c.linkedin_url || ''} onChange={(e) => {
const next = [...contacts];
next[idx] = { ...next[idx], linkedin_url: e.target.value };
updateContacts(next);
}} onKeyDown={(e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
setEditing(null);
moveByKey(rowIndex, colIndex, 'Enter');
}
}} />
<input
className="text-input"
list="location-suggestions"
@@ -7012,13 +7023,13 @@
const next = contacts.filter((_, i) => i !== idx);
updateContacts(next);
}}>×</button>
<div style={{ gridColumn: '1 / span 5', fontSize: '11px', color: '#8ea2b7' }}>
<div style={{ gridColumn: '1 / span 6', fontSize: '11px', color: '#8ea2b7' }}>
{c.city || c.state || c.country ? `${c.city || '-'}, ${c.state || '-'}, ${c.country || '-'}` : 'No location set'}
</div>
</div>
))}
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<button type="button" className="button-secondary" onClick={() => updateContacts([...contacts, { name: '', email: '', title: '', city: '', state: '', country: '', location_query: '' }])}>+ Contact</button>
<button type="button" className="button-secondary" onClick={() => updateContacts([...contacts, { name: '', email: '', title: '', linkedin_url: '', city: '', state: '', country: '', location_query: '' }])}>+ Contact</button>
<button type="button" onClick={() => { setEditing(null); moveFocus(rowIndex + 1, colIndex); }}>Done</button>
</div>
<datalist id="location-suggestions">