Feature 3: Implement Status Transitions with Workflow Validation
Add comprehensive workflow management system for ticket status transitions: - Created WorkflowModel.php for managing status transition rules - Updated TicketController.php to load allowed transitions for each ticket - Modified TicketView.php to display dynamic status dropdown with only allowed transitions - Enhanced api/update_ticket.php with server-side workflow validation - Added updateTicketStatus() JavaScript function for client-side status changes - Included CSS styling for status select dropdown with color-coded states - Transitions can require comments or admin privileges - Status changes are validated against status_transitions table This feature enforces proper ticket workflows and prevents invalid status changes. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -515,4 +515,59 @@ body.dark-mode .timeline-content {
|
||||
--border-color: #444;
|
||||
--text-muted: #a0aec0;
|
||||
--text-secondary: #cbd5e0;
|
||||
}
|
||||
}
|
||||
/* Status select dropdown */
|
||||
.status-select {
|
||||
padding: 8px 16px;
|
||||
border-radius: 6px;
|
||||
font-weight: 500;
|
||||
text-transform: uppercase;
|
||||
font-size: 0.9em;
|
||||
letter-spacing: 0.5px;
|
||||
border: 2px solid transparent;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.status-select:hover {
|
||||
opacity: 0.9;
|
||||
border-color: rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
.status-select:focus {
|
||||
outline: none;
|
||||
border-color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
/* Status colors for dropdown */
|
||||
.status-select.status-open {
|
||||
background-color: var(--status-open) !important;
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
.status-select.status-in-progress {
|
||||
background-color: var(--status-in-progress) !important;
|
||||
color: #212529 !important;
|
||||
}
|
||||
|
||||
.status-select.status-closed {
|
||||
background-color: var(--status-closed) !important;
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
.status-select.status-resolved {
|
||||
background-color: #28a745 !important;
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
/* Dropdown options inherit colors */
|
||||
.status-select option {
|
||||
background-color: var(--bg-primary);
|
||||
color: var(--text-primary);
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
body.dark-mode .status-select option {
|
||||
background-color: #2d3748;
|
||||
color: #f7fafc;
|
||||
}
|
||||
|
||||
@@ -267,6 +267,98 @@ function handleAssignmentChange() {
|
||||
});
|
||||
}
|
||||
|
||||
function updateTicketStatus() {
|
||||
const statusSelect = document.getElementById('statusSelect');
|
||||
const selectedOption = statusSelect.options[statusSelect.selectedIndex];
|
||||
const newStatus = selectedOption.value;
|
||||
const requiresComment = selectedOption.dataset.requiresComment === '1';
|
||||
const requiresAdmin = selectedOption.dataset.requiresAdmin === '1';
|
||||
|
||||
// Check if transitioning to the same status (current)
|
||||
if (selectedOption.text.includes('(current)')) {
|
||||
return; // No change needed
|
||||
}
|
||||
|
||||
// Warn if comment is required
|
||||
if (requiresComment) {
|
||||
const proceed = confirm(`This status change requires a comment. Please add a comment explaining the reason for this transition.\n\nProceed with status change to "${newStatus}"?`);
|
||||
if (!proceed) {
|
||||
// Reset to current status
|
||||
statusSelect.selectedIndex = 0;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Extract ticket ID
|
||||
let ticketId;
|
||||
if (window.location.href.includes('?id=')) {
|
||||
ticketId = window.location.href.split('id=')[1];
|
||||
} else {
|
||||
const matches = window.location.pathname.match(/\/ticket\/(\d+)/);
|
||||
ticketId = matches ? matches[1] : null;
|
||||
}
|
||||
|
||||
if (!ticketId) {
|
||||
console.error('Could not determine ticket ID');
|
||||
statusSelect.selectedIndex = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
// Update status via API
|
||||
fetch('/api/update_ticket.php', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
ticket_id: ticketId,
|
||||
status: newStatus
|
||||
})
|
||||
})
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
return response.text().then(text => {
|
||||
console.error('Server response:', text);
|
||||
throw new Error('Network response was not ok');
|
||||
});
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
// Update the dropdown to show new status as current
|
||||
const newClass = 'status-' + newStatus.toLowerCase().replace(/ /g, '-');
|
||||
statusSelect.className = 'editable status-select ' + newClass;
|
||||
|
||||
// Update the selected option text to show as current
|
||||
selectedOption.text = newStatus + ' (current)';
|
||||
|
||||
// Move the selected option to the top
|
||||
statusSelect.remove(statusSelect.selectedIndex);
|
||||
statusSelect.insertBefore(selectedOption, statusSelect.firstChild);
|
||||
statusSelect.selectedIndex = 0;
|
||||
|
||||
console.log('Status updated successfully to:', newStatus);
|
||||
|
||||
// Reload page to refresh activity timeline
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 500);
|
||||
} else {
|
||||
console.error('Error updating status:', data.error || 'Unknown error');
|
||||
alert('Error updating status: ' + (data.error || 'Unknown error'));
|
||||
// Reset to current status
|
||||
statusSelect.selectedIndex = 0;
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error updating status:', error);
|
||||
alert('Error updating status: ' + error.message);
|
||||
// Reset to current status
|
||||
statusSelect.selectedIndex = 0;
|
||||
});
|
||||
}
|
||||
|
||||
function showTab(tabName) {
|
||||
// Hide all tab contents
|
||||
const descriptionTab = document.getElementById('description-tab');
|
||||
|
||||
Reference in New Issue
Block a user