Compare commits
5 Commits
84b104a501
...
54887ffa24
| Author | SHA1 | Date | |
|---|---|---|---|
| 54887ffa24 | |||
| 613886068d | |||
| 847d6b2656 | |||
| c2cd923d32 | |||
| 67a7d769f0 |
@@ -1319,7 +1319,9 @@ select option:checked {
|
||||
.lt-modal-body {
|
||||
padding: var(--space-lg);
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.lt-modal-footer {
|
||||
@@ -3187,6 +3189,29 @@ input[type="range"].lt-range::-moz-range-thumb {
|
||||
.lt-kv-val--green { color: var(--accent-green); }
|
||||
.lt-kv-val--red { color: var(--accent-red); }
|
||||
|
||||
/* lt-kv-row / lt-kv-label / lt-kv-value — alternate KV row pattern */
|
||||
.lt-kv-row {
|
||||
display: contents; /* children become direct grid items of lt-kv-grid */
|
||||
}
|
||||
.lt-kv-label {
|
||||
padding: var(--space-xs) var(--space-md) var(--space-xs) 0;
|
||||
color: var(--text-dim);
|
||||
font-size: 0.75rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
white-space: nowrap;
|
||||
border-bottom: 1px solid var(--border-dim);
|
||||
align-self: center;
|
||||
}
|
||||
.lt-kv-value {
|
||||
padding: var(--space-xs) 0 var(--space-xs) var(--space-md);
|
||||
color: var(--text-primary);
|
||||
border-bottom: 1px solid var(--border-dim);
|
||||
min-width: 0;
|
||||
overflow-wrap: break-word;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
|
||||
/* ----------------------------------------------------------------
|
||||
43. HERO / BANNER SECTION
|
||||
|
||||
+17
-10
@@ -191,7 +191,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
break;
|
||||
// View mode toggle
|
||||
case 'set-view-mode':
|
||||
if (target.dataset.mode === 'card') populateKanbanCards();
|
||||
setViewMode(target.dataset.mode);
|
||||
break;
|
||||
// Settings
|
||||
case 'open-settings':
|
||||
@@ -686,7 +686,9 @@ function closeBulkPriorityModal() {
|
||||
}
|
||||
|
||||
function performBulkPriority() {
|
||||
const priority = document.getElementById('bulkPriority').value;
|
||||
const priorityEl = document.getElementById('bulkPriority');
|
||||
if (!priorityEl) return;
|
||||
const priority = priorityEl.value;
|
||||
const ticketIds = getSelectedTicketIds();
|
||||
|
||||
if (!priority) {
|
||||
@@ -789,7 +791,9 @@ function closeBulkStatusModal() {
|
||||
}
|
||||
|
||||
function performBulkStatusChange() {
|
||||
const status = document.getElementById('bulkStatus').value;
|
||||
const bulkStatusEl = document.getElementById('bulkStatus');
|
||||
if (!bulkStatusEl) return;
|
||||
const status = bulkStatusEl.value;
|
||||
const ticketIds = getSelectedTicketIds();
|
||||
|
||||
if (!status) {
|
||||
@@ -986,7 +990,9 @@ function closeQuickStatusModal() {
|
||||
}
|
||||
|
||||
function performQuickStatusChange(ticketId) {
|
||||
const newStatus = document.getElementById('quickStatusSelect').value;
|
||||
const quickStatusEl = document.getElementById('quickStatusSelect');
|
||||
if (!quickStatusEl) return;
|
||||
const newStatus = quickStatusEl.value;
|
||||
|
||||
lt.api.post('/api/update_ticket.php', { ticket_id: ticketId, status: newStatus })
|
||||
.then(data => {
|
||||
@@ -1239,14 +1245,15 @@ function populateKanbanCards() {
|
||||
}
|
||||
}
|
||||
|
||||
// Restore view mode on page load — click the kanban tab button to trigger lt.tabs
|
||||
// Restore view mode on page load — lt.tabs already restores the active panel visually
|
||||
// via lt_activeTab_<path>; we just need to populate kanban cards if that panel is active
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const savedMode = localStorage.getItem('ticketViewMode');
|
||||
if (savedMode === 'card') {
|
||||
const cardBtn = document.getElementById('cardViewBtn');
|
||||
if (cardBtn) cardBtn.click();
|
||||
else populateKanbanCards();
|
||||
try {
|
||||
const savedTab = localStorage.getItem('lt_activeTab_' + location.pathname);
|
||||
if (savedTab === 'tab-kanban') {
|
||||
populateKanbanCards();
|
||||
}
|
||||
} catch (_) {}
|
||||
});
|
||||
|
||||
// ========================================
|
||||
|
||||
@@ -136,10 +136,16 @@ class DashboardController {
|
||||
|
||||
// Validate user ID filters
|
||||
$createdBy = $this->validateUserId($_GET['created_by'] ?? null);
|
||||
$assignedTo = $this->validateUserId($_GET['assigned_to'] ?? null);
|
||||
|
||||
if ($createdBy !== null) $filters['created_by'] = $createdBy;
|
||||
|
||||
// assigned_to accepts a numeric user ID or the special string 'unassigned'
|
||||
$assignedToRaw = $_GET['assigned_to'] ?? null;
|
||||
if ($assignedToRaw === 'unassigned') {
|
||||
$filters['assigned_to'] = 'unassigned';
|
||||
} else {
|
||||
$assignedTo = $this->validateUserId($assignedToRaw);
|
||||
if ($assignedTo !== null) $filters['assigned_to'] = $assignedTo;
|
||||
}
|
||||
|
||||
// Get tickets with pagination, sorting, search, and advanced filters
|
||||
$result = $this->ticketModel->getAllTickets($page, $limit, $status, $sortColumn, $sortDirection, $category, $type, $search, $filters, $GLOBALS['currentUser'] ?? []);
|
||||
|
||||
@@ -58,7 +58,7 @@ class RecurringTicketModel {
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
|
||||
|
||||
$stmt = $this->conn->prepare($sql);
|
||||
$stmt->bind_param('ssssiiisssis',
|
||||
$stmt->bind_param('ssssiiisssii',
|
||||
$data['title_template'],
|
||||
$data['description_template'],
|
||||
$data['category'],
|
||||
|
||||
@@ -81,9 +81,12 @@ class TicketModel {
|
||||
if ($search && !empty($search)) {
|
||||
if ($this->hasFulltextIndex()) {
|
||||
// MATCH...AGAINST for indexed full-text search (much faster at scale)
|
||||
// Strip MySQL boolean mode special chars to prevent parse errors on user input
|
||||
$ftSearch = preg_replace('/[+\-><()\~*"@]+/', ' ', $search);
|
||||
$ftSearch = trim(preg_replace('/\s+/', ' ', $ftSearch)) . '*';
|
||||
$whereConditions[] = "(MATCH(t.title, t.description) AGAINST (? IN BOOLEAN MODE) OR t.ticket_id LIKE ? OR t.category LIKE ? OR t.type LIKE ?)";
|
||||
$searchTerm = "%$search%";
|
||||
$params = array_merge($params, [$search . '*', $searchTerm, $searchTerm, $searchTerm]);
|
||||
$params = array_merge($params, [$ftSearch, $searchTerm, $searchTerm, $searchTerm]);
|
||||
$paramTypes .= 'ssss';
|
||||
} else {
|
||||
$whereConditions[] = "(t.title LIKE ? OR t.description LIKE ? OR t.ticket_id LIKE ? OR t.category LIKE ? OR t.type LIKE ?)";
|
||||
|
||||
@@ -165,7 +165,7 @@ $_lt_assetVer = $GLOBALS['config']['ASSET_VERSION'] ?? '20260329';
|
||||
<?php if ($_lt_userId > 0): ?>
|
||||
<img src="/api/user_avatar.php?user_id=<?= $_lt_userId ?>"
|
||||
alt=""
|
||||
class="lt-avatar-img"
|
||||
class="lt-avatar-img">
|
||||
<?php endif ?>
|
||||
<span class="lt-avatar-initials"><?= htmlspecialchars($_lt_initials) ?></span>
|
||||
</div>
|
||||
@@ -195,7 +195,7 @@ $_lt_assetVer = $GLOBALS['config']['ASSET_VERSION'] ?? '20260329';
|
||||
<div style="padding:0.75rem;font-size:0.75rem;color:var(--text-muted);text-align:center">Loading…</div>
|
||||
</div>
|
||||
<div class="lt-notif-panel-footer">
|
||||
<a href="/admin/audit-log" class="lt-btn lt-btn-ghost lt-btn-sm" style="width:100%;text-align:center">View activity log</a>
|
||||
<a href="/admin/audit-log" class="lt-btn lt-btn-ghost lt-btn-sm lt-w-full lt-text-center">View activity log</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user