feat: Chart.js donut/bar charts, Flatpickr dates, skeleton loaders, CSP update
- DashboardView: Charts row with 3 panels (priority donut, status donut, category bar) using Chart.js from CDN; data passed inline from PHP stats; TDS color palette - DashboardView: Flatpickr date picker on advanced search date fields with TDS theme overrides - dashboard.js: showTableSkeleton() shows lt-skeleton-row during filter-triggered reloads and auto-refresh; called before all location.reload() with delay - dashboard.css: Flatpickr TDS theme overrides (dark BG, monospace font, TDS accent colors) - SecurityHeadersMiddleware: Added cdn.jsdelivr.net to script-src and style-src CSP to allow Chart.js and Flatpickr from CDN Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -361,3 +361,39 @@ kbd {
|
||||
letter-spacing: 0.08em;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
/* ── Flatpickr TDS theme overrides ──────────────────────────────── */
|
||||
.flatpickr-calendar {
|
||||
background: var(--bg-secondary, #0a0e14) !important;
|
||||
border: 1px solid var(--border-color, rgba(0,255,65,0.25)) !important;
|
||||
box-shadow: 0 8px 24px rgba(0,0,0,0.6) !important;
|
||||
border-radius: 0 !important;
|
||||
font-family: var(--font-mono, monospace) !important;
|
||||
}
|
||||
.flatpickr-day {
|
||||
color: var(--text-secondary, #8fa3b1) !important;
|
||||
border-radius: 0 !important;
|
||||
}
|
||||
.flatpickr-day.today {
|
||||
border-color: var(--accent-cyan, #00d4ff) !important;
|
||||
color: var(--accent-cyan, #00d4ff) !important;
|
||||
}
|
||||
.flatpickr-day.selected, .flatpickr-day.selected:hover {
|
||||
background: var(--accent-orange, #ff8c00) !important;
|
||||
border-color: var(--accent-orange, #ff8c00) !important;
|
||||
color: #000 !important;
|
||||
}
|
||||
.flatpickr-day:hover {
|
||||
background: rgba(0,212,255,0.1) !important;
|
||||
color: var(--text-primary, #e8f4f8) !important;
|
||||
}
|
||||
.flatpickr-months, .flatpickr-weekdays {
|
||||
background: var(--bg-tertiary, #1a1f2e) !important;
|
||||
}
|
||||
.flatpickr-current-month, .flatpickr-weekday {
|
||||
color: var(--text-muted, #5a7a8a) !important;
|
||||
font-family: var(--font-mono, monospace) !important;
|
||||
}
|
||||
.flatpickr-prev-month svg path, .flatpickr-next-month svg path {
|
||||
fill: var(--text-muted) !important;
|
||||
}
|
||||
|
||||
+25
-7
@@ -535,7 +535,7 @@ function performBulkCloseAction(ticketIds) {
|
||||
} else {
|
||||
lt.toast.success(`Successfully closed ${data.processed} ticket(s)`, 4000);
|
||||
}
|
||||
setTimeout(() => window.location.reload(), 1500);
|
||||
showTableSkeleton(5); setTimeout(() => window.location.reload(), 1500);
|
||||
} else {
|
||||
lt.toast.error('Error: ' + (data.error || 'Unknown error'), 5000);
|
||||
}
|
||||
@@ -631,7 +631,7 @@ function performBulkAssign() {
|
||||
} else {
|
||||
lt.toast.success(`Successfully assigned ${data.processed} ticket(s)`, 4000);
|
||||
}
|
||||
setTimeout(() => window.location.reload(), 1500);
|
||||
showTableSkeleton(5); setTimeout(() => window.location.reload(), 1500);
|
||||
} else {
|
||||
lt.toast.error('Error: ' + (data.error || 'Unknown error'), 5000);
|
||||
}
|
||||
@@ -707,7 +707,7 @@ function performBulkPriority() {
|
||||
} else {
|
||||
lt.toast.success(`Successfully updated priority for ${data.processed} ticket(s)`, 4000);
|
||||
}
|
||||
setTimeout(() => window.location.reload(), 1500);
|
||||
showTableSkeleton(5); setTimeout(() => window.location.reload(), 1500);
|
||||
} else {
|
||||
lt.toast.error('Error: ' + (data.error || 'Unknown error'), 5000);
|
||||
}
|
||||
@@ -810,7 +810,7 @@ function performBulkStatusChange() {
|
||||
} else {
|
||||
lt.toast.success(`Successfully updated status for ${data.processed} ticket(s)`, 4000);
|
||||
}
|
||||
setTimeout(() => window.location.reload(), 1500);
|
||||
showTableSkeleton(5); setTimeout(() => window.location.reload(), 1500);
|
||||
} else {
|
||||
lt.toast.error('Error: ' + (data.error || 'Unknown error'), 5000);
|
||||
}
|
||||
@@ -869,7 +869,7 @@ function performBulkDelete() {
|
||||
closeBulkDeleteModal();
|
||||
if (data.success) {
|
||||
lt.toast.success(`Successfully deleted ${ticketIds.length} ticket(s)`, 4000);
|
||||
setTimeout(() => window.location.reload(), 1500);
|
||||
showTableSkeleton(5); setTimeout(() => window.location.reload(), 1500);
|
||||
} else {
|
||||
lt.toast.error('Error: ' + (data.error || 'Unknown error'), 5000);
|
||||
}
|
||||
@@ -993,7 +993,7 @@ function performQuickStatusChange(ticketId) {
|
||||
closeQuickStatusModal();
|
||||
if (data.success) {
|
||||
lt.toast.success(`Status updated to ${newStatus}`, 3000);
|
||||
setTimeout(() => window.location.reload(), 1000);
|
||||
showTableSkeleton(5); setTimeout(() => window.location.reload(), 1000);
|
||||
} else {
|
||||
lt.toast.error('Error: ' + (data.error || 'Unknown error'), 4000);
|
||||
}
|
||||
@@ -1079,7 +1079,7 @@ function performQuickAssign(ticketId) {
|
||||
closeQuickAssignModal();
|
||||
if (data.success) {
|
||||
lt.toast.success('Assignment updated', 3000);
|
||||
setTimeout(() => window.location.reload(), 1000);
|
||||
showTableSkeleton(5); setTimeout(() => window.location.reload(), 1000);
|
||||
} else {
|
||||
lt.toast.error('Error: ' + (data.error || 'Unknown error'), 4000);
|
||||
}
|
||||
@@ -1455,12 +1455,30 @@ function hideLoadingOverlay(element) {
|
||||
* 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.
|
||||
*/
|
||||
/**
|
||||
* Replace table body rows with skeleton placeholders before a page reload.
|
||||
* Gives visual feedback that a reload is in progress.
|
||||
*/
|
||||
function showTableSkeleton(rowCount) {
|
||||
rowCount = rowCount || 5;
|
||||
const tbody = document.querySelector('#tickets-table tbody');
|
||||
if (!tbody) return;
|
||||
let html = '';
|
||||
for (let i = 0; i < rowCount; i++) {
|
||||
html += '<tr class="lt-skeleton-row" aria-hidden="true">' +
|
||||
'<td><div class="lt-skeleton" style="height:0.8rem;width:100%"></div></td>'.repeat(6) +
|
||||
'</tr>';
|
||||
}
|
||||
tbody.innerHTML = html;
|
||||
}
|
||||
|
||||
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;
|
||||
showTableSkeleton(6);
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user