Replace resource filter with V2's Active/Inactive/All button bar
Ported from V2: three-button filter bar (Active | Inactive | All) that shows/hides resource rows via JS data-active attribute. Defaults to Active so inactive workers/projects/teams are hidden. Toggle switch updates data-active instantly and re-applies filter. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
ef77c97719
commit
97866f1e74
@ -177,134 +177,72 @@
|
||||
</a>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
<ul class="nav nav-tabs px-3 pt-3" id="resourceTabs" role="tablist">
|
||||
<p class="text-muted small mb-0 px-3 pt-3">Toggle active status. Inactive items are hidden from forms.</p>
|
||||
|
||||
<ul class="nav nav-tabs px-3 pt-2" id="resourceTabs" role="tablist">
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link active" id="workers-tab" data-bs-toggle="tab" data-bs-target="#workers" type="button" role="tab" aria-selected="true">
|
||||
Workers <span class="badge bg-secondary bg-opacity-50 ms-1">{{ active_workers|length }}</span>
|
||||
</button>
|
||||
<button class="nav-link active" id="workers-tab" data-bs-toggle="tab" data-bs-target="#workers" type="button" role="tab" aria-selected="true">Workers</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link" id="projects-tab" data-bs-toggle="tab" data-bs-target="#projects" type="button" role="tab" aria-selected="false">
|
||||
Projects <span class="badge bg-secondary bg-opacity-50 ms-1">{{ active_projects|length }}</span>
|
||||
</button>
|
||||
<button class="nav-link" id="projects-tab" data-bs-toggle="tab" data-bs-target="#projects" type="button" role="tab" aria-selected="false">Projects</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link" id="teams-tab" data-bs-toggle="tab" data-bs-target="#teams" type="button" role="tab" aria-selected="false">
|
||||
Teams <span class="badge bg-secondary bg-opacity-50 ms-1">{{ active_teams|length }}</span>
|
||||
</button>
|
||||
<button class="nav-link" id="teams-tab" data-bs-toggle="tab" data-bs-target="#teams" type="button" role="tab" aria-selected="false">Teams</button>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="tab-content" id="resourceTabsContent">
|
||||
|
||||
{# Filter bar — Active / Inactive / All (defaults to Active) #}
|
||||
<div class="btn-group btn-group-sm w-100 px-3 mt-2" id="resourceFilter" role="group">
|
||||
<button type="button" class="btn btn-outline-secondary active" data-filter="active">Active</button>
|
||||
<button type="button" class="btn btn-outline-secondary" data-filter="inactive">Inactive</button>
|
||||
<button type="button" class="btn btn-outline-secondary" data-filter="all">All</button>
|
||||
</div>
|
||||
|
||||
<div class="tab-content px-0 mt-2" id="resourceTabsContent" style="max-height: 350px; overflow-y: auto;">
|
||||
|
||||
{# === WORKERS TAB === #}
|
||||
<div class="tab-pane fade show active" id="workers" role="tabpanel">
|
||||
<ul class="list-group list-group-flush" style="max-height: 300px; overflow-y: auto;">
|
||||
{% for item in active_workers %}
|
||||
<li class="list-group-item d-flex justify-content-between align-items-center">
|
||||
{{ item.name }}
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input toggle-active" type="checkbox" role="switch" data-type="worker" data-id="{{ item.id }}" checked>
|
||||
</div>
|
||||
</li>
|
||||
{% empty %}
|
||||
<li class="list-group-item text-muted">No active workers.</li>
|
||||
{% endfor %}
|
||||
|
||||
{# Collapsible section for inactive workers #}
|
||||
{% if inactive_workers %}
|
||||
<li class="list-group-item p-0 border-0">
|
||||
<button class="btn btn-sm btn-link text-muted w-100 py-2" type="button" data-bs-toggle="collapse" data-bs-target="#inactiveWorkers">
|
||||
<i class="fas fa-chevron-down fa-xs me-1"></i> Show {{ inactive_workers|length }} Inactive
|
||||
</button>
|
||||
<div class="collapse" id="inactiveWorkers">
|
||||
<ul class="list-group list-group-flush">
|
||||
{% for item in inactive_workers %}
|
||||
<li class="list-group-item d-flex justify-content-between align-items-center opacity-50">
|
||||
{{ item.name }}
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input toggle-active" type="checkbox" role="switch" data-type="worker" data-id="{{ item.id }}">
|
||||
</div>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
{% for item in workers %}
|
||||
<div class="resource-row d-flex justify-content-between align-items-center py-2 px-3 border-bottom" data-active="{% if item.active %}true{% else %}false{% endif %}">
|
||||
<strong class="small">{{ item.name }}</strong>
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input toggle-active" type="checkbox" role="switch" data-type="worker" data-id="{{ item.id }}" {% if item.active %}checked{% endif %}>
|
||||
</div>
|
||||
</div>
|
||||
{% empty %}
|
||||
<p class="text-muted small px-3 py-2">No workers found.</p>
|
||||
{% endfor %}
|
||||
<p class="text-muted small text-center mt-2 resource-empty" style="display:none;">No matching items.</p>
|
||||
</div>
|
||||
|
||||
{# === PROJECTS TAB === #}
|
||||
<div class="tab-pane fade" id="projects" role="tabpanel">
|
||||
<ul class="list-group list-group-flush" style="max-height: 300px; overflow-y: auto;">
|
||||
{% for item in active_projects %}
|
||||
<li class="list-group-item d-flex justify-content-between align-items-center">
|
||||
{{ item.name }}
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input toggle-active" type="checkbox" role="switch" data-type="project" data-id="{{ item.id }}" checked>
|
||||
</div>
|
||||
</li>
|
||||
{% empty %}
|
||||
<li class="list-group-item text-muted">No active projects.</li>
|
||||
{% endfor %}
|
||||
|
||||
{# Collapsible section for inactive projects #}
|
||||
{% if inactive_projects %}
|
||||
<li class="list-group-item p-0 border-0">
|
||||
<button class="btn btn-sm btn-link text-muted w-100 py-2" type="button" data-bs-toggle="collapse" data-bs-target="#inactiveProjects">
|
||||
<i class="fas fa-chevron-down fa-xs me-1"></i> Show {{ inactive_projects|length }} Inactive
|
||||
</button>
|
||||
<div class="collapse" id="inactiveProjects">
|
||||
<ul class="list-group list-group-flush">
|
||||
{% for item in inactive_projects %}
|
||||
<li class="list-group-item d-flex justify-content-between align-items-center opacity-50">
|
||||
{{ item.name }}
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input toggle-active" type="checkbox" role="switch" data-type="project" data-id="{{ item.id }}">
|
||||
</div>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
{% for item in projects %}
|
||||
<div class="resource-row d-flex justify-content-between align-items-center py-2 px-3 border-bottom" data-active="{% if item.active %}true{% else %}false{% endif %}">
|
||||
<strong class="small">{{ item.name }}</strong>
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input toggle-active" type="checkbox" role="switch" data-type="project" data-id="{{ item.id }}" {% if item.active %}checked{% endif %}>
|
||||
</div>
|
||||
</div>
|
||||
{% empty %}
|
||||
<p class="text-muted small px-3 py-2">No projects found.</p>
|
||||
{% endfor %}
|
||||
<p class="text-muted small text-center mt-2 resource-empty" style="display:none;">No matching items.</p>
|
||||
</div>
|
||||
|
||||
{# === TEAMS TAB === #}
|
||||
<div class="tab-pane fade" id="teams" role="tabpanel">
|
||||
<ul class="list-group list-group-flush" style="max-height: 300px; overflow-y: auto;">
|
||||
{% for item in active_teams %}
|
||||
<li class="list-group-item d-flex justify-content-between align-items-center">
|
||||
{{ item.name }}
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input toggle-active" type="checkbox" role="switch" data-type="team" data-id="{{ item.id }}" checked>
|
||||
</div>
|
||||
</li>
|
||||
{% empty %}
|
||||
<li class="list-group-item text-muted">No active teams.</li>
|
||||
{% endfor %}
|
||||
|
||||
{# Collapsible section for inactive teams #}
|
||||
{% if inactive_teams %}
|
||||
<li class="list-group-item p-0 border-0">
|
||||
<button class="btn btn-sm btn-link text-muted w-100 py-2" type="button" data-bs-toggle="collapse" data-bs-target="#inactiveTeams">
|
||||
<i class="fas fa-chevron-down fa-xs me-1"></i> Show {{ inactive_teams|length }} Inactive
|
||||
</button>
|
||||
<div class="collapse" id="inactiveTeams">
|
||||
<ul class="list-group list-group-flush">
|
||||
{% for item in inactive_teams %}
|
||||
<li class="list-group-item d-flex justify-content-between align-items-center opacity-50">
|
||||
{{ item.name }}
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input toggle-active" type="checkbox" role="switch" data-type="team" data-id="{{ item.id }}">
|
||||
</div>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
{% for item in teams %}
|
||||
<div class="resource-row d-flex justify-content-between align-items-center py-2 px-3 border-bottom" data-active="{% if item.active %}true{% else %}false{% endif %}">
|
||||
<strong class="small">{{ item.name }}</strong>
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input toggle-active" type="checkbox" role="switch" data-type="team" data-id="{{ item.id }}" {% if item.active %}checked{% endif %}>
|
||||
</div>
|
||||
</div>
|
||||
{% empty %}
|
||||
<p class="text-muted small px-3 py-2">No teams found.</p>
|
||||
{% endfor %}
|
||||
<p class="text-muted small text-center mt-2 resource-empty" style="display:none;">No matching items.</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@ -413,37 +351,81 @@
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const toggleSwitches = document.querySelectorAll('.toggle-active');
|
||||
|
||||
toggleSwitches.forEach(switchEl => {
|
||||
|
||||
// === RESOURCE FILTER (Active / Inactive / All) ===
|
||||
// Hides/shows resource rows based on their data-active attribute.
|
||||
// Starts on "Active" so only current items are visible by default.
|
||||
var currentFilter = 'active';
|
||||
var filterBtns = document.querySelectorAll('#resourceFilter button');
|
||||
|
||||
function applyFilter() {
|
||||
document.querySelectorAll('.resource-row').forEach(function(row) {
|
||||
var isActive = row.dataset.active === 'true';
|
||||
var show = false;
|
||||
if (currentFilter === 'all') show = true;
|
||||
else if (currentFilter === 'active') show = isActive;
|
||||
else if (currentFilter === 'inactive') show = !isActive;
|
||||
row.style.display = show ? '' : 'none';
|
||||
});
|
||||
// Show "No matching items" if a tab has rows but none are visible
|
||||
document.querySelectorAll('.tab-pane').forEach(function(pane) {
|
||||
var rows = pane.querySelectorAll('.resource-row');
|
||||
var visibleRows = Array.from(rows).filter(function(r) { return r.style.display !== 'none'; });
|
||||
var emptyMsg = pane.querySelector('.resource-empty');
|
||||
if (emptyMsg) {
|
||||
emptyMsg.style.display = (rows.length > 0 && visibleRows.length === 0) ? '' : 'none';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
filterBtns.forEach(function(btn) {
|
||||
btn.addEventListener('click', function() {
|
||||
filterBtns.forEach(function(b) { b.classList.remove('active'); });
|
||||
this.classList.add('active');
|
||||
currentFilter = this.dataset.filter;
|
||||
applyFilter();
|
||||
});
|
||||
});
|
||||
|
||||
// Apply filter on page load (shows only active by default)
|
||||
applyFilter();
|
||||
|
||||
// === TOGGLE HANDLER ===
|
||||
// When a toggle switch is flipped, POST to the server to update active status.
|
||||
// On success, update the row's data-active attribute and re-apply the filter
|
||||
// so the row moves to the correct section immediately.
|
||||
var toggleSwitches = document.querySelectorAll('.toggle-active');
|
||||
|
||||
toggleSwitches.forEach(function(switchEl) {
|
||||
switchEl.addEventListener('change', function() {
|
||||
const type = this.getAttribute('data-type');
|
||||
const id = this.getAttribute('data-id');
|
||||
const isChecked = this.checked;
|
||||
|
||||
fetch(`/toggle/${type}/${id}/`, {
|
||||
var type = this.getAttribute('data-type');
|
||||
var id = this.getAttribute('data-id');
|
||||
var isChecked = this.checked;
|
||||
var row = this.closest('.resource-row');
|
||||
|
||||
fetch('/toggle/' + type + '/' + id + '/', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'X-CSRFToken': '{{ csrf_token }}',
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
})
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error('Network response was not ok');
|
||||
}
|
||||
.then(function(response) {
|
||||
if (!response.ok) throw new Error('Network error');
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
if (data.status !== 'success') {
|
||||
// Revert if failed
|
||||
this.checked = !isChecked;
|
||||
.then(function(data) {
|
||||
if (data.status === 'success') {
|
||||
// Update the row's data-active and re-apply filter
|
||||
row.dataset.active = isChecked ? 'true' : 'false';
|
||||
applyFilter();
|
||||
} else {
|
||||
switchEl.checked = !isChecked;
|
||||
alert('Error updating status.');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
// Revert on error
|
||||
this.checked = !isChecked;
|
||||
.catch(function(error) {
|
||||
switchEl.checked = !isChecked;
|
||||
alert('Error updating status.');
|
||||
});
|
||||
});
|
||||
|
||||
@ -125,14 +125,12 @@ def index(request):
|
||||
'project', 'supervisor'
|
||||
).prefetch_related('workers').order_by('-date', '-id')[:5]
|
||||
|
||||
# Workers, projects, and teams for the Manage Resources tab
|
||||
# Split into active (shown by default) and inactive (collapsed section)
|
||||
active_workers = Worker.objects.filter(active=True).order_by('name')
|
||||
inactive_workers = Worker.objects.filter(active=False).order_by('name')
|
||||
active_projects = Project.objects.filter(active=True).order_by('name')
|
||||
inactive_projects = Project.objects.filter(active=False).order_by('name')
|
||||
active_teams = Team.objects.filter(active=True).order_by('name')
|
||||
inactive_teams = Team.objects.filter(active=False).order_by('name')
|
||||
# All workers, projects, and teams for the Manage Resources tab.
|
||||
# The template uses a JS filter bar (Active / Inactive / All) to show/hide
|
||||
# rows based on data-active attribute — defaults to showing only active items.
|
||||
workers = Worker.objects.all().order_by('name')
|
||||
projects = Project.objects.all().order_by('name')
|
||||
teams = Team.objects.all().order_by('name')
|
||||
|
||||
context = {
|
||||
'is_admin': True,
|
||||
@ -143,12 +141,9 @@ def index(request):
|
||||
'outstanding_by_project': outstanding_by_project,
|
||||
'this_week_logs': this_week_logs,
|
||||
'recent_activity': recent_activity,
|
||||
'active_workers': active_workers,
|
||||
'inactive_workers': inactive_workers,
|
||||
'active_projects': active_projects,
|
||||
'inactive_projects': inactive_projects,
|
||||
'active_teams': active_teams,
|
||||
'inactive_teams': inactive_teams,
|
||||
'workers': workers,
|
||||
'projects': projects,
|
||||
'teams': teams,
|
||||
}
|
||||
return render(request, 'core/index.html', context)
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user