2025-05-16 20:02:49 -04:00
< ? php
// This file contains the HTML template for the dashboard
2025-09-05 11:08:56 -04:00
// It receives $tickets, $totalTickets, $totalPages, $page, $status, $categories, $types variables from the controller
2026-01-28 20:27:15 -05:00
require_once __DIR__ . '/../middleware/SecurityHeadersMiddleware.php' ;
require_once __DIR__ . '/../middleware/CsrfMiddleware.php' ;
$nonce = SecurityHeadersMiddleware :: getNonce ();
2025-05-16 20:02:49 -04:00
?>
<! DOCTYPE html >
< html lang = " en " >
< head >
< meta charset = " UTF-8 " >
< meta name = " viewport " content = " width=device-width, initial-scale=1.0 " >
< title > Ticket Dashboard </ title >
< link rel = " icon " type = " image/png " href = " <?php echo $GLOBALS['config'] ['ASSETS_URL']; ?>/images/favicon.png " >
2026-01-26 11:34:15 -05:00
< link rel = " stylesheet " href = " <?php echo $GLOBALS['config'] ['ASSETS_URL']; ?>/css/dashboard.css?v=20260126c " >
2026-01-28 20:27:15 -05:00
< 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/markdown.js?v=20260124e " ></ script >
< script nonce = " <?php echo $nonce ; ?> " src = " <?php echo $GLOBALS['config'] ['ASSETS_URL']; ?>/js/dashboard.js?v=20260124e " ></ script >
< script nonce = " <?php echo $nonce ; ?> " >
2026-01-09 16:32:11 -05:00
// CSRF Token for AJAX requests
2026-01-28 20:27:15 -05:00
window . CSRF_TOKEN = '<?php echo CsrfMiddleware::getToken(); ?>' ;
2026-01-20 21:48:25 -05:00
// Timezone configuration (from server)
window . APP_TIMEZONE = '<?php echo $GLOBALS[' config '][' TIMEZONE ']; ?>' ;
window . APP_TIMEZONE_OFFSET = < ? php echo $GLOBALS [ 'config' ][ 'TIMEZONE_OFFSET' ]; ?> ; // minutes from UTC
window . APP_TIMEZONE_ABBREV = '<?php echo $GLOBALS[' config '][' TIMEZONE_ABBREV ']; ?>' ;
2026-01-09 16:32:11 -05:00
</ script >
2025-05-16 20:02:49 -04:00
</ head >
2025-09-05 11:08:56 -04:00
< body data - categories = '<?php echo json_encode($categories); ?>' data - types = '<?php echo json_encode($types); ?>' >
2026-01-07 18:49:44 -05:00
<!-- Terminal Boot Sequence -->
< div id = " boot-sequence " class = " boot-overlay " >
< pre id = " boot-text " ></ pre >
</ div >
2026-01-28 20:27:15 -05:00
< script nonce = " <?php echo $nonce ; ?> " >
2026-01-07 18:49:44 -05:00
function showBootSequence () {
const bootText = document . getElementById ( 'boot-text' );
const bootOverlay = document . getElementById ( 'boot-sequence' );
const messages = [
'╔═══════════════════════════════════════╗' ,
'║ TINKER TICKETS TERMINAL v1.0 ║' ,
'║ BOOTING SYSTEM... ║' ,
'╚═══════════════════════════════════════╝' ,
'' ,
'[ OK ] Loading kernel modules...' ,
'[ OK ] Initializing ticket database...' ,
'[ OK ] Mounting user session...' ,
'[ OK ] Starting dashboard services...' ,
'[ OK ] Rendering ASCII frames...' ,
'' ,
'> SYSTEM READY ✓' ,
''
];
let i = 0 ;
const interval = setInterval (() => {
if ( i < messages . length ) {
bootText . textContent += messages [ i ] + '\n' ;
i ++ ;
} else {
setTimeout (() => {
bootOverlay . style . opacity = '0' ;
setTimeout (() => bootOverlay . remove (), 500 );
}, 500 );
clearInterval ( interval );
}
}, 80 );
}
// Run on first visit only (per session)
if ( ! sessionStorage . getItem ( 'booted' )) {
showBootSequence ();
sessionStorage . setItem ( 'booted' , 'true' );
} else {
document . getElementById ( 'boot-sequence' ) . remove ();
}
</ script >
2026-01-01 16:14:56 -05:00
< div class = " user-header " >
< div class = " user-header-left " >
2026-01-20 21:54:04 -05:00
< a href = " / " class = " app-title " style = " text-decoration: none; color: inherit; " > 🎫 Tinker Tickets </ a >
2026-01-01 15:40:32 -05:00
</ div >
2026-01-01 16:14:56 -05:00
< div class = " user-header-right " >
2026-01-01 15:40:32 -05:00
< ? php if ( isset ( $GLOBALS [ 'currentUser' ])) : ?>
2026-01-01 16:14:56 -05:00
< span class = " user-name " > 👤 < ? php echo htmlspecialchars ( $GLOBALS [ 'currentUser' ][ 'display_name' ] ? ? $GLOBALS [ 'currentUser' ][ 'username' ]); ?> </span>
2026-01-01 15:40:32 -05:00
< ? php if ( $GLOBALS [ 'currentUser' ][ 'is_admin' ]) : ?>
2026-01-20 21:11:49 -05:00
< div class = " admin-dropdown " >
2026-01-30 13:15:55 -05:00
< button class = " admin-badge " data - action = " toggle-admin-menu " > Admin ▼ </ button >
2026-01-20 21:11:49 -05:00
< div class = " admin-dropdown-content " id = " adminDropdown " >
< a href = " /admin/templates " > 📋 Templates </ a >
< a href = " /admin/workflow " > 🔄 Workflow </ a >
< a href = " /admin/recurring-tickets " > 🔁 Recurring Tickets </ a >
< a href = " /admin/custom-fields " > 📝 Custom Fields </ a >
< a href = " /admin/user-activity " > 👥 User Activity </ a >
< a href = " /admin/audit-log " > 📜 Audit Log </ a >
2026-01-23 10:01:50 -05:00
< a href = " /admin/api-keys " > 🔑 API Keys </ a >
2026-01-20 21:11:49 -05:00
</ div >
</ div >
2026-01-01 15:40:32 -05:00
< ? php endif ; ?>
2026-01-30 13:15:55 -05:00
< button class = " settings-icon " title = " Settings (Alt+S) " data - action = " open-settings " > ⚙ </ button >
2026-01-01 15:40:32 -05:00
< ? php endif ; ?>
</ div >
</ div >
Implement complete ANSI art terminal redesign
Transform entire UI into retro terminal aesthetic with ASCII/ANSI art:
Visual Changes:
- Add large ASCII art "TINKER TICKETS" banner with typewriter animation
- Terminal black background (#0a0a0a) with matrix green text (#00ff41)
- ASCII borders throughout using box-drawing characters (┌─┐│└─┘╔═╗║╚╝)
- Monospace fonts (Courier New, Consolas, Monaco) everywhere
- All rounded corners removed (border-radius: 0)
- Text glow effects on important elements
- Terminal prompts (>, $) and brackets ([]) on all UI elements
Dashboard:
- Table with ASCII corner decorations and terminal green borders
- Headers with > prefix and amber glow
- Priority badges: [P1] [P2] format with colored glows
- Status badges: [OPEN] [CLOSED] with borders and glows
- Search box with $ SEARCH prompt
- All buttons in [ BRACKET ] format
Ticket View:
- Ticket container with double ASCII borders (╔╗╚╝)
- Priority-colored corner characters
- UUID display: [UUID: xxx] format
- Comments section: ╔═══ COMMENTS ═══╗ header
- Activity timeline with ASCII tree (├──, │, └──)
- Tabs with [ ] brackets and ▼ active indicator
Components:
- Modals with ╔═══ TITLE ═══╗ headers and ASCII corners
- Hamburger menu with MENU SYSTEM box decoration
- Settings modal with terminal styling
- All inputs with green borders and amber focus glow
- Checkboxes with ✓ characters
Technical:
- New file: ascii-banner.js with banner artwork and typewriter renderer
- Comprehensive responsive design (1024px, 768px, 480px breakpoints)
- Mobile: simplified ASCII, hidden decorations, full-width menu
- Print styles for clean black/white output
- All functionality preserved, purely visual transformation
Colors preserved:
- Priority: P1=red, P2=orange, P3=blue, P4=green, P5=gray
- Status: Open=green, In Progress=yellow, Closed=red
- Accents: Terminal green, amber, cyan
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-06 23:22:25 -05:00
2026-01-07 17:47:11 -05:00
<!-- Collapsible ASCII Banner -->
< div class = " ascii-banner-wrapper collapsed " >
2026-01-30 13:15:55 -05:00
< button class = " banner-toggle " data - action = " toggle-banner " >
2026-01-07 17:47:11 -05:00
< span class = " toggle-icon " > ▼ </ span > ASCII Banner
</ button >
< div id = " ascii-banner-container " class = " banner-content " ></ div >
</ div >
2026-01-28 20:27:15 -05:00
< script nonce = " <?php echo $nonce ; ?> " >
2026-01-07 17:47:11 -05:00
function toggleBanner () {
const wrapper = document . querySelector ( '.ascii-banner-wrapper' );
const icon = document . querySelector ( '.toggle-icon' );
wrapper . classList . toggle ( 'collapsed' );
icon . textContent = wrapper . classList . contains ( 'collapsed' ) ? '▼' : '▲' ;
Implement complete ANSI art terminal redesign
Transform entire UI into retro terminal aesthetic with ASCII/ANSI art:
Visual Changes:
- Add large ASCII art "TINKER TICKETS" banner with typewriter animation
- Terminal black background (#0a0a0a) with matrix green text (#00ff41)
- ASCII borders throughout using box-drawing characters (┌─┐│└─┘╔═╗║╚╝)
- Monospace fonts (Courier New, Consolas, Monaco) everywhere
- All rounded corners removed (border-radius: 0)
- Text glow effects on important elements
- Terminal prompts (>, $) and brackets ([]) on all UI elements
Dashboard:
- Table with ASCII corner decorations and terminal green borders
- Headers with > prefix and amber glow
- Priority badges: [P1] [P2] format with colored glows
- Status badges: [OPEN] [CLOSED] with borders and glows
- Search box with $ SEARCH prompt
- All buttons in [ BRACKET ] format
Ticket View:
- Ticket container with double ASCII borders (╔╗╚╝)
- Priority-colored corner characters
- UUID display: [UUID: xxx] format
- Comments section: ╔═══ COMMENTS ═══╗ header
- Activity timeline with ASCII tree (├──, │, └──)
- Tabs with [ ] brackets and ▼ active indicator
Components:
- Modals with ╔═══ TITLE ═══╗ headers and ASCII corners
- Hamburger menu with MENU SYSTEM box decoration
- Settings modal with terminal styling
- All inputs with green borders and amber focus glow
- Checkboxes with ✓ characters
Technical:
- New file: ascii-banner.js with banner artwork and typewriter renderer
- Comprehensive responsive design (1024px, 768px, 480px breakpoints)
- Mobile: simplified ASCII, hidden decorations, full-width menu
- Print styles for clean black/white output
- All functionality preserved, purely visual transformation
Colors preserved:
- Priority: P1=red, P2=orange, P3=blue, P4=green, P5=gray
- Status: Open=green, In Progress=yellow, Closed=red
- Accents: Terminal green, amber, cyan
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-06 23:22:25 -05:00
2026-01-07 17:47:11 -05:00
// Render banner on first expand (no animation for instant display)
if ( ! wrapper . classList . contains ( 'collapsed' ) && ! wrapper . dataset . rendered ) {
renderResponsiveBanner ( '#ascii-banner-container' , 0 ); // Speed 0 = no animation
wrapper . dataset . rendered = 'true' ;
}
}
</ script >
2026-01-07 10:34:56 -05:00
2026-01-07 17:47:11 -05:00
<!-- Dashboard Layout with Sidebar -->
2026-01-23 10:39:55 -05:00
< div class = " dashboard-layout " id = " dashboardLayout " >
2026-01-07 17:47:11 -05:00
<!-- Left Sidebar with Filters -->
2026-01-23 10:01:50 -05:00
< aside class = " dashboard-sidebar " id = " dashboardSidebar " >
2026-01-30 13:15:55 -05:00
< button class = " sidebar-collapse-btn " data - action = " toggle-sidebar " title = " Collapse Sidebar " > ◀ Hide </ button >
2026-01-23 10:01:50 -05:00
< div class = " sidebar-content " >
2026-01-07 10:34:56 -05:00
< div class = " ascii-frame-inner " >
2026-01-07 17:47:11 -05:00
< div class = " ascii-subsection-header " > Filters </ div >
<!-- Status Filter -->
< div class = " filter-group " >
< h4 > Status </ h4 >
< ? php
2026-01-08 13:20:41 -05:00
$currentStatus = isset ( $_GET [ 'status' ]) ? explode ( ',' , $_GET [ 'status' ]) : [ 'Open' , 'Pending' , 'In Progress' ];
$allStatuses = [ 'Open' , 'Pending' , 'In Progress' , 'Closed' ];
2026-01-07 17:47:11 -05:00
foreach ( $allStatuses as $status ) :
?>
< label >
< input type = " checkbox "
name = " status "
value = " <?php echo $status ; ?> "
< ? php echo in_array ( $status , $currentStatus ) ? 'checked' : '' ; ?> >
< ? php echo $status ; ?>
</ label >
< ? php endforeach ; ?>
</ div >
<!-- Category Filter -->
< div class = " filter-group " >
< h4 > Category </ h4 >
< ? php
$currentCategories = isset ( $_GET [ 'category' ]) ? explode ( ',' , $_GET [ 'category' ]) : [];
foreach ( $categories as $cat ) :
?>
< label >
< input type = " checkbox "
name = " category "
value = " <?php echo $cat ; ?> "
< ? php echo in_array ( $cat , $currentCategories ) ? 'checked' : '' ; ?> >
< ? php echo htmlspecialchars ( $cat ); ?>
</ label >
< ? php endforeach ; ?>
</ div >
<!-- Type Filter -->
< div class = " filter-group " >
< h4 > Type </ h4 >
< ? php
$currentTypes = isset ( $_GET [ 'type' ]) ? explode ( ',' , $_GET [ 'type' ]) : [];
foreach ( $types as $type ) :
?>
< label >
< input type = " checkbox "
name = " type "
value = " <?php echo $type ; ?> "
< ? php echo in_array ( $type , $currentTypes ) ? 'checked' : '' ; ?> >
< ? php echo htmlspecialchars ( $type ); ?>
</ label >
< ? php endforeach ; ?>
2026-01-07 10:34:56 -05:00
</ div >
2026-01-07 17:47:11 -05:00
< button id = " apply-filters-btn " class = " btn " > Apply Filters </ button >
< button id = " clear-filters-btn " class = " btn btn-secondary " > Clear All </ button >
2026-01-07 10:34:56 -05:00
</ div >
2026-01-23 10:01:50 -05:00
</ div >
2026-01-07 17:47:11 -05:00
</ aside >
2026-01-23 21:16:29 -05:00
<!-- Expand button shown when sidebar is collapsed -->
2026-01-30 13:15:55 -05:00
< button class = " sidebar-expand-btn " id = " sidebarExpandBtn " data - action = " toggle-sidebar " title = " Show Filters " > ▶ Filters </ button >
2025-05-16 20:02:49 -04:00
2026-01-07 17:47:11 -05:00
<!-- Main Content Area -->
< main class = " dashboard-main " >
2025-05-16 20:02:49 -04:00
Implement comprehensive improvement plan (Phases 1-6)
Security (Phase 1-2):
- Add SecurityHeadersMiddleware with CSP, X-Frame-Options, etc.
- Add RateLimitMiddleware for API rate limiting
- Add security event logging to AuditLogModel
- Add ResponseHelper for standardized API responses
- Update config.php with security constants
Database (Phase 3):
- Add migration 014 for additional indexes
- Add migration 015 for ticket dependencies
- Add migration 016 for ticket attachments
- Add migration 017 for recurring tickets
- Add migration 018 for custom fields
Features (Phase 4-5):
- Add ticket dependencies with DependencyModel and API
- Add duplicate detection with check_duplicates API
- Add file attachments with AttachmentModel and upload/download APIs
- Add @mentions with autocomplete and highlighting
- Add quick actions on dashboard rows
Collaboration (Phase 5):
- Add mention extraction in CommentModel
- Add mention autocomplete dropdown in ticket.js
- Add mention highlighting CSS styles
Admin & Export (Phase 6):
- Add StatsModel for dashboard widgets
- Add dashboard stats cards (open, critical, unassigned, etc.)
- Add CSV/JSON export via export_tickets API
- Add rich text editor toolbar in markdown.js
- Add RecurringTicketModel with cron job
- Add CustomFieldModel for per-category fields
- Add admin views: RecurringTickets, CustomFields, Workflow,
Templates, AuditLog, UserActivity
- Add admin APIs: manage_workflows, manage_templates,
manage_recurring, custom_fields, get_users
- Add admin routes in index.php
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-20 09:55:01 -05:00
<!-- Dashboard Stats Widgets -->
< ? php if ( isset ( $stats )) : ?>
< div class = " stats-widgets " >
< div class = " stats-row " >
< div class = " stat-card stat-open " >
< div class = " stat-icon " > 📂 </ div >
< div class = " stat-content " >
< div class = " stat-value " >< ? php echo $stats [ 'open_tickets' ]; ?> </div>
< div class = " stat-label " > Open Tickets </ div >
</ div >
</ div >
< div class = " stat-card stat-critical " >
< div class = " stat-icon " > 🔥 </ div >
< div class = " stat-content " >
< div class = " stat-value " >< ? php echo $stats [ 'critical' ]; ?> </div>
< div class = " stat-label " > Critical ( P1 ) </ div >
</ div >
</ div >
< div class = " stat-card stat-unassigned " >
< div class = " stat-icon " > 👤 </ div >
< div class = " stat-content " >
< div class = " stat-value " >< ? php echo $stats [ 'unassigned' ]; ?> </div>
< div class = " stat-label " > Unassigned </ div >
</ div >
</ div >
< div class = " stat-card stat-today " >
< div class = " stat-icon " > 📅 </ div >
< div class = " stat-content " >
< div class = " stat-value " >< ? php echo $stats [ 'created_today' ]; ?> </div>
< div class = " stat-label " > Created Today </ div >
</ div >
</ div >
< div class = " stat-card stat-resolved " >
< div class = " stat-icon " > ✓ </ div >
< div class = " stat-content " >
< div class = " stat-value " >< ? php echo $stats [ 'closed_today' ]; ?> </div>
< div class = " stat-label " > Closed Today </ div >
</ div >
</ div >
< div class = " stat-card stat-time " >
< div class = " stat-icon " > ⏱ </ div >
< div class = " stat-content " >
< div class = " stat-value " >< ? php echo $stats [ 'avg_resolution_hours' ]; ?> h</div>
< div class = " stat-label " > Avg Resolution </ div >
</ div >
</ div >
</ div >
</ div >
< ? php endif ; ?>
2026-01-07 17:47:11 -05:00
<!-- CONDENSED TOOLBAR : Combined Header , Search , Actions , Pagination -->
< div class = " dashboard-toolbar " >
<!-- Left : Title + Search -->
< div class = " toolbar-left " >
< h1 class = " dashboard-title " > 🎫 Tickets </ h1 >
< form method = " GET " action = " " class = " toolbar-search " >
<!-- Preserve existing parameters -->
< ? php if ( isset ( $_GET [ 'status' ])) : ?>
< input type = " hidden " name = " status " value = " <?php echo htmlspecialchars( $_GET['status'] ); ?> " >
< ? php endif ; ?>
< ? php if ( isset ( $_GET [ 'category' ])) : ?>
< input type = " hidden " name = " category " value = " <?php echo htmlspecialchars( $_GET['category'] ); ?> " >
< ? php endif ; ?>
< ? php if ( isset ( $_GET [ 'type' ])) : ?>
< input type = " hidden " name = " type " value = " <?php echo htmlspecialchars( $_GET['type'] ); ?> " >
< ? php endif ; ?>
< ? php if ( isset ( $_GET [ 'sort' ])) : ?>
< input type = " hidden " name = " sort " value = " <?php echo htmlspecialchars( $_GET['sort'] ); ?> " >
< ? php endif ; ?>
< ? php if ( isset ( $_GET [ 'dir' ])) : ?>
< input type = " hidden " name = " dir " value = " <?php echo htmlspecialchars( $_GET['dir'] ); ?> " >
< ? php endif ; ?>
< input type = " text "
name = " search "
placeholder = " 🔍 Search tickets... "
class = " search-box "
value = " <?php echo isset( $_GET['search'] ) ? htmlspecialchars( $_GET['search'] ) : ''; ?> " >
< button type = " submit " class = " btn search-btn " > Search </ button >
2026-01-30 13:15:55 -05:00
< button type = " button " class = " btn btn-secondary " data - action = " open-advanced-search " title = " Advanced Search " > ⚙ Advanced </ button >
2026-01-07 10:34:56 -05:00
< ? php if ( isset ( $_GET [ 'search' ]) && ! empty ( $_GET [ 'search' ])) : ?>
2026-01-07 17:47:11 -05:00
< a href = " ? " class = " clear-search-btn " > ✗ </ a >
2026-01-07 10:34:56 -05:00
< ? php endif ; ?>
2026-01-07 17:47:11 -05:00
</ form >
2025-05-16 20:02:49 -04:00
</ div >
2026-01-07 17:47:11 -05:00
<!-- Center : Actions + Count -->
< div class = " toolbar-center " >
2026-01-23 10:01:50 -05:00
< div class = " view-toggle " >
2026-01-30 13:15:55 -05:00
< button id = " tableViewBtn " class = " view-btn active " data - action = " set-view-mode " data - mode = " table " title = " Table View " > ≡ </ button >
< button id = " cardViewBtn " class = " view-btn " data - action = " set-view-mode " data - mode = " card " title = " Kanban View " > ▦ </ button >
2026-01-23 10:01:50 -05:00
</ div >
2026-01-30 13:15:55 -05:00
< button data - action = " navigate " data - url = " <?php echo $GLOBALS['config'] ['BASE_URL']; ?>/ticket/create " class = " btn create-ticket " >+ New Ticket </ button >
2026-01-20 15:16:14 -05:00
< div class = " export-dropdown " id = " exportDropdown " style = " display: none; " >
2026-01-30 13:15:55 -05:00
< button class = " btn " data - action = " toggle-export-menu " > ↓ Export Selected ( < span id = " exportCount " > 0 </ span > ) </ button >
2026-01-20 15:16:14 -05:00
< div class = " export-dropdown-content " id = " exportDropdownContent " >
2026-01-30 13:15:55 -05:00
< a href = " # " data - action = " export-tickets " data - format = " csv " > CSV </ a >
< a href = " # " data - action = " export-tickets " data - format = " json " > JSON </ a >
Implement comprehensive improvement plan (Phases 1-6)
Security (Phase 1-2):
- Add SecurityHeadersMiddleware with CSP, X-Frame-Options, etc.
- Add RateLimitMiddleware for API rate limiting
- Add security event logging to AuditLogModel
- Add ResponseHelper for standardized API responses
- Update config.php with security constants
Database (Phase 3):
- Add migration 014 for additional indexes
- Add migration 015 for ticket dependencies
- Add migration 016 for ticket attachments
- Add migration 017 for recurring tickets
- Add migration 018 for custom fields
Features (Phase 4-5):
- Add ticket dependencies with DependencyModel and API
- Add duplicate detection with check_duplicates API
- Add file attachments with AttachmentModel and upload/download APIs
- Add @mentions with autocomplete and highlighting
- Add quick actions on dashboard rows
Collaboration (Phase 5):
- Add mention extraction in CommentModel
- Add mention autocomplete dropdown in ticket.js
- Add mention highlighting CSS styles
Admin & Export (Phase 6):
- Add StatsModel for dashboard widgets
- Add dashboard stats cards (open, critical, unassigned, etc.)
- Add CSV/JSON export via export_tickets API
- Add rich text editor toolbar in markdown.js
- Add RecurringTicketModel with cron job
- Add CustomFieldModel for per-category fields
- Add admin views: RecurringTickets, CustomFields, Workflow,
Templates, AuditLog, UserActivity
- Add admin APIs: manage_workflows, manage_templates,
manage_recurring, custom_fields, get_users
- Add admin routes in index.php
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-20 09:55:01 -05:00
</ div >
</ div >
2026-01-07 17:47:11 -05:00
< span class = " ticket-count " > Total : < ? php echo $totalTickets ; ?> </span>
2026-01-01 19:06:33 -05:00
</ div >
2026-01-07 10:34:56 -05:00
2026-01-07 17:47:11 -05:00
<!-- Right : Pagination -->
< div class = " toolbar-right " >
< div class = " pagination " >
< ? php
$currentParams = $_GET ;
2026-01-07 10:34:56 -05:00
2026-01-07 17:47:11 -05:00
// Previous page button
if ( $page > 1 ) {
$currentParams [ 'page' ] = $page - 1 ;
$prevUrl = '?' . http_build_query ( $currentParams );
2026-01-30 13:15:55 -05:00
echo " <button data-action='navigate' data-url=' $prevUrl '>«</button> " ;
2026-01-07 17:47:11 -05:00
}
// Page number buttons
for ( $i = 1 ; $i <= $totalPages ; $i ++ ) {
$activeClass = ( $i === $page ) ? 'active' : '' ;
$currentParams [ 'page' ] = $i ;
$pageUrl = '?' . http_build_query ( $currentParams );
2026-01-30 13:15:55 -05:00
echo " <button class=' $activeClass ' data-action='navigate' data-url=' $pageUrl '> $i </button> " ;
2026-01-07 17:47:11 -05:00
}
// Next page button
if ( $page < $totalPages ) {
$currentParams [ 'page' ] = $page + 1 ;
$nextUrl = '?' . http_build_query ( $currentParams );
2026-01-30 13:15:55 -05:00
echo " <button data-action='navigate' data-url=' $nextUrl '>»</button> " ;
2026-01-07 17:47:11 -05:00
}
?>
2026-01-07 10:34:56 -05:00
</ div >
2026-01-01 19:06:33 -05:00
</ div >
2026-01-07 17:47:11 -05:00
</ div >
2026-01-01 19:06:33 -05:00
2026-01-07 17:47:11 -05:00
< ? php if ( isset ( $_GET [ 'search' ]) && ! empty ( $_GET [ 'search' ])) : ?>
< div class = " search-results-info " >
Showing results for : " <strong><?php echo htmlspecialchars( $_GET['search'] ); ?></strong> "
( < ? php echo $totalTickets ; ?> ticket<?php echo $totalTickets != 1 ? 's' : ''; ?> found)
</ div >
< ? php endif ; ?>
<!-- TICKET TABLE WITH INLINE BULK ACTIONS -->
< div class = " ascii-frame-outer " >
< span class = " bottom-left-corner " > ╚ </ span >
< span class = " bottom-right-corner " > ╝ </ span >
2026-01-07 10:34:56 -05:00
< div class = " ascii-section-header " > Ticket List </ div >
< div class = " ascii-content " >
< div class = " ascii-frame-inner " >
2026-01-07 17:47:11 -05:00
<!-- Inline Bulk Actions ( appears above table when items selected ) -->
< ? php if ( $GLOBALS [ 'currentUser' ][ 'is_admin' ] ? ? false ) : ?>
< div class = " bulk-actions-inline " style = " display: none; " >
< span id = " selected-count " > 0 </ span > tickets selected
2026-01-30 13:15:55 -05:00
< button data - action = " bulk-status " class = " btn btn-bulk " > Change Status </ button >
< button data - action = " bulk-assign " class = " btn btn-bulk " > Assign </ button >
< button data - action = " bulk-priority " class = " btn btn-bulk " > Priority </ button >
< button data - action = " clear-selection " class = " btn btn-secondary " > Clear </ button >
2026-01-07 17:47:11 -05:00
</ div >
< ? php endif ; ?>
2026-01-30 19:21:36 -05:00
<!-- Active Filters Display -->
< ? php
$activeFilters = [];
if ( ! empty ( $_GET [ 'status' ])) {
$statuses = explode ( ',' , $_GET [ 'status' ]);
foreach ( $statuses as $s ) {
$activeFilters [] = [ 'type' => 'status' , 'value' => trim ( $s ), 'label' => 'Status: ' . trim ( $s )];
}
}
if ( ! empty ( $_GET [ 'priority' ])) {
$priorities = is_array ( $_GET [ 'priority' ]) ? $_GET [ 'priority' ] : explode ( ',' , $_GET [ 'priority' ]);
foreach ( $priorities as $p ) {
$activeFilters [] = [ 'type' => 'priority' , 'value' => trim ( $p ), 'label' => 'Priority: P' . trim ( $p )];
}
}
if ( ! empty ( $_GET [ 'category' ])) {
$activeFilters [] = [ 'type' => 'category' , 'value' => $_GET [ 'category' ], 'label' => 'Category: ' . $_GET [ 'category' ]];
}
if ( ! empty ( $_GET [ 'type' ])) {
$activeFilters [] = [ 'type' => 'type' , 'value' => $_GET [ 'type' ], 'label' => 'Type: ' . $_GET [ 'type' ]];
}
if ( ! empty ( $_GET [ 'assigned_to' ])) {
$activeFilters [] = [ 'type' => 'assigned_to' , 'value' => $_GET [ 'assigned_to' ], 'label' => 'Assigned To: ' . ( $_GET [ 'assigned_to' ] === 'unassigned' ? 'Unassigned' : 'User #' . $_GET [ 'assigned_to' ])];
}
if ( ! empty ( $_GET [ 'search' ])) {
$activeFilters [] = [ 'type' => 'search' , 'value' => $_GET [ 'search' ], 'label' => 'Search: "' . htmlspecialchars ( substr ( $_GET [ 'search' ], 0 , 20 )) . ( strlen ( $_GET [ 'search' ]) > 20 ? '...' : '' ) . '"' ];
}
?>
< ? php if ( ! empty ( $activeFilters )) : ?>
< div class = " active-filters-bar " >
< span class = " active-filters-label " > Active Filters :</ span >
< div class = " active-filters-list " >
< ? 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'] ); ?> " >
< ? 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 >
</ span >
< ? php endforeach ; ?>
</ div >
< button type = " button " class = " btn btn-secondary btn-sm " data - action = " clear-all-filters " > Clear All </ button >
</ div >
< ? php endif ; ?>
2026-01-07 17:47:11 -05:00
<!-- Table -->
2026-01-23 22:10:29 -05:00
< div class = " table-wrapper " >
2026-01-07 10:34:56 -05:00
< table >
2025-05-16 20:02:49 -04:00
< thead >
< tr >
2026-01-01 19:06:33 -05:00
< ? php if ( $GLOBALS [ 'currentUser' ][ 'is_admin' ] ? ? false ) : ?>
2026-01-30 13:15:55 -05:00
< th style = " width: 40px; " >< input type = " checkbox " id = " selectAllCheckbox " data - action = " toggle-select-all " ></ th >
2026-01-01 19:06:33 -05:00
< ? php endif ; ?>
2025-09-05 11:08:56 -04:00
< ? php
$currentSort = isset ( $_GET [ 'sort' ]) ? $_GET [ 'sort' ] : 'ticket_id' ;
$currentDir = isset ( $_GET [ 'dir' ]) ? $_GET [ 'dir' ] : 'desc' ;
$columns = [
'ticket_id' => 'Ticket ID' ,
2026-01-01 17:37:01 -05:00
'priority' => 'Priority' ,
2025-09-05 11:08:56 -04:00
'title' => 'Title' ,
'category' => 'Category' ,
'type' => 'Type' ,
'status' => 'Status' ,
2026-01-01 17:37:01 -05:00
'created_by' => 'Created By' ,
2026-01-01 19:28:07 -05:00
'assigned_to' => 'Assigned To' ,
2025-09-05 11:08:56 -04:00
'created_at' => 'Created' ,
Implement comprehensive improvement plan (Phases 1-6)
Security (Phase 1-2):
- Add SecurityHeadersMiddleware with CSP, X-Frame-Options, etc.
- Add RateLimitMiddleware for API rate limiting
- Add security event logging to AuditLogModel
- Add ResponseHelper for standardized API responses
- Update config.php with security constants
Database (Phase 3):
- Add migration 014 for additional indexes
- Add migration 015 for ticket dependencies
- Add migration 016 for ticket attachments
- Add migration 017 for recurring tickets
- Add migration 018 for custom fields
Features (Phase 4-5):
- Add ticket dependencies with DependencyModel and API
- Add duplicate detection with check_duplicates API
- Add file attachments with AttachmentModel and upload/download APIs
- Add @mentions with autocomplete and highlighting
- Add quick actions on dashboard rows
Collaboration (Phase 5):
- Add mention extraction in CommentModel
- Add mention autocomplete dropdown in ticket.js
- Add mention highlighting CSS styles
Admin & Export (Phase 6):
- Add StatsModel for dashboard widgets
- Add dashboard stats cards (open, critical, unassigned, etc.)
- Add CSV/JSON export via export_tickets API
- Add rich text editor toolbar in markdown.js
- Add RecurringTicketModel with cron job
- Add CustomFieldModel for per-category fields
- Add admin views: RecurringTickets, CustomFields, Workflow,
Templates, AuditLog, UserActivity
- Add admin APIs: manage_workflows, manage_templates,
manage_recurring, custom_fields, get_users
- Add admin routes in index.php
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-20 09:55:01 -05:00
'updated_at' => 'Updated' ,
'_actions' => 'Actions'
2025-09-05 11:08:56 -04:00
];
foreach ( $columns as $col => $label ) {
Implement comprehensive improvement plan (Phases 1-6)
Security (Phase 1-2):
- Add SecurityHeadersMiddleware with CSP, X-Frame-Options, etc.
- Add RateLimitMiddleware for API rate limiting
- Add security event logging to AuditLogModel
- Add ResponseHelper for standardized API responses
- Update config.php with security constants
Database (Phase 3):
- Add migration 014 for additional indexes
- Add migration 015 for ticket dependencies
- Add migration 016 for ticket attachments
- Add migration 017 for recurring tickets
- Add migration 018 for custom fields
Features (Phase 4-5):
- Add ticket dependencies with DependencyModel and API
- Add duplicate detection with check_duplicates API
- Add file attachments with AttachmentModel and upload/download APIs
- Add @mentions with autocomplete and highlighting
- Add quick actions on dashboard rows
Collaboration (Phase 5):
- Add mention extraction in CommentModel
- Add mention autocomplete dropdown in ticket.js
- Add mention highlighting CSS styles
Admin & Export (Phase 6):
- Add StatsModel for dashboard widgets
- Add dashboard stats cards (open, critical, unassigned, etc.)
- Add CSV/JSON export via export_tickets API
- Add rich text editor toolbar in markdown.js
- Add RecurringTicketModel with cron job
- Add CustomFieldModel for per-category fields
- Add admin views: RecurringTickets, CustomFields, Workflow,
Templates, AuditLog, UserActivity
- Add admin APIs: manage_workflows, manage_templates,
manage_recurring, custom_fields, get_users
- Add admin routes in index.php
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-20 09:55:01 -05:00
if ( $col === '_actions' ) {
echo " <th style='width: 100px; text-align: center;'> $label </th> " ;
} else {
$newDir = ( $currentSort === $col && $currentDir === 'asc' ) ? 'desc' : 'asc' ;
$sortClass = ( $currentSort === $col ) ? " sort- $currentDir " : '' ;
$sortParams = array_merge ( $_GET , [ 'sort' => $col , 'dir' => $newDir ]);
$sortUrl = '?' . http_build_query ( $sortParams );
2026-01-30 13:15:55 -05:00
echo " <th class=' $sortClass ' data-action='navigate' data-url=' $sortUrl '> $label </th> " ;
Implement comprehensive improvement plan (Phases 1-6)
Security (Phase 1-2):
- Add SecurityHeadersMiddleware with CSP, X-Frame-Options, etc.
- Add RateLimitMiddleware for API rate limiting
- Add security event logging to AuditLogModel
- Add ResponseHelper for standardized API responses
- Update config.php with security constants
Database (Phase 3):
- Add migration 014 for additional indexes
- Add migration 015 for ticket dependencies
- Add migration 016 for ticket attachments
- Add migration 017 for recurring tickets
- Add migration 018 for custom fields
Features (Phase 4-5):
- Add ticket dependencies with DependencyModel and API
- Add duplicate detection with check_duplicates API
- Add file attachments with AttachmentModel and upload/download APIs
- Add @mentions with autocomplete and highlighting
- Add quick actions on dashboard rows
Collaboration (Phase 5):
- Add mention extraction in CommentModel
- Add mention autocomplete dropdown in ticket.js
- Add mention highlighting CSS styles
Admin & Export (Phase 6):
- Add StatsModel for dashboard widgets
- Add dashboard stats cards (open, critical, unassigned, etc.)
- Add CSV/JSON export via export_tickets API
- Add rich text editor toolbar in markdown.js
- Add RecurringTicketModel with cron job
- Add CustomFieldModel for per-category fields
- Add admin views: RecurringTickets, CustomFields, Workflow,
Templates, AuditLog, UserActivity
- Add admin APIs: manage_workflows, manage_templates,
manage_recurring, custom_fields, get_users
- Add admin routes in index.php
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-20 09:55:01 -05:00
}
2025-09-05 11:08:56 -04:00
}
?>
2025-05-16 20:02:49 -04:00
</ tr >
</ thead >
< tbody >
< ? php
if ( count ( $tickets ) > 0 ) {
foreach ( $tickets as $row ) {
2026-01-01 17:37:01 -05:00
$creator = $row [ 'creator_display_name' ] ? ? $row [ 'creator_username' ] ? ? 'System' ;
2026-01-01 19:28:07 -05:00
$assignedTo = $row [ 'assigned_display_name' ] ? ? $row [ 'assigned_username' ] ? ? 'Unassigned' ;
2025-05-16 20:02:49 -04:00
echo " <tr class='priority- { $row [ 'priority' ] } '> " ;
2026-01-01 19:06:33 -05:00
// Add checkbox column for admins
if ( $GLOBALS [ 'currentUser' ][ 'is_admin' ] ? ? false ) {
2026-01-30 13:15:55 -05:00
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> " ;
2026-01-01 19:06:33 -05:00
}
2025-09-05 11:08:56 -04:00
echo " <td><a href='/ticket/ { $row [ 'ticket_id' ] } ' class='ticket-link'> { $row [ 'ticket_id' ] } </a></td> " ;
2025-05-16 20:02:49 -04:00
echo " <td><span> { $row [ 'priority' ] } </span></td> " ;
echo " <td> " . htmlspecialchars ( $row [ 'title' ]) . " </td> " ;
echo " <td> { $row [ 'category' ] } </td> " ;
echo " <td> { $row [ 'type' ] } </td> " ;
2025-09-05 11:08:56 -04:00
echo " <td><span class='status- " . str_replace ( ' ' , '-' , $row [ 'status' ]) . " '> { $row [ 'status' ] } </span></td> " ;
2026-01-01 17:37:01 -05:00
echo " <td> " . htmlspecialchars ( $creator ) . " </td> " ;
2026-01-01 19:28:07 -05:00
echo " <td> " . htmlspecialchars ( $assignedTo ) . " </td> " ;
2025-05-16 20:02:49 -04:00
echo " <td> " . date ( 'Y-m-d H:i' , strtotime ( $row [ 'created_at' ])) . " </td> " ;
echo " <td> " . date ( 'Y-m-d H:i' , strtotime ( $row [ 'updated_at' ])) . " </td> " ;
Implement comprehensive improvement plan (Phases 1-6)
Security (Phase 1-2):
- Add SecurityHeadersMiddleware with CSP, X-Frame-Options, etc.
- Add RateLimitMiddleware for API rate limiting
- Add security event logging to AuditLogModel
- Add ResponseHelper for standardized API responses
- Update config.php with security constants
Database (Phase 3):
- Add migration 014 for additional indexes
- Add migration 015 for ticket dependencies
- Add migration 016 for ticket attachments
- Add migration 017 for recurring tickets
- Add migration 018 for custom fields
Features (Phase 4-5):
- Add ticket dependencies with DependencyModel and API
- Add duplicate detection with check_duplicates API
- Add file attachments with AttachmentModel and upload/download APIs
- Add @mentions with autocomplete and highlighting
- Add quick actions on dashboard rows
Collaboration (Phase 5):
- Add mention extraction in CommentModel
- Add mention autocomplete dropdown in ticket.js
- Add mention highlighting CSS styles
Admin & Export (Phase 6):
- Add StatsModel for dashboard widgets
- Add dashboard stats cards (open, critical, unassigned, etc.)
- Add CSV/JSON export via export_tickets API
- Add rich text editor toolbar in markdown.js
- Add RecurringTicketModel with cron job
- Add CustomFieldModel for per-category fields
- Add admin views: RecurringTickets, CustomFields, Workflow,
Templates, AuditLog, UserActivity
- Add admin APIs: manage_workflows, manage_templates,
manage_recurring, custom_fields, get_users
- Add admin routes in index.php
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-20 09:55:01 -05:00
// Quick actions column
echo " <td class='quick-actions-cell'> " ;
echo " <div class='quick-actions'> " ;
2026-01-30 13:15:55 -05:00
echo " <button data-action='view-ticket' data-ticket-id=' { $row [ 'ticket_id' ] } ' class='quick-action-btn' title='View'>👁</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-assign' data-ticket-id=' { $row [ 'ticket_id' ] } ' class='quick-action-btn' title='Assign'>👤</button> " ;
Implement comprehensive improvement plan (Phases 1-6)
Security (Phase 1-2):
- Add SecurityHeadersMiddleware with CSP, X-Frame-Options, etc.
- Add RateLimitMiddleware for API rate limiting
- Add security event logging to AuditLogModel
- Add ResponseHelper for standardized API responses
- Update config.php with security constants
Database (Phase 3):
- Add migration 014 for additional indexes
- Add migration 015 for ticket dependencies
- Add migration 016 for ticket attachments
- Add migration 017 for recurring tickets
- Add migration 018 for custom fields
Features (Phase 4-5):
- Add ticket dependencies with DependencyModel and API
- Add duplicate detection with check_duplicates API
- Add file attachments with AttachmentModel and upload/download APIs
- Add @mentions with autocomplete and highlighting
- Add quick actions on dashboard rows
Collaboration (Phase 5):
- Add mention extraction in CommentModel
- Add mention autocomplete dropdown in ticket.js
- Add mention highlighting CSS styles
Admin & Export (Phase 6):
- Add StatsModel for dashboard widgets
- Add dashboard stats cards (open, critical, unassigned, etc.)
- Add CSV/JSON export via export_tickets API
- Add rich text editor toolbar in markdown.js
- Add RecurringTicketModel with cron job
- Add CustomFieldModel for per-category fields
- Add admin views: RecurringTickets, CustomFields, Workflow,
Templates, AuditLog, UserActivity
- Add admin APIs: manage_workflows, manage_templates,
manage_recurring, custom_fields, get_users
- Add admin routes in index.php
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-20 09:55:01 -05:00
echo " </div> " ;
echo " </td> " ;
2025-05-16 20:02:49 -04:00
echo " </tr> " ;
}
2026-01-08 22:49:48 -05:00
} else {
Implement comprehensive improvement plan (Phases 1-6)
Security (Phase 1-2):
- Add SecurityHeadersMiddleware with CSP, X-Frame-Options, etc.
- Add RateLimitMiddleware for API rate limiting
- Add security event logging to AuditLogModel
- Add ResponseHelper for standardized API responses
- Update config.php with security constants
Database (Phase 3):
- Add migration 014 for additional indexes
- Add migration 015 for ticket dependencies
- Add migration 016 for ticket attachments
- Add migration 017 for recurring tickets
- Add migration 018 for custom fields
Features (Phase 4-5):
- Add ticket dependencies with DependencyModel and API
- Add duplicate detection with check_duplicates API
- Add file attachments with AttachmentModel and upload/download APIs
- Add @mentions with autocomplete and highlighting
- Add quick actions on dashboard rows
Collaboration (Phase 5):
- Add mention extraction in CommentModel
- Add mention autocomplete dropdown in ticket.js
- Add mention highlighting CSS styles
Admin & Export (Phase 6):
- Add StatsModel for dashboard widgets
- Add dashboard stats cards (open, critical, unassigned, etc.)
- Add CSV/JSON export via export_tickets API
- Add rich text editor toolbar in markdown.js
- Add RecurringTicketModel with cron job
- Add CustomFieldModel for per-category fields
- Add admin views: RecurringTickets, CustomFields, Workflow,
Templates, AuditLog, UserActivity
- Add admin APIs: manage_workflows, manage_templates,
manage_recurring, custom_fields, get_users
- Add admin routes in index.php
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-20 09:55:01 -05:00
$colspan = ( $GLOBALS [ 'currentUser' ][ 'is_admin' ] ? ? false ) ? '12' : '11' ;
2026-01-08 22:49:48 -05:00
echo " <tr><td colspan=' $colspan ' style='text-align: center; padding: 3rem;'> " ;
echo " <pre style='color: var(--terminal-green); text-shadow: var(--glow-green); font-size: 0.8rem; line-height: 1.2;'> " ;
echo " ╔════════════════════════════════════════╗ \n " ;
echo " ║ ║ \n " ;
echo " ║ NO TICKETS FOUND ║ \n " ;
echo " ║ ║ \n " ;
echo " ║ [ ] Empty queue - all clear! ║ \n " ;
echo " ║ ║ \n " ;
echo " ╚════════════════════════════════════════╝ " ;
echo " </pre> " ;
echo " </td></tr> " ;
2025-05-16 20:02:49 -04:00
}
?>
</ tbody >
2026-01-07 10:34:56 -05:00
</ table >
2026-01-23 22:10:29 -05:00
</ div ><!-- End table - wrapper -->
2026-01-07 10:34:56 -05:00
</ div >
</ div >
</ div >
<!-- END OUTER FRAME -->
2026-01-08 23:05:03 -05:00
2026-01-23 10:01:50 -05:00
<!-- Kanban Card View -->
< div id = " cardView " class = " card-view-container " style = " display: none; " >
< div class = " kanban-board " >
< div class = " kanban-column " data - status = " Open " >
< div class = " kanban-column-header status-Open " >
< span class = " column-title " > Open </ span >
< span class = " column-count " > 0 </ span >
</ div >
< div class = " kanban-cards " ></ div >
</ div >
< div class = " kanban-column " data - status = " Pending " >
< div class = " kanban-column-header status-Pending " >
< span class = " column-title " > Pending </ span >
< span class = " column-count " > 0 </ span >
</ div >
< div class = " kanban-cards " ></ div >
</ div >
< div class = " kanban-column " data - status = " In Progress " >
< div class = " kanban-column-header status-In-Progress " >
< span class = " column-title " > In Progress </ span >
< span class = " column-count " > 0 </ span >
</ div >
< div class = " kanban-cards " ></ div >
</ div >
< div class = " kanban-column " data - status = " Closed " >
< div class = " kanban-column-header status-Closed " >
< span class = " column-title " > Closed </ span >
< span class = " column-count " > 0 </ span >
</ div >
< div class = " kanban-cards " ></ div >
</ div >
</ div >
</ div >
2026-01-08 23:05:03 -05:00
<!-- Settings Modal -->
2026-01-30 13:15:55 -05:00
< div class = " settings-modal " id = " settingsModal " style = " display: none; " data - action = " close-settings-backdrop " >
2026-01-08 23:05:03 -05:00
< div class = " settings-content " >
< span class = " bottom-left-corner " > ╚ </ span >
< span class = " bottom-right-corner " > ╝ </ span >
< div class = " settings-header " >
< h3 > ⚙ System Preferences </ h3 >
2026-01-30 13:15:55 -05:00
< button class = " close-settings " data - action = " close-settings " > ✗ </ button >
2026-01-08 23:05:03 -05:00
</ div >
< div class = " settings-body " >
<!-- Display Preferences -->
< div class = " settings-section " >
< h4 > ╔══ Display Preferences ══╗ </ h4 >
< div class = " setting-row " >
< label for = " rowsPerPage " > Rows per page :</ label >
< select id = " rowsPerPage " class = " setting-select " >
< option value = " 15 " > 15 </ option >
< option value = " 25 " > 25 </ option >
< option value = " 50 " > 50 </ option >
< option value = " 100 " > 100 </ option >
</ select >
</ div >
< div class = " setting-row " >
< label for = " defaultFilters " > Default status filters :</ label >
< div class = " checkbox-group " >
< label >< input type = " checkbox " name = " defaultFilters " value = " Open " checked > Open </ label >
< label >< input type = " checkbox " name = " defaultFilters " value = " Pending " checked > Pending </ label >
< label >< input type = " checkbox " name = " defaultFilters " value = " In Progress " checked > In Progress </ label >
< label >< input type = " checkbox " name = " defaultFilters " value = " Closed " > Closed </ label >
</ div >
</ div >
< div class = " setting-row " >
< label for = " tableDensity " > Table density :</ label >
< select id = " tableDensity " class = " setting-select " >
< option value = " compact " > Compact </ option >
< option value = " normal " selected > Normal </ option >
< option value = " comfortable " > Comfortable </ option >
</ select >
</ div >
2026-01-20 21:54:04 -05:00
< div class = " setting-row " >
< label for = " userTimezone " > Timezone :</ label >
< select id = " userTimezone " class = " setting-select " >
< option value = " America/New_York " > Eastern ( EST / EDT ) </ option >
< option value = " America/Chicago " > Central ( CST / CDT ) </ option >
< option value = " America/Denver " > Mountain ( MST / MDT ) </ option >
< option value = " America/Los_Angeles " > Pacific ( PST / PDT ) </ option >
< option value = " America/Anchorage " > Alaska ( AKST / AKDT ) </ option >
< option value = " Pacific/Honolulu " > Hawaii ( HST ) </ option >
< option value = " UTC " > UTC </ option >
< option value = " Europe/London " > London ( GMT / BST ) </ option >
< option value = " Europe/Paris " > Paris ( CET / CEST ) </ option >
< option value = " Europe/Berlin " > Berlin ( CET / CEST ) </ option >
< option value = " Asia/Tokyo " > Tokyo ( JST ) </ option >
< option value = " Asia/Shanghai " > Shanghai ( CST ) </ option >
< option value = " Asia/Kolkata " > India ( IST ) </ option >
< option value = " Australia/Sydney " > Sydney ( AEST / AEDT ) </ option >
</ select >
< small style = " color: var(--text-muted); margin-top: 0.25rem; display: block; " >
Current : < ? php echo $GLOBALS [ 'config' ][ 'TIMEZONE_ABBREV' ]; ?> (<?php echo $GLOBALS['config']['TIMEZONE']; ?>)
</ small >
</ div >
2026-01-08 23:05:03 -05:00
</ div >
<!-- Notifications -->
< div class = " settings-section " >
< h4 > ╔══ Notifications ══╗ </ h4 >
< div class = " setting-row " >
< label >
< input type = " checkbox " id = " notificationsEnabled " checked >
Enable browser notifications
</ label >
</ div >
< div class = " setting-row " >
< label >
< input type = " checkbox " id = " soundEffects " checked >
Sound effects
</ label >
</ div >
< div class = " setting-row " >
< label for = " toastDuration " > Toast duration :</ label >
< select id = " toastDuration " class = " setting-select " >
< option value = " 3000 " selected > 3 seconds </ option >
< option value = " 5000 " > 5 seconds </ option >
< option value = " 10000 " > 10 seconds </ option >
</ select >
</ div >
</ div >
<!-- Keyboard Shortcuts -->
< div class = " settings-section " >
< h4 > ╔══ Keyboard Shortcuts ══╗ </ h4 >
< div class = " shortcuts-list " >
< div class = " shortcut-item " >
< kbd > Ctrl / Cmd + K </ kbd > < span > Focus search </ span >
</ div >
< div class = " shortcut-item " >
< kbd > Alt + S </ kbd > < span > Open settings </ span >
</ div >
< div class = " shortcut-item " >
< kbd > ESC </ kbd > < span > Close modal </ span >
</ div >
< div class = " shortcut-item " >
< kbd > ? </ kbd > < span > Show shortcuts </ span >
</ div >
</ div >
</ div >
<!-- User Info ( Read - only ) -->
< div class = " settings-section " >
< h4 > ╔══ User Information ══╗ </ h4 >
< div class = " user-info-grid " >
< div >< strong > Display Name :</ strong ></ div >
< div >< ? php echo htmlspecialchars ( $GLOBALS [ 'currentUser' ][ 'display_name' ] ? ? 'N/A' ); ?> </div>
< div >< strong > Username :</ strong ></ div >
< div >< ? php echo htmlspecialchars ( $GLOBALS [ 'currentUser' ][ 'username' ]); ?> </div>
< div >< strong > Email :</ strong ></ div >
< div >< ? php echo htmlspecialchars ( $GLOBALS [ 'currentUser' ][ 'email' ] ? ? 'N/A' ); ?> </div>
< div >< strong > Role :</ strong ></ div >
< div >< ? php echo $GLOBALS [ 'currentUser' ][ 'is_admin' ] ? 'Administrator' : 'User' ; ?> </div>
2026-01-23 10:01:50 -05:00
< div >< strong > Groups :</ strong ></ div >
< div class = " user-groups-list " >
< ? php
$groups = explode ( ',' , $GLOBALS [ 'currentUser' ][ 'groups' ] ? ? '' );
foreach ( $groups as $g ) :
if ( trim ( $g )) :
?>
< span class = " group-badge " >< ? php echo htmlspecialchars ( trim ( $g )); ?> </span>
< ? php
endif ;
endforeach ;
if ( empty ( trim ( $GLOBALS [ 'currentUser' ][ 'groups' ] ? ? '' ))) :
?>
< span style = " color: var(--text-muted); " > No groups assigned </ span >
< ? php endif ; ?>
</ div >
2026-01-08 23:05:03 -05:00
</ div >
</ div >
</ div >
< div class = " settings-footer " >
2026-01-30 13:15:55 -05:00
< button class = " btn btn-primary " data - action = " save-settings " > Save Preferences </ button >
< button class = " btn btn-secondary " data - action = " close-settings " > Cancel </ button >
2026-01-08 23:05:03 -05:00
</ div >
</ div >
</ div >
2026-01-09 11:20:27 -05:00
<!-- Advanced Search Modal -->
2026-01-30 13:15:55 -05:00
< div class = " settings-modal " id = " advancedSearchModal " style = " display: none; " data - action = " close-advanced-search-backdrop " >
2026-01-09 11:20:27 -05:00
< div class = " settings-content " >
< div class = " settings-header " >
< h3 > 🔍 Advanced Search </ h3 >
2026-01-30 13:15:55 -05:00
< button class = " close-settings " data - action = " close-advanced-search " > ✗ </ button >
2026-01-09 11:20:27 -05:00
</ div >
2026-01-30 13:15:55 -05:00
< form id = " advancedSearchForm " >
2026-01-09 11:20:27 -05:00
< div class = " settings-body " >
<!-- Saved Filters -->
< div class = " settings-section " >
< h4 > ╔══ Saved Filters ══╗ </ h4 >
< div class = " setting-row " >
< label for = " saved-filters-select " > Load Filter :</ label >
2026-01-30 13:15:55 -05:00
< select id = " saved-filters-select " class = " setting-select " style = " max-width: 70%; " data - action = " load-saved-filter " >
2026-01-09 11:20:27 -05:00
< option value = " " >-- Select a saved filter --</ option >
</ select >
</ div >
< div class = " setting-row " style = " justify-content: flex-end; gap: 0.5rem; " >
2026-01-30 13:15:55 -05:00
< 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 " data - action = " delete-filter " style = " padding: 0.5rem 1rem; " > 🗑 Delete Selected </ button >
2026-01-09 11:20:27 -05:00
</ div >
</ div >
<!-- Search Text -->
< div class = " settings-section " >
< h4 > ╔══ Search Criteria ══╗ </ h4 >
< div class = " setting-row " >
< 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... " >
</ div >
</ div >
<!-- Date Ranges -->
< div class = " settings-section " >
< h4 > ╔══ Date Range ══╗ </ h4 >
< div class = " setting-row " >
< label for = " adv-created-from " > Created From :</ label >
< input type = " date " id = " adv-created-from " class = " setting-select " >
</ div >
< div class = " setting-row " >
< label for = " adv-created-to " > Created To :</ label >
< input type = " date " id = " adv-created-to " class = " setting-select " >
</ div >
< div class = " setting-row " >
< label for = " adv-updated-from " > Updated From :</ label >
< input type = " date " id = " adv-updated-from " class = " setting-select " >
</ div >
< div class = " setting-row " >
< label for = " adv-updated-to " > Updated To :</ label >
< input type = " date " id = " adv-updated-to " class = " setting-select " >
</ div >
</ div >
<!-- Status / Priority / Category / Type -->
< div class = " settings-section " >
< h4 > ╔══ Filters ══╗ </ h4 >
< div class = " setting-row " >
< label for = " adv-status " > Status :</ label >
< select id = " adv-status " class = " setting-select " multiple size = " 4 " >
< option value = " Open " > Open </ option >
< option value = " Pending " > Pending </ option >
< option value = " In Progress " > In Progress </ option >
< option value = " Closed " > Closed </ option >
</ select >
</ div >
< div class = " setting-row " >
< label for = " adv-priority-min " > Priority Range :</ label >
< select id = " adv-priority-min " class = " setting-select " style = " max-width: 90px; " >
< option value = " " > Any </ option >
< option value = " 1 " > P1 </ option >
< option value = " 2 " > P2 </ option >
< option value = " 3 " > P3 </ option >
< option value = " 4 " > P4 </ option >
< option value = " 5 " > P5 </ option >
</ select >
< span style = " color: var(--terminal-green); " > to </ span >
< select id = " adv-priority-max " class = " setting-select " style = " max-width: 90px; " >
< option value = " " > Any </ option >
< option value = " 1 " > P1 </ option >
< option value = " 2 " > P2 </ option >
< option value = " 3 " > P3 </ option >
< option value = " 4 " > P4 </ option >
< option value = " 5 " > P5 </ option >
</ select >
</ div >
</ div >
<!-- User Filters -->
< div class = " settings-section " >
< h4 > ╔══ Users ══╗ </ h4 >
< div class = " setting-row " >
< label for = " adv-created-by " > Created By :</ label >
< select id = " adv-created-by " class = " setting-select " >
< option value = " " > Any User </ option >
<!-- Will be populated by JavaScript -->
</ select >
</ div >
< div class = " setting-row " >
< label for = " adv-assigned-to " > Assigned To :</ label >
< select id = " adv-assigned-to " class = " setting-select " >
< option value = " " > Any User </ option >
< option value = " unassigned " > Unassigned </ option >
<!-- Will be populated by JavaScript -->
</ select >
</ div >
</ div >
</ div >
< div class = " settings-footer " >
< button type = " submit " class = " btn btn-primary " > Search </ button >
2026-01-30 13:15:55 -05:00
< button type = " button " class = " btn btn-secondary " data - action = " reset-advanced-search " > Reset </ button >
< button type = " button " class = " btn btn-secondary " data - action = " close-advanced-search " > Cancel </ button >
2026-01-09 11:20:27 -05:00
</ div >
</ form >
</ div >
</ div >
2026-01-28 20:27:15 -05:00
< script nonce = " <?php echo $nonce ; ?> " src = " <?php echo $GLOBALS['config'] ['ASSETS_URL']; ?>/js/settings.js " ></ script >
< script nonce = " <?php echo $nonce ; ?> " src = " <?php echo $GLOBALS['config'] ['ASSETS_URL']; ?>/js/keyboard-shortcuts.js " ></ script >
< script nonce = " <?php echo $nonce ; ?> " src = " <?php echo $GLOBALS['config'] ['ASSETS_URL']; ?>/js/advanced-search.js " ></ script >
< script nonce = " <?php echo $nonce ; ?> " >
2026-01-30 13:15:55 -05:00
// Event delegation for all data-action handlers
2026-01-20 21:11:49 -05:00
document . addEventListener ( 'click' , function ( event ) {
2026-01-30 13:15:55 -05:00
const target = event . target . closest ( '[data-action]' );
if ( ! target ) {
// Close admin dropdown when clicking outside
const dropdown = document . getElementById ( 'adminDropdown' );
if ( dropdown && ! event . target . closest ( '.admin-dropdown' )) {
dropdown . classList . remove ( 'show' );
}
return ;
}
const action = target . dataset . action ;
switch ( action ) {
case 'toggle-admin-menu' :
event . stopPropagation ();
document . getElementById ( 'adminDropdown' ) . classList . toggle ( 'show' );
break ;
case 'open-settings' :
openSettingsModal ();
break ;
case 'close-settings' :
closeSettingsModal ();
break ;
case 'close-settings-backdrop' :
if ( event . target === target ) closeSettingsModal ();
break ;
case 'save-settings' :
saveSettings ();
break ;
case 'toggle-banner' :
toggleBanner ();
break ;
case 'toggle-sidebar' :
toggleSidebar ();
break ;
case 'open-advanced-search' :
openAdvancedSearch ();
break ;
case 'close-advanced-search' :
closeAdvancedSearch ();
break ;
case 'close-advanced-search-backdrop' :
if ( event . target === target ) closeAdvancedSearch ();
break ;
case 'reset-advanced-search' :
resetAdvancedSearch ();
break ;
case 'set-view-mode' :
setViewMode ( target . dataset . mode );
break ;
case 'navigate' :
window . location . href = target . dataset . url ;
break ;
case 'toggle-export-menu' :
event . stopPropagation ();
toggleExportMenu ( event );
break ;
case 'export-tickets' :
event . preventDefault ();
exportSelectedTickets ( target . dataset . format );
break ;
case 'bulk-status' :
showBulkStatusModal ();
break ;
case 'bulk-assign' :
showBulkAssignModal ();
break ;
case 'bulk-priority' :
showBulkPriorityModal ();
break ;
case 'clear-selection' :
clearSelection ();
break ;
case 'toggle-select-all' :
toggleSelectAll ();
break ;
case 'toggle-row-checkbox' :
toggleRowCheckbox ( event , target );
break ;
case 'view-ticket' :
event . stopPropagation ();
window . location . href = '/ticket/' + target . dataset . ticketId ;
break ;
case 'quick-status' :
event . stopPropagation ();
quickStatusChange ( target . dataset . ticketId , target . dataset . status );
break ;
case 'quick-assign' :
event . stopPropagation ();
quickAssign ( target . dataset . ticketId );
break ;
case 'save-filter' :
saveCurrentFilter ();
break ;
case 'delete-filter' :
deleteSavedFilter ();
break ;
}
});
// Handle change events separately
document . addEventListener ( 'change' , function ( event ) {
const target = event . target . closest ( '[data-action]' );
if ( ! target ) return ;
const action = target . dataset . action ;
switch ( action ) {
case 'update-selection' :
updateSelectionCount ();
break ;
case 'load-saved-filter' :
loadSavedFilter ();
break ;
2026-01-20 21:11:49 -05:00
}
});
2026-01-30 13:15:55 -05:00
// Handle form submit for advanced search
document . getElementById ( 'advancedSearchForm' ) . addEventListener ( 'submit' , function ( event ) {
performAdvancedSearch ( event );
});
2026-01-20 21:48:25 -05:00
// Helper function to get date in server timezone
function getServerDate () {
const now = new Date ();
const serverTime = new Date ( now . getTime () + ( window . APP_TIMEZONE_OFFSET * 60000 ) + ( now . getTimezoneOffset () * 60000 ));
return serverTime . getFullYear () + '-' +
String ( serverTime . getMonth () + 1 ) . padStart ( 2 , '0' ) + '-' +
String ( serverTime . getDate ()) . padStart ( 2 , '0' );
}
2026-01-20 21:11:49 -05:00
// Stat card click handlers for filtering
document . querySelectorAll ( '.stat-card' ) . forEach ( card => {
card . style . cursor = 'pointer' ;
card . addEventListener ( 'click' , function () {
const classList = this . classList ;
let url = '/?' ;
2026-01-20 21:48:25 -05:00
const today = getServerDate ();
2026-01-20 21:11:49 -05:00
if ( classList . contains ( 'stat-open' )) {
url += 'status=Open' ;
} else if ( classList . contains ( 'stat-critical' )) {
url += 'status=Open,Pending,In+Progress&priority_max=1' ;
} else if ( classList . contains ( 'stat-unassigned' )) {
url += 'status=Open,Pending,In+Progress&assigned_to=unassigned' ;
} else if ( classList . contains ( 'stat-today' )) {
url += 'status=Open,Pending,In+Progress&created_from=' + today + '&created_to=' + today ;
} else if ( classList . contains ( 'stat-resolved' )) {
url += 'status=Closed&updated_from=' + today + '&updated_to=' + today ;
}
if ( url !== '/?' ) {
window . location . href = url ;
}
});
});
</ script >
2025-05-16 20:02:49 -04:00
</ body >
</ html >