diff --git a/assets/css/dashboard.css b/assets/css/dashboard.css index 739d0d9..b9981bd 100644 --- a/assets/css/dashboard.css +++ b/assets/css/dashboard.css @@ -4642,6 +4642,32 @@ table td:nth-child(4) { border-top: 1px solid var(--terminal-green); } +/* ===== DUPLICATE DETECTION LIST ===== */ + +.duplicate-list { + margin: 0; + padding-left: 1.5rem; + color: var(--terminal-green); +} + +.duplicate-list li { + margin-bottom: 0.5rem; +} + +.duplicate-list a { + color: var(--terminal-green); +} + +.duplicate-meta { + color: var(--terminal-amber); +} + +.duplicate-hint { + margin-top: 0.5rem; + font-size: 0.85rem; + color: var(--terminal-green-dim); +} + /* ===== INLINE ALERT BOXES ===== */ .inline-error { diff --git a/assets/js/dashboard.js b/assets/js/dashboard.js index 1ad290e..a7acea3 100644 --- a/assets/js/dashboard.js +++ b/assets/js/dashboard.js @@ -1819,6 +1819,30 @@ function hideLoadingOverlay(element) { } } +// ======================================== +// AUTO-REFRESH (lt.autoRefresh integration) +// ======================================== + +/** + * Reload the dashboard, but skip if a modal is open or user is typing. + * Registered with lt.autoRefresh so it runs every 5 minutes automatically. + */ +function dashboardAutoRefresh() { + // Don't interrupt the user if a modal is open + if (document.querySelector('.lt-modal-overlay[aria-hidden="false"]')) return; + // Don't interrupt if focus is in a text input + const tag = document.activeElement?.tagName; + if (tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT') return; + window.location.reload(); +} + +document.addEventListener('DOMContentLoaded', function() { + // Only run auto-refresh on the dashboard, not on ticket pages + if (!window.location.pathname.includes('/ticket/')) { + lt.autoRefresh.start(dashboardAutoRefresh, 5 * 60 * 1000); + } +}); + // ======================================== // RELATIVE TIMESTAMPS // ======================================== diff --git a/views/CreateTicketView.php b/views/CreateTicketView.php index d0cf543..3387256 100644 --- a/views/CreateTicketView.php +++ b/views/CreateTicketView.php @@ -295,18 +295,18 @@ $nonce = SecurityHeadersMiddleware::getNonce(); const listDiv = document.getElementById('duplicatesList'); if (data.success && data.duplicates && data.duplicates.length > 0) { - let html = '
Consider checking these tickets before creating a new one.
'; + html += 'Consider checking these tickets before creating a new one.
'; listDiv.innerHTML = html; warningDiv.style.display = 'block'; diff --git a/views/DashboardView.php b/views/DashboardView.php index b64d3f5..e65128b 100644 --- a/views/DashboardView.php +++ b/views/DashboardView.php @@ -102,6 +102,7 @@ $nonce = SecurityHeadersMiddleware::getNonce(); + @@ -861,6 +862,10 @@ $nonce = SecurityHeadersMiddleware::getNonce(); openSettingsModal(); break; + case 'manual-refresh': + lt.autoRefresh.now(); + break; + case 'close-settings': closeSettingsModal(); break; diff --git a/views/admin/ApiKeysView.php b/views/admin/ApiKeysView.php index 5dfe967..570f1bb 100644 --- a/views/admin/ApiKeysView.php +++ b/views/admin/ApiKeysView.php @@ -187,7 +187,7 @@ $nonce = SecurityHeadersMiddleware::getNonce(); const expiresIn = document.getElementById('expiresIn').value; if (!keyName) { - showToast('Please enter a key name', 'error'); + lt.toast.error('Please enter a key name'); return; } @@ -212,15 +212,15 @@ $nonce = SecurityHeadersMiddleware::getNonce(); document.getElementById('newKeyDisplay').style.display = 'block'; document.getElementById('keyName').value = ''; - showToast('API key generated successfully', 'success'); + lt.toast.success('API key generated successfully'); // Reload page after 5 seconds to show new key in table setTimeout(() => location.reload(), 5000); } else { - showToast(data.error || 'Failed to generate API key', 'error'); + lt.toast.error(data.error || 'Failed to generate API key'); } } catch (error) { - showToast('Error generating API key: ' + error.message, 'error'); + lt.toast.error('Error generating API key: ' + error.message); } }); @@ -228,7 +228,7 @@ $nonce = SecurityHeadersMiddleware::getNonce(); const keyInput = document.getElementById('newKeyValue'); keyInput.select(); document.execCommand('copy'); - showToast('API key copied to clipboard', 'success'); + lt.toast.success('API key copied to clipboard'); } async function revokeKey(keyId) { @@ -249,13 +249,13 @@ $nonce = SecurityHeadersMiddleware::getNonce(); const data = await response.json(); if (data.success) { - showToast('API key revoked successfully', 'success'); + lt.toast.success('API key revoked successfully'); location.reload(); } else { - showToast(data.error || 'Failed to revoke API key', 'error'); + lt.toast.error(data.error || 'Failed to revoke API key'); } } catch (error) { - showToast('Error revoking API key: ' + error.message, 'error'); + lt.toast.error('Error revoking API key: ' + error.message); } }