UX and architecture fixes: bulk-delete, template guard, statuses config
Bug fixes: - bulk-delete action called undefined bulkDelete() — wired to the existing showBulkDeleteModal() so the confirmation modal actually shows UX: - Template loader now checks for existing title/description and asks for confirmation before overwriting user-typed content - Visibility select shows a dynamic hint paragraph that updates when the user changes the selection (public/internal/confidential) Architecture: - TICKET_STATUSES added to config as single source of truth; all hardcoded ['Open','Pending','In Progress','Closed'] arrays in DashboardView now read from config; bulk-status modal in dashboard.js reads window.TICKET_STATUSES (set from PHP) with array fallback - ASSET_VERSION now auto-computed from max mtime of dashboard/ticket CSS+JS files so browsers always pick up changes on deploy; manual override still available via ASSET_VERSION in .env - Removed 10 dead standalone stat methods from StatsModel (getOpenTicketCount, getClosedTicketCount, getTicketsByPriority, etc.) — all superseded by the consolidated fetchAllStats() queries, never called externally Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -22,110 +22,6 @@ class StatsModel {
|
||||
$this->conn = $conn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get count of open tickets
|
||||
*/
|
||||
public function getOpenTicketCount(): int {
|
||||
$sql = "SELECT COUNT(*) as count FROM tickets WHERE status IN ('Open', 'Pending', 'In Progress')";
|
||||
$result = $this->conn->query($sql);
|
||||
$row = $result->fetch_assoc();
|
||||
return (int)$row['count'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get count of closed tickets
|
||||
*/
|
||||
public function getClosedTicketCount(): int {
|
||||
$sql = "SELECT COUNT(*) as count FROM tickets WHERE status = 'Closed'";
|
||||
$result = $this->conn->query($sql);
|
||||
$row = $result->fetch_assoc();
|
||||
return (int)$row['count'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get tickets grouped by priority
|
||||
*/
|
||||
public function getTicketsByPriority(): array {
|
||||
$sql = "SELECT priority, COUNT(*) as count FROM tickets WHERE status != 'Closed' GROUP BY priority ORDER BY priority";
|
||||
$result = $this->conn->query($sql);
|
||||
$data = [];
|
||||
while ($row = $result->fetch_assoc()) {
|
||||
$data['P' . $row['priority']] = (int)$row['count'];
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get tickets grouped by status
|
||||
*/
|
||||
public function getTicketsByStatus(): array {
|
||||
$sql = "SELECT status, COUNT(*) as count FROM tickets GROUP BY status ORDER BY FIELD(status, 'Open', 'Pending', 'In Progress', 'Closed')";
|
||||
$result = $this->conn->query($sql);
|
||||
$data = [];
|
||||
while ($row = $result->fetch_assoc()) {
|
||||
$data[$row['status']] = (int)$row['count'];
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get tickets grouped by category
|
||||
*/
|
||||
public function getTicketsByCategory(): array {
|
||||
$sql = "SELECT category, COUNT(*) as count FROM tickets WHERE status != 'Closed' GROUP BY category ORDER BY count DESC";
|
||||
$result = $this->conn->query($sql);
|
||||
$data = [];
|
||||
while ($row = $result->fetch_assoc()) {
|
||||
$data[$row['category']] = (int)$row['count'];
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get average resolution time in hours
|
||||
*/
|
||||
public function getAverageResolutionTime(): float {
|
||||
$sql = "SELECT AVG(TIMESTAMPDIFF(HOUR, created_at, closed_at)) as avg_hours
|
||||
FROM tickets
|
||||
WHERE status = 'Closed'
|
||||
AND created_at IS NOT NULL
|
||||
AND closed_at IS NOT NULL
|
||||
AND closed_at > created_at";
|
||||
$result = $this->conn->query($sql);
|
||||
$row = $result->fetch_assoc();
|
||||
return $row['avg_hours'] ? round($row['avg_hours'], 1) : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get count of tickets created today
|
||||
*/
|
||||
public function getTicketsCreatedToday(): int {
|
||||
$sql = "SELECT COUNT(*) as count FROM tickets WHERE DATE(created_at) = CURDATE()";
|
||||
$result = $this->conn->query($sql);
|
||||
$row = $result->fetch_assoc();
|
||||
return (int)$row['count'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get count of tickets created this week
|
||||
*/
|
||||
public function getTicketsCreatedThisWeek(): int {
|
||||
$sql = "SELECT COUNT(*) as count FROM tickets WHERE YEARWEEK(created_at, 1) = YEARWEEK(CURDATE(), 1)";
|
||||
$result = $this->conn->query($sql);
|
||||
$row = $result->fetch_assoc();
|
||||
return (int)$row['count'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get count of tickets closed today
|
||||
*/
|
||||
public function getTicketsClosedToday(): int {
|
||||
$sql = "SELECT COUNT(*) as count FROM tickets WHERE status = 'Closed' AND DATE(closed_at) = CURDATE()";
|
||||
$result = $this->conn->query($sql);
|
||||
$row = $result->fetch_assoc();
|
||||
return (int)$row['count'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get tickets by assignee (top 5)
|
||||
*/
|
||||
@@ -153,26 +49,6 @@ class StatsModel {
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get unassigned ticket count
|
||||
*/
|
||||
public function getUnassignedTicketCount(): int {
|
||||
$sql = "SELECT COUNT(*) as count FROM tickets WHERE assigned_to IS NULL AND status != 'Closed'";
|
||||
$result = $this->conn->query($sql);
|
||||
$row = $result->fetch_assoc();
|
||||
return (int)$row['count'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get critical (P1) ticket count
|
||||
*/
|
||||
public function getCriticalTicketCount(): int {
|
||||
$sql = "SELECT COUNT(*) as count FROM tickets WHERE priority = 1 AND status != 'Closed'";
|
||||
$result = $this->conn->query($sql);
|
||||
$row = $result->fetch_assoc();
|
||||
return (int)$row['count'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all stats as a single array, respecting ticket visibility for the given user.
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user