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.
|
// DealOrgMember is a person associated with a deal org.
|
||||||
type DealOrgMember struct {
|
type DealOrgMember struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
Title string `json:"title,omitempty"`
|
Title string `json:"title,omitempty"`
|
||||||
Phone string `json:"phone,omitempty"`
|
Phone string `json:"phone,omitempty"` // legacy / kept for compat
|
||||||
Photo string `json:"photo,omitempty"`
|
PhoneBusiness string `json:"phone_business,omitempty"`
|
||||||
Bio string `json:"bio,omitempty"`
|
PhonePersonal string `json:"phone_personal,omitempty"`
|
||||||
LinkedIn string `json:"linkedin,omitempty"`
|
Photo string `json:"photo,omitempty"`
|
||||||
|
Bio string `json:"bio,omitempty"`
|
||||||
|
LinkedIn string `json:"linkedin,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// User represents an account.
|
// 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>
|
<div id="noMembers" class="hidden text-sm text-center py-6" style="color:var(--ds-tx3)">No members yet</div>
|
||||||
<!-- Add manually -->
|
<!-- Add manually -->
|
||||||
<div class="pt-3 border-t" style="border-color:var(--ds-bd)">
|
<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>
|
<p class="text-xs mb-2 font-medium" style="color:var(--ds-tx3)">Add manually</p>
|
||||||
<div class="grid gap-2" style="grid-template-columns:1fr 1fr 1fr auto">
|
<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="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="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]">
|
<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]">
|
||||||
<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="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>
|
||||||
<div id="addMemberError" class="hidden mt-2 text-xs text-red-400"></div>
|
<div id="addMemberError" class="hidden mt-2 text-xs text-red-400"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -228,6 +230,7 @@
|
||||||
if (!o) return;
|
if (!o) return;
|
||||||
editingOrgId = o.entry_id;
|
editingOrgId = o.entry_id;
|
||||||
editingMembers = (o.members || []).map(m => Object.assign({}, m));
|
editingMembers = (o.members || []).map(m => Object.assign({}, m));
|
||||||
|
window._memberExpanded = {};
|
||||||
document.getElementById('eOrgId').value = o.entry_id;
|
document.getElementById('eOrgId').value = o.entry_id;
|
||||||
document.getElementById('eVersion').value = o.version || 1;
|
document.getElementById('eVersion').value = o.version || 1;
|
||||||
document.getElementById('eName').value = o.name || '';
|
document.getElementById('eName').value = o.name || '';
|
||||||
|
|
@ -318,22 +321,51 @@
|
||||||
none.classList.add('hidden');
|
none.classList.add('hidden');
|
||||||
list.innerHTML = editingMembers.map((m, i) => {
|
list.innerHTML = editingMembers.map((m, i) => {
|
||||||
const initial = (m.name || m.email || '?')[0].toUpperCase();
|
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="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="flex-1 min-w-0">'
|
||||||
+ '<div class="text-sm font-medium truncate" style="color:var(--ds-tx)">' + escHtml(m.name || m.email || '—') + '</div>'
|
+ '<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>' : '')
|
+ '<div class="text-xs truncate" style="color:var(--ds-tx3)">'
|
||||||
+ (m.email && m.name ? '<div class="text-xs truncate" style="color:var(--ds-tx3)">' + escHtml(m.email) + '</div>' : '')
|
+ [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>'
|
+ '</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>';
|
+ '</div>';
|
||||||
}).join('');
|
}).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() {
|
function addGlobalMember() {
|
||||||
const name = document.getElementById('newMemberName').value.trim();
|
const name = document.getElementById('newMemberName').value.trim();
|
||||||
const email = document.getElementById('newMemberEmail').value.trim();
|
const email = document.getElementById('newMemberEmail').value.trim();
|
||||||
const title = document.getElementById('newMemberTitle').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');
|
const errEl = document.getElementById('addMemberError');
|
||||||
if (!name && !email) {
|
if (!name && !email) {
|
||||||
errEl.textContent = 'Enter a name or email.';
|
errEl.textContent = 'Enter a name or email.';
|
||||||
|
|
@ -343,10 +375,9 @@
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
errEl.classList.add('hidden');
|
errEl.classList.add('hidden');
|
||||||
editingMembers.push({ name, email, title, phone: '', photo: '', bio: '', linkedin: '' });
|
editingMembers.push({ name, email, title, phone_business, phone_personal, photo: '', bio: '', linkedin: '' });
|
||||||
document.getElementById('newMemberName').value = '';
|
['newMemberName','newMemberEmail','newMemberTitle','newMemberPhoneBiz','newMemberPhonePersonal'].forEach(id => document.getElementById(id).value = '');
|
||||||
document.getElementById('newMemberEmail').value = '';
|
window._memberExpanded = {};
|
||||||
document.getElementById('newMemberTitle').value = '';
|
|
||||||
renderMemberList();
|
renderMemberList();
|
||||||
document.getElementById('newMemberName').focus();
|
document.getElementById('newMemberName').focus();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue