feat: Add functionality to move selected series between cases with a new form and UI enhancements
This commit is contained in:
@@ -417,50 +417,80 @@
|
||||
Manage Series
|
||||
</button>
|
||||
</summary>
|
||||
<a href="{% url 'atlas:series_id_create' pk=case.pk %}">Create and add new series</a><br />
|
||||
<button class="btn btn-sm btn-outline-secondary mb-1" hx-get="{% url 'atlas:case_order_dicom' pk=case.pk %}"
|
||||
title="order dicom by slice location"
|
||||
hx-target="#series-action-results"
|
||||
hx-swap="innerHTML"
|
||||
hx-confirm="This will reorder all case series based upon slice location"
|
||||
>
|
||||
Order dicoms by slice location
|
||||
</button>
|
||||
<button class="btn btn-sm btn-outline-primary mb-1" hx-post="{% url 'atlas:combine_series' %}"
|
||||
title="merge series"
|
||||
hx-include="[name='series-ids']"
|
||||
hx-target="#series-action-results"
|
||||
hx-swap="innerHTML"
|
||||
hx-confirm="This will merge all selected series into one"
|
||||
>
|
||||
Merge selected series
|
||||
</button>
|
||||
<button class="btn btn-sm btn-outline-secondary mb-1" hx-post="{% url 'atlas:use_dates_as_descriptions' case.pk %}"
|
||||
title="merge series"
|
||||
hx-include="[name='series-ids']"
|
||||
hx-target="#series-action-results"
|
||||
hx-swap="innerHTML"
|
||||
hx-confirm="This will use the dicom date as the series description"
|
||||
>
|
||||
Use dates as description
|
||||
</button>
|
||||
<button hx-post="{% url 'atlas:remove_selected_series_from_case' case.pk %}"
|
||||
title="remove selected series from this case"
|
||||
hx-include="[name='series-ids']:checked"
|
||||
hx-target="#series-action-results"
|
||||
hx-swap="innerHTML"
|
||||
hx-confirm="Are you sure you want to remove the selected series from this case? They will not be deleted from the database."
|
||||
class="btn btn-warning mt-2"
|
||||
type="button"
|
||||
>
|
||||
Remove selected series from case
|
||||
</button>
|
||||
<button type="button"
|
||||
class="btn btn-secondary mt-2"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#reorderSeriesModal">
|
||||
Reorder series
|
||||
</button>
|
||||
<div class="series-actions-body">
|
||||
<a class="btn btn-sm btn-outline-secondary text-start" href="{% url 'atlas:series_id_create' pk=case.pk %}">Create and add new series</a>
|
||||
|
||||
<div class="series-selection-toolbar d-flex flex-wrap gap-2">
|
||||
<button type="button" class="btn btn-sm btn-outline-primary" id="series-select-all-btn">Select all</button>
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary" id="series-deselect-all-btn">Deselect all</button>
|
||||
</div>
|
||||
|
||||
<div class="series-actions-grid d-flex flex-column gap-2">
|
||||
<button class="btn btn-sm btn-outline-secondary text-start" hx-get="{% url 'atlas:case_order_dicom' pk=case.pk %}"
|
||||
title="order dicom by slice location"
|
||||
hx-target="#series-action-results"
|
||||
hx-swap="innerHTML"
|
||||
hx-confirm="This will reorder all case series based upon slice location"
|
||||
>
|
||||
Order dicoms by slice location
|
||||
</button>
|
||||
<button class="btn btn-sm btn-outline-primary text-start" hx-post="{% url 'atlas:combine_series' %}"
|
||||
title="merge series"
|
||||
hx-include="[name='series-ids']"
|
||||
hx-target="#series-action-results"
|
||||
hx-swap="innerHTML"
|
||||
hx-confirm="This will merge all selected series into one"
|
||||
>
|
||||
Merge selected series
|
||||
</button>
|
||||
<button class="btn btn-sm btn-outline-secondary text-start" hx-post="{% url 'atlas:use_dates_as_descriptions' case.pk %}"
|
||||
title="use dicom dates as series descriptions"
|
||||
hx-include="[name='series-ids']"
|
||||
hx-target="#series-action-results"
|
||||
hx-swap="innerHTML"
|
||||
hx-confirm="This will use the dicom date as the series description"
|
||||
>
|
||||
Use dates as description
|
||||
</button>
|
||||
<button hx-post="{% url 'atlas:remove_selected_series_from_case' case.pk %}"
|
||||
title="remove selected series from this case"
|
||||
hx-include="[name='series-ids']:checked"
|
||||
hx-target="#series-action-results"
|
||||
hx-swap="innerHTML"
|
||||
hx-confirm="Are you sure you want to remove the selected series from this case? They will not be deleted from the database."
|
||||
class="btn btn-sm btn-warning text-start"
|
||||
type="button"
|
||||
>
|
||||
Remove selected series from case
|
||||
</button>
|
||||
<button type="button"
|
||||
class="btn btn-sm btn-secondary text-start"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#reorderSeriesModal">
|
||||
Reorder series
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="series-move-panel border rounded p-2">
|
||||
<div class="small fw-semibold mb-2">Move selected series to another case</div>
|
||||
{% if move_series_form %}
|
||||
<form id="move-selected-series-form" class="d-flex flex-column gap-2">
|
||||
{{ move_series_form.destination_case }}
|
||||
<button type="button"
|
||||
class="btn btn-sm btn-outline-primary text-start"
|
||||
hx-post="{% url 'atlas:move_selected_series_to_case' case.pk %}"
|
||||
hx-include="[name='series-ids']:checked, #move-selected-series-form [name='destination_case']"
|
||||
hx-target="#series-action-results"
|
||||
hx-swap="innerHTML"
|
||||
hx-confirm="Move selected series to the chosen destination case?">
|
||||
Move selected series
|
||||
</button>
|
||||
</form>
|
||||
{% else %}
|
||||
<div class="text-muted small">Case move options are unavailable in this view.</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="alert alert-info mt-2" id="series-action-alert" style="display:none;">
|
||||
<span id="series-action-results" ></span>
|
||||
</div>
|
||||
@@ -901,32 +931,80 @@
|
||||
|
||||
const detailsElement = document.getElementById("series-actions");
|
||||
const buttons = document.querySelectorAll(".select-series-btn");
|
||||
const seriesCheckboxes = document.querySelectorAll("input[type='checkbox'][name='series-ids']");
|
||||
const selectAllBtn = document.getElementById("series-select-all-btn");
|
||||
const deselectAllBtn = document.getElementById("series-deselect-all-btn");
|
||||
|
||||
function toggleCheckboxes() {
|
||||
const isOpen = detailsElement.hasAttribute("open");
|
||||
buttons.forEach(button => {
|
||||
const isOpen = detailsElement && detailsElement.hasAttribute("open");
|
||||
buttons.forEach((button) => {
|
||||
button.style.display = isOpen ? "inline-block" : "none";
|
||||
});
|
||||
}
|
||||
document.querySelectorAll(".select-series-btn").forEach(function(btn) {
|
||||
btn.addEventListener("click", function() {
|
||||
|
||||
function syncSeriesSelectionUi() {
|
||||
document.querySelectorAll(".select-series-btn").forEach(function(btn) {
|
||||
const parent = btn.closest(".series-block");
|
||||
if (!parent) {
|
||||
return;
|
||||
}
|
||||
const checkbox = parent.querySelector("input[type='checkbox'][name='series-ids']");
|
||||
checkbox.checked = !checkbox.checked;
|
||||
if (!checkbox) {
|
||||
return;
|
||||
}
|
||||
btn.classList.toggle("btn-primary", checkbox.checked);
|
||||
btn.classList.toggle("btn-outline-primary", !checkbox.checked);
|
||||
btn.textContent = checkbox.checked ? "Selected" : "Select";
|
||||
// Highlight the series-block when selected
|
||||
if (checkbox.checked) {
|
||||
parent.classList.add("highlight-series");
|
||||
} else {
|
||||
parent.classList.remove("highlight-series");
|
||||
}
|
||||
});
|
||||
|
||||
const selectedCount = Array.from(seriesCheckboxes).filter((checkbox) => checkbox.checked).length;
|
||||
if (selectAllBtn) {
|
||||
selectAllBtn.disabled = selectedCount === seriesCheckboxes.length && seriesCheckboxes.length > 0;
|
||||
}
|
||||
if (deselectAllBtn) {
|
||||
deselectAllBtn.disabled = selectedCount === 0;
|
||||
}
|
||||
}
|
||||
|
||||
document.querySelectorAll(".select-series-btn").forEach(function(btn) {
|
||||
btn.addEventListener("click", function() {
|
||||
const parent = btn.closest(".series-block");
|
||||
const checkbox = parent.querySelector("input[type='checkbox'][name='series-ids']");
|
||||
checkbox.checked = !checkbox.checked;
|
||||
syncSeriesSelectionUi();
|
||||
});
|
||||
});
|
||||
|
||||
seriesCheckboxes.forEach((checkbox) => {
|
||||
checkbox.addEventListener("change", syncSeriesSelectionUi);
|
||||
});
|
||||
|
||||
if (selectAllBtn) {
|
||||
selectAllBtn.addEventListener("click", function() {
|
||||
seriesCheckboxes.forEach((checkbox) => {
|
||||
checkbox.checked = true;
|
||||
});
|
||||
syncSeriesSelectionUi();
|
||||
});
|
||||
}
|
||||
|
||||
if (deselectAllBtn) {
|
||||
deselectAllBtn.addEventListener("click", function() {
|
||||
seriesCheckboxes.forEach((checkbox) => {
|
||||
checkbox.checked = false;
|
||||
});
|
||||
syncSeriesSelectionUi();
|
||||
});
|
||||
}
|
||||
|
||||
// Initial state
|
||||
toggleCheckboxes();
|
||||
syncSeriesSelectionUi();
|
||||
|
||||
|
||||
// Show the alert when a result is received
|
||||
@@ -942,7 +1020,9 @@
|
||||
observer.observe(resultsSpan, { childList: true, subtree: true });
|
||||
|
||||
// Listen for toggle events on the <details> element
|
||||
detailsElement.addEventListener("toggle", toggleCheckboxes);
|
||||
if (detailsElement) {
|
||||
detailsElement.addEventListener("toggle", toggleCheckboxes);
|
||||
}
|
||||
|
||||
const modal = new bootstrap.Modal(document.getElementById("findingModal"));
|
||||
const modalBody = document.getElementById("findingModalBody");
|
||||
@@ -1231,12 +1311,31 @@
|
||||
background: rgba(255,255,255,0.02);
|
||||
margin-top: .5rem;
|
||||
}
|
||||
.series-actions-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: .5rem;
|
||||
}
|
||||
.series-selection-toolbar {
|
||||
border-top: 1px solid rgba(0,0,0,0.08);
|
||||
border-bottom: 1px solid rgba(0,0,0,0.08);
|
||||
padding: .5rem 0;
|
||||
}
|
||||
.series-actions-grid .btn,
|
||||
.series-actions-body > a.btn,
|
||||
.series-move-panel .btn {
|
||||
width: 100%;
|
||||
}
|
||||
.series-move-panel {
|
||||
background: rgba(255,255,255,0.03);
|
||||
}
|
||||
.series-move-panel .case-details {
|
||||
margin-bottom: .5rem;
|
||||
}
|
||||
.series-actions summary { list-style: none; }
|
||||
.series-actions summary::-webkit-details-marker { display:none; }
|
||||
.series-actions .btn { vertical-align: middle; }
|
||||
.series-actions > a, .series-actions > button { width: 100%; text-align: left; }
|
||||
.series-actions > button.btn.btn-warning,
|
||||
.series-actions > button.btn.btn-secondary { width: auto; align-self: flex-start; }
|
||||
.series-actions .btn { text-align: left; }
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.series-actions { background: rgba(255,255,255,0.02); border-color: rgba(255,255,255,0.04); }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user