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>
This commit is contained in:
2026-01-20 09:55:01 -05:00
parent 8c7211d311
commit be505b7312
53 changed files with 6640 additions and 169 deletions

View File

@@ -175,6 +175,56 @@
<!-- Main Content Area -->
<main class="dashboard-main">
<!-- 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; ?>
<!-- CONDENSED TOOLBAR: Combined Header, Search, Actions, Pagination -->
<div class="dashboard-toolbar">
<!-- Left: Title + Search -->
@@ -214,6 +264,13 @@
<!-- Center: Actions + Count -->
<div class="toolbar-center">
<button onclick="window.location.href='<?php echo $GLOBALS['config']['BASE_URL']; ?>/ticket/create'" class="btn create-ticket">+ New Ticket</button>
<div class="export-dropdown">
<button class="btn">↓ Export</button>
<div class="export-dropdown-content">
<a href="/api/export_tickets.php?format=csv<?php echo isset($_GET['status']) ? '&status=' . urlencode($_GET['status']) : ''; ?><?php echo isset($_GET['category']) ? '&category=' . urlencode($_GET['category']) : ''; ?>">CSV</a>
<a href="/api/export_tickets.php?format=json<?php echo isset($_GET['status']) ? '&status=' . urlencode($_GET['status']) : ''; ?><?php echo isset($_GET['category']) ? '&category=' . urlencode($_GET['category']) : ''; ?>">JSON</a>
</div>
</div>
<span class="ticket-count">Total: <?php echo $totalTickets; ?></span>
</div>
@@ -296,16 +353,20 @@
'created_by' => 'Created By',
'assigned_to' => 'Assigned To',
'created_at' => 'Created',
'updated_at' => 'Updated'
'updated_at' => 'Updated',
'_actions' => 'Actions'
];
foreach($columns as $col => $label) {
$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);
echo "<th class='$sortClass' onclick='window.location.href=\"$sortUrl\"'>$label</th>";
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);
echo "<th class='$sortClass' onclick='window.location.href=\"$sortUrl\"'>$label</th>";
}
}
?>
</tr>
@@ -333,10 +394,18 @@
echo "<td>" . htmlspecialchars($assignedTo) . "</td>";
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>";
// Quick actions column
echo "<td class='quick-actions-cell'>";
echo "<div class='quick-actions'>";
echo "<button onclick=\"event.stopPropagation(); window.location.href='/ticket/{$row['ticket_id']}'\" class='quick-action-btn' title='View'>👁</button>";
echo "<button onclick=\"event.stopPropagation(); quickStatusChange('{$row['ticket_id']}', '{$row['status']}')\" class='quick-action-btn' title='Change Status'>🔄</button>";
echo "<button onclick=\"event.stopPropagation(); quickAssign('{$row['ticket_id']}')\" class='quick-action-btn' title='Assign'>👤</button>";
echo "</div>";
echo "</td>";
echo "</tr>";
}
} else {
$colspan = ($GLOBALS['currentUser']['is_admin'] ?? false) ? '11' : '10';
$colspan = ($GLOBALS['currentUser']['is_admin'] ?? false) ? '12' : '11';
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";