Imrpvoe the case links

This commit is contained in:
Ross
2026-06-12 22:55:50 +01:00
parent 96d5fa4a2e
commit aaee3577ad
4 changed files with 260 additions and 13 deletions
+42 -13
View File
@@ -426,21 +426,40 @@
</div>
</h2>
{% if case.previous_case or case.next_case %}
<div class="alert alert-info" role="alert">
This case is part of a <a href='{% url "atlas:linked_cases_overview" case.pk %}'>series.</a><br/>
<div class="row">
{% if case.previous_case %}
<div class="col-sm-6 mb-3 mb-sm-0">
<h5 class="card-title">Previous Case</h5>
{{ case.previous_case.get_link }}
<div class="card border-info bg-info-subtle mb-3">
<div class="card-body p-3">
<div class="d-flex justify-content-between align-items-center border-bottom border-info-subtle pb-2 mb-2">
<span class="fw-semibold text-info-emphasis"><i class="bi bi-layers-half me-1"></i> Part of a Case Series</span>
<a href='{% url "atlas:linked_cases_overview" case.pk %}' class="btn btn-sm btn-outline-info text-decoration-none py-0 px-2" style="font-size: 0.8rem;">View Series Overview</a>
</div>
<div class="row text-center text-sm-start g-2">
<div class="col-6 border-end border-info-subtle d-flex flex-column justify-content-center">
{% if case.previous_case %}
<div>
<a href="{{ case.previous_case.get_absolute_url }}" class="text-decoration-none fw-semibold link-info" data-bs-toggle="tooltip" data-bs-placement="bottom" data-bs-delay='{"show":500,"hide":100}' title="{{ case.previous_case.description|striptags|default:'No description' }}">
<i class="bi bi-arrow-left me-1"></i> Previous Case: {{ case.previous_case.title|default:'Untitled Case' }}
</a>
</div>
{% else %}
<div class="small text-muted italic">
<i class="bi bi-arrow-left me-1"></i> No previous case
</div>
{% endif %}
</div>
{% endif %}
{% if case.next_case %}
<div class="col-sm-6">
<h5 class="card-title">Next Case</h5>
{{ case.next_case.get_link }}
<div class="col-6 d-flex flex-column justify-content-center text-sm-end align-items-sm-end">
{% if case.next_case %}
<div>
<a href="{{ case.next_case.get_absolute_url }}" class="text-decoration-none fw-semibold link-info" data-bs-toggle="tooltip" data-bs-placement="bottom" data-bs-delay='{"show":500,"hide":100}' title="{{ case.next_case.description|striptags|default:'No description' }}">
Next Case: {{ case.next_case.title|default:'Untitled Case' }} <i class="bi bi-arrow-right ms-1"></i>
</a>
</div>
{% else %}
<div class="small text-muted italic">
No follow-up case <i class="bi bi-arrow-right ms-1"></i>
</div>
{% endif %}
</div>
{% endif %}
</div>
</div>
</div>
{% endif %}
@@ -1123,6 +1142,16 @@
}
document.addEventListener("DOMContentLoaded", function () {
// Initialize tooltips
function initAlertTooltips() {
document.querySelectorAll('[data-bs-toggle="tooltip"]').forEach(function(el) {
try { new bootstrap.Tooltip(el); } catch (e) {}
});
}
initAlertTooltips();
document.body.addEventListener('htmx:afterSwap', function() {
initAlertTooltips();
});
const dicomDetails = document.getElementById("dicom-viewer-details");
let dicomViewerLoaded = false;
@@ -28,6 +28,12 @@
{% if show_study_dates_editor %}
<a class="btn btn-outline-secondary" href="{% url 'atlas:linked_cases_overview' current_case.pk %}">Hide study dates editor</a>
{% endif %}
{% if can_edit_chain and not show_reorder_editor %}
<a class="btn btn-outline-warning" href="{% url 'atlas:linked_cases_overview' current_case.pk %}?show_reorder=1">Open case reorder editor</a>
{% endif %}
{% if show_reorder_editor %}
<a class="btn btn-outline-secondary" href="{% url 'atlas:linked_cases_overview' current_case.pk %}">Hide case reorder editor</a>
{% endif %}
</div>
</div>
@@ -104,6 +110,39 @@
</div>
{% endif %}
{% if can_edit_chain and show_reorder_editor %}
<div class="card border-warning-subtle shadow-sm mb-4">
<div class="card-body">
<h2 class="h5 mb-1 text-warning-emphasis">Reorder Cases</h2>
<p class="text-muted mb-3">Drag and drop cases to reorder them, or use the Up/Down buttons, then click Save.</p>
<form method="post" id="reorder-cases-form">
{% csrf_token %}
<input type="hidden" name="form_action" value="reorder_cases">
<ul id="cases-reorder-list" class="list-group mb-3">
{% for linked_case in chain_cases %}
<li class="list-group-item d-flex justify-content-between align-items-center {% if linked_case.pk == current_case.pk %}border-primary{% endif %}" data-case-id="{{ linked_case.pk }}">
<span>
<i class="bi bi-grip-vertical me-2 text-muted drag-handle" style="cursor: grab;"></i>
<span class="fw-semibold">Case {{ linked_case.pk }}:</span> {{ linked_case.title|default:"Untitled case" }}
{% if linked_case.pk == current_case.pk %}<span class="badge bg-primary ms-1">Current</span>{% endif %}
</span>
<span>
<button type="button" class="btn btn-sm btn-outline-secondary move-up"></button>
<button type="button" class="btn btn-sm btn-outline-secondary move-down"></button>
</span>
<input type="hidden" name="case_order" value="{{ linked_case.pk }}">
</li>
{% endfor %}
</ul>
<div class="d-flex justify-content-end">
<button type="submit" class="btn btn-warning">Save Case Order</button>
</div>
</form>
</div>
</div>
{% endif %}
{% if show_case_editor and case_editor_formset %}
<div class="card border-info-subtle shadow-sm mb-4">
<div class="card-body">
@@ -273,4 +312,92 @@
</div>
</div>
</div>
{% endblock %}
{% block css %}
<style>
#cases-reorder-list .dragging {
opacity: 0.55;
box-shadow: 0 4px 16px rgba(255, 193, 7, 0.35);
}
#cases-reorder-list .drag-over {
border: 2px dashed #ffc107 !important;
}
</style>
{% endblock %}
{% block js %}
<script>
document.addEventListener("DOMContentLoaded", function() {
// Move up/down logic
document.querySelectorAll("#cases-reorder-list .move-up, #cases-reorder-list .move-down").forEach(function(btn) {
btn.addEventListener("click", function(e) {
e.preventDefault();
const li = btn.closest("li");
if (btn.classList.contains("move-up") && li.previousElementSibling) {
li.parentNode.insertBefore(li, li.previousElementSibling);
}
if (btn.classList.contains("move-down") && li.nextElementSibling) {
li.parentNode.insertBefore(li.nextElementSibling, li);
}
});
});
// On submit, update the hidden inputs to match the new order
const reorderCasesForm = document.getElementById("reorder-cases-form");
if (reorderCasesForm) {
reorderCasesForm.addEventListener("submit", function(e) {
const lis = document.querySelectorAll("#cases-reorder-list li");
lis.forEach((li, idx) => {
const input = li.querySelector("input[name='case_order']");
if (input) {
input.setAttribute("name", "case_order_" + idx);
}
});
});
}
// Drag and drop reordering for cases list
const reorderList = document.getElementById("cases-reorder-list");
let draggedItem = null;
if (reorderList) {
reorderList.querySelectorAll("li").forEach(function(li) {
li.setAttribute("draggable", "true");
li.addEventListener("dragstart", function(e) {
draggedItem = li;
li.classList.add("dragging");
e.dataTransfer.effectAllowed = "move";
});
li.addEventListener("dragend", function() {
draggedItem = null;
li.classList.remove("dragging");
reorderList.querySelectorAll("li").forEach(el => el.classList.remove("drag-over"));
});
li.addEventListener("dragover", function(e) {
e.preventDefault();
e.dataTransfer.dropEffect = "move";
li.classList.add("drag-over");
});
li.addEventListener("dragleave", function() {
li.classList.remove("drag-over");
});
li.addEventListener("drop", function(e) {
e.preventDefault();
li.classList.remove("drag-over");
if (draggedItem && draggedItem !== li) {
const rect = li.getBoundingClientRect();
const next = (e.clientY - rect.top) / (rect.bottom - rect.top) > 0.5;
li.parentNode.insertBefore(draggedItem, next ? li.nextElementSibling : li);
}
});
});
}
});
</script>
{% endblock %}