feat: complete TDS v1.2 redesign across all views

Full application redesign using Terminal Design System v1.2 (lt-* class
system). Introduces shared layout_header/footer partials, upgrades
base.css/base.js to TDS v1.2, and rewrites all views (Dashboard, Ticket,
CreateTicket, and all 7 admin views) with lt-frame, lt-table, lt-modal,
lt-stats-grid, lt-kv-grid, and data-action event delegation patterns.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-27 19:05:42 -04:00
parent 1989bcb8c8
commit 79c2d2b513
16 changed files with 11265 additions and 14094 deletions
+207 -251
View File
@@ -1,271 +1,227 @@
<?php
// Admin view for workflow/status transitions designer
// Receives $workflows from controller
require_once __DIR__ . '/../../middleware/SecurityHeadersMiddleware.php';
require_once __DIR__ . '/../../middleware/CsrfMiddleware.php';
$nonce = SecurityHeadersMiddleware::getNonce();
$nonce = SecurityHeadersMiddleware::getNonce();
$pageTitle = 'Workflow Designer';
$activeNav = 'admin-workflow';
$pageStyles = ['/assets/css/dashboard.css?v=20260327'];
$pageScripts = [];
include __DIR__ . '/../../views/layout_header.php';
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Workflow Designer - Admin</title>
<link rel="icon" type="image/png" href="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/images/favicon.png">
<link rel="stylesheet" href="/assets/css/base.css">
<link rel="stylesheet" href="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/css/dashboard.css?v=20260320">
<link rel="stylesheet" href="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/css/ticket.css?v=20260320">
<script nonce="<?php echo $nonce; ?>" src="/assets/js/base.js"></script>
<script nonce="<?php echo $nonce; ?>" src="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/js/utils.js?v=20260320"></script>
<script nonce="<?php echo $nonce; ?>">
window.CSRF_TOKEN = '<?php echo CsrfMiddleware::getToken(); ?>';
</script>
</head>
<body>
<div class="user-header">
<div class="user-header-left">
<a href="/" class="back-link">[ ← DASHBOARD ]</a>
<span class="admin-page-title">Admin: Workflow Designer</span>
</div>
<div class="user-header-right">
<?php if (isset($GLOBALS['currentUser'])): ?>
<span class="user-name">[ <?php echo htmlspecialchars(strtoupper($GLOBALS['currentUser']['display_name'] ?? $GLOBALS['currentUser']['username'])); ?> ]</span>
<span class="admin-badge">[ ADMIN ]</span>
<?php endif; ?>
<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">&larr; 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="ascii-frame-outer admin-container">
<span class="bottom-left-corner"></span>
<span class="bottom-right-corner">╝</span>
<div class="ascii-section-header">Status Workflow Designer</div>
<div class="ascii-content">
<div class="ascii-frame-inner">
<div class="admin-header-row">
<h2>Status Transitions</h2>
<button data-action="show-create-modal" class="btn">+ NEW TRANSITION</button>
<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">&rarr;</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">&rarr;</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>
<p class="text-muted-green mb-1">
Define which status transitions are allowed. This controls what options appear in the status dropdown.
</p>
<!-- Visual Workflow Diagram -->
<div class="workflow-diagram">
<h4 class="admin-section-title">Workflow Diagram</h4>
<div class="workflow-diagram-nodes">
<?php
$statuses = ['Open', 'Pending', 'In Progress', 'Closed'];
foreach ($statuses as $status):
$statusClass = 'status-' . str_replace(' ', '-', strtolower($status));
?>
<div class="workflow-diagram-node">
<div class="<?php echo $statusClass; ?>">
<?php echo $status; ?>
</div>
<div class="text-muted-green workflow-diagram-node-label">
<?php
$toCount = 0;
if (isset($workflows)) {
foreach ($workflows as $w) {
if ($w['from_status'] === $status) $toCount++;
}
}
echo "→ $toCount transitions";
?>
</div>
</div>
<?php endforeach; ?>
</div>
</div>
<!-- Transitions Table -->
<div class="table-wrapper">
<table>
<thead>
<tr>
<th>From Status</th>
<th>→</th>
<th>To Status</th>
<th>Requires Comment</th>
<th>Requires Admin</th>
<th>Active</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php if (empty($workflows)): ?>
<tr>
<td colspan="7" class="empty-state">No transitions defined. Add transitions to enable status changes.</td>
</tr>
<?php else: ?>
<?php foreach ($workflows as $wf): ?>
<tr>
<td>
<span class="status-<?php echo str_replace(' ', '-', strtolower($wf['from_status'])); ?>">
<?php echo htmlspecialchars($wf['from_status']); ?>
</span>
</td>
<td class="text-amber text-center">→</td>
<td>
<span class="status-<?php echo str_replace(' ', '-', strtolower($wf['to_status'])); ?>">
<?php echo htmlspecialchars($wf['to_status']); ?>
</span>
</td>
<td class="text-center"><?php echo $wf['requires_comment'] ? '✓' : ''; ?></td>
<td class="text-center"><?php echo $wf['requires_admin'] ? '✓' : ''; ?></td>
<td class="text-center">
<span class="<?php echo $wf['is_active'] ? 'text-open' : 'text-closed'; ?>">
<?php echo $wf['is_active'] ? '✓' : '✗'; ?>
</span>
</td>
<td>
<button data-action="edit-transition" data-id="<?php echo $wf['transition_id']; ?>" class="btn btn-small">EDIT</button>
<button data-action="delete-transition" data-id="<?php echo $wf['transition_id']; ?>" class="btn btn-small btn-danger">DELETE</button>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</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="modalTitle">
<div class="lt-modal lt-modal-sm">
<div class="lt-modal-header">
<span class="lt-modal-title" id="modalTitle">Create Transition</span>
<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="setting-row">
<label for="from_status">From Status *</label>
<select id="from_status" name="from_status" 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="setting-row">
<label for="to_status">To Status *</label>
<select id="to_status" name="to_status" 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="setting-row">
<label><input type="checkbox" id="requires_comment" name="requires_comment"> Requires comment</label>
</div>
<div class="setting-row">
<label><input type="checkbox" id="requires_admin" name="requires_admin"> Requires admin privileges</label>
</div>
<div class="setting-row">
<label><input type="checkbox" id="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>
<!-- 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">&#x2715;</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="<?php echo $nonce; ?>">
const workflows = <?php echo json_encode($workflows ?? []); ?>;
<script nonce="<?= $nonce ?>">
var workflows = <?= json_encode($workflows ?? []) ?>;
function showCreateModal() {
document.getElementById('modalTitle').textContent = 'Create Transition';
document.getElementById('workflowForm').reset();
document.getElementById('transition_id').value = '';
document.getElementById('is_active').checked = true;
lt.modal.open('workflowModal');
}
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;
}
});
function closeModal() {
lt.modal.close('workflowModal');
}
document.getElementById('workflowForm').addEventListener('submit', function (e) {
saveTransition(e);
});
// Event delegation for data-action handlers
document.addEventListener('click', function(event) {
const target = event.target.closest('[data-action]');
if (!target) return;
if (window.lt) lt.keys.initDefaults();
const action = target.dataset.action;
switch (action) {
case 'show-create-modal':
showCreateModal();
break;
case 'edit-transition':
editTransition(target.dataset.id);
break;
case 'delete-transition':
deleteTransition(target.dataset.id);
break;
}
});
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');
}
// Form submit handler
document.getElementById('workflowForm').addEventListener('submit', function(e) {
saveTransition(e);
});
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');
}
if (window.lt) lt.keys.initDefaults();
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();
const 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('is_active').checked ? 1 : 0
};
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>
const url = '/api/manage_workflows.php' + (data.transition_id ? '?id=' + data.transition_id : '');
const apiCall = data.transition_id ? lt.api.put(url, data) : lt.api.post(url, data);
apiCall.then(result => {
if (result.success) {
window.location.reload();
} else {
lt.toast.error(result.error || 'Failed to save');
}
}).catch(err => lt.toast.error('Failed to save'));
}
function editTransition(id) {
const wf = workflows.find(w => 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('is_active').checked = wf.is_active == 1;
document.getElementById('modalTitle').textContent = 'Edit Transition';
lt.modal.open('workflowModal');
}
function deleteTransition(id) {
showConfirmModal('Delete Transition', 'Delete this status transition?', 'error', function() {
lt.api.delete('/api/manage_workflows.php?id=' + id)
.then(data => {
if (data.success) window.location.reload();
else lt.toast.error(data.error || 'Failed to delete');
}).catch(err => lt.toast.error('Failed to delete'));
});
}
</script>
</body>
</html>
<?php include __DIR__ . '/../../views/layout_footer.php'; ?>