Feature 4: Implement Ticket Templates
Add ticket template system for quick ticket creation: - Created TemplateModel.php with full CRUD operations for templates - Added get_template.php API endpoint to fetch template data - Updated TicketController to load templates in create() method - Modified CreateTicketView.php to include template selector dropdown - Added loadTemplate() JavaScript function to populate form fields - Templates include: title, description, category, type, and default priority - Database already seeded with default templates (Hardware Failure, Software Installation, Network Issue, Maintenance Request) Users can now select from predefined templates when creating tickets, speeding up common ticket creation workflows. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
45
api/get_template.php
Normal file
45
api/get_template.php
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
<?php
|
||||||
|
session_start();
|
||||||
|
require_once dirname(__DIR__) . '/config/db.php';
|
||||||
|
require_once dirname(__DIR__) . '/models/TemplateModel.php';
|
||||||
|
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
// Check authentication
|
||||||
|
if (!isset($_SESSION['user']) || !isset($_SESSION['user']['user_id'])) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Not authenticated']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get template ID from query parameter
|
||||||
|
$templateId = $_GET['template_id'] ?? null;
|
||||||
|
|
||||||
|
if (!$templateId) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Template ID required']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create database connection
|
||||||
|
$conn = new mysqli(
|
||||||
|
$GLOBALS['config']['DB_HOST'],
|
||||||
|
$GLOBALS['config']['DB_USER'],
|
||||||
|
$GLOBALS['config']['DB_PASS'],
|
||||||
|
$GLOBALS['config']['DB_NAME']
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($conn->connect_error) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Database connection failed']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get template
|
||||||
|
$templateModel = new TemplateModel($conn);
|
||||||
|
$template = $templateModel->getTemplateById($templateId);
|
||||||
|
|
||||||
|
$conn->close();
|
||||||
|
|
||||||
|
if ($template) {
|
||||||
|
echo json_encode(['success' => true, 'template' => $template]);
|
||||||
|
} else {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Template not found']);
|
||||||
|
}
|
||||||
@@ -852,3 +852,64 @@ function saveTicket() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Load template data into the create ticket form
|
||||||
|
*/
|
||||||
|
function loadTemplate() {
|
||||||
|
const templateSelect = document.getElementById('templateSelect');
|
||||||
|
const templateId = templateSelect.value;
|
||||||
|
|
||||||
|
if (!templateId) {
|
||||||
|
// Clear form when "No Template" is selected
|
||||||
|
document.getElementById('title').value = '';
|
||||||
|
document.getElementById('description').value = '';
|
||||||
|
document.getElementById('priority').value = '4';
|
||||||
|
document.getElementById('category').value = 'General';
|
||||||
|
document.getElementById('type').value = 'Issue';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch template data
|
||||||
|
fetch(`/api/get_template.php?template_id=${templateId}`)
|
||||||
|
.then(response => {
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Failed to fetch template');
|
||||||
|
}
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then(data => {
|
||||||
|
if (data.success && data.template) {
|
||||||
|
const template = data.template;
|
||||||
|
|
||||||
|
// Populate form fields with template data
|
||||||
|
if (template.title_template) {
|
||||||
|
document.getElementById('title').value = template.title_template;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (template.description_template) {
|
||||||
|
document.getElementById('description').value = template.description_template;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (template.category) {
|
||||||
|
document.getElementById('category').value = template.category;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (template.type) {
|
||||||
|
document.getElementById('type').value = template.type;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (template.default_priority) {
|
||||||
|
document.getElementById('priority').value = template.default_priority;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Template loaded:', template.template_name);
|
||||||
|
} else {
|
||||||
|
console.error('Failed to load template:', data.error);
|
||||||
|
alert('Failed to load template: ' + (data.error || 'Unknown error'));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error loading template:', error);
|
||||||
|
alert('Error loading template: ' + error.message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ require_once dirname(__DIR__) . '/models/CommentModel.php';
|
|||||||
require_once dirname(__DIR__) . '/models/AuditLogModel.php';
|
require_once dirname(__DIR__) . '/models/AuditLogModel.php';
|
||||||
require_once dirname(__DIR__) . '/models/UserModel.php';
|
require_once dirname(__DIR__) . '/models/UserModel.php';
|
||||||
require_once dirname(__DIR__) . '/models/WorkflowModel.php';
|
require_once dirname(__DIR__) . '/models/WorkflowModel.php';
|
||||||
|
require_once dirname(__DIR__) . '/models/TemplateModel.php';
|
||||||
|
|
||||||
class TicketController {
|
class TicketController {
|
||||||
private $ticketModel;
|
private $ticketModel;
|
||||||
@@ -12,6 +13,7 @@ class TicketController {
|
|||||||
private $auditLogModel;
|
private $auditLogModel;
|
||||||
private $userModel;
|
private $userModel;
|
||||||
private $workflowModel;
|
private $workflowModel;
|
||||||
|
private $templateModel;
|
||||||
private $envVars;
|
private $envVars;
|
||||||
|
|
||||||
public function __construct($conn) {
|
public function __construct($conn) {
|
||||||
@@ -20,6 +22,7 @@ class TicketController {
|
|||||||
$this->auditLogModel = new AuditLogModel($conn);
|
$this->auditLogModel = new AuditLogModel($conn);
|
||||||
$this->userModel = new UserModel($conn);
|
$this->userModel = new UserModel($conn);
|
||||||
$this->workflowModel = new WorkflowModel($conn);
|
$this->workflowModel = new WorkflowModel($conn);
|
||||||
|
$this->templateModel = new TemplateModel($conn);
|
||||||
|
|
||||||
// Load environment variables for Discord webhook
|
// Load environment variables for Discord webhook
|
||||||
$envPath = dirname(__DIR__) . '/.env';
|
$envPath = dirname(__DIR__) . '/.env';
|
||||||
@@ -95,6 +98,7 @@ class TicketController {
|
|||||||
// Validate input
|
// Validate input
|
||||||
if (empty($ticketData['title'])) {
|
if (empty($ticketData['title'])) {
|
||||||
$error = "Title is required";
|
$error = "Title is required";
|
||||||
|
$templates = $this->templateModel->getAllTemplates();
|
||||||
include dirname(__DIR__) . '/views/CreateTicketView.php';
|
include dirname(__DIR__) . '/views/CreateTicketView.php';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -116,10 +120,14 @@ class TicketController {
|
|||||||
exit;
|
exit;
|
||||||
} else {
|
} else {
|
||||||
$error = $result['error'];
|
$error = $result['error'];
|
||||||
|
$templates = $this->templateModel->getAllTemplates();
|
||||||
include dirname(__DIR__) . '/views/CreateTicketView.php';
|
include dirname(__DIR__) . '/views/CreateTicketView.php';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// Get all templates for the template selector
|
||||||
|
$templates = $this->templateModel->getAllTemplates();
|
||||||
|
|
||||||
// Display the create ticket form
|
// Display the create ticket form
|
||||||
include dirname(__DIR__) . '/views/CreateTicketView.php';
|
include dirname(__DIR__) . '/views/CreateTicketView.php';
|
||||||
}
|
}
|
||||||
|
|||||||
120
models/TemplateModel.php
Normal file
120
models/TemplateModel.php
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* TemplateModel - Handles ticket template operations
|
||||||
|
*/
|
||||||
|
class TemplateModel {
|
||||||
|
private $conn;
|
||||||
|
|
||||||
|
public function __construct($conn) {
|
||||||
|
$this->conn = $conn;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all active templates
|
||||||
|
*
|
||||||
|
* @return array Array of template records
|
||||||
|
*/
|
||||||
|
public function getAllTemplates() {
|
||||||
|
$sql = "SELECT * FROM ticket_templates WHERE is_active = TRUE ORDER BY template_name";
|
||||||
|
$result = $this->conn->query($sql);
|
||||||
|
|
||||||
|
$templates = [];
|
||||||
|
while ($row = $result->fetch_assoc()) {
|
||||||
|
$templates[] = $row;
|
||||||
|
}
|
||||||
|
return $templates;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get template by ID
|
||||||
|
*
|
||||||
|
* @param int $templateId Template ID
|
||||||
|
* @return array|null Template record or null if not found
|
||||||
|
*/
|
||||||
|
public function getTemplateById($templateId) {
|
||||||
|
$sql = "SELECT * FROM ticket_templates WHERE template_id = ? AND is_active = TRUE";
|
||||||
|
$stmt = $this->conn->prepare($sql);
|
||||||
|
$stmt->bind_param("i", $templateId);
|
||||||
|
$stmt->execute();
|
||||||
|
$result = $stmt->get_result();
|
||||||
|
|
||||||
|
$template = $result->fetch_assoc();
|
||||||
|
$stmt->close();
|
||||||
|
return $template;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new template
|
||||||
|
*
|
||||||
|
* @param array $data Template data
|
||||||
|
* @param int $createdBy User ID creating the template
|
||||||
|
* @return bool Success status
|
||||||
|
*/
|
||||||
|
public function createTemplate($data, $createdBy) {
|
||||||
|
$sql = "INSERT INTO ticket_templates (template_name, title_template, description_template,
|
||||||
|
category, type, default_priority, created_by)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?, ?)";
|
||||||
|
$stmt = $this->conn->prepare($sql);
|
||||||
|
$stmt->bind_param("sssssii",
|
||||||
|
$data['template_name'],
|
||||||
|
$data['title_template'],
|
||||||
|
$data['description_template'],
|
||||||
|
$data['category'],
|
||||||
|
$data['type'],
|
||||||
|
$data['default_priority'],
|
||||||
|
$createdBy
|
||||||
|
);
|
||||||
|
|
||||||
|
$result = $stmt->execute();
|
||||||
|
$stmt->close();
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update an existing template
|
||||||
|
*
|
||||||
|
* @param int $templateId Template ID
|
||||||
|
* @param array $data Template data to update
|
||||||
|
* @return bool Success status
|
||||||
|
*/
|
||||||
|
public function updateTemplate($templateId, $data) {
|
||||||
|
$sql = "UPDATE ticket_templates SET
|
||||||
|
template_name = ?,
|
||||||
|
title_template = ?,
|
||||||
|
description_template = ?,
|
||||||
|
category = ?,
|
||||||
|
type = ?,
|
||||||
|
default_priority = ?
|
||||||
|
WHERE template_id = ?";
|
||||||
|
$stmt = $this->conn->prepare($sql);
|
||||||
|
$stmt->bind_param("ssssiii",
|
||||||
|
$data['template_name'],
|
||||||
|
$data['title_template'],
|
||||||
|
$data['description_template'],
|
||||||
|
$data['category'],
|
||||||
|
$data['type'],
|
||||||
|
$data['default_priority'],
|
||||||
|
$templateId
|
||||||
|
);
|
||||||
|
|
||||||
|
$result = $stmt->execute();
|
||||||
|
$stmt->close();
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deactivate a template (soft delete)
|
||||||
|
*
|
||||||
|
* @param int $templateId Template ID
|
||||||
|
* @return bool Success status
|
||||||
|
*/
|
||||||
|
public function deactivateTemplate($templateId) {
|
||||||
|
$sql = "UPDATE ticket_templates SET is_active = FALSE WHERE template_id = ?";
|
||||||
|
$stmt = $this->conn->prepare($sql);
|
||||||
|
$stmt->bind_param("i", $templateId);
|
||||||
|
|
||||||
|
$result = $stmt->execute();
|
||||||
|
$stmt->close();
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -24,6 +24,20 @@
|
|||||||
|
|
||||||
<form method="POST" action="<?php echo $GLOBALS['config']['BASE_URL']; ?>/ticket/create" class="ticket-form">
|
<form method="POST" action="<?php echo $GLOBALS['config']['BASE_URL']; ?>/ticket/create" class="ticket-form">
|
||||||
<div class="ticket-details">
|
<div class="ticket-details">
|
||||||
|
<div class="detail-group">
|
||||||
|
<label for="template">Use Template (Optional)</label>
|
||||||
|
<select id="templateSelect" class="editable" onchange="loadTemplate()">
|
||||||
|
<option value="">-- No Template --</option>
|
||||||
|
<?php if (isset($templates) && !empty($templates)): ?>
|
||||||
|
<?php foreach ($templates as $template): ?>
|
||||||
|
<option value="<?php echo $template['template_id']; ?>">
|
||||||
|
<?php echo htmlspecialchars($template['template_name']); ?>
|
||||||
|
</option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
<?php endif; ?>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="detail-group">
|
<div class="detail-group">
|
||||||
<label for="title">Title</label>
|
<label for="title">Title</label>
|
||||||
<input type="text" id="title" name="title" class="editable" required>
|
<input type="text" id="title" name="title" class="editable" required>
|
||||||
|
|||||||
Reference in New Issue
Block a user