Show active resources by default, collapse inactive + add project dates
Dashboard Manage Resources now shows only active workers/projects/teams by default. Inactive items are hidden behind a collapsible "Show X Inactive" button — faded at 50% opacity. Tab badges show active counts. Also adds start_date and end_date fields to Project model (optional). Dates display under the project name in the resource list. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
2aad9ac623
commit
47de74bde4
@ -12,7 +12,7 @@ class UserProfileAdmin(admin.ModelAdmin):
|
|||||||
|
|
||||||
@admin.register(Project)
|
@admin.register(Project)
|
||||||
class ProjectAdmin(admin.ModelAdmin):
|
class ProjectAdmin(admin.ModelAdmin):
|
||||||
list_display = ('name', 'active')
|
list_display = ('name', 'start_date', 'end_date', 'active')
|
||||||
list_filter = ('active',)
|
list_filter = ('active',)
|
||||||
search_fields = ('name', 'description')
|
search_fields = ('name', 'description')
|
||||||
filter_horizontal = ('supervisors',)
|
filter_horizontal = ('supervisors',)
|
||||||
|
|||||||
23
core/migrations/0003_add_project_start_end_dates.py
Normal file
23
core/migrations/0003_add_project_start_end_dates.py
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
# Generated by Django 5.2.7 on 2026-02-22 22:44
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('core', '0002_update_worker_id_numbers'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='project',
|
||||||
|
name='end_date',
|
||||||
|
field=models.DateField(blank=True, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='project',
|
||||||
|
name='start_date',
|
||||||
|
field=models.DateField(blank=True, null=True),
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -25,6 +25,8 @@ def save_user_profile(sender, instance, **kwargs):
|
|||||||
class Project(models.Model):
|
class Project(models.Model):
|
||||||
name = models.CharField(max_length=200)
|
name = models.CharField(max_length=200)
|
||||||
description = models.TextField(blank=True)
|
description = models.TextField(blank=True)
|
||||||
|
start_date = models.DateField(null=True, blank=True)
|
||||||
|
end_date = models.DateField(null=True, blank=True)
|
||||||
supervisors = models.ManyToManyField(User, related_name='assigned_projects')
|
supervisors = models.ManyToManyField(User, related_name='assigned_projects')
|
||||||
active = models.BooleanField(default=True)
|
active = models.BooleanField(default=True)
|
||||||
|
|
||||||
|
|||||||
@ -179,61 +179,148 @@
|
|||||||
<div class="card-body p-0">
|
<div class="card-body p-0">
|
||||||
<ul class="nav nav-tabs px-3 pt-3" id="resourceTabs" role="tablist">
|
<ul class="nav nav-tabs px-3 pt-3" id="resourceTabs" role="tablist">
|
||||||
<li class="nav-item" role="presentation">
|
<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</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 <span class="badge bg-secondary bg-opacity-50 ms-1">{{ active_workers|length }}</span>
|
||||||
|
</button>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item" role="presentation">
|
<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</button>
|
<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>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item" role="presentation">
|
<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</button>
|
<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>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<div class="tab-content" id="resourceTabsContent">
|
<div class="tab-content" id="resourceTabsContent">
|
||||||
<!-- Workers Tab -->
|
|
||||||
|
{# === WORKERS TAB === #}
|
||||||
<div class="tab-pane fade show active" id="workers" role="tabpanel">
|
<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;">
|
<ul class="list-group list-group-flush" style="max-height: 300px; overflow-y: auto;">
|
||||||
{% for item in workers %}
|
{% for item in active_workers %}
|
||||||
<li class="list-group-item d-flex justify-content-between align-items-center">
|
<li class="list-group-item d-flex justify-content-between align-items-center">
|
||||||
{{ item.name }}
|
{{ item.name }}
|
||||||
<div class="form-check form-switch">
|
<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 %}>
|
<input class="form-check-input toggle-active" type="checkbox" role="switch" data-type="worker" data-id="{{ item.id }}" checked>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
{% empty %}
|
{% empty %}
|
||||||
<li class="list-group-item text-muted">No workers found.</li>
|
<li class="list-group-item text-muted">No active workers.</li>
|
||||||
{% endfor %}
|
{% 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>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<!-- Projects Tab -->
|
|
||||||
|
{# === PROJECTS TAB === #}
|
||||||
<div class="tab-pane fade" id="projects" role="tabpanel">
|
<div class="tab-pane fade" id="projects" role="tabpanel">
|
||||||
<ul class="list-group list-group-flush" style="max-height: 300px; overflow-y: auto;">
|
<ul class="list-group list-group-flush" style="max-height: 300px; overflow-y: auto;">
|
||||||
{% for item in projects %}
|
{% for item in active_projects %}
|
||||||
<li class="list-group-item d-flex justify-content-between align-items-center">
|
<li class="list-group-item d-flex justify-content-between align-items-center">
|
||||||
{{ item.name }}
|
<div>
|
||||||
|
{{ item.name }}
|
||||||
|
{% if item.start_date or item.end_date %}
|
||||||
|
<br><small class="text-muted">
|
||||||
|
{{ item.start_date|date:"j M Y"|default:"?" }} – {{ item.end_date|date:"j M Y"|default:"ongoing" }}
|
||||||
|
</small>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
<div class="form-check form-switch">
|
<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 %}>
|
<input class="form-check-input toggle-active" type="checkbox" role="switch" data-type="project" data-id="{{ item.id }}" checked>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
{% empty %}
|
{% empty %}
|
||||||
<li class="list-group-item text-muted">No projects found.</li>
|
<li class="list-group-item text-muted">No active projects.</li>
|
||||||
{% endfor %}
|
{% 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">
|
||||||
|
<div>
|
||||||
|
{{ item.name }}
|
||||||
|
{% if item.start_date or item.end_date %}
|
||||||
|
<br><small class="text-muted">
|
||||||
|
{{ item.start_date|date:"j M Y"|default:"?" }} – {{ item.end_date|date:"j M Y"|default:"?" }}
|
||||||
|
</small>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<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>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<!-- Teams Tab -->
|
|
||||||
|
{# === TEAMS TAB === #}
|
||||||
<div class="tab-pane fade" id="teams" role="tabpanel">
|
<div class="tab-pane fade" id="teams" role="tabpanel">
|
||||||
<ul class="list-group list-group-flush" style="max-height: 300px; overflow-y: auto;">
|
<ul class="list-group list-group-flush" style="max-height: 300px; overflow-y: auto;">
|
||||||
{% for item in teams %}
|
{% for item in active_teams %}
|
||||||
<li class="list-group-item d-flex justify-content-between align-items-center">
|
<li class="list-group-item d-flex justify-content-between align-items-center">
|
||||||
{{ item.name }}
|
{{ item.name }}
|
||||||
<div class="form-check form-switch">
|
<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 %}>
|
<input class="form-check-input toggle-active" type="checkbox" role="switch" data-type="team" data-id="{{ item.id }}" checked>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
{% empty %}
|
{% empty %}
|
||||||
<li class="list-group-item text-muted">No teams found.</li>
|
<li class="list-group-item text-muted">No active teams.</li>
|
||||||
{% endfor %}
|
{% 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>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -125,10 +125,14 @@ def index(request):
|
|||||||
'project', 'supervisor'
|
'project', 'supervisor'
|
||||||
).prefetch_related('workers').order_by('-date', '-id')[:5]
|
).prefetch_related('workers').order_by('-date', '-id')[:5]
|
||||||
|
|
||||||
# All workers, projects, and teams for the Manage Resources tab
|
# Workers, projects, and teams for the Manage Resources tab
|
||||||
workers = Worker.objects.all().order_by('name')
|
# Split into active (shown by default) and inactive (collapsed section)
|
||||||
projects = Project.objects.all().order_by('name')
|
active_workers = Worker.objects.filter(active=True).order_by('name')
|
||||||
teams = Team.objects.all().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')
|
||||||
|
|
||||||
context = {
|
context = {
|
||||||
'is_admin': True,
|
'is_admin': True,
|
||||||
@ -139,9 +143,12 @@ def index(request):
|
|||||||
'outstanding_by_project': outstanding_by_project,
|
'outstanding_by_project': outstanding_by_project,
|
||||||
'this_week_logs': this_week_logs,
|
'this_week_logs': this_week_logs,
|
||||||
'recent_activity': recent_activity,
|
'recent_activity': recent_activity,
|
||||||
'workers': workers,
|
'active_workers': active_workers,
|
||||||
'projects': projects,
|
'inactive_workers': inactive_workers,
|
||||||
'teams': teams,
|
'active_projects': active_projects,
|
||||||
|
'inactive_projects': inactive_projects,
|
||||||
|
'active_teams': active_teams,
|
||||||
|
'inactive_teams': inactive_teams,
|
||||||
}
|
}
|
||||||
return render(request, 'core/index.html', context)
|
return render(request, 'core/index.html', context)
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user