Add Ticket Assignment feature (Feature 2)
- Add assigned_to column support in TicketModel with assignTicket() and unassignTicket() methods - Create assign_ticket.php API endpoint for assignment operations - Update TicketController to load user list from UserModel - Add assignment dropdown UI in TicketView - Add JavaScript handler for assignment changes - Integrate with audit log for assignment tracking Users can now assign tickets to team members via dropdown selector. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
42
api/assign_ticket.php
Normal file
42
api/assign_ticket.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
session_start();
|
||||
require_once '../config/db.php';
|
||||
require_once '../models/TicketModel.php';
|
||||
require_once '../models/AuditLogModel.php';
|
||||
|
||||
header('Content-Type: application/json');
|
||||
|
||||
// Check authentication
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
echo json_encode(['success' => false, 'error' => 'Not authenticated']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Get request data
|
||||
$data = json_decode(file_get_contents('php://input'), true);
|
||||
$ticketId = $data['ticket_id'] ?? null;
|
||||
$assignedTo = $data['assigned_to'] ?? null;
|
||||
|
||||
if (!$ticketId) {
|
||||
echo json_encode(['success' => false, 'error' => 'Ticket ID required']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$ticketModel = new TicketModel($conn);
|
||||
$auditLogModel = new AuditLogModel($conn);
|
||||
|
||||
if ($assignedTo === null || $assignedTo === '') {
|
||||
// Unassign ticket
|
||||
$success = $ticketModel->unassignTicket($ticketId, $_SESSION['user_id']);
|
||||
if ($success) {
|
||||
$auditLogModel->log($_SESSION['user_id'], 'unassign', 'ticket', $ticketId);
|
||||
}
|
||||
} else {
|
||||
// Assign ticket
|
||||
$success = $ticketModel->assignTicket($ticketId, $assignedTo, $_SESSION['user_id']);
|
||||
if ($success) {
|
||||
$auditLogModel->log($_SESSION['user_id'], 'assign', 'ticket', $ticketId, ['assigned_to' => $assignedTo]);
|
||||
}
|
||||
}
|
||||
|
||||
echo json_encode(['success' => $success]);
|
||||
@@ -213,7 +213,7 @@ function toggleMarkdownMode() {
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Show description tab by default
|
||||
showTab('description');
|
||||
|
||||
|
||||
// Auto-resize the description textarea to fit content
|
||||
const descriptionTextarea = document.querySelector('textarea[data-field="description"]');
|
||||
if (descriptionTextarea) {
|
||||
@@ -223,15 +223,50 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
// Set the height to match the scrollHeight
|
||||
descriptionTextarea.style.height = descriptionTextarea.scrollHeight + 'px';
|
||||
}
|
||||
|
||||
|
||||
// Initial resize
|
||||
autoResizeTextarea();
|
||||
|
||||
|
||||
// Resize on input when in edit mode
|
||||
descriptionTextarea.addEventListener('input', autoResizeTextarea);
|
||||
}
|
||||
|
||||
// Initialize assignment handling
|
||||
handleAssignmentChange();
|
||||
});
|
||||
|
||||
/**
|
||||
* Handle ticket assignment dropdown changes
|
||||
*/
|
||||
function handleAssignmentChange() {
|
||||
const assignedToSelect = document.getElementById('assignedToSelect');
|
||||
if (!assignedToSelect) return;
|
||||
|
||||
assignedToSelect.addEventListener('change', function() {
|
||||
const ticketId = window.ticketData.id;
|
||||
const assignedTo = this.value || null;
|
||||
|
||||
fetch('/api/assign_ticket.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ ticket_id: ticketId, assigned_to: assignedTo })
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (!data.success) {
|
||||
alert('Error updating assignment');
|
||||
console.error(data.error);
|
||||
} else {
|
||||
console.log('Assignment updated successfully');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error updating assignment:', error);
|
||||
alert('Error updating assignment: ' + error.message);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function showTab(tabName) {
|
||||
// Hide all tab contents
|
||||
const descriptionTab = document.getElementById('description-tab');
|
||||
|
||||
@@ -3,17 +3,20 @@
|
||||
require_once dirname(__DIR__) . '/models/TicketModel.php';
|
||||
require_once dirname(__DIR__) . '/models/CommentModel.php';
|
||||
require_once dirname(__DIR__) . '/models/AuditLogModel.php';
|
||||
require_once dirname(__DIR__) . '/models/UserModel.php';
|
||||
|
||||
class TicketController {
|
||||
private $ticketModel;
|
||||
private $commentModel;
|
||||
private $auditLogModel;
|
||||
private $userModel;
|
||||
private $envVars;
|
||||
|
||||
public function __construct($conn) {
|
||||
$this->ticketModel = new TicketModel($conn);
|
||||
$this->commentModel = new CommentModel($conn);
|
||||
$this->auditLogModel = new AuditLogModel($conn);
|
||||
$this->userModel = new UserModel($conn);
|
||||
|
||||
// Load environment variables for Discord webhook
|
||||
$envPath = dirname(__DIR__) . '/.env';
|
||||
@@ -61,6 +64,9 @@ class TicketController {
|
||||
// Get timeline for this ticket
|
||||
$timeline = $this->auditLogModel->getTicketTimeline($id);
|
||||
|
||||
// Get all users for assignment dropdown
|
||||
$allUsers = $this->userModel->getAllUsers();
|
||||
|
||||
// Load the view
|
||||
include dirname(__DIR__) . '/views/TicketView.php';
|
||||
}
|
||||
|
||||
@@ -11,10 +11,13 @@ class TicketModel {
|
||||
u_created.username as creator_username,
|
||||
u_created.display_name as creator_display_name,
|
||||
u_updated.username as updater_username,
|
||||
u_updated.display_name as updater_display_name
|
||||
u_updated.display_name as updater_display_name,
|
||||
u_assigned.username as assigned_username,
|
||||
u_assigned.display_name as assigned_display_name
|
||||
FROM tickets t
|
||||
LEFT JOIN users u_created ON t.created_by = u_created.user_id
|
||||
LEFT JOIN users u_updated ON t.updated_by = u_updated.user_id
|
||||
LEFT JOIN users u_assigned ON t.assigned_to = u_assigned.user_id
|
||||
WHERE t.ticket_id = ?";
|
||||
$stmt = $this->conn->prepare($sql);
|
||||
$stmt->bind_param("i", $id);
|
||||
@@ -283,4 +286,37 @@ class TicketModel {
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Assign ticket to a user
|
||||
*
|
||||
* @param int $ticketId Ticket ID
|
||||
* @param int $userId User ID to assign to
|
||||
* @param int $assignedBy User ID performing the assignment
|
||||
* @return bool Success status
|
||||
*/
|
||||
public function assignTicket($ticketId, $userId, $assignedBy) {
|
||||
$sql = "UPDATE tickets SET assigned_to = ?, updated_by = ?, updated_at = NOW() WHERE ticket_id = ?";
|
||||
$stmt = $this->conn->prepare($sql);
|
||||
$stmt->bind_param("iii", $userId, $assignedBy, $ticketId);
|
||||
$result = $stmt->execute();
|
||||
$stmt->close();
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unassign ticket (set assigned_to to NULL)
|
||||
*
|
||||
* @param int $ticketId Ticket ID
|
||||
* @param int $updatedBy User ID performing the unassignment
|
||||
* @return bool Success status
|
||||
*/
|
||||
public function unassignTicket($ticketId, $updatedBy) {
|
||||
$sql = "UPDATE tickets SET assigned_to = NULL, updated_by = ?, updated_at = NOW() WHERE ticket_id = ?";
|
||||
$stmt = $this->conn->prepare($sql);
|
||||
$stmt->bind_param("ii", $updatedBy, $ticketId);
|
||||
$result = $stmt->execute();
|
||||
$stmt->close();
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
@@ -167,6 +167,18 @@ function formatDetails($details, $actionType) {
|
||||
}
|
||||
?>
|
||||
</div>
|
||||
<div class="ticket-assignment" style="margin-top: 0.5rem;">
|
||||
<label style="font-weight: 500; margin-right: 0.5rem;">Assigned to:</label>
|
||||
<select id="assignedToSelect" class="assignment-select" style="padding: 0.25rem 0.5rem; border-radius: 4px; border: 1px solid var(--border-color, #ddd);">
|
||||
<option value="">Unassigned</option>
|
||||
<?php foreach ($allUsers as $user): ?>
|
||||
<option value="<?php echo $user['user_id']; ?>"
|
||||
<?php echo ($ticket['assigned_to'] == $user['user_id']) ? 'selected' : ''; ?>>
|
||||
<?php echo htmlspecialchars($user['display_name'] ?? $user['username']); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="header-controls">
|
||||
<div class="status-priority-group">
|
||||
|
||||
Reference in New Issue
Block a user