Fix design system violations: replace off-brand colors with terminal palette

- dashboard.css: replace all hardcoded Tailwind hex colors (#2d3748, #1a202c,
  #e2e8f0, #4a5568, #007cba, #3b82f6 etc.) in dark-mode sections and component
  styles with terminal CSS variables (--bg-*, --text-*, --border-color,
  --terminal-green/amber)
- dashboard.css: fix card-priority colors white/black → var(--bg-primary)
- dashboard.css: fix card-assignee border-radius: 50% → 0 (no circles rule)
- dashboard.css: fix mobile bottom-sheet border-radius: 12px → 0
- dashboard.css: fix search-box focus border (#007cba → var(--terminal-green))
- dashboard.css: fix save-filter button blue (#3b82f6) → terminal green
- dashboard.css: fix search-results-info blue highlight → terminal green
- dashboard.css: fix btn-bulk/btn-secondary dark-mode bootstrap colors → terminal
- ticket.css: replace comprehensive dark-mode Tailwind hex block with CSS vars
- ticket.css: fix status-select white/black text → var(--bg-primary)
- ticket.css: fix status-select.status-resolved hardcoded #28a745 → var(--status-open)
- ticket.css: fix timeline dark-mode hardcoded colors → CSS vars
- ticket.css: fix .slider:before background white → var(--bg-primary)
- ticket.css: fix .btn-danger:hover color white → var(--bg-primary)
- ticket.css: fix visibility-groups-list label border-radius: 4px → 0
- ticket.css: add will-change: opacity to age-warning/age-critical animations
- views: bump CSS version strings to v=20260319c
- views/DashboardView.php: add aria-labels to card view quick action buttons

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-19 22:37:19 -04:00
parent 27075a62ee
commit 2f9af856dc
5 changed files with 122 additions and 142 deletions

View File

@@ -2115,44 +2115,43 @@ input[type="checkbox"]:checked {
.search-box:focus { .search-box:focus {
outline: none; outline: none;
border-color: #007cba; border-color: var(--terminal-green);
} }
.search-btn { .search-btn {
padding: 10px 20px; background: transparent;
background-color: #007cba; color: var(--terminal-green);
color: white; border: 2px solid var(--terminal-green);
border: none; transition: background-color 0.2s ease, color 0.2s ease, border-color 0.2s ease;
border-radius: 0;
cursor: pointer;
font-size: 16px;
transition: background-color 0.3s;
} }
.search-btn:hover { .search-btn:hover {
background-color: #005a87; background: rgba(0, 255, 65, 0.15);
color: var(--terminal-amber);
border-color: var(--terminal-amber);
} }
.clear-search-btn { .clear-search-btn {
padding: 10px 15px; padding: 6px 10px;
background-color: #6c757d; color: var(--text-muted);
color: white;
text-decoration: none; text-decoration: none;
border-radius: 0; font-family: var(--font-mono);
font-size: 14px; font-size: 1rem;
transition: background-color 0.3s; transition: color 0.15s ease;
line-height: 1;
} }
.clear-search-btn:hover { .clear-search-btn:hover {
background-color: #545b62; color: var(--terminal-red);
text-decoration: none;
} }
/* Show search results info */ /* Show search results info */
.search-results-info { .search-results-info {
margin: 10px 0; margin: 10px 0;
padding: 10px; padding: 10px;
background-color: #e7f3ff; background-color: rgba(0, 255, 65, 0.05);
border-left: 4px solid #007cba; border-left: 4px solid var(--terminal-green);
border-radius: 0; border-radius: 0;
} }
@@ -2210,8 +2209,8 @@ input[type="checkbox"]:checked {
margin-top: 10px; margin-top: 10px;
width: 100%; width: 100%;
padding: 8px; padding: 8px;
background: #3b82f6; background: var(--terminal-green);
color: white; color: var(--bg-primary);
border: none; border: none;
border-radius: 0; border-radius: 0;
cursor: pointer; cursor: pointer;
@@ -2825,25 +2824,25 @@ th.sort-desc::after {
/* Dark mode bulk actions */ /* Dark mode bulk actions */
body.dark-mode .bulk-actions-toolbar { body.dark-mode .bulk-actions-toolbar {
--toolbar-bg: #2d3748; --toolbar-bg: var(--bg-tertiary);
--toolbar-border: #ffc107; --toolbar-border: var(--terminal-amber);
--text-primary: #f8f9fa; --text-primary: var(--terminal-green);
background: #2d3748 !important; background: var(--bg-tertiary) !important;
} }
body.dark-mode .bulk-actions-info { body.dark-mode .bulk-actions-info {
color: #ffc107; color: var(--terminal-amber);
font-weight: bold; font-weight: bold;
} }
body.dark-mode .btn-bulk { body.dark-mode .btn-bulk {
--btn-bulk-bg: #0d6efd; --btn-bulk-bg: var(--terminal-green);
--btn-bulk-hover: #0b5ed7; --btn-bulk-hover: var(--text-secondary);
} }
body.dark-mode .btn-secondary { body.dark-mode .btn-secondary {
--btn-secondary-bg: #495057; --btn-secondary-bg: var(--bg-tertiary);
--btn-secondary-hover: #343a40; --btn-secondary-hover: var(--bg-secondary);
} }
/* Modal styles */ /* Modal styles */
@@ -2901,19 +2900,7 @@ body.dark-mode .btn-secondary {
justify-content: flex-end; justify-content: flex-end;
} }
/* Dark mode modal */ /* Dark mode modal — terminal CSS variables already apply; no overrides needed */
body.dark-mode .modal-content {
--bg-primary: #2d3748;
--bg-secondary: #1a202c;
--text-primary: #f7fafc;
--border-color: #4a5568;
}
body.dark-mode .modal-body select {
background: #1a202c;
color: #f7fafc;
border-color: #4a5568;
}
/* Checkbox styling in table */ /* Checkbox styling in table */
.ticket-checkbox { .ticket-checkbox {
@@ -2956,60 +2943,60 @@ body.dark-mode .modal-body select {
} }
} }
/* Comprehensive Dark Mode Fix - Dashboard */ /* Comprehensive Dark Mode Fix - Dashboard (terminal palette) */
body.dark-mode { body.dark-mode {
background: #1a202c !important; background: var(--bg-secondary) !important;
color: #e2e8f0 !important; color: var(--text-primary) !important;
} }
body.dark-mode .dashboard-container, body.dark-mode .dashboard-container,
body.dark-mode .dashboard-content { body.dark-mode .dashboard-content {
background: #1a202c !important; background: var(--bg-secondary) !important;
color: #e2e8f0 !important; color: var(--text-primary) !important;
} }
/* Ensure table has dark background */ /* Ensure table has dark background */
body.dark-mode table { body.dark-mode table {
background: #2d3748 !important; background: var(--bg-tertiary) !important;
} }
body.dark-mode table thead { body.dark-mode table thead {
background: #1a202c !important; background: var(--bg-secondary) !important;
} }
body.dark-mode table tbody tr { body.dark-mode table tbody tr {
background: #2d3748 !important; background: var(--bg-tertiary) !important;
} }
body.dark-mode table tbody tr:hover { body.dark-mode table tbody tr:hover {
background: #374151 !important; background: var(--hover-bg) !important;
} }
body.dark-mode table td, body.dark-mode table td,
body.dark-mode table th { body.dark-mode table th {
color: #e2e8f0 !important; color: var(--text-primary) !important;
border-color: #4a5568 !important; border-color: var(--border-color) !important;
} }
/* Fix search box */ /* Fix search box */
body.dark-mode .search-box, body.dark-mode .search-box,
body.dark-mode input[type="search"], body.dark-mode input[type="search"],
body.dark-mode input[type="text"] { body.dark-mode input[type="text"] {
background: #2d3748 !important; background: var(--bg-tertiary) !important;
color: #e2e8f0 !important; color: var(--text-primary) !important;
border-color: #4a5568 !important; border-color: var(--border-color) !important;
} }
/* Fix any white backgrounds in modals */ /* Fix any white backgrounds in modals */
body.dark-mode .modal-content { body.dark-mode .modal-content {
background: #2d3748 !important; background: var(--bg-tertiary) !important;
color: #e2e8f0 !important; color: var(--text-primary) !important;
} }
/* Fix dropdown menus */ /* Fix dropdown menus */
body.dark-mode select option { body.dark-mode select option {
background: #2d3748 !important; background: var(--bg-tertiary) !important;
color: #e2e8f0 !important; color: var(--text-primary) !important;
} }
/* ===== RESPONSIVE DESIGN - TERMINAL EDITION ===== */ /* ===== RESPONSIVE DESIGN - TERMINAL EDITION ===== */
@@ -4954,11 +4941,11 @@ table td:nth-child(4) {
font-weight: bold; font-weight: bold;
} }
.card-priority.p1 { background: var(--priority-1); color: white; } .card-priority.p1 { background: var(--priority-1); color: var(--bg-primary); }
.card-priority.p2 { background: var(--priority-2); color: black; } .card-priority.p2 { background: var(--priority-2); color: var(--bg-primary); }
.card-priority.p3 { background: var(--priority-3); color: white; } .card-priority.p3 { background: var(--priority-3); color: var(--bg-primary); }
.card-priority.p4 { background: var(--priority-4); color: black; } .card-priority.p4 { background: var(--priority-4); color: var(--bg-primary); }
.card-priority.p5 { background: var(--priority-5); color: white; } .card-priority.p5 { background: var(--priority-5); color: var(--bg-primary); }
.card-title { .card-title {
color: var(--terminal-green); color: var(--terminal-green);
@@ -4990,7 +4977,7 @@ table td:nth-child(4) {
height: 28px; height: 28px;
background: var(--terminal-cyan); background: var(--terminal-cyan);
color: var(--bg-primary); color: var(--bg-primary);
border-radius: 50%; border-radius: 0;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
@@ -5202,7 +5189,7 @@ table td:nth-child(4) {
min-height: 44px; min-height: 44px;
font-size: 0.95rem; font-size: 0.95rem;
background: rgba(0, 255, 65, 0.05); background: rgba(0, 255, 65, 0.05);
border-radius: 4px; border-radius: 0;
} }
.dashboard-sidebar .filter-group label:active { .dashboard-sidebar .filter-group label:active {
@@ -5593,7 +5580,7 @@ table td:nth-child(4) {
width: 100% !important; width: 100% !important;
max-width: 100% !important; max-width: 100% !important;
max-height: 90vh; max-height: 90vh;
border-radius: 12px 12px 0 0; border-radius: 0;
margin: 0; margin: 0;
} }
@@ -5635,7 +5622,7 @@ table td:nth-child(4) {
width: 100% !important; width: 100% !important;
max-width: 100% !important; max-width: 100% !important;
max-height: 85vh; max-height: 85vh;
border-radius: 12px 12px 0 0; border-radius: 0;
} }
/* ===== ADMIN DROPDOWN ===== */ /* ===== ADMIN DROPDOWN ===== */
@@ -5648,7 +5635,7 @@ table td:nth-child(4) {
width: 100%; width: 100%;
max-height: 60vh; max-height: 60vh;
overflow-y: auto; overflow-y: auto;
border-radius: 12px 12px 0 0; border-radius: 0;
z-index: 1002; z-index: 1002;
} }

View File

@@ -347,6 +347,7 @@ textarea[data-field="description"]:not(:disabled)::after {
background: rgba(255, 176, 0, 0.1); background: rgba(255, 176, 0, 0.1);
box-shadow: 0 0 6px rgba(255, 176, 0, 0.4); box-shadow: 0 0 6px rgba(255, 176, 0, 0.4);
animation: pulse-warning 2s ease-in-out infinite; animation: pulse-warning 2s ease-in-out infinite;
will-change: opacity;
} }
.ticket-age.age-critical { .ticket-age.age-critical {
@@ -355,6 +356,7 @@ textarea[data-field="description"]:not(:disabled)::after {
background: rgba(255, 77, 77, 0.15); background: rgba(255, 77, 77, 0.15);
box-shadow: 0 0 8px rgba(255, 77, 77, 0.5); box-shadow: 0 0 8px rgba(255, 77, 77, 0.5);
animation: pulse-critical 1s ease-in-out infinite; animation: pulse-critical 1s ease-in-out infinite;
will-change: opacity;
} }
@keyframes pulse-warning { @keyframes pulse-warning {
@@ -1172,7 +1174,7 @@ textarea.editable {
width: 16px; width: 16px;
left: 4px; left: 4px;
bottom: 4px; bottom: 4px;
background-color: white; background-color: var(--bg-primary);
transition: transform 0.4s ease; transition: transform 0.4s ease;
} }
@@ -1331,24 +1333,24 @@ input:checked + .slider:before {
} }
body.dark-mode .timeline-content { body.dark-mode .timeline-content {
--card-bg: #2d3748; --card-bg: var(--bg-tertiary);
--border-color: #444; --border-color: var(--border-color);
--text-muted: #a0aec0; --text-muted: var(--text-muted);
--text-secondary: #cbd5e0; --text-secondary: var(--text-secondary);
background: #2d3748; background: var(--bg-tertiary);
color: #f7fafc; color: var(--text-primary);
} }
body.dark-mode .timeline-header strong { body.dark-mode .timeline-header strong {
color: #f7fafc; color: var(--text-primary);
} }
body.dark-mode .timeline-action { body.dark-mode .timeline-action {
color: #a0aec0; color: var(--text-muted);
} }
body.dark-mode .timeline-date { body.dark-mode .timeline-date {
color: #718096; color: var(--text-secondary);
} }
/* Status select dropdown */ /* Status select dropdown */
.status-select { .status-select {
@@ -1376,22 +1378,22 @@ body.dark-mode .timeline-date {
/* Status colors for dropdown */ /* Status colors for dropdown */
.status-select.status-open { .status-select.status-open {
background-color: var(--status-open) !important; background-color: var(--status-open) !important;
color: white !important; color: var(--bg-primary) !important;
} }
.status-select.status-in-progress { .status-select.status-in-progress {
background-color: var(--status-in-progress) !important; background-color: var(--status-in-progress) !important;
color: #212529 !important; color: var(--bg-primary) !important;
} }
.status-select.status-closed { .status-select.status-closed {
background-color: var(--status-closed) !important; background-color: var(--status-closed) !important;
color: white !important; color: var(--bg-primary) !important;
} }
.status-select.status-resolved { .status-select.status-resolved {
background-color: #28a745 !important; background-color: var(--status-open) !important;
color: white !important; color: var(--bg-primary) !important;
} }
/* Dropdown options inherit colors */ /* Dropdown options inherit colors */
@@ -1402,8 +1404,8 @@ body.dark-mode .timeline-date {
} }
body.dark-mode .status-select option { body.dark-mode .status-select option {
background-color: #2d3748; background-color: var(--bg-tertiary);
color: #f7fafc; color: var(--text-primary);
} }
/* Dark mode for Activity tab and general improvements */ /* Dark mode for Activity tab and general improvements */
@@ -1420,48 +1422,38 @@ body.dark-mode #activity-tab p {
color: var(--text-primary, #f7fafc); color: var(--text-primary, #f7fafc);
} }
/* Comprehensive Dark Mode Fix - Ensure no white on white */ /* Comprehensive Dark Mode Fix - terminal CSS variables apply throughout */
body.dark-mode {
--bg-primary: #1a202c;
--bg-secondary: #2d3748;
--bg-tertiary: #4a5568;
--text-primary: #e2e8f0;
--text-secondary: #cbd5e0;
--text-muted: #a0aec0;
--border-color: #4a5568;
--card-bg: #2d3748;
}
/* Ensure ticket container has dark background */ /* Ensure ticket container has dark background */
body.dark-mode .ticket-container { body.dark-mode .ticket-container {
background: #1a202c !important; background: var(--bg-secondary) !important;
color: #e2e8f0 !important; color: var(--text-primary) !important;
} }
/* Ensure all ticket details sections are dark */ /* Ensure all ticket details sections are dark */
body.dark-mode .ticket-details { body.dark-mode .ticket-details {
background: #1a202c !important; background: var(--bg-secondary) !important;
color: #e2e8f0 !important; color: var(--text-primary) !important;
} }
/* Ensure detail groups are dark */ /* Ensure detail groups are dark */
body.dark-mode .detail-group { body.dark-mode .detail-group {
background: transparent !important; background: transparent !important;
color: #e2e8f0 !important; color: var(--text-primary) !important;
} }
/* Ensure labels are visible */ /* Ensure labels are visible */
body.dark-mode .detail-group label, body.dark-mode .detail-group label,
body.dark-mode label { body.dark-mode label {
color: #cbd5e0 !important; color: var(--text-secondary) !important;
} }
/* Fix textarea and input fields */ /* Fix textarea and input fields */
body.dark-mode textarea, body.dark-mode textarea,
body.dark-mode input[type="text"] { body.dark-mode input[type="text"] {
background: #2d3748 !important; background: var(--bg-tertiary) !important;
color: #e2e8f0 !important; color: var(--text-primary) !important;
border-color: #4a5568 !important; border-color: var(--border-color) !important;
} }
/* Ensure timeline event backgrounds are dark */ /* Ensure timeline event backgrounds are dark */
@@ -1471,30 +1463,30 @@ body.dark-mode .timeline-event {
/* Fix any remaining white text issues */ /* Fix any remaining white text issues */
body.dark-mode .timeline-details { body.dark-mode .timeline-details {
color: #cbd5e0 !important; color: var(--text-secondary) !important;
background: transparent !important; background: transparent !important;
} }
/* Fix comment sections */ /* Fix comment sections */
body.dark-mode .comment { body.dark-mode .comment {
background: #2d3748 !important; background: var(--bg-tertiary) !important;
color: #e2e8f0 !important; color: var(--text-primary) !important;
} }
body.dark-mode .comment-text { body.dark-mode .comment-text {
color: #e2e8f0 !important; color: var(--text-primary) !important;
} }
body.dark-mode .comment-header { body.dark-mode .comment-header {
color: #cbd5e0 !important; color: var(--text-secondary) !important;
} }
/* Fix any form elements */ /* Fix any form elements */
body.dark-mode select, body.dark-mode select,
body.dark-mode .editable { body.dark-mode .editable {
background: #2d3748 !important; background: var(--bg-tertiary) !important;
color: #e2e8f0 !important; color: var(--text-primary) !important;
border-color: #4a5568 !important; border-color: var(--border-color) !important;
} }
/* ===== RESPONSIVE DESIGN - TERMINAL EDITION ===== */ /* ===== RESPONSIVE DESIGN - TERMINAL EDITION ===== */
@@ -1759,7 +1751,7 @@ body.dark-mode .editable {
.btn-danger:hover { .btn-danger:hover {
background: var(--priority-1); background: var(--priority-1);
color: white; color: var(--bg-primary);
} }
/* Mobile responsiveness for attachments */ /* Mobile responsiveness for attachments */
@@ -2647,7 +2639,7 @@ body.dark-mode .editable {
min-height: 44px; min-height: 44px;
padding: 0.5rem; padding: 0.5rem;
background: rgba(0, 255, 65, 0.05); background: rgba(0, 255, 65, 0.05);
border-radius: 4px; border-radius: 0;
} }
} }

View File

@@ -12,8 +12,8 @@ $nonce = SecurityHeadersMiddleware::getNonce();
<title>Create New Ticket</title> <title>Create New Ticket</title>
<link rel="icon" type="image/png" href="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/images/favicon.png"> <link rel="icon" type="image/png" href="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/images/favicon.png">
<link rel="stylesheet" href="/assets/css/base.css"> <link rel="stylesheet" href="/assets/css/base.css">
<link rel="stylesheet" href="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/css/dashboard.css?v=20260319b"> <link rel="stylesheet" href="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/css/dashboard.css?v=20260319c">
<link rel="stylesheet" href="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/css/ticket.css?v=20260319b"> <link rel="stylesheet" href="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/css/ticket.css?v=20260319c">
<script nonce="<?php echo $nonce; ?>" src="/assets/js/base.js"></script> <script nonce="<?php echo $nonce; ?>" src="/assets/js/base.js"></script>
<script nonce="<?php echo $nonce; ?>" src="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/js/utils.js"></script> <script nonce="<?php echo $nonce; ?>" src="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/js/utils.js"></script>
<script nonce="<?php echo $nonce; ?>" src="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/js/dashboard.js?v=20260205"></script> <script nonce="<?php echo $nonce; ?>" src="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/js/dashboard.js?v=20260205"></script>
@@ -109,11 +109,11 @@ $nonce = SecurityHeadersMiddleware::getNonce();
<input type="text" id="title" name="title" class="editable" required placeholder="Enter a descriptive title for this ticket"> <input type="text" id="title" name="title" class="editable" required placeholder="Enter a descriptive title for this ticket">
</div> </div>
<!-- Duplicate Warning Area --> <!-- Duplicate Warning Area -->
<div id="duplicateWarning" style="display: none; margin-top: 1rem; padding: 1rem; border: 2px solid var(--terminal-amber); background: rgba(241, 196, 15, 0.1);"> <div id="duplicateWarning" role="alert" aria-live="polite" aria-atomic="true" style="display: none; margin-top: 1rem; padding: 1rem; border: 2px solid var(--terminal-amber); background: rgba(241, 196, 15, 0.1);">
<div style="color: var(--terminal-amber); font-weight: bold; margin-bottom: 0.5rem;"> <div style="color: var(--terminal-amber); font-weight: bold; margin-bottom: 0.5rem;">
Possible Duplicates Found Possible Duplicates Found
</div> </div>
<div id="duplicatesList"></div> <div id="duplicatesList" aria-live="polite"></div>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -13,7 +13,7 @@ $nonce = SecurityHeadersMiddleware::getNonce();
<title>Ticket Dashboard</title> <title>Ticket Dashboard</title>
<link rel="icon" type="image/png" href="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/images/favicon.png"> <link rel="icon" type="image/png" href="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/images/favicon.png">
<link rel="stylesheet" href="/assets/css/base.css"> <link rel="stylesheet" href="/assets/css/base.css">
<link rel="stylesheet" href="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/css/dashboard.css?v=20260319b"> <link rel="stylesheet" href="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/css/dashboard.css?v=20260319c">
<script nonce="<?php echo $nonce; ?>" src="/assets/js/base.js"></script> <script nonce="<?php echo $nonce; ?>" src="/assets/js/base.js"></script>
<script nonce="<?php echo $nonce; ?>" src="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/js/ascii-banner.js"></script> <script nonce="<?php echo $nonce; ?>" src="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/js/ascii-banner.js"></script>
<script nonce="<?php echo $nonce; ?>" src="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/js/toast.js"></script> <script nonce="<?php echo $nonce; ?>" src="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/js/toast.js"></script>
@@ -87,7 +87,7 @@ $nonce = SecurityHeadersMiddleware::getNonce();
<span class="user-name">[ <?php echo htmlspecialchars(strtoupper($GLOBALS['currentUser']['display_name'] ?? $GLOBALS['currentUser']['username'])); ?> ]</span> <span class="user-name">[ <?php echo htmlspecialchars(strtoupper($GLOBALS['currentUser']['display_name'] ?? $GLOBALS['currentUser']['username'])); ?> ]</span>
<?php if ($GLOBALS['currentUser']['is_admin']): ?> <?php if ($GLOBALS['currentUser']['is_admin']): ?>
<div class="admin-dropdown"> <div class="admin-dropdown">
<button class="admin-badge" data-action="toggle-admin-menu">[ ADMIN ▼ ]</button> <button class="admin-badge" data-action="toggle-admin-menu" aria-label="Admin menu" aria-haspopup="true" aria-expanded="false">[ ADMIN ▼ ]</button>
<div class="admin-dropdown-content" id="adminDropdown"> <div class="admin-dropdown-content" id="adminDropdown">
<a href="/admin/templates">TEMPLATES</a> <a href="/admin/templates">TEMPLATES</a>
<a href="/admin/workflow">WORKFLOW</a> <a href="/admin/workflow">WORKFLOW</a>
@@ -279,7 +279,7 @@ $nonce = SecurityHeadersMiddleware::getNonce();
<button type="submit" class="btn search-btn">Search</button> <button type="submit" class="btn search-btn">Search</button>
<button type="button" class="btn btn-secondary" data-action="open-advanced-search" title="Advanced Search">FILTER</button> <button type="button" class="btn btn-secondary" data-action="open-advanced-search" title="Advanced Search">FILTER</button>
<?php if (isset($_GET['search']) && !empty($_GET['search'])): ?> <?php if (isset($_GET['search']) && !empty($_GET['search'])): ?>
<a href="?" class="clear-search-btn">✗</a> <a href="?" class="clear-search-btn" aria-label="Clear search">✗</a>
<?php endif; ?> <?php endif; ?>
</form> </form>
</div> </div>
@@ -292,7 +292,7 @@ $nonce = SecurityHeadersMiddleware::getNonce();
</div> </div>
<button data-action="navigate" data-url="<?php echo $GLOBALS['config']['BASE_URL']; ?>/ticket/create" class="btn create-ticket">+ New Ticket</button> <button data-action="navigate" data-url="<?php echo $GLOBALS['config']['BASE_URL']; ?>/ticket/create" class="btn create-ticket">+ New Ticket</button>
<div class="export-dropdown" id="exportDropdown" style="display: none;"> <div class="export-dropdown" id="exportDropdown" style="display: none;">
<button class="btn" data-action="toggle-export-menu">↓ Export Selected (<span id="exportCount">0</span>)</button> <button class="btn" data-action="toggle-export-menu" aria-label="Export selected tickets" aria-haspopup="true" aria-expanded="false">↓ Export Selected (<span id="exportCount">0</span>)</button>
<div class="export-dropdown-content" id="exportDropdownContent"> <div class="export-dropdown-content" id="exportDropdownContent">
<a href="#" data-action="export-tickets" data-format="csv">CSV</a> <a href="#" data-action="export-tickets" data-format="csv">CSV</a>
<a href="#" data-action="export-tickets" data-format="json">JSON</a> <a href="#" data-action="export-tickets" data-format="json">JSON</a>
@@ -311,7 +311,7 @@ $nonce = SecurityHeadersMiddleware::getNonce();
if ($page > 1) { if ($page > 1) {
$currentParams['page'] = $page - 1; $currentParams['page'] = $page - 1;
$prevUrl = '?' . http_build_query($currentParams); $prevUrl = '?' . http_build_query($currentParams);
echo "<button data-action='navigate' data-url='$prevUrl'>«</button>"; echo "<button data-action='navigate' data-url='$prevUrl' aria-label='Previous page'>«</button>";
} }
// Page number buttons // Page number buttons
@@ -326,7 +326,7 @@ $nonce = SecurityHeadersMiddleware::getNonce();
if ($page < $totalPages) { if ($page < $totalPages) {
$currentParams['page'] = $page + 1; $currentParams['page'] = $page + 1;
$nextUrl = '?' . http_build_query($currentParams); $nextUrl = '?' . http_build_query($currentParams);
echo "<button data-action='navigate' data-url='$nextUrl'>»</button>"; echo "<button data-action='navigate' data-url='$nextUrl' aria-label='Next page'>»</button>";
} }
?> ?>
</div> </div>
@@ -394,7 +394,7 @@ $nonce = SecurityHeadersMiddleware::getNonce();
<?php foreach ($activeFilters as $filter): ?> <?php foreach ($activeFilters as $filter): ?>
<span class="filter-badge" data-filter-type="<?php echo htmlspecialchars($filter['type']); ?>" data-filter-value="<?php echo htmlspecialchars($filter['value']); ?>"> <span class="filter-badge" data-filter-type="<?php echo htmlspecialchars($filter['type']); ?>" data-filter-value="<?php echo htmlspecialchars($filter['value']); ?>">
<?php echo htmlspecialchars($filter['label']); ?> <?php echo htmlspecialchars($filter['label']); ?>
<button type="button" class="filter-remove" data-action="remove-filter" data-filter-type="<?php echo htmlspecialchars($filter['type']); ?>" data-filter-value="<?php echo htmlspecialchars($filter['value']); ?>" title="Remove filter">&times;</button> <button type="button" class="filter-remove" data-action="remove-filter" data-filter-type="<?php echo htmlspecialchars($filter['type']); ?>" data-filter-value="<?php echo htmlspecialchars($filter['value']); ?>" title="Remove filter" aria-label="Remove <?php echo htmlspecialchars($filter['label']); ?> filter">&times;</button>
</span> </span>
<?php endforeach; ?> <?php endforeach; ?>
</div> </div>
@@ -408,7 +408,7 @@ $nonce = SecurityHeadersMiddleware::getNonce();
<thead> <thead>
<tr> <tr>
<?php if ($GLOBALS['currentUser']['is_admin'] ?? false): ?> <?php if ($GLOBALS['currentUser']['is_admin'] ?? false): ?>
<th style="width: 40px;"><input type="checkbox" id="selectAllCheckbox" data-action="toggle-select-all"></th> <th style="width: 40px;" scope="col"><input type="checkbox" id="selectAllCheckbox" data-action="toggle-select-all" aria-label="Select all tickets"></th>
<?php endif; ?> <?php endif; ?>
<?php <?php
$currentSort = isset($_GET['sort']) ? $_GET['sort'] : 'ticket_id'; $currentSort = isset($_GET['sort']) ? $_GET['sort'] : 'ticket_id';
@@ -430,13 +430,14 @@ $nonce = SecurityHeadersMiddleware::getNonce();
foreach($columns as $col => $label) { foreach($columns as $col => $label) {
if ($col === '_actions') { if ($col === '_actions') {
echo "<th style='width: 100px; text-align: center;'>$label</th>"; echo "<th scope='col' style='width: 100px; text-align: center;'>$label</th>";
} else { } else {
$newDir = ($currentSort === $col && $currentDir === 'asc') ? 'desc' : 'asc'; $newDir = ($currentSort === $col && $currentDir === 'asc') ? 'desc' : 'asc';
$sortClass = ($currentSort === $col) ? "sort-$currentDir" : ''; $sortClass = ($currentSort === $col) ? "sort-$currentDir" : '';
$ariaSort = ($currentSort === $col) ? "aria-sort='" . ($currentDir === 'asc' ? 'ascending' : 'descending') . "'" : '';
$sortParams = array_merge($_GET, ['sort' => $col, 'dir' => $newDir]); $sortParams = array_merge($_GET, ['sort' => $col, 'dir' => $newDir]);
$sortUrl = '?' . http_build_query($sortParams); $sortUrl = '?' . http_build_query($sortParams);
echo "<th class='$sortClass' data-action='navigate' data-url='$sortUrl'>$label</th>"; echo "<th scope='col' class='$sortClass' data-action='navigate' data-url='$sortUrl' $ariaSort>$label</th>";
} }
} }
?> ?>
@@ -452,7 +453,7 @@ $nonce = SecurityHeadersMiddleware::getNonce();
// Add checkbox column for admins // Add checkbox column for admins
if ($GLOBALS['currentUser']['is_admin'] ?? false) { if ($GLOBALS['currentUser']['is_admin'] ?? false) {
echo "<td data-action='toggle-row-checkbox' class='checkbox-cell'><input type='checkbox' class='ticket-checkbox' value='{$row['ticket_id']}' data-action='update-selection'></td>"; echo "<td data-action='toggle-row-checkbox' class='checkbox-cell'><input type='checkbox' class='ticket-checkbox' value='{$row['ticket_id']}' data-action='update-selection' aria-label='Select ticket {$row[\"ticket_id\"]}'></td>";
} }
echo "<td><a href='/ticket/{$row['ticket_id']}' class='ticket-link'>{$row['ticket_id']}</a></td>"; echo "<td><a href='/ticket/{$row['ticket_id']}' class='ticket-link'>{$row['ticket_id']}</a></td>";
@@ -468,9 +469,9 @@ $nonce = SecurityHeadersMiddleware::getNonce();
// Quick actions column // Quick actions column
echo "<td class='quick-actions-cell'>"; echo "<td class='quick-actions-cell'>";
echo "<div class='quick-actions'>"; echo "<div class='quick-actions'>";
echo "<button data-action='view-ticket' data-ticket-id='{$row['ticket_id']}' class='quick-action-btn' title='View'>&gt;</button>"; echo "<button data-action='view-ticket' data-ticket-id='{$row['ticket_id']}' class='quick-action-btn' title='View' aria-label='View ticket {$row[\"ticket_id\"]}'>&gt;</button>";
echo "<button data-action='quick-status' data-ticket-id='{$row['ticket_id']}' data-status='{$row['status']}' class='quick-action-btn' title='Change Status'>~</button>"; echo "<button data-action='quick-status' data-ticket-id='{$row['ticket_id']}' data-status='{$row['status']}' class='quick-action-btn' title='Change Status' aria-label='Change status for ticket {$row[\"ticket_id\"]}'>~</button>";
echo "<button data-action='quick-assign' data-ticket-id='{$row['ticket_id']}' class='quick-action-btn' title='Assign'>@</button>"; echo "<button data-action='quick-assign' data-ticket-id='{$row['ticket_id']}' class='quick-action-btn' title='Assign' aria-label='Assign ticket {$row[\"ticket_id\"]}'>@</button>";
echo "</div>"; echo "</div>";
echo "</td>"; echo "</td>";
echo "</tr>"; echo "</tr>";
@@ -518,8 +519,8 @@ $nonce = SecurityHeadersMiddleware::getNonce();
<?php echo $row['status']; ?> <?php echo $row['status']; ?>
</div> </div>
<div class="ticket-card-actions"> <div class="ticket-card-actions">
<button data-action="view-ticket" data-ticket-id="<?php echo $row['ticket_id']; ?>" title="View">&gt;</button> <button data-action="view-ticket" data-ticket-id="<?php echo $row['ticket_id']; ?>" title="View" aria-label="View ticket <?php echo $row['ticket_id']; ?>">&gt;</button>
<button data-action="quick-status" data-ticket-id="<?php echo $row['ticket_id']; ?>" data-status="<?php echo $row['status']; ?>" title="Status">~</button> <button data-action="quick-status" data-ticket-id="<?php echo $row['ticket_id']; ?>" data-status="<?php echo $row['status']; ?>" title="Status" aria-label="Change status for ticket <?php echo $row['ticket_id']; ?>">~</button>
</div> </div>
</div> </div>
<?php <?php

View File

@@ -51,8 +51,8 @@ $nonce = SecurityHeadersMiddleware::getNonce();
<title>Ticket #<?php echo $ticket['ticket_id']; ?></title> <title>Ticket #<?php echo $ticket['ticket_id']; ?></title>
<link rel="icon" type="image/png" href="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/images/favicon.png"> <link rel="icon" type="image/png" href="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/images/favicon.png">
<link rel="stylesheet" href="/assets/css/base.css"> <link rel="stylesheet" href="/assets/css/base.css">
<link rel="stylesheet" href="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/css/dashboard.css?v=20260319b"> <link rel="stylesheet" href="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/css/dashboard.css?v=20260319c">
<link rel="stylesheet" href="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/css/ticket.css?v=20260319b"> <link rel="stylesheet" href="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/css/ticket.css?v=20260319c">
<script nonce="<?php echo $nonce; ?>" src="/assets/js/base.js"></script> <script nonce="<?php echo $nonce; ?>" src="/assets/js/base.js"></script>
<script nonce="<?php echo $nonce; ?>" src="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/js/toast.js"></script> <script nonce="<?php echo $nonce; ?>" src="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/js/toast.js"></script>
<script nonce="<?php echo $nonce; ?>" src="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/js/utils.js"></script> <script nonce="<?php echo $nonce; ?>" src="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/js/utils.js"></script>
@@ -244,7 +244,7 @@ $nonce = SecurityHeadersMiddleware::getNonce();
</div> </div>
<div class="header-controls"> <div class="header-controls">
<div class="status-priority-group"> <div class="status-priority-group">
<select id="statusSelect" class="editable status-select status-<?php echo str_replace(' ', '-', strtolower($ticket["status"])); ?>" data-field="status" data-action="update-ticket-status"> <select id="statusSelect" class="editable status-select status-<?php echo str_replace(' ', '-', strtolower($ticket["status"])); ?>" data-field="status" data-action="update-ticket-status" aria-label="Change ticket status">
<option value="<?php echo $ticket['status']; ?>" selected> <option value="<?php echo $ticket['status']; ?>" selected>
<?php echo $ticket['status']; ?> (current) <?php echo $ticket['status']; ?> (current)
</option> </option>
@@ -463,7 +463,7 @@ $nonce = SecurityHeadersMiddleware::getNonce();
<option value="relates_to">Relates To</option> <option value="relates_to">Relates To</option>
<option value="duplicates">Duplicates</option> <option value="duplicates">Duplicates</option>
</select> </select>
<button id="addDependencyBtn" class="btn">Add</button> <button id="addDependencyBtn" class="btn" aria-label="Add ticket dependency">Add</button>
</div> </div>
</div> </div>