From 29165811d120de4cddb877a94371bc21d334d260 Mon Sep 17 00:00:00 2001 From: Ross Date: Mon, 27 Apr 2026 22:41:16 +0100 Subject: [PATCH] feat: Enhance linked cases overview with detailed case information and navigation, and add linked case card partial --- atlas/templates/atlas/case_display_block.html | 116 ++++++++++++-- .../atlas/linked_cases_overview.html | 145 ++++++++++++++---- .../atlas/partials/_linked_case_card.html | 116 ++++++++++++++ atlas/views.py | 11 +- 4 files changed, 341 insertions(+), 47 deletions(-) create mode 100644 atlas/templates/atlas/partials/_linked_case_card.html diff --git a/atlas/templates/atlas/case_display_block.html b/atlas/templates/atlas/case_display_block.html index 782e7d7f..f7180230 100755 --- a/atlas/templates/atlas/case_display_block.html +++ b/atlas/templates/atlas/case_display_block.html @@ -493,6 +493,60 @@ Move selected series + {% else %}
Case move options are unavailable in this view.
{% endif %} @@ -881,12 +935,15 @@ }); // On submit, update the hidden inputs to match the new order - document.getElementById("reorder-series-form").addEventListener("submit", function(e) { - const lis = document.querynelectorAll("#series-reorder-list li"); - lis.forEach((li, idx) => { - li.querySelector("input[name='series_order']").setAttribute("name", "series_order_" + idx); + const reorderSeriesForm = document.getElementById("reorder-series-form"); + if (reorderSeriesForm) { + reorderSeriesForm.addEventListener("submit", function(e) { + const lis = document.querySelectorAll("#series-reorder-list li"); + lis.forEach((li, idx) => { + li.querySelector("input[name='series_order']").setAttribute("name", "series_order_" + idx); + }); }); - }); + } // Drag and drop reordering for series list const reorderList = document.getElementById("series-reorder-list"); @@ -1035,6 +1092,8 @@ if (moveSeriesForm) { const hiddenInput = moveSeriesForm.querySelector("[name='destination_case']"); const moveSubmitButton = document.getElementById("move-selected-series-submit"); + const moveSearchResults = document.getElementById("move-series-case-search-results"); + const selectionLabel = document.getElementById("selected-move-case"); function syncMoveSeriesSubmitState() { if (!moveSubmitButton || !hiddenInput) { @@ -1043,22 +1102,51 @@ moveSubmitButton.disabled = !hiddenInput.value; } + function setSelectedDestination(casePk, caseTitle) { + if (!hiddenInput || !casePk) { + return; + } + + hiddenInput.value = casePk; + if (selectionLabel) { + selectionLabel.innerHTML = "Selected destination: " + caseTitle + " (" + casePk + ")"; + selectionLabel.classList.remove("text-muted"); + } + + if (moveSearchResults) { + moveSearchResults.querySelectorAll(".list-group-item").forEach((item) => { + item.classList.toggle("active", String(item.getAttribute("data-case-pk")) === String(casePk)); + }); + } + + syncMoveSeriesSubmitState(); + } + + if (moveSearchResults) { + moveSearchResults.addEventListener("click", function(event) { + const item = event.target.closest(".list-group-item[data-case-pk]"); + if (!item || !moveSearchResults.contains(item)) { + return; + } + if (event.target.closest("a,button,form,input")) { + return; + } + + const casePk = item.getAttribute("data-case-pk"); + const titleEl = item.querySelector("h6"); + const caseTitle = titleEl ? titleEl.textContent.trim() : ("Case #" + casePk); + setSelectedDestination(casePk, caseTitle); + }); + } + moveSeriesForm.addEventListener("case:selected", function(event) { - const selectionLabel = document.getElementById("selected-move-case"); if (!hiddenInput || !event.detail) { return; } const casePk = event.detail.casePk; const caseTitle = event.detail.caseTitle || ("Case #" + casePk); - hiddenInput.value = casePk; - - if (selectionLabel) { - selectionLabel.innerHTML = "Selected destination: " + caseTitle + " (" + casePk + ")"; - selectionLabel.classList.remove("text-muted"); - } - - syncMoveSeriesSubmitState(); + setSelectedDestination(casePk, caseTitle); }); syncMoveSeriesSubmitState(); diff --git a/atlas/templates/atlas/linked_cases_overview.html b/atlas/templates/atlas/linked_cases_overview.html index a3f08ee9..ceab5bc3 100644 --- a/atlas/templates/atlas/linked_cases_overview.html +++ b/atlas/templates/atlas/linked_cases_overview.html @@ -1,38 +1,119 @@ - -{% extends "base.html" %} - - +{% extends "atlas/base.html" %} {% block content %} -

Linked Cases Overview

-

Current Case: {{ current_case.title }}

+
+
+
+

Linked Cases Overview

+

Review the full case chain around this study, with quick access to related cases and their linked series.

+
+
+ Open current case + {% if direct_previous %} + Previous in chain + {% endif %} + {% if direct_next %} + Next in chain + {% endif %} +
+
-

Previous Cases

- {% if previous_cases %} - - {% else %} -

No previous cases.

