better filtering and searching

This commit is contained in:
2026-01-09 11:20:27 -05:00
parent f9d9c775fb
commit 962724d811
10 changed files with 1388 additions and 9 deletions

View File

@@ -321,4 +321,123 @@ class AuditLogModel {
$stmt->close();
return $timeline;
}
/**
* Get filtered audit logs with advanced search
*
* @param array $filters Associative array of filter criteria
* @param int $limit Maximum number of logs to return
* @param int $offset Offset for pagination
* @return array Array containing logs and total count
*/
public function getFilteredLogs($filters = [], $limit = 50, $offset = 0) {
$whereConditions = [];
$params = [];
$paramTypes = '';
// Action type filter
if (!empty($filters['action_type'])) {
$actions = explode(',', $filters['action_type']);
$placeholders = str_repeat('?,', count($actions) - 1) . '?';
$whereConditions[] = "al.action_type IN ($placeholders)";
$params = array_merge($params, $actions);
$paramTypes .= str_repeat('s', count($actions));
}
// Entity type filter
if (!empty($filters['entity_type'])) {
$entities = explode(',', $filters['entity_type']);
$placeholders = str_repeat('?,', count($entities) - 1) . '?';
$whereConditions[] = "al.entity_type IN ($placeholders)";
$params = array_merge($params, $entities);
$paramTypes .= str_repeat('s', count($entities));
}
// User filter
if (!empty($filters['user_id'])) {
$whereConditions[] = "al.user_id = ?";
$params[] = (int)$filters['user_id'];
$paramTypes .= 'i';
}
// Entity ID filter (for specific ticket/comment)
if (!empty($filters['entity_id'])) {
$whereConditions[] = "al.entity_id = ?";
$params[] = $filters['entity_id'];
$paramTypes .= 's';
}
// Date range filters
if (!empty($filters['date_from'])) {
$whereConditions[] = "DATE(al.created_at) >= ?";
$params[] = $filters['date_from'];
$paramTypes .= 's';
}
if (!empty($filters['date_to'])) {
$whereConditions[] = "DATE(al.created_at) <= ?";
$params[] = $filters['date_to'];
$paramTypes .= 's';
}
// IP address filter
if (!empty($filters['ip_address'])) {
$whereConditions[] = "al.ip_address LIKE ?";
$params[] = '%' . $filters['ip_address'] . '%';
$paramTypes .= 's';
}
// Build WHERE clause
$whereClause = '';
if (!empty($whereConditions)) {
$whereClause = 'WHERE ' . implode(' AND ', $whereConditions);
}
// Get total count for pagination
$countSql = "SELECT COUNT(*) as total FROM audit_log al $whereClause";
$countStmt = $this->conn->prepare($countSql);
if (!empty($params)) {
$countStmt->bind_param($paramTypes, ...$params);
}
$countStmt->execute();
$totalResult = $countStmt->get_result();
$totalCount = $totalResult->fetch_assoc()['total'];
$countStmt->close();
// Get filtered logs
$sql = "SELECT al.*, u.username, u.display_name
FROM audit_log al
LEFT JOIN users u ON al.user_id = u.user_id
$whereClause
ORDER BY al.created_at DESC
LIMIT ? OFFSET ?";
$stmt = $this->conn->prepare($sql);
// Add limit and offset parameters
$params[] = $limit;
$params[] = $offset;
$paramTypes .= 'ii';
if (!empty($params)) {
$stmt->bind_param($paramTypes, ...$params);
}
$stmt->execute();
$result = $stmt->get_result();
$logs = [];
while ($row = $result->fetch_assoc()) {
if ($row['details']) {
$row['details'] = json_decode($row['details'], true);
}
$logs[] = $row;
}
$stmt->close();
return [
'logs' => $logs,
'total' => $totalCount,
'pages' => ceil($totalCount / $limit)
];
}
}

View File

