Dashboard view per Client

This commit is contained in:
Flatlogic Bot 2026-01-22 12:56:34 +00:00
parent cedb637763
commit e7935eb585
5 changed files with 113 additions and 51 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 202 KiB

View File

@ -130,7 +130,7 @@
<div class="row g-4 mb-5">
<div class="col-md-12">
<div class="stat-card">
<h6 class="mb-3">Incomplete Folder Breakdown</h6>
<h6 class="mb-3">Incomplete Folder Breakdown (Overall)</h6>
{% if incomplete_folder_breakdown %}
<ul class="list-group list-group-flush">
{% for item in incomplete_folder_breakdown %}
@ -149,45 +149,57 @@
</div>
</div>
<div class="card border-0 shadow-sm mb-5">
<div class="card-header bg-white py-3 border-0 d-flex justify-content-between align-items-center">
<h5 class="mb-0">Jobs by Client</h5>
</div>
<div class="card-body">
{% if jobs_by_client %}
{% for client_name, client_jobs in jobs_by_client.items %}
<h6 class="mt-3 mb-2">{{ client_name }} ({{ client_jobs|length }} Jobs)</h6>
<div class="table-responsive mb-4">
<table class="table align-middle mb-0 table-sm">
<thead class="bg-light">
<tr>
<th class="border-0 px-4">Job Ref</th>
<th class="border-0">Address</th>
<th class="border-0">Status</th>
<th class="border-0 text-end px-4">Actions</th>
</tr>
</thead>
<tbody>
{% for job in client_jobs %}
<tr>
<td class="px-4 fw-bold text-primary">{{ job.job_ref }}</td>
<td>{{ job.address_line_1 }}, {{ job.postcode }}</td>
<td><span class="badge bg-light text-dark border">{{ job.status.name }}</span></td>
<td class="text-end px-4">
<a href="{% url 'job_detail' job.pk %}" class="btn btn-sm btn-outline-secondary">View</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{# Client-specific breakdown #}
{% if client_stats %}
{% for client_stat in client_stats %}
<div class="card border-0 shadow-sm mb-5">
<div class="card-header bg-white py-3 border-0">
<h5 class="mb-0">{{ client_stat.client_name }}</h5>
</div>
<div class="card-body">
<div class="row g-4 mb-4">
<div class="col-md-4">
<div class="stat-card">
<div class="stat-value text-primary">{{ client_stat.total_jobs }}</div>
<div class="stat-label">Total Jobs</div>
</div>
</div>
{% endfor %}
{% else %}
<p class="text-muted">No jobs found or clients assigned.</p>
{% endif %}
<div class="col-md-4">
<div class="stat-card">
<div class="stat-value text-warning">{{ client_stat.incomplete_jobs }}</div>
<div class="stat-label">Jobs with Incomplete Folders</div>
</div>
</div>
<div class="col-md-4">
<div class="stat-card">
<h6 class="mb-3">Incomplete Folders</h6>
{% if client_stat.incomplete_folder_breakdown %}
<ul class="list-group list-group-flush">
{% for folder_item in client_stat.incomplete_folder_breakdown %}
<li class="list-group-item d-flex justify-content-between align-items-center px-0 py-1">
<a href="{% url 'job_list' %}?missing_folder_id={{ folder_item.folder_id }}" class="text-decoration-none text-dark">
{{ folder_item.folder_name }}
</a>
<span class="badge bg-danger rounded-pill">{{ folder_item.missing_count }}</span>
</li>
{% endfor %}
</ul>
{% else %}
<p class="text-muted">All folders complete.</p>
{% endif %}
</div>
</div>
</div>
</div>
</div>
</div>
{% endfor %}
{% else %}
<div class="card border-0 shadow-sm mb-5">
<div class="card-body">
<p class="text-muted">No client-specific job data available.</p>
</div>
</div>
{% endif %}
<div class="card border-0 shadow-sm">
<div class="card-header bg-white py-3 border-0 d-flex justify-content-between align-items-center">

View File

@ -10,7 +10,7 @@ from django.contrib.auth.forms import UserCreationForm, AuthenticationForm
from django.contrib.auth.decorators import login_required
from django.contrib import messages
from django.db import transaction
from django.db.models import Count
from django.db.models import Count, Q
from django.http import HttpResponse
from django.core.mail import send_mail
from django.conf import settings
@ -114,7 +114,7 @@ def dashboard(request):
return redirect('company_setup')
company = profile.company
jobs = Job.objects.filter(company=company).select_related('client')
jobs = Job.objects.filter(company=company).select_related('client', 'status')
total_jobs = jobs.count()
@ -129,8 +129,7 @@ def dashboard(request):
for folder in required_folders:
# Count jobs where this specific folder is NOT completed
missing_count = Job.objects.filter(
company=company,
missing_count = jobs.filter(
folder_completions__folder=folder,
folder_completions__is_completed=False
).distinct().count()
@ -141,13 +140,62 @@ def dashboard(request):
'missing_count': missing_count
})
# NEW LOGIC: Group jobs by client
jobs_by_client = {}
for job in jobs:
client_name = job.client.name if job.client else "No Client Assigned"
if client_name not in jobs_by_client:
jobs_by_client[client_name] = []
jobs_by_client[client_name].append(job)
# NEW LOGIC: Group jobs by client with detailed statistics
client_stats = []
clients = company.clients.all()
# Add a "No Client Assigned" entry first if there are jobs without a client
jobs_no_client = jobs.filter(client__isnull=True)
if jobs_no_client.exists():
no_client_total_jobs = jobs_no_client.count()
no_client_incomplete_jobs = jobs_no_client.filter(folder_completions__is_completed=False).distinct().count()
no_client_incomplete_folder_breakdown = []
for folder in required_folders:
missing_count = jobs_no_client.filter(
folder_completions__folder=folder,
folder_completions__is_completed=False
).distinct().count()
if missing_count > 0:
no_client_incomplete_folder_breakdown.append({
'folder_name': folder.name,
'missing_count': missing_count,
'folder_id': folder.id
})
client_stats.append({
'client_name': "No Client Assigned",
'total_jobs': no_client_total_jobs,
'incomplete_jobs': no_client_incomplete_jobs,
'incomplete_folder_breakdown': no_client_incomplete_folder_breakdown
})
for client in clients:
client_jobs = jobs.filter(client=client)
total_client_jobs = client_jobs.count()
if total_client_jobs > 0: # Only include clients with jobs
client_incomplete_jobs = client_jobs.filter(folder_completions__is_completed=False).distinct().count()
client_incomplete_folder_breakdown = []
for folder in required_folders:
missing_count = client_jobs.filter(
folder_completions__folder=folder,
folder_completions__is_completed=False
).distinct().count()
if missing_count > 0:
client_incomplete_folder_breakdown.append({
'folder_name': folder.name,
'missing_count': missing_count,
'folder_id': folder.id
})
client_stats.append({
'client_name': client.name,
'total_jobs': total_client_jobs,
'incomplete_jobs': client_incomplete_jobs,
'incomplete_folder_breakdown': client_incomplete_folder_breakdown
})
context = {
'company': company,
@ -156,7 +204,7 @@ def dashboard(request):
'jobs_with_incomplete_folders': jobs_with_incomplete_folders,
'jobs': jobs.order_by('-created_at')[:5],
'incomplete_folder_breakdown': incomplete_folder_breakdown,
'jobs_by_client': jobs_by_client,
'client_stats': client_stats, # Replaced jobs_by_client
}
return render(request, 'core/dashboard.html', context)
@ -277,6 +325,8 @@ def job_delete(request, pk):
job = get_object_or_404(Job, pk=pk, company=company)
if request.method == 'POST':
# You might want to add a check here if there are any jobs associated with this client
# If jobs are associated, you might want to reassign them or prevent deletion.
job_ref = job.job_ref
job.delete()
messages.success(request, f"Job {job_ref} deleted.")
@ -781,4 +831,4 @@ def user_delete(request, pk):
user_to_delete.delete()
messages.success(request, f"User {username} deleted.")
return redirect('user_list')
return redirect('user_list')

Binary file not shown.

After

Width:  |  Height:  |  Size: 202 KiB