Dashboard view per Client
This commit is contained in:
parent
cedb637763
commit
e7935eb585
BIN
assets/pasted-20260122-091742-8812311a.png
Normal file
BIN
assets/pasted-20260122-091742-8812311a.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 202 KiB |
Binary file not shown.
@ -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">
|
||||
|
||||
@ -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')
|
||||
|
||||
BIN
staticfiles/pasted-20260122-091742-8812311a.png
Normal file
BIN
staticfiles/pasted-20260122-091742-8812311a.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 202 KiB |
Loading…
x
Reference in New Issue
Block a user