Enhance case inline editing and search functionality with improved UI and new subspecialty handling
This commit is contained in:
@@ -103,9 +103,9 @@
|
|||||||
{% endwith %}
|
{% endwith %}
|
||||||
{% if can_edit %}
|
{% if can_edit %}
|
||||||
<button class="btn btn-sm btn-link text-muted p-0 ms-2 quick-edit-button"
|
<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-get="{% url 'atlas:case_inline_field' case.pk 'title' %}?edit=1&show_label=0"
|
||||||
hx-target="#case-inline-title"
|
hx-target="#case-inline-title"
|
||||||
hx-swap="innerHTML">quick edit</button>
|
hx-swap="outerHTML">quick edit</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</h2>
|
</h2>
|
||||||
@@ -212,13 +212,23 @@
|
|||||||
|
|
||||||
<div class="float-end text-end">
|
<div class="float-end text-end">
|
||||||
{# Subspecialty badges (preserve any anchor returned by get_link) #}
|
{# Subspecialty badges (preserve any anchor returned by get_link) #}
|
||||||
{% if case.subspecialty.all %}
|
<div class="quick-edit-wrap d-inline-block align-middle">
|
||||||
{% for sub in case.subspecialty.all %}
|
<span id="case-inline-subspecialty">
|
||||||
<span class="badge bg-secondary ms-1">{{ sub.get_link|safe }}</span>
|
{% if case.subspecialty.all %}
|
||||||
{% endfor %}
|
{% for sub in case.subspecialty.all %}
|
||||||
{% else %}
|
<span class="badge bg-secondary ms-1">{{ sub.get_link|safe }}</span>
|
||||||
<span class="text-muted me-2">No subspecialty</span>
|
{% endfor %}
|
||||||
{% endif %}
|
{% 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 #}
|
{# Pathological process badges #}
|
||||||
{% if case.pathological_process.all %}
|
{% if case.pathological_process.all %}
|
||||||
@@ -232,9 +242,9 @@
|
|||||||
<span>Diagnostic certainty:</span>
|
<span>Diagnostic certainty:</span>
|
||||||
{% if can_edit %}
|
{% if can_edit %}
|
||||||
<button class="btn btn-sm btn-link text-muted p-0 ms-2 quick-edit-button"
|
<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-get="{% url 'atlas:case_inline_field' case.pk 'diagnostic_certainty' %}?edit=1&show_label=0"
|
||||||
hx-target="#case-inline-diagnostic_certainty"
|
hx-target="#case-inline-diagnostic_certainty"
|
||||||
hx-swap="innerHTML">quick edit</button>
|
hx-swap="outerHTML">quick edit</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% url 'atlas:case_inline_field' case.pk 'diagnostic_certainty' as diag_url %}
|
{% 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" %}
|
{% with endpoint_default=diag_url|add:"?show_label=0" endpoint_edit=diag_url|add:"?edit=1&show_label=0" %}
|
||||||
@@ -268,9 +278,9 @@
|
|||||||
</div>
|
</div>
|
||||||
{% if can_edit %}
|
{% if can_edit %}
|
||||||
<button class="btn btn-sm btn-link text-muted p-0 ms-2 quick-edit-button"
|
<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-get="{% url 'atlas:case_inline_field' case.pk 'description' %}?edit=1&show_label=0"
|
||||||
hx-target="#case-inline-description"
|
hx-target="#case-inline-description"
|
||||||
hx-swap="innerHTML">quick edit</button>
|
hx-swap="outerHTML">quick edit</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -288,19 +298,17 @@
|
|||||||
<div class="card h-100">
|
<div class="card h-100">
|
||||||
<div class="card-header d-flex justify-content-between align-items-center quick-edit-wrap">History
|
<div class="card-header d-flex justify-content-between align-items-center quick-edit-wrap">History
|
||||||
{% if can_edit %}
|
{% 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-get="{% url 'atlas:case_inline_field' case.pk 'history' %}?edit=1&show_label=0"
|
||||||
hx-target="#case-inline-history"
|
hx-target="#case-inline-history"
|
||||||
hx-swap="innerHTML">quick edit</button>
|
hx-swap="outerHTML">quick edit</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div id="case-inline-history"
|
{% url 'atlas:case_inline_field' case.pk 'history' as history_url %}
|
||||||
hx-get="{% url 'atlas:case_inline_field' case.pk 'history' %}?show_label=0"
|
{% with endpoint_default=history_url|add:"?show_label=0" endpoint_edit=history_url|add:"?edit=1&show_label=0" %}
|
||||||
hx-trigger="load"
|
{% 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='' %}
|
||||||
hx-swap="innerHTML">
|
{% endwith %}
|
||||||
<div class="pre-whitespace">{{ case.history|linebreaks }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -308,19 +316,17 @@
|
|||||||
<div class="card h-100">
|
<div class="card h-100">
|
||||||
<div class="card-header d-flex justify-content-between align-items-center quick-edit-wrap">Discussion
|
<div class="card-header d-flex justify-content-between align-items-center quick-edit-wrap">Discussion
|
||||||
{% if can_edit %}
|
{% 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-get="{% url 'atlas:case_inline_field' case.pk 'discussion' %}?edit=1&show_label=0"
|
||||||
hx-target="#case-inline-discussion"
|
hx-target="#case-inline-discussion"
|
||||||
hx-swap="innerHTML">quick edit</button>
|
hx-swap="outerHTML">quick edit</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div id="case-inline-discussion"
|
{% url 'atlas:case_inline_field' case.pk 'discussion' as discussion_url %}
|
||||||
hx-get="{% url 'atlas:case_inline_field' case.pk 'discussion' %}?show_label=0"
|
{% with endpoint_default=discussion_url|add:"?show_label=0" endpoint_edit=discussion_url|add:"?edit=1&show_label=0" %}
|
||||||
hx-trigger="load"
|
{% 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='' %}
|
||||||
hx-swap="innerHTML">
|
{% endwith %}
|
||||||
<div class="pre-whitespace">{{ case.discussion|linebreaks }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -328,19 +334,17 @@
|
|||||||
<div class="card h-100">
|
<div class="card h-100">
|
||||||
<div class="card-header d-flex justify-content-between align-items-center quick-edit-wrap">Report
|
<div class="card-header d-flex justify-content-between align-items-center quick-edit-wrap">Report
|
||||||
{% if can_edit %}
|
{% 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-get="{% url 'atlas:case_inline_field' case.pk 'report' %}?edit=1&show_label=0"
|
||||||
hx-target="#case-inline-report"
|
hx-target="#case-inline-report"
|
||||||
hx-swap="innerHTML">quick edit</button>
|
hx-swap="outerHTML">quick edit</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div id="case-inline-report"
|
{% url 'atlas:case_inline_field' case.pk 'report' as report_url %}
|
||||||
hx-get="{% url 'atlas:case_inline_field' case.pk 'report' %}?show_label=0"
|
{% with endpoint_default=report_url|add:"?show_label=0" endpoint_edit=report_url|add:"?edit=1&show_label=0" %}
|
||||||
hx-trigger="load"
|
{% 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='' %}
|
||||||
hx-swap="innerHTML">
|
{% endwith %}
|
||||||
<div class="pre-whitespace">{{ case.report|linebreaks }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</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 %}
|
{% 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">
|
<div class="d-flex {% if show_label %}justify-content-between{% else %}justify-content-end{% endif %} align-items-start gap-2">
|
||||||
{% if show_label %}
|
{% if show_label %}
|
||||||
<strong>{{ field_label }}:</strong>
|
<strong>{{ field_label }}:</strong>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if can_edit %}
|
{% if can_edit %}
|
||||||
{% if editing %}
|
{% if not 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 %}
|
|
||||||
<button class="quick-edit-trigger quick-edit-button"
|
<button class="quick-edit-trigger quick-edit-button"
|
||||||
type="button"
|
type="button"
|
||||||
hx-get="{{ endpoint_url_edit }}"
|
hx-get="{{ endpoint_url_edit }}"
|
||||||
hx-target="#{{ container_id }}"
|
hx-target="#{{ container_id }}"
|
||||||
hx-swap="innerHTML">
|
hx-swap="outerHTML">
|
||||||
quick edit
|
quick edit
|
||||||
</button>
|
</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -27,17 +19,21 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if editing %}
|
{% if editing %}
|
||||||
<form hx-post="{{ endpoint_url_default }}"
|
<form hx-post="{{ endpoint_url_default }}"
|
||||||
hx-target="#{{ container_id }}"
|
hx-target="#{{ container_id }}"
|
||||||
hx-swap="innerHTML"
|
hx-swap="outerHTML"
|
||||||
class="mt-2">
|
class="mt-2">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{% if field_kind == "textarea" %}
|
{% if field_kind == "textarea" %}
|
||||||
<textarea class="form-control" name="value" rows="4">{{ value|default_if_none:"" }}</textarea>
|
<textarea class="form-control" name="value" rows="4">{{ value|default_if_none:"" }}</textarea>
|
||||||
{% elif field_kind == "select" %}
|
{% 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 %}
|
{% 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 %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
{% else %}
|
{% else %}
|
||||||
@@ -64,8 +60,12 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% 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-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>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
{% else %}
|
{% else %}
|
||||||
|
|||||||
@@ -2,38 +2,7 @@
|
|||||||
{% if cases and cases|length > 0 %}
|
{% if cases and cases|length > 0 %}
|
||||||
<div class="list-group mb-2">
|
<div class="list-group mb-2">
|
||||||
{% for case in cases %}
|
{% for case in cases %}
|
||||||
<div class="list-group-item d-flex justify-content-between align-items-center" data-case-pk="{{ case.pk }}">
|
{% include 'atlas/partials/_case_search_item.html' with case=case collection=collection %}
|
||||||
<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>
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
@@ -46,39 +15,7 @@
|
|||||||
<div class="small text-muted mb-1">Recently created</div>
|
<div class="small text-muted mb-1">Recently created</div>
|
||||||
<div class="list-group">
|
<div class="list-group">
|
||||||
{% for case in recent_cases %}
|
{% for case in recent_cases %}
|
||||||
<div class="list-group-item d-flex justify-content-between align-items-center" data-case-pk="{{ case.pk }}">
|
{% include 'atlas/partials/_case_search_item.html' with case=case collection=collection %}
|
||||||
<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>
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -192,9 +192,13 @@
|
|||||||
<script>
|
<script>
|
||||||
// Handle selecting a case from the modal results to import selected series into it
|
// Handle selecting a case from the modal results to import selected series into it
|
||||||
document.body.addEventListener('click', async function (e) {
|
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');
|
const anchor = e.target.closest('#case-search-results-modal .list-group-item, #case-search-results-modal .list-group-item-action');
|
||||||
if (!anchor) return;
|
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();
|
e.preventDefault();
|
||||||
// Determine case pk: prefer data-case-pk, else parse from href
|
// Determine case pk: prefer data-case-pk, else parse from href
|
||||||
let casePk = anchor.getAttribute('data-case-pk');
|
let casePk = anchor.getAttribute('data-case-pk');
|
||||||
@@ -208,24 +212,30 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gather selected series (same logic as import button)
|
// Get a display title for confirmation
|
||||||
const selected = Array.from(document.querySelectorAll('input[name="selection"]:checked'))
|
let caseTitle = anchor.querySelector('h6') ? anchor.querySelector('h6').textContent.trim() : null;
|
||||||
.filter(cb => !cb.disabled)
|
if (!caseTitle) caseTitle = `#${casePk}`;
|
||||||
.map(cb => cb.value);
|
|
||||||
|
// 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 allCheckboxes = Array.from(document.querySelectorAll('input[name="selection"]'));
|
||||||
const selectable = allCheckboxes.filter(cb => !cb.disabled).map(cb => cb.value);
|
const selectable = allCheckboxes.filter(cb => !cb.disabled).map(cb => cb.value);
|
||||||
const selection = selected.length ? selected : selectable;
|
const selectionList = checkboxes.length ? checkboxes.map(cb => cb.value) : selectable;
|
||||||
if (!selection.length) {
|
if (!selectionList.length) {
|
||||||
alert('Please select at least one series to import into the case.');
|
alert('Please select at least one series to import into the case.');
|
||||||
return;
|
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
|
// Build import URL template and replace trailing 0 with casePk
|
||||||
const importTemplate = "{% url 'api-1:import_dicoms_case' 0 %}";
|
const importTemplate = "{% url 'api-1:import_dicoms_case' 0 %}";
|
||||||
const importUrl = importTemplate.replace(/0\/?$/, casePk + '/');
|
const importUrl = importTemplate.replace(/0\/?$/, casePk + '/');
|
||||||
|
|
||||||
const form = new URLSearchParams();
|
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');
|
const orderSeriesInput = document.querySelector('input[name="order-series"]:checked');
|
||||||
if (orderSeriesInput) form.append('order-series', orderSeriesInput.value);
|
if (orderSeriesInput) form.append('order-series', orderSeriesInput.value);
|
||||||
|
|
||||||
|
|||||||
+57
-18
@@ -761,6 +761,7 @@ CASE_INLINE_FIELD_CONFIG = {
|
|||||||
"discussion": {"label": "Discussion", "kind": "textarea"},
|
"discussion": {"label": "Discussion", "kind": "textarea"},
|
||||||
"report": {"label": "Report", "kind": "textarea"},
|
"report": {"label": "Report", "kind": "textarea"},
|
||||||
"diagnostic_certainty": {"label": "Diagnostic certainty", "kind": "select"},
|
"diagnostic_certainty": {"label": "Diagnostic certainty", "kind": "select"},
|
||||||
|
"subspecialty": {"label": "Subspecialty", "kind": "select"},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -883,33 +884,69 @@ def case_inline_field(request, pk, field_name):
|
|||||||
show_label = request.GET.get("show_label", "1") not in ("0", "false", "False")
|
show_label = request.GET.get("show_label", "1") not in ("0", "false", "False")
|
||||||
error_message = ""
|
error_message = ""
|
||||||
|
|
||||||
if request.method == "POST":
|
# Special-case handling for many-to-many `subspecialty` field
|
||||||
if not can_edit:
|
if field_name == "subspecialty":
|
||||||
return HttpResponse(status=403)
|
if request.method == "POST":
|
||||||
|
if not can_edit:
|
||||||
editing = True
|
return HttpResponse(status=403)
|
||||||
cleaned_value, error_message = _validate_case_inline_value(
|
editing = True
|
||||||
field_name=field_name,
|
# Accept multiple values via POST list
|
||||||
raw_value=request.POST.get("value", ""),
|
posted = request.POST.getlist("value")
|
||||||
)
|
ids = []
|
||||||
if error_message:
|
for v in posted:
|
||||||
value = request.POST.get("value", "")
|
try:
|
||||||
|
ids.append(int(v))
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
# keep only valid existing ids
|
||||||
|
valid_ids = list(
|
||||||
|
Subspecialty.objects.filter(pk__in=ids).values_list("pk", flat=True)
|
||||||
|
)
|
||||||
|
case.subspecialty.set(valid_ids)
|
||||||
|
editing = False
|
||||||
|
value = case.subspecialty.all()
|
||||||
else:
|
else:
|
||||||
setattr(case, field_name, cleaned_value)
|
value = case.subspecialty.all()
|
||||||
case.save(update_fields=[field_name])
|
if editing and not can_edit:
|
||||||
editing = False
|
editing = False
|
||||||
value = getattr(case, field_name)
|
|
||||||
else:
|
else:
|
||||||
value = getattr(case, field_name)
|
if request.method == "POST":
|
||||||
if editing and not can_edit:
|
if not can_edit:
|
||||||
editing = False
|
return HttpResponse(status=403)
|
||||||
|
|
||||||
|
editing = True
|
||||||
|
cleaned_value, error_message = _validate_case_inline_value(
|
||||||
|
field_name=field_name,
|
||||||
|
raw_value=request.POST.get("value", ""),
|
||||||
|
)
|
||||||
|
if error_message:
|
||||||
|
value = request.POST.get("value", "")
|
||||||
|
else:
|
||||||
|
setattr(case, field_name, cleaned_value)
|
||||||
|
case.save(update_fields=[field_name])
|
||||||
|
editing = False
|
||||||
|
value = getattr(case, field_name)
|
||||||
|
else:
|
||||||
|
value = getattr(case, field_name)
|
||||||
|
if editing and not can_edit:
|
||||||
|
editing = False
|
||||||
|
|
||||||
if field_name == "diagnostic_certainty":
|
if field_name == "diagnostic_certainty":
|
||||||
display_value = case.get_diagnostic_certainty_display()
|
display_value = case.get_diagnostic_certainty_display()
|
||||||
select_choices = [(str(choice.value), str(choice.label)) for choice in Case.CertaintyChoices]
|
select_choices = [(str(choice.value), str(choice.label)) for choice in Case.CertaintyChoices]
|
||||||
|
selected_values = []
|
||||||
|
multiple = False
|
||||||
|
elif field_name == "subspecialty":
|
||||||
|
# For many-to-many, provide choices and selected values
|
||||||
|
display_value = ", ".join([str(s) for s in case.subspecialty.all()])
|
||||||
|
select_choices = [(str(s.pk), str(s.name)) for s in Subspecialty.objects.all()]
|
||||||
|
selected_values = [str(s.pk) for s in case.subspecialty.all()]
|
||||||
|
multiple = True
|
||||||
else:
|
else:
|
||||||
display_value = value
|
display_value = value
|
||||||
select_choices = []
|
select_choices = []
|
||||||
|
selected_values = []
|
||||||
|
multiple = False
|
||||||
|
|
||||||
endpoint_url = reverse("atlas:case_inline_field", kwargs={"pk": case.pk, "field_name": field_name})
|
endpoint_url = reverse("atlas:case_inline_field", kwargs={"pk": case.pk, "field_name": field_name})
|
||||||
if show_label:
|
if show_label:
|
||||||
@@ -934,6 +971,8 @@ def case_inline_field(request, pk, field_name):
|
|||||||
"error_message": error_message,
|
"error_message": error_message,
|
||||||
"suggestions": _get_case_inline_suggestions(case, field_name),
|
"suggestions": _get_case_inline_suggestions(case, field_name),
|
||||||
"select_choices": select_choices,
|
"select_choices": select_choices,
|
||||||
|
"selected_values": selected_values,
|
||||||
|
"multiple": multiple,
|
||||||
"endpoint_url_default": endpoint_url_default,
|
"endpoint_url_default": endpoint_url_default,
|
||||||
"endpoint_url_edit": endpoint_url_edit,
|
"endpoint_url_edit": endpoint_url_edit,
|
||||||
"container_id": f"case-inline-{field_name}",
|
"container_id": f"case-inline-{field_name}",
|
||||||
|
|||||||
Reference in New Issue
Block a user