Enhance CSS/HTML with semantic elements, utility classes, and breakpoints

- Move inline styles to CSS classes in ticket.css and dashboard.css
- Add intermediate responsive breakpoints (600px, 900px, 1200px)
- Convert HTML to semantic elements (header, section, article)
- Add ARIA attributes for modals and navigation
- Add utility classes for text styling and spacing
- Update cache-busting version numbers

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-31 10:29:20 -05:00
parent 73162d9a9b
commit 2657e86d24
4 changed files with 461 additions and 101 deletions

View File

@@ -7,13 +7,13 @@
/* Terminal Colors */ /* Terminal Colors */
--terminal-green: #00ff41; --terminal-green: #00ff41;
--terminal-green-dim: #00aa2a; --terminal-green-dim: #00cc33;
--terminal-amber: #ffb000; --terminal-amber: #ffb000;
--terminal-cyan: #00ffff; --terminal-cyan: #00ffff;
--terminal-red: #ff4444; --terminal-red: #ff4444;
--text-primary: #00ff41; --text-primary: #00ff41;
--text-secondary: #00cc33; --text-secondary: #00cc33;
--text-muted: #008822; --text-muted: #00bb33; /* Improved contrast ratio for WCAG AA */
/* Border & UI */ /* Border & UI */
--border-color: #00ff41; --border-color: #00ff41;
@@ -58,6 +58,18 @@
/* Transitions */ /* Transitions */
--transition-default: all 0.3s ease; --transition-default: all 0.3s ease;
/* Z-Index Scale - Centralized stacking context management */
--z-base: 1;
--z-dropdown: 100;
--z-sticky: 200;
--z-fixed: 300;
--z-modal-backdrop: 400;
--z-modal: 500;
--z-popover: 600;
--z-tooltip: 700;
--z-toast: 800;
--z-overlay: 9999;
} }
/* Dark theme (now same as light for terminal aesthetic) */ /* Dark theme (now same as light for terminal aesthetic) */
@@ -105,7 +117,7 @@ body::before {
transparent 2px transparent 2px
); );
pointer-events: none; pointer-events: none;
z-index: 9999; z-index: var(--z-overlay);
animation: scanline 8s linear infinite; animation: scanline 8s linear infinite;
} }
@@ -521,6 +533,84 @@ h1 {
margin-left: 3px; margin-left: 3px;
} }
/* ===== INTERMEDIATE RESPONSIVE BREAKPOINTS ===== */
/* Large desktop (1200px+) */
@media (min-width: 1200px) {
.dashboard-layout {
max-width: 1800px;
margin: 0 auto;
}
.stats-widgets .stats-row {
grid-template-columns: repeat(6, 1fr);
}
.kanban-board {
grid-template-columns: repeat(4, 1fr);
}
}
/* Medium desktop (900px - 1199px) */
@media (min-width: 900px) and (max-width: 1199px) {
.stats-widgets .stats-row {
grid-template-columns: repeat(3, 1fr);
}
.dashboard-toolbar {
flex-wrap: wrap;
gap: 1rem;
}
.toolbar-center {
order: 3;
width: 100%;
justify-content: flex-start;
}
}
/* Tablet landscape (600px - 899px) */
@media (min-width: 600px) and (max-width: 899px) {
.stats-widgets .stats-row {
grid-template-columns: repeat(2, 1fr);
}
.dashboard-toolbar {
flex-direction: column;
align-items: stretch;
}
.toolbar-left,
.toolbar-center,
.toolbar-right {
width: 100%;
}
.toolbar-search {
width: 100%;
}
.search-box {
flex: 1;
}
.dashboard-sidebar {
width: 200px;
}
.kanban-board {
grid-template-columns: repeat(2, 1fr);
}
table {
font-size: 0.85rem;
}
th, td {
padding: 0.5rem;
}
}
/* Responsive design for smaller screens */ /* Responsive design for smaller screens */
@media (max-width: 768px) { @media (max-width: 768px) {
.user-header { .user-header {
@@ -597,7 +687,7 @@ h1 {
font-size: 1.5rem; font-size: 1.5rem;
color: var(--terminal-green); color: var(--terminal-green);
line-height: 1; line-height: 1;
z-index: 10; z-index: var(--z-base);
} }
.ascii-frame-outer::after { .ascii-frame-outer::after {
@@ -608,7 +698,7 @@ h1 {
font-size: 1.5rem; font-size: 1.5rem;
color: var(--terminal-green); color: var(--terminal-green);
line-height: 1; line-height: 1;
z-index: 10; z-index: var(--z-base);
} }
/* Bottom corners as separate elements with enhanced glow */ /* Bottom corners as separate elements with enhanced glow */
@@ -620,7 +710,7 @@ h1 {
color: var(--terminal-green); color: var(--terminal-green);
text-shadow: var(--glow-green); text-shadow: var(--glow-green);
line-height: 1; line-height: 1;
z-index: 10; z-index: var(--z-base);
animation: corner-pulse 4s ease-in-out infinite; animation: corner-pulse 4s ease-in-out infinite;
} }
@@ -1068,7 +1158,7 @@ h1 {
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
z-index: 100; z-index: var(--z-dropdown);
font-family: var(--font-mono); font-family: var(--font-mono);
color: var(--terminal-green); color: var(--terminal-green);
} }
@@ -1323,7 +1413,7 @@ table::before {
color: var(--terminal-green); color: var(--terminal-green);
text-shadow: var(--glow-green); text-shadow: var(--glow-green);
line-height: 1; line-height: 1;
z-index: 10; z-index: var(--z-base);
} }
table::after { table::after {
@@ -1335,7 +1425,7 @@ table::after {
color: var(--terminal-green); color: var(--terminal-green);
text-shadow: var(--glow-green); text-shadow: var(--glow-green);
line-height: 1; line-height: 1;
z-index: 10; z-index: var(--z-base);
} }
th, td { th, td {
@@ -1833,7 +1923,7 @@ input[type="checkbox"]:checked {
box-shadow: var(--shadow); box-shadow: var(--shadow);
border-radius: 0; border-radius: 0;
padding: 10px; padding: 10px;
z-index: 100; z-index: var(--z-dropdown);
border: 1px solid var(--border-color); border: 1px solid var(--border-color);
} }
@@ -2295,7 +2385,7 @@ input[type="checkbox"]:checked {
width: 100%; width: 100%;
height: 100%; height: 100%;
background: var(--bg-primary); background: var(--bg-primary);
z-index: 99999; z-index: var(--z-modal-backdrop);
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
@@ -2318,7 +2408,7 @@ input[type="checkbox"]:checked {
top: 50%; top: 50%;
left: 50%; left: 50%;
transform: translate(-50%, -50%); transform: translate(-50%, -50%);
z-index: 10000; z-index: var(--z-modal);
background: var(--bg-primary); background: var(--bg-primary);
border: 3px solid var(--terminal-green); border: 3px solid var(--terminal-green);
padding: 2rem; padding: 2rem;
@@ -2478,7 +2568,7 @@ body.dark-mode .btn-secondary {
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
z-index: 10000; z-index: var(--z-modal);
} }
.modal-content { .modal-content {
@@ -2920,7 +3010,7 @@ body.dark-mode select option {
color: var(--terminal-green); color: var(--terminal-green);
text-shadow: var(--glow-green); text-shadow: var(--glow-green);
box-shadow: 0 0 20px rgba(0, 255, 65, 0.3); box-shadow: 0 0 20px rgba(0, 255, 65, 0.3);
z-index: 10001; z-index: var(--z-toast);
opacity: 0; opacity: 0;
transform: translateX(400px); transform: translateX(400px);
transition: all 0.3s ease; transition: all 0.3s ease;
@@ -2994,7 +3084,7 @@ body.dark-mode select option {
left: 0; left: 0;
width: 100vw; width: 100vw;
height: 100vh; height: 100vh;
z-index: 10000; z-index: var(--z-modal);
background: rgba(0, 0, 0, 0.85); background: rgba(0, 0, 0, 0.85);
backdrop-filter: blur(5px); backdrop-filter: blur(5px);
display: flex; display: flex;
@@ -3185,6 +3275,80 @@ body.dark-mode select option {
cursor: pointer; cursor: pointer;
} }
/* Setting select width variants */
.setting-select-wide {
max-width: 70%;
}
.setting-select-full {
max-width: 100%;
}
.setting-select-narrow {
max-width: 90px;
}
/* Setting row layout variants */
.setting-row-right {
justify-content: flex-end;
}
/* Button size variants for settings */
.btn-setting {
padding: 0.5rem 1rem;
}
/* Utility text classes */
.text-muted {
color: var(--text-muted);
}
.text-small {
font-size: 0.85rem;
}
.text-center {
text-align: center;
}
/* Utility spacing classes */
.mt-sm {
margin-top: 0.25rem;
}
.display-block {
display: block;
}
/* Empty state styling */
.empty-state {
text-align: center;
padding: 3rem;
}
.empty-state-terminal {
color: var(--terminal-green);
text-shadow: var(--glow-green);
font-size: 0.8rem;
line-height: 1.2;
}
/* Separator text */
.separator-text {
color: var(--terminal-green);
}
/* Checkbox column width */
.checkbox-column {
width: 40px;
}
/* Actions column width */
.actions-column {
width: 100px;
text-align: center;
}
.setting-select:focus { .setting-select:focus {
outline: none; outline: none;
border-color: var(--terminal-amber); border-color: var(--terminal-amber);
@@ -3481,7 +3645,7 @@ code.inline-code {
left: 0; left: 0;
width: 100vw; width: 100vw;
height: 100vh; height: 100vh;
z-index: 10000; z-index: var(--z-modal);
background: rgba(0, 0, 0, 0.85); background: rgba(0, 0, 0, 0.85);
backdrop-filter: blur(5px); backdrop-filter: blur(5px);
display: flex; display: flex;
@@ -3852,7 +4016,7 @@ tr:hover .quick-actions {
background: var(--bg-primary); background: var(--bg-primary);
border: 2px solid var(--terminal-green); border: 2px solid var(--terminal-green);
min-width: 120px; min-width: 120px;
z-index: 100; z-index: var(--z-dropdown);
box-shadow: 0 0 10px rgba(0, 255, 65, 0.3); box-shadow: 0 0 10px rgba(0, 255, 65, 0.3);
} }
@@ -3894,7 +4058,7 @@ tr:hover .quick-actions {
background: var(--bg-primary); background: var(--bg-primary);
border: 2px solid var(--priority-1); border: 2px solid var(--priority-1);
min-width: 180px; min-width: 180px;
z-index: 1000; z-index: var(--z-popover);
box-shadow: 0 0 15px rgba(255, 77, 77, 0.3); box-shadow: 0 0 15px rgba(255, 77, 77, 0.3);
} }
@@ -4033,7 +4197,7 @@ table td:nth-child(4) {
/* ===== INLINE TICKET PREVIEW POPUP ===== */ /* ===== INLINE TICKET PREVIEW POPUP ===== */
.ticket-preview-popup { .ticket-preview-popup {
position: absolute; position: absolute;
z-index: 10000; z-index: var(--z-modal);
width: 320px; width: 320px;
background: var(--bg-primary); background: var(--bg-primary);
border: 2px solid var(--terminal-green); border: 2px solid var(--terminal-green);
@@ -4533,7 +4697,7 @@ table td:nth-child(4) {
.user-header { .user-header {
position: sticky; position: sticky;
top: 0; top: 0;
z-index: 100; z-index: var(--z-dropdown);
padding: 0.5rem 0.75rem; padding: 0.5rem 0.75rem;
flex-wrap: nowrap; flex-wrap: nowrap;
gap: 0.5rem; gap: 0.5rem;
@@ -4684,7 +4848,7 @@ table td:nth-child(4) {
width: 100%; width: 100%;
height: 100%; height: 100%;
background: rgba(0, 0, 0, 0.7); background: rgba(0, 0, 0, 0.7);
z-index: 1000; z-index: var(--z-popover);
} }
.mobile-sidebar-overlay.active { .mobile-sidebar-overlay.active {
@@ -4706,7 +4870,7 @@ table td:nth-child(4) {
cursor: pointer; cursor: pointer;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
z-index: 10; z-index: var(--z-base);
} }
/* ===== MAIN CONTENT AREA ===== */ /* ===== MAIN CONTENT AREA ===== */
@@ -5076,7 +5240,7 @@ table td:nth-child(4) {
height: 60px; height: 60px;
background: var(--bg-primary); background: var(--bg-primary);
border-top: 2px solid var(--terminal-green); border-top: 2px solid var(--terminal-green);
z-index: 100; z-index: var(--z-dropdown);
padding: 0; padding: 0;
} }

View File

@@ -1635,6 +1635,10 @@ body.dark-mode .editable {
margin-top: 0.5rem; margin-top: 0.5rem;
} }
.upload-browse-btn {
margin-top: 1rem;
}
/* Progress Bar */ /* Progress Bar */
.progress-bar { .progress-bar {
width: 100%; width: 100%;
@@ -1798,7 +1802,7 @@ body.dark-mode .editable {
border-radius: 0; border-radius: 0;
max-height: 200px; max-height: 200px;
overflow-y: auto; overflow-y: auto;
z-index: 1000; z-index: var(--z-popover);
display: none; display: none;
min-width: 200px; min-width: 200px;
box-shadow: 0 0 10px rgba(0, 255, 65, 0.3); box-shadow: 0 0 10px rgba(0, 255, 65, 0.3);
@@ -1893,7 +1897,7 @@ body.dark-mode .editable {
background: var(--bg-primary); background: var(--bg-primary);
border: 2px solid var(--terminal-green); border: 2px solid var(--terminal-green);
min-width: 150px; min-width: 150px;
z-index: 100; z-index: var(--z-dropdown);
box-shadow: 0 0 10px rgba(0, 255, 65, 0.3); box-shadow: 0 0 10px rgba(0, 255, 65, 0.3);
} }
@@ -2028,6 +2032,196 @@ body.dark-mode .editable {
} }
} }
/* ===== INLINE STYLE REPLACEMENTS ===== */
/* These classes replace inline styles from TicketView.php */
.ticket-user-info {
font-size: 0.85rem;
color: var(--text-muted);
margin-top: 0.25rem;
}
.ticket-assignment {
margin-top: 0.5rem;
}
.ticket-assignment-label {
font-weight: 500;
margin-right: 0.5rem;
}
.assignment-select {
padding: 0.25rem 0.5rem;
border-radius: 0;
border: 2px solid var(--terminal-green);
}
/* Visibility settings section */
.ticket-visibility-settings {
margin-top: 0.75rem;
padding-top: 0.75rem;
border-top: 1px solid var(--terminal-green);
}
.visibility-settings-grid {
display: grid;
grid-template-columns: 1fr 2fr;
gap: 0.75rem;
}
.visibility-groups-edit {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
}
.visibility-group-label {
display: flex;
align-items: center;
gap: 0.3rem;
cursor: pointer;
}
/* Metadata labels */
.metadata-label {
font-weight: 500;
display: block;
margin-bottom: 0.25rem;
color: var(--terminal-amber);
font-family: var(--font-mono);
font-size: 0.85rem;
}
.metadata-label-cyan {
color: var(--terminal-cyan);
}
/* Generic full-width select styling */
.full-width-select {
width: 100%;
padding: 0.25rem 0.5rem;
border-radius: 0;
border: 2px solid var(--terminal-green);
background: var(--bg-primary);
color: var(--terminal-green);
font-family: var(--font-mono);
}
/* Add dependency form */
.add-dependency-form {
display: flex;
gap: 0.5rem;
flex-wrap: wrap;
align-items: center;
}
.dependency-input {
flex: 1;
min-width: 150px;
padding: 0.5rem;
border: 2px solid var(--terminal-green);
background: var(--bg-primary);
color: var(--terminal-green);
font-family: var(--font-mono);
}
.dependency-type-select {
padding: 0.5rem;
border: 2px solid var(--terminal-green);
background: var(--bg-primary);
color: var(--terminal-green);
font-family: var(--font-mono);
}
/* Upload progress */
.upload-progress {
margin-top: 1rem;
}
.upload-status-text {
margin-top: 0.5rem;
color: var(--terminal-green);
font-family: var(--font-mono);
font-size: 0.85rem;
}
/* Frame inner spacing */
.frame-inner-spacing {
margin-bottom: 1rem;
}
.frame-inner-spacing-top {
margin-top: 1rem;
}
/* No groups message */
.no-groups-message {
color: var(--text-muted);
font-size: 0.85rem;
}
/* ===== INTERMEDIATE RESPONSIVE BREAKPOINTS ===== */
/* Large desktop (1200px+) */
@media (min-width: 1200px) {
.ticket-container {
max-width: 1600px;
}
.ticket-metadata-fields {
grid-template-columns: repeat(4, 1fr);
}
.visibility-settings-grid {
grid-template-columns: 1fr 3fr;
}
}
/* Medium desktop (900px - 1199px) */
@media (min-width: 900px) and (max-width: 1199px) {
.ticket-container {
width: 95%;
max-width: 1200px;
}
.ticket-metadata-fields {
grid-template-columns: repeat(3, 1fr);
}
}
/* Tablet landscape (600px - 899px) */
@media (min-width: 600px) and (max-width: 899px) {
.ticket-container {
width: 98%;
min-width: unset;
}
.ticket-metadata-fields {
grid-template-columns: repeat(2, 1fr);
}
.visibility-settings-grid {
grid-template-columns: 1fr;
}
.header-controls {
flex-wrap: wrap;
}
.tab-btn {
padding: 10px 18px;
font-size: 0.95em;
}
.timeline-event {
margin-bottom: 1.25rem;
}
.comment {
padding: 12px;
}
}
/* ===== COMPREHENSIVE TICKET PAGE MOBILE STYLES ===== */ /* ===== COMPREHENSIVE TICKET PAGE MOBILE STYLES ===== */
@media (max-width: 768px) { @media (max-width: 768px) {
/* Prevent iOS zoom on input focus */ /* Prevent iOS zoom on input focus */
@@ -2044,7 +2238,7 @@ body.dark-mode .editable {
.user-header { .user-header {
position: sticky; position: sticky;
top: 0; top: 0;
z-index: 100; z-index: var(--z-dropdown);
padding: 0.5rem 0.75rem; padding: 0.5rem 0.75rem;
} }

View File

@@ -12,11 +12,11 @@ $nonce = SecurityHeadersMiddleware::getNonce();
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<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="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/css/dashboard.css?v=20260126c"> <link rel="stylesheet" href="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/css/dashboard.css?v=20260131a">
<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>
<script nonce="<?php echo $nonce; ?>" src="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/js/markdown.js?v=20260124e"></script> <script nonce="<?php echo $nonce; ?>" src="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/js/markdown.js?v=20260131a"></script>
<script nonce="<?php echo $nonce; ?>" src="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/js/dashboard.js?v=20260124e"></script> <script nonce="<?php echo $nonce; ?>" src="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/js/dashboard.js?v=20260131a"></script>
<script nonce="<?php echo $nonce; ?>"> <script nonce="<?php echo $nonce; ?>">
// CSRF Token for AJAX requests // CSRF Token for AJAX requests
window.CSRF_TOKEN = '<?php echo CsrfMiddleware::getToken(); ?>'; window.CSRF_TOKEN = '<?php echo CsrfMiddleware::getToken(); ?>';
@@ -75,9 +75,9 @@ $nonce = SecurityHeadersMiddleware::getNonce();
document.getElementById('boot-sequence').remove(); document.getElementById('boot-sequence').remove();
} }
</script> </script>
<div class="user-header"> <header class="user-header" role="banner">
<div class="user-header-left"> <div class="user-header-left">
<a href="/" class="app-title" style="text-decoration: none; color: inherit;">🎫 Tinker Tickets</a> <a href="/" class="app-title">🎫 Tinker Tickets</a>
</div> </div>
<div class="user-header-right"> <div class="user-header-right">
<?php if (isset($GLOBALS['currentUser'])): ?> <?php if (isset($GLOBALS['currentUser'])): ?>
@@ -99,7 +99,7 @@ $nonce = SecurityHeadersMiddleware::getNonce();
<button class="settings-icon" title="Settings (Alt+S)" data-action="open-settings" aria-label="Settings">⚙</button> <button class="settings-icon" title="Settings (Alt+S)" data-action="open-settings" aria-label="Settings">⚙</button>
<?php endif; ?> <?php endif; ?>
</div> </div>
</div> </header>
<!-- Collapsible ASCII Banner --> <!-- Collapsible ASCII Banner -->
<div class="ascii-banner-wrapper collapsed"> <div class="ascii-banner-wrapper collapsed">
@@ -126,8 +126,8 @@ $nonce = SecurityHeadersMiddleware::getNonce();
<!-- Dashboard Layout with Sidebar --> <!-- Dashboard Layout with Sidebar -->
<div class="dashboard-layout" id="dashboardLayout"> <div class="dashboard-layout" id="dashboardLayout">
<!-- Left Sidebar with Filters --> <!-- Left Sidebar with Filters -->
<aside class="dashboard-sidebar" id="dashboardSidebar"> <aside class="dashboard-sidebar" id="dashboardSidebar" role="complementary" aria-label="Filter options">
<button class="sidebar-collapse-btn" data-action="toggle-sidebar" title="Collapse Sidebar">◀ Hide</button> <button class="sidebar-collapse-btn" data-action="toggle-sidebar" title="Collapse Sidebar" aria-expanded="true" aria-controls="dashboardSidebar">◀ Hide</button>
<div class="sidebar-content"> <div class="sidebar-content">
<div class="ascii-frame-inner"> <div class="ascii-frame-inner">
<div class="ascii-subsection-header">Filters</div> <div class="ascii-subsection-header">Filters</div>
@@ -190,7 +190,7 @@ $nonce = SecurityHeadersMiddleware::getNonce();
</div> </div>
</aside> </aside>
<!-- Expand button shown when sidebar is collapsed --> <!-- Expand button shown when sidebar is collapsed -->
<button class="sidebar-expand-btn" id="sidebarExpandBtn" data-action="toggle-sidebar" title="Show Filters">▶ Filters</button> <button class="sidebar-expand-btn" id="sidebarExpandBtn" data-action="toggle-sidebar" title="Show Filters" aria-expanded="false" aria-controls="dashboardSidebar">▶ Filters</button>
<!-- Main Content Area --> <!-- Main Content Area -->
<main class="dashboard-main"> <main class="dashboard-main">
@@ -338,7 +338,7 @@ $nonce = SecurityHeadersMiddleware::getNonce();
<?php endif; ?> <?php endif; ?>
<!-- TICKET TABLE WITH INLINE BULK ACTIONS --> <!-- TICKET TABLE WITH INLINE BULK ACTIONS -->
<div class="ascii-frame-outer"> <section class="ascii-frame-outer" aria-label="Ticket list">
<span class="bottom-left-corner">╚</span> <span class="bottom-left-corner">╚</span>
<span class="bottom-right-corner">╝</span> <span class="bottom-right-corner">╝</span>
@@ -492,11 +492,11 @@ $nonce = SecurityHeadersMiddleware::getNonce();
</div><!-- End table-wrapper --> </div><!-- End table-wrapper -->
</div> </div>
</div> </div>
</div> </section>
<!-- END OUTER FRAME --> <!-- END OUTER FRAME -->
<!-- Kanban Card View --> <!-- Kanban Card View -->
<div id="cardView" class="card-view-container" style="display: none;"> <section id="cardView" class="card-view-container" style="display: none;" aria-label="Kanban board view">
<div class="kanban-board"> <div class="kanban-board">
<div class="kanban-column" data-status="Open"> <div class="kanban-column" data-status="Open">
<div class="kanban-column-header status-Open"> <div class="kanban-column-header status-Open">
@@ -527,17 +527,17 @@ $nonce = SecurityHeadersMiddleware::getNonce();
<div class="kanban-cards"></div> <div class="kanban-cards"></div>
</div> </div>
</div> </div>
</div> </section>
<!-- Settings Modal --> <!-- Settings Modal -->
<div class="settings-modal" id="settingsModal" style="display: none;" data-action="close-settings-backdrop"> <div class="settings-modal" id="settingsModal" style="display: none;" data-action="close-settings-backdrop" role="dialog" aria-modal="true" aria-labelledby="settingsModalTitle">
<div class="settings-content"> <div class="settings-content">
<span class="bottom-left-corner">╚</span> <span class="bottom-left-corner">╚</span>
<span class="bottom-right-corner">╝</span> <span class="bottom-right-corner">╝</span>
<div class="settings-header"> <div class="settings-header">
<h3>⚙ System Preferences</h3> <h3 id="settingsModalTitle">⚙ System Preferences</h3>
<button class="close-settings" data-action="close-settings">✗</button> <button class="close-settings" data-action="close-settings" aria-label="Close settings">✗</button>
</div> </div>
<div class="settings-body"> <div class="settings-body">
@@ -592,7 +592,7 @@ $nonce = SecurityHeadersMiddleware::getNonce();
<option value="Asia/Kolkata">India (IST)</option> <option value="Asia/Kolkata">India (IST)</option>
<option value="Australia/Sydney">Sydney (AEST/AEDT)</option> <option value="Australia/Sydney">Sydney (AEST/AEDT)</option>
</select> </select>
<small style="color: var(--text-muted); margin-top: 0.25rem; display: block;"> <small class="text-muted mt-sm display-block">
Current: <?php echo $GLOBALS['config']['TIMEZONE_ABBREV']; ?> (<?php echo $GLOBALS['config']['TIMEZONE']; ?>) Current: <?php echo $GLOBALS['config']['TIMEZONE_ABBREV']; ?> (<?php echo $GLOBALS['config']['TIMEZONE']; ?>)
</small> </small>
</div> </div>
@@ -674,7 +674,7 @@ $nonce = SecurityHeadersMiddleware::getNonce();
endforeach; endforeach;
if (empty(trim($GLOBALS['currentUser']['groups'] ?? ''))): if (empty(trim($GLOBALS['currentUser']['groups'] ?? ''))):
?> ?>
<span style="color: var(--text-muted);">No groups assigned</span> <span class="text-muted">No groups assigned</span>
<?php endif; ?> <?php endif; ?>
</div> </div>
</div> </div>
@@ -689,11 +689,11 @@ $nonce = SecurityHeadersMiddleware::getNonce();
</div> </div>
<!-- Advanced Search Modal --> <!-- Advanced Search Modal -->
<div class="settings-modal" id="advancedSearchModal" style="display: none;" data-action="close-advanced-search-backdrop"> <div class="settings-modal" id="advancedSearchModal" style="display: none;" data-action="close-advanced-search-backdrop" role="dialog" aria-modal="true" aria-labelledby="advancedSearchModalTitle">
<div class="settings-content"> <div class="settings-content">
<div class="settings-header"> <div class="settings-header">
<h3>🔍 Advanced Search</h3> <h3 id="advancedSearchModalTitle">🔍 Advanced Search</h3>
<button class="close-settings" data-action="close-advanced-search">✗</button> <button class="close-settings" data-action="close-advanced-search" aria-label="Close advanced search">✗</button>
</div> </div>
<form id="advancedSearchForm"> <form id="advancedSearchForm">
@@ -703,13 +703,13 @@ $nonce = SecurityHeadersMiddleware::getNonce();
<h4>╔══ Saved Filters ══╗</h4> <h4>╔══ Saved Filters ══╗</h4>
<div class="setting-row"> <div class="setting-row">
<label for="saved-filters-select">Load Filter:</label> <label for="saved-filters-select">Load Filter:</label>
<select id="saved-filters-select" class="setting-select" style="max-width: 70%;" data-action="load-saved-filter"> <select id="saved-filters-select" class="setting-select setting-select-wide" data-action="load-saved-filter">
<option value="">-- Select a saved filter --</option> <option value="">-- Select a saved filter --</option>
</select> </select>
</div> </div>
<div class="setting-row" style="justify-content: flex-end; gap: 0.5rem;"> <div class="setting-row setting-row-right">
<button type="button" class="btn btn-secondary" data-action="save-filter" style="padding: 0.5rem 1rem;">💾 Save Current</button> <button type="button" class="btn btn-secondary btn-setting" data-action="save-filter">💾 Save Current</button>
<button type="button" class="btn btn-secondary" data-action="delete-filter" style="padding: 0.5rem 1rem;">🗑 Delete Selected</button> <button type="button" class="btn btn-secondary btn-setting" data-action="delete-filter">🗑 Delete Selected</button>
</div> </div>
</div> </div>
@@ -718,7 +718,7 @@ $nonce = SecurityHeadersMiddleware::getNonce();
<h4>╔══ Search Criteria ══╗</h4> <h4>╔══ Search Criteria ══╗</h4>
<div class="setting-row"> <div class="setting-row">
<label for="adv-search-text">Search Text:</label> <label for="adv-search-text">Search Text:</label>
<input type="text" id="adv-search-text" class="setting-select" style="max-width: 100%;" placeholder="Search in title, description..."> <input type="text" id="adv-search-text" class="setting-select setting-select-full" placeholder="Search in title, description...">
</div> </div>
</div> </div>
@@ -757,7 +757,7 @@ $nonce = SecurityHeadersMiddleware::getNonce();
</div> </div>
<div class="setting-row"> <div class="setting-row">
<label for="adv-priority-min">Priority Range:</label> <label for="adv-priority-min">Priority Range:</label>
<select id="adv-priority-min" class="setting-select" style="max-width: 90px;"> <select id="adv-priority-min" class="setting-select setting-select-narrow">
<option value="">Any</option> <option value="">Any</option>
<option value="1">P1</option> <option value="1">P1</option>
<option value="2">P2</option> <option value="2">P2</option>
@@ -765,8 +765,8 @@ $nonce = SecurityHeadersMiddleware::getNonce();
<option value="4">P4</option> <option value="4">P4</option>
<option value="5">P5</option> <option value="5">P5</option>
</select> </select>
<span style="color: var(--terminal-green);">to</span> <span class="separator-text">to</span>
<select id="adv-priority-max" class="setting-select" style="max-width: 90px;"> <select id="adv-priority-max" class="setting-select setting-select-narrow">
<option value="">Any</option> <option value="">Any</option>
<option value="1">P1</option> <option value="1">P1</option>
<option value="2">P2</option> <option value="2">P2</option>

View File

@@ -50,12 +50,12 @@ $nonce = SecurityHeadersMiddleware::getNonce();
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<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="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/css/dashboard.css?v=20260126c"> <link rel="stylesheet" href="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/css/dashboard.css?v=20260131a">
<link rel="stylesheet" href="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/css/ticket.css?v=20260124e"> <link rel="stylesheet" href="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/css/ticket.css?v=20260131a">
<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/markdown.js?v=20260124e"></script> <script nonce="<?php echo $nonce; ?>" src="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/js/markdown.js?v=20260131a"></script>
<script nonce="<?php echo $nonce; ?>" src="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/js/dashboard.js?v=20260124e"></script> <script nonce="<?php echo $nonce; ?>" src="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/js/dashboard.js?v=20260131a"></script>
<script nonce="<?php echo $nonce; ?>" src="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/js/ticket.js?v=20260124e"></script> <script nonce="<?php echo $nonce; ?>" src="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/js/ticket.js?v=20260131a"></script>
<script nonce="<?php echo $nonce; ?>"> <script nonce="<?php echo $nonce; ?>">
// CSRF Token for AJAX requests // CSRF Token for AJAX requests
window.CSRF_TOKEN = '<?php echo CsrfMiddleware::getToken(); ?>'; window.CSRF_TOKEN = '<?php echo CsrfMiddleware::getToken(); ?>';
@@ -77,7 +77,7 @@ $nonce = SecurityHeadersMiddleware::getNonce();
</script> </script>
</head> </head>
<body> <body>
<div class="user-header"> <header class="user-header">
<div class="user-header-left"> <div class="user-header-left">
<a href="/" class="back-link">← Dashboard</a> <a href="/" class="back-link">← Dashboard</a>
</div> </div>
@@ -90,8 +90,10 @@ $nonce = SecurityHeadersMiddleware::getNonce();
<button class="settings-icon" title="Settings (Alt+S)" id="settingsBtn" aria-label="Settings">⚙</button> <button class="settings-icon" title="Settings (Alt+S)" id="settingsBtn" aria-label="Settings">⚙</button>
<?php endif; ?> <?php endif; ?>
</div> </div>
</div> </header>
<div class="ticket-container ascii-frame-outer" data-priority="<?php echo $ticket["priority"]; ?>"> <main>
<h1 class="sr-only">Ticket: <?php echo htmlspecialchars($ticket["title"]); ?></h1>
<article class="ticket-container ascii-frame-outer" data-priority="<?php echo $ticket["priority"]; ?>">
<span class="bottom-left-corner">╚</span> <span class="bottom-left-corner">╚</span>
<span class="bottom-right-corner">╝</span> <span class="bottom-right-corner">╝</span>
@@ -132,7 +134,7 @@ $nonce = SecurityHeadersMiddleware::getNonce();
<span class="age-icon"><?php echo $ageClass === 'age-critical' ? '⚠️' : ($ageClass === 'age-warning' ? '⏰' : '📅'); ?></span> <span class="age-icon"><?php echo $ageClass === 'age-critical' ? '⚠️' : ($ageClass === 'age-warning' ? '⏰' : '📅'); ?></span>
<span class="age-text">Last activity: <?php echo $ageStr; ?> ago</span> <span class="age-text">Last activity: <?php echo $ageStr; ?> ago</span>
</div> </div>
<div class="ticket-user-info" style="font-size: 0.85rem; color: #666; margin-top: 0.25rem;"> <div class="ticket-user-info">
<?php <?php
$creator = $ticket['creator_display_name'] ?? $ticket['creator_username'] ?? 'System'; $creator = $ticket['creator_display_name'] ?? $ticket['creator_username'] ?? 'System';
echo "Created by: <strong>" . htmlspecialchars($creator) . "</strong>"; echo "Created by: <strong>" . htmlspecialchars($creator) . "</strong>";
@@ -148,9 +150,9 @@ $nonce = SecurityHeadersMiddleware::getNonce();
} }
?> ?>
</div> </div>
<div class="ticket-assignment" style="margin-top: 0.5rem;"> <div class="ticket-assignment">
<label style="font-weight: 500; margin-right: 0.5rem;">Assigned to:</label> <label class="ticket-assignment-label">Assigned to:</label>
<select id="assignedToSelect" class="assignment-select" style="padding: 0.25rem 0.5rem; border-radius: 0; border: 2px solid var(--terminal-green);"> <select id="assignedToSelect" class="assignment-select">
<option value="">Unassigned</option> <option value="">Unassigned</option>
<?php foreach ($allUsers as $user): ?> <?php foreach ($allUsers as $user): ?>
<option value="<?php echo $user['user_id']; ?>" <option value="<?php echo $user['user_id']; ?>"
@@ -162,10 +164,10 @@ $nonce = SecurityHeadersMiddleware::getNonce();
</div> </div>
<!-- Metadata Fields: Priority, Category, Type --> <!-- Metadata Fields: Priority, Category, Type -->
<div class="ticket-metadata-fields" style="margin-top: 0.75rem; display: grid; grid-template-columns: repeat(3, 1fr); gap: 0.75rem;"> <div class="ticket-metadata-fields">
<div class="metadata-field"> <div class="metadata-field">
<label style="font-weight: 500; display: block; margin-bottom: 0.25rem; color: var(--terminal-amber); font-family: var(--font-mono); font-size: 0.85rem;">Priority:</label> <label class="metadata-label">Priority:</label>
<select id="prioritySelect" class="metadata-select editable-metadata" disabled style="width: 100%; padding: 0.25rem 0.5rem; border-radius: 0; border: 2px solid var(--terminal-green); background: var(--bg-primary); color: var(--terminal-green); font-family: var(--font-mono);"> <select id="prioritySelect" class="metadata-select editable-metadata" disabled>
<option value="1" <?php echo $ticket['priority'] == 1 ? 'selected' : ''; ?>>P1 - Critical</option> <option value="1" <?php echo $ticket['priority'] == 1 ? 'selected' : ''; ?>>P1 - Critical</option>
<option value="2" <?php echo $ticket['priority'] == 2 ? 'selected' : ''; ?>>P2 - High</option> <option value="2" <?php echo $ticket['priority'] == 2 ? 'selected' : ''; ?>>P2 - High</option>
<option value="3" <?php echo $ticket['priority'] == 3 ? 'selected' : ''; ?>>P3 - Medium</option> <option value="3" <?php echo $ticket['priority'] == 3 ? 'selected' : ''; ?>>P3 - Medium</option>
@@ -175,8 +177,8 @@ $nonce = SecurityHeadersMiddleware::getNonce();
</div> </div>
<div class="metadata-field"> <div class="metadata-field">
<label style="font-weight: 500; display: block; margin-bottom: 0.25rem; color: var(--terminal-amber); font-family: var(--font-mono); font-size: 0.85rem;">Category:</label> <label class="metadata-label">Category:</label>
<select id="categorySelect" class="metadata-select editable-metadata" disabled style="width: 100%; padding: 0.25rem 0.5rem; border-radius: 0; border: 2px solid var(--terminal-green); background: var(--bg-primary); color: var(--terminal-green); font-family: var(--font-mono);"> <select id="categorySelect" class="metadata-select editable-metadata" disabled>
<option value="Hardware" <?php echo $ticket['category'] == 'Hardware' ? 'selected' : ''; ?>>Hardware</option> <option value="Hardware" <?php echo $ticket['category'] == 'Hardware' ? 'selected' : ''; ?>>Hardware</option>
<option value="Software" <?php echo $ticket['category'] == 'Software' ? 'selected' : ''; ?>>Software</option> <option value="Software" <?php echo $ticket['category'] == 'Software' ? 'selected' : ''; ?>>Software</option>
<option value="Network" <?php echo $ticket['category'] == 'Network' ? 'selected' : ''; ?>>Network</option> <option value="Network" <?php echo $ticket['category'] == 'Network' ? 'selected' : ''; ?>>Network</option>
@@ -186,8 +188,8 @@ $nonce = SecurityHeadersMiddleware::getNonce();
</div> </div>
<div class="metadata-field"> <div class="metadata-field">
<label style="font-weight: 500; display: block; margin-bottom: 0.25rem; color: var(--terminal-amber); font-family: var(--font-mono); font-size: 0.85rem;">Type:</label> <label class="metadata-label">Type:</label>
<select id="typeSelect" class="metadata-select editable-metadata" disabled style="width: 100%; padding: 0.25rem 0.5rem; border-radius: 0; border: 2px solid var(--terminal-green); background: var(--bg-primary); color: var(--terminal-green); font-family: var(--font-mono);"> <select id="typeSelect" class="metadata-select editable-metadata" disabled>
<option value="Maintenance" <?php echo $ticket['type'] == 'Maintenance' ? 'selected' : ''; ?>>Maintenance</option> <option value="Maintenance" <?php echo $ticket['type'] == 'Maintenance' ? 'selected' : ''; ?>>Maintenance</option>
<option value="Install" <?php echo $ticket['type'] == 'Install' ? 'selected' : ''; ?>>Install</option> <option value="Install" <?php echo $ticket['type'] == 'Install' ? 'selected' : ''; ?>>Install</option>
<option value="Task" <?php echo $ticket['type'] == 'Task' ? 'selected' : ''; ?>>Task</option> <option value="Task" <?php echo $ticket['type'] == 'Task' ? 'selected' : ''; ?>>Task</option>
@@ -206,31 +208,31 @@ $nonce = SecurityHeadersMiddleware::getNonce();
$visUserModel = new UserModel($conn); $visUserModel = new UserModel($conn);
$allAvailableGroups = $visUserModel->getAllGroups(); $allAvailableGroups = $visUserModel->getAllGroups();
?> ?>
<div class="ticket-visibility-settings" style="margin-top: 0.75rem; padding-top: 0.75rem; border-top: 1px solid var(--terminal-green);"> <div class="ticket-visibility-settings">
<div style="display: grid; grid-template-columns: 1fr 2fr; gap: 0.75rem;"> <div class="visibility-settings-grid">
<div class="metadata-field"> <div class="metadata-field">
<label style="font-weight: 500; display: block; margin-bottom: 0.25rem; color: var(--terminal-cyan); font-family: var(--font-mono); font-size: 0.85rem;">Visibility:</label> <label class="metadata-label metadata-label-cyan">Visibility:</label>
<select id="visibilitySelect" class="metadata-select editable-metadata" disabled data-action="toggle-visibility-groups" style="width: 100%; padding: 0.25rem 0.5rem; border-radius: 0; border: 2px solid var(--terminal-green); background: var(--bg-primary); color: var(--terminal-green); font-family: var(--font-mono);"> <select id="visibilitySelect" class="metadata-select editable-metadata" disabled data-action="toggle-visibility-groups">
<option value="public" <?php echo $currentVisibility == 'public' ? 'selected' : ''; ?>>Public</option> <option value="public" <?php echo $currentVisibility == 'public' ? 'selected' : ''; ?>>Public</option>
<option value="internal" <?php echo $currentVisibility == 'internal' ? 'selected' : ''; ?>>Internal</option> <option value="internal" <?php echo $currentVisibility == 'internal' ? 'selected' : ''; ?>>Internal</option>
<option value="confidential" <?php echo $currentVisibility == 'confidential' ? 'selected' : ''; ?>>Confidential</option> <option value="confidential" <?php echo $currentVisibility == 'confidential' ? 'selected' : ''; ?>>Confidential</option>
</select> </select>
</div> </div>
<div class="metadata-field" id="visibilityGroupsField" style="<?php echo $currentVisibility !== 'internal' ? 'display: none;' : ''; ?>"> <div class="metadata-field" id="visibilityGroupsField" <?php echo $currentVisibility !== 'internal' ? 'style="display: none;"' : ''; ?>>
<label style="font-weight: 500; display: block; margin-bottom: 0.25rem; color: var(--terminal-cyan); font-family: var(--font-mono); font-size: 0.85rem;">Allowed Groups:</label> <label class="metadata-label metadata-label-cyan">Allowed Groups:</label>
<div class="visibility-groups-edit" style="display: flex; flex-wrap: wrap; gap: 0.5rem;"> <div class="visibility-groups-edit">
<?php foreach ($allAvailableGroups as $group): <?php foreach ($allAvailableGroups as $group):
$isChecked = in_array($group, $currentVisibilityGroups); $isChecked = in_array($group, $currentVisibilityGroups);
?> ?>
<label style="display: flex; align-items: center; gap: 0.3rem; cursor: pointer;"> <label class="visibility-group-label">
<input type="checkbox" class="visibility-group-checkbox editable-metadata" disabled <input type="checkbox" class="visibility-group-checkbox editable-metadata" disabled
value="<?php echo htmlspecialchars($group); ?>" value="<?php echo htmlspecialchars($group); ?>"
<?php echo $isChecked ? 'checked' : ''; ?>> <?php echo $isChecked ? 'checked' : ''; ?>>
<span class="group-badge" style="font-size: 0.7rem;"><?php echo htmlspecialchars($group); ?></span> <span class="group-badge"><?php echo htmlspecialchars($group); ?></span>
</label> </label>
<?php endforeach; ?> <?php endforeach; ?>
<?php if (empty($allAvailableGroups)): ?> <?php if (empty($allAvailableGroups)): ?>
<span style="color: var(--text-muted); font-size: 0.85rem;">No groups available</span> <span class="no-groups-message">No groups available</span>
<?php endif; ?> <?php endif; ?>
</div> </div>
</div> </div>
@@ -293,7 +295,7 @@ $nonce = SecurityHeadersMiddleware::getNonce();
</div> </div>
</div> </div>
<div id="comments-tab" class="tab-content" role="tabpanel" aria-labelledby="comments-tab-btn"> <section id="comments-tab" class="tab-content" role="tabpanel" aria-labelledby="comments-tab-btn">
<div class="ascii-subsection-header">Comments Section</div> <div class="ascii-subsection-header">Comments Section</div>
<div class="comments-section"> <div class="comments-section">
<div class="ascii-frame-inner"> <div class="ascii-frame-inner">
@@ -413,22 +415,22 @@ $nonce = SecurityHeadersMiddleware::getNonce();
<div class="ascii-subsection-header">File Attachments</div> <div class="ascii-subsection-header">File Attachments</div>
<div class="attachments-container"> <div class="attachments-container">
<!-- Upload Form --> <!-- Upload Form -->
<div class="ascii-frame-inner" style="margin-bottom: 1rem;"> <div class="ascii-frame-inner frame-inner-spacing">
<h3>Upload Files</h3> <h3>Upload Files</h3>
<div class="upload-zone" id="uploadZone"> <div class="upload-zone" id="uploadZone">
<div class="upload-zone-content"> <div class="upload-zone-content">
<div class="upload-icon">📁</div> <div class="upload-icon">📁</div>
<p>Drag and drop files here or click to browse</p> <p>Drag and drop files here or click to browse</p>
<p class="upload-hint">Max file size: <?php echo $GLOBALS['config']['MAX_UPLOAD_SIZE'] ? number_format($GLOBALS['config']['MAX_UPLOAD_SIZE'] / 1048576, 0) . 'MB' : '10MB'; ?></p> <p class="upload-hint">Max file size: <?php echo $GLOBALS['config']['MAX_UPLOAD_SIZE'] ? number_format($GLOBALS['config']['MAX_UPLOAD_SIZE'] / 1048576, 0) . 'MB' : '10MB'; ?></p>
<input type="file" id="fileInput" multiple style="display: none;" aria-label="Upload files"> <input type="file" id="fileInput" multiple class="sr-only" aria-label="Upload files">
<button type="button" id="browseFilesBtn" class="btn" style="margin-top: 1rem;">Browse Files</button> <button type="button" id="browseFilesBtn" class="btn upload-browse-btn">Browse Files</button>
</div> </div>
</div> </div>
<div id="uploadProgress" style="display: none; margin-top: 1rem;"> <div id="uploadProgress" class="upload-progress" style="display: none;">
<div class="progress-bar"> <div class="progress-bar">
<div class="progress-fill" id="progressFill"></div> <div class="progress-fill" id="progressFill"></div>
</div> </div>
<p id="uploadStatus" style="margin-top: 0.5rem; color: var(--terminal-green); font-family: var(--font-mono); font-size: 0.85rem;"></p> <p id="uploadStatus" class="upload-status-text"></p>
</div> </div>
</div> </div>
@@ -446,14 +448,13 @@ $nonce = SecurityHeadersMiddleware::getNonce();
<div class="ascii-subsection-header">Ticket Dependencies</div> <div class="ascii-subsection-header">Ticket Dependencies</div>
<div class="dependencies-container"> <div class="dependencies-container">
<!-- Add Dependency Form --> <!-- Add Dependency Form -->
<div class="ascii-frame-inner" style="margin-bottom: 1rem;"> <div class="ascii-frame-inner frame-inner-spacing">
<h3>Add Dependency</h3> <h3>Add Dependency</h3>
<div class="add-dependency-form" style="display: flex; gap: 0.5rem; flex-wrap: wrap; align-items: center;"> <div class="add-dependency-form">
<label for="dependencyTicketId" class="sr-only">Ticket ID for dependency</label> <label for="dependencyTicketId" class="sr-only">Ticket ID for dependency</label>
<input type="text" id="dependencyTicketId" placeholder="Ticket ID (e.g., 123456789)" aria-label="Ticket ID for dependency" <input type="text" id="dependencyTicketId" class="dependency-input" placeholder="Ticket ID (e.g., 123456789)" aria-label="Ticket ID for dependency">
style="flex: 1; min-width: 150px; padding: 0.5rem; border: 2px solid var(--terminal-green); background: var(--bg-primary); color: var(--terminal-green); font-family: var(--font-mono);">
<label for="dependencyType" class="sr-only">Dependency type</label> <label for="dependencyType" class="sr-only">Dependency type</label>
<select id="dependencyType" aria-label="Dependency type" style="padding: 0.5rem; border: 2px solid var(--terminal-green); background: var(--bg-primary); color: var(--terminal-green); font-family: var(--font-mono);"> <select id="dependencyType" class="dependency-type-select" aria-label="Dependency type">
<option value="blocks">Blocks</option> <option value="blocks">Blocks</option>
<option value="blocked_by">Blocked By</option> <option value="blocked_by">Blocked By</option>
<option value="relates_to">Relates To</option> <option value="relates_to">Relates To</option>
@@ -472,7 +473,7 @@ $nonce = SecurityHeadersMiddleware::getNonce();
</div> </div>
<!-- Dependent Tickets --> <!-- Dependent Tickets -->
<div class="ascii-frame-inner" style="margin-top: 1rem;"> <div class="ascii-frame-inner frame-inner-spacing-top">
<h3>Tickets That Depend On This</h3> <h3>Tickets That Depend On This</h3>
<div id="dependentsList" class="dependencies-list"> <div id="dependentsList" class="dependencies-list">
<p class="loading-text">Loading dependents...</p> <p class="loading-text">Loading dependents...</p>
@@ -510,7 +511,8 @@ $nonce = SecurityHeadersMiddleware::getNonce();
</div> </div>
</div> </div>
</div> </div>
</div> </article>
</main>
<!-- END OUTER FRAME --> <!-- END OUTER FRAME -->
<script nonce="<?php echo $nonce; ?>"> <script nonce="<?php echo $nonce; ?>">
// Initialize the ticket view and attach event listeners (CSP-compliant) // Initialize the ticket view and attach event listeners (CSP-compliant)
@@ -685,14 +687,14 @@ $nonce = SecurityHeadersMiddleware::getNonce();
</script> </script>
<!-- Settings Modal (same as dashboard) --> <!-- Settings Modal (same as dashboard) -->
<div class="settings-modal" id="settingsModal" style="display: none;"> <div class="settings-modal" id="settingsModal" style="display: none;" role="dialog" aria-modal="true" aria-labelledby="ticketSettingsTitle">
<div class="settings-content"> <div class="settings-content">
<span class="bottom-left-corner">╚</span> <span class="bottom-left-corner">╚</span>
<span class="bottom-right-corner">╝</span> <span class="bottom-right-corner">╝</span>
<div class="settings-header"> <div class="settings-header">
<h3>⚙ System Preferences</h3> <h3 id="ticketSettingsTitle">⚙ System Preferences</h3>
<button class="close-settings" id="closeSettingsBtn">✗</button> <button class="close-settings" id="closeSettingsBtn" aria-label="Close settings">✗</button>
</div> </div>
<div class="settings-body"> <div class="settings-body">
@@ -806,7 +808,7 @@ $nonce = SecurityHeadersMiddleware::getNonce();
endforeach; endforeach;
if (empty(trim($GLOBALS['currentUser']['groups'] ?? ''))): if (empty(trim($GLOBALS['currentUser']['groups'] ?? ''))):
?> ?>
<span style="color: var(--text-muted);">No groups assigned</span> <span class="text-muted">No groups assigned</span>
<?php endif; ?> <?php endif; ?>
</div> </div>
</div> </div>