Refine legal profile dropdown UI

This commit is contained in:
Sean McElwain 2026-06-11 08:04:41 -05:00
parent adcba89350
commit deea989f77
3 changed files with 165 additions and 69 deletions

View File

@ -1165,36 +1165,116 @@ if (downloadExcelTemplateButton) {
/* final legal profile UI normalization */ /* final legal profile UI normalization */
function legalProfileDisplayName(label) {
const value = String(label || "").trim();
if (value === "Legal Profile" || value === "legal_profile") return "Legal";
return value;
}
function normalizeLegalUi() { function normalizeLegalUi() {
const profileSelect = document.getElementById("documentTypeSelect"); const selectedLabel = document.getElementById("selectedDocumentLabel");
const excelSelect = document.getElementById("excelMapSelect"); if (selectedLabel) {
const templateSelect = document.getElementById("legacyTemplateSelect"); selectedLabel.textContent = legalProfileDisplayName(selectedLabel.textContent);
for (const select of [profileSelect, excelSelect, templateSelect]) {
if (select) {
select.classList.add("same-dropdown-style");
}
} }
if (profileSelect) { const description = document.getElementById("documentDescription");
for (const option of [...profileSelect.options]) { if (description && currentDocumentType?.id === "legal_profile") {
if (option.value === "legal_profile" || option.textContent.trim() === "Legal Profile") { description.textContent = "Consumer Debt Defense Legal Profile";
option.textContent = "Legal"; description.classList.add("legal-profile-caption");
}
}
} }
const longText = "Consumer debt defense legal profile based on the legacy app form fields. Additional template fields are calculated at generation time."; setupSelectPicker("excelMapSelect", "Excel Template / Map");
for (const el of [...document.querySelectorAll("p, div, span")]) { setupSelectPicker("legacyTemplateSelect", "Document Template");
const value = el.textContent.trim();
if (value === longText || value === "Consumer Debt Defense Legal Profile") { renderUploadedTemplateDeleteUi().catch(() => {});
el.textContent = "Consumer Debt Defense Legal Profile"; }
el.classList.add("legal-profile-caption");
function setupSelectPicker(selectId, fallbackLabel) {
const select = document.getElementById(selectId);
if (!select) return;
select.classList.add("native-select-hidden");
let picker = document.querySelector(`[data-select-picker-for="${selectId}"]`);
if (!picker) {
picker = document.createElement("div");
picker.className = "document-picker dropdown-picker select-picker";
picker.dataset.selectPickerFor = selectId;
picker.innerHTML = `
<button class="document-picker-toggle select-picker-toggle" type="button">
<span class="select-picker-label">${fallbackLabel}</span>
<span class="dropdown-arrow"></span>
</button>
<div class="document-picker-menu select-picker-menu"></div>
`;
select.insertAdjacentElement("afterend", picker);
const toggle = picker.querySelector(".select-picker-toggle");
toggle.addEventListener("click", event => {
event.preventDefault();
event.stopPropagation();
document.querySelectorAll(".select-picker.open").forEach(openPicker => {
if (openPicker !== picker) openPicker.classList.remove("open");
});
picker.classList.toggle("open");
});
} }
const menu = picker.querySelector(".select-picker-menu");
const label = picker.querySelector(".select-picker-label");
menu.innerHTML = "";
const options = [...select.options].filter(option => option.value !== "");
if (!options.length) {
label.textContent = fallbackLabel;
return;
}
const selectedOption = select.selectedOptions?.[0] || options[0];
label.textContent = selectedOption?.textContent?.trim() || fallbackLabel;
for (const option of options) {
const button = document.createElement("button");
button.type = "button";
button.className = "picker-item";
if (option.value === select.value) button.classList.add("active");
button.textContent = option.textContent.trim();
button.addEventListener("click", event => {
event.preventDefault();
select.value = option.value;
select.dispatchEvent(new Event("change", { bubbles: true }));
picker.classList.remove("open");
setupSelectPicker(selectId, fallbackLabel);
});
menu.appendChild(button);
} }
} }
document.addEventListener("click", () => {
document.querySelectorAll(".select-picker.open").forEach(picker => picker.classList.remove("open"));
});
const originalSetActivePickerForLegalUi = typeof setActivePicker === "function" ? setActivePicker : null;
if (originalSetActivePickerForLegalUi) {
setActivePicker = function(kind, id, label) {
originalSetActivePickerForLegalUi(kind, id, legalProfileDisplayName(label));
normalizeLegalUi();
};
}
const originalRenderDocumentTypeForLegalUi = renderDocumentType;
renderDocumentType = function(documentType) {
originalRenderDocumentTypeForLegalUi(documentType);
normalizeLegalUi();
};
const originalRenderTemplateSelectorForLegalUi = renderTemplateSelector;
renderTemplateSelector = function(documentType) {
originalRenderTemplateSelectorForLegalUi(documentType);
normalizeLegalUi();
};
async function fetchUploadedTemplatesForDeleteUi() { async function fetchUploadedTemplatesForDeleteUi() {
if (!currentDocumentType?.id) return []; if (!currentDocumentType?.id) return [];
const response = await fetch(`/api/doc-generator/uploaded-templates/${encodeURIComponent(currentDocumentType.id)}`); const response = await fetch(`/api/doc-generator/uploaded-templates/${encodeURIComponent(currentDocumentType.id)}`);
@ -1206,10 +1286,8 @@ async function fetchUploadedTemplatesForDeleteUi() {
function ensureUploadedTemplateManager() { function ensureUploadedTemplateManager() {
const row = document.getElementById("legacyTemplateRow"); const row = document.getElementById("legacyTemplateRow");
if (!row) return null; if (!row) return null;
let manager = document.getElementById("uploadedTemplatesManager"); let manager = document.getElementById("uploadedTemplatesManager");
if (manager) return manager; if (manager) return manager;
manager = document.createElement("div"); manager = document.createElement("div");
manager.id = "uploadedTemplatesManager"; manager.id = "uploadedTemplatesManager";
manager.className = "uploaded-template-manager"; manager.className = "uploaded-template-manager";
@ -1243,68 +1321,51 @@ async function renderUploadedTemplateDeleteUi() {
manager.style.display = ""; manager.style.display = "";
list.innerHTML = ""; list.innerHTML = "";
for (const item of uploaded) { for (const template of uploaded) {
const rawId = item.id || item.filename || ""; const id = template.id || template.filename;
const filename = rawId.replace(/^uploaded:/, ""); const name = template.label || template.filename || id;
const row = document.createElement("div"); const row = document.createElement("div");
row.className = "uploaded-template-row"; row.className = "uploaded-template-row";
const name = document.createElement("span"); const nameSpan = document.createElement("span");
name.className = "uploaded-template-name"; nameSpan.textContent = name;
name.textContent = item.label || filename;
const button = document.createElement("button"); const deleteButton = document.createElement("button");
button.type = "button"; deleteButton.type = "button";
button.className = "small-delete-button"; deleteButton.className = "delete-json-button";
button.textContent = "Delete"; deleteButton.textContent = "x";
deleteButton.title = `Delete ${name}`;
button.addEventListener("click", async () => { deleteButton.addEventListener("click", async event => {
if (!confirm(`Delete uploaded template "${filename}"?`)) return; event.preventDefault();
event.stopPropagation();
const response = await fetch( if (!confirm(`Delete uploaded template?\n\n${name}`)) return;
`/api/doc-generator/uploaded-template/${encodeURIComponent(currentDocumentType.id)}/${encodeURIComponent(filename)}`,
{method: "DELETE"} const deleteId = String(id).replace(/^uploaded:/, "");
); const response = await fetch(`/api/doc-generator/uploaded-template/${encodeURIComponent(currentDocumentType.id)}/${encodeURIComponent(deleteId)}`, {
method: "DELETE"
});
if (!response.ok) { if (!response.ok) {
const json = await response.json().catch(() => ({})); setStatus("Unable to delete uploaded template.");
setStatus(`Could not delete template: ${json.detail || response.statusText}`);
return; return;
} }
setStatus("Uploaded template deleted."); setStatus("Uploaded template deleted.");
await refreshUploadedTemplatesForCurrentProfile();
if (typeof renderTemplateSelector === "function") { normalizeLegalUi();
await renderTemplateSelector(currentDocumentType);
}
await renderUploadedTemplateDeleteUi();
}); });
row.appendChild(name); row.appendChild(nameSpan);
row.appendChild(button); row.appendChild(deleteButton);
list.appendChild(row); list.appendChild(row);
} }
} }
function runLegalUiCleanup() { setInterval(() => {
normalizeLegalUi(); normalizeLegalUi();
renderUploadedTemplateDeleteUi(); }, 1000);
}
document.addEventListener("DOMContentLoaded", () => { normalizeLegalUi();
runLegalUiCleanup();
setTimeout(runLegalUiCleanup, 300);
setTimeout(runLegalUiCleanup, 1000);
});
const finalLegalUiObserver = new MutationObserver(() => {
clearTimeout(window.__legalUiTimer);
window.__legalUiTimer = setTimeout(runLegalUiCleanup, 100);
});
finalLegalUiObserver.observe(document.body, {
childList: true,
subtree: true
});

View File

@ -2,7 +2,7 @@
<html> <html>
<head> <head>
<title>Utility App</title> <title>Utility App</title>
<link rel="stylesheet" href="/static/styles.css?v=legalui3"> <link rel="stylesheet" href="/static/styles.css?v=legalui4">
</head> </head>
<body class="app-loading"> <body class="app-loading">
<header class="app-header"> <header class="app-header">
@ -152,6 +152,6 @@
<div id="status"></div> <div id="status"></div>
</main> </main>
<script src="/static/app.js?v=legalui3"></script> <script src="/static/app.js?v=legalui4"></script>
</body> </body>
</html> </html>

View File

@ -907,3 +907,38 @@ label.tool-action-button {
grid-template-columns: 1fr !important; grid-template-columns: 1fr !important;
} }
} }
/* Legal profile dropdown normalization */
#documentDescription.legal-profile-caption,
.legal-profile-caption {
margin-top: 0.35rem;
color: #8a8f98;
font-size: 0.88rem;
font-weight: 400;
}
.native-select-hidden {
display: none !important;
}
.select-picker {
width: 100%;
margin-top: 0.35rem;
}
.select-picker .document-picker-toggle {
width: 100%;
}
.select-picker-menu {
max-height: 18rem;
overflow-y: auto;
}
.uploaded-template-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 0.75rem;
margin-top: 0.4rem;
}