Add side-by-side selected-contacts panel and simplify contact rows to name + company
Selected panel now mirrors the contact list's width and stays in sync via a pageshow reset and refreshSelectedList(). Contact rows drop the raw email/ conference/draft-opened clutter in favor of just name and company. Also sorts the contact list alphabetically and clears out test-drafting noise that had accumulated in the dash's Outreach Log during today's debugging. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
b745d6f15d
commit
2ac38607c7
3 files changed
+66
-37
No files matched your search
@@ -6,11 +6,12 @@
|
||||
{% if error %}<div class="warning">{{ error }}</div>{% endif %}
|
||||
|
||||
<form method="post">
|
||||
<div class="card">
|
||||
<div style="display:flex; gap:20px; align-items:flex-start;">
|
||||
<div class="card" style="flex:1; min-width:0;">
|
||||
<h2>Contacts <span class="sub" style="font-weight:400;">({{ contacts|length }} total)</span></h2>
|
||||
<div style="margin-bottom:10px;">
|
||||
<button type="button" onclick="document.querySelectorAll('.contact-check').forEach(c=>c.checked=true)">Select All</button>
|
||||
<button type="button" class="secondary" onclick="document.querySelectorAll('.contact-check').forEach(c=>c.checked=false)">Select None</button>
|
||||
<button type="button" onclick="document.querySelectorAll('.contact-check').forEach(c=>c.checked=true); refreshSelectedList();">Select All</button>
|
||||
<button type="button" class="secondary" onclick="document.querySelectorAll('.contact-check').forEach(c=>c.checked=false); refreshSelectedList();">Select None</button>
|
||||
<a href="/" style="margin-left:8px; font-size:0.85rem;">Refresh from Excel</a>
|
||||
</div>
|
||||
<input type="text" id="contact-search" placeholder="Search by name..." style="margin-bottom:10px; max-width:300px;">
|
||||
@@ -19,12 +20,10 @@
|
||||
{% for c in contacts %}
|
||||
{% set is_preselected = preselect_email and c.email|lower == preselect_email %}
|
||||
{% if is_preselected %}{% set ns.found = true %}{% endif %}
|
||||
<div class="contact-row {% if c.sent %}disabled{% endif %}" data-name="{{ (c.first ~ ' ' ~ c.last)|lower }}" {% if is_preselected %}id="preselected-contact" style="background:#eef4fd;"{% endif %}>
|
||||
<div class="contact-row {% if c.sent %}disabled{% endif %}" data-name="{{ (c.first ~ ' ' ~ c.last)|lower }}" data-email="{{ c.email }}" {% if is_preselected %}id="preselected-contact" style="background:#eef4fd;"{% endif %}>
|
||||
<input type="checkbox" class="contact-check" name="contacts" value="{{ c.email }}" {% if c.sent %}disabled{% endif %} {% if is_preselected %}checked{% endif %}>
|
||||
<span>{{ c.first }} {{ c.last }} <{{ c.email }}></span>
|
||||
{% if c.conference %}<span class="meta">— {{ c.conference }}</span>{% endif %}
|
||||
{% if c.draft_opened %}<span class="meta">(draft opened {{ c.draft_opened }})</span>{% endif %}
|
||||
{% if c.sent %}<span class="meta">(already marked as sent)</span>{% endif %}
|
||||
<span>{{ c.first }} {{ c.last }}</span>
|
||||
{% if c.company %}<span class="meta">— {{ c.company }}</span>{% endif %}
|
||||
</div>
|
||||
{% else %}
|
||||
<p class="sub">No contacts in the sheet yet.</p>
|
||||
@@ -34,6 +33,13 @@
|
||||
<p class="warning" style="margin-top:10px;">No contact with email {{ preselect_email }} found in the sheet yet — add them first.</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="card" style="flex:1; min-width:0;">
|
||||
<h2>Selected <span class="sub" style="font-weight:400;" id="selected-count">(0)</span></h2>
|
||||
<div class="contact-list-scroll" id="selected-list">
|
||||
<p class="sub">No one selected yet.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>Message template</h2>
|
||||
@@ -64,7 +70,9 @@
|
||||
</form>
|
||||
<script>
|
||||
// Prevent double-clicks/double-submits from firing the same action twice (e.g. writing
|
||||
// two draft files or opening two Mail.app windows for one click).
|
||||
// two draft files or opening two Mail.app windows for one click). Reset on pageshow so a
|
||||
// browser back-button restore (bfcache) doesn't leave every button permanently disabled
|
||||
// from whatever was clicked before navigating away.
|
||||
document.querySelector("form").addEventListener("submit", function (e) {
|
||||
if (e.target.dataset.submitted) {
|
||||
e.preventDefault();
|
||||
@@ -76,6 +84,14 @@
|
||||
});
|
||||
});
|
||||
|
||||
window.addEventListener("pageshow", function () {
|
||||
var form = document.querySelector("form");
|
||||
delete form.dataset.submitted;
|
||||
document.querySelectorAll("form button[type=submit]").forEach(function (btn) {
|
||||
btn.disabled = false;
|
||||
});
|
||||
});
|
||||
|
||||
var preselected = document.getElementById("preselected-contact");
|
||||
if (preselected) { preselected.scrollIntoView({ block: "center" }); }
|
||||
|
||||
@@ -85,5 +101,33 @@
|
||||
row.classList.toggle("no-match", q !== "" && row.dataset.name.indexOf(q) === -1);
|
||||
});
|
||||
});
|
||||
|
||||
// Mirrors exactly who's currently checked, using the same row markup as the contact
|
||||
// list itself, so there's never a mismatch between "what it says" and "who's selected".
|
||||
function refreshSelectedList() {
|
||||
var selectedList = document.getElementById("selected-list");
|
||||
var selectedCount = document.getElementById("selected-count");
|
||||
var checked = Array.from(document.querySelectorAll(".contact-check:checked"));
|
||||
selectedCount.textContent = "(" + checked.length + ")";
|
||||
if (!checked.length) {
|
||||
selectedList.innerHTML = "<p class=\"sub\">No one selected yet.</p>";
|
||||
return;
|
||||
}
|
||||
selectedList.innerHTML = "";
|
||||
checked.forEach(function (cb) {
|
||||
var row = cb.closest(".contact-row");
|
||||
var clone = row.cloneNode(true);
|
||||
clone.removeAttribute("id");
|
||||
var checkbox = clone.querySelector("input[type=checkbox]");
|
||||
if (checkbox) { checkbox.remove(); }
|
||||
selectedList.appendChild(clone);
|
||||
});
|
||||
}
|
||||
|
||||
document.querySelectorAll(".contact-check").forEach(function (cb) {
|
||||
cb.addEventListener("change", refreshSelectedList);
|
||||
});
|
||||
|
||||
refreshSelectedList();
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -41,19 +41,21 @@ def get_contacts():
|
||||
except Exception:
|
||||
return []
|
||||
contacts = []
|
||||
def clean(value):
|
||||
text = str(value if value is not None else "").strip()
|
||||
return "" if text.lower() == "nan" else text
|
||||
|
||||
for _, row in df.iterrows():
|
||||
conference = str(row.get("Conference", "")).strip()
|
||||
conference = "" if conference.lower() == "nan" else conference
|
||||
draft_opened = str(row.get("Draft Opened", "")).strip()
|
||||
draft_opened = "" if draft_opened.lower() == "nan" else draft_opened
|
||||
contacts.append({
|
||||
"first": row.get("First Name", ""),
|
||||
"last": row.get("Last Name", ""),
|
||||
"email": row.get("Email", ""),
|
||||
"conference": conference,
|
||||
"sent": str(row.get("Materials Sent", "")).strip().lower() == "yes",
|
||||
"draft_opened": draft_opened,
|
||||
"first": clean(row.get("First Name", "")),
|
||||
"last": clean(row.get("Last Name", "")),
|
||||
"email": clean(row.get("Email", "")),
|
||||
"company": clean(row.get("Company", "")),
|
||||
"conference": clean(row.get("Conference", "")),
|
||||
"sent": clean(row.get("Materials Sent", "")).lower() == "yes",
|
||||
"draft_opened": clean(row.get("Draft Opened", "")),
|
||||
})
|
||||
contacts.sort(key=lambda c: (c["first"].lower(), c["last"].lower()))
|
||||
return contacts
|
||||
|
||||
|
||||
|
||||
+1
-18
@@ -3820,24 +3820,7 @@
|
||||
"time": "3:40 PM - 5:10 PM"
|
||||
}
|
||||
],
|
||||
"outreach_log": [
|
||||
{
|
||||
"date": "15.08.26",
|
||||
"note": "Test note - confirming the bridge works"
|
||||
},
|
||||
{
|
||||
"date": "15.08.26",
|
||||
"note": "Draft opened in Mail.app (wavegate_focus.txt), 15.08.26 21:24"
|
||||
},
|
||||
{
|
||||
"date": "16.08.26",
|
||||
"note": "Draft opened in Mail.app (wavegate_focus.txt), 16.08.26 06:10"
|
||||
},
|
||||
{
|
||||
"date": "16.08.26",
|
||||
"note": "Draft opened in Mail.app (reconnect_message.txt), 16.08.26 08:44"
|
||||
}
|
||||
],
|
||||
"outreach_log": [],
|
||||
"notes": "",
|
||||
"value_tags": [
|
||||
"money"
|
||||
|
||||
Reference in new issue
Block a user