const documentTypeSelect = document.getElementById("documentTypeSelect");
const documentDescription = document.getElementById("documentDescription");
const fieldsContainer = document.getElementById("fieldsContainer");
const docForm = document.getElementById("docForm");
const statusBox = document.getElementById("status");
const clearFormButton = document.getElementById("clearFormButton");
const presetFileInput = document.getElementById("presetFileInput");
const dataFileInput = document.getElementById("dataFileInput");
const downloadDataButton = document.getElementById("downloadDataButton");
const downloadJsonButton = document.getElementById("downloadJsonButton");
const defaultDocumentOptions = document.getElementById("defaultDocumentOptions");
const uploadedJsonOptions = document.getElementById("uploadedJsonOptions");
const legacyTemplateSelect = document.getElementById("legacyTemplateSelect");
const legacyTemplateRow = document.getElementById("legacyTemplateRow");
const documentPickerToggle = document.getElementById("documentPickerToggle");
const documentPickerMenu = document.getElementById("documentPickerMenu");
const selectedDocumentLabel = document.getElementById("selectedDocumentLabel");
let currentDocumentType = null;
let currentFields = [];
let defaultDocumentTypes = [];
let activePickerKind = "default";
let activePickerId = "";
let selectedTemplateId = "";
function setStatus(message) {
statusBox.innerHTML = message || "";
}
function setActivePicker(kind, id, label = "") {
activePickerKind = kind;
activePickerId = id;
document.querySelectorAll(".picker-item").forEach(item => {
item.classList.toggle(
"active",
item.dataset.kind === kind && item.dataset.id === id
);
});
if (label) {
selectedDocumentLabel.textContent = label;
}
}
function normalizeField(field, index, inheritedSection = "General Fields") {
return {
name: field.name || `field${index + 1}`,
label: field.label || field.name || `Field ${index + 1}`,
type: field.type || "text",
required: Boolean(field.required),
section: field.section || inheritedSection,
value: field.value ?? "",
options: field.options || []
};
}
function createInput(field) {
let input;
if (field.type === "textarea") {
input = document.createElement("textarea");
} else if (field.type === "select") {
input = document.createElement("select");
for (const option of field.options || []) {
const opt = document.createElement("option");
opt.value = option.value ?? option;
opt.textContent = option.label ?? option;
input.appendChild(opt);
}
} else {
input = document.createElement("input");
input.type = "text";
if (field.type === "autocomplete" && field.list) {
input.setAttribute("list", `datalist-${field.list}`);
} else {
input.type = field.type || "text";
}
}
input.id = field.name;
input.name = field.name;
input.required = field.required;
input.value = field.value ?? "";
return input;
}
function makeFormGroup(field) {
const group = document.createElement("div");
group.className = "form-group";
const label = document.createElement("label");
label.setAttribute("for", field.name);
label.textContent = field.label;
group.appendChild(label);
group.appendChild(createInput(field));
return group;
}
function appendFieldRows(parent, fields) {
for (let i = 0; i < fields.length; i += 3) {
const row = document.createElement("div");
row.className = "form-row";
for (const field of fields.slice(i, i + 3)) {
row.appendChild(makeFormGroup(field));
}
parent.appendChild(row);
}
}
function renderSection(section, level = 2) {
const sectionWrapper = document.createElement("div");
sectionWrapper.className = "json-section";
const collapsible = section.collapsible !== false;
const defaultOpen = Boolean(section.defaultOpen);
let contentParent = sectionWrapper;
if (collapsible) {
const details = document.createElement("details");
details.open = defaultOpen;
const summary = document.createElement("summary");
summary.textContent = section.heading || "Section";
details.appendChild(summary);
sectionWrapper.appendChild(details);
contentParent = details;
} else {
const heading = document.createElement(`h${Math.min(level, 4)}`);
heading.textContent = section.heading || "Section";
sectionWrapper.appendChild(heading);
}
const normalizedFields = (section.fields || []).map((field, index) =>
normalizeField(field, currentFields.length + index, section.heading || "General Fields")
);
currentFields.push(...normalizedFields);
appendFieldRows(contentParent, normalizedFields);
for (const subsection of section.subsections || []) {
contentParent.appendChild(renderSection(subsection, level + 1));
}
return sectionWrapper;
}
function renderFlatFields(fields) {
const bySection = {};
const normalizedFields = fields.map((field, index) => normalizeField(field, index));
for (const field of normalizedFields) {
const section = field.section || "General Fields";
if (!bySection[section]) bySection[section] = [];
bySection[section].push(field);
}
currentFields = [];
for (const [sectionName, sectionFields] of Object.entries(bySection)) {
fieldsContainer.appendChild(renderSection({
heading: sectionName,
collapsible: sectionName !== "General Fields",
defaultOpen: sectionName === "General Fields",
fields: sectionFields
}));
}
}
function renderDatalists(documentType) {
document.querySelectorAll("datalist[data-generated-list='true']").forEach(el => el.remove());
const lists = documentType.lists || {};
for (const [listName, values] of Object.entries(lists)) {
const datalist = document.createElement("datalist");
datalist.id = `datalist-${listName}`;
datalist.dataset.generatedList = "true";
for (const value of values || []) {
const option = document.createElement("option");
option.value = value;
datalist.appendChild(option);
}
document.body.appendChild(datalist);
}
}
function renderTemplateSelector(documentType) {
if (!legacyTemplateSelect || !legacyTemplateRow) return;
const templates = documentType.templates || [];
legacyTemplateSelect.innerHTML = "";
if (!templates.length) {
legacyTemplateRow.style.display = "none";
selectedTemplateId = "";
return;
}
legacyTemplateRow.style.display = "block";
for (const template of templates) {
const option = document.createElement("option");
option.value = template.id;
option.textContent = template.label || template.id;
legacyTemplateSelect.appendChild(option);
}
const savedTemplate = localStorage.getItem(`utilityAppSelectedTemplate:${documentType.id}`);
selectedTemplateId = savedTemplate && templates.some(t => t.id === savedTemplate)
? savedTemplate
: (documentType.defaultTemplateId || templates[0].id);
legacyTemplateSelect.value = selectedTemplateId;
}
function getSelectedTemplateId() {
if (!legacyTemplateSelect || legacyTemplateRow?.style.display === "none") {
return "";
}
return legacyTemplateSelect.value || selectedTemplateId || "";
}
function renderDocumentType(documentType) {
fieldsContainer.innerHTML = "";
currentFields = [];
renderDatalists(documentType);
renderTemplateSelector(documentType);
if (Array.isArray(documentType.sections)) {
for (const section of documentType.sections) {
fieldsContainer.appendChild(renderSection(section));
}
return;
}
renderFlatFields(documentType.fields || []);
}
function applyDataToForm(data) {
for (const field of currentFields) {
const el = document.getElementById(field.name);
if (el && data[field.name] !== undefined) {
el.value = data[field.name];
}
}
}
function loadPresetObject(preset) {
if (preset.documentTypeId || preset.document_type_id) {
documentTypeSelect.value = preset.documentTypeId || preset.document_type_id;
}
if (preset.sections) {
currentDocumentType = {
...currentDocumentType,
...preset,
id: currentDocumentType?.id || preset.documentTypeId || preset.document_type_id || "uploaded_json"
};
documentDescription.textContent = currentDocumentType.description || "";
renderDocumentType(currentDocumentType);
return;
}
if (Array.isArray(preset.fields)) {
currentDocumentType = {
...currentDocumentType,
...preset,
id: currentDocumentType?.id || preset.documentTypeId || preset.document_type_id || "uploaded_json",
sections: [
{
heading: preset.heading || preset.name || "Imported Fields",
collapsible: false,
defaultOpen: true,
fields: preset.fields
}
]
};
documentDescription.textContent = currentDocumentType.description || "";
renderDocumentType(currentDocumentType);
return;
}
if (preset.data && typeof preset.data === "object") {
applyDataToForm(preset.data);
return;
}
if (typeof preset === "object") {
currentDocumentType = {
...currentDocumentType,
id: currentDocumentType?.id || "uploaded_json",
name: "Uploaded JSON",
sections: [
{
heading: "Imported Fields",
collapsible: false,
defaultOpen: true,
fields: Object.entries(preset).map(([key, value]) => ({
name: key,
label: key,
type: typeof value === "string" && value.length > 80 ? "textarea" : "text",
value
}))
}
]
};
renderDocumentType(currentDocumentType);
}
}
async function loadDocumentTypes() {
const response = await fetch("/api/doc-generator/document-types");
const json = await response.json();
defaultDocumentTypes = json.document_types || [];
renderDefaultDocumentOptions();
await loadUploadedJsonOptions();
const savedSelectionRaw = localStorage.getItem("utilityAppSelectedDocument");
let savedSelection = null;
try {
savedSelection = savedSelectionRaw ? JSON.parse(savedSelectionRaw) : null;
} catch {
savedSelection = null;
}
if (savedSelection?.kind === "uploaded" && savedSelection.id) {
await loadUploadedJson(savedSelection.id);
return;
}
if (savedSelection?.kind === "default" && savedSelection.id) {
await loadDefaultDocumentType(savedSelection.id);
return;
}
if (defaultDocumentTypes.length > 0) {
await loadDefaultDocumentType(defaultDocumentTypes[0].id);
}
}
function renderDefaultDocumentOptions() {
defaultDocumentOptions.innerHTML = "";
for (const documentType of defaultDocumentTypes) {
const item = document.createElement("button");
item.type = "button";
item.className = "picker-item";
item.dataset.kind = "default";
item.dataset.id = documentType.id;
item.textContent = documentType.name;
item.addEventListener("click", async () => {
await loadDefaultDocumentType(documentType.id);
});
defaultDocumentOptions.appendChild(item);
}
}
async function loadDefaultDocumentType(documentTypeId) {
const response = await fetch(`/api/doc-generator/document-types/${documentTypeId}`);
currentDocumentType = await response.json();
documentTypeSelect.value = documentTypeId;
documentDescription.textContent = currentDocumentType.description || "";
renderDocumentType(currentDocumentType);
setActivePicker("default", documentTypeId, currentDocumentType.name || documentTypeId);
closeDocumentPicker();
localStorage.setItem("utilityAppSelectedDocument", JSON.stringify({
kind: "default",
id: documentTypeId,
label: currentDocumentType.name || documentTypeId
}));
restoreSavedFormData();
}
async function loadUploadedJsonOptions() {
const response = await fetch("/api/doc-generator/uploaded-json");
const json = await response.json();
uploadedJsonOptions.innerHTML = "";
if (!json.files || json.files.length === 0) {
const empty = document.createElement("div");
empty.className = "picker-empty";
empty.textContent = "No uploaded JSON files yet.";
uploadedJsonOptions.appendChild(empty);
return;
}
for (const file of json.files) {
const row = document.createElement("div");
row.className = "uploaded-json-row";
const loadButton = document.createElement("button");
loadButton.type = "button";
loadButton.className = "picker-item uploaded-json-load";
loadButton.dataset.kind = "uploaded";
loadButton.dataset.id = file.filename;
loadButton.textContent = file.filename;
loadButton.addEventListener("click", async () => {
await loadUploadedJson(file.filename);
});
const deleteButton = document.createElement("button");
deleteButton.type = "button";
deleteButton.className = "delete-json-button";
deleteButton.textContent = "x";
deleteButton.title = `Delete ${file.filename}`;
deleteButton.addEventListener("click", async event => {
event.stopPropagation();
if (!confirm(`Delete uploaded JSON file?\n\n${file.filename}`)) {
return;
}
const response = await fetch(`/api/doc-generator/uploaded-json/${encodeURIComponent(file.filename)}`, {
method: "DELETE"
});
const result = await response.json();
if (!response.ok) {
setStatus(`Could not delete JSON: ${result.detail || "Delete failed."}`);
return;
}
setStatus(`Deleted uploaded JSON: ${result.deleted}`);
await loadUploadedJsonOptions();
if (activePickerKind === "uploaded" && activePickerId === file.filename) {
if (defaultDocumentTypes.length > 0) {
await loadDefaultDocumentType(defaultDocumentTypes[0].id);
}
}
});
row.appendChild(loadButton);
row.appendChild(deleteButton);
uploadedJsonOptions.appendChild(row);
}
}
async function loadUploadedJson(filename) {
const response = await fetch(`/api/doc-generator/uploaded-json/${encodeURIComponent(filename)}`);
const result = await response.json();
if (!response.ok) {
setStatus(`Could not load JSON: ${result.detail || "Load failed."}`);
return;
}
loadPresetObject(result.json);
setActivePicker("uploaded", filename, filename);
closeDocumentPicker();
localStorage.setItem("utilityAppSelectedDocument", JSON.stringify({
kind: "uploaded",
id: filename,
label: filename
}));
restoreSavedFormData();
setStatus(`Loaded uploaded JSON: ${filename}`);
}
function getFormData(useFieldNameForBlanks = true) {
const data = {};
for (const field of currentFields) {
const el = document.getElementById(field.name);
const value = el ? el.value.trim() : "";
data[field.name] = useFieldNameForBlanks ? (value || field.name) : value;
}
return data;
}
function csvEscape(value) {
const text = String(value ?? "");
if (/[",\n\r]/.test(text)) {
return `"${text.replaceAll('"', '""')}"`;
}
return text;
}
function downloadCurrentDataCsv() {
const headers = currentFields.map(field => field.name);
const data = getFormData(false);
const csv = [
headers.map(csvEscape).join(","),
headers.map(header => csvEscape(data[header] ?? "")).join(",")
].join("\n");
const blob = new Blob([csv], {type: "text/csv;charset=utf-8"});
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = `${currentDocumentType?.id || "document"}_data.csv`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}
function cloneWithCurrentValues(value) {
const byName = {};
for (const field of currentFields) {
const el = document.getElementById(field.name);
byName[field.name] = el ? el.value : "";
}
const clone = JSON.parse(JSON.stringify(value));
function patchSection(section) {
for (const field of section.fields || []) {
if (field.name && byName[field.name] !== undefined) {
field.value = byName[field.name];
}
}
for (const subsection of section.subsections || []) {
patchSection(subsection);
}
}
if (Array.isArray(clone.sections)) {
for (const section of clone.sections) {
patchSection(section);
}
} else if (Array.isArray(clone.fields)) {
for (const field of clone.fields) {
if (field.name && byName[field.name] !== undefined) {
field.value = byName[field.name];
}
}
}
return clone;
}
function downloadCurrentJson() {
if (!currentDocumentType) {
setStatus("No document type selected.");
return;
}
const exportJson = cloneWithCurrentValues({
...currentDocumentType,
exportedAt: new Date().toISOString()
});
const text = JSON.stringify(exportJson, null, 2);
const blob = new Blob([text], {type: "application/json;charset=utf-8"});
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = `${currentDocumentType.id || "document"}_preset.json`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}
clearFormButton.addEventListener("click", () => {
for (const field of currentFields) {
const el = document.getElementById(field.name);
if (el) el.value = "";
}
localStorage.removeItem(getFormStorageKey());
setStatus("Cleared form data.");
});
presetFileInput.addEventListener("change", async event => {
const file = event.target.files[0];
if (!file) return;
try {
const formData = new FormData();
formData.append("file", file);
const response = await fetch("/api/doc-generator/upload-json", {
method: "POST",
body: formData
});
const result = await response.json();
if (!response.ok) {
throw new Error(result.detail || "JSON upload failed.");
}
await loadUploadedJsonOptions();
loadPresetObject(result.json);
setActivePicker("uploaded", result.filename, result.filename);
closeDocumentPicker();
setStatus(`Uploaded JSON: ${result.filename}
Saved to: ${result.saved_path}`);
} catch (error) {
setStatus(`Could not upload JSON: ${error.message}`);
} finally {
presetFileInput.value = "";
}
});
dataFileInput.addEventListener("change", async event => {
const file = event.target.files[0];
if (!file) return;
try {
const formData = new FormData();
formData.append("file", file);
const response = await fetch("/api/doc-generator/upload-data", {
method: "POST",
body: formData
});
const result = await response.json();
if (!response.ok) {
throw new Error(result.detail || "CSV upload failed.");
}
applyDataToForm(result.data);
setStatus(`Uploaded data CSV: ${result.filename}
Saved to: ${result.saved_path}
Rows found: ${result.row_count}`);
} catch (error) {
setStatus(`Could not upload CSV: ${error.message}`);
} finally {
dataFileInput.value = "";
}
});
downloadDataButton.addEventListener("click", () => {
downloadCurrentDataCsv();
});
downloadJsonButton.addEventListener("click", () => {
downloadCurrentJson();
});
docForm.addEventListener("submit", async event => {
event.preventDefault();
setStatus("Generating DOCX...");
const response = await fetch("/api/doc-generator/generate", {
method: "POST",
headers: {"Content-Type": "application/json"},
body: JSON.stringify({
document_type_id: currentDocumentType.id,
template_id: getSelectedTemplateId(),
data: getFormData(true)
})
});
const json = await response.json();
if (!response.ok) {
setStatus(`Error: ${json.detail || "Generation failed."}`);
return;
}
setStatus(`Download ${json.filename}`);
});
loadDocumentTypes().catch(error => {
setStatus(`Startup error: ${error.message}`);
});
function openDocumentPicker() {
documentPickerMenu.classList.add("open");
documentPickerToggle.classList.add("open");
}
function closeDocumentPicker() {
documentPickerMenu.classList.remove("open");
documentPickerToggle.classList.remove("open");
}
function toggleDocumentPicker() {
if (documentPickerMenu.classList.contains("open")) {
closeDocumentPicker();
} else {
openDocumentPicker();
}
}
documentPickerToggle.addEventListener("click", event => {
event.stopPropagation();
toggleDocumentPicker();
});
document.addEventListener("click", event => {
if (!event.target.closest(".dropdown-picker")) {
closeDocumentPicker();
}
});
function showView(viewId) {
document.querySelectorAll(".app-view").forEach(view => {
view.classList.toggle("active", view.id === viewId);
});
document.querySelectorAll(".nav-button").forEach(button => {
button.classList.toggle("active", button.dataset.view === viewId);
});
localStorage.setItem("utilityAppActiveView", viewId);
}
document.querySelectorAll(".nav-button").forEach(button => {
button.addEventListener("click", () => {
showView(button.dataset.view);
});
});
const mainPageContent = document.getElementById("mainPageContent");
const saveMainPageButton = document.getElementById("saveMainPageButton");
const resetMainPageButton = document.getElementById("resetMainPageButton");
function loadMainPageContent() {
const saved = localStorage.getItem("utilityAppMainPageContent");
if (saved) {
mainPageContent.innerHTML = saved;
}
}
saveMainPageButton.addEventListener("click", () => {
localStorage.setItem("utilityAppMainPageContent", mainPageContent.innerHTML);
setStatus("Saved main page text in this browser.");
});
resetMainPageButton.addEventListener("click", () => {
localStorage.removeItem("utilityAppMainPageContent");
mainPageContent.innerHTML = `
Select a tool above. This main page text is editable in the browser for quick notes, instructions, or workflow reminders.
Current tools: Document Generator and Document Processor.
`; setStatus("Reset main page text."); }); loadMainPageContent(); function getFormStorageKey() { const selectedRaw = localStorage.getItem("utilityAppSelectedDocument"); let selected = null; try { selected = selectedRaw ? JSON.parse(selectedRaw) : null; } catch { selected = null; } const kind = selected?.kind || activePickerKind || "default"; const id = selected?.id || activePickerId || currentDocumentType?.id || "unknown"; return `utilityAppFormData:${kind}:${id}`; } function saveCurrentFormData() { if (!currentFields || currentFields.length === 0) return; const data = getFormData(false); localStorage.setItem(getFormStorageKey(), JSON.stringify(data)); } function restoreSavedFormData() { const saved = localStorage.getItem(getFormStorageKey()); if (!saved) return; try { const data = JSON.parse(saved); applyDataToForm(data); } catch { return; } } fieldsContainer.addEventListener("input", () => { saveCurrentFormData(); }); fieldsContainer.addEventListener("change", () => { saveCurrentFormData(); }); window.addEventListener("beforeunload", () => { saveCurrentFormData(); }); const savedActiveView = localStorage.getItem("utilityAppActiveView"); if (savedActiveView) { showView(savedActiveView); } requestAnimationFrame(() => { document.body.classList.remove("app-loading"); }); if (legacyTemplateSelect) { legacyTemplateSelect.addEventListener("change", () => { selectedTemplateId = legacyTemplateSelect.value; if (currentDocumentType?.id) { localStorage.setItem(`utilityAppSelectedTemplate:${currentDocumentType.id}`, selectedTemplateId); } saveCurrentFormData(); }); } function removeDynamicFields(groupId) { document.querySelectorAll(`[data-dynamic-group="${groupId}"]`).forEach(el => el.remove()); currentFields = currentFields.filter(field => field.dynamicGroup !== groupId); } function renderDynamicFieldGroup(group) { if (!currentDocumentType) return; const countEl = document.getElementById(group.countField); if (!countEl) return; const count = Math.min( parseInt(countEl.value || "0", 10) || 0, group.maxCount || 100 ); removeDynamicFields(group.id); if (count <= 0) return; const wrapper = document.createElement("div"); wrapper.dataset.dynamicGroup = group.id; wrapper.className = "json-section dynamic-field-group"; const details = document.createElement("details"); details.open = group.defaultOpen !== false; const summary = document.createElement("summary"); summary.textContent = `${group.section || "Dynamic Fields"} (${count})`; details.appendChild(summary); const generatedFields = []; for (let n = 1; n <= count; n++) { for (const fieldDef of group.fields || []) { generatedFields.push({ name: fieldDef.namePattern.replaceAll("{n}", String(n)), label: fieldDef.labelPattern.replaceAll("{n}", String(n)), type: fieldDef.type || "text", list: fieldDef.list, required: Boolean(fieldDef.required), dynamicGroup: group.id }); } } currentFields.push(...generatedFields); appendFieldRows(details, generatedFields); wrapper.appendChild(details); const countField = document.getElementById(group.countField); const sectionWrapper = countField?.closest(".json-section"); if (sectionWrapper) { sectionWrapper.appendChild(wrapper); } else { fieldsContainer.appendChild(wrapper); } restoreSavedFormData(); } function renderAllDynamicFieldGroups() { if (!currentDocumentType?.dynamicFieldGroups) return; for (const group of currentDocumentType.dynamicFieldGroups) { renderDynamicFieldGroup(group); const countEl = document.getElementById(group.countField); if (countEl && !countEl.dataset.dynamicListenerAttached) { countEl.dataset.dynamicListenerAttached = "true"; countEl.addEventListener("input", () => { saveCurrentFormData(); renderDynamicFieldGroup(group); }); countEl.addEventListener("change", () => { saveCurrentFormData(); renderDynamicFieldGroup(group); }); } } } const originalRenderDocumentTypeForDynamicGroups = renderDocumentType; renderDocumentType = function(documentType) { originalRenderDocumentTypeForDynamicGroups(documentType); renderAllDynamicFieldGroups(); loadExcelMaps(); }; const templateFileInput = document.getElementById("templateFileInput"); async function fetchUploadedTemplatesForCurrentProfile() { if (!currentDocumentType?.id) return []; try { const response = await fetch(`/api/doc-generator/uploaded-templates/${encodeURIComponent(currentDocumentType.id)}`); const json = await response.json(); if (!response.ok) { return []; } return json.templates || []; } catch { return []; } } const originalRenderTemplateSelectorWithUploads = renderTemplateSelector; renderTemplateSelector = function(documentType) { if (!legacyTemplateSelect || !legacyTemplateRow) return; const libraryTemplates = documentType.templates || []; legacyTemplateSelect.innerHTML = ""; if (!libraryTemplates.length && !documentType.id) { legacyTemplateRow.style.display = "none"; selectedTemplateId = ""; return; } legacyTemplateRow.style.display = "block"; if (libraryTemplates.length) { const libraryGroup = document.createElement("optgroup"); libraryGroup.label = "Library Templates"; for (const template of libraryTemplates) { const option = document.createElement("option"); option.value = template.id; option.textContent = template.label || template.id; libraryGroup.appendChild(option); } legacyTemplateSelect.appendChild(libraryGroup); } const uploadedGroup = document.createElement("optgroup"); uploadedGroup.label = "Uploaded Templates"; uploadedGroup.id = "uploadedTemplateOptGroup"; legacyTemplateSelect.appendChild(uploadedGroup); const savedTemplate = localStorage.getItem(`utilityAppSelectedTemplate:${documentType.id}`); selectedTemplateId = savedTemplate || documentType.defaultTemplateId || libraryTemplates[0]?.id || ""; if (selectedTemplateId) { legacyTemplateSelect.value = selectedTemplateId; } refreshUploadedTemplateOptions(); }; async function refreshUploadedTemplateOptions() { if (!legacyTemplateSelect || !currentDocumentType?.id) return; let uploadedGroup = document.getElementById("uploadedTemplateOptGroup"); if (!uploadedGroup) { uploadedGroup = document.createElement("optgroup"); uploadedGroup.label = "Uploaded Templates"; uploadedGroup.id = "uploadedTemplateOptGroup"; legacyTemplateSelect.appendChild(uploadedGroup); } uploadedGroup.innerHTML = ""; const uploadedTemplates = await fetchUploadedTemplatesForCurrentProfile(); for (const template of uploadedTemplates) { const option = document.createElement("option"); option.value = template.id; option.textContent = template.label || template.filename; uploadedGroup.appendChild(option); } const savedTemplate = localStorage.getItem(`utilityAppSelectedTemplate:${currentDocumentType.id}`); if (savedTemplate && [...legacyTemplateSelect.options].some(option => option.value === savedTemplate)) { legacyTemplateSelect.value = savedTemplate; selectedTemplateId = savedTemplate; } } if (templateFileInput) { templateFileInput.addEventListener("change", async event => { const file = event.target.files[0]; if (!file) return; if (!currentDocumentType?.id) { setStatus("Select a profile before uploading a template."); templateFileInput.value = ""; return; } try { const formData = new FormData(); formData.append("file", file); const response = await fetch(`/api/doc-generator/upload-template/${encodeURIComponent(currentDocumentType.id)}`, { method: "POST", body: formData }); const result = await response.json(); if (!response.ok) { throw new Error(result.detail || "Template upload failed."); } await refreshUploadedTemplateOptions(); legacyTemplateSelect.value = result.template_id; selectedTemplateId = result.template_id; localStorage.setItem(`utilityAppSelectedTemplate:${currentDocumentType.id}`, selectedTemplateId); setStatus(`Uploaded template: ${result.filename}