From 76b0b02933a89a8981668b6911ad9490412a2a5e Mon Sep 17 00:00:00 2001 From: Laurent Date: Thu, 9 Oct 2025 15:36:55 +0200 Subject: [PATCH] Adds prospects dashboard --- biz/admin.py | 51 +++ biz/templates/admin/biz/dashboard.html | 302 ++++++++++++++++++ .../admin/biz/prospect/change_list.html | 1 + 3 files changed, 354 insertions(+) create mode 100644 biz/templates/admin/biz/dashboard.html diff --git a/biz/admin.py b/biz/admin.py index f35bd94..e0e0aee 100644 --- a/biz/admin.py +++ b/biz/admin.py @@ -6,6 +6,7 @@ from django.shortcuts import render, redirect from django.contrib.auth import get_user_model from django.utils.html import format_html from django.core.mail import send_mail +from django.db.models import Q, Max, Subquery, OuterRef import csv import io @@ -151,12 +152,62 @@ class ProspectAdmin(SyncedObjectAdmin): def get_urls(self): urls = super().get_urls() custom_urls = [ + path('dashboard/', self.admin_site.admin_view(self.dashboard), name='biz_dashboard'), path('import_file/', self.admin_site.admin_view(self.import_file), name='import_file'), path('import_app_users/', self.admin_site.admin_view(self.import_app_users), name='import_app_users'), path('cleanup/', self.admin_site.admin_view(self.cleanup), name='cleanup'), ] return custom_urls + urls + def dashboard(self, request): + """ + Dashboard view showing prospects organized by status columns + """ + # Get filter parameter - if 'my' is true, filter by current user + filter_my = request.GET.get('my', 'false') == 'true' + + # Base queryset + base_queryset = Prospect.objects.select_related().prefetch_related('entities', 'activities') + + # Apply user filter if requested + if filter_my: + base_queryset = base_queryset.filter(related_user=request.user) + + # Helper function to get prospects by status + def get_prospects_by_status(statuses): + # Get the latest activity status for each prospect + latest_activity = Activity.objects.filter( + prospects=OuterRef('pk'), + status__isnull=False + ).order_by('-creation_date') + + prospects = base_queryset.annotate( + latest_status=Subquery(latest_activity.values('status')[:1]) + ).filter( + latest_status__in=statuses + ).order_by('last_update') + + return prospects + + # Get prospects for each column + should_test_prospects = get_prospects_by_status([Status.SHOULD_TEST]) + testing_prospects = get_prospects_by_status([Status.TESTING]) + responded_prospects = get_prospects_by_status([Status.RESPONDED]) + others_prospects = get_prospects_by_status([Status.INBOUND, Status.SHOULD_BUY]) + + context = { + 'title': 'CRM Dashboard', + 'should_test_prospects': should_test_prospects, + 'testing_prospects': testing_prospects, + 'responded_prospects': responded_prospects, + 'others_prospects': others_prospects, + 'filter_my': filter_my, + 'opts': self.model._meta, + 'has_view_permission': self.has_view_permission(request), + } + + return render(request, 'admin/biz/dashboard.html', context) + def cleanup(self, request): Entity.objects.all().delete() Prospect.objects.all().delete() diff --git a/biz/templates/admin/biz/dashboard.html b/biz/templates/admin/biz/dashboard.html new file mode 100644 index 0000000..2ba1ab1 --- /dev/null +++ b/biz/templates/admin/biz/dashboard.html @@ -0,0 +1,302 @@ +{% extends "admin/base_site.html" %} +{% load static %} + +{% block extrahead %} +{{ block.super }} + +{% endblock %} + +{% block content %} +
+ +
+ +
+ + +
+
+ SHOULD TEST + ({{ should_test_prospects.count }}) +
+ {% if should_test_prospects %} + + + + + + + + + + + {% for prospect in should_test_prospects %} + + + + + + + {% endfor %} + +
NameEntityPhoneLast Update
+ + {{ prospect.first_name|default:"" }} {{ prospect.last_name|default:"" }} + + {{ prospect.entity_names }}{{ prospect.phone|default:"-" }}{{ prospect.last_update|date:"d/m/Y H:i" }}
+ {% else %} +
No prospects
+ {% endif %} +
+ + +
+
+ TESTING + ({{ testing_prospects.count }}) +
+ {% if testing_prospects %} + + + + + + + + + + + {% for prospect in testing_prospects %} + + + + + + + {% endfor %} + +
NameEntityPhoneLast Update
+ + {{ prospect.first_name|default:"" }} {{ prospect.last_name|default:"" }} + + {{ prospect.entity_names }}{{ prospect.phone|default:"-" }}{{ prospect.last_update|date:"d/m/Y H:i" }}
+ {% else %} +
No prospects
+ {% endif %} +
+ + +
+
+ OTHERS + ({{ others_prospects.count }}) +
+ {% if others_prospects %} + + + + + + + + + + + + {% for prospect in others_prospects %} + + + + + + + + {% endfor %} + +
NameEntityPhoneLast UpdateStatus
+ + {{ prospect.first_name|default:"" }} {{ prospect.last_name|default:"" }} + + {{ prospect.entity_names }}{{ prospect.phone|default:"-" }}{{ prospect.last_update|date:"d/m/Y H:i" }}{{ prospect.current_status }}
+ {% else %} +
No prospects
+ {% endif %} +
+ + +
+
+ RESPONDED + ({{ responded_prospects.count }}) +
+ {% if responded_prospects %} + + + + + + + + + + + {% for prospect in responded_prospects %} + + + + + + + {% endfor %} + +
NameEntityPhoneLast Update
+ + {{ prospect.first_name|default:"" }} {{ prospect.last_name|default:"" }} + + {{ prospect.entity_names }}{{ prospect.phone|default:"-" }}{{ prospect.last_update|date:"d/m/Y H:i" }}
+ {% else %} +
No prospects
+ {% endif %} +
+ +
+ + +{% endblock %} diff --git a/biz/templates/admin/biz/prospect/change_list.html b/biz/templates/admin/biz/prospect/change_list.html index cdbb5d8..517750e 100644 --- a/biz/templates/admin/biz/prospect/change_list.html +++ b/biz/templates/admin/biz/prospect/change_list.html @@ -3,6 +3,7 @@ {% block object-tools-items %} {{ block.super }}
  • + Dashboard Import Import App Users