<?php
$pageTitle = "Support Tickets";
include_once('includes/header.php');
// Create support tickets and replies tables if not exists
$conn->query("
CREATE TABLE IF NOT EXISTS `support_tickets` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`ticket_number` varchar(20) NOT NULL,
`user_id` int(11) NOT NULL,
`subject` varchar(255) NOT NULL,
`message` text NOT NULL,
`category` varchar(50) DEFAULT NULL,
`priority` varchar(20) DEFAULT 'normal',
`status` enum('open','in_progress','resolved','closed') DEFAULT 'open',
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
`course_id` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `ticket_number` (`ticket_number`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
");
$conn->query("
CREATE TABLE IF NOT EXISTS `support_ticket_replies` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`ticket_id` int(11) NOT NULL,
`is_admin` tinyint(1) NOT NULL DEFAULT 0,
`user_id` int(11) NOT NULL,
`message` text NOT NULL,
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `ticket_id` (`ticket_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
");
// Handle ticket status update
if (isset($_POST['update_status']) && isset($_POST['ticket_ids']) && is_array($_POST['ticket_ids'])) {
$status = $conn->real_escape_string($_POST['status']);
$ticket_ids = array_map('intval', $_POST['ticket_ids']);
if (!empty($ticket_ids)) {
$ids_str = implode(',', $ticket_ids);
$query = "UPDATE support_tickets SET status = '$status' WHERE id IN ($ids_str)";
if ($conn->query($query)) {
$_SESSION['success_msg'] = count($ticket_ids) . " ticket(s) updated successfully.";
} else {
$_SESSION['error_msg'] = "Failed to update tickets.";
}
}
// Redirect to avoid form resubmission
header("Location: support_tickets.php");
exit();
}
// Define filters from GET parameters
$status = isset($_GET['status']) ? $conn->real_escape_string($_GET['status']) : '';
$priority = isset($_GET['priority']) ? $conn->real_escape_string($_GET['priority']) : '';
$category = isset($_GET['category']) ? $conn->real_escape_string($_GET['category']) : '';
$search = isset($_GET['search']) ? $conn->real_escape_string($_GET['search']) : '';
// Check if we're using student_id or user_id in the support_tickets table
$column_check = $conn->query("SHOW COLUMNS FROM support_tickets LIKE 'student_id'");
$user_column = ($column_check->num_rows > 0) ? 'student_id' : 'user_id';
// Check if we're using ticket_number or ticket_id in the table
$ticket_number_check = $conn->query("SHOW COLUMNS FROM support_tickets LIKE 'ticket_number'");
$ticket_id_field = ($ticket_number_check->num_rows > 0) ? 'ticket_number' : 'ticket_id';
// Build query with filters
$query = "SELECT t.*, CONCAT(u.first_name, ' ', u.last_name) as student_name, u.email as student_email,
c.title as course_name,
(SELECT COUNT(*) FROM support_ticket_replies WHERE ticket_id = t.id) as reply_count
FROM support_tickets t
LEFT JOIN users u ON t.{$user_column} = u.id
LEFT JOIN courses c ON t.course_id = c.id
WHERE 1=1";
if (!empty($status)) {
$query .= " AND t.status = '$status'";
}
if (!empty($priority)) {
$query .= " AND t.priority = '$priority'";
}
if (!empty($category)) {
$query .= " AND t.category = '$category'";
}
if (!empty($search)) {
$query .= " AND (t.{$ticket_id_field} LIKE '%$search%' OR t.subject LIKE '%$search%' OR CONCAT(u.first_name, ' ', u.last_name) LIKE '%$search%' OR u.email LIKE '%$search%')";
}
// Order by updated_at to show recently updated tickets first
$query .= " ORDER BY
CASE WHEN t.status = 'open' THEN 1
WHEN t.status = 'in_progress' THEN 2
WHEN t.status = 'resolved' THEN 3
WHEN t.status = 'closed' THEN 4
END,
CASE WHEN t.priority = 'high' THEN 1
WHEN t.priority = 'normal' THEN 2
WHEN t.priority = 'low' THEN 3
END,
t.updated_at DESC";
$result = $conn->query($query);
$tickets = [];
if ($result && $result->num_rows > 0) {
while ($row = $result->fetch_assoc()) {
$tickets[] = $row;
}
}
// Count tickets by status
$status_counts = [
'open' => 0,
'in_progress' => 0,
'resolved' => 0,
'closed' => 0,
'total' => 0
];
$count_result = $conn->query("SELECT status, COUNT(*) as count FROM support_tickets GROUP BY status");
if ($count_result && $count_result->num_rows > 0) {
while ($row = $count_result->fetch_assoc()) {
$status_counts[$row['status']] = (int)$row['count'];
$status_counts['total'] += (int)$row['count'];
}
}
// Define support categories and priorities for display
$support_categories = [
'account' => 'Account Access',
'payment' => 'Payment Issues',
'enrollment' => 'Course Enrollment',
'certificate' => 'Certificate Issues',
'exam' => 'Exam Related',
'technical' => 'Technical Support',
'general' => 'General Information',
'other' => 'Other'
];
$priorities = [
'low' => 'Low',
'normal' => 'Normal',
'high' => 'High'
];
$statuses = [
'open' => 'Open',
'in_progress' => 'In Progress',
'resolved' => 'Resolved',
'closed' => 'Closed'
];
// CSS classes for status badges
$status_classes = [
'open' => 'bg-danger',
'in_progress' => 'bg-warning',
'resolved' => 'bg-success',
'closed' => 'bg-secondary'
];
// CSS classes for priority badges
$priority_classes = [
'low' => 'bg-info',
'normal' => 'bg-primary',
'high' => 'bg-danger'
];
?>
<div class="container-fluid py-4">
<div class="d-flex justify-content-between align-items-center mb-4">
<h2 class="h3">Support Tickets</h2>
<div>
<a href="canned_responses.php" class="btn btn-outline-primary me-2">
<i class="fas fa-comment-dots me-1"></i> Manage Canned Responses
</a>
</div>
</div>
<?php if (isset($_SESSION['success_msg'])): ?>
<div class="alert alert-success alert-dismissible fade show" role="alert">
<?php echo $_SESSION['success_msg']; unset($_SESSION['success_msg']); ?>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
<?php endif; ?>
<?php if (isset($_SESSION['error_msg'])): ?>
<div class="alert alert-danger alert-dismissible fade show" role="alert">
<?php echo $_SESSION['error_msg']; unset($_SESSION['error_msg']); ?>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
<?php endif; ?>
<!-- Status summary cards -->
<div class="row mb-4">
<div class="col-md-3 mb-3">
<div class="card h-100 border-left-primary">
<div class="card-body">
<div class="row align-items-center">
<div class="col mr-2">
<div class="text-xs font-weight-bold text-primary text-uppercase mb-1">Open Tickets</div>
<div class="h5 mb-0 font-weight-bold"><?php echo $status_counts['open']; ?></div>
</div>
<div class="col-auto">
<i class="fas fa-ticket-alt fa-2x text-gray-300"></i>
</div>
</div>
</div>
<div class="card-footer bg-transparent">
<a href="support_tickets.php?status=open" class="text-decoration-none">View Open Tickets</a>
</div>
</div>
</div>
<div class="col-md-3 mb-3">
<div class="card h-100 border-left-warning">
<div class="card-body">
<div class="row align-items-center">
<div class="col mr-2">
<div class="text-xs font-weight-bold text-warning text-uppercase mb-1">In Progress</div>
<div class="h5 mb-0 font-weight-bold"><?php echo $status_counts['in_progress']; ?></div>
</div>
<div class="col-auto">
<i class="fas fa-spinner fa-2x text-gray-300"></i>
</div>
</div>
</div>
<div class="card-footer bg-transparent">
<a href="support_tickets.php?status=in_progress" class="text-decoration-none">View In Progress</a>
</div>
</div>
</div>
<div class="col-md-3 mb-3">
<div class="card h-100 border-left-success">
<div class="card-body">
<div class="row align-items-center">
<div class="col mr-2">
<div class="text-xs font-weight-bold text-success text-uppercase mb-1">Resolved</div>
<div class="h5 mb-0 font-weight-bold"><?php echo $status_counts['resolved']; ?></div>
</div>
<div class="col-auto">
<i class="fas fa-check-circle fa-2x text-gray-300"></i>
</div>
</div>
</div>
<div class="card-footer bg-transparent">
<a href="support_tickets.php?status=resolved" class="text-decoration-none">View Resolved</a>
</div>
</div>
</div>
<div class="col-md-3 mb-3">
<div class="card h-100 border-left-secondary">
<div class="card-body">
<div class="row align-items-center">
<div class="col mr-2">
<div class="text-xs font-weight-bold text-secondary text-uppercase mb-1">Total Tickets</div>
<div class="h5 mb-0 font-weight-bold"><?php echo $status_counts['total']; ?></div>
</div>
<div class="col-auto">
<i class="fas fa-clipboard-list fa-2x text-gray-300"></i>
</div>
</div>
</div>
<div class="card-footer bg-transparent">
<a href="support_tickets.php" class="text-decoration-none">View All Tickets</a>
</div>
</div>
</div>
</div>
<!-- Filter bar -->
<div class="card mb-4">
<div class="card-body">
<form method="get" action="" id="filter-form" class="row g-3">
<div class="col-md-3">
<label for="status" class="form-label">Status</label>
<select class="form-select" name="status" id="status">
<option value="">All Statuses</option>
<?php foreach ($statuses as $key => $label): ?>
<option value="<?php echo $key; ?>" <?php echo $status === $key ? 'selected' : ''; ?>>
<?php echo $label; ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-3">
<label for="priority" class="form-label">Priority</label>
<select class="form-select" name="priority" id="priority">
<option value="">All Priorities</option>
<?php foreach ($priorities as $key => $label): ?>
<option value="<?php echo $key; ?>" <?php echo $priority === $key ? 'selected' : ''; ?>>
<?php echo $label; ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-3">
<label for="category" class="form-label">Category</label>
<select class="form-select" name="category" id="category">
<option value="">All Categories</option>
<?php foreach ($support_categories as $key => $label): ?>
<option value="<?php echo $key; ?>" <?php echo $category === $key ? 'selected' : ''; ?>>
<?php echo $label; ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-3">
<label for="search" class="form-label">Search</label>
<input type="text" class="form-control" id="search" name="search" value="<?php echo htmlspecialchars($search); ?>" placeholder="Ticket ID, Subject, Student...">
</div>
<div class="col-12 d-flex justify-content-end mt-4">
<?php if (!empty($status) || !empty($priority) || !empty($category) || !empty($search)): ?>
<a href="support_tickets.php" class="btn btn-outline-secondary me-2">
<i class="fas fa-times me-1"></i> Clear Filters
</a>
<?php endif; ?>
<button type="submit" class="btn btn-primary">
<i class="fas fa-filter me-1"></i> Apply Filters
</button>
</div>
</form>
</div>
</div>
<!-- Tickets table -->
<form method="post" action="" id="tickets-form">
<div class="card">
<div class="card-body">
<?php if (count($tickets) > 0): ?>
<div class="mb-3">
<div class="d-flex align-items-center">
<div class="form-check me-3">
<input class="form-check-input" type="checkbox" id="select-all">
<label class="form-check-label" for="select-all">Select All</label>
</div>
<div class="d-flex align-items-center">
<select class="form-select form-select-sm me-2" name="status" id="bulk-status" style="width: auto;">
<option value="">Change Status</option>
<?php foreach ($statuses as $key => $label): ?>
<option value="<?php echo $key; ?>"><?php echo $label; ?></option>
<?php endforeach; ?>
</select>
<button type="submit" name="update_status" class="btn btn-sm btn-outline-primary" id="bulk-update-btn" disabled>
Update Selected
</button>
</div>
</div>
</div>
<div class="table-responsive">
<table class="table table-striped table-hover">
<thead>
<tr>
<th width="3%"></th>
<th width="10%">Ticket #</th>
<th width="20%">Subject</th>
<th width="15%">User</th>
<th width="12%">Category</th>
<th width="8%">Priority</th>
<th width="10%">Status</th>
<th width="12%">Last Updated</th>
<th width="10%">Actions</th>
</tr>
</thead>
<tbody>
<?php foreach ($tickets as $ticket): ?>
<tr>
<td>
<div class="form-check">
<input class="form-check-input ticket-checkbox" type="checkbox" name="ticket_ids[]" value="<?php echo $ticket['id']; ?>">
</div>
</td>
<td>
<a href="view_ticket.php?id=<?php echo $ticket['id']; ?>" class="text-decoration-none">
<?php
// Handle different field names (ticket_number or ticket_id)
echo htmlspecialchars(isset($ticket['ticket_number']) ? $ticket['ticket_number'] :
(isset($ticket['ticket_id']) ? $ticket['ticket_id'] : 'Unknown'));
?>
</a>
<?php if ($ticket['reply_count'] > 0): ?>
<span class="badge bg-primary rounded-pill ms-1"><?php echo $ticket['reply_count']; ?></span>
<?php endif; ?>
</td>
<td>
<a href="view_ticket.php?id=<?php echo $ticket['id']; ?>" class="text-decoration-none">
<?php echo htmlspecialchars($ticket['subject']); ?>
</a>
</td>
<td>
<div><?php echo htmlspecialchars($ticket['student_name']); ?></div>
<small class="text-muted"><?php echo htmlspecialchars($ticket['student_email']); ?></small>
</td>
<td>
<?php if (!empty($ticket['category'])): ?>
<span class="badge bg-info text-dark">
<?php echo isset($support_categories[$ticket['category']]) ?
htmlspecialchars($support_categories[$ticket['category']]) :
htmlspecialchars($ticket['category']); ?>
</span>
<?php else: ?>
<span class="text-muted">Not categorized</span>
<?php endif; ?>
</td>
<td>
<span class="badge <?php echo isset($priority_classes[$ticket['priority']]) ? $priority_classes[$ticket['priority']] : 'bg-secondary'; ?>">
<?php echo ucfirst($ticket['priority']); ?>
</span>
</td>
<td>
<span class="badge <?php echo $status_classes[$ticket['status']]; ?>">
<?php echo $statuses[$ticket['status']]; ?>
</span>
</td>
<td>
<?php
$date = new DateTime($ticket['updated_at'] ? $ticket['updated_at'] : $ticket['created_at']);
echo $date->format('M d, Y');
echo '<br><small class="text-muted">' . $date->format('h:i A') . '</small>';
?>
</td>
<td>
<div class="btn-group">
<a href="view_ticket.php?id=<?php echo $ticket['id']; ?>" class="btn btn-sm btn-outline-primary">
<i class="fas fa-eye"></i>
</a>
<button type="button" class="btn btn-sm btn-outline-secondary dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
<i class="fas fa-ellipsis-v"></i>
</button>
<ul class="dropdown-menu dropdown-menu-end">
<li>
<a class="dropdown-item" href="view_ticket.php?id=<?php echo $ticket['id']; ?>">
<i class="fas fa-eye me-2"></i> View Details
</a>
</li>
<?php if ($ticket['status'] !== 'in_progress'): ?>
<li>
<a class="dropdown-item status-update-link" href="#"
data-ticket-id="<?php echo $ticket['id']; ?>"
data-status="in_progress">
<i class="fas fa-sync-alt me-2"></i> Mark as In Progress
</a>
</li>
<?php endif; ?>
<?php if ($ticket['status'] !== 'resolved'): ?>
<li>
<a class="dropdown-item status-update-link" href="#"
data-ticket-id="<?php echo $ticket['id']; ?>"
data-status="resolved">
<i class="fas fa-check me-2"></i> Mark as Resolved
</a>
</li>
<?php endif; ?>
<?php if ($ticket['status'] !== 'closed'): ?>
<li>
<a class="dropdown-item status-update-link" href="#"
data-ticket-id="<?php echo $ticket['id']; ?>"
data-status="closed">
<i class="fas fa-times-circle me-2"></i> Close Ticket
</a>
</li>
<?php endif; ?>
</ul>
</div>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php else: ?>
<div class="alert alert-info mb-0">
<i class="fas fa-info-circle me-2"></i> No tickets found matching your criteria.
<?php if (!empty($status) || !empty($priority) || !empty($category) || !empty($search)): ?>
<a href="support_tickets.php">Clear the filters</a> to see all tickets.
<?php endif; ?>
</div>
<?php endif; ?>
</div>
</div>
</form>
<!-- Hidden form for individual status updates -->
<form id="status-update-form" method="post" style="display: none;">
<input type="hidden" name="ticket_ids[]" id="status-ticket-id">
<input type="hidden" name="status" id="status-value">
<input type="hidden" name="update_status" value="1">
</form>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
// Filter form enhancements
const filterSelects = document.querySelectorAll('#filter-form select');
filterSelects.forEach(select => {
select.addEventListener('change', function() {
if (document.getElementById('search').value.trim() === '') {
document.getElementById('filter-form').submit();
}
});
});
// Select all functionality
const selectAllCheckbox = document.getElementById('select-all');
const ticketCheckboxes = document.querySelectorAll('.ticket-checkbox');
const bulkUpdateBtn = document.getElementById('bulk-update-btn');
selectAllCheckbox.addEventListener('change', function() {
ticketCheckboxes.forEach(checkbox => {
checkbox.checked = selectAllCheckbox.checked;
});
updateBulkButtonState();
});
ticketCheckboxes.forEach(checkbox => {
checkbox.addEventListener('change', updateBulkButtonState);
});
function updateBulkButtonState() {
const checkedCount = document.querySelectorAll('.ticket-checkbox:checked').length;
bulkUpdateBtn.disabled = checkedCount === 0 || document.getElementById('bulk-status').value === '';
}
// Bulk status dropdown
document.getElementById('bulk-status').addEventListener('change', function() {
updateBulkButtonState();
});
// Validate form before submit
document.getElementById('tickets-form').addEventListener('submit', function(e) {
const checkedCount = document.querySelectorAll('.ticket-checkbox:checked').length;
const statusValue = document.getElementById('bulk-status').value;
if (checkedCount === 0 || statusValue === '') {
e.preventDefault();
alert('Please select at least one ticket and a status to update.');
}
});
// Individual status update links
const statusUpdateLinks = document.querySelectorAll('.status-update-link');
statusUpdateLinks.forEach(link => {
link.addEventListener('click', function(e) {
e.preventDefault();
const ticketId = this.getAttribute('data-ticket-id');
const status = this.getAttribute('data-status');
document.getElementById('status-ticket-id').value = ticketId;
document.getElementById('status-value').value = status;
document.getElementById('status-update-form').submit();
});
});
});
</script>
<?php include_once('includes/footer.php'); ?>