diff --git a/anatomy/static/js/anatomy.js b/anatomy/static/js/anatomy.js index 13b414cc..25e7132a 100644 --- a/anatomy/static/js/anatomy.js +++ b/anatomy/static/js/anatomy.js @@ -12,6 +12,10 @@ window.control_pressed = false; $(document).ready(function () { + // Select all text input fields and textareas with the clear button + clearableTextAreaSetup(); + + document.body.addEventListener("htmx:responseError", function(e) { let el = document.getElementById("htmx-error"); var error; @@ -193,6 +197,40 @@ window.addEventListener('loadDicomViewerUrls', function (e) { loadDicomViewer(e.detail.images, e.detail.annotations); }, false); +function clearableTextAreaSetup() { + const textFields = document.querySelectorAll(".position-relative input[type='text'], .position-relative textarea"); + + textFields.forEach((field) => { + const wrapper = field.closest(".position-relative"); + const clearButton = wrapper.querySelector(".clear-input-button"); + + // Function to toggle the visibility of the clear button + const toggleClearButton = () => { + if (field.value.trim() !== "" && wrapper.matches(":hover")) { + clearButton.style.display = "inline-block"; // Show the button + } else { + clearButton.style.display = "none"; // Hide the button + } + }; + + // Initial check + toggleClearButton(); + + // Add event listeners to monitor changes in the input field or textarea + field.addEventListener("input", toggleClearButton); + + // Add hover event listeners to the wrapper + wrapper.addEventListener("mouseenter", toggleClearButton); + wrapper.addEventListener("mouseleave", toggleClearButton); + + // Add click event to clear the field or textarea + clearButton.addEventListener("click", function () { + field.value = ""; // Clear the field + toggleClearButton(); + }); + }); +} + function loadDicomViewer(images_to_load, annotations_to_load) { console.log("loadDicomViewer", images_to_load); let single_dicom = document.getElementById("single-dicom-viewer"); diff --git a/atlas/forms.py b/atlas/forms.py index d18d1161..82cda80d 100755 --- a/atlas/forms.py +++ b/atlas/forms.py @@ -49,7 +49,7 @@ from generic.models import Examination#, Sign from django.contrib.admin.widgets import FilteredSelectMultiple from django.forms.widgets import RadioSelect, TextInput, Textarea, Select -from crispy_forms.layout import Submit, Layout, Fieldset, Field, Div +from crispy_forms.layout import Submit, Layout, Fieldset, Field, Div, HTML from crispy_forms.bootstrap import Accordion, AccordionGroup from tinymce.widgets import TinyMCE @@ -71,6 +71,12 @@ from typing import Any from helpers.cimar import CimarAPI, NotFoundError from rad.settings import CIMAR_USERNAME, CIMAR_PASSWORD +class ClearableInput(TextInput): + template_name = "crispy_forms/widgets/clearable_input_single_line.html" + +class ClearableTextarea(Textarea): + template_name = "crispy_forms/widgets/clearable_input.html" + class CaseSelect(Select): template_name = "atlas/case_select_widget.html" @@ -295,14 +301,16 @@ class SeriesForm(ModelForm): class CaseForm(ModelForm): - # exams = ModelMultipleChoiceField(required=False, queryset=Exam.objects.all(),widget=FilteredSelectMultiple(verbose_name="Exams", is_stacked=False)) - - class Media: # Django also includes a few javascript files necessary # for the operation of this form element. You need to # include # in the template. + #history = forms.CharField( + # widget=ClearableTextarea(attrs={"maxlength": 1000, "rows": 5, "cols": 80}), + # required=False, + #) + css = { "all": ["css/widgets.css"], } @@ -367,7 +375,7 @@ class CaseForm(ModelForm): "open_access", "previous_case", "diagnostic_certainty", - "cimar_uuid" + "cimar_uuid", ), ) @@ -410,9 +418,11 @@ class CaseForm(ModelForm): # (False, 'No')]) # "findings": TinyMCE(attrs={"cols": 80, "rows": 20}), # "mark_scheme": TinyMCE(attrs={"cols": 80, "rows": 30}), - "description": Textarea(attrs={"cols": 80, "rows": 5}), - "history": Textarea(attrs={"cols": 80, "rows": 5}), - "discussion": Textarea(attrs={"cols": 80, "rows": 5}), + "title": ClearableInput(attrs={"rows": 1}), + "description": ClearableTextarea(attrs={"cols": 80, "rows": 5}), + "history": ClearableTextarea(attrs={"maxlength": 2000, "rows": 5}), + "discussion": ClearableTextarea(attrs={"maxlength": 2000, "rows": 5}), + "report": ClearableTextarea(attrs={"maxlength": 2000, "rows": 5}), "condition": autocomplete.ModelSelect2Multiple( url="atlas:condition-autocomplete" ), diff --git a/atlas/models.py b/atlas/models.py index d5b354ef..54b7ae52 100644 --- a/atlas/models.py +++ b/atlas/models.py @@ -774,6 +774,21 @@ class Series(SeriesBase): def get_link_headers(self): return "atlas/series_headers.html" + def get_related_findings(self): + """Returns the related findings for the series, including findings from other series in the same case.""" + # Get findings directly related to this series + findings = list(self.findings.all()) + + # Get findings from other series in the same case + if hasattr(self, "case") and self.case.exists(): + for case in self.case.all(): + for other_series in case.series.exclude(pk=self.pk): + findings.extend(other_series.findings.all()) + + # Remove duplicates (by pk) + unique_findings = {f.pk: f for f in findings}.values() + return list(unique_findings) + class CaseCollection(ExamOrCollectionGenericBase): diff --git a/atlas/templates/atlas/case_display_block.html b/atlas/templates/atlas/case_display_block.html index 0654b2c2..1f92c72c 100755 --- a/atlas/templates/atlas/case_display_block.html +++ b/atlas/templates/atlas/case_display_block.html @@ -214,8 +214,6 @@ > - - diff --git a/atlas/templates/atlas/case_form.html b/atlas/templates/atlas/case_form.html index 3f947d82..0207334d 100755 --- a/atlas/templates/atlas/case_form.html +++ b/atlas/templates/atlas/case_form.html @@ -51,8 +51,70 @@ $('#add_more_resource').click(() => { add_resource_input_form() }); }) - +{% comment %} document.addEventListener("DOMContentLoaded", function () { + // Select all text input fields and textareas + const textFields = document.querySelectorAll("input[type='text'], textarea"); + textFields.forEach((field) => { + // Create a clear button for each text field or textarea + const clearButton = document.createElement("button"); + clearButton.type = "button"; + clearButton.className = "btn btn-danger btn-sm clear-input-button"; + clearButton.textContent = "×"; + + // Wrap the input field or textarea in a container with position-relative + const wrapper = document.createElement("div"); + wrapper.className = "position-relative"; + field.parentNode.insertBefore(wrapper, field); + wrapper.appendChild(field); + wrapper.appendChild(clearButton); + + // Function to toggle the visibility of the clear button + const toggleClearButton = () => { + if (field.value.trim() !== "") { + clearButton.classList.add("show"); // Add the "show" class + } else { + clearButton.classList.remove("show"); // Remove the "show" class + } + }; + + // Initial check + toggleClearButton(); + + // Add event listeners to monitor changes in the input field or textarea + field.addEventListener("input", toggleClearButton); + + // Add click event to clear the field or textarea + clearButton.addEventListener("click", function () { + field.value = ""; // Clear the field + toggleClearButton(); // Update the button visibility + }); + }); +}); + {% endcomment %} + +{% comment %} {% endcomment %} {% endblock %} {% block content %} diff --git a/atlas/templates/atlas/series_finding_related.html b/atlas/templates/atlas/series_finding_related.html new file mode 100644 index 00000000..081d7b67 --- /dev/null +++ b/atlas/templates/atlas/series_finding_related.html @@ -0,0 +1,124 @@ + + \ No newline at end of file diff --git a/atlas/templates/atlas/series_viewer.html b/atlas/templates/atlas/series_viewer.html index fa404026..799046b3 100755 --- a/atlas/templates/atlas/series_viewer.html +++ b/atlas/templates/atlas/series_viewer.html @@ -47,6 +47,13 @@ {% if editing_finding < 1 %} {% endif %} +
@@ -182,6 +189,16 @@ {% endif %} + +