Add finding and displayset search functionality with corresponding widgets and endpoints

This commit is contained in:
Ross
2026-03-23 13:34:28 +00:00
parent d7ed8881d8
commit 20b4d6b6af
8 changed files with 254 additions and 2 deletions
@@ -0,0 +1,20 @@
<div class="list-group">
{% if displaysets %}
{% for ds in displaysets %}
<div class="list-group-item d-flex justify-content-between align-items-center" data-ds-pk="{{ ds.pk }}">
<div class="flex-fill">
<div class="d-flex w-100 justify-content-between">
<strong class="mb-1">{{ ds.name }}</strong>
<small class="text-muted">{{ ds.pk }}</small>
</div>
<p class="mb-1 text-truncate">{{ ds.description }}</p>
</div>
<div class="ms-2">
<a class="btn btn-sm btn-outline-secondary" href="{% url 'atlas:case_displaysets_detail' ds.pk %}">View</a>
</div>
</div>
{% endfor %}
{% else %}
<div class="list-group-item">No display sets found</div>
{% endif %}
</div>
@@ -0,0 +1,20 @@
<div class="list-group">
{% if findings %}
{% for f in findings %}
<div class="list-group-item d-flex justify-content-between align-items-center" data-finding-pk="{{ f.pk }}">
<div class="flex-fill">
<div class="d-flex w-100 justify-content-between">
<strong class="mb-1">{{ f.name }}</strong>
<small class="text-muted">{{ f.pk }}</small>
</div>
<p class="mb-1 text-truncate">{{ f.description }}</p>
</div>
<div class="ms-2">
<a class="btn btn-sm btn-outline-secondary" href="{% url 'atlas:finding_detail' f.pk %}">View</a>
</div>
</div>
{% endfor %}
{% else %}
<div class="list-group-item">No findings found</div>
{% endif %}
</div>
@@ -0,0 +1,45 @@
<div class="displayset-search-widget">
{% with input_id=input_id|default:'displayset-search-input' target_id=target_id|default:'displayset-search-results' %}
<label for="{{ input_id }}" class="form-label">Search display sets</label>
<input id="{{ input_id }}" name="q" class="form-control" type="search"
placeholder="Type to search display sets..."
hx-get="{% url 'atlas:displayset_search_partial' %}"
hx-include="#{{ input_id }}"
{% if case %}hx-vals='{"case": "{{ case.pk }}"}'{% endif %}
hx-target="#{{ target_id }}"
hx-trigger="keyup changed delay:400ms"
autocomplete="off">
<div id="{{ target_id }}" class="mt-2">
{% include 'atlas/partials/_displayset_search_results.html' with displaysets=displaysets case=case %}
</div>
{% endwith %}
</div>
<script>
(function(){
try {
var input = document.getElementById('{{ input_id|default:"displayset-search-input" }}');
if (!input) return;
var timer = null;
input.addEventListener('input', function(){
if (timer) clearTimeout(timer);
timer = setTimeout(function(){
try {
var q = encodeURIComponent(input.value || '');
var url = "{% url 'atlas:displayset_search' %}?q=" + q + ({% if case %} "&case={{ case.pk }}" {% else %} '' {% endif %});
var target = '#{{ target_id|default:"displayset-search-results" }}';
if (window.htmx && typeof window.htmx.ajax === 'function') {
window.htmx.ajax('GET', url, { target: target, swap: 'innerHTML' });
} else {
fetch(url, { credentials: 'same-origin' }).then(function(r){ return r.text(); }).then(function(html){
var el = document.querySelector(target);
if (el) el.innerHTML = html;
}).catch(function(e){ console.error('displayset search fetch error', e); });
}
} catch(e) { console.error(e); }
}, 400);
});
} catch(e) { console.error('displayset search widget init error', e); }
})();
</script>
@@ -0,0 +1,45 @@
<div class="finding-search-widget">
{% with input_id=input_id|default:'finding-search-input' target_id=target_id|default:'finding-search-results' %}
<label for="{{ input_id }}" class="form-label">Search findings</label>
<input id="{{ input_id }}" name="q" class="form-control" type="search"
placeholder="Type to search findings..."
hx-get="{% url 'atlas:finding_search_partial' %}"
hx-include="#{{ input_id }}"
{% if case %}hx-vals='{"case": "{{ case.pk }}"}'{% endif %}
hx-target="#{{ target_id }}"
hx-trigger="keyup changed delay:400ms"
autocomplete="off">
<div id="{{ target_id }}" class="mt-2">
{% include 'atlas/partials/_finding_search_results.html' with findings=findings case=case %}
</div>
{% endwith %}
</div>
<script>
(function(){
try {
var input = document.getElementById('{{ input_id|default:"finding-search-input" }}');
if (!input) return;
var timer = null;
input.addEventListener('input', function(){
if (timer) clearTimeout(timer);
timer = setTimeout(function(){
try {
var q = encodeURIComponent(input.value || '');
var url = "{% url 'atlas:finding_search' %}?q=" + q + ({% if case %} "&case={{ case.pk }}" {% else %} '' {% endif %});
var target = '#{{ target_id|default:"finding-search-results" }}';
if (window.htmx && typeof window.htmx.ajax === 'function') {
window.htmx.ajax('GET', url, { target: target, swap: 'innerHTML' });
} else {
fetch(url, { credentials: 'same-origin' }).then(function(r){ return r.text(); }).then(function(html){
var el = document.querySelector(target);
if (el) el.innerHTML = html;
}).catch(function(e){ console.error('finding search fetch error', e); });
}
} catch(e) { console.error(e); }
}, 400);
});
} catch(e) { console.error('finding search widget init error', e); }
})();
</script>