From a7b5e4f6412b78e2903f3e85251ee40621ddf8e2 Mon Sep 17 00:00:00 2001 From: McElwain Date: Tue, 9 Jun 2026 23:41:16 -0500 Subject: [PATCH] Add JSON presets and CSV data import/export --- app/routes/doc_generator.py | 189 ++++++ static/app.js | 613 +++++++++++++++++ static/index.html | 67 ++ static/styles.css | 303 +++++++++ .../content/document_types/blank_merge.json | 637 ++++++++++++++++++ .../content/templates/blank_merge.docx | Bin 0 -> 37211 bytes 6 files changed, 1809 insertions(+) create mode 100644 app/routes/doc_generator.py create mode 100644 static/app.js create mode 100644 static/index.html create mode 100644 static/styles.css create mode 100644 tools/doc_generator/content/document_types/blank_merge.json create mode 100644 tools/doc_generator/content/templates/blank_merge.docx diff --git a/app/routes/doc_generator.py b/app/routes/doc_generator.py new file mode 100644 index 0000000..9c72992 --- /dev/null +++ b/app/routes/doc_generator.py @@ -0,0 +1,189 @@ +import csv +import json +import re +from pathlib import Path + +from fastapi import APIRouter, File, HTTPException, UploadFile +from fastapi.responses import FileResponse +from pydantic import BaseModel + +from tools.doc_generator.logic.document_types import get_document_type, list_document_types +from tools.doc_generator.logic.renderer import generate_docx + +router = APIRouter() + +PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent +EXPORTS_DIR = PROJECT_ROOT / "exports" +INPUTS_DIR = PROJECT_ROOT / "inputs" +UPLOADS_DIR = INPUTS_DIR / "uploads" +JSON_UPLOADS_DIR = UPLOADS_DIR / "json" +DATA_UPLOADS_DIR = UPLOADS_DIR / "data" + + +class GenerateDocRequest(BaseModel): + document_type_id: str + data: dict + + +def safe_filename(filename: str) -> str: + filename = Path(filename or "upload").name + filename = re.sub(r"[^A-Za-z0-9._ -]+", "", filename) + filename = re.sub(r"\s+", "_", filename).strip("._ ") + return filename or "upload" + + +def save_upload(upload: UploadFile, directory: Path) -> Path: + directory.mkdir(parents=True, exist_ok=True) + filename = safe_filename(upload.filename) + path = directory / filename + + counter = 1 + while path.exists(): + stem = path.stem + suffix = path.suffix + path = directory / f"{stem}_{counter}{suffix}" + counter += 1 + + with path.open("wb") as f: + f.write(upload.file.read()) + + return path + + +@router.get("/document-types") +def document_types(): + return {"document_types": list_document_types()} + + +@router.get("/document-types/{document_type_id}") +def document_type(document_type_id: str): + try: + return get_document_type(document_type_id) + except FileNotFoundError as exc: + raise HTTPException(status_code=404, detail=str(exc)) + + +@router.post("/upload-json") +async def upload_json(file: UploadFile = File(...)): + if not file.filename.lower().endswith(".json"): + raise HTTPException(status_code=400, detail="Upload must be a JSON file.") + + path = save_upload(file, JSON_UPLOADS_DIR) + + try: + data = json.loads(path.read_text(encoding="utf-8")) + except Exception as exc: + raise HTTPException(status_code=400, detail=f"Invalid JSON: {exc}") + + return { + "ok": True, + "filename": path.name, + "saved_path": str(path.relative_to(PROJECT_ROOT)), + "json": data, + } + + +@router.get("/uploaded-json") +def uploaded_json_files(): + JSON_UPLOADS_DIR.mkdir(parents=True, exist_ok=True) + + files = [] + for path in sorted(JSON_UPLOADS_DIR.glob("*.json")): + files.append({ + "filename": path.name, + "saved_path": str(path.relative_to(PROJECT_ROOT)), + "modified": path.stat().st_mtime, + }) + + return {"files": files} + + +@router.get("/uploaded-json/{filename}") +def get_uploaded_json(filename: str): + file_path = JSON_UPLOADS_DIR / safe_filename(filename) + + if not file_path.exists(): + raise HTTPException(status_code=404, detail="Uploaded JSON not found.") + + try: + data = json.loads(file_path.read_text(encoding="utf-8")) + except Exception as exc: + raise HTTPException(status_code=400, detail=f"Invalid JSON: {exc}") + + return { + "ok": True, + "filename": file_path.name, + "saved_path": str(file_path.relative_to(PROJECT_ROOT)), + "json": data, + } + + +@router.delete("/uploaded-json/{filename}") +def delete_uploaded_json(filename: str): + file_path = JSON_UPLOADS_DIR / safe_filename(filename) + + if not file_path.exists(): + raise HTTPException(status_code=404, detail="Uploaded JSON not found.") + + file_path.unlink() + + return { + "ok": True, + "deleted": file_path.name, + } + + +@router.post("/upload-data") +async def upload_data(file: UploadFile = File(...)): + if not file.filename.lower().endswith(".csv"): + raise HTTPException(status_code=400, detail="For now, upload data as CSV.") + + path = save_upload(file, DATA_UPLOADS_DIR) + + try: + with path.open("r", encoding="utf-8-sig", newline="") as f: + reader = csv.DictReader(f) + rows = list(reader) + except Exception as exc: + raise HTTPException(status_code=400, detail=f"Could not read CSV: {exc}") + + if not rows: + raise HTTPException(status_code=400, detail="CSV has no data rows.") + + return { + "ok": True, + "filename": path.name, + "saved_path": str(path.relative_to(PROJECT_ROOT)), + "data": rows[0], + "row_count": len(rows), + } + + +@router.post("/generate") +def generate_document(request: GenerateDocRequest): + try: + output_path = generate_docx(request.document_type_id, request.data) + except FileNotFoundError as exc: + raise HTTPException(status_code=404, detail=str(exc)) + except Exception as exc: + raise HTTPException(status_code=400, detail=str(exc)) + + return { + "ok": True, + "filename": output_path.name, + "download_url": f"/api/doc-generator/download/{output_path.name}" + } + + +@router.get("/download/{filename}") +def download(filename: str): + file_path = EXPORTS_DIR / safe_filename(filename) + + if not file_path.exists(): + raise HTTPException(status_code=404, detail="File not found") + + return FileResponse( + path=file_path, + filename=file_path.name, + media_type="application/vnd.openxmlformats-officedocument.wordprocessingml.document", + ) diff --git a/static/app.js b/static/app.js new file mode 100644 index 0000000..9b29b18 --- /dev/null +++ b/static/app.js @@ -0,0 +1,613 @@ +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 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 = ""; + +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 = 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 renderDocumentType(documentType) { + fieldsContainer.innerHTML = ""; + currentFields = []; + + 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(); + + if (defaultDocumentTypes.length > 0) { + await loadDefaultDocumentType(defaultDocumentTypes[0].id); + } + + await loadUploadedJsonOptions(); +} + +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(); +} + +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(); + 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 = ""; + } + setStatus(""); +}); + +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, + 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(); + } +}); diff --git a/static/index.html b/static/index.html new file mode 100644 index 0000000..c514093 --- /dev/null +++ b/static/index.html @@ -0,0 +1,67 @@ + + + + Document Generator + + + +
+
+

