Enhance case inline editing and search functionality with improved UI and new subspecialty handling
This commit is contained in:
@@ -103,9 +103,9 @@
|
||||
{% endwith %}
|
||||
{% if can_edit %}
|
||||
<button class="btn btn-sm btn-link text-muted p-0 ms-2 quick-edit-button"
|
||||
hx-get="{% url 'atlas:case_inline_field' case.pk 'title' %}?edit=1&show_label=0"
|
||||
hx-target="#case-inline-title"
|
||||
hx-swap="innerHTML">quick edit</button>
|
||||
hx-get="{% url 'atlas:case_inline_field' case.pk 'title' %}?edit=1&show_label=0"
|
||||
hx-target="#case-inline-title"
|
||||
hx-swap="outerHTML">quick edit</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</h2>
|
||||
@@ -212,13 +212,23 @@
|
||||
|
||||
<div class="float-end text-end">
|
||||
{# Subspecialty badges (preserve any anchor returned by get_link) #}
|
||||
{% if case.subspecialty.all %}
|
||||
{% for sub in case.subspecialty.all %}
|
||||
<span class="badge bg-secondary ms-1">{{ sub.get_link|safe }}</span>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<span class="text-muted me-2">No subspecialty</span>
|
||||
{% endif %}
|
||||
<div class="quick-edit-wrap d-inline-block align-middle">
|
||||
<span id="case-inline-subspecialty">
|
||||
{% if case.subspecialty.all %}
|
||||
{% for sub in case.subspecialty.all %}
|
||||
<span class="badge bg-secondary ms-1">{{ sub.get_link|safe }}</span>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<span class="text-muted me-2">No subspecialty</span>
|
||||
{% endif %}
|
||||
</span>
|
||||
{% if can_edit %}
|
||||
<button class="btn btn-sm btn-link text-muted p-0 ms-2 quick-edit-button"
|
||||
hx-get="{% url 'atlas:case_inline_field' case.pk 'subspecialty' %}?edit=1&show_label=0"
|
||||
hx-target="#case-inline-subspecialty"
|
||||
hx-swap="outerHTML">quick edit</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{# Pathological process badges #}
|
||||
{% if case.pathological_process.all %}
|
||||
@@ -232,9 +242,9 @@
|
||||
<span>Diagnostic certainty:</span>
|
||||
{% if can_edit %}
|
||||
<button class="btn btn-sm btn-link text-muted p-0 ms-2 quick-edit-button"
|
||||
hx-get="{% url 'atlas:case_inline_field' case.pk 'diagnostic_certainty' %}?edit=1&show_label=0"
|
||||
hx-target="#case-inline-diagnostic_certainty"
|
||||
hx-swap="innerHTML">quick edit</button>
|
||||
hx-get="{% url 'atlas:case_inline_field' case.pk 'diagnostic_certainty' %}?edit=1&show_label=0"
|
||||
hx-target="#case-inline-diagnostic_certainty"
|
||||
hx-swap="outerHTML">quick edit</button>
|
||||
{% endif %}
|
||||
{% url 'atlas:case_inline_field' case.pk 'diagnostic_certainty' as diag_url %}
|
||||
{% with endpoint_default=diag_url|add:"?show_label=0" endpoint_edit=diag_url|add:"?edit=1&show_label=0" %}
|
||||
@@ -268,9 +278,9 @@
|
||||
</div>
|
||||
{% if can_edit %}
|
||||
<button class="btn btn-sm btn-link text-muted p-0 ms-2 quick-edit-button"
|
||||
hx-get="{% url 'atlas:case_inline_field' case.pk 'description' %}?edit=1&show_label=0"
|
||||
hx-target="#case-inline-description"
|
||||
hx-swap="innerHTML">quick edit</button>
|
||||
hx-get="{% url 'atlas:case_inline_field' case.pk 'description' %}?edit=1&show_label=0"
|
||||
hx-target="#case-inline-description"
|
||||
hx-swap="outerHTML">quick edit</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
@@ -288,19 +298,17 @@
|
||||
<div class="card h-100">
|
||||
<div class="card-header d-flex justify-content-between align-items-center quick-edit-wrap">History
|
||||
{% if can_edit %}
|
||||
<button class="btn btn-sm btn-link text-muted p-0 quick-edit-button"
|
||||
<button class="btn btn-sm btn-link text-muted p-0 quick-edit-button"
|
||||
hx-get="{% url 'atlas:case_inline_field' case.pk 'history' %}?edit=1&show_label=0"
|
||||
hx-target="#case-inline-history"
|
||||
hx-swap="innerHTML">quick edit</button>
|
||||
hx-swap="outerHTML">quick edit</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div id="case-inline-history"
|
||||
hx-get="{% url 'atlas:case_inline_field' case.pk 'history' %}?show_label=0"
|
||||
hx-trigger="load"
|
||||
hx-swap="innerHTML">
|
||||
<div class="pre-whitespace">{{ case.history|linebreaks }}</div>
|
||||
</div>
|
||||
{% url 'atlas:case_inline_field' case.pk 'history' as history_url %}
|
||||
{% with endpoint_default=history_url|add:"?show_label=0" endpoint_edit=history_url|add:"?edit=1&show_label=0" %}
|
||||
{% include 'atlas/partials/case_inline_field.html' with show_label=0 editing=False field_label="History" can_edit=can_edit endpoint_url_default=endpoint_default endpoint_url_edit=endpoint_edit container_id='case-inline-history' field_kind='textarea' value=case.history display_value=case.history field_name='history' error_message='' %}
|
||||
{% endwith %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -308,19 +316,17 @@
|
||||
<div class="card h-100">
|
||||
<div class="card-header d-flex justify-content-between align-items-center quick-edit-wrap">Discussion
|
||||
{% if can_edit %}
|
||||
<button class="btn btn-sm btn-link text-muted p-0 quick-edit-button"
|
||||
<button class="btn btn-sm btn-link text-muted p-0 quick-edit-button"
|
||||
hx-get="{% url 'atlas:case_inline_field' case.pk 'discussion' %}?edit=1&show_label=0"
|
||||
hx-target="#case-inline-discussion"
|
||||
hx-swap="innerHTML">quick edit</button>
|
||||
hx-swap="outerHTML">quick edit</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div id="case-inline-discussion"
|
||||
hx-get="{% url 'atlas:case_inline_field' case.pk 'discussion' %}?show_label=0"
|
||||
hx-trigger="load"
|
||||
hx-swap="innerHTML">
|
||||
<div class="pre-whitespace">{{ case.discussion|linebreaks }}</div>
|
||||
</div>
|
||||
{% url 'atlas:case_inline_field' case.pk 'discussion' as discussion_url %}
|
||||
{% with endpoint_default=discussion_url|add:"?show_label=0" endpoint_edit=discussion_url|add:"?edit=1&show_label=0" %}
|
||||
{% include 'atlas/partials/case_inline_field.html' with show_label=0 editing=False field_label="Discussion" can_edit=can_edit endpoint_url_default=endpoint_default endpoint_url_edit=endpoint_edit container_id='case-inline-discussion' field_kind='textarea' value=case.discussion display_value=case.discussion field_name='discussion' error_message='' %}
|
||||
{% endwith %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -328,19 +334,17 @@
|
||||
<div class="card h-100">
|
||||
<div class="card-header d-flex justify-content-between align-items-center quick-edit-wrap">Report
|
||||
{% if can_edit %}
|
||||
<button class="btn btn-sm btn-link text-muted p-0 quick-edit-button"
|
||||
<button class="btn btn-sm btn-link text-muted p-0 quick-edit-button"
|
||||
hx-get="{% url 'atlas:case_inline_field' case.pk 'report' %}?edit=1&show_label=0"
|
||||
hx-target="#case-inline-report"
|
||||
hx-swap="innerHTML">quick edit</button>
|
||||
hx-swap="outerHTML">quick edit</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div id="case-inline-report"
|
||||
hx-get="{% url 'atlas:case_inline_field' case.pk 'report' %}?show_label=0"
|
||||
hx-trigger="load"
|
||||
hx-swap="innerHTML">
|
||||
<div class="pre-whitespace">{{ case.report|linebreaks }}</div>
|
||||
</div>
|
||||
{% url 'atlas:case_inline_field' case.pk 'report' as report_url %}
|
||||
{% with endpoint_default=report_url|add:"?show_label=0" endpoint_edit=report_url|add:"?edit=1&show_label=0" %}
|
||||
{% include 'atlas/partials/case_inline_field.html' with show_label=0 editing=False field_label="Report" can_edit=can_edit endpoint_url_default=endpoint_default endpoint_url_edit=endpoint_edit container_id='case-inline-report' field_kind='textarea' value=case.report display_value=case.report field_name='report' error_message='' %}
|
||||
{% endwith %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
{# Unified single case item used by search results and recent-created lists #}
|
||||
<div class="list-group-item d-flex justify-content-between align-items-center" data-case-pk="{{ case.pk }}">
|
||||
<div class="flex-fill me-3">
|
||||
<div class="d-flex w-100 justify-content-between">
|
||||
<h6 class="mb-1">{{ case.title }}</h6>
|
||||
<small class="text-muted">{{ case.created_date|date:"Y-m-d" }}</small>
|
||||
</div>
|
||||
<div class="mb-1">
|
||||
<small class="text-muted me-2">{% if case.author.all %}By {{ case.author.all|join:", " }}{% endif %}</small>
|
||||
{% for cond in case.condition.all %}
|
||||
{% if cond.name %}<span class="badge bg-secondary me-1" title="Condition: {{ cond.name }}">{{ cond.name }}</span>{% endif %}
|
||||
{% endfor %}
|
||||
{% for pres in case.presentation.all %}
|
||||
{% if pres.name %}<span class="badge bg-secondary me-1" title="Presentation: {{ pres.name }}">{{ pres.name }}</span>{% endif %}
|
||||
{% endfor %}
|
||||
{% for ss in case.subspecialty.all %}
|
||||
{% if ss.name %}<span class="badge bg-secondary" title="Subspecialty: {{ ss.name }}">{{ ss.name }}</span>{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
<p class="mb-1 text-truncate">{% if case.description %}{{ case.description|truncatechars:140 }}{% endif %}</p>
|
||||
</div>
|
||||
<div class="ms-2 d-flex align-items-center">
|
||||
{% if collection %}
|
||||
<form class="m-0" hx-post="{% url 'atlas:add_case_to_collection' collection.pk %}" hx-target="#full-question-list" hx-swap="beforeend">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="case" value="{{ case.pk }}">
|
||||
<button type="submit" class="btn btn-sm btn-outline-primary">Add</button>
|
||||
</form>
|
||||
<a class="btn btn-sm btn-outline-secondary ms-2" href="{% url 'atlas:case_detail' case.pk %}" target="_blank" rel="noopener">View</a>
|
||||
{% else %}
|
||||
<a class="btn btn-sm btn-outline-secondary" href="{% url 'atlas:case_displaysets' case.pk %}">View</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,24 +1,16 @@
|
||||
<div class="case-inline-field">
|
||||
<div id="{{ container_id }}" class="case-inline-field">
|
||||
{% if show_label or editing %}
|
||||
<div class="d-flex {% if show_label %}justify-content-between{% else %}justify-content-end{% endif %} align-items-start gap-2">
|
||||
{% if show_label %}
|
||||
<strong>{{ field_label }}:</strong>
|
||||
{% endif %}
|
||||
{% if can_edit %}
|
||||
{% if editing %}
|
||||
<button class="btn btn-sm btn-outline-secondary"
|
||||
type="button"
|
||||
hx-get="{{ endpoint_url_default }}"
|
||||
hx-target="#{{ container_id }}"
|
||||
hx-swap="innerHTML">
|
||||
Cancel
|
||||
</button>
|
||||
{% else %}
|
||||
{% if not editing %}
|
||||
<button class="quick-edit-trigger quick-edit-button"
|
||||
type="button"
|
||||
hx-get="{{ endpoint_url_edit }}"
|
||||
hx-target="#{{ container_id }}"
|
||||
hx-swap="innerHTML">
|
||||
hx-swap="outerHTML">
|
||||
quick edit
|
||||
</button>
|
||||
{% endif %}
|
||||
@@ -27,17 +19,21 @@
|
||||
{% endif %}
|
||||
|
||||
{% if editing %}
|
||||
<form hx-post="{{ endpoint_url_default }}"
|
||||
<form hx-post="{{ endpoint_url_default }}"
|
||||
hx-target="#{{ container_id }}"
|
||||
hx-swap="innerHTML"
|
||||
hx-swap="outerHTML"
|
||||
class="mt-2">
|
||||
{% csrf_token %}
|
||||
{% if field_kind == "textarea" %}
|
||||
<textarea class="form-control" name="value" rows="4">{{ value|default_if_none:"" }}</textarea>
|
||||
{% elif field_kind == "select" %}
|
||||
<select class="form-select" name="value">
|
||||
<select class="form-select" name="value" {% if multiple %}multiple{% endif %}>
|
||||
{% for option_value, option_label in select_choices %}
|
||||
<option value="{{ option_value }}" {% if value|stringformat:"s" == option_value %}selected{% endif %}>{{ option_label }}</option>
|
||||
{% if multiple %}
|
||||
<option value="{{ option_value }}" {% if option_value|stringformat:"s" in selected_values %}selected{% endif %}>{{ option_label }}</option>
|
||||
{% else %}
|
||||
<option value="{{ option_value }}" {% if value|stringformat:"s" == option_value %}selected{% endif %}>{{ option_label }}</option>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</select>
|
||||
{% else %}
|
||||
@@ -64,8 +60,12 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="mt-2">
|
||||
<div class="mt-2 d-flex gap-2">
|
||||
<button class="btn btn-sm btn-primary" type="submit">Save</button>
|
||||
<button class="btn btn-sm btn-outline-secondary" type="button"
|
||||
hx-get="{{ endpoint_url_default }}"
|
||||
hx-target="#{{ container_id }}"
|
||||
hx-swap="outerHTML">Cancel</button>
|
||||
</div>
|
||||
</form>
|
||||
{% else %}
|
||||
|
||||
@@ -2,38 +2,7 @@
|
||||
{% if cases and cases|length > 0 %}
|
||||
<div class="list-group mb-2">
|
||||
{% for case in cases %}
|
||||
<div class="list-group-item d-flex justify-content-between align-items-center" data-case-pk="{{ case.pk }}">
|
||||
<div class="flex-fill me-3">
|
||||
<div class="d-flex w-100 justify-content-between">
|
||||
<h6 class="mb-1">{{ case.title }}</h6>
|
||||
<small class="text-muted">{{ case.created_date|date:"Y-m-d" }}</small>
|
||||
</div>
|
||||
<div class="mb-1">
|
||||
<small class="text-muted me-2">{% if case.author.all %}By {{ case.author.all|join:", " }}{% endif %}</small>
|
||||
{% for cond in case.condition.all %}
|
||||
{% if cond.name %}<span class="badge bg-secondary me-1" title="Condition: {{ cond.name }}">{{ cond.name }}</span>{% endif %}
|
||||
{% endfor %}
|
||||
{% for pres in case.presentation.all %}
|
||||
{% if pres.name %}<span class="badge bg-secondary me-1" title="Presentation: {{ pres.name }}">{{ pres.name }}</span>{% endif %}
|
||||
{% endfor %}
|
||||
{% for ss in case.subspecialty.all %}
|
||||
{% if ss.name %}<span class="badge bg-secondary" title="Subspecialty: {{ ss.name }}">{{ ss.name }}</span>{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
<p class="mb-1 text-truncate">{% if case.description %}{{ case.description|truncatechars:140 }}{% endif %}</p>
|
||||
</div>
|
||||
<div class="ms-2">
|
||||
{% if collection %}
|
||||
<form class="m-0" hx-post="{% url 'atlas:add_case_to_collection' collection.pk %}" hx-target="#full-question-list" hx-swap="beforeend">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="case" value="{{ case.pk }}">
|
||||
<button type="submit" class="btn btn-sm btn-outline-primary">Add</button>
|
||||
</form>
|
||||
{% else %}
|
||||
<a class="btn btn-sm btn-outline-secondary" href="{% url 'atlas:case_displaysets' case.pk %}">View</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% include 'atlas/partials/_case_search_item.html' with case=case collection=collection %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
@@ -46,39 +15,7 @@
|
||||
<div class="small text-muted mb-1">Recently created</div>
|
||||
<div class="list-group">
|
||||
{% for case in recent_cases %}
|
||||
<div class="list-group-item d-flex justify-content-between align-items-center" data-case-pk="{{ case.pk }}">
|
||||
<div class="flex-fill me-3">
|
||||
<div class="d-flex w-100 justify-content-between">
|
||||
<h6 class="mb-1">{{ case.title }}</h6>
|
||||
<small class="text-muted">{{ case.created_date|date:"Y-m-d" }}</small>
|
||||
</div>
|
||||
<div class="mb-1">
|
||||
<small class="text-muted me-2">{% if case.author.all %}By {{ case.author.all|join:", " }}{% endif %}</small>
|
||||
{% for cond in case.condition.all %}
|
||||
{% if cond.name %}<span class="badge bg-secondary me-1" title="Condition: {{ cond.name }}">{{ cond.name }}</span>{% endif %}
|
||||
{% endfor %}
|
||||
{% for pres in case.presentation.all %}
|
||||
{% if pres.name %}<span class="badge bg-secondary me-1" title="Presentation: {{ pres.name }}">{{ pres.name }}</span>{% endif %}
|
||||
{% endfor %}
|
||||
{% for ss in case.subspecialty.all %}
|
||||
{% if ss.name %}<span class="badge bg-secondary" title="Subspecialty: {{ ss.name }}">{{ ss.name }}</span>{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
<p class="mb-1 text-truncate">{% if case.description %}{{ case.description|truncatechars:100 }}{% endif %}</p>
|
||||
</div>
|
||||
<div class="ms-2 d-flex align-items-center">
|
||||
{% if collection %}
|
||||
<form class="m-0" hx-post="{% url 'atlas:add_case_to_collection' collection.pk %}" hx-target="#full-question-list" hx-swap="beforeend">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="case" value="{{ case.pk }}">
|
||||
<button type="submit" class="btn btn-sm btn-outline-primary">Add</button>
|
||||
</form>
|
||||
<a class="btn btn-sm btn-outline-secondary ms-2" href="{% url 'atlas:case_detail' case.pk %}" target="_blank" rel="noopener">View</a>
|
||||
{% else %}
|
||||
<a class="btn btn-sm btn-outline-secondary" href="{% url 'atlas:case_displaysets' case.pk %}">View</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% include 'atlas/partials/_case_search_item.html' with case=case collection=collection %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -192,9 +192,13 @@
|
||||
<script>
|
||||
// Handle selecting a case from the modal results to import selected series into it
|
||||
document.body.addEventListener('click', async function (e) {
|
||||
// Only handle clicks inside the case select modal results
|
||||
// Only handle clicks inside the case select modal results, but ignore clicks on interactive controls inside the item
|
||||
const anchor = e.target.closest('#case-search-results-modal .list-group-item, #case-search-results-modal .list-group-item-action');
|
||||
if (!anchor) return;
|
||||
if (e.target.closest('#case-search-results-modal .list-group-item a, #case-search-results-modal .list-group-item button, #case-search-results-modal .list-group-item form, #case-search-results-modal .list-group-item input')) {
|
||||
// Let buttons/links/forms behave normally (e.g. View link)
|
||||
return;
|
||||
}
|
||||
e.preventDefault();
|
||||
// Determine case pk: prefer data-case-pk, else parse from href
|
||||
let casePk = anchor.getAttribute('data-case-pk');
|
||||
@@ -208,24 +212,30 @@
|
||||
return;
|
||||
}
|
||||
|
||||
// Gather selected series (same logic as import button)
|
||||
const selected = Array.from(document.querySelectorAll('input[name="selection"]:checked'))
|
||||
.filter(cb => !cb.disabled)
|
||||
.map(cb => cb.value);
|
||||
// Get a display title for confirmation
|
||||
let caseTitle = anchor.querySelector('h6') ? anchor.querySelector('h6').textContent.trim() : null;
|
||||
if (!caseTitle) caseTitle = `#${casePk}`;
|
||||
|
||||
// Confirm with the user before importing
|
||||
const checkboxes = Array.from(document.querySelectorAll('input[name="selection"]:checked'));
|
||||
const allCheckboxes = Array.from(document.querySelectorAll('input[name="selection"]'));
|
||||
const selectable = allCheckboxes.filter(cb => !cb.disabled).map(cb => cb.value);
|
||||
const selection = selected.length ? selected : selectable;
|
||||
if (!selection.length) {
|
||||
const selectionList = checkboxes.length ? checkboxes.map(cb => cb.value) : selectable;
|
||||
if (!selectionList.length) {
|
||||
alert('Please select at least one series to import into the case.');
|
||||
return;
|
||||
}
|
||||
const confirmMsg = `Import ${selectionList.length} series into case "${caseTitle}" (ID ${casePk})?`;
|
||||
if (!window.confirm(confirmMsg)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Build import URL template and replace trailing 0 with casePk
|
||||
const importTemplate = "{% url 'api-1:import_dicoms_case' 0 %}";
|
||||
const importUrl = importTemplate.replace(/0\/?$/, casePk + '/');
|
||||
|
||||
const form = new URLSearchParams();
|
||||
for (const s of selection) form.append('selection', s);
|
||||
for (const s of selectionList) form.append('selection', s);
|
||||
const orderSeriesInput = document.querySelector('input[name="order-series"]:checked');
|
||||
if (orderSeriesInput) form.append('order-series', orderSeriesInput.value);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user