Embed Edit Existing Prospect panel directly in the dashboard

Mckinley wants Edit in the dash itself, not the separate localhost
page - the earlier split (Add embedded, Edit stays remote) didn't
hold. Built the same picker -> field -> current-value -> new-value ->
submit flow as Input Interface's standalone page (reused their exact
UX pattern, restyled to match this dashboard), calling their JSON API
via absolute URLs (this page isn't served by their Flask app, so
relative paths from their reference wouldn't resolve):

  GET  http://localhost:8507/prospect-scout/api/entries
  GET  http://localhost:8507/prospect-scout/api/entries/<id>
  POST http://localhost:8507/prospect-scout/api/edit

Category field swaps to a select (matching the 6 known categories)
instead of free text, same as their reference. Editing contact_email
shows the same "counts as human confirmation" hint. Viewing the
dashboard stays fully static/dependency-free; only Add and Edit
require the backend running, same trade-off already accepted for Add.

Verified via jsdom with a stubbed backend: entry list loads, field
list populates per entry, current value fetches and displays, email
hint shows correctly, category field renders as a select with the
right value pre-selected, submit posts and updates status, and none
of the existing 73 rendered cards regressed.
This commit is contained in:
beantownbabe committed 2026-09-01 18:34:24 +02:00
1 parent b34b75224b
commit f1f0d23e7e
1 file changed
+155 -6
+155 -6
View File
@@ -208,12 +208,15 @@
.layout { flex-direction: column; }
.sidebar { width: 100%; min-width: 0; height: auto; position: static; }
}
#ps-add-panel .ps-add-row { display: flex; gap: 16px; flex-wrap: wrap; align-items: flex-end; margin-top: 12px; }
#ps-add-panel .ps-add-field { display: flex; flex-direction: column; gap: 5px; min-width: 180px; }
#ps-add-panel label { font-size: 12px; font-weight: 800; color: var(--navy); text-transform: uppercase; letter-spacing: 0.04em; }
#ps-add-panel select, #ps-add-panel input[type="text"] { padding: 9px 11px; border: 1px solid var(--border); border-radius: 6px; font-size: 14px; font-family: var(--sans); color: var(--txt); background: var(--surface); }
#ps-add-panel button { padding: 10px 20px; border: none; border-radius: 6px; background: var(--navy); color: #fff; font-weight: 700; font-size: 14px; cursor: pointer; white-space: nowrap; }
#ps-add-panel button:disabled { background: var(--txt-m); cursor: not-allowed; opacity: 0.6; }
#ps-add-panel .ps-add-row, #ps-edit-panel .ps-add-row { display: flex; gap: 16px; flex-wrap: wrap; align-items: flex-end; margin-top: 12px; }
#ps-add-panel .ps-add-field, #ps-edit-panel .ps-add-field { display: flex; flex-direction: column; gap: 5px; min-width: 180px; }
#ps-add-panel label, #ps-edit-panel label { font-size: 12px; font-weight: 800; color: var(--navy); text-transform: uppercase; letter-spacing: 0.04em; }
#ps-add-panel select, #ps-add-panel input[type="text"], #ps-edit-panel select, #ps-edit-panel input[type="text"], #ps-edit-panel textarea { padding: 9px 11px; border: 1px solid var(--border); border-radius: 6px; font-size: 14px; font-family: var(--sans); color: var(--txt); background: var(--surface); }
#ps-add-panel button, #ps-edit-panel button { padding: 10px 20px; border: none; border-radius: 6px; background: var(--navy); color: #fff; font-weight: 700; font-size: 14px; cursor: pointer; white-space: nowrap; }
#ps-add-panel button:disabled, #ps-edit-panel button:disabled { background: var(--txt-m); cursor: not-allowed; opacity: 0.6; }
#ps-edit-panel textarea { width: 100%; min-height: 60px; box-sizing: border-box; resize: vertical; }
#ps-edit-panel .ps-edit-current { margin-top: 10px; padding: 10px 12px; background: var(--border-bg, rgba(0,0,0,0.03)); border-radius: 6px; font-size: 13px; color: var(--txt-s); word-break: break-word; }
#ps-edit-panel .ps-edit-full { display: flex; flex-direction: column; gap: 5px; margin-top: 12px; }
</style>
</head>
<body>
@@ -1033,6 +1036,30 @@
<div class="note mt16" id="ps-status" style="min-height:16px;"></div>
</div>
<div class="panel mt16" id="ps-edit-panel">
<div class="panel-title">Edit an Existing Prospect</div>
<div class="ps-add-row">
<div class="ps-add-field" style="min-width:260px;">
<label for="ps-edit-entry">Entry</label>
<select id="ps-edit-entry"><option value="">Loading…</option></select>
</div>
<div class="ps-add-field">
<label for="ps-edit-field">Field to edit</label>
<select id="ps-edit-field"><option value="">Choose an entry first…</option></select>
</div>
</div>
<div class="ps-edit-current" id="ps-edit-current" style="display:none;"></div>
<div class="ps-edit-full" id="ps-edit-value-wrap" style="display:none;">
<label for="ps-edit-value">New value</label>
<textarea id="ps-edit-value"></textarea>
<div class="note" id="ps-edit-email-hint" style="display:none;">Editing contact_email counts as a human confirmation — no "unverified guess" caveat will be attached.</div>
<div>
<button id="ps-edit-submit" disabled style="margin-top:6px;">Save Change</button>
</div>
</div>
<div class="note mt16" id="ps-edit-status" style="min-height:16px;"></div>
</div>
<button class="acc-header acc-header-f mt16" onclick="toggleAcc('prospect-scout-note-body', this)">
<div class="acc-left"><span class="cat cat-f">Methodology / Verification Note</span><span class="cat-desc">Sourcing details, corrections, and limitations for this sweep — click to expand</span></div>
<span class="acc-chevron">▾</span>
@@ -2077,6 +2104,128 @@ function initProspectAddForm() {
});
}
initProspectAddForm();
var PS_API_BASE = "http://localhost:8507/prospect-scout/api";
var PS_CATEGORIES = [
"Semiconductor Materials & Substrates (GaN/SiC)",
"Power Electronics (Devices)",
"WBG Device Manufacturer",
"Device Testing",
"Radiation Hardness",
"Research & Collaborations"
];
var PS_EDITABLE_FIELDS = ["category", "name", "location", "website", "source", "reason", "ceo", "contact_person", "contact_role", "contact_email", "contact_linkedin", "notes"];
function psEscapeHtml(s) {
var d = document.createElement("div");
d.textContent = s || "";
return d.innerHTML;
}
function initProspectEditForm() {
var entrySel = document.getElementById("ps-edit-entry");
var fieldSel = document.getElementById("ps-edit-field");
var currentEl = document.getElementById("ps-edit-current");
var valueWrap = document.getElementById("ps-edit-value-wrap");
var emailHint = document.getElementById("ps-edit-email-hint");
var submitBtn = document.getElementById("ps-edit-submit");
var statusEl = document.getElementById("ps-edit-status");
if (!entrySel) return;
function loadEntries() {
return fetch(PS_API_BASE + "/entries")
.then(function (r) { return r.json(); })
.then(function (data) {
var entries = data.entries || [];
entrySel.innerHTML = '<option value="">Choose…</option>' + entries.map(function (e) {
return '<option value="' + psEscapeHtml(e.id) + '">' + psEscapeHtml(e.name) + ' — ' + psEscapeHtml(e.category) + '</option>';
}).join("");
})
.catch(function () {
entrySel.innerHTML = '<option value="">Couldn\'t reach the Input Interface backend (localhost:8507)</option>';
});
}
entrySel.addEventListener("change", function () {
currentEl.style.display = "none";
valueWrap.style.display = "none";
submitBtn.disabled = true;
statusEl.textContent = "";
if (!entrySel.value) {
fieldSel.innerHTML = '<option value="">Choose an entry first…</option>';
return;
}
fieldSel.innerHTML = '<option value="">Choose a field…</option>' + PS_EDITABLE_FIELDS.map(function (f) {
return '<option value="' + f + '">' + f + '</option>';
}).join("");
});
fieldSel.addEventListener("change", function () {
var id = entrySel.value;
var field = fieldSel.value;
statusEl.textContent = "";
emailHint.style.display = field === "contact_email" ? "block" : "none";
if (!field || !id) { currentEl.style.display = "none"; valueWrap.style.display = "none"; submitBtn.disabled = true; return; }
fetch(PS_API_BASE + "/entries/" + encodeURIComponent(id))
.then(function (r) { return r.json(); })
.then(function (data) {
var entry = data.entry || {};
var valueEl = document.getElementById("ps-edit-value");
if (field === "category") {
var sel = document.createElement("select");
sel.id = "ps-edit-value";
sel.innerHTML = PS_CATEGORIES.map(function (c) {
return '<option value="' + psEscapeHtml(c) + '"' + (c === entry.category ? " selected" : "") + '>' + psEscapeHtml(c) + '</option>';
}).join("");
valueEl.replaceWith(sel);
} else if (valueEl.tagName !== "TEXTAREA") {
var ta = document.createElement("textarea");
ta.id = "ps-edit-value";
valueEl.replaceWith(ta);
} else {
valueEl.value = entry[field] || "";
}
document.getElementById("ps-edit-value").value = entry[field] || "";
currentEl.textContent = "Current value: " + (entry[field] || "(empty)");
currentEl.style.display = "block";
valueWrap.style.display = "flex";
submitBtn.disabled = false;
})
.catch(function () {
statusEl.textContent = "Couldn't reach the Input Interface backend (localhost:8507) — is it running?";
});
});
submitBtn.addEventListener("click", function () {
var id = entrySel.value;
var field = fieldSel.value;
var value = document.getElementById("ps-edit-value").value;
submitBtn.disabled = true;
statusEl.textContent = "Saving…";
fetch(PS_API_BASE + "/edit", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ id: id, field: field, value: value })
})
.then(function (r) { return r.json().then(function (data) { return { ok: r.ok, data: data }; }); })
.then(function (result) {
submitBtn.disabled = false;
if (!result.ok) {
statusEl.textContent = (result.data && result.data.error) || "Something went wrong.";
return;
}
statusEl.textContent = 'Saved "' + field + '" — it will appear on this dashboard the next time it is synced from prospect_scout.json, not immediately.';
currentEl.textContent = "Current value: " + (value || "(empty)");
loadEntries();
})
.catch(function () {
submitBtn.disabled = false;
statusEl.textContent = "Couldn't reach the Input Interface backend (localhost:8507) — is it running?";
});
});
loadEntries();
}
initProspectEditForm();
</script>
</body>
</html>