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') {
|
} else if (filterType === 'search') {
|
||||||
params.delete('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 {
|
} else {
|
||||||
params.delete(filterType);
|
params.delete(filterType);
|
||||||
}
|
}
|
||||||
@@ -310,44 +316,33 @@ function initSidebarFilters() {
|
|||||||
applyFiltersBtn.addEventListener('click', () => {
|
applyFiltersBtn.addEventListener('click', () => {
|
||||||
const params = new URLSearchParams(window.location.search);
|
const params = new URLSearchParams(window.location.search);
|
||||||
|
|
||||||
// Collect selected statuses
|
// Checkboxes
|
||||||
const selectedStatuses = Array.from(
|
const selectedStatuses = Array.from(
|
||||||
document.querySelectorAll('.lt-filter-group input[name="status"]:checked')
|
document.querySelectorAll('.lt-filter-group input[name="status"]:checked')
|
||||||
).map(cb => cb.value);
|
).map(cb => cb.value);
|
||||||
|
|
||||||
// Collect selected categories
|
|
||||||
const selectedCategories = Array.from(
|
const selectedCategories = Array.from(
|
||||||
document.querySelectorAll('.lt-filter-group input[name="category"]:checked')
|
document.querySelectorAll('.lt-filter-group input[name="category"]:checked')
|
||||||
).map(cb => cb.value);
|
).map(cb => cb.value);
|
||||||
|
|
||||||
// Collect selected types
|
|
||||||
const selectedTypes = Array.from(
|
const selectedTypes = Array.from(
|
||||||
document.querySelectorAll('.lt-filter-group input[name="type"]:checked')
|
document.querySelectorAll('.lt-filter-group input[name="type"]:checked')
|
||||||
).map(cb => cb.value);
|
).map(cb => cb.value);
|
||||||
|
|
||||||
// Update URL parameters
|
if (selectedStatuses.length > 0) params.set('status', selectedStatuses.join(','));
|
||||||
if (selectedStatuses.length > 0) {
|
else params.delete('status');
|
||||||
params.set('status', selectedStatuses.join(','));
|
if (selectedCategories.length > 0) params.set('category', selectedCategories.join(','));
|
||||||
} else {
|
else params.delete('category');
|
||||||
params.delete('status');
|
if (selectedTypes.length > 0) params.set('type', selectedTypes.join(','));
|
||||||
}
|
else params.delete('type');
|
||||||
|
|
||||||
if (selectedCategories.length > 0) {
|
// Date inputs
|
||||||
params.set('category', selectedCategories.join(','));
|
const dateFields = ['created_from','created_to','updated_from','updated_to','closed_from','closed_to'];
|
||||||
} else {
|
dateFields.forEach(name => {
|
||||||
params.delete('category');
|
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');
|
params.set('page', '1');
|
||||||
|
|
||||||
// Reload with new parameters
|
|
||||||
window.location.search = params.toString();
|
window.location.search = params.toString();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -355,14 +350,10 @@ function initSidebarFilters() {
|
|||||||
if (clearFiltersBtn) {
|
if (clearFiltersBtn) {
|
||||||
clearFiltersBtn.addEventListener('click', () => {
|
clearFiltersBtn.addEventListener('click', () => {
|
||||||
const params = new URLSearchParams(window.location.search);
|
const params = new URLSearchParams(window.location.search);
|
||||||
|
['status','category','type',
|
||||||
// Remove filter parameters
|
'created_from','created_to','updated_from','updated_to','closed_from','closed_to'
|
||||||
params.delete('status');
|
].forEach(k => params.delete(k));
|
||||||
params.delete('category');
|
|
||||||
params.delete('type');
|
|
||||||
params.set('page', '1');
|
params.set('page', '1');
|
||||||
|
|
||||||
// Reload with cleared filters
|
|
||||||
window.location.search = params.toString();
|
window.location.search = params.toString();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -118,14 +118,18 @@ class DashboardController {
|
|||||||
|
|
||||||
// Validate date filters
|
// Validate date filters
|
||||||
$createdFrom = $this->validateDate($_GET['created_from'] ?? null);
|
$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);
|
$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 ($createdFrom) $filters['created_from'] = $createdFrom;
|
||||||
if ($createdTo) $filters['created_to'] = $createdTo;
|
if ($createdTo) $filters['created_to'] = $createdTo;
|
||||||
if ($updatedFrom) $filters['updated_from'] = $updatedFrom;
|
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
|
// Validate priority filters
|
||||||
$priorityMin = $this->validatePriority($_GET['priority_min'] ?? null);
|
$priorityMin = $this->validatePriority($_GET['priority_min'] ?? null);
|
||||||
|
|||||||
@@ -121,6 +121,18 @@ class TicketModel {
|
|||||||
$paramTypes .= 's';
|
$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
|
// Priority range
|
||||||
if (!empty($filters['priority_min'])) {
|
if (!empty($filters['priority_min'])) {
|
||||||
$whereConditions[] = "t.priority >= ?";
|
$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']);
|
$label = $_GET['assigned_to'] === 'unassigned' ? 'Unassigned' : 'User #' . htmlspecialchars($_GET['assigned_to']);
|
||||||
$activeFilters[] = ['type' => 'assigned_to', 'value' => $_GET['assigned_to'], 'label' => 'Assigned: ' . $label];
|
$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'];
|
$_lt_statuses = $GLOBALS['config']['TICKET_STATUSES'];
|
||||||
$currentStatus = isset($_GET['status']) ? explode(',', $_GET['status']) : ['Open', 'Pending', 'In Progress'];
|
$currentStatus = isset($_GET['status']) ? explode(',', $_GET['status']) : ['Open', 'Pending', 'In Progress'];
|
||||||
@@ -446,6 +461,37 @@ include __DIR__ . '/layout_header.php';
|
|||||||
</fieldset>
|
</fieldset>
|
||||||
<?php endif ?>
|
<?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">
|
<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="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>
|
<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';
|
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-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-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-today')) url += '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-resolved')) url += 'status=Closed&closed_from=' + today + '&closed_to=' + today;
|
||||||
else return;
|
else return;
|
||||||
window.location.href = url;
|
window.location.href = url;
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user