05/22 Enhance codes and fix bugs

This commit is contained in:
2026-05-22 11:53:41 -04:00
parent d6d9b5e9dc
commit 3511ac2b56
13 changed files with 891 additions and 47 deletions
+74 -19
View File
@@ -8,21 +8,8 @@
<title id="page-title-text">{% block title %}IT Helpdesk{% endblock %} — {{ branding.app_name }}</title>
<style>
:root {
--accent: {
{
branding.primary_color
}
}
;
--accent-h: {
{
branding.primary_color
}
}
;
--accent: {{ branding.primary_color }};
--accent-h: {{ branding.primary_color }};
}
</style>
<link rel="preconnect" href="https://fonts.googleapis.com" />
@@ -1270,6 +1257,25 @@
</div>
{% endif %}
<!-- ── Reusable confirmation modal ─────────────────────────────────────────── -->
<div class="modal fade" id="confirm-modal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered modal-sm">
<div class="modal-content">
<div class="modal-header" style="padding:14px 20px;">
<h5 class="modal-title" id="confirm-modal-title" style="font-size:15px;font-weight:600;"></h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body" style="padding:16px 20px;">
<p id="confirm-modal-body" style="font-size:14px;color:var(--text2);margin:0;"></p>
</div>
<div class="modal-footer" style="padding:12px 20px;gap:8px;">
<button type="button" class="btn btn-secondary btn-sm" data-bs-dismiss="modal">Cancel</button>
<button type="button" id="confirm-modal-ok" class="btn btn-sm btn-danger">Confirm</button>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://cdn.socket.io/4.7.5/socket.io.min.js"></script>
<script>
@@ -1278,6 +1284,55 @@
// Use csrfPost(url, body) instead of raw fetch(..., {method:'POST'}) to ensure
// the token from the <meta> tag is sent automatically.
const _csrfToken = () => document.querySelector('meta[name="csrf-token"]')?.content || '';
// HTML-escape helper — use whenever inserting server-supplied strings into innerHTML.
function _esc(s) {
const d = document.createElement('div');
d.textContent = String(s == null ? '' : s);
return d.innerHTML;
}
// ── Confirmation modal ────────────────────────────────────────────────────────
// Use instead of window.confirm() for all destructive actions.
// Forms: add data-confirm="message" (and optionally data-confirm-title, data-confirm-ok,
// data-confirm-class) — the interceptor below handles submission automatically.
// JS callers: call confirmModal(title, message, okText, okClass, callback) directly.
function confirmModal(title, message, okText, okClass, onConfirm) {
const modal = document.getElementById('confirm-modal');
if (!modal) { if (window.confirm(message)) onConfirm(); return; }
document.getElementById('confirm-modal-title').textContent = title || 'Confirm Action';
document.getElementById('confirm-modal-body').textContent = message || 'Are you sure?';
const okBtn = document.getElementById('confirm-modal-ok');
okBtn.textContent = okText || 'Confirm';
okBtn.className = 'btn btn-sm ' + (okClass || 'btn-danger');
// Clone to drop any previous click listener
const fresh = okBtn.cloneNode(true);
okBtn.replaceWith(fresh);
const bsModal = new bootstrap.Modal(modal);
fresh.addEventListener('click', () => { bsModal.hide(); onConfirm(); });
bsModal.show();
}
// Auto-intercept any <form data-confirm="..."> or submit button with data-confirm.
document.addEventListener('submit', function(e) {
const form = e.target;
const submitter = e.submitter; // the button that triggered submit
const source = submitter || form;
const msg = source.dataset.confirm || form.dataset.confirm;
if (!msg || form._confirmed) { if (form._confirmed) form._confirmed = false; return; }
e.preventDefault();
confirmModal(
source.dataset.confirmTitle || form.dataset.confirmTitle || 'Confirm Action',
msg,
source.dataset.confirmOk || form.dataset.confirmOk || 'Confirm',
source.dataset.confirmClass || form.dataset.confirmClass || 'btn-danger',
() => {
form._confirmed = true;
form.requestSubmit ? form.requestSubmit(submitter || null) : form.submit();
}
);
}, true);
async function csrfPost(url, body = null) {
const opts = {
method: 'POST',
@@ -1373,9 +1428,9 @@
`style="cursor:${n.link ? 'pointer' : 'default'}">` +
dot +
`<div style="flex:1;min-width:0;">` +
`<div class="notif-title">${n.title}</div>` +
`<div class="notif-msg">${n.message || ''}</div>` +
`<div class="notif-time">${n.created_at}</div>` +
`<div class="notif-title">${_esc(n.title)}</div>` +
`<div class="notif-msg">${_esc(n.message)}</div>` +
`<div class="notif-time">${_esc(n.created_at)}</div>` +
`</div></div>`
);
}
@@ -1459,7 +1514,7 @@
function showToast(title, msg) {
const t = document.createElement('div');
t.style.cssText = 'position:fixed;bottom:90px;right:28px;background:var(--surface);border:1px solid var(--border);border-left:3px solid var(--accent3);border-radius:8px;padding:12px 16px;z-index:300;max-width:300px;box-shadow:0 4px 16px rgba(0,0,0,.4);animation:slideIn .25s ease;';
t.innerHTML = `<div style="font-size:13px;font-weight:600;margin-bottom:4px;">${title}</div><div style="font-size:12px;color:var(--muted);">${msg || ''}</div>`;
t.innerHTML = `<div style="font-size:13px;font-weight:600;margin-bottom:4px;">${_esc(title)}</div><div style="font-size:12px;color:var(--muted);">${_esc(msg)}</div>`;
document.body.appendChild(t);
setTimeout(() => t.remove(), 5000);
}