338 lines
17 KiB
PHP
338 lines
17 KiB
PHP
<?php
|
||
// Admin view for managing recurring tickets
|
||
// Receives $recurringTickets from controller
|
||
?>
|
||
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Recurring Tickets - Admin</title>
|
||
<link rel="icon" type="image/png" href="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/images/favicon.png">
|
||
<link rel="stylesheet" href="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/css/dashboard.css">
|
||
<link rel="stylesheet" href="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/css/ticket.css">
|
||
<script>
|
||
window.CSRF_TOKEN = '<?php
|
||
require_once __DIR__ . '/../../middleware/CsrfMiddleware.php';
|
||
echo CsrfMiddleware::getToken();
|
||
?>';
|
||
</script>
|
||
</head>
|
||
<body>
|
||
<div class="user-header">
|
||
<div class="user-header-left">
|
||
<a href="/" class="back-link">← Dashboard</a>
|
||
<span style="margin-left: 1rem; color: var(--terminal-amber);">Admin: Recurring Tickets</span>
|
||
</div>
|
||
<div class="user-header-right">
|
||
<?php if (isset($GLOBALS['currentUser'])): ?>
|
||
<span class="user-name"><?php echo htmlspecialchars($GLOBALS['currentUser']['display_name'] ?? $GLOBALS['currentUser']['username']); ?></span>
|
||
<span class="admin-badge">Admin</span>
|
||
<?php endif; ?>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="ascii-frame-outer" style="max-width: 1200px; margin: 2rem auto;">
|
||
<span class="bottom-left-corner">╚</span>
|
||
<span class="bottom-right-corner">╝</span>
|
||
|
||
<div class="ascii-section-header">Recurring Tickets Management</div>
|
||
<div class="ascii-content">
|
||
<div class="ascii-frame-inner">
|
||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem;">
|
||
<h2 style="margin: 0;">Scheduled Tickets</h2>
|
||
<button onclick="showCreateModal()" class="btn">+ New Recurring Ticket</button>
|
||
</div>
|
||
|
||
<table style="width: 100%;">
|
||
<thead>
|
||
<tr>
|
||
<th>ID</th>
|
||
<th>Title Template</th>
|
||
<th>Schedule</th>
|
||
<th>Category</th>
|
||
<th>Assigned To</th>
|
||
<th>Next Run</th>
|
||
<th>Status</th>
|
||
<th>Actions</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<?php if (empty($recurringTickets)): ?>
|
||
<tr>
|
||
<td colspan="8" style="text-align: center; padding: 2rem; color: var(--terminal-green-dim);">
|
||
No recurring tickets configured.
|
||
</td>
|
||
</tr>
|
||
<?php else: ?>
|
||
<?php foreach ($recurringTickets as $rt): ?>
|
||
<tr>
|
||
<td><?php echo $rt['recurring_id']; ?></td>
|
||
<td><?php echo htmlspecialchars($rt['title_template']); ?></td>
|
||
<td>
|
||
<?php
|
||
$schedule = ucfirst($rt['schedule_type']);
|
||
if ($rt['schedule_type'] === 'weekly') {
|
||
$days = ['', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
|
||
$schedule .= ' (' . ($days[$rt['schedule_day']] ?? '?') . ')';
|
||
} elseif ($rt['schedule_type'] === 'monthly') {
|
||
$schedule .= ' (Day ' . $rt['schedule_day'] . ')';
|
||
}
|
||
$schedule .= ' @ ' . substr($rt['schedule_time'], 0, 5);
|
||
echo $schedule;
|
||
?>
|
||
</td>
|
||
<td><?php echo htmlspecialchars($rt['category']); ?></td>
|
||
<td><?php echo htmlspecialchars($rt['assigned_name'] ?? $rt['assigned_username'] ?? 'Unassigned'); ?></td>
|
||
<td><?php echo date('M d, Y H:i', strtotime($rt['next_run_at'])); ?></td>
|
||
<td>
|
||
<span style="color: <?php echo $rt['is_active'] ? 'var(--status-open)' : 'var(--status-closed)'; ?>;">
|
||
<?php echo $rt['is_active'] ? 'Active' : 'Inactive'; ?>
|
||
</span>
|
||
</td>
|
||
<td>
|
||
<button onclick="editRecurring(<?php echo $rt['recurring_id']; ?>)" class="btn btn-small">Edit</button>
|
||
<button onclick="toggleRecurring(<?php echo $rt['recurring_id']; ?>)" class="btn btn-small">
|
||
<?php echo $rt['is_active'] ? 'Disable' : 'Enable'; ?>
|
||
</button>
|
||
<button onclick="deleteRecurring(<?php echo $rt['recurring_id']; ?>)" class="btn btn-small btn-danger">Delete</button>
|
||
</td>
|
||
</tr>
|
||
<?php endforeach; ?>
|
||
<?php endif; ?>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Create/Edit Modal -->
|
||
<div class="settings-modal" id="recurringModal" style="display: none;">
|
||
<div class="settings-content" style="max-width: 800px; width: 90%;">
|
||
<div class="settings-header">
|
||
<h3 id="modalTitle">Create Recurring Ticket</h3>
|
||
<button class="close-settings" onclick="closeModal()">×</button>
|
||
</div>
|
||
<form id="recurringForm" onsubmit="saveRecurring(event)">
|
||
<input type="hidden" id="recurring_id" name="recurring_id">
|
||
<div class="settings-body">
|
||
<div class="setting-row">
|
||
<label for="title_template">Title Template *</label>
|
||
<input type="text" id="title_template" name="title_template" required style="width: 100%;" placeholder="Use {{date}}, {{month}}, etc.">
|
||
</div>
|
||
<div class="setting-row">
|
||
<label for="description_template">Description Template</label>
|
||
<textarea id="description_template" name="description_template" rows="8" style="width: 100%; min-height: 150px;"></textarea>
|
||
</div>
|
||
<div class="setting-row">
|
||
<label for="schedule_type">Schedule Type *</label>
|
||
<select id="schedule_type" name="schedule_type" required onchange="updateScheduleOptions()">
|
||
<option value="daily">Daily</option>
|
||
<option value="weekly">Weekly</option>
|
||
<option value="monthly">Monthly</option>
|
||
</select>
|
||
</div>
|
||
<div class="setting-row" id="schedule_day_row" style="display: none;">
|
||
<label for="schedule_day">Schedule Day</label>
|
||
<select id="schedule_day" name="schedule_day"></select>
|
||
</div>
|
||
<div class="setting-row">
|
||
<label for="schedule_time">Schedule Time *</label>
|
||
<input type="time" id="schedule_time" name="schedule_time" value="09:00" required>
|
||
</div>
|
||
<div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 1rem;">
|
||
<div class="setting-row setting-row-compact">
|
||
<label for="category">Category</label>
|
||
<select id="category" name="category">
|
||
<option value="General">General</option>
|
||
<option value="Hardware">Hardware</option>
|
||
<option value="Software">Software</option>
|
||
<option value="Network">Network</option>
|
||
<option value="Security">Security</option>
|
||
</select>
|
||
</div>
|
||
<div class="setting-row setting-row-compact">
|
||
<label for="type">Type</label>
|
||
<select id="type" name="type">
|
||
<option value="Issue">Issue</option>
|
||
<option value="Maintenance">Maintenance</option>
|
||
<option value="Install">Install</option>
|
||
<option value="Task">Task</option>
|
||
<option value="Upgrade">Upgrade</option>
|
||
<option value="Problem">Problem</option>
|
||
</select>
|
||
</div>
|
||
<div class="setting-row setting-row-compact">
|
||
<label for="priority">Priority</label>
|
||
<select id="priority" name="priority">
|
||
<option value="1">P1 - Critical</option>
|
||
<option value="2">P2 - High</option>
|
||
<option value="3">P3 - Medium</option>
|
||
<option value="4" selected>P4 - Low</option>
|
||
<option value="5">P5 - Lowest</option>
|
||
</select>
|
||
</div>
|
||
<div class="setting-row setting-row-compact">
|
||
<label for="assigned_to">Assign To</label>
|
||
<select id="assigned_to" name="assigned_to">
|
||
<option value="">Unassigned</option>
|
||
<!-- Populated by JavaScript -->
|
||
</select>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="settings-footer">
|
||
<button type="submit" class="btn btn-primary">Save</button>
|
||
<button type="button" class="btn btn-secondary" onclick="closeModal()">Cancel</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
|
||
<script src="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/js/toast.js"></script>
|
||
<script>
|
||
function showCreateModal() {
|
||
document.getElementById('modalTitle').textContent = 'Create Recurring Ticket';
|
||
document.getElementById('recurringForm').reset();
|
||
document.getElementById('recurring_id').value = '';
|
||
updateScheduleOptions();
|
||
document.getElementById('recurringModal').style.display = 'flex';
|
||
}
|
||
|
||
function closeModal() {
|
||
document.getElementById('recurringModal').style.display = 'none';
|
||
}
|
||
|
||
// Close modal on ESC key
|
||
document.addEventListener('keydown', (e) => {
|
||
if (e.key === 'Escape') {
|
||
closeModal();
|
||
}
|
||
});
|
||
|
||
// Close modal when clicking on backdrop (outside content)
|
||
document.getElementById('recurringModal').addEventListener('click', (e) => {
|
||
if (e.target.classList.contains('settings-modal')) {
|
||
closeModal();
|
||
}
|
||
});
|
||
|
||
function updateScheduleOptions() {
|
||
const type = document.getElementById('schedule_type').value;
|
||
const dayRow = document.getElementById('schedule_day_row');
|
||
const daySelect = document.getElementById('schedule_day');
|
||
|
||
daySelect.innerHTML = '';
|
||
|
||
if (type === 'daily') {
|
||
dayRow.style.display = 'none';
|
||
} else if (type === 'weekly') {
|
||
dayRow.style.display = 'flex';
|
||
const days = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'];
|
||
days.forEach((day, i) => {
|
||
daySelect.innerHTML += `<option value="${i + 1}">${day}</option>`;
|
||
});
|
||
} else if (type === 'monthly') {
|
||
dayRow.style.display = 'flex';
|
||
for (let i = 1; i <= 28; i++) {
|
||
daySelect.innerHTML += `<option value="${i}">Day ${i}</option>`;
|
||
}
|
||
}
|
||
}
|
||
|
||
function saveRecurring(e) {
|
||
e.preventDefault();
|
||
const form = new FormData(document.getElementById('recurringForm'));
|
||
const data = Object.fromEntries(form);
|
||
|
||
const method = data.recurring_id ? 'PUT' : 'POST';
|
||
const url = '/api/manage_recurring.php' + (data.recurring_id ? '?id=' + data.recurring_id : '');
|
||
|
||
fetch(url, {
|
||
method: method,
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRF-Token': window.CSRF_TOKEN
|
||
},
|
||
body: JSON.stringify(data)
|
||
})
|
||
.then(r => r.json())
|
||
.then(data => {
|
||
if (data.success) {
|
||
window.location.reload();
|
||
} else {
|
||
toast.error(data.error || 'Failed to save');
|
||
}
|
||
});
|
||
}
|
||
|
||
function toggleRecurring(id) {
|
||
fetch('/api/manage_recurring.php?action=toggle&id=' + id, {
|
||
method: 'POST',
|
||
headers: { 'X-CSRF-Token': window.CSRF_TOKEN }
|
||
})
|
||
.then(r => r.json())
|
||
.then(data => {
|
||
if (data.success) window.location.reload();
|
||
});
|
||
}
|
||
|
||
function deleteRecurring(id) {
|
||
if (!confirm('Delete this recurring ticket schedule?')) return;
|
||
fetch('/api/manage_recurring.php?id=' + id, {
|
||
method: 'DELETE',
|
||
headers: { 'X-CSRF-Token': window.CSRF_TOKEN }
|
||
})
|
||
.then(r => r.json())
|
||
.then(data => {
|
||
if (data.success) window.location.reload();
|
||
});
|
||
}
|
||
|
||
function editRecurring(id) {
|
||
fetch('/api/manage_recurring.php?id=' + id)
|
||
.then(r => r.json())
|
||
.then(data => {
|
||
if (data.success && data.recurring) {
|
||
const rt = data.recurring;
|
||
document.getElementById('recurring_id').value = rt.recurring_id;
|
||
document.getElementById('title_template').value = rt.title_template;
|
||
document.getElementById('description_template').value = rt.description_template || '';
|
||
document.getElementById('schedule_type').value = rt.schedule_type;
|
||
updateScheduleOptions();
|
||
document.getElementById('schedule_day').value = rt.schedule_day || '';
|
||
document.getElementById('schedule_time').value = rt.schedule_time ? rt.schedule_time.substring(0, 5) : '09:00';
|
||
document.getElementById('category').value = rt.category || 'General';
|
||
document.getElementById('type').value = rt.type || 'Issue';
|
||
document.getElementById('priority').value = rt.priority || 4;
|
||
document.getElementById('assigned_to').value = rt.assigned_to || '';
|
||
document.getElementById('modalTitle').textContent = 'Edit Recurring Ticket';
|
||
document.getElementById('recurringModal').style.display = 'flex';
|
||
}
|
||
});
|
||
}
|
||
|
||
// Load users for assignee dropdown
|
||
function loadUsers() {
|
||
fetch('/api/get_users.php')
|
||
.then(r => r.json())
|
||
.then(data => {
|
||
if (data.success && data.users) {
|
||
const select = document.getElementById('assigned_to');
|
||
data.users.forEach(user => {
|
||
const option = document.createElement('option');
|
||
option.value = user.user_id;
|
||
option.textContent = user.display_name || user.username;
|
||
select.appendChild(option);
|
||
});
|
||
}
|
||
});
|
||
}
|
||
|
||
// Initialize
|
||
updateScheduleOptions();
|
||
loadUsers();
|
||
</script>
|
||
</body>
|
||
</html>
|