Refine legal profile dropdown UI
This commit is contained in:
parent
adcba89350
commit
deea989f77
195
static/app.js
195
static/app.js
|
|
@ -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
|
|
||||||
});
|
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue