let state; let groups = []; let selectedIndex = -1; let currentPdfUrl = ""; const people = document.querySelector("#people"); const fields = document.querySelector("#fields"); const pdf = document.querySelector("#pdf"); const viewer = document.querySelector(".viewer"); const pdfMessage = document.querySelector("#pdfMessage"); const status = document.querySelector("#status"); const search = document.querySelector("#search"); const errorBanner = document.querySelector("#errorBanner"); const fieldDefs = [ ["Name / Company", "name_company"], ["Prospective Buyer", "prospective_buyer"], ["Company", "company"], ["Phone", "phone"], ["Cell", "cell"], ["Email", "email"], ["Address", "address"], ["State", "state"], ["How did you hear", "how_did_you_hear"], ["Interested in updates", "interested_in_updates"], ["Background experience", "background_experience"], ["Types of businesses", "types_of_business_raw"], ["Businesses from Notes", "notes_business_raw"], ["Date of Introduction", "date_of_introduction"], ["Down payment", "down_payment_raw"], ["Total purchase price", "total_purchase_price"], ["Notes Page", "_notes_page"], ["Buyer Info Page", "_info_page"], ["CA Page", "_ca_page"], ]; const esc = (value) => String(value ?? "").replace(/[&<>"']/g, (char) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'", })[char]); function buildGroups(docs) { const map = new Map(); docs.forEach((doc, index) => { const key = doc.name_from_filename; const group = map.get(key) || { key, displayName: doc.prospective_buyer || key, docs: [], text: "", }; group.docs.push({ ...doc, index }); group.text += " " + [ key, doc.prospective_buyer, doc.types_of_business_raw, doc.notes_business_raw, doc.address, ].filter(Boolean).join(" "); map.set(key, group); }); return [...map.values()].sort((a, b) => a.key.localeCompare(b.key)); } function visibleGroups() { const terms = search.value.toLowerCase().trim().split(/\s+/).filter(Boolean); return groups.filter((group) => terms.every((term) => group.text.toLowerCase().includes(term))); } function updateStatus(shownCount = visibleGroups().length) { const source = state.dataSource === "sample" ? "sample data" : "JSON file"; status.textContent = `${shownCount} people / ${state.documents.length} documents · ${source}`; status.className = ""; } function showError(message) { errorBanner.textContent = message; errorBanner.hidden = !message; } function renderList() { const shown = visibleGroups(); people.innerHTML = shown.map((group) => `
${esc(group.displayName)} (${group.docs.length})
${group.docs.map((doc) => ` `).join("")}
`).join(""); updateStatus(shown.length); } async function loadPdf(index) { if (currentPdfUrl) { URL.revokeObjectURL(currentPdfUrl); currentPdfUrl = ""; } pdf.removeAttribute("src"); viewer.classList.remove("loaded"); pdfMessage.hidden = false; pdfMessage.textContent = "Loading PDF…"; try { const response = await fetch(`/api/pdf?index=${index}`, { cache: "no-store" }); if (!response.ok) { const body = await response.json().catch(() => ({})); throw new Error(body.error || `PDF request failed with HTTP ${response.status}.`); } const blob = await response.blob(); currentPdfUrl = URL.createObjectURL(blob); pdf.src = currentPdfUrl; viewer.classList.add("loaded"); pdfMessage.hidden = true; } catch (error) { const message = error instanceof Error ? error.message : String(error); pdfMessage.textContent = `Cannot open PDF: ${message}`; pdfMessage.hidden = false; } } function select(index) { selectedIndex = index; const doc = state.documents[index]; if (!doc) return; fields.innerHTML = `

${esc(doc.name_from_filename)}

${esc(doc.file_name)} · ${esc(doc._doc_type || "unknown")} · ${esc(doc._pages_total ?? "?")} pages

${doc._vision_error ? `

Vision error: ${esc(doc._vision_error)}

` : ""} ${fieldDefs.map(([label, key]) => `
${label}
${esc(doc[key] || "—")}
`).join("")} `; void loadPdf(index); renderList(); } people.addEventListener("click", (event) => { const button = event.target.closest("[data-index]"); if (button) select(Number(button.dataset.index)); }); search.addEventListener("input", renderList); document.addEventListener("keydown", (event) => { if (["INPUT", "TEXTAREA"].includes(document.activeElement.tagName)) return; if (event.key === "ArrowDown" || event.key === "ArrowUp") { event.preventDefault(); const delta = event.key === "ArrowDown" ? 1 : -1; select(Math.max(0, Math.min(state.documents.length - 1, selectedIndex < 0 ? 0 : selectedIndex + delta))); } }); const dialog = document.querySelector("#settingsDialog"); const jsonPath = document.querySelector("#jsonPath"); const pdfBase = document.querySelector("#pdfBase"); const settingsError = document.querySelector("#settingsError"); const saveSettings = document.querySelector("#saveSettings"); document.querySelector("#settings").onclick = () => { jsonPath.value = state.config.jsonPath; pdfBase.value = state.config.pdfBaseDirectory; settingsError.hidden = true; settingsError.textContent = ""; dialog.showModal(); }; saveSettings.onclick = async (event) => { event.preventDefault(); saveSettings.disabled = true; settingsError.hidden = true; try { const response = await fetch("/api/config", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ jsonPath: jsonPath.value, pdfBaseDirectory: pdfBase.value, }), }); const body = await response.json(); if (!response.ok) throw new Error(body.error || "Could not save settings."); dialog.close(); await load(); } catch (error) { settingsError.textContent = error instanceof Error ? error.message : String(error); settingsError.hidden = false; } finally { saveSettings.disabled = false; } }; async function load() { try { const response = await fetch("/api/state", { cache: "no-store" }); if (!response.ok) throw new Error(`State request failed with HTTP ${response.status}.`); state = await response.json(); groups = buildGroups(state.documents); showError(state.loadError || ""); renderList(); if (state.documents.length) select(0); } catch (error) { showError(error instanceof Error ? error.message : String(error)); } } void load();