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:
@@ -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";
|
||||
|
||||
Reference in New Issue
Block a user