diff --git a/src/registrar/admin.py b/src/registrar/admin.py index 40c2bc0501..15f1ccb798 100644 --- a/src/registrar/admin.py +++ b/src/registrar/admin.py @@ -1,8 +1,6 @@ from datetime import date import logging import copy -import json -from django.template.loader import get_template from django import forms from django.db.models import Value, CharField, Q from django.db.models.functions import Concat, Coalesce @@ -23,6 +21,7 @@ from registrar.models.user_domain_role import UserDomainRole from waffle.admin import FlagAdmin from waffle.models import Sample, Switch +from registrar.utility.admin_helpers import get_all_action_needed_reason_emails, get_action_needed_reason_default_email from registrar.models import Contact, Domain, DomainRequest, DraftDomain, User, Website, SeniorOfficial from registrar.utility.constants import BranchChoices from registrar.utility.errors import FSMDomainRequestError, FSMErrorCodes @@ -1948,9 +1947,9 @@ def save_model(self, request, obj, form, change): # Set the action_needed_reason_email to the default if nothing exists. # Since this check occurs after save, if the user enters a value then we won't update. - default_email = self._get_action_needed_reason_default_email(obj, obj.action_needed_reason) + default_email = get_action_needed_reason_default_email(request, obj, obj.action_needed_reason) if obj.action_needed_reason_email: - emails = self.get_all_action_needed_reason_emails(obj) + emails = get_all_action_needed_reason_emails(request, obj) is_custom_email = obj.action_needed_reason_email not in emails.values() if not is_custom_email: obj.action_needed_reason_email = default_email @@ -2180,8 +2179,6 @@ def change_view(self, request, object_id, form_url="", extra_context=None): # Initialize extra_context and add filtered entries extra_context = extra_context or {} extra_context["filtered_audit_log_entries"] = filtered_audit_log_entries - emails = self.get_all_action_needed_reason_emails(obj) - extra_context["action_needed_reason_emails"] = json.dumps(emails) # Denote if an action needed email was sent or not email_sent = request.session.get("action_needed_email_sent", False) @@ -2192,39 +2189,6 @@ def change_view(self, request, object_id, form_url="", extra_context=None): # Call the superclass method with updated extra_context return super().change_view(request, object_id, form_url, extra_context) - def get_all_action_needed_reason_emails(self, domain_request): - """Returns a json dictionary of every action needed reason and its associated email - for this particular domain request.""" - - emails = {} - for action_needed_reason in domain_request.ActionNeededReasons: - # Map the action_needed_reason to its default email - emails[action_needed_reason.value] = self._get_action_needed_reason_default_email( - domain_request, action_needed_reason.value - ) - - return emails - - def _get_action_needed_reason_default_email(self, domain_request, action_needed_reason): - """Returns the default email associated with the given action needed reason""" - if not action_needed_reason or action_needed_reason == DomainRequest.ActionNeededReasons.OTHER: - return None - - recipient = domain_request.creator - - # Return the context of the rendered views - context = {"domain_request": domain_request, "recipient": recipient} - - # Get the email body - template_path = f"emails/action_needed_reasons/{action_needed_reason}.txt" - - email_body_text = get_template(template_path).render(context=context) - email_body_text_cleaned = None - if email_body_text: - email_body_text_cleaned = email_body_text.strip().lstrip("\n") - - return email_body_text_cleaned - def process_log_entry(self, log_entry): """Process a log entry and return filtered entry dictionary if applicable.""" changes = log_entry.changes diff --git a/src/registrar/assets/js/get-gov-admin.js b/src/registrar/assets/js/get-gov-admin.js index 7ff02ba1f5..7e3c086c44 100644 --- a/src/registrar/assets/js/get-gov-admin.js +++ b/src/registrar/assets/js/get-gov-admin.js @@ -504,167 +504,111 @@ function initializeWidgetOnList(list, parentId) { /** An IIFE that hooks to the show/hide button underneath action needed reason. * This shows the auto generated email on action needed reason. */ -(function () { - // Since this is an iife, these vars will be removed from memory afterwards - var actionNeededReasonDropdown = document.querySelector("#id_action_needed_reason"); - - // Placeholder text (for certain "action needed" reasons that do not involve e=mails) - var placeholderText = document.querySelector("#action-needed-reason-email-placeholder-text") - - // E-mail divs and textarea components - var actionNeededEmail = document.querySelector("#id_action_needed_reason_email") - var actionNeededEmailReadonly = document.querySelector("#action-needed-reason-email-readonly") - var actionNeededEmailReadonlyTextarea = document.querySelector("#action-needed-reason-email-readonly-textarea") - - // Edit e-mail modal (and its confirmation button) - var confirmEditEmailButton = document.querySelector("#email-already-sent-modal_continue-editing-button") - - // Headers and footers (which change depending on if the e-mail was sent or not) - var actionNeededEmailHeader = document.querySelector("#action-needed-email-header") - var actionNeededEmailHeaderOnSave = document.querySelector("#action-needed-email-header-email-sent") - var actionNeededEmailFooter = document.querySelector("#action-needed-email-footer") - - let emailWasSent = document.getElementById("action-needed-email-sent"); - let lastSentEmailText = document.getElementById("action-needed-email-last-sent-text"); - - // Get the list of e-mails associated with each action-needed dropdown value - let emailData = document.getElementById('action-needed-emails-data'); - if (!emailData) { - return; - } - let actionNeededEmailData = emailData.textContent; - if(!actionNeededEmailData) { - return; - } - let actionNeededEmailsJson = JSON.parse(actionNeededEmailData); - - const domainRequestId = actionNeededReasonDropdown ? document.querySelector("#domain_request_id").value : null - const emailSentSessionVariableName = `actionNeededEmailSent-${domainRequestId}`; - const oldDropdownValue = actionNeededReasonDropdown ? actionNeededReasonDropdown.value : null; - const oldEmailValue = actionNeededEmailData ? actionNeededEmailData.value : null; - - if(actionNeededReasonDropdown && actionNeededEmail && domainRequestId) { - // Add a change listener to dom load - document.addEventListener('DOMContentLoaded', function() { - let reason = actionNeededReasonDropdown.value; - - // Handle the session boolean (to enable/disable editing) - if (emailWasSent && emailWasSent.value === "True") { - // An email was sent out - store that information in a session variable - addOrRemoveSessionBoolean(emailSentSessionVariableName, add=true); - } - - // Show an editable email field or a readonly one - updateActionNeededEmailDisplay(reason) - }); - - // editEmailButton.addEventListener("click", function() { - // if (!checkEmailAlreadySent()) { - // showEmail(canEdit=true) - // } - // }); - - confirmEditEmailButton.addEventListener("click", function() { - // Show editable view - showEmail(canEdit=true) - }); - - - // Add a change listener to the action needed reason dropdown - actionNeededReasonDropdown.addEventListener("change", function() { - let reason = actionNeededReasonDropdown.value; - let emailBody = reason in actionNeededEmailsJson ? actionNeededEmailsJson[reason] : null; - - if (reason && emailBody) { - // Reset the session object on change since change refreshes the email content. - if (oldDropdownValue !== actionNeededReasonDropdown.value || oldEmailValue !== actionNeededEmail.value) { - // Replace the email content - actionNeededEmail.value = emailBody; - actionNeededEmailReadonlyTextarea.value = emailBody; - hideEmailAlreadySentView(); - } - } - - // Show either a preview of the email or some text describing no email will be sent - updateActionNeededEmailDisplay(reason) - }); +document.addEventListener('DOMContentLoaded', function() { + const dropdown = document.getElementById("id_action_needed_reason"); + const textarea = document.getElementById("id_action_needed_reason_email") + const domainRequestId = dropdown ? document.getElementById("domain_request_id").value : null + const textareaPlaceholder = document.querySelector(".field-action_needed_reason_email__placeholder"); + const directEditButton = document.querySelector('.field-action_needed_reason_email__edit'); + const modalTrigger = document.querySelector('.field-action_needed_reason_email__modal-trigger'); + const modalConfirm = document.getElementById('confirm-edit-email'); + const formLabel = document.querySelector('label[for="id_action_needed_reason_email"]'); + let lastSentEmailContent = document.getElementById("last-sent-email-content"); + const initialDropdownValue = dropdown ? dropdown.value : null; + const initialEmailValue = textarea.value; + + // We will use the const to control the modal + let isEmailAlreadySentConst = lastSentEmailContent.value.replace(/\s+/g, '') === textarea.value.replace(/\s+/g, ''); + // We will use the function to control the label and help + function isEmailAlreadySent() { + return lastSentEmailContent.value.replace(/\s+/g, '') === textarea.value.replace(/\s+/g, ''); } - function checkEmailAlreadySent() - { - lastEmailSent = lastSentEmailText.value.replace(/\s+/g, '') - currentEmailInTextArea = actionNeededEmail.value.replace(/\s+/g, '') - return lastEmailSent === currentEmailInTextArea - } - - // Shows a readonly preview of the email with updated messaging to indicate this email was sent - function showEmailAlreadySentView() - { - hideElement(actionNeededEmailHeader) - showElement(actionNeededEmailHeaderOnSave) - actionNeededEmailFooter.innerHTML = "This email has been sent to the creator of this request"; - } - - // Shows a readonly preview of the email with updated messaging to indicate this email was sent - function hideEmailAlreadySentView() - { - showElement(actionNeededEmailHeader) - hideElement(actionNeededEmailHeaderOnSave) - actionNeededEmailFooter.innerHTML = "This email will be sent to the creator of this request after saving"; - } - - // Shows either a preview of the email or some text describing no email will be sent. - // If the email doesn't exist or if we're of reason "other", display that no email was sent. - function updateActionNeededEmailDisplay(reason) { - hideElement(actionNeededEmail.parentElement) - - if (reason) { - if (reason === "other") { - // Hide email preview and show this text instead - showPlaceholderText("No email will be sent"); + if (!dropdown || !textarea || !domainRequestId || !formLabel || !modalConfirm) return; + const apiUrl = document.getElementById("get-action-needed-email-for-user-json").value; + + function updateUserInterface(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 + formLabel.innerHTML = "Email:"; + textareaPlaceholder.innerHTML = "Select an action needed reason to see email"; + showElement(textareaPlaceholder); + hideElement(directEditButton); + hideElement(modalTrigger); + hideElement(textarea); + } else if (reason === 'other') { + // 'Other' selected, we will set the label to "Email", show the "No email will be sent" placeholder, hide the trigger, textarea, hide the help text + formLabel.innerHTML = "Email:"; + textareaPlaceholder.innerHTML = "No email will be sent"; + showElement(textareaPlaceholder); + hideElement(directEditButton); + hideElement(modalTrigger); + hideElement(textarea); + } else { + // A triggering selection is selected, all hands on board: + textarea.setAttribute('readonly', true); + showElement(textarea); + hideElement(textareaPlaceholder); + + if (isEmailAlreadySentConst) { + hideElement(directEditButton); + showElement(modalTrigger); + } else { + showElement(directEditButton); + hideElement(modalTrigger); } - else { - // Always show readonly view of email to start - showEmail(canEdit=false) - if(checkEmailAlreadySent()) - { - showEmailAlreadySentView(); - } + if (isEmailAlreadySent()) { + formLabel.innerHTML = "Email sent to creator:"; + } else { + formLabel.innerHTML = "Email:"; } - } else { - // Hide email preview and show this text instead - showPlaceholderText("Select an action needed reason to see email"); } } - // Shows either a readonly view (canEdit=false) or editable view (canEdit=true) of the action needed email - function showEmail(canEdit) - { - if(!canEdit) - { - showElement(actionNeededEmailReadonly) - hideElement(actionNeededEmail.parentElement) - } - else - { - hideElement(actionNeededEmailReadonly) - showElement(actionNeededEmail.parentElement) + // Initialize UI + updateUserInterface(dropdown.value); + + dropdown.addEventListener("change", function() { + const reason = dropdown.value; + // Update the UI + updateUserInterface(reason); + if (reason && reason !== "other") { + // If it's not the initial value + if (initialDropdownValue !== dropdown.value || initialEmailValue !== textarea.value) { + // Replace the email content + fetch(`${apiUrl}?reason=${reason}&domain_request_id=${domainRequestId}`) + .then(response => { + return response.json().then(data => data); + }) + .then(data => { + if (data.error) { + console.error("Error in AJAX call: " + data.error); + }else { + textarea.value = data.action_needed_email; + } + updateUserInterface(reason); + }) + .catch(error => { + console.error("Error action needed email: ", error) + }); + } } - showElement(actionNeededEmailFooter) // this is the same for both views, so it was separated out - hideElement(placeholderText) - } - // Hides preview of action needed email and instead displays the given text (innerHTML) - function showPlaceholderText(innerHTML) - { - hideElement(actionNeededEmail.parentElement) - hideElement(actionNeededEmailReadonly) - hideElement(actionNeededEmailFooter) + }); - placeholderText.innerHTML = innerHTML; - showElement(placeholderText) - } -})(); + modalConfirm.addEventListener("click", () => { + textarea.removeAttribute('readonly'); + textarea.focus(); + hideElement(directEditButton); + hideElement(modalTrigger); + }); + directEditButton.addEventListener("click", () => { + textarea.removeAttribute('readonly'); + textarea.focus(); + hideElement(directEditButton); + hideElement(modalTrigger); + }); +}); /** An IIFE for copy summary button (appears in DomainRegistry models) diff --git a/src/registrar/assets/sass/_theme/_admin.scss b/src/registrar/assets/sass/_theme/_admin.scss index 3e6d0d171f..7bc7dc53ce 100644 --- a/src/registrar/assets/sass/_theme/_admin.scss +++ b/src/registrar/assets/sass/_theme/_admin.scss @@ -893,23 +893,6 @@ div.dja__model-description{ } } -.vertical-separator { - min-height: 20px; - height: 100%; - width: 1px; - background-color: #d1d2d2; - vertical-align: middle -} - -.usa-summary-box_admin { - color: var(--body-fg); - border-color: var(--summary-box-border); - background-color: var(--summary-box-bg); - min-width: fit-content; - padding: .5rem; - border-radius: .25rem; -} - .text-faded { color: #{$dhs-gray-60}; } diff --git a/src/registrar/config/urls.py b/src/registrar/config/urls.py index f45770cf66..76c77955fb 100644 --- a/src/registrar/config/urls.py +++ b/src/registrar/config/urls.py @@ -28,6 +28,7 @@ from registrar.views.utility.api_views import ( get_senior_official_from_federal_agency_json, get_federal_and_portfolio_types_from_federal_agency_json, + get_action_needed_email_for_user_json, ) from registrar.views.domains_json import get_domains_json from registrar.views.utility import always_404 @@ -153,6 +154,11 @@ get_federal_and_portfolio_types_from_federal_agency_json, name="get-federal-and-portfolio-types-from-federal-agency-json", ), + path( + "admin/api/get-action-needed-email-for-user-json/", + get_action_needed_email_for_user_json, + name="get-action-needed-email-for-user-json", + ), path("admin/", admin.site.urls), path( "reports/export_data_type_user/", diff --git a/src/registrar/templates/django/admin/domain_request_change_form.html b/src/registrar/templates/django/admin/domain_request_change_form.html index 0396326d93..afdd9e6c2c 100644 --- a/src/registrar/templates/django/admin/domain_request_change_form.html +++ b/src/registrar/templates/django/admin/domain_request_change_form.html @@ -8,6 +8,8 @@ {# Store the current object id so we can access it easier #} + {% url 'get-action-needed-email-for-user-json' as url %} + {% for fieldset in adminform %} {% comment %} TODO: this will eventually need to be changed to something like this diff --git a/src/registrar/templates/django/admin/includes/detail_table_fieldset.html b/src/registrar/templates/django/admin/includes/detail_table_fieldset.html index e22bcb5712..0540a7b60c 100644 --- a/src/registrar/templates/django/admin/includes/detail_table_fieldset.html +++ b/src/registrar/templates/django/admin/includes/detail_table_fieldset.html @@ -66,24 +66,6 @@ No changelog to display. {% endif %} - {% elif field.field.name == "action_needed_reason_email" %} -
- - -
{% elif field.field.name == "other_contacts" %} {% if all_contacts.count > 2 %}
@@ -155,131 +137,102 @@ {% block field_other %} {% if field.field.name == "action_needed_reason_email" %} -
-
- - + +
+ –
-
-