9916daa904
- updateTicketField() now targets .lt-frame-ticket[data-priority] (TDS v1.2)
instead of old .priority-indicator / .ticket-container selectors
- All 7 admin views: keyboard-shortcuts.js now uses dynamic ?v={$_v}
instead of hardcoded unversioned path
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
229 lines
10 KiB
PHP
229 lines
10 KiB
PHP
<?php
|
|
require_once __DIR__ . '/../../middleware/SecurityHeadersMiddleware.php';
|
|
require_once __DIR__ . '/../../middleware/CsrfMiddleware.php';
|
|
$nonce = SecurityHeadersMiddleware::getNonce();
|
|
$pageTitle = 'Workflow Designer';
|
|
$activeNav = 'admin-workflow';
|
|
$_v = $GLOBALS['config']['ASSET_VERSION'] ?? '1';
|
|
$pageStyles = ["/assets/css/dashboard.css?v={$_v}"];
|
|
$pageScripts = ["/assets/js/keyboard-shortcuts.js?v={$_v}"];
|
|
include __DIR__ . '/../../views/layout_header.php';
|
|
?>
|
|
|
|
<div class="lt-page-header">
|
|
<div class="lt-flex lt-flex-gap-sm lt-flex-align-center">
|
|
<a href="/" class="lt-btn lt-btn-ghost lt-btn-sm">← Dashboard</a>
|
|
<span class="lt-text-muted lt-text-xs">/</span>
|
|
<span class="lt-text-muted lt-text-xs">Admin: Workflow</span>
|
|
</div>
|
|
<button type="button" class="lt-btn lt-btn-primary" data-action="show-create-modal">+ NEW TRANSITION</button>
|
|
</div>
|
|
|
|
<div class="lt-frame lt-mb-md">
|
|
<span class="lt-frame-bl">╚</span><span class="lt-frame-br">╝</span>
|
|
<div class="lt-section-header">Workflow Diagram</div>
|
|
<div class="lt-section-body">
|
|
<div class="lt-grid-4">
|
|
<?php
|
|
$statuses = ['Open', 'Pending', 'In Progress', 'Closed'];
|
|
foreach ($statuses as $status):
|
|
$slug = strtolower(str_replace(' ', '-', $status));
|
|
$toCount = 0;
|
|
if (isset($workflows)) { foreach ($workflows as $w) { if ($w['from_status'] === $status) $toCount++; } }
|
|
?>
|
|
<div class="lt-card" style="text-align:center">
|
|
<span class="lt-status lt-status-<?= $slug ?>"><?= $status ?></span>
|
|
<div class="lt-text-xs lt-text-muted lt-mt-sm">→ <?= $toCount ?> transition<?= $toCount !== 1 ? 's' : '' ?></div>
|
|
</div>
|
|
<?php endforeach ?>
|
|
</div>
|
|
<p class="lt-text-xs lt-text-muted" style="margin-top:0.5rem">
|
|
Define which status transitions are allowed. This controls what options appear in the status dropdown on tickets.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="lt-frame">
|
|
<span class="lt-frame-bl">╚</span><span class="lt-frame-br">╝</span>
|
|
<div class="lt-section-header">Status Transitions</div>
|
|
<div class="lt-section-body">
|
|
<div class="lt-table-wrap">
|
|
<table class="lt-table lt-table-responsive" aria-label="Status transitions">
|
|
<thead>
|
|
<tr>
|
|
<th scope="col">From Status</th>
|
|
<th scope="col">→</th>
|
|
<th scope="col">To Status</th>
|
|
<th scope="col">Req. Comment</th>
|
|
<th scope="col">Req. Admin</th>
|
|
<th scope="col">Active</th>
|
|
<th scope="col">Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php if (empty($workflows)): ?>
|
|
<tr><td colspan="7" class="lt-empty">No transitions defined. Add transitions to enable status changes.</td></tr>
|
|
<?php else: foreach ($workflows as $wf): ?>
|
|
<?php $fromSlug = strtolower(str_replace(' ', '-', $wf['from_status'])); $toSlug = strtolower(str_replace(' ', '-', $wf['to_status'])); ?>
|
|
<tr>
|
|
<td data-label="From">
|
|
<span class="lt-status lt-status-<?= $fromSlug ?>"><?= htmlspecialchars($wf['from_status']) ?></span>
|
|
</td>
|
|
<td class="lt-text-amber lt-text-xs" style="text-align:center">→</td>
|
|
<td data-label="To">
|
|
<span class="lt-status lt-status-<?= $toSlug ?>"><?= htmlspecialchars($wf['to_status']) ?></span>
|
|
</td>
|
|
<td data-label="Req. Comment" style="text-align:center">
|
|
<?= $wf['requires_comment'] ? '<span class="lt-text-cyan">✓</span>' : '<span class="lt-text-muted">—</span>' ?>
|
|
</td>
|
|
<td data-label="Req. Admin" style="text-align:center">
|
|
<?= $wf['requires_admin'] ? '<span class="lt-text-amber">✓</span>' : '<span class="lt-text-muted">—</span>' ?>
|
|
</td>
|
|
<td data-label="Active" style="text-align:center">
|
|
<?= $wf['is_active']
|
|
? '<span class="lt-text-cyan">✓</span>'
|
|
: '<span class="lt-text-danger">✗</span>' ?>
|
|
</td>
|
|
<td data-label="Actions">
|
|
<div class="lt-btn-group">
|
|
<button type="button" class="lt-btn lt-btn-sm"
|
|
data-action="edit-transition" data-id="<?= $wf['transition_id'] ?>">EDIT</button>
|
|
<button type="button" class="lt-btn lt-btn-sm lt-btn-danger"
|
|
data-action="delete-transition" data-id="<?= $wf['transition_id'] ?>">DEL</button>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
<?php endforeach; endif ?>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Create/Edit Modal -->
|
|
<div class="lt-modal-overlay" id="workflowModal" aria-hidden="true" role="dialog"
|
|
aria-modal="true" aria-labelledby="wfModalTitle">
|
|
<div class="lt-modal">
|
|
<div class="lt-modal-header">
|
|
<span class="lt-modal-title" id="wfModalTitle">Create Transition</span>
|
|
<button type="button" class="lt-modal-close" data-modal-close aria-label="Close">✕</button>
|
|
</div>
|
|
<form id="workflowForm">
|
|
<input type="hidden" id="transition_id" name="transition_id">
|
|
<div class="lt-modal-body">
|
|
<div class="lt-form-group">
|
|
<label class="lt-label" for="from_status">From Status *</label>
|
|
<select id="from_status" name="from_status" class="lt-select" required>
|
|
<option value="Open">Open</option>
|
|
<option value="Pending">Pending</option>
|
|
<option value="In Progress">In Progress</option>
|
|
<option value="Closed">Closed</option>
|
|
</select>
|
|
</div>
|
|
<div class="lt-form-group">
|
|
<label class="lt-label" for="to_status">To Status *</label>
|
|
<select id="to_status" name="to_status" class="lt-select" required>
|
|
<option value="Open">Open</option>
|
|
<option value="Pending">Pending</option>
|
|
<option value="In Progress">In Progress</option>
|
|
<option value="Closed">Closed</option>
|
|
</select>
|
|
</div>
|
|
<div class="lt-form-group">
|
|
<label class="lt-filter-option">
|
|
<input type="checkbox" class="lt-checkbox" id="requires_comment" name="requires_comment">
|
|
Requires a comment when transitioning
|
|
</label>
|
|
</div>
|
|
<div class="lt-form-group">
|
|
<label class="lt-filter-option">
|
|
<input type="checkbox" class="lt-checkbox" id="requires_admin" name="requires_admin">
|
|
Requires administrator privileges
|
|
</label>
|
|
</div>
|
|
<div class="lt-form-group">
|
|
<label class="lt-filter-option">
|
|
<input type="checkbox" class="lt-checkbox" id="wf_is_active" name="is_active" checked>
|
|
Active
|
|
</label>
|
|
</div>
|
|
</div>
|
|
<div class="lt-modal-footer">
|
|
<button type="submit" class="lt-btn lt-btn-primary">SAVE</button>
|
|
<button type="button" class="lt-btn lt-btn-ghost" data-modal-close>CANCEL</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<script nonce="<?= $nonce ?>">
|
|
var workflows = <?= json_encode($workflows ?? [], JSON_HEX_TAG) ?>;
|
|
|
|
document.addEventListener('click', function (e) {
|
|
var target = e.target.closest('[data-action]');
|
|
if (!target) return;
|
|
switch (target.getAttribute('data-action')) {
|
|
case 'show-create-modal': showCreateModal(); break;
|
|
case 'edit-transition': editTransition(target.getAttribute('data-id')); break;
|
|
case 'delete-transition': deleteTransition(target.getAttribute('data-id')); break;
|
|
}
|
|
});
|
|
|
|
document.getElementById('workflowForm').addEventListener('submit', function (e) {
|
|
saveTransition(e);
|
|
});
|
|
|
|
if (window.lt) lt.keys.initDefaults();
|
|
|
|
function showCreateModal() {
|
|
document.getElementById('wfModalTitle').textContent = 'Create Transition';
|
|
document.getElementById('workflowForm').reset();
|
|
document.getElementById('transition_id').value = '';
|
|
document.getElementById('wf_is_active').checked = true;
|
|
lt.modal.open('workflowModal');
|
|
}
|
|
|
|
function editTransition(id) {
|
|
var wf = workflows.find(function (w) { return w.transition_id == id; });
|
|
if (!wf) return;
|
|
document.getElementById('transition_id').value = wf.transition_id;
|
|
document.getElementById('from_status').value = wf.from_status;
|
|
document.getElementById('to_status').value = wf.to_status;
|
|
document.getElementById('requires_comment').checked = wf.requires_comment == 1;
|
|
document.getElementById('requires_admin').checked = wf.requires_admin == 1;
|
|
document.getElementById('wf_is_active').checked = wf.is_active == 1;
|
|
document.getElementById('wfModalTitle').textContent = 'Edit Transition';
|
|
lt.modal.open('workflowModal');
|
|
}
|
|
|
|
function deleteTransition(id) {
|
|
showConfirmModal('Delete Transition', 'Delete this status transition? This cannot be undone.', 'error', function () {
|
|
lt.api.delete('/api/manage_workflows.php?id=' + id)
|
|
.then(function (data) {
|
|
if (data.success) window.location.reload();
|
|
else lt.toast.error(data.error || 'Failed to delete');
|
|
}).catch(function () { lt.toast.error('Failed to delete'); });
|
|
});
|
|
}
|
|
|
|
function saveTransition(e) {
|
|
e.preventDefault();
|
|
var data = {
|
|
transition_id: document.getElementById('transition_id').value,
|
|
from_status: document.getElementById('from_status').value,
|
|
to_status: document.getElementById('to_status').value,
|
|
requires_comment: document.getElementById('requires_comment').checked ? 1 : 0,
|
|
requires_admin: document.getElementById('requires_admin').checked ? 1 : 0,
|
|
is_active: document.getElementById('wf_is_active').checked ? 1 : 0,
|
|
};
|
|
var url = '/api/manage_workflows.php' + (data.transition_id ? '?id=' + data.transition_id : '');
|
|
var apiCall = data.transition_id ? lt.api.put(url, data) : lt.api.post(url, data);
|
|
apiCall.then(function (result) {
|
|
if (result.success) window.location.reload();
|
|
else lt.toast.error(result.error || 'Failed to save');
|
|
}).catch(function () { lt.toast.error('Failed to save'); });
|
|
}
|
|
</script>
|
|
|
|
<?php include __DIR__ . '/../../views/layout_footer.php'; ?>
|