@@ -0,0 +1,189 @@
<?php
/**
* SavedFiltersModel
* Handles saving, loading, and managing user's custom search filters
*/
class SavedFiltersModel {
private $conn;
public function __construct($conn) {
$this->conn = $conn;
}
/**
* Get all saved filters for a user
*/
public function getUserFilters($userId) {
$sql = "SELECT filter_id, filter_name, filter_criteria, is_default, created_at, updated_at
FROM saved_filters
WHERE user_id = ?
ORDER BY is_default DESC, filter_name ASC";
$stmt = $this->conn->prepare($sql);
$stmt->bind_param("i", $userId);
$stmt->execute();
$result = $stmt->get_result();
$filters = [];
while ($row = $result->fetch_assoc()) {
$row['filter_criteria'] = json_decode($row['filter_criteria'], true);
$filters[] = $row;
}
return $filters;
}
/**
* Get a specific saved filter
*/
public function getFilter($filterId, $userId) {
$sql = "SELECT filter_id, filter_name, filter_criteria, is_default
FROM saved_filters
WHERE filter_id = ? AND user_id = ?";
$stmt = $this->conn->prepare($sql);
$stmt->bind_param("ii", $filterId, $userId);
$stmt->execute();
$result = $stmt->get_result();
if ($row = $result->fetch_assoc()) {
$row['filter_criteria'] = json_decode($row['filter_criteria'], true);
return $row;
}
return null;
}
/**
* Save a new filter
*/
public function saveFilter($userId, $filterName, $filterCriteria, $isDefault = false) {
// If this is set as default, unset all other defaults for this user
if ($isDefault) {
$this->clearDefaultFilters($userId);
}
$sql = "INSERT INTO saved_filters (user_id, filter_name, filter_criteria, is_default)
VALUES (?, ?, ?, ?)
ON DUPLICATE KEY UPDATE
filter_criteria = VALUES(filter_criteria),
is_default = VALUES(is_default),
updated_at = CURRENT_TIMESTAMP";
$stmt = $this->conn->prepare($sql);
$criteriaJson = json_encode($filterCriteria);
$stmt->bind_param("issi", $userId, $filterName, $criteriaJson, $isDefault);
if ($stmt->execute()) {
return [
'success' => true,
'filter_id' => $stmt->insert_id ?: $this->getFilterIdByName($userId, $filterName)
];
}
return ['success' => false, 'error' => $this->conn->error];
}
/**
* Update an existing filter
*/
public function updateFilter($filterId, $userId, $filterName, $filterCriteria, $isDefault = false) {
// Verify ownership
$existing = $this->getFilter($filterId, $userId);
if (!$existing) {
return ['success' => false, 'error' => 'Filter not found'];
}
// If this is set as default, unset all other defaults for this user
if ($isDefault) {
$this->clearDefaultFilters($userId);
}
$sql = "UPDATE saved_filters
SET filter_name = ?, filter_criteria = ?, is_default = ?, updated_at = CURRENT_TIMESTAMP
WHERE filter_id = ? AND user_id = ?";
$stmt = $this->conn->prepare($sql);
$criteriaJson = json_encode($filterCriteria);
$stmt->bind_param("ssiii", $filterName, $criteriaJson, $isDefault, $filterId, $userId);
if ($stmt->execute()) {
return ['success' => true];
}
return ['success' => false, 'error' => $this->conn->error];
}
/**
* Delete a saved filter
*/
public function deleteFilter($filterId, $userId) {
$sql = "DELETE FROM saved_filters WHERE filter_id = ? AND user_id = ?";
$stmt = $this->conn->prepare($sql);
$stmt->bind_param("ii", $filterId, $userId);
if ($stmt->execute() && $stmt->affected_rows > 0) {
return ['success' => true];
}
return ['success' => false, 'error' => 'Filter not found'];
}
/**
* Set a filter as default
*/
public function setDefaultFilter($filterId, $userId) {
// First, clear all defaults
$this->clearDefaultFilters($userId);
// Then set this one as default
$sql = "UPDATE saved_filters SET is_default = 1 WHERE filter_id = ? AND user_id = ?";
$stmt = $this->conn->prepare($sql);
$stmt->bind_param("ii", $filterId, $userId);
if ($stmt->execute()) {
return ['success' => true];
}
return ['success' => false, 'error' => $this->conn->error];
}
/**
* Get the default filter for a user
*/
public function getDefaultFilter($userId) {
$sql = "SELECT filter_id, filter_name, filter_criteria
FROM saved_filters
WHERE user_id = ? AND is_default = 1
LIMIT 1";
$stmt = $this->conn->prepare($sql);
$stmt->bind_param("i", $userId);
$stmt->execute();
$result = $stmt->get_result();
if ($row = $result->fetch_assoc()) {
$row['filter_criteria'] = json_decode($row['filter_criteria'], true);
return $row;
}
return null;
}
/**
* Clear all default filters for a user (helper method)
*/
private function clearDefaultFilters($userId) {
$sql = "UPDATE saved_filters SET is_default = 0 WHERE user_id = ?";
$stmt = $this->conn->prepare($sql);
$stmt->bind_param("i", $userId);
$stmt->execute();
}
/**
* Get filter ID by name (helper method)
*/
private function getFilterIdByName($userId, $filterName) {
$sql = "SELECT filter_id FROM saved_filters WHERE user_id = ? AND filter_name = ?";
$stmt = $this->conn->prepare($sql);
$stmt->bind_param("is", $userId, $filterName);
$stmt->execute();
$result = $stmt->get_result();
if ($row = $result->fetch_assoc()) {
return $row['filter_id'];
}
return null;
}
}
?>

