Enhance finding search functionality to support series findings and improve result display with badges

This commit is contained in:
Ross
2026-03-23 14:30:07 +00:00
parent a792f0c7cd
commit f70ecb4648
3 changed files with 35 additions and 9 deletions
+14 -1
View File
@@ -209,7 +209,20 @@
var timer = null;
input.addEventListener('input', function(){ if(timer) clearTimeout(timer); timer = setTimeout(function(){ var q = encodeURIComponent(input.value || ''); var url = window.location.origin + '/atlas/search/findings/?q=' + q + (currentCaseF ? '&case=' + currentCaseF : ''); fetch(url, {credentials:'same-origin'}).then(function(r){ return r.text(); }).then(function(html){ results.innerHTML = html; }).catch(function(e){ console.error('finding picker fetch error', e); results.innerHTML = '<div class="alert alert-danger">Search failed</div>'; }); }, 300); });
results.addEventListener('click', function(e){ var item = e.target.closest('.list-group-item'); if(!item) return; var pk = item.getAttribute('data-finding-pk'); if(!pk) return; insertAtCursor(textarea, '[['+'finding:'+pk+']]' ); if(overlay && overlay.parentNode) overlay.parentNode.removeChild(overlay); });
results.addEventListener('click', function(e){
var item = e.target.closest('.list-group-item');
if(!item) return;
var sfpk = item.getAttribute('data-seriesfinding-pk');
var fpk = item.getAttribute('data-finding-pk');
if(sfpk){
insertAtCursor(textarea, '[['+'seriesfinding:'+sfpk+']]' );
} else if(fpk){
insertAtCursor(textarea, '[['+'finding:'+fpk+']]' );
} else {
return;
}
if(overlay && overlay.parentNode) overlay.parentNode.removeChild(overlay);
});
overlay.tabIndex = -1; overlay.focus();
function fetchInitialFindings(){ var initFindingsUrl = window.location.origin + '/atlas/search/findings/' + (currentCaseF ? '?case=' + currentCaseF : ''); fetch(initFindingsUrl).then(function(r){ return r.text(); }).then(function(html){ results.innerHTML = html; }).catch(function(){}); }
fetchInitialFindings();
@@ -1,16 +1,25 @@
<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="list-group-item d-flex justify-content-between align-items-center" data-finding-pk="{{ f.pk }}" data-seriesfinding-pk="{{ f.pk }}">
<div class="flex-fill">
<div class="d-flex w-100 justify-content-between">
<strong class="mb-1">{{ f.name }}</strong>
<strong class="mb-1">{% if f.series %}{{ f.series.id }}{% else %}Series{% endif %}{% if f.description %}: {{ f.description|truncatechars:80 }}{% endif %}</strong>
<small class="text-muted">{{ f.pk }}</small>
</div>
<p class="mb-1 text-truncate">{{ f.description }}</p>
<p class="mb-1 text-truncate">
{% if f.findings.all %}
{% for sub in f.findings.all %}
<span class="badge bg-light text-dark me-1">{{ sub.name }}</span>
{% endfor %}
{% endif %}
{{ 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>
{% if f.series %}
<a class="btn btn-sm btn-outline-secondary" href="{% url 'atlas:series_detail' pk=f.series.pk %}">View series</a>
{% endif %}
</div>
</div>
{% endfor %}
+8 -4
View File
@@ -365,16 +365,20 @@ def finding_search(request):
# Allow both HTMX and standard fetch/ajax requests (don't require the HTMX header)
q = request.GET.get('q', '').strip()
case_id = request.GET.get('case')
qs = Finding.objects.all()
# Use SeriesFinding objects for the widget (series-scoped findings)
qs = SeriesFinding.objects.select_related('series').prefetch_related('findings')
if case_id:
try:
cid = int(case_id)
qs = qs.filter(case__pk=cid)
# Series are linked to cases through SeriesDetail; filter SeriesFinding by linked series' case
qs = qs.filter(series__seriesdetail__case__pk=cid)
except Exception:
pass
if q:
qs = qs.filter(name__icontains=q)
qs = qs.order_by('name')[:50]
qs = qs.filter(
Q(description__icontains=q) | Q(findings__name__icontains=q)
)
qs = qs.order_by('-pk').distinct()[:50]
html = render_to_string('atlas/partials/_finding_search_results.html', {'findings': qs}, request=request)
return HttpResponse(html)