From 9f89f2e2ffa134116bb5c29f4cf2a6b7670fe5b0 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Tue, 18 Jun 2024 14:06:58 -0600 Subject: [PATCH 01/31] Add endpoint --- src/registrar/config/urls.py | 3 ++- src/registrar/models/domain_request.py | 24 +++++++++++++++++++++ src/registrar/views/domain_requests_json.py | 23 ++++++++++++++++++++ 3 files changed, 49 insertions(+), 1 deletion(-) diff --git a/src/registrar/config/urls.py b/src/registrar/config/urls.py index dc6c8acb5..5cf76886b 100644 --- a/src/registrar/config/urls.py +++ b/src/registrar/config/urls.py @@ -22,7 +22,7 @@ from registrar.views.admin_views import ( ) from registrar.views.domain_request import Step -from registrar.views.domain_requests_json import get_domain_requests_json +from registrar.views.domain_requests_json import get_domain_requests_json, get_action_needed_email from registrar.views.domains_json import get_domains_json from registrar.views.utility import always_404 from api.views import available, get_current_federal, get_current_full @@ -213,6 +213,7 @@ urlpatterns = [ ), path("get-domains-json/", get_domains_json, name="get_domains_json"), path("get-domain-requests-json/", get_domain_requests_json, name="get_domain_requests_json"), + path("get-domain-requests-json//action-needed-email/", get_action_needed_email, name="get_action_needed_email"), ] # Djangooidc strips out context data from that context, so we define a custom error diff --git a/src/registrar/models/domain_request.py b/src/registrar/models/domain_request.py index 4f306f403..7a7f46f65 100644 --- a/src/registrar/models/domain_request.py +++ b/src/registrar/models/domain_request.py @@ -12,6 +12,7 @@ from registrar.models.federal_agency import FederalAgency from registrar.models.utility.generic_helper import CreateOrUpdateOrganizationTypeHelper from registrar.utility.errors import FSMDomainRequestError, FSMErrorCodes from registrar.utility.constants import BranchChoices +from django.template.loader import get_template from .utility.time_stamped_model import TimeStampedModel from ..utility.email import send_templated_email, EmailSendingError @@ -541,6 +542,29 @@ class DomainRequest(TimeStampedModel): blank=True, ) + + def get_action_needed_reason_default_email_text(self, action_needed_reason: str): + """Returns the default email associated with the given action needed reason""" + logger.info(f"reason? {action_needed_reason}") + if action_needed_reason is None or action_needed_reason == self.ActionNeededReasons.OTHER: + return {} + + # Get the email body + template_path = f"emails/action_needed_reasons/{action_needed_reason}.txt" + template = get_template(template_path) + + # Get the email subject + template_subject_path = f"emails/action_needed_reasons/{action_needed_reason}_subject.txt" + subject_template = get_template(template_subject_path) + + # Return the content of the rendered views + context = {"domain_request": self} + return { + "subject_text": subject_template.render(context=context), + "email_body_text": template.render(context=context) + } + + def sync_organization_type(self): """ Updates the organization_type (without saving) to match diff --git a/src/registrar/views/domain_requests_json.py b/src/registrar/views/domain_requests_json.py index 2e58c8e48..21096891b 100644 --- a/src/registrar/views/domain_requests_json.py +++ b/src/registrar/views/domain_requests_json.py @@ -1,3 +1,4 @@ +import logging from django.http import JsonResponse from django.core.paginator import Paginator from registrar.models import DomainRequest @@ -5,6 +6,10 @@ from django.utils.dateformat import format from django.contrib.auth.decorators import login_required from django.urls import reverse from django.db.models import Q +from django.core.exceptions import PermissionDenied + + +logger = logging.getLogger(__name__) @login_required @@ -97,3 +102,21 @@ def get_domain_requests_json(request): "unfiltered_total": unfiltered_total, } ) + + + +@login_required +def get_action_needed_email(request, pk, reason): + has_access = request.user.is_staff or request.user.is_superuser + # TODO also check the perm group + if not has_access: + raise PermissionDenied("You do not have permission to access this resource.") + + logger.info(f"pk: {pk} reason: {reason}") + domain_request = DomainRequest.objects.filter(id=pk).first() + + reason_dict = domain_request.get_action_needed_reason_default_email_text(reason) + + return JsonResponse( + reason_dict + ) From 2e5f564ae673a49dbf81576cc65b3f86b0f274a1 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Tue, 18 Jun 2024 15:36:15 -0600 Subject: [PATCH 02/31] Add field --- src/registrar/admin.py | 1 + src/registrar/assets/js/get-gov-admin.js | 77 +++++++++++++++++++ ...omainrequest_action_needed_reason_email.py | 18 +++++ src/registrar/models/domain_request.py | 18 ++++- .../admin/includes/detail_table_fieldset.html | 27 +++++++ src/registrar/views/domain_requests_json.py | 2 - 6 files changed, 140 insertions(+), 3 deletions(-) create mode 100644 src/registrar/migrations/0103_domainrequest_action_needed_reason_email.py diff --git a/src/registrar/admin.py b/src/registrar/admin.py index 160b906ab..811946549 100644 --- a/src/registrar/admin.py +++ b/src/registrar/admin.py @@ -1485,6 +1485,7 @@ class DomainRequestAdmin(ListHeaderAdmin, ImportExportModelAdmin): "status", "rejection_reason", "action_needed_reason", + "action_needed_reason_email", "investigator", "creator", "submitter", diff --git a/src/registrar/assets/js/get-gov-admin.js b/src/registrar/assets/js/get-gov-admin.js index 524cfe594..5824f22c2 100644 --- a/src/registrar/assets/js/get-gov-admin.js +++ b/src/registrar/assets/js/get-gov-admin.js @@ -57,6 +57,7 @@ function openInNewTab(el, removeAttribute = false){ createPhantomModalFormButtons(); })(); + /** An IIFE for DomainRequest to hook a modal to a dropdown option. * This intentionally does not interact with createPhantomModalFormButtons() */ @@ -518,3 +519,79 @@ function initializeWidgetOnList(list, parentId) { handleShowMoreButton(toggleButton, descriptionDiv) } })(); + + + +/** An IIFE that hooks up to the "show email" button + * which shows the auto generated email on action needed reason +*/ +(function () { + let statusDropdown = document.getElementById("id_status"); + + statusDropdown.addEventListener('change', function() { + // TODO we should also handle when action needed + if (statusDropdown.value != "action needed"){ + formRow.classList.add("display-none") + } + }); + + let actionNeededDropdownReason = document.getElementById("id_action_needed_reason"); + // Store the domain request id on this record for simplicity + let showEmailButton = document.getElementById("show_action_needed_email"); + let actionNeededEmail = document.getElementById("id_action_needed_reason_email") + let formRow = actionNeededEmail.closest('.form-row'); + if(actionNeededDropdownReason && showEmailButton && actionNeededEmail && formRow) { + actionNeededDropdownReason.addEventListener('change', function() { + // TODO on change if not actionneeded on status, hide show email button + const pk = showEmailButton.getAttribute("domain-request-id") + const reason = actionNeededDropdownReason.value + fetch(`/get-domain-requests-json/${pk}/action-needed-email/${reason}`) + .then(response => response.json()) + .then(data => { + if (data.error) { + console.log('Error in AJAX call: ' + data.error); + return; + } + + let noEmailMessage = document.getElementById("no-email-message"); + if(data && data.email_body_text) { + actionNeededEmail.value = data.email_body_text + + // Show the text field + if(actionNeededEmail.classList.contains("display-none")) { + actionNeededEmail.classList.remove("display-none") + } + + // Hide the message + if(noEmailMessage && !noEmailMessage.classList.contains("display-none")) { + noEmailMessage.classList.add("display-none") + } + + }else if (data && !data.email_body_text) { + if (!noEmailMessage) { + noEmailMessage = document.createElement("p"); + noEmailMessage.id = "no-email-message"; + noEmailMessage.textContent = "No email will be sent"; + actionNeededEmail.parentNode.appendChild(noEmailMessage); + } + + // Hide the text field + if(!actionNeededEmail.classList.contains("display-none")) { + actionNeededEmail.classList.add("display-none") + } + + // Show the message + if(noEmailMessage.classList.contains("display-none")) { + noEmailMessage.classList.remove("display-none") + } + } + console.log(data) + }); + }); + + showEmailButton.addEventListener('click', function() { + formRow.classList.remove("display-none") + }); + } + +})(); \ No newline at end of file diff --git a/src/registrar/migrations/0103_domainrequest_action_needed_reason_email.py b/src/registrar/migrations/0103_domainrequest_action_needed_reason_email.py new file mode 100644 index 000000000..8df3e47dc --- /dev/null +++ b/src/registrar/migrations/0103_domainrequest_action_needed_reason_email.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.10 on 2024-06-18 20:13 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("registrar", "0102_domain_dsdata_last_change"), + ] + + operations = [ + migrations.AddField( + model_name="domainrequest", + name="action_needed_reason_email", + field=models.TextField(blank=True, null=True), + ), + ] diff --git a/src/registrar/models/domain_request.py b/src/registrar/models/domain_request.py index 7a7f46f65..14b698da7 100644 --- a/src/registrar/models/domain_request.py +++ b/src/registrar/models/domain_request.py @@ -295,6 +295,11 @@ class DomainRequest(TimeStampedModel): blank=True, ) + action_needed_reason_email = models.TextField( + null=True, + blank=True, + ) + federal_agency = models.ForeignKey( "registrar.FederalAgency", on_delete=models.PROTECT, @@ -547,7 +552,10 @@ class DomainRequest(TimeStampedModel): """Returns the default email associated with the given action needed reason""" logger.info(f"reason? {action_needed_reason}") if action_needed_reason is None or action_needed_reason == self.ActionNeededReasons.OTHER: - return {} + return { + "subject_text": None, + "email_body_text": None, + } # Get the email body template_path = f"emails/action_needed_reasons/{action_needed_reason}.txt" @@ -606,6 +614,11 @@ class DomainRequest(TimeStampedModel): def save(self, *args, **kwargs): """Save override for custom properties""" + + if self.action_needed_reason and not self.action_needed_reason_email: + text = self.get_action_needed_reason_default_email_text(self.action_needed_reason) + self.action_needed_reason_email = text.get("email_body_text") + self.sync_organization_type() self.sync_yes_no_form_fields() @@ -851,6 +864,9 @@ class DomainRequest(TimeStampedModel): # Unknown and other are default cases - do nothing can_send_email = False + # TODO - replace this logic with self.action_needed_reason_email in #1901. + # The email content should be dependent on that field. + # Assumes that the template name matches the action needed reason if nothing is specified. # This is so you can override if you need, or have this taken care of for you. if not email_template_name and not email_template_subject_name: 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 0f4274802..e4779c332 100644 --- a/src/registrar/templates/django/admin/includes/detail_table_fieldset.html +++ b/src/registrar/templates/django/admin/includes/detail_table_fieldset.html @@ -106,12 +106,39 @@ This is using a custom implementation fieldset.html (see admin/fieldset.html) + + + {% comment %} +
+
  • + +
  • +
  • + +
  • +
    + {% endcomment %} {% elif field.field.name == "creator" %} diff --git a/src/registrar/views/domain_requests_json.py b/src/registrar/views/domain_requests_json.py index 21096891b..afc457b00 100644 --- a/src/registrar/views/domain_requests_json.py +++ b/src/registrar/views/domain_requests_json.py @@ -112,9 +112,7 @@ def get_action_needed_email(request, pk, reason): if not has_access: raise PermissionDenied("You do not have permission to access this resource.") - logger.info(f"pk: {pk} reason: {reason}") domain_request = DomainRequest.objects.filter(id=pk).first() - reason_dict = domain_request.get_action_needed_reason_default_email_text(reason) return JsonResponse( From 305bad257587b87c7eb996eee6c55da50a5ba05a Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Thu, 20 Jun 2024 09:17:47 -0600 Subject: [PATCH 03/31] Minor rewrite --- src/registrar/assets/js/get-gov-admin.js | 82 +++++++++++-------- .../admin/domain_request_change_form.html | 3 +- .../admin/includes/detail_table_fieldset.html | 8 +- 3 files changed, 52 insertions(+), 41 deletions(-) diff --git a/src/registrar/assets/js/get-gov-admin.js b/src/registrar/assets/js/get-gov-admin.js index 5824f22c2..83348ca7f 100644 --- a/src/registrar/assets/js/get-gov-admin.js +++ b/src/registrar/assets/js/get-gov-admin.js @@ -8,6 +8,25 @@ // <<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>> // Helper functions. + +/** + * Hide element + * +*/ +const hideElement = (element) => { + if (!element.classList.contains("display-none")) + element.classList.add('display-none'); +}; + +/** + * Show element + * + */ +const showElement = (element) => { + if (element.classList.contains("display-none")) + element.classList.remove('display-none'); +}; + /** Either sets attribute target="_blank" to a given element, or removes it */ function openInNewTab(el, removeAttribute = false){ if(removeAttribute){ @@ -521,30 +540,35 @@ function initializeWidgetOnList(list, parentId) { })(); - /** An IIFE that hooks up to the "show email" button * which shows the auto generated email on action needed reason */ (function () { let statusDropdown = document.getElementById("id_status"); - - statusDropdown.addEventListener('change', function() { - // TODO we should also handle when action needed - if (statusDropdown.value != "action needed"){ - formRow.classList.add("display-none") - } - }); - - let actionNeededDropdownReason = document.getElementById("id_action_needed_reason"); - // Store the domain request id on this record for simplicity - let showEmailButton = document.getElementById("show_action_needed_email"); + let actionNeededReasonDropdown = document.getElementById("id_action_needed_reason"); let actionNeededEmail = document.getElementById("id_action_needed_reason_email") - let formRow = actionNeededEmail.closest('.form-row'); - if(actionNeededDropdownReason && showEmailButton && actionNeededEmail && formRow) { - actionNeededDropdownReason.addEventListener('change', function() { + let reasonFormRow = actionNeededEmail.closest(".form-row"); + + if(actionNeededReasonDropdown && actionNeededEmail && reasonFormRow) { + statusDropdown.addEventListener('change', function() { + if (statusDropdown.value != "action needed") { + // Hide the email field by default + hideElement(reasonFormRow) + }else { + showElement(reasonFormRow) + } + }); + + if (statusDropdown.value == "action needed") + handleChangeActionNeededEmail(actionNeededReasonDropdown, actionNeededEmail, reasonFormRow); + } + + // TODO fix edge case where nothing is selected + function handleChangeActionNeededEmail(actionNeededReasonDropdown, actionNeededEmail, reasonFormRow) { + actionNeededReasonDropdown.addEventListener('change', function() { // TODO on change if not actionneeded on status, hide show email button - const pk = showEmailButton.getAttribute("domain-request-id") - const reason = actionNeededDropdownReason.value + const pk = document.querySelector("#domain_request_id").value + const reason = actionNeededReasonDropdown.value fetch(`/get-domain-requests-json/${pk}/action-needed-email/${reason}`) .then(response => response.json()) .then(data => { @@ -558,13 +582,11 @@ function initializeWidgetOnList(list, parentId) { actionNeededEmail.value = data.email_body_text // Show the text field - if(actionNeededEmail.classList.contains("display-none")) { - actionNeededEmail.classList.remove("display-none") - } + showElement(actionNeededEmail); - // Hide the message - if(noEmailMessage && !noEmailMessage.classList.contains("display-none")) { - noEmailMessage.classList.add("display-none") + // Hide the "no email" message + if(noEmailMessage) { + hideElement(noEmailMessage); } }else if (data && !data.email_body_text) { @@ -576,22 +598,16 @@ function initializeWidgetOnList(list, parentId) { } // Hide the text field - if(!actionNeededEmail.classList.contains("display-none")) { - actionNeededEmail.classList.add("display-none") - } + hideElement(actionNeededEmail); // Show the message - if(noEmailMessage.classList.contains("display-none")) { - noEmailMessage.classList.remove("display-none") - } + showElement(noEmailMessage); } - console.log(data) + + showElement(reasonFormRow) }); }); - showEmailButton.addEventListener('click', function() { - formRow.classList.remove("display-none") - }); } })(); \ No newline at end of file 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 1c8ce2633..1b56ee597 100644 --- a/src/registrar/templates/django/admin/domain_request_change_form.html +++ b/src/registrar/templates/django/admin/domain_request_change_form.html @@ -5,7 +5,8 @@ {% block field_sets %} {# Create an invisible tag so that we can use a click event to toggle the modal. #} - + {# Store the current object id so we can access it easier #} + {% 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 e4779c332..a7c50a810 100644 --- a/src/registrar/templates/django/admin/includes/detail_table_fieldset.html +++ b/src/registrar/templates/django/admin/includes/detail_table_fieldset.html @@ -62,7 +62,7 @@ This is using a custom implementation fieldset.html (see admin/fieldset.html) {% endwith %} {% else %} -
    {{ field.contents }}
    +
    {{ field.contents }}
    {% endif %} {% endwith %} {% endblock field_readonly %} @@ -113,12 +113,6 @@ This is using a custom implementation fieldset.html (see admin/fieldset.html) - {% comment %}
  • From 9da96b06c7a3052136e00f082511d40084af1331 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Thu, 20 Jun 2024 10:47:38 -0600 Subject: [PATCH 04/31] Remove field --- src/registrar/admin.py | 1 - src/registrar/assets/js/get-gov-admin.js | 22 ++++---------- src/registrar/assets/sass/_theme/_admin.scss | 9 ++++++ .../admin/includes/detail_table_fieldset.html | 30 +++++++------------ 4 files changed, 25 insertions(+), 37 deletions(-) diff --git a/src/registrar/admin.py b/src/registrar/admin.py index 811946549..160b906ab 100644 --- a/src/registrar/admin.py +++ b/src/registrar/admin.py @@ -1485,7 +1485,6 @@ class DomainRequestAdmin(ListHeaderAdmin, ImportExportModelAdmin): "status", "rejection_reason", "action_needed_reason", - "action_needed_reason_email", "investigator", "creator", "submitter", diff --git a/src/registrar/assets/js/get-gov-admin.js b/src/registrar/assets/js/get-gov-admin.js index 83348ca7f..04dc04dac 100644 --- a/src/registrar/assets/js/get-gov-admin.js +++ b/src/registrar/assets/js/get-gov-admin.js @@ -546,25 +546,17 @@ function initializeWidgetOnList(list, parentId) { (function () { let statusDropdown = document.getElementById("id_status"); let actionNeededReasonDropdown = document.getElementById("id_action_needed_reason"); - let actionNeededEmail = document.getElementById("id_action_needed_reason_email") - let reasonFormRow = actionNeededEmail.closest(".form-row"); - - if(actionNeededReasonDropdown && actionNeededEmail && reasonFormRow) { - statusDropdown.addEventListener('change', function() { - if (statusDropdown.value != "action needed") { - // Hide the email field by default - hideElement(reasonFormRow) - }else { - showElement(reasonFormRow) - } - }); + // If you need to account for the non-readonly version as well, you will need to check + // for both of these things seperately. + let actionNeededEmail = document.querySelector("#action_needed_reason_email_view_more") + if(actionNeededReasonDropdown && actionNeededEmail) { if (statusDropdown.value == "action needed") - handleChangeActionNeededEmail(actionNeededReasonDropdown, actionNeededEmail, reasonFormRow); + handleChangeActionNeededEmail(actionNeededReasonDropdown, actionNeededEmail); } // TODO fix edge case where nothing is selected - function handleChangeActionNeededEmail(actionNeededReasonDropdown, actionNeededEmail, reasonFormRow) { + function handleChangeActionNeededEmail(actionNeededReasonDropdown, actionNeededEmail) { actionNeededReasonDropdown.addEventListener('change', function() { // TODO on change if not actionneeded on status, hide show email button const pk = document.querySelector("#domain_request_id").value @@ -603,8 +595,6 @@ function initializeWidgetOnList(list, parentId) { // Show the message showElement(noEmailMessage); } - - showElement(reasonFormRow) }); }); diff --git a/src/registrar/assets/sass/_theme/_admin.scss b/src/registrar/assets/sass/_theme/_admin.scss index 360055d91..0248d33b7 100644 --- a/src/registrar/assets/sass/_theme/_admin.scss +++ b/src/registrar/assets/sass/_theme/_admin.scss @@ -786,3 +786,12 @@ div.dja__model-description{ .usa-button--dja-link-color { color: var(--link-fg); } + +// If the dja textarea is set as readonly, display this on hover +textarea.vLargeTextField[readonly]:hover { + cursor: not-allowed; +} + +.max-full { + width: 100% !important; +} \ No newline at end of file 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 a7c50a810..5e162f8e8 100644 --- a/src/registrar/templates/django/admin/includes/detail_table_fieldset.html +++ b/src/registrar/templates/django/admin/includes/detail_table_fieldset.html @@ -105,34 +105,24 @@ This is using a custom implementation fieldset.html (see admin/fieldset.html) {% endfor %} + + {% if original_object.action_needed_reason_email %} +
    + + +
    + {% endif %}
  • + - {% comment %} -
    -
  • - -
  • -
  • - -
  • -
    - {% endcomment %} {% elif field.field.name == "creator" %} From 39629a3e10b46da866bd6fbedbc577f2ed769443 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Thu, 20 Jun 2024 11:00:11 -0600 Subject: [PATCH 05/31] Cleanup logic --- src/registrar/assets/js/get-gov-admin.js | 21 +++++++++++++--- src/registrar/assets/sass/_theme/_admin.scss | 25 +++++++++++++++++++ .../admin/includes/detail_table_fieldset.html | 6 +++-- 3 files changed, 46 insertions(+), 6 deletions(-) diff --git a/src/registrar/assets/js/get-gov-admin.js b/src/registrar/assets/js/get-gov-admin.js index 04dc04dac..5e6550ef5 100644 --- a/src/registrar/assets/js/get-gov-admin.js +++ b/src/registrar/assets/js/get-gov-admin.js @@ -548,16 +548,29 @@ function initializeWidgetOnList(list, parentId) { let actionNeededReasonDropdown = document.getElementById("id_action_needed_reason"); // If you need to account for the non-readonly version as well, you will need to check // for both of these things seperately. - let actionNeededEmail = document.querySelector("#action_needed_reason_email_view_more") + let actionNeededEmail = document.querySelector("#action_needed_reason_email_view_more"); + if(actionNeededReasonDropdown && actionNeededEmail) { - if (statusDropdown.value == "action needed") - handleChangeActionNeededEmail(actionNeededReasonDropdown, actionNeededEmail); + let emailContainer = actionNeededEmail.closest(".dja-textarea-container"); + if (statusDropdown.value == "action needed") { + showElement(emailContainer) + } + + statusDropdown.addEventListener("change", function() { + if (statusDropdown.value == "action needed") { + showElement(emailContainer) + }else { + hideElement(emailContainer) + } + }); + + handleChangeActionNeededEmail(actionNeededReasonDropdown, actionNeededEmail); } // TODO fix edge case where nothing is selected function handleChangeActionNeededEmail(actionNeededReasonDropdown, actionNeededEmail) { - actionNeededReasonDropdown.addEventListener('change', function() { + actionNeededReasonDropdown.addEventListener("change", function() { // TODO on change if not actionneeded on status, hide show email button const pk = document.querySelector("#domain_request_id").value const reason = actionNeededReasonDropdown.value diff --git a/src/registrar/assets/sass/_theme/_admin.scss b/src/registrar/assets/sass/_theme/_admin.scss index 0248d33b7..ff993b87b 100644 --- a/src/registrar/assets/sass/_theme/_admin.scss +++ b/src/registrar/assets/sass/_theme/_admin.scss @@ -792,6 +792,31 @@ textarea.vLargeTextField[readonly]:hover { cursor: not-allowed; } +.textarea-container textarea { + width: 100%; /* Full width to fit the container */ + height: 150px; /* Fixed height or as needed */ + padding-right: 10px; /* Enough padding to nest the scrollbar inside */ + box-sizing: border-box; /* Include padding in width and height calculations */ + overflow-y: auto; /* Only show vertical scrollbar when necessary */ + scrollbar-width: thin; /* Makes the scrollbar thinner */ + scrollbar-color: #888 #000; /* Optional: styling the scrollbar with a thumb and track color */ +} + +/* For WebKit browsers like Chrome, Safari */ +.textarea-container textarea::-webkit-scrollbar { + width: 8px; /* Width of the scrollbar */ +} + +.textarea-container textarea::-webkit-scrollbar-track { + background: #000; /* Track color */ +} + +.textarea-container textarea::-webkit-scrollbar-thumb { + background-color: #888; /* Color of the scrollbar thumb */ + border-radius: 10px; /* Rounded corners for the scrollbar thumb */ + border: 2px solid #000; /* Optional: Adds a border around the thumb */ +} + .max-full { width: 100% !important; } \ No newline at end of file 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 5e162f8e8..cb14817bb 100644 --- a/src/registrar/templates/django/admin/includes/detail_table_fieldset.html +++ b/src/registrar/templates/django/admin/includes/detail_table_fieldset.html @@ -107,8 +107,10 @@ This is using a custom implementation fieldset.html (see admin/fieldset.html) {% if original_object.action_needed_reason_email %} -
    - +