Document Generator

+
+ + + + + + + + + + + +

Document Type

+ + + + + +
+ +
+
+ +

Generate Documents

+ +
+
+ +
+
+
+ +
+
+ + + + diff --git a/static/styles.css b/static/styles.css new file mode 100644 index 0000000..420de67 --- /dev/null +++ b/static/styles.css @@ -0,0 +1,303 @@ +body { + font-family: Arial, sans-serif; + margin: 0; + padding: 0; + background-color: #f0f0f0; +} + +.container { + max-width: 800px; + margin: 20px auto; + padding: 20px; + background-color: #fff; + border-radius: 8px; + box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.1); +} + +.heading { + text-align: center; + margin-bottom: 20px; +} + +.heading h1 { + font-size: 24px; + margin: 0; + color: #333; +} + +h2 { + font-size: 20px; + margin: 20px 0 10px; + color: #555; +} + +h3 { + font-size: 18px; + margin: 10px 0 5px; + color: #666; +} + +.form-row { + display: flex; + flex-wrap: wrap; + margin-bottom: 15px; + justify-content: space-between; +} + +.form-group { + flex-basis: calc(33.33% - 10px); + padding: 0 10px; + box-sizing: border-box; + margin: 5px; +} + +.form-group label { + font-weight: bold; + display: block; + margin-bottom: 5px; + color: #333; + font-size: 14px; +} + +.form-group input, +.form-group select, +.form-group textarea { + width: 100%; + padding: 8px; + border: 1px solid #ccc; + border-radius: 5px; + box-sizing: border-box; + font-size: 14px; + font-family: Arial, sans-serif; +} + +.form-group input:focus, +.form-group select:focus, +.form-group textarea:focus { + border-color: #007bff; + outline: none; +} + +textarea { + height: 100px; + resize: vertical; +} + +button, +.file-button, +input[type="submit"] { + display: inline-block; + padding: 5px 10px; + margin: 4px; + border: 1px solid #aaa; + border-radius: 3px; + background-color: #e9e9ef; + color: #222; + font-size: 14px; + font-family: Arial, sans-serif; + cursor: pointer; +} + +.file-button input { + display: none; +} + +#documentDescription { + color: #666; + margin: 10px 5px; +} + +#status { + margin-top: 20px; + padding: 12px; + min-height: 32px; + background-color: #eee; + word-break: break-word; +} + +#status a { + font-weight: bold; + color: #0645ad; +} + +.json-section { + margin-top: 10px; +} + +.json-section details { + margin: 10px 0; +} + +.json-section summary { + font-size: 18px; + margin: 10px 0 8px; + color: #666; + font-weight: bold; + cursor: pointer; +} + +.json-section h2 { + font-size: 20px; + margin: 20px 0 10px; + color: #555; +} + +.json-section h3, +.json-section h4 { + font-size: 18px; + margin: 10px 0 5px; + color: #666; +} + +.document-picker { + margin: 10px 0 18px; + padding: 12px; + background: #f7f7f7; + border: 1px solid #ddd; + border-radius: 6px; +} + +.document-picker h3 { + margin: 8px 0 6px; + color: #555; +} + +.picker-list { + margin-bottom: 10px; +} + +.picker-item { + display: block; + width: 100%; + text-align: left; + margin: 4px 0; + padding: 7px 10px; +} + +.picker-item.active { + border-color: #6b35ff; + outline: 2px solid #6b35ff; + background: #f1edff; +} + +.uploaded-json-row { + display: flex; + gap: 6px; + align-items: center; +} + +.uploaded-json-load { + flex: 1; +} + +.delete-json-button { + flex: 0 0 34px; + width: 34px; + text-align: center; + color: #8a0000; + font-weight: bold; +} + +.picker-empty { + color: #777; + font-style: italic; + padding: 4px 0 8px; +} + +.dropdown-picker { + position: relative; + margin: 10px 0 18px; + padding: 0; + background: transparent; + border: none; +} + +.document-picker-toggle { + display: flex; + justify-content: space-between; + align-items: center; + width: 300px; + text-align: left; + padding: 8px 10px; + margin: 4px 0; + border: 1px solid #999; + border-radius: 4px; + background: #e9e9ef; + font-size: 14px; +} + +.document-picker-toggle.open { + border-color: #6b35ff; + outline: 2px solid #6b35ff; +} + +.dropdown-arrow { + margin-left: 12px; + font-size: 12px; +} + +.document-picker-menu { + display: none; + position: absolute; + z-index: 1000; + width: 420px; + max-height: 420px; + overflow-y: auto; + margin-top: 4px; + padding: 12px; + background: #f7f7f7; + border: 1px solid #bbb; + border-radius: 6px; + box-shadow: 0 4px 14px rgba(0, 0, 0, 0.2); +} + +.document-picker-menu.open { + display: block; +} + +.document-picker-menu h3 { + margin: 8px 0 6px; + color: #555; +} + +.picker-list { + margin-bottom: 10px; +} + +.picker-item { + display: block; + width: 100%; + text-align: left; + margin: 4px 0; + padding: 7px 10px; +} + +.picker-item.active { + border-color: #6b35ff; + outline: 2px solid #6b35ff; + background: #f1edff; +} + +.uploaded-json-row { + display: flex; + gap: 6px; + align-items: center; +} + +.uploaded-json-load { + flex: 1; +} + +.delete-json-button { + flex: 0 0 34px; + width: 34px; + text-align: center; + color: #8a0000; + font-weight: bold; +} + +.picker-empty { + color: #777; + font-style: italic; + padding: 4px 0 8px; +} diff --git a/tools/doc_generator/content/document_types/blank_merge.json b/tools/doc_generator/content/document_types/blank_merge.json new file mode 100644 index 0000000..069060f --- /dev/null +++ b/tools/doc_generator/content/document_types/blank_merge.json @@ -0,0 +1,637 @@ +{ + "id": "blank_merge", + "name": "Blank Merge Form", + "description": "Generic merge form with 100 placeholder fields.", + "template": "blank_merge.docx", + "outputFilename": "blank_merge_{timestamp_YYYY-MM-DD_HH-mm-ss}.docx", + "sections": [ + { + "heading": "Fields 1-25", + "collapsible": false, + "defaultOpen": true, + "fields": [ + { + "name": "field1", + "label": "Field 1", + "type": "text", + "required": false + }, + { + "name": "field2", + "label": "Field 2", + "type": "text", + "required": false + }, + { + "name": "field3", + "label": "Field 3", + "type": "text", + "required": false + }, + { + "name": "field4", + "label": "Field 4", + "type": "text", + "required": false + }, + { + "name": "field5", + "label": "Field 5", + "type": "text", + "required": false + }, + { + "name": "field6", + "label": "Field 6", + "type": "text", + "required": false + }, + { + "name": "field7", + "label": "Field 7", + "type": "text", + "required": false + }, + { + "name": "field8", + "label": "Field 8", + "type": "text", + "required": false + }, + { + "name": "field9", + "label": "Field 9", + "type": "text", + "required": false + }, + { + "name": "field10", + "label": "Field 10", + "type": "textarea", + "required": false + }, + { + "name": "field11", + "label": "Field 11", + "type": "text", + "required": false + }, + { + "name": "field12", + "label": "Field 12", + "type": "text", + "required": false + }, + { + "name": "field13", + "label": "Field 13", + "type": "text", + "required": false + }, + { + "name": "field14", + "label": "Field 14", + "type": "text", + "required": false + }, + { + "name": "field15", + "label": "Field 15", + "type": "text", + "required": false + }, + { + "name": "field16", + "label": "Field 16", + "type": "text", + "required": false + }, + { + "name": "field17", + "label": "Field 17", + "type": "text", + "required": false + }, + { + "name": "field18", + "label": "Field 18", + "type": "text", + "required": false + }, + { + "name": "field19", + "label": "Field 19", + "type": "text", + "required": false + }, + { + "name": "field20", + "label": "Field 20", + "type": "textarea", + "required": false + }, + { + "name": "field21", + "label": "Field 21", + "type": "text", + "required": false + }, + { + "name": "field22", + "label": "Field 22", + "type": "text", + "required": false + }, + { + "name": "field23", + "label": "Field 23", + "type": "text", + "required": false + }, + { + "name": "field24", + "label": "Field 24", + "type": "text", + "required": false + }, + { + "name": "field25", + "label": "Field 25", + "type": "text", + "required": false + } + ] + }, + { + "heading": "Fields 26-50", + "collapsible": true, + "defaultOpen": false, + "fields": [ + { + "name": "field26", + "label": "Field 26", + "type": "text", + "required": false + }, + { + "name": "field27", + "label": "Field 27", + "type": "text", + "required": false + }, + { + "name": "field28", + "label": "Field 28", + "type": "text", + "required": false + }, + { + "name": "field29", + "label": "Field 29", + "type": "text", + "required": false + }, + { + "name": "field30", + "label": "Field 30", + "type": "textarea", + "required": false + }, + { + "name": "field31", + "label": "Field 31", + "type": "text", + "required": false + }, + { + "name": "field32", + "label": "Field 32", + "type": "text", + "required": false + }, + { + "name": "field33", + "label": "Field 33", + "type": "text", + "required": false + }, + { + "name": "field34", + "label": "Field 34", + "type": "text", + "required": false + }, + { + "name": "field35", + "label": "Field 35", + "type": "text", + "required": false + }, + { + "name": "field36", + "label": "Field 36", + "type": "text", + "required": false + }, + { + "name": "field37", + "label": "Field 37", + "type": "text", + "required": false + }, + { + "name": "field38", + "label": "Field 38", + "type": "text", + "required": false + }, + { + "name": "field39", + "label": "Field 39", + "type": "text", + "required": false + }, + { + "name": "field40", + "label": "Field 40", + "type": "textarea", + "required": false + }, + { + "name": "field41", + "label": "Field 41", + "type": "text", + "required": false + }, + { + "name": "field42", + "label": "Field 42", + "type": "text", + "required": false + }, + { + "name": "field43", + "label": "Field 43", + "type": "text", + "required": false + }, + { + "name": "field44", + "label": "Field 44", + "type": "text", + "required": false + }, + { + "name": "field45", + "label": "Field 45", + "type": "text", + "required": false + }, + { + "name": "field46", + "label": "Field 46", + "type": "text", + "required": false + }, + { + "name": "field47", + "label": "Field 47", + "type": "text", + "required": false + }, + { + "name": "field48", + "label": "Field 48", + "type": "text", + "required": false + }, + { + "name": "field49", + "label": "Field 49", + "type": "text", + "required": false + }, + { + "name": "field50", + "label": "Field 50", + "type": "textarea", + "required": false + } + ] + }, + { + "heading": "Fields 51-75", + "collapsible": true, + "defaultOpen": false, + "fields": [ + { + "name": "field51", + "label": "Field 51", + "type": "text", + "required": false + }, + { + "name": "field52", + "label": "Field 52", + "type": "text", + "required": false + }, + { + "name": "field53", + "label": "Field 53", + "type": "text", + "required": false + }, + { + "name": "field54", + "label": "Field 54", + "type": "text", + "required": false + }, + { + "name": "field55", + "label": "Field 55", + "type": "text", + "required": false + }, + { + "name": "field56", + "label": "Field 56", + "type": "text", + "required": false + }, + { + "name": "field57", + "label": "Field 57", + "type": "text", + "required": false + }, + { + "name": "field58", + "label": "Field 58", + "type": "text", + "required": false + }, + { + "name": "field59", + "label": "Field 59", + "type": "text", + "required": false + }, + { + "name": "field60", + "label": "Field 60", + "type": "textarea", + "required": false + }, + { + "name": "field61", + "label": "Field 61", + "type": "text", + "required": false + }, + { + "name": "field62", + "label": "Field 62", + "type": "text", + "required": false + }, + { + "name": "field63", + "label": "Field 63", + "type": "text", + "required": false + }, + { + "name": "field64", + "label": "Field 64", + "type": "text", + "required": false + }, + { + "name": "field65", + "label": "Field 65", + "type": "text", + "required": false + }, + { + "name": "field66", + "label": "Field 66", + "type": "text", + "required": false + }, + { + "name": "field67", + "label": "Field 67", + "type": "text", + "required": false + }, + { + "name": "field68", + "label": "Field 68", + "type": "text", + "required": false + }, + { + "name": "field69", + "label": "Field 69", + "type": "text", + "required": false + }, + { + "name": "field70", + "label": "Field 70", + "type": "textarea", + "required": false + }, + { + "name": "field71", + "label": "Field 71", + "type": "text", + "required": false + }, + { + "name": "field72", + "label": "Field 72", + "type": "text", + "required": false + }, + { + "name": "field73", + "label": "Field 73", + "type": "text", + "required": false + }, + { + "name": "field74", + "label": "Field 74", + "type": "text", + "required": false + }, + { + "name": "field75", + "label": "Field 75", + "type": "text", + "required": false + } + ] + }, + { + "heading": "Fields 76-100", + "collapsible": true, + "defaultOpen": false, + "fields": [ + { + "name": "field76", + "label": "Field 76", + "type": "text", + "required": false + }, + { + "name": "field77", + "label": "Field 77", + "type": "text", + "required": false + }, + { + "name": "field78", + "label": "Field 78", + "type": "text", + "required": false + }, + { + "name": "field79", + "label": "Field 79", + "type": "text", + "required": false + }, + { + "name": "field80", + "label": "Field 80", + "type": "textarea", + "required": false + }, + { + "name": "field81", + "label": "Field 81", + "type": "text", + "required": false + }, + { + "name": "field82", + "label": "Field 82", + "type": "text", + "required": false + }, + { + "name": "field83", + "label": "Field 83", + "type": "text", + "required": false + }, + { + "name": "field84", + "label": "Field 84", + "type": "text", + "required": false + }, + { + "name": "field85", + "label": "Field 85", + "type": "text", + "required": false + }, + { + "name": "field86", + "label": "Field 86", + "type": "text", + "required": false + }, + { + "name": "field87", + "label": "Field 87", + "type": "text", + "required": false + }, + { + "name": "field88", + "label": "Field 88", + "type": "text", + "required": false + }, + { + "name": "field89", + "label": "Field 89", + "type": "text", + "required": false + }, + { + "name": "field90", + "label": "Field 90", + "type": "textarea", + "required": false + }, + { + "name": "field91", + "label": "Field 91", + "type": "text", + "required": false + }, + { + "name": "field92", + "label": "Field 92", + "type": "text", + "required": false + }, + { + "name": "field93", + "label": "Field 93", + "type": "text", + "required": false + }, + { + "name": "field94", + "label": "Field 94", + "type": "text", + "required": false + }, + { + "name": "field95", + "label": "Field 95", + "type": "text", + "required": false + }, + { + "name": "field96", + "label": "Field 96", + "type": "text", + "required": false + }, + { + "name": "field97", + "label": "Field 97", + "type": "text", + "required": false + }, + { + "name": "field98", + "label": "Field 98", + "type": "text", + "required": false + }, + { + "name": "field99", + "label": "Field 99", + "type": "text", + "required": false + }, + { + "name": "field100", + "label": "Field 100", + "type": "textarea", + "required": false + } + ] + } + ] +} \ No newline at end of file diff --git a/tools/doc_generator/content/templates/blank_merge.docx b/tools/doc_generator/content/templates/blank_merge.docx new file mode 100644 index 0000000000000000000000000000000000000000..782c1bbbbd53034c6301d35ff4c134c98d594f40 GIT binary patch literal 37211 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 QK)FhgArY88p7elt0CjSGNB{r; literal 0 HcmV?d00001