- {% endif %} +
+ {% if current_case.open_access %} + Open access + {% else %} + Restricted + {% endif %} + {% if current_case.verified %} + Verified + {% endif %} + {% if current_case.archive %} + Archived + {% endif %} + {% if current_case.diagnostic_certainty %} + {{ current_case.get_diagnostic_certainty_display }} + {% endif %} + {{ current_case.get_ordered_series|length }} series +
+
+ + +
+
+
+

Chain Summary

+ +
+
+
+ -

Next Cases

- {% if next_cases %} - - {% else %} -

No next cases.

- {% endif %} +
+
+
+

Previous Cases

+ {{ previous_cases|length }} +
+ {% if previous_cases %} +
+ {% for linked_case in previous_cases %} + {% include "atlas/partials/_linked_case_card.html" with relation_label="Earlier in the linked chain" is_current=False %} + {% endfor %} +
+ {% else %} +
+
No previous linked cases.
+
+ {% endif %} +
+ +
+
+

Next Cases

+ {{ next_cases|length }} +
+ {% if next_cases %} +
+ {% for linked_case in next_cases %} + {% include "atlas/partials/_linked_case_card.html" with relation_label="Later in the linked chain" is_current=False %} + {% endfor %} +
+ {% else %} +
+
No follow-up linked cases.
+
+ {% endif %} +
+
+ {% endblock %} \ No newline at end of file diff --git a/atlas/templates/atlas/partials/_linked_case_card.html b/atlas/templates/atlas/partials/_linked_case_card.html new file mode 100644 index 00000000..fe2ef9f4 --- /dev/null +++ b/atlas/templates/atlas/partials/_linked_case_card.html @@ -0,0 +1,116 @@ +
+
+
+
+
+ Case {{ linked_case.pk }} + {% if is_current %} + Current + {% endif %} + {% if linked_case.open_access %} + Open access + {% else %} + Restricted + {% endif %} + {% if linked_case.verified %} + Verified + {% endif %} + {% if linked_case.archive %} + Archived + {% endif %} +
+

+ + {{ linked_case.title|default:"Untitled case" }} + +

+ {% if relation_label %} +
{{ relation_label }}
+ {% endif %} +
+
+ {% if linked_case.published_date %} +
Published {{ linked_case.published_date|date:"Y-m-d" }}
+ {% else %} +
Created {{ linked_case.created_date|date:"Y-m-d" }}
+ {% endif %} + {% if linked_case.diagnostic_certainty %} +
Certainty: {{ linked_case.get_diagnostic_certainty_display }}
+ {% endif %} +
+
+ + {% if linked_case.description %} +

{{ linked_case.description|striptags|truncatechars:220 }}

+ {% elif linked_case.history %} +

{{ linked_case.history|striptags|truncatechars:220 }}

+ {% endif %} + +
+ {% if linked_case.subspecialty.all %} +
+ Subspecialty + {% for item in linked_case.subspecialty.all %} + {{ item }} + {% empty %}{% endfor %} +
+ {% endif %} + {% if linked_case.condition.all %} +
+ Condition + {% for item in linked_case.condition.all|slice:":4" %} + {{ item }} + {% endfor %} + {% if linked_case.condition.count > 4 %} + +{{ linked_case.condition.count|add:-4 }} more + {% endif %} +
+ {% endif %} + {% if linked_case.presentation.all %} +
+ Presentation + {% for item in linked_case.presentation.all|slice:":4" %} + {{ item }} + {% endfor %} + {% if linked_case.presentation.count > 4 %} + +{{ linked_case.presentation.count|add:-4 }} more + {% endif %} +
+ {% endif %} +
+ +
+
+ Series + {{ linked_case.get_ordered_series|length }} linked +
+ {% with ordered_series=linked_case.get_ordered_series %} + {% if ordered_series %} +
+ {% for series in ordered_series|slice:":4" %} +
+
+ + {{ series.description|default:series.examination }} + +
{{ series.modality }}{% if series.examination %} · {{ series.examination }}{% endif %}{% if series.plane %} · {{ series.plane }}{% endif %}
+
+ #{{ forloop.counter }} +
+ {% endfor %} +
+ {% if ordered_series|length > 4 %} +
Showing 4 of {{ ordered_series|length }} linked series.
+ {% endif %} + {% else %} +
No series linked to this case.
+ {% endif %} + {% endwith %} +
+ + +
+
\ No newline at end of file diff --git a/atlas/views.py b/atlas/views.py index 373ef86a..f865af6d 100755 --- a/atlas/views.py +++ b/atlas/views.py @@ -7289,7 +7289,7 @@ def reorder_series(request, case_pk): def linked_cases_overview(request, case_id): """ - View to display an overview of linked cases via the previous_case field. + Display the linked-case chain around a case, including prior and follow-up cases. """ # Get the current case case = get_object_or_404(Case, pk=case_id) @@ -7314,6 +7314,10 @@ def linked_cases_overview(request, case_id): except ObjectDoesNotExist: pass + chain_cases = previous_cases + [case] + next_cases + direct_previous = previous_cases[-1] if previous_cases else None + direct_next = next_cases[0] if next_cases else None + # Render the template with the linked cases return render( request, @@ -7322,6 +7326,11 @@ def linked_cases_overview(request, case_id): "current_case": case, "previous_cases": previous_cases, "next_cases": next_cases, + "chain_cases": chain_cases, + "chain_total": len(chain_cases), + "current_index": len(previous_cases) + 1, + "direct_previous": direct_previous, + "direct_next": direct_next, }, )