04/09: fixed searching ticket, and deleting/bulk deleting tickets

This commit is contained in:
2026-04-09 13:10:56 -04:00
parent cc81e0cca3
commit 05ecf5a624
3 changed files with 213 additions and 5 deletions
+134 -1
View File
@@ -95,7 +95,22 @@
<td style="font-size:13px;">{{ t.creator.full_name }}</td>
<td style="font-size:13px;color:var(--muted);">{{ t.assignee.full_name if t.assignee else '—' }}</td>
<td style="font-size:11px;color:var(--muted);">{{ t.created_at | localtime("%b %d") }}</td>
<td><a href="{{ url_for('tickets.ticket_detail', ticket_id=t.id) }}" class="btn btn-secondary btn-sm"><i class="bi bi-eye"></i></a></td>
<td>
<div class="d-flex gap-1">
<a href="{{ url_for('tickets.ticket_detail', ticket_id=t.id) }}" class="btn btn-secondary btn-sm"><i class="bi bi-eye"></i></a>
{% if current_user.is_admin %}
<button type="button"
class="btn btn-sm delete-ticket-btn"
style="background:#fee2e2;color:#dc2626;border:1px solid #fca5a5;"
data-ticket-id="{{ t.id }}"
data-ticket-number="{{ t.ticket_number }}"
data-ticket-title="{{ t.title | e }}"
title="Delete ticket">
<i class="bi bi-trash"></i>
</button>
{% endif %}
</div>
</td>
</tr>
{% endfor %}
</tbody>
@@ -130,6 +145,37 @@
</div>
</div>
<!-- ── Single-ticket delete form (hidden, submitted via JS) ────────────────── -->
{% if current_user.is_admin %}
<form id="single-delete-form" method="POST" action="" style="display:none;">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
</form>
<!-- ── Confirmation modal for delete actions ──────────────────────────────── -->
<div id="delete-modal-overlay"
style="display:none;position:fixed;inset:0;background:rgba(0,0,0,.55);
z-index:2000;align-items:center;justify-content:center;">
<div style="background:var(--card-bg,#fff);border-radius:12px;padding:28px 32px;
max-width:480px;width:90%;box-shadow:0 16px 48px rgba(0,0,0,.25);">
<div style="display:flex;align-items:center;gap:12px;margin-bottom:16px;">
<span style="background:#fee2e2;border-radius:50%;width:40px;height:40px;
display:flex;align-items:center;justify-content:center;flex-shrink:0;">
<i class="bi bi-exclamation-triangle-fill" style="color:#dc2626;font-size:18px;"></i>
</span>
<h5 id="modal-title" style="margin:0;font-size:16px;font-weight:700;">Delete Ticket</h5>
</div>
<p id="modal-body" style="font-size:14px;color:var(--muted,#64748b);margin-bottom:24px;line-height:1.6;"></p>
<div class="d-flex gap-2 justify-content-end">
<button type="button" id="modal-cancel-btn" class="btn btn-secondary btn-sm">Cancel</button>
<button type="button" id="modal-confirm-btn"
class="btn btn-sm" style="background:#dc2626;color:#fff;border:none;">
<i class="bi bi-trash me-1"></i>Delete Permanently
</button>
</div>
</div>
</div>
{% endif %}
<!-- Bulk action bar: fixed at bottom, hidden until checkboxes are ticked -->
<!-- NOTE: display is set entirely via JS — no inline display property here -->
<div id="bulk-action-bar"
@@ -163,6 +209,12 @@
class="btn btn-sm" style="color:#fff;border:1px solid #6b7280;background:transparent;">
<i class="bi bi-person-dash me-1"></i>Unassign
</button>
{% if current_user.is_admin %}
<button type="button" id="bulk-delete-btn"
class="btn btn-sm" style="background:#dc2626;color:#fff;border:none;">
<i class="bi bi-trash me-1"></i>Delete
</button>
{% endif %}
</div>
</form>
<button type="button" onclick="clearSelection()"
@@ -224,6 +276,87 @@
updateBar();
});
});
// ── Delete modal ──────────────────────────────────────────────────────────
const overlay = document.getElementById('delete-modal-overlay');
const modalTitle = document.getElementById('modal-title');
const modalBody = document.getElementById('modal-body');
const cancelBtn = document.getElementById('modal-cancel-btn');
const confirmBtn = document.getElementById('modal-confirm-btn');
if (!overlay) return; // non-admin: modal not rendered
let pendingAction = null;
function showModal(title, body, onConfirm) {
modalTitle.textContent = title;
modalBody.innerHTML = body;
pendingAction = onConfirm;
overlay.style.display = 'flex';
}
function hideModal() {
overlay.style.display = 'none';
pendingAction = null;
}
cancelBtn.addEventListener('click', hideModal);
overlay.addEventListener('click', function (e) {
if (e.target === overlay) hideModal();
});
confirmBtn.addEventListener('click', function () {
if (typeof pendingAction === 'function') pendingAction();
hideModal();
});
// ── Single-ticket delete ──────────────────────────────────────────────────
document.querySelectorAll('.delete-ticket-btn').forEach(function (btn) {
btn.addEventListener('click', function () {
const ticketId = this.dataset.ticketId;
const ticketNumber = this.dataset.ticketNumber;
const ticketTitle = this.dataset.ticketTitle;
showModal(
'Delete Ticket',
'This will <strong>permanently delete</strong> ticket ' +
'<code>' + ticketNumber + '</code> — <em>' + ticketTitle + '</em> ' +
'and all associated comments, attachments, and history.' +
'<br><br>This action <strong>cannot be undone</strong>.',
function () {
const form = document.getElementById('single-delete-form');
form.action = '/admin/tickets/' + ticketId + '/delete';
form.submit();
}
);
});
});
// ── Bulk delete ───────────────────────────────────────────────────────────
const bulkDeleteBtn = document.getElementById('bulk-delete-btn');
if (bulkDeleteBtn) {
bulkDeleteBtn.addEventListener('click', function () {
const checked = getChecked();
if (checked.length === 0) return;
const n = checked.length;
showModal(
'Delete Selected Tickets',
'This will <strong>permanently delete ' + n + ' ticket' + (n !== 1 ? 's' : '') + '</strong> ' +
'and all associated comments, attachments, and history.' +
'<br><br>This action <strong>cannot be undone</strong>.',
function () {
const form = document.getElementById('bulk-form');
const input = document.createElement('input');
input.type = 'hidden';
input.name = 'action';
input.value = 'delete';
form.appendChild(input);
form.submit();
}
);
});
}
})();
</script>
{% endblock %}