diff --git a/efile_app/efile/middleware.py b/efile_app/efile/middleware.py new file mode 100644 index 0000000..15d338f --- /dev/null +++ b/efile_app/efile/middleware.py @@ -0,0 +1,25 @@ +class NoCacheHTMLMiddleware: + """Stop browsers from reusing a stale rendered page without asking the server first. + + Django's views send no Cache-Control at all, which leaves the browser free to + apply its own heuristic freshness and serve an old page byte-for-byte on a plain + reload -- no request even reaches the server. That's the same failure mode + WHITENOISE_MAX_AGE=0 (settings_dev.py) fixes for static JS/CSS, but it was still + open for the HTML itself: a template change (e.g. a - - + }"> +

Select Party Type

+ `; @@ -995,9 +997,6 @@ class DynamicFormSections { if (result.success && result.data) { const partyTypes = result.data; - // Clear existing options except the first one - dropdown.innerHTML = ''; - // If no saved data, try to find intelligent default from API response if (!defaultValue) { // Find the parent section title @@ -1037,20 +1036,7 @@ class DynamicFormSections { } } - // Add all party type options - let addedCount = 0; - partyTypes.forEach((partyType) => { - const option = document.createElement("option"); - option.value = partyType.code; - option.textContent = partyType.name; - // Set as selected if this is the default value - if (partyType.code === defaultValue) { - option.selected = true; - } - - dropdown.appendChild(option); - addedCount++; - }); + this.renderPartyTypeRadios(dropdown, partyTypes, defaultValue); } else { console.error( "Failed to load party types:", @@ -1058,16 +1044,14 @@ class DynamicFormSections { result ); - // Add error option dropdown.innerHTML = - ''; + '

Error loading party types

'; } } catch (error) { console.error(`Error loading party types for ${fieldId}:`, error); - // Add error option dropdown.innerHTML = - ''; + '

Error loading party types

'; } finally { // Hide loading spinner if (loadingSpinner) { @@ -1077,6 +1061,45 @@ class DynamicFormSections { }); } + renderPartyTypeRadios(container, partyTypes, defaultValue) { + container.innerHTML = ""; + + if (!partyTypes || partyTypes.length === 0) { + container.innerHTML = '

No party types available

'; + return; + } + + const groupName = container.dataset.fieldName || container.id; + const isRequired = container.dataset.required === "true"; + + partyTypes.forEach((partyType, index) => { + const wrapper = document.createElement("div"); + wrapper.className = "form-check"; + + const input = document.createElement("input"); + input.type = "radio"; + input.className = "form-check-input"; + input.name = groupName; + input.id = `${container.id}_${index}`; + input.value = partyType.code; + if (isRequired) { + input.required = true; + } + if (partyType.code === defaultValue) { + input.checked = true; + } + + const label = document.createElement("label"); + label.className = "form-check-label"; + label.setAttribute("for", input.id); + label.textContent = partyType.name; + + wrapper.appendChild(input); + wrapper.appendChild(label); + container.appendChild(wrapper); + }); + } + showDynamicSections() { if (this.dynamicSections) { this.dynamicSections.style.display = "block"; @@ -1119,6 +1142,9 @@ class DynamicFormSections { if (field) { if (field.type === "checkbox") { formData[fieldName] = field.checked; + } else if (field.type === "radio") { + const checked = document.querySelector(`input[name="${fieldName}"]:checked`); + formData[fieldName] = checked ? checked.value : ""; } else { formData[fieldName] = field.value; } @@ -1142,6 +1168,11 @@ class DynamicFormSections { if (field) { if (field.type === "checkbox") { field.checked = this.preservedFormData[fieldName]; + } else if (field.type === "radio") { + const radio = document.querySelector( + `input[name="${fieldName}"][value="${this.preservedFormData[fieldName]}"]` + ); + if (radio) radio.checked = true; } else { field.value = this.preservedFormData[fieldName]; } @@ -1202,35 +1233,33 @@ class DynamicFormSections { field.checked = Array.isArray(data[key]) ? data[key].includes(field.value) : data[key] === field.value; - } else if (field.classList.contains("party-type-dropdown")) { - // For party type dropdowns, we may need to wait for options to load - const setDropdownValue = () => { - // Check if the option exists - const option = field.querySelector( - `option[value="${data[key]}"]` + } else if (field.type === "radio") { + // Party type radios are added asynchronously once the + // API responds, so keep retrying until the matching + // option exists. + const setRadioValue = () => { + const radio = document.querySelector( + `input[name="${key}"][value="${data[key]}"]` ); - if (option) { - field.value = data[key]; - - // Add visual validation feedback after successful population - field.classList.remove("is-invalid"); - field.classList.add("is-valid"); - } else if (field.options.length <= 1) { - // Options haven't loaded yet, wait a bit longer - setTimeout(setDropdownValue, 500); + if (radio) { + radio.checked = true; + } else if ( + document.querySelectorAll(`input[name="${key}"]`).length === 0 + ) { + setTimeout(setRadioValue, 500); } }; - setDropdownValue(); + setRadioValue(); } else { field.value = data[key]; } fieldsPopulated++; - // Add visual validation feedback (but not for dropdowns until they're populated) + // Add visual validation feedback (but not for radios until they're populated) if ( + field.type !== "radio" && field.value && - field.value.trim() && - !field.classList.contains("party-type-dropdown") + field.value.trim() ) { field.classList.remove("is-invalid"); field.classList.add("is-valid"); diff --git a/efile_app/efile/static/js/form-validation.js b/efile_app/efile/static/js/form-validation.js index 1f282a9..2ef0341 100644 --- a/efile_app/efile/static/js/form-validation.js +++ b/efile_app/efile/static/js/form-validation.js @@ -505,9 +505,17 @@ class FormValidation { "document_type", ].includes(key) ) { - const field = this.form.querySelector(`[name="${key}"]`); - if (field) { - field.value = Array.isArray(data[key]) ? data[key][0] : data[key]; + const fields = this.form.querySelectorAll(`[name="${key}"]`); + if (fields.length === 1 && fields[0].type !== "radio") { + fields[0].value = Array.isArray(data[key]) ? data[key][0] : data[key]; + } else { + fields.forEach((field) => { + if (field.type === "radio" || field.type === "checkbox") { + field.checked = Array.isArray(data[key]) ? + data[key].includes(field.value) : + data[key] === field.value; + } + }); } } }); diff --git a/efile_app/efile/static/js/upload-handler.js b/efile_app/efile/static/js/upload-handler.js index 06a559f..ce0c6d5 100644 --- a/efile_app/efile/static/js/upload-handler.js +++ b/efile_app/efile/static/js/upload-handler.js @@ -309,7 +309,7 @@ class UploadHandler { await this.populateDocumentTypes(upload_data.lead_filing_type, document.getElementById("leadDocumentType")); } if (upload_data.lead_document_type) { - document.getElementById("leadDocumentType").value = upload_data.lead_document_type; + this.setRadioGroupValue(document.getElementById("leadDocumentType"), upload_data.lead_document_type); } if (upload_data.lead_cc_email) { let input_element = document.getElementById("leadCertifiedCopies") @@ -338,7 +338,7 @@ class UploadHandler { await this.populateDocumentTypes(d.filing_type, document.getElementById(`supportingDocumentType${index}`)); } if (d.document_type) { - document.getElementById(`supportingDocumentType${index}`).value = d.document_type; + this.setRadioGroupValue(document.getElementById(`supportingDocumentType${index}`), d.document_type); } if (d.cc_email) { let input_element = document.getElementById(`supportingFilingType${index}`); @@ -559,7 +559,7 @@ class UploadHandler { if (selectedFilingTypeId) { await this.populateDocumentTypes(selectedFilingTypeId, documentTypeSelect); } else { - documentTypeSelect.innerHTML = ''; + documentTypeSelect.innerHTML = '

Select filing type first

'; } }); } @@ -696,7 +696,7 @@ class UploadHandler { this.showError(`Please select a filing component for supporting document: ${this.uploadedFiles[i].name}`); return; } - const supportingDocumentType = document.getElementById(`supportingDocumentType${i}`)?.value; + const supportingDocumentType = this.getRadioGroupValue(document.getElementById(`supportingDocumentType${i}`)); if (!supportingDocumentType) { this.showError(`Please select a document type for supporting document: ${this.uploadedFiles[i].name}`); return; @@ -706,12 +706,12 @@ class UploadHandler { try { // Collect dropdown values for lead document const leadFilingTypeSelect = document.getElementById('leadFilingType'); - const leadDocumentTypeSelect = document.getElementById('leadDocumentType'); + const leadDocumentTypeContainer = document.getElementById('leadDocumentType'); const leadFilingType = leadFilingTypeSelect ? leadFilingTypeSelect.value : ''; const leadFilingTypeName = leadFilingTypeSelect && leadFilingTypeSelect.selectedOptions[0] ? leadFilingTypeSelect.selectedOptions[0].text : ''; - const leadDocumentType = leadDocumentTypeSelect ? leadDocumentTypeSelect.value : ''; - const leadDocumentTypeName = leadDocumentTypeSelect && leadDocumentTypeSelect.selectedOptions[0] ? leadDocumentTypeSelect.selectedOptions[0].text : ''; + const leadDocumentType = this.getRadioGroupValue(leadDocumentTypeContainer); + const leadDocumentTypeName = this.getRadioGroupText(leadDocumentTypeContainer); if (!leadFilingType || !leadDocumentType) { this.showError('Please select a filing type and document type for the lead document.'); @@ -732,9 +732,9 @@ class UploadHandler { supportingDropdowns.forEach((dropdown, index) => { const filingType = dropdown.value; const filingTypeName = dropdown.selectedOptions[0]?.text || ''; - const docTypeSelect = document.getElementById(`supportingDocumentType${index}`); - const docType = docTypeSelect?.value || ''; - const docTypeName = docTypeSelect?.selectedOptions[0]?.text || ''; + const docTypeContainer = document.getElementById(`supportingDocumentType${index}`); + const docType = this.getRadioGroupValue(docTypeContainer); + const docTypeName = this.getRadioGroupText(docTypeContainer); const component = this.globalFilingComponentSupport.id; const componentName = this.globalFilingComponentSupport.name; @@ -1009,15 +1009,15 @@ class UploadHandler { this.initializeFilingTypeDropdowns(); } - async populateDocumentTypes(filingTypeId, documentTypeSelect) { + async populateDocumentTypes(filingTypeId, documentTypeContainer) { try { // Get court data from Django context to pass required parameters const court = JSON.parse(document.getElementById("case-classification").textContent)["court"] || sessionStorage.getItem("selected_court"); if (!court) { console.error("Missing court parameter for document types API"); - documentTypeSelect.innerHTML = - ''; + documentTypeContainer.innerHTML = + '

Missing court data

'; return; } @@ -1028,27 +1028,71 @@ class UploadHandler { } const result = await apiUtils.get('/api/dropdowns/document-types', params, true); if (result.success && result.data) { - documentTypeSelect.innerHTML = - ''; - result.data.forEach((docType) => { - const option = document.createElement("option"); - option.value = docType.value || docType.code || docType.id; - option.textContent = - docType.text || docType.name || docType.description; - documentTypeSelect.appendChild(option); - }); + this.renderDocumentTypeRadios(documentTypeContainer, result.data); } else { console.error("API returned error:", result.error); - documentTypeSelect.innerHTML = - ''; + documentTypeContainer.innerHTML = + '

Error loading document types

'; } } catch (error) { console.error("Error loading document types:", error); - documentTypeSelect.innerHTML = - ''; + documentTypeContainer.innerHTML = + '

Error loading document types

'; } } + // Confidential/non-confidential is a binary choice (occasionally a third + // option), so it's rendered as radio buttons rather than a dropdown. + renderDocumentTypeRadios(container, options) { + container.innerHTML = ""; + const groupName = container.dataset.fieldName || container.id; + + options.forEach((docType, index) => { + const value = docType.value || docType.code || docType.id; + const text = docType.text || docType.name || docType.description; + + const wrapper = document.createElement("div"); + wrapper.className = "form-check"; + + const input = document.createElement("input"); + input.type = "radio"; + input.className = "form-check-input document-type-select"; + input.name = groupName; + input.id = `${container.id}_${index}`; + input.value = value; + input.required = true; + + const label = document.createElement("label"); + label.className = "form-check-label"; + label.setAttribute("for", input.id); + label.textContent = text; + + wrapper.appendChild(input); + wrapper.appendChild(label); + container.appendChild(wrapper); + }); + } + + getRadioGroupValue(container) { + if (!container) return ''; + const checked = container.querySelector('input[type="radio"]:checked'); + return checked ? checked.value : ''; + } + + getRadioGroupText(container) { + if (!container) return ''; + const checked = container.querySelector('input[type="radio"]:checked'); + if (!checked) return ''; + const label = container.querySelector(`label[for="${checked.id}"]`); + return label ? label.textContent.trim() : ''; + } + + setRadioGroupValue(container, value) { + if (!container) return; + const radio = container.querySelector(`input[type="radio"][value="${value}"]`); + if (radio) radio.checked = true; + } + setupCascadingDropdowns() { // Lead document cascading dropdowns const leadFilingTypeSelect = document.getElementById("leadFilingType"); @@ -1145,10 +1189,12 @@ function createSupportingDocumentOptions(index, fileName) {
- - +
+ Request Documents to be Sealed / Confidential? * +
+

Select filing type first

+
+
diff --git a/efile_app/efile/templates/efile/expert_form.html b/efile_app/efile/templates/efile/expert_form.html index b532727..690eca7 100644 --- a/efile_app/efile/templates/efile/expert_form.html +++ b/efile_app/efile/templates/efile/expert_form.html @@ -116,20 +116,18 @@

{% translate "Case type" %}

- - {% translate "This is your party type." %} - - +
+ + {% translate "Party Type" %}* + + {% translate "This is your party type." %} +
+

{% translate "Select a case type first" %}

+
+ +
diff --git a/efile_app/efile/templates/efile/upload.html b/efile_app/efile/templates/efile/upload.html index 3c67966..1fadc13 100644 --- a/efile_app/efile/templates/efile/upload.html +++ b/efile_app/efile/templates/efile/upload.html @@ -86,15 +86,14 @@

- - +
+ + {% translate "Request documents be sealed / confidential?" %}* + +
+

{% translate "Select filing type first" %}

+
+