Add case inline editing functionality and search widget improvements
This commit is contained in:
@@ -1,7 +1,9 @@
|
||||
<div id="add-case-form" class="p-2 border rounded">
|
||||
<p class="mb-2">Add an existing case to this collection. You can also use the case overview view to add multiple cases at once: <a href="{% url 'atlas:case_view' %}">case overview</a>.</p>
|
||||
|
||||
{% include 'atlas/partials/case_search_widget.html' %}
|
||||
{% load case_widgets %}
|
||||
{% url 'atlas:add_case_to_collection' collection.pk as add_case_url %}
|
||||
{% case_search_widget collection=collection cases=cases recent_cases=recent_cases selection_mode='multiple' action_url=add_case_url input_id='case-search-input-collection-'|add:collection.pk|stringformat:'s' target_id='case-search-results-collection-'|add:collection.pk|stringformat:'s' %}
|
||||
|
||||
<div class="mt-2">
|
||||
<button class="btn btn-sm btn-secondary" _="on click remove #add-case-form">Close</button>
|
||||
|
||||
@@ -24,8 +24,91 @@
|
||||
{% endfor %}
|
||||
{% endpartialdef %}
|
||||
|
||||
<style>
|
||||
.case-inline-field {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.case-inline-field .quick-edit-button {
|
||||
border: 0;
|
||||
background: transparent;
|
||||
color: var(--bs-secondary-color, #6c757d);
|
||||
font-size: 0.8rem;
|
||||
line-height: 1.1;
|
||||
padding: 0;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.case-inline-field .quick-edit-button:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.case-inline-field .quick-edit-compact {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
z-index: 2;
|
||||
opacity: 0.35;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.case-inline-field .quick-edit-trigger {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: opacity 0.15s ease;
|
||||
}
|
||||
|
||||
.case-inline-field:hover .quick-edit-trigger,
|
||||
.case-inline-field:focus-within .quick-edit-trigger {
|
||||
opacity: 0.65;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.case-inline-field .quick-edit-trigger:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.case-inline-field .quick-edit-compact.quick-edit-trigger {
|
||||
opacity: 0.35;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.case-inline-field:hover .quick-edit-compact.quick-edit-trigger,
|
||||
.case-inline-field:focus-within .quick-edit-compact.quick-edit-trigger {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
/* Generic hover-only quick-edit behaviour for host-wrapped buttons */
|
||||
.quick-edit-wrap { position: relative; display: inline-flex; align-items: center; }
|
||||
.quick-edit-wrap .quick-edit-button {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: opacity 0.15s ease;
|
||||
}
|
||||
.quick-edit-wrap:hover .quick-edit-button,
|
||||
.quick-edit-wrap:focus-within .quick-edit-button {
|
||||
opacity: 0.65;
|
||||
pointer-events: auto;
|
||||
}
|
||||
.quick-edit-wrap .quick-edit-button:hover { opacity: 1; }
|
||||
</style>
|
||||
|
||||
<div class="atlas {% if case.scrapped %}atlas-scrapped{% endif %}">
|
||||
<h2>Case: {{ case.title }}</h2>
|
||||
<h2 class="d-flex align-items-center" style="gap: .5rem; white-space: nowrap;">
|
||||
<span>Case:</span>
|
||||
<div class="d-flex align-items-center quick-edit-wrap">
|
||||
<span id="case-inline-title"
|
||||
hx-get="{% url 'atlas:case_inline_field' case.pk 'title' %}?show_label=0"
|
||||
hx-trigger="load"
|
||||
hx-swap="innerHTML">{{ case.title }}</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 'title' %}?edit=1&show_label=0"
|
||||
hx-target="#case-inline-title"
|
||||
hx-swap="innerHTML">quick edit</button>
|
||||
{% endif %}
|
||||
</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/>
|
||||
@@ -144,8 +227,22 @@
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
{# Diagnostic certainty as a badge #}
|
||||
Diagnostic certainty: <span class="badge bg-info text-dark ms-2">{{ case.get_diagnostic_certainty_display }}</span>
|
||||
{# Diagnostic certainty quick inline editor #}
|
||||
<div class="d-flex align-items-center quick-edit-wrap">
|
||||
<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>
|
||||
{% endif %}
|
||||
<div id="case-inline-diagnostic_certainty" class="ms-2"
|
||||
hx-get="{% url 'atlas:case_inline_field' case.pk 'diagnostic_certainty' %}?show_label=0"
|
||||
hx-trigger="load"
|
||||
hx-swap="innerHTML">
|
||||
<span class="badge bg-info text-dark">{{ case.get_diagnostic_certainty_display }}</span>
|
||||
</div>
|
||||
</div>
|
||||
{# Normal toggle HTMX block #}
|
||||
<div class="mt-1">
|
||||
{% include "atlas/partials/_normal_toggle.html" %}
|
||||
@@ -163,7 +260,21 @@
|
||||
></div>
|
||||
</details>
|
||||
|
||||
<p class="pre-whitespace"><b>Description:</b> {{ case.description }}</p>
|
||||
<div class="d-flex align-items-start justify-content-between quick-edit-wrap">
|
||||
<div class="d-flex align-items-start flex-grow-1">
|
||||
<strong>Description:</strong>
|
||||
<div id="case-inline-description" class="ms-2 flex-grow-1"
|
||||
hx-get="{% url 'atlas:case_inline_field' case.pk 'description' %}?show_label=0"
|
||||
hx-trigger="load"
|
||||
hx-swap="innerHTML">{{ case.description }}</div>
|
||||
</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>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<p>
|
||||
<b>Presentation:</b>
|
||||
@@ -177,25 +288,61 @@
|
||||
<div class="row g-3 mb-3">
|
||||
<div class="col-12 col-md-12">
|
||||
<div class="card h-100">
|
||||
<div class="card-header">History</div>
|
||||
<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"
|
||||
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>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="pre-whitespace">{{ case.history|linebreaks }}</div>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-md-12">
|
||||
<div class="card h-100">
|
||||
<div class="card-header">Discussion</div>
|
||||
<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"
|
||||
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>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="pre-whitespace">{{ case.discussion|linebreaks }}</div>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-md-12">
|
||||
<div class="card h-100">
|
||||
<div class="card-header">Report</div>
|
||||
<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"
|
||||
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>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="pre-whitespace">{{ case.report|linebreaks }}</div>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,39 +2,52 @@
|
||||
<div class="list-group">
|
||||
{% if cases %}
|
||||
{% for case in cases %}
|
||||
<a href="{% url 'atlas:case_detail' case.pk %}" class="list-group-item list-group-item-action">
|
||||
<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>
|
||||
<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>
|
||||
|
||||
<div class="mb-1">
|
||||
{# show up to three series modalities/examinations if present #}
|
||||
{% for s in case.series.all|slice:":3" %}
|
||||
{% if s.modality and s.modality.modality or s.examination and s.examination.examination %}
|
||||
<span class="badge bg-light text-dark me-1" title="Series: {% if s.modality and s.modality.modality %}{{ s.modality.modality }}{% endif %}{% if s.examination and s.examination.examination %} / {{ s.examination.examination }}{% endif %}">
|
||||
{% if s.modality and s.modality.modality %}{{ s.modality.modality }}{% endif %}{% if s.examination and s.examination.examination %}{% if s.modality and s.modality.modality %} / {% endif %}{{ s.examination.examination }}{% endif %}
|
||||
</span>
|
||||
{% endif %}
|
||||
{% empty %}
|
||||
<small class="text-muted">No series</small>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="mb-1">
|
||||
{# show up to three series modalities/examinations if present #}
|
||||
{% for s in case.series.all|slice:":3" %}
|
||||
{% if s.modality and s.modality.modality or s.examination and s.examination.examination %}
|
||||
<span class="badge bg-light text-dark me-1" title="Series: {% if s.modality and s.modality.modality %}{{ s.modality.modality }}{% endif %}{% if s.examination and s.examination.examination %} / {{ s.examination.examination }}{% endif %}">
|
||||
{% if s.modality and s.modality.modality %}{{ s.modality.modality }}{% endif %}{% if s.examination and s.examination.examination %}{% if s.modality and s.modality.modality %} / {% endif %}{{ s.examination.examination }}{% endif %}
|
||||
</span>
|
||||
{% endif %}
|
||||
{% empty %}
|
||||
<small class="text-muted">No series</small>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<p class="mb-1 text-truncate">{{ case.description }}</p>
|
||||
</a>
|
||||
<p class="mb-1 text-truncate">{{ case.description }}</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 %}
|
||||
{% else %}
|
||||
<div class="list-group-item">No cases found</div>
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
<div 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 %}
|
||||
<button class="quick-edit-trigger quick-edit-button"
|
||||
type="button"
|
||||
hx-get="{{ endpoint_url_edit }}"
|
||||
hx-target="#{{ container_id }}"
|
||||
hx-swap="innerHTML">
|
||||
quick edit
|
||||
</button>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if editing %}
|
||||
<form hx-post="{{ endpoint_url_default }}"
|
||||
hx-target="#{{ container_id }}"
|
||||
hx-swap="innerHTML"
|
||||
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">
|
||||
{% for option_value, option_label in select_choices %}
|
||||
<option value="{{ option_value }}" {% if value|stringformat:"s" == option_value %}selected{% endif %}>{{ option_label }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
{% else %}
|
||||
<input class="form-control" type="text" name="value" value="{{ value|default_if_none:"" }}" />
|
||||
{% endif %}
|
||||
|
||||
{% if error_message %}
|
||||
<div class="text-danger small mt-1">{{ error_message }}</div>
|
||||
{% endif %}
|
||||
|
||||
{% if suggestions %}
|
||||
<div class="mt-2">
|
||||
<div class="small text-muted mb-1">Suggested options</div>
|
||||
<div class="d-flex flex-wrap gap-1">
|
||||
{% for suggestion in suggestions %}
|
||||
<button type="button"
|
||||
class="btn btn-sm btn-outline-info"
|
||||
onclick="(function(btn){const f=btn.closest('form'); const el=f.querySelector('[name=value]'); if(!el){return;} el.value=btn.getAttribute('data-suggestion') || ''; if(el.tagName==='TEXTAREA' || el.tagName==='INPUT'){el.focus();}})(this)"
|
||||
data-suggestion="{{ suggestion }}">
|
||||
{{ suggestion|truncatechars:80 }}
|
||||
</button>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="mt-2">
|
||||
<button class="btn btn-sm btn-primary" type="submit">Save</button>
|
||||
</div>
|
||||
</form>
|
||||
{% else %}
|
||||
{% if field_kind == "textarea" %}
|
||||
{% if display_value %}
|
||||
<div class="pre-whitespace{% if show_label %} mt-1{% endif %}">{{ display_value|linebreaks }}</div>
|
||||
{% else %}
|
||||
<div class="{% if show_label %}mt-1 {% endif %}text-muted">Not set</div>
|
||||
{% endif %}
|
||||
{% elif field_name == "diagnostic_certainty" %}
|
||||
<div class="{% if show_label %}mt-1{% endif %}">
|
||||
{% if show_label %}Diagnostic certainty: {% endif %}
|
||||
<span class="badge bg-info text-dark ms-1">{{ display_value|default:"Not set" }}</span>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="{% if show_label %}mt-1{% endif %}">
|
||||
{% if display_value %}
|
||||
{{ display_value }}
|
||||
{% else %}
|
||||
<span class="text-muted">Not set</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
@@ -2,25 +2,38 @@
|
||||
{% if cases and cases|length > 0 %}
|
||||
<div class="list-group mb-2">
|
||||
{% for case in cases %}
|
||||
<a href="{% url 'atlas:case_detail' case.pk %}" class="list-group-item list-group-item-action">
|
||||
<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 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="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 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>
|
||||
<p class="mb-1 text-truncate">{% if case.description %}{{ case.description|truncatechars:140 }}{% endif %}</p>
|
||||
</a>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
|
||||
@@ -1,45 +1,47 @@
|
||||
<div class="case-search-widget">
|
||||
<label for="case-search-input" class="form-label">Search cases</label>
|
||||
<input id="case-search-input" name="q" class="form-control" type="search"
|
||||
{% with input_id=input_id|default:'case-search-input' target_id=target_id|default:'case-search-results' %}
|
||||
<label for="{{ input_id }}" class="form-label">Search cases</label>
|
||||
<input id="{{ input_id }}" name="q" class="form-control" type="search"
|
||||
placeholder="Type to search cases..."
|
||||
hx-get="{% url 'atlas:case_search' %}"
|
||||
hx-include="#case-search-input"
|
||||
hx-vals='{"collection": "{{ collection.pk }}"}'
|
||||
hx-target="#case-search-results"
|
||||
hx-include="#{{ input_id }}"
|
||||
{% if collection %}hx-vals='{"collection": "{{ collection.pk }}"}'{% endif %}
|
||||
hx-target="#{{ target_id }}"
|
||||
hx-trigger="keyup changed delay:400ms"
|
||||
autocomplete="off">
|
||||
|
||||
<div id="case-search-results" class="mt-2">
|
||||
<div id="{{ target_id }}" class="mt-2">
|
||||
{# Render initial results (search + recent) on initial GET; HTMX searches will replace this div with partial results #}
|
||||
{% include 'atlas/partials/case_search_results.html' with cases=cases recent_cases=recent_cases collection=collection %}
|
||||
</div>
|
||||
{% endwith %}
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(function(){
|
||||
// Robust fallback: ensure repeated searches always work by using htmx.ajax when available.
|
||||
try {
|
||||
var input = document.getElementById('case-search-input');
|
||||
var input = document.getElementById('{{ input_id|default:"case-search-input" }}');
|
||||
if (!input) return;
|
||||
var timer = null;
|
||||
input.addEventListener('input', function(){
|
||||
if (timer) clearTimeout(timer);
|
||||
timer = setTimeout(function(){
|
||||
try {
|
||||
try {
|
||||
var q = encodeURIComponent(input.value || '');
|
||||
var url = "{% url 'atlas:case_search' %}?q=" + q + "&collection={{ collection.pk }}";
|
||||
var url = "{% url 'atlas:case_search' %}?q=" + q + ({% if collection %} "&collection={{ collection.pk }}" {% else %} '' {% endif %});
|
||||
var target = '#{{ target_id|default:"case-search-results" }}';
|
||||
if (window.htmx && typeof window.htmx.ajax === 'function') {
|
||||
window.htmx.ajax('GET', url, { target: '#case-search-results', swap: 'innerHTML' });
|
||||
window.htmx.ajax('GET', url, { target: target, swap: 'innerHTML' });
|
||||
} else {
|
||||
// fallback to fetch
|
||||
fetch(url, { credentials: 'same-origin' }).then(function(r){ return r.text(); }).then(function(html){
|
||||
var el = document.getElementById('case-search-results');
|
||||
var el = document.querySelector(target);
|
||||
if (el) el.innerHTML = html;
|
||||
}).catch(function(e){ console.error('case search fetch error', e); });
|
||||
}
|
||||
} catch(e) { console.error(e); }
|
||||
}, 400);
|
||||
});
|
||||
// No multi-select action bar; items provide per-item Add buttons.
|
||||
} catch(e) { console.error('case search widget init error', e); }
|
||||
})();
|
||||
</script>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{% extends 'atlas/base.html' %}
|
||||
{% load case_widgets %}
|
||||
|
||||
{% block content %}
|
||||
<h2>Uploaded dicoms</h2>
|
||||
@@ -178,18 +179,7 @@
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="mb-2">
|
||||
<label for="case-search-input-modal" class="form-label">Search cases</label>
|
||||
<input id="case-search-input-modal" name="q" class="form-control" type="search"
|
||||
placeholder="Type to search cases..."
|
||||
hx-get="{% url 'atlas:case_search' %}"
|
||||
hx-include="#case-search-input-modal"
|
||||
hx-target="#case-search-results-modal"
|
||||
hx-trigger="keyup changed delay:400ms"
|
||||
autocomplete="off">
|
||||
</div>
|
||||
<div id="case-search-results-modal" class="mt-2">
|
||||
<!-- initial empty state; results will be loaded by HTMX -->
|
||||
<div class="text-muted small">Type to search cases to import into.</div>
|
||||
{% case_search_widget collection=None input_id='case-search-input-modal' target_id='case-search-results-modal' %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
|
||||
Reference in New Issue
Block a user