Jun 26 - Update AI Analysis History bulk action for admin

This commit is contained in:
2026-06-26 13:54:13 -04:00
parent fedb782b69
commit 5b94858f7f
+116 -1
View File
@@ -123,19 +123,36 @@
<button class="ai-hist-filter" data-verdict="none">No verdict</button>
</div>
</div>
{% if is_admin %}
<div class="ai-bulk-bar" id="bulk-bar">
<label style="display:flex;align-items:center;gap:.45rem;cursor:pointer">
<input type="checkbox" id="cb-all" title="Select all visible">
<span id="bulk-count" class="text-muted" style="font-size:.8rem">0 selected</span>
</label>
<button class="btn btn-danger btn-sm" id="btn-bulk-delete" disabled onclick="bulkDelete()">🗑 Delete Selected</button>
<button class="btn btn-ghost btn-sm" onclick="clearSelection()">✕ Clear</button>
</div>
{% endif %}
<div class="table-container">
<table>
<thead>
<tr>
{% if is_admin %}<th style="width:36px;padding-right:0"></th>{% endif %}
<th>Date</th>
<th>User</th>
<th>Files</th>
<th>Verdict</th>
{% if is_admin %}<th style="width:40px"></th>{% endif %}
</tr>
</thead>
<tbody>
{% for h in history %}
<tr class="history-row" data-id="{{ h.id }}" style="cursor:pointer" title="Click to view result">
{% if is_admin %}
<td onclick="event.stopPropagation()" style="padding-right:0">
<input type="checkbox" class="hist-cb" data-id="{{ h.id }}">
</td>
{% endif %}
<td class="nowrap text-muted">{{ h.analyzed_at.strftime('%Y-%m-%d %H:%M') if h.analyzed_at else '—' }}</td>
<td class="text-muted">{{ h.username or '—' }}</td>
<td class="text-muted">{{ h.file_names[:50] }}{% if h.file_names|length > 50 %}…{% endif %}</td>
@@ -146,9 +163,14 @@
</span>
{% else %}—{% endif %}
</td>
{% if is_admin %}
<td onclick="event.stopPropagation()">
<button class="btn-row-delete js-delete-row" data-id="{{ h.id }}" title="Delete this record">🗑</button>
</td>
{% endif %}
</tr>
{% else %}
<tr><td colspan="4" style="padding:2.5rem 1rem;text-align:center">
<tr><td colspan="{{ 6 if is_admin else 4 }}" style="padding:2.5rem 1rem;text-align:center">
<div style="font-size:2rem;margin-bottom:.5rem">🤖</div>
<p class="text-muted" style="margin:0 0 .75rem">No analyses yet. Upload a document above to get started.</p>
<button class="btn btn-primary btn-sm" onclick="document.getElementById('ai-files').scrollIntoView({behavior:'smooth'});document.getElementById('ai-files').focus()">Start Analyzing</button>
@@ -435,6 +457,87 @@ document.querySelectorAll('.js-edit-criterion').forEach(function(btn) {
});
});
/* ─── Bulk & row-level delete (admin only) ──────────────────── */
if (IS_ADMIN) {
// "Select all" checkbox
document.getElementById('cb-all').addEventListener('change', function() {
document.querySelectorAll('.hist-cb').forEach(function(cb) {
var row = cb.closest('.history-row');
if (row && row.style.display !== 'none') cb.checked = this.checked;
}, this);
_updateBulkBar();
});
// Individual checkboxes
document.addEventListener('change', function(e) {
if (e.target.classList.contains('hist-cb')) _updateBulkBar();
});
// Row delete icon — delegated
document.addEventListener('click', function(e) {
var btn = e.target.closest('.js-delete-row');
if (!btn) return;
e.stopPropagation();
var id = btn.dataset.id;
if (!confirm('Delete this analysis? This cannot be undone.')) return;
_sendDeletes([id], function(ok) {
if (!ok) return;
var row = document.querySelector('.history-row[data-id="' + id + '"]');
if (row) row.remove();
_updateBulkBar();
});
});
}
function _updateBulkBar() {
var cbs = document.querySelectorAll('.hist-cb:checked');
var count = cbs.length;
var barEl = document.getElementById('bulk-bar');
var countEl = document.getElementById('bulk-count');
var delBtn = document.getElementById('btn-bulk-delete');
if (!barEl) return;
countEl.textContent = count + ' selected';
delBtn.disabled = count === 0;
}
function clearSelection() {
document.querySelectorAll('.hist-cb').forEach(function(cb) { cb.checked = false; });
var cbAll = document.getElementById('cb-all');
if (cbAll) cbAll.checked = false;
_updateBulkBar();
}
function _sendDeletes(ids, callback) {
Promise.all(ids.map(function(id) {
return fetch('/ai-summary/history/' + id + '/delete', {
method: 'POST',
headers: { 'X-CSRFToken': getCsrfToken() },
}).then(function(r) { return r.json(); });
})).then(function(results) {
var failed = results.filter(function(r) { return r.error; });
if (failed.length) alert('Failed to delete ' + failed.length + ' item(s).');
if (callback) callback(true);
}).catch(function() {
alert('Delete failed. Please try again.');
if (callback) callback(false);
});
}
function bulkDelete() {
var checked = document.querySelectorAll('.hist-cb:checked');
if (!checked.length) return;
var ids = Array.from(checked).map(function(cb) { return cb.dataset.id; });
if (!confirm('Delete ' + ids.length + ' analysis record(s)? This cannot be undone.')) return;
_sendDeletes(ids, function(ok) {
if (!ok) return;
ids.forEach(function(id) {
var row = document.querySelector('.history-row[data-id="' + id + '"]');
if (row) row.remove();
});
clearSelection();
});
}
/* ─── History rows ──────────────────────────────────────────── */
var _currentAnalysisId = null;
@@ -622,6 +725,18 @@ function escHtml(str) {
/* ── History ───────────────────────────────────────────────── */
.history-row:hover td{background:var(--bg-subtle)}
.ai-bulk-bar{
display:flex;align-items:center;gap:.75rem;
padding:.55rem 1rem;background:var(--bg-subtle);
border-bottom:1px solid var(--border);
}
.btn-row-delete{
background:none;border:none;padding:2px 6px;border-radius:var(--r-sm);
cursor:pointer;font-size:.85rem;color:var(--text-muted);
transition:color var(--t),background var(--t);opacity:0;
}
.history-row:hover .btn-row-delete{opacity:1}
.btn-row-delete:hover{color:var(--danger,#dc2626);background:var(--danger-bg,#fef2f2)}
.ai-hist-filter{
background:none;border:1px solid var(--border);
padding:.2rem .55rem;font-size:.72rem;border-radius:var(--r-sm);