Fix dashboard stat tiles and add sidebar date filters
- Created Today tile: no longer limits to open statuses (count is all statuses) - Closed Today tile: filters by closed_at range, not updated_at - Add closed_from/closed_to support to TicketModel and DashboardController - Add Created/Updated/Closed date range inputs to sidebar filter panel - Apply button collects date inputs; Clear All removes them - removeFilter handles date chip removal (clears both _from and _to) - Active filter chips shown for date ranges Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+23
-32
@@ -259,6 +259,12 @@ function removeFilter(filterType, filterValue) {
|
||||
}
|
||||
} else if (filterType === 'search') {
|
||||
params.delete('search');
|
||||
} else if (filterType === 'created_from') {
|
||||
params.delete('created_from'); params.delete('created_to');
|
||||
} else if (filterType === 'updated_from') {
|
||||
params.delete('updated_from'); params.delete('updated_to');
|
||||
} else if (filterType === 'closed_from') {
|
||||
params.delete('closed_from'); params.delete('closed_to');
|
||||
} else {
|
||||
params.delete(filterType);
|
||||
}
|
||||
@@ -310,44 +316,33 @@ function initSidebarFilters() {
|
||||
applyFiltersBtn.addEventListener('click', () => {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
|
||||
// Collect selected statuses
|
||||
// Checkboxes
|
||||
const selectedStatuses = Array.from(
|
||||
document.querySelectorAll('.lt-filter-group input[name="status"]:checked')
|
||||
).map(cb => cb.value);
|
||||
|
||||
// Collect selected categories
|
||||
const selectedCategories = Array.from(
|
||||
document.querySelectorAll('.lt-filter-group input[name="category"]:checked')
|
||||
).map(cb => cb.value);
|
||||
|
||||
// Collect selected types
|
||||
const selectedTypes = Array.from(
|
||||
document.querySelectorAll('.lt-filter-group input[name="type"]:checked')
|
||||
).map(cb => cb.value);
|
||||
|
||||
// Update URL parameters
|
||||
if (selectedStatuses.length > 0) {
|
||||
params.set('status', selectedStatuses.join(','));
|
||||
} else {
|
||||
params.delete('status');
|
||||
}
|
||||
if (selectedStatuses.length > 0) params.set('status', selectedStatuses.join(','));
|
||||
else params.delete('status');
|
||||
if (selectedCategories.length > 0) params.set('category', selectedCategories.join(','));
|
||||
else params.delete('category');
|
||||
if (selectedTypes.length > 0) params.set('type', selectedTypes.join(','));
|
||||
else params.delete('type');
|
||||
|
||||
if (selectedCategories.length > 0) {
|
||||
params.set('category', selectedCategories.join(','));
|
||||
} else {
|
||||
params.delete('category');
|
||||
}
|
||||
// Date inputs
|
||||
const dateFields = ['created_from','created_to','updated_from','updated_to','closed_from','closed_to'];
|
||||
dateFields.forEach(name => {
|
||||
const el = document.getElementById('filter-' + name.replace('_', '-'));
|
||||
if (el && el.value) params.set(name, el.value);
|
||||
else params.delete(name);
|
||||
});
|
||||
|
||||
if (selectedTypes.length > 0) {
|
||||
params.set('type', selectedTypes.join(','));
|
||||
} else {
|
||||
params.delete('type');
|
||||
}
|
||||
|
||||
// Reset to page 1 when filters change
|
||||
params.set('page', '1');
|
||||
|
||||
// Reload with new parameters
|
||||
window.location.search = params.toString();
|
||||
});
|
||||
}
|
||||
@@ -355,14 +350,10 @@ function initSidebarFilters() {
|
||||
if (clearFiltersBtn) {
|
||||
clearFiltersBtn.addEventListener('click', () => {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
|
||||
// Remove filter parameters
|
||||
params.delete('status');
|
||||
params.delete('category');
|
||||
params.delete('type');
|
||||
['status','category','type',
|
||||
'created_from','created_to','updated_from','updated_to','closed_from','closed_to'
|
||||
].forEach(k => params.delete(k));
|
||||
params.set('page', '1');
|
||||
|
||||
// Reload with cleared filters
|
||||
window.location.search = params.toString();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -118,14 +118,18 @@ class DashboardController {
|
||||
|
||||
// Validate date filters
|
||||
$createdFrom = $this->validateDate($_GET['created_from'] ?? null);
|
||||
$createdTo = $this->validateDate($_GET['created_to'] ?? null);
|
||||
$createdTo = $this->validateDate($_GET['created_to'] ?? null);
|
||||
$updatedFrom = $this->validateDate($_GET['updated_from'] ?? null);
|
||||
$updatedTo = $this->validateDate($_GET['updated_to'] ?? null);
|
||||
$updatedTo = $this->validateDate($_GET['updated_to'] ?? null);
|
||||
$closedFrom = $this->validateDate($_GET['closed_from'] ?? null);
|
||||
$closedTo = $this->validateDate($_GET['closed_to'] ?? null);
|
||||
|
||||
if ($createdFrom) $filters['created_from'] = $createdFrom;
|
||||
if ($createdTo) $filters['created_to'] = $createdTo;
|
||||
if ($createdTo) $filters['created_to'] = $createdTo;
|
||||
if ($updatedFrom) $filters['updated_from'] = $updatedFrom;
|
||||
if ($updatedTo) $filters['updated_to'] = $updatedTo;
|
||||
if ($updatedTo) $filters['updated_to'] = $updatedTo;
|
||||
if ($closedFrom) $filters['closed_from'] = $closedFrom;
|
||||
if ($closedTo) $filters['closed_to'] = $closedTo;
|
||||
|
||||
// Validate priority filters
|
||||
$priorityMin = $this->validatePriority($_GET['priority_min'] ?? null);
|
||||
|
||||
@@ -121,6 +121,18 @@ class TicketModel {
|
||||
$paramTypes .= 's';
|
||||
}
|
||||
|
||||
// Date range - closed_at
|
||||
if (!empty($filters['closed_from'])) {
|
||||
$whereConditions[] = "DATE(t.closed_at) >= ?";
|
||||
$params[] = $filters['closed_from'];
|
||||
$paramTypes .= 's';
|
||||
}
|
||||
if (!empty($filters['closed_to'])) {
|
||||
$whereConditions[] = "DATE(t.closed_at) <= ?";
|
||||
$params[] = $filters['closed_to'];
|
||||
$paramTypes .= 's';
|
||||
}
|
||||
|
||||
// Priority range
|
||||
if (!empty($filters['priority_min'])) {
|
||||
$whereConditions[] = "t.priority >= ?";
|
||||
|
||||
+48
-2
@@ -56,6 +56,21 @@ if (!empty($_GET['assigned_to'])) {
|
||||
$label = $_GET['assigned_to'] === 'unassigned' ? 'Unassigned' : 'User #' . htmlspecialchars($_GET['assigned_to']);
|
||||
$activeFilters[] = ['type' => 'assigned_to', 'value' => $_GET['assigned_to'], 'label' => 'Assigned: ' . $label];
|
||||
}
|
||||
if (!empty($_GET['created_from']) || !empty($_GET['created_to'])) {
|
||||
$from = $_GET['created_from'] ?? ''; $to = $_GET['created_to'] ?? '';
|
||||
$label = $from === $to && $from ? 'Created: ' . $from : 'Created: ' . ($from ?: '…') . ' – ' . ($to ?: '…');
|
||||
$activeFilters[] = ['type' => 'created_from', 'value' => $from, 'label' => $label];
|
||||
}
|
||||
if (!empty($_GET['updated_from']) || !empty($_GET['updated_to'])) {
|
||||
$from = $_GET['updated_from'] ?? ''; $to = $_GET['updated_to'] ?? '';
|
||||
$label = $from === $to && $from ? 'Updated: ' . $from : 'Updated: ' . ($from ?: '…') . ' – ' . ($to ?: '…');
|
||||
$activeFilters[] = ['type' => 'updated_from', 'value' => $from, 'label' => $label];
|
||||
}
|
||||
if (!empty($_GET['closed_from']) || !empty($_GET['closed_to'])) {
|
||||
$from = $_GET['closed_from'] ?? ''; $to = $_GET['closed_to'] ?? '';
|
||||
$label = $from === $to && $from ? 'Closed: ' . $from : 'Closed: ' . ($from ?: '…') . ' – ' . ($to ?: '…');
|
||||
$activeFilters[] = ['type' => 'closed_from', 'value' => $from, 'label' => $label];
|
||||
}
|
||||
|
||||
$_lt_statuses = $GLOBALS['config']['TICKET_STATUSES'];
|
||||
$currentStatus = isset($_GET['status']) ? explode(',', $_GET['status']) : ['Open', 'Pending', 'In Progress'];
|
||||
@@ -446,6 +461,37 @@ include __DIR__ . '/layout_header.php';
|
||||
</fieldset>
|
||||
<?php endif ?>
|
||||
|
||||
<!-- Date Filters -->
|
||||
<fieldset class="lt-filter-group">
|
||||
<legend class="lt-filter-label">Created</legend>
|
||||
<div class="lt-flex lt-flex-col lt-flex-gap-xs">
|
||||
<input type="date" id="filter-created-from" name="created_from" class="lt-input lt-input-sm"
|
||||
placeholder="From" value="<?= htmlspecialchars($_GET['created_from'] ?? '') ?>">
|
||||
<input type="date" id="filter-created-to" name="created_to" class="lt-input lt-input-sm"
|
||||
placeholder="To" value="<?= htmlspecialchars($_GET['created_to'] ?? '') ?>">
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<fieldset class="lt-filter-group">
|
||||
<legend class="lt-filter-label">Updated</legend>
|
||||
<div class="lt-flex lt-flex-col lt-flex-gap-xs">
|
||||
<input type="date" id="filter-updated-from" name="updated_from" class="lt-input lt-input-sm"
|
||||
placeholder="From" value="<?= htmlspecialchars($_GET['updated_from'] ?? '') ?>">
|
||||
<input type="date" id="filter-updated-to" name="updated_to" class="lt-input lt-input-sm"
|
||||
placeholder="To" value="<?= htmlspecialchars($_GET['updated_to'] ?? '') ?>">
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<fieldset class="lt-filter-group">
|
||||
<legend class="lt-filter-label">Closed</legend>
|
||||
<div class="lt-flex lt-flex-col lt-flex-gap-xs">
|
||||
<input type="date" id="filter-closed-from" name="closed_from" class="lt-input lt-input-sm"
|
||||
placeholder="From" value="<?= htmlspecialchars($_GET['closed_from'] ?? '') ?>">
|
||||
<input type="date" id="filter-closed-to" name="closed_to" class="lt-input lt-input-sm"
|
||||
placeholder="To" value="<?= htmlspecialchars($_GET['closed_to'] ?? '') ?>">
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<div class="lt-btn-group lt-flex-col">
|
||||
<button type="button" id="apply-filters-btn" class="lt-btn lt-btn-primary lt-btn-sm">APPLY</button>
|
||||
<button type="button" id="clear-filters-btn" class="lt-btn lt-btn-ghost lt-btn-sm">CLEAR ALL</button>
|
||||
@@ -1165,8 +1211,8 @@ document.querySelectorAll('.lt-stat-card').forEach(function (card) {
|
||||
if (card.classList.contains('stat-open')) url += 'status=Open,Pending,In+Progress';
|
||||
else if (card.classList.contains('stat-critical')) url += 'status=Open,Pending,In+Progress&priority_max=1';
|
||||
else if (card.classList.contains('stat-unassigned')) url += 'status=Open,Pending,In+Progress&assigned_to=unassigned';
|
||||
else if (card.classList.contains('stat-today')) url += 'status=Open,Pending,In+Progress&created_from=' + today + '&created_to=' + today;
|
||||
else if (card.classList.contains('stat-resolved')) url += 'status=Closed&updated_from=' + today + '&updated_to=' + today;
|
||||
else if (card.classList.contains('stat-today')) url += 'created_from=' + today + '&created_to=' + today;
|
||||
else if (card.classList.contains('stat-resolved')) url += 'status=Closed&closed_from=' + today + '&closed_to=' + today;
|
||||
else return;
|
||||
window.location.href = url;
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user