fixed javascript and other issues on domain request admin

This commit is contained in:
David Kennedy 2025-03-06 22:00:37 -05:00
parent fe1e64828b
commit 343d8dc962
No known key found for this signature in database
GPG key ID: 6528A5386E66B96B
3 changed files with 149 additions and 92 deletions

View file

@ -2976,6 +2976,10 @@ class DomainRequestAdmin(ListHeaderAdmin, ImportExportRegistrarModelAdmin):
"federally_recognized_tribe", "federally_recognized_tribe",
"state_recognized_tribe", "state_recognized_tribe",
"about_your_organization", "about_your_organization",
"rejection_reason",
"rejection_reason_email",
"action_needed_reason",
"action_needed_reason_email",
] ]
autocomplete_fields = [ autocomplete_fields = [

View file

@ -106,7 +106,9 @@ export function initApprovedDomain() {
} }
const statusToCheck = "approved"; const statusToCheck = "approved";
const readonlyStatusToCheck = "Approved";
const statusSelect = document.getElementById("id_status"); const statusSelect = document.getElementById("id_status");
const statusField = document.querySelector("field-status");
const sessionVariableName = "showApprovedDomain"; const sessionVariableName = "showApprovedDomain";
let approvedDomainFormGroup = document.querySelector(".field-approved_domain"); let approvedDomainFormGroup = document.querySelector(".field-approved_domain");
@ -120,18 +122,30 @@ export function initApprovedDomain() {
// Handle showing/hiding the related fields on page load. // Handle showing/hiding the related fields on page load.
function initializeFormGroups() { function initializeFormGroups() {
let isStatus = statusSelect.value == statusToCheck; let isStatus = false;
if (statusSelect) {
isStatus = statusSelect.value == statusToCheck;
} else {
// statusSelect does not exist, indicating readonly
if (statusField) {
let readonlyDiv = statusField.querySelector("div.readonly");
let readonlyStatusText = readonlyDiv.textContent.trim();
isStatus = readonlyStatusText == readonlyStatusToCheck;
}
}
// Initial handling of these groups. // Initial handling of these groups.
updateFormGroupVisibility(isStatus); updateFormGroupVisibility(isStatus);
// Listen to change events and handle rejectionReasonFormGroup display, then save status to session storage if (statusSelect) {
statusSelect.addEventListener('change', () => { // Listen to change events and handle rejectionReasonFormGroup display, then save status to session storage
// Show the approved if the status is what we expect. statusSelect.addEventListener('change', () => {
isStatus = statusSelect.value == statusToCheck; // Show the approved if the status is what we expect.
updateFormGroupVisibility(isStatus); isStatus = statusSelect.value == statusToCheck;
addOrRemoveSessionBoolean(sessionVariableName, isStatus); updateFormGroupVisibility(isStatus);
}); addOrRemoveSessionBoolean(sessionVariableName, isStatus);
});
}
// Listen to Back/Forward button navigation and handle approvedDomainFormGroup display based on session storage // Listen to Back/Forward button navigation and handle approvedDomainFormGroup display based on session storage
// When you navigate using forward/back after changing status but not saving, when you land back on the DA page the // When you navigate using forward/back after changing status but not saving, when you land back on the DA page the
@ -322,6 +336,7 @@ class CustomizableEmailBase {
* @property {HTMLElement} modalConfirm - The confirm button in the modal. * @property {HTMLElement} modalConfirm - The confirm button in the modal.
* @property {string} apiUrl - The API URL for fetching email content. * @property {string} apiUrl - The API URL for fetching email content.
* @property {string} statusToCheck - The status to check against. Used for show/hide on textAreaFormGroup/dropdownFormGroup. * @property {string} statusToCheck - The status to check against. Used for show/hide on textAreaFormGroup/dropdownFormGroup.
* @property {string} readonlyStatusToCheck - The status to check against when readonly. Used for show/hide on textAreaFormGroup/dropdownFormGroup.
* @property {string} sessionVariableName - The session variable name. Used for show/hide on textAreaFormGroup/dropdownFormGroup. * @property {string} sessionVariableName - The session variable name. Used for show/hide on textAreaFormGroup/dropdownFormGroup.
* @property {string} apiErrorMessage - The error message that the ajax call returns. * @property {string} apiErrorMessage - The error message that the ajax call returns.
*/ */
@ -338,6 +353,7 @@ class CustomizableEmailBase {
this.textAreaFormGroup = config.textAreaFormGroup; this.textAreaFormGroup = config.textAreaFormGroup;
this.dropdownFormGroup = config.dropdownFormGroup; this.dropdownFormGroup = config.dropdownFormGroup;
this.statusToCheck = config.statusToCheck; this.statusToCheck = config.statusToCheck;
this.readonlyStatusToCheck = config.readonlyStatusToCheck;
this.sessionVariableName = config.sessionVariableName; this.sessionVariableName = config.sessionVariableName;
// Non-configurable variables // Non-configurable variables
@ -363,19 +379,31 @@ class CustomizableEmailBase {
// Handle showing/hiding the related fields on page load. // Handle showing/hiding the related fields on page load.
initializeFormGroups() { initializeFormGroups() {
let isStatus = this.statusSelect.value == this.statusToCheck; let isStatus = false;
if (this.statusSelect) {
this.statusSelect.value == this.statusToCheck;
} else {
// statusSelect does not exist, indicating readonly
if (this.dropdownFormGroup) {
let readonlyDiv = this.dropdownFormGroup.querySelector("div.readonly");
let readonlyStatusText = readonlyDiv.textContent.trim();
isStatus = readonlyStatusText == this.readonlyStatusToCheck;
}
}
// Initial handling of these groups. // Initial handling of these groups.
this.updateFormGroupVisibility(isStatus); this.updateFormGroupVisibility(isStatus);
// Listen to change events and handle rejectionReasonFormGroup display, then save status to session storage if (this.statusSelect) {
this.statusSelect.addEventListener('change', () => { // Listen to change events and handle rejectionReasonFormGroup display, then save status to session storage
// Show the action needed field if the status is what we expect. this.statusSelect.addEventListener('change', () => {
// Then track if its shown or hidden in our session cache. // Show the action needed field if the status is what we expect.
isStatus = this.statusSelect.value == this.statusToCheck; // Then track if its shown or hidden in our session cache.
this.updateFormGroupVisibility(isStatus); isStatus = this.statusSelect.value == this.statusToCheck;
addOrRemoveSessionBoolean(this.sessionVariableName, isStatus); this.updateFormGroupVisibility(isStatus);
}); addOrRemoveSessionBoolean(this.sessionVariableName, isStatus);
});
}
// Listen to Back/Forward button navigation and handle rejectionReasonFormGroup display based on session storage // Listen to Back/Forward button navigation and handle rejectionReasonFormGroup display based on session storage
// When you navigate using forward/back after changing status but not saving, when you land back on the DA page the // When you navigate using forward/back after changing status but not saving, when you land back on the DA page the
@ -403,58 +431,64 @@ class CustomizableEmailBase {
} }
initializeDropdown() { initializeDropdown() {
this.dropdown.addEventListener("change", () => { if (this.dropdown) {
let reason = this.dropdown.value; this.dropdown.addEventListener("change", () => {
if (this.initialDropdownValue !== this.dropdown.value || this.initialEmailValue !== this.textarea.value) { let reason = this.dropdown.value;
let searchParams = new URLSearchParams( if (this.initialDropdownValue !== this.dropdown.value || this.initialEmailValue !== this.textarea.value) {
{ let searchParams = new URLSearchParams(
"reason": reason, {
"domain_request_id": this.domainRequestId, "reason": reason,
} "domain_request_id": this.domainRequestId,
); }
// Replace the email content );
fetch(`${this.apiUrl}?${searchParams.toString()}`) // Replace the email content
.then(response => { fetch(`${this.apiUrl}?${searchParams.toString()}`)
return response.json().then(data => data); .then(response => {
}) return response.json().then(data => data);
.then(data => { })
if (data.error) { .then(data => {
console.error("Error in AJAX call: " + data.error); if (data.error) {
}else { console.error("Error in AJAX call: " + data.error);
this.textarea.value = data.email; }else {
} this.textarea.value = data.email;
this.updateUserInterface(reason); }
}) this.updateUserInterface(reason);
.catch(error => { })
console.error(this.apiErrorMessage, error) .catch(error => {
}); console.error(this.apiErrorMessage, error)
} });
}); }
});
}
} }
initializeModalConfirm() { initializeModalConfirm() {
this.modalConfirm.addEventListener("click", () => { if (this.modalConfirm) {
this.textarea.removeAttribute('readonly'); this.modalConfirm.addEventListener("click", () => {
this.textarea.focus(); this.textarea.removeAttribute('readonly');
hideElement(this.directEditButton); this.textarea.focus();
hideElement(this.modalTrigger); hideElement(this.directEditButton);
}); hideElement(this.modalTrigger);
});
}
} }
initializeDirectEditButton() { initializeDirectEditButton() {
this.directEditButton.addEventListener("click", () => { if (this.directEditButton) {
this.textarea.removeAttribute('readonly'); this.directEditButton.addEventListener("click", () => {
this.textarea.focus(); this.textarea.removeAttribute('readonly');
hideElement(this.directEditButton); this.textarea.focus();
hideElement(this.modalTrigger); hideElement(this.directEditButton);
}); hideElement(this.modalTrigger);
});
}
} }
isEmailAlreadySent() { isEmailAlreadySent() {
return this.lastSentEmailContent.value.replace(/\s+/g, '') === this.textarea.value.replace(/\s+/g, ''); return this.lastSentEmailContent.value.replace(/\s+/g, '') === this.textarea.value.replace(/\s+/g, '');
} }
updateUserInterface(reason=this.dropdown.value, excluded_reasons=["other"]) { updateUserInterface(reason, excluded_reasons=["other"]) {
if (!reason) { if (!reason) {
// No reason selected, we will set the label to "Email", show the "Make a selection" placeholder, hide the trigger, textarea, hide the help text // No reason selected, we will set the label to "Email", show the "Make a selection" placeholder, hide the trigger, textarea, hide the help text
this.showPlaceholderNoReason(); this.showPlaceholderNoReason();
@ -468,23 +502,25 @@ class CustomizableEmailBase {
// Helper function that makes overriding the readonly textarea easy // Helper function that makes overriding the readonly textarea easy
showReadonlyTextarea() { showReadonlyTextarea() {
// A triggering selection is selected, all hands on board: if (this.textarea && this.textareaPlaceholder) {
this.textarea.setAttribute('readonly', true); // A triggering selection is selected, all hands on board:
showElement(this.textarea); this.textarea.setAttribute('readonly', true);
hideElement(this.textareaPlaceholder); showElement(this.textarea);
hideElement(this.textareaPlaceholder);
if (this.isEmailAlreadySentConst) { if (this.isEmailAlreadySentConst) {
hideElement(this.directEditButton); hideElement(this.directEditButton);
showElement(this.modalTrigger); showElement(this.modalTrigger);
} else {
showElement(this.directEditButton);
hideElement(this.modalTrigger);
}
if (this.isEmailAlreadySent()) {
this.formLabel.innerHTML = "Email sent to creator:";
} else { } else {
showElement(this.directEditButton); this.formLabel.innerHTML = "Email:";
hideElement(this.modalTrigger); }
}
if (this.isEmailAlreadySent()) {
this.formLabel.innerHTML = "Email sent to creator:";
} else {
this.formLabel.innerHTML = "Email:";
} }
} }
@ -516,9 +552,10 @@ class customActionNeededEmail extends CustomizableEmailBase {
lastSentEmailContent: document.getElementById("last-sent-action-needed-email-content"), lastSentEmailContent: document.getElementById("last-sent-action-needed-email-content"),
modalConfirm: document.getElementById("action-needed-reason__confirm-edit-email"), modalConfirm: document.getElementById("action-needed-reason__confirm-edit-email"),
apiUrl: document.getElementById("get-action-needed-email-for-user-json")?.value || null, apiUrl: document.getElementById("get-action-needed-email-for-user-json")?.value || null,
textAreaFormGroup: document.querySelector('.field-action_needed_reason'), textAreaFormGroup: document.querySelector('.field-action_needed_reason_email'),
dropdownFormGroup: document.querySelector('.field-action_needed_reason_email'), dropdownFormGroup: document.querySelector('.field-action_needed_reason'),
statusToCheck: "action needed", statusToCheck: "action needed",
readonlyStatusToCheck: "Action needed",
sessionVariableName: "showActionNeededReason", sessionVariableName: "showActionNeededReason",
apiErrorMessage: "Error when attempting to grab action needed email: " apiErrorMessage: "Error when attempting to grab action needed email: "
} }
@ -529,7 +566,15 @@ class customActionNeededEmail extends CustomizableEmailBase {
// Hide/show the email fields depending on the current status // Hide/show the email fields depending on the current status
this.initializeFormGroups(); this.initializeFormGroups();
// Setup the textarea, edit button, helper text // Setup the textarea, edit button, helper text
this.updateUserInterface(); let reason = null;
if (this.dropdown) {
reason = this.dropdown.value;
} else if (this.dropdownFormGroup && this.dropdownFormGroup.querySelector("div.readonly")) {
if (this.dropdownFormGroup.querySelector("div.readonly").textContent) {
reason = this.dropdownFormGroup.querySelector("div.readonly").textContent.trim()
}
}
this.updateUserInterface(reason);
this.initializeDropdown(); this.initializeDropdown();
this.initializeModalConfirm(); this.initializeModalConfirm();
this.initializeDirectEditButton(); this.initializeDirectEditButton();
@ -560,12 +605,12 @@ export function initActionNeededEmail() {
// Initialize UI // Initialize UI
const customEmail = new customActionNeededEmail(); const customEmail = new customActionNeededEmail();
// Check that every variable was setup correctly // // Check that every variable was setup correctly
const nullItems = Object.entries(customEmail.config).filter(([key, value]) => value === null).map(([key]) => key); // const nullItems = Object.entries(customEmail.config).filter(([key, value]) => value === null).map(([key]) => key);
if (nullItems.length > 0) { // if (nullItems.length > 0) {
console.error(`Failed to load customActionNeededEmail(). Some variables were null: ${nullItems.join(", ")}`) // console.error(`Failed to load customActionNeededEmail(). Some variables were null: ${nullItems.join(", ")}`)
return; // return;
} // }
customEmail.loadActionNeededEmail() customEmail.loadActionNeededEmail()
}); });
} }
@ -581,6 +626,7 @@ class customRejectedEmail extends CustomizableEmailBase {
textAreaFormGroup: document.querySelector('.field-rejection_reason'), textAreaFormGroup: document.querySelector('.field-rejection_reason'),
dropdownFormGroup: document.querySelector('.field-rejection_reason_email'), dropdownFormGroup: document.querySelector('.field-rejection_reason_email'),
statusToCheck: "rejected", statusToCheck: "rejected",
readonlyStatusToCheck: "Rejected",
sessionVariableName: "showRejectionReason", sessionVariableName: "showRejectionReason",
errorMessage: "Error when attempting to grab rejected email: " errorMessage: "Error when attempting to grab rejected email: "
}; };
@ -589,7 +635,15 @@ class customRejectedEmail extends CustomizableEmailBase {
loadRejectedEmail() { loadRejectedEmail() {
this.initializeFormGroups(); this.initializeFormGroups();
this.updateUserInterface(); let reason = null;
if (this.dropdown) {
reason = this.dropdown.value;
} else if (this.dropdownFormGroup && this.dropdownFormGroup.querySelector("div.readonly")) {
if (this.dropdownFormGroup.querySelector("div.readonly").textContent) {
reason = this.dropdownFormGroup.querySelector("div.readonly").textContent.trim()
}
}
this.updateUserInterface(reason);
this.initializeDropdown(); this.initializeDropdown();
this.initializeModalConfirm(); this.initializeModalConfirm();
this.initializeDirectEditButton(); this.initializeDirectEditButton();
@ -600,7 +654,7 @@ class customRejectedEmail extends CustomizableEmailBase {
this.showPlaceholder("Email:", "Select a rejection reason to see email"); this.showPlaceholder("Email:", "Select a rejection reason to see email");
} }
updateUserInterface(reason=this.dropdown.value, excluded_reasons=[]) { updateUserInterface(reason, excluded_reasons=[]) {
super.updateUserInterface(reason, excluded_reasons); super.updateUserInterface(reason, excluded_reasons);
} }
} }
@ -619,12 +673,12 @@ export function initRejectedEmail() {
// Initialize UI // Initialize UI
const customEmail = new customRejectedEmail(); const customEmail = new customRejectedEmail();
// Check that every variable was setup correctly // // Check that every variable was setup correctly
const nullItems = Object.entries(customEmail.config).filter(([key, value]) => value === null).map(([key]) => key); // const nullItems = Object.entries(customEmail.config).filter(([key, value]) => value === null).map(([key]) => key);
if (nullItems.length > 0) { // if (nullItems.length > 0) {
console.error(`Failed to load customRejectedEmail(). Some variables were null: ${nullItems.join(", ")}`) // console.error(`Failed to load customRejectedEmail(). Some variables were null: ${nullItems.join(", ")}`)
return; // return;
} // }
customEmail.loadRejectedEmail() customEmail.loadRejectedEmail()
}); });
} }
@ -648,7 +702,6 @@ function handleSuborgFieldsAndButtons() {
// Ensure that every variable is present before proceeding // Ensure that every variable is present before proceeding
if (!requestedSuborganizationField || !suborganizationCity || !suborganizationStateTerritory || !rejectButton) { if (!requestedSuborganizationField || !suborganizationCity || !suborganizationStateTerritory || !rejectButton) {
console.warn("handleSuborganizationSelection() => Could not find required fields.")
return; return;
} }

View file

@ -404,7 +404,7 @@ export function handlePortfolioSelection(
updateSubOrganizationDropdown(portfolio_id); updateSubOrganizationDropdown(portfolio_id);
// Show fields relevant to a selected portfolio // Show fields relevant to a selected portfolio
showElement(suborganizationField); if (suborganizationField) showElement(suborganizationField);
hideElement(seniorOfficialField); hideElement(seniorOfficialField);
showElement(portfolioSeniorOfficialField); showElement(portfolioSeniorOfficialField);
@ -427,7 +427,7 @@ export function handlePortfolioSelection(
// No portfolio is selected - reverse visibility of fields // No portfolio is selected - reverse visibility of fields
// Hide suborganization field as no portfolio is selected // Hide suborganization field as no portfolio is selected
hideElement(suborganizationField); if (suborganizationField) hideElement(suborganizationField);
// Show fields that are relevant when no portfolio is selected // Show fields that are relevant when no portfolio is selected
showElement(seniorOfficialField); showElement(seniorOfficialField);