View File

@@ -46,15 +46,15 @@ class TicketModel {
return $comments;
}
public function getAllTickets($page = 1, $limit = 15, $status = 'Open', $sortColumn = 'ticket_id', $sortDirection = 'desc', $category = null, $type = null, $search = null) {
public function getAllTickets($page = 1, $limit = 15, $status = 'Open', $sortColumn = 'ticket_id', $sortDirection = 'desc', $category = null, $type = null, $search = null, $filters = []) {
// Calculate offset
$offset = ($page - 1) * $limit;
// Build WHERE clause
$whereConditions = [];
$params = [];
$paramTypes = '';
// Status filtering
if ($status) {
$statuses = explode(',', $status);
@@ -63,7 +63,7 @@ class TicketModel {
$params = array_merge($params, $statuses);
$paramTypes .= str_repeat('s', count($statuses));
}
// Category filtering
if ($category) {
$categories = explode(',', $category);
@@ -72,7 +72,7 @@ class TicketModel {
$params = array_merge($params, $categories);
$paramTypes .= str_repeat('s', count($categories));
}
// Type filtering
if ($type) {
$types = explode(',', $type);
@@ -81,7 +81,7 @@ class TicketModel {
$params = array_merge($params, $types);
$paramTypes .= str_repeat('s', count($types));
}
// Search Functionality
if ($search && !empty($search)) {
$whereConditions[] = "(title LIKE ? OR description LIKE ? OR ticket_id LIKE ? OR category LIKE ? OR type LIKE ?)";
@@ -89,6 +89,61 @@ class TicketModel {
$params = array_merge($params, [$searchTerm, $searchTerm, $searchTerm, $searchTerm, $searchTerm]);
$paramTypes .= 'sssss';
}
// Advanced search filters
// Date range - created_at
if (!empty($filters['created_from'])) {
$whereConditions[] = "DATE(t.created_at) >= ?";
$params[] = $filters['created_from'];
$paramTypes .= 's';
}
if (!empty($filters['created_to'])) {
$whereConditions[] = "DATE(t.created_at) <= ?";
$params[] = $filters['created_to'];
$paramTypes .= 's';
}
// Date range - updated_at
if (!empty($filters['updated_from'])) {
$whereConditions[] = "DATE(t.updated_at) >= ?";
$params[] = $filters['updated_from'];
$paramTypes .= 's';
}
if (!empty($filters['updated_to'])) {
$whereConditions[] = "DATE(t.updated_at) <= ?";
$params[] = $filters['updated_to'];
$paramTypes .= 's';
}
// Priority range
if (!empty($filters['priority_min'])) {
$whereConditions[] = "t.priority >= ?";
$params[] = (int)$filters['priority_min'];
$paramTypes .= 'i';
}
if (!empty($filters['priority_max'])) {
$whereConditions[] = "t.priority <= ?";
$params[] = (int)$filters['priority_max'];
$paramTypes .= 'i';
}
// Created by user
if (!empty($filters['created_by'])) {
$whereConditions[] = "t.created_by = ?";
$params[] = (int)$filters['created_by'];
$paramTypes .= 'i';
}
// Assigned to user (including unassigned option)
if (!empty($filters['assigned_to'])) {
if ($filters['assigned_to'] === 'unassigned') {
$whereConditions[] = "t.assigned_to IS NULL";
} else {
$whereConditions[] = "t.assigned_to = ?";
$params[] = (int)$filters['assigned_to'];
$paramTypes .= 'i';
}
}
$whereClause = '';
if (!empty($whereConditions)) {