Jun 26 - Update AI Analysis History bulk action for admin
This commit is contained in:
+116
-1
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user