feat: member inline edit (name/title/email/biz+personal phone); expand/collapse rows; add form includes phone fields
This commit is contained in:
parent
5291edfa94
commit
ce80aeeb4a
16
lib/types.go
16
lib/types.go
|
|
@ -106,13 +106,15 @@ type DealOrgPerms struct {
|
|||
|
||||
// DealOrgMember is a person associated with a deal org.
|
||||
type DealOrgMember struct {
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
Title string `json:"title,omitempty"`
|
||||
Phone string `json:"phone,omitempty"`
|
||||
Photo string `json:"photo,omitempty"`
|
||||
Bio string `json:"bio,omitempty"`
|
||||
LinkedIn string `json:"linkedin,omitempty"`
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
Title string `json:"title,omitempty"`
|
||||
Phone string `json:"phone,omitempty"` // legacy / kept for compat
|
||||
PhoneBusiness string `json:"phone_business,omitempty"`
|
||||
PhonePersonal string `json:"phone_personal,omitempty"`
|
||||
Photo string `json:"photo,omitempty"`
|
||||
Bio string `json:"bio,omitempty"`
|
||||
LinkedIn string `json:"linkedin,omitempty"`
|
||||
}
|
||||
|
||||
// User represents an account.
|
||||
|
|
|
|||
|
|
@ -109,12 +109,14 @@
|
|||
<div id="noMembers" class="hidden text-sm text-center py-6" style="color:var(--ds-tx3)">No members yet</div>
|
||||
<!-- Add manually -->
|
||||
<div class="pt-3 border-t" style="border-color:var(--ds-bd)">
|
||||
<p class="text-xs mb-2" style="color:var(--ds-tx3)">Add manually</p>
|
||||
<div class="grid gap-2" style="grid-template-columns:1fr 1fr 1fr auto">
|
||||
<p class="text-xs mb-2 font-medium" style="color:var(--ds-tx3)">Add manually</p>
|
||||
<div class="grid gap-2 mb-2" style="grid-template-columns:1fr 1fr">
|
||||
<input type="text" id="newMemberName" placeholder="Name" class="px-3 py-2 bg-[#0a1628] border border-white/[0.08] rounded-lg text-white text-sm focus:outline-none focus:border-[#c9a84c] placeholder-[#64748b]">
|
||||
<input type="text" id="newMemberTitle" placeholder="Title" class="px-3 py-2 bg-[#0a1628] border border-white/[0.08] rounded-lg text-white text-sm focus:outline-none focus:border-[#c9a84c] placeholder-[#64748b]">
|
||||
<input type="email" id="newMemberEmail" placeholder="Email" class="px-3 py-2 bg-[#0a1628] border border-white/[0.08] rounded-lg text-white text-sm focus:outline-none focus:border-[#c9a84c] placeholder-[#64748b]">
|
||||
<input type="text" id="newMemberTitle" placeholder="Title" onkeydown="if(event.key==='Enter'){event.preventDefault();addGlobalMember();}" class="px-3 py-2 bg-[#0a1628] border border-white/[0.08] rounded-lg text-white text-sm focus:outline-none focus:border-[#c9a84c] placeholder-[#64748b]">
|
||||
<button onclick="addGlobalMember()" class="px-3 py-2 bg-[#c9a84c] hover:bg-[#b8973f] text-[#0a1628] font-bold rounded-lg text-sm transition">+</button>
|
||||
<input type="tel" id="newMemberPhoneBiz" placeholder="Business phone" class="px-3 py-2 bg-[#0a1628] border border-white/[0.08] rounded-lg text-white text-sm focus:outline-none focus:border-[#c9a84c] placeholder-[#64748b]">
|
||||
<input type="tel" id="newMemberPhonePersonal" placeholder="Personal phone" onkeydown="if(event.key==='Enter'){event.preventDefault();addGlobalMember();}" class="px-3 py-2 bg-[#0a1628] border border-white/[0.08] rounded-lg text-white text-sm focus:outline-none focus:border-[#c9a84c] placeholder-[#64748b]">
|
||||
<button onclick="addGlobalMember()" class="px-3 py-2 bg-[#c9a84c] hover:bg-[#b8973f] text-[#0a1628] font-bold rounded-lg text-sm transition">+ Add person</button>
|
||||
</div>
|
||||
<div id="addMemberError" class="hidden mt-2 text-xs text-red-400"></div>
|
||||
</div>
|
||||
|
|
@ -228,6 +230,7 @@
|
|||
if (!o) return;
|
||||
editingOrgId = o.entry_id;
|
||||
editingMembers = (o.members || []).map(m => Object.assign({}, m));
|
||||
window._memberExpanded = {};
|
||||
document.getElementById('eOrgId').value = o.entry_id;
|
||||
document.getElementById('eVersion').value = o.version || 1;
|
||||
document.getElementById('eName').value = o.name || '';
|
||||
|
|
@ -318,22 +321,51 @@
|
|||
none.classList.add('hidden');
|
||||
list.innerHTML = editingMembers.map((m, i) => {
|
||||
const initial = (m.name || m.email || '?')[0].toUpperCase();
|
||||
return '<div class="flex items-center gap-3 px-3 py-2.5 rounded-lg" style="background:var(--ds-hv)">'
|
||||
const expanded = window._memberExpanded && window._memberExpanded[i];
|
||||
return '<div class="rounded-lg overflow-hidden mb-1" style="border:1px solid var(--ds-bd)">'
|
||||
// header row
|
||||
+ '<div class="flex items-center gap-3 px-3 py-2.5 cursor-pointer" style="background:var(--ds-hv)" onclick="toggleMemberExpand(' + i + ')">'
|
||||
+ '<div class="w-8 h-8 rounded-full shrink-0 flex items-center justify-center text-sm font-semibold" style="background:var(--ds-ac);color:var(--ds-act)">' + escHtml(initial) + '</div>'
|
||||
+ '<div class="flex-1 min-w-0">'
|
||||
+ '<div class="text-sm font-medium truncate" style="color:var(--ds-tx)">' + escHtml(m.name || m.email || '—') + '</div>'
|
||||
+ (m.title ? '<div class="text-xs truncate" style="color:var(--ds-tx3)">' + escHtml(m.title) + '</div>' : '')
|
||||
+ (m.email && m.name ? '<div class="text-xs truncate" style="color:var(--ds-tx3)">' + escHtml(m.email) + '</div>' : '')
|
||||
+ '<div class="text-xs truncate" style="color:var(--ds-tx3)">'
|
||||
+ [m.title, m.email].filter(Boolean).map(escHtml).join(' · ')
|
||||
+ (m.phone_business ? ' · ' + escHtml(m.phone_business) : '')
|
||||
+ '</div>'
|
||||
+ '</div>'
|
||||
+ '<svg class="w-3.5 h-3.5 shrink-0 transition-transform ' + (expanded ? 'rotate-180' : '') + '" style="color:var(--ds-tx3)" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/></svg>'
|
||||
+ '</div>'
|
||||
// edit panel (collapsed by default)
|
||||
+ '<div class="' + (expanded ? '' : 'hidden') + ' p-3" style="background:var(--ds-bg)">'
|
||||
+ '<div class="grid gap-2 mb-2" style="grid-template-columns:1fr 1fr">'
|
||||
+ '<div><label class="text-xs mb-1 block" style="color:var(--ds-tx3)">Name</label><input type="text" value="' + escHtml(m.name||'') + '" oninput="updateMember(' + i + ','name',this.value)" class="w-full px-2 py-1.5 bg-[#0a1628] border border-white/[0.08] rounded text-white text-sm focus:outline-none focus:border-[#c9a84c]"></div>'
|
||||
+ '<div><label class="text-xs mb-1 block" style="color:var(--ds-tx3)">Title</label><input type="text" value="' + escHtml(m.title||'') + '" oninput="updateMember(' + i + ','title',this.value)" class="w-full px-2 py-1.5 bg-[#0a1628] border border-white/[0.08] rounded text-white text-sm focus:outline-none focus:border-[#c9a84c]"></div>'
|
||||
+ '<div><label class="text-xs mb-1 block" style="color:var(--ds-tx3)">Email</label><input type="email" value="' + escHtml(m.email||'') + '" oninput="updateMember(' + i + ','email',this.value)" class="w-full px-2 py-1.5 bg-[#0a1628] border border-white/[0.08] rounded text-white text-sm focus:outline-none focus:border-[#c9a84c]"></div>'
|
||||
+ '<div><label class="text-xs mb-1 block" style="color:var(--ds-tx3)">Business phone</label><input type="tel" value="' + escHtml(m.phone_business||m.phone||'') + '" oninput="updateMember(' + i + ','phone_business',this.value)" class="w-full px-2 py-1.5 bg-[#0a1628] border border-white/[0.08] rounded text-white text-sm focus:outline-none focus:border-[#c9a84c]"></div>'
|
||||
+ '<div><label class="text-xs mb-1 block" style="color:var(--ds-tx3)">Personal phone</label><input type="tel" value="' + escHtml(m.phone_personal||'') + '" oninput="updateMember(' + i + ','phone_personal',this.value)" class="w-full px-2 py-1.5 bg-[#0a1628] border border-white/[0.08] rounded text-white text-sm focus:outline-none focus:border-[#c9a84c]"></div>'
|
||||
+ '</div>'
|
||||
+ '<button onclick="removeGlobalMember(' + i + ')" class="px-2 py-1 rounded text-xs transition hover:opacity-80" style="background:rgba(239,68,68,0.1);color:#ef4444;border:1px solid rgba(239,68,68,0.2)">Remove person</button>'
|
||||
+ '</div>'
|
||||
+ '<button onclick="removeGlobalMember(' + i + ')" class="shrink-0 px-2 py-1 rounded text-xs transition hover:opacity-80" style="background:rgba(239,68,68,0.1);color:#ef4444">Remove</button>'
|
||||
+ '</div>';
|
||||
}).join('');
|
||||
}
|
||||
|
||||
function toggleMemberExpand(i) {
|
||||
if (!window._memberExpanded) window._memberExpanded = {};
|
||||
window._memberExpanded[i] = !window._memberExpanded[i];
|
||||
renderMemberList();
|
||||
}
|
||||
|
||||
function updateMember(i, field, value) {
|
||||
if (editingMembers[i]) editingMembers[i][field] = value;
|
||||
}
|
||||
|
||||
function addGlobalMember() {
|
||||
const name = document.getElementById('newMemberName').value.trim();
|
||||
const email = document.getElementById('newMemberEmail').value.trim();
|
||||
const title = document.getElementById('newMemberTitle').value.trim();
|
||||
const phone_business = document.getElementById('newMemberPhoneBiz').value.trim();
|
||||
const phone_personal = document.getElementById('newMemberPhonePersonal').value.trim();
|
||||
const errEl = document.getElementById('addMemberError');
|
||||
if (!name && !email) {
|
||||
errEl.textContent = 'Enter a name or email.';
|
||||
|
|
@ -343,10 +375,9 @@
|
|||
return;
|
||||
}
|
||||
errEl.classList.add('hidden');
|
||||
editingMembers.push({ name, email, title, phone: '', photo: '', bio: '', linkedin: '' });
|
||||
document.getElementById('newMemberName').value = '';
|
||||
document.getElementById('newMemberEmail').value = '';
|
||||
document.getElementById('newMemberTitle').value = '';
|
||||
editingMembers.push({ name, email, title, phone_business, phone_personal, photo: '', bio: '', linkedin: '' });
|
||||
['newMemberName','newMemberEmail','newMemberTitle','newMemberPhoneBiz','newMemberPhonePersonal'].forEach(id => document.getElementById(id).value = '');
|
||||
window._memberExpanded = {};
|
||||
renderMemberList();
|
||||
document.getElementById('newMemberName').focus();
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue