Files
tinker_tickets/views/admin/RecurringTicketsView.php
Jared Vititoe 0046721fde feat: Add admin navigation, fix modals, clickable stats, update docs
- Add admin dropdown menu in dashboard header with links to all admin pages
- Fix template modal: larger size (800px), responsive grid, type/priority dropdowns
- Fix recurring tickets modal: add Type and Assign To fields, larger size
- Make dashboard stat cards clickable for quick filtering
- Fix user-activity query (remove is_active requirement)
- Add table existence check in ticket_dependencies API
- Fix table overflow on dashboard
- Update Claude.md and README.md with current project status
- Remove migrations directory (all migrations completed)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-20 21:11:49 -05:00

324 lines
16 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?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(auto-fit, minmax(180px, 1fr)); gap: 1rem;">
<div class="setting-row">
<label for="category">Category</label>
<select id="category" name="category" style="width: 100%;">
<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">
<label for="type">Type</label>
<select id="type" name="type" style="width: 100%;">
<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">
<label for="priority">Priority</label>
<select id="priority" name="priority" style="width: 100%;">
<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">
<label for="assigned_to">Assign To</label>
<select id="assigned_to" name="assigned_to" style="width: 100%;">
<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';
}
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>