diff --git a/crm/filters.py b/crm/filters.py index 002ac07..97e433b 100644 --- a/crm/filters.py +++ b/crm/filters.py @@ -1,14 +1,23 @@ import django_filters +from django.db.models import Q from .models import Event, Status, Prospect + class ProspectFilter(django_filters.FilterSet): - zip_code = django_filters.CharFilter(lookup_expr='icontains') + zip_code = django_filters.CharFilter(lookup_expr='istartswith', label='Code postal') events = django_filters.ModelMultipleChoiceFilter( queryset=Event.objects.all(), field_name='events', ) + city = django_filters.CharFilter(lookup_expr='icontains', label='Ville') + name = django_filters.CharFilter(method='filter_name', label='Nom') + + def filter_name(self, queryset, name, value): + return queryset.filter( + Q(first_name__icontains=value) | Q(last_name__icontains=value) | Q(entity_name__icontains=value) + ) class Meta: model = Prospect - fields = ['city', 'events'] + fields = ['name', 'city', 'events', 'zip_code'] diff --git a/crm/forms.py b/crm/forms.py index efd945a..10edf6c 100644 --- a/crm/forms.py +++ b/crm/forms.py @@ -12,8 +12,11 @@ class SmallTextArea(forms.Textarea): }) super().__init__(*args, **kwargs) -class CSVImportForm(forms.Form): - csv_file = forms.FileField() +class ProspectForm(forms.ModelForm): + class Meta: + model = Prospect + fields = ['entity_name', 'first_name', 'last_name', 'email', + 'phone', 'address', 'zip_code', 'city'] class BulkEmailForm(forms.Form): prospects = forms.ModelMultipleChoiceField( @@ -38,3 +41,6 @@ class EventForm(forms.ModelForm): widgets = { 'date': forms.DateTimeInput(attrs={'type': 'datetime-local'}), } + +class CSVImportForm(forms.Form): + csv_file = forms.FileField() diff --git a/crm/models.py b/crm/models.py index 8baa4e9..5028da6 100644 --- a/crm/models.py +++ b/crm/models.py @@ -41,6 +41,9 @@ class Prospect(models.Model): ("view_prospects", "Can view prospects"), ] + def full_name(self): + return f'{self.first_name} {self.last_name}' + def __str__(self): return ' - '.join(filter(None, [self.entity_name, self.first_name, self.last_name, f"({self.email})"])) diff --git a/crm/static/crm/js/prospects.js b/crm/static/crm/js/prospects.js new file mode 100644 index 0000000..ca4a579 --- /dev/null +++ b/crm/static/crm/js/prospects.js @@ -0,0 +1,6 @@ +document.getElementById("select-all").addEventListener("change", function () { + const checkboxes = document.getElementsByName("selected_prospects"); + for (let checkbox of checkboxes) { + checkbox.checked = this.checked; + } +}); diff --git a/crm/static/js/prospect_list.js b/crm/static/js/prospect_list.js deleted file mode 100644 index 9378e4a..0000000 --- a/crm/static/js/prospect_list.js +++ /dev/null @@ -1,10 +0,0 @@ -document.addEventListener("DOMContentLoaded", function () { - const selectAll = document.getElementById("select-all"); - const prospectCheckboxes = document.getElementsByName("selected_prospects"); - - selectAll.addEventListener("change", function () { - prospectCheckboxes.forEach((checkbox) => { - checkbox.checked = selectAll.checked; - }); - }); -}); diff --git a/crm/templates/crm/base.html b/crm/templates/crm/base.html index 14cf3de..51e4079 100644 --- a/crm/templates/crm/base.html +++ b/crm/templates/crm/base.html @@ -37,6 +37,8 @@ + {% block extra_js %}{% endblock %} + diff --git a/crm/templates/crm/prospect_form.html b/crm/templates/crm/prospect_form.html new file mode 100644 index 0000000..334e8bf --- /dev/null +++ b/crm/templates/crm/prospect_form.html @@ -0,0 +1,17 @@ +{% extends "crm/base.html" %} + +{% block head_title %}{{ first_title }}{% endblock %} +{% block first_title %}{{ first_title }}{% endblock %} +{% block second_title %}{{ second_title }}{% endblock %} + +{% block content %} + +
+ {% csrf_token %} + {{ form.as_p }} + +
+ +{% endblock %} diff --git a/crm/templates/crm/prospect_list.html b/crm/templates/crm/prospect_list.html index f82349b..0068867 100644 --- a/crm/templates/crm/prospect_list.html +++ b/crm/templates/crm/prospect_list.html @@ -1,14 +1,22 @@ {% extends "crm/base.html" %} +{% load static %} + {% block content %}

Prospects

-
-
-
- {{ filter.form }} -
+
+
+ + + {% for field in filter.form %} +
+ + {{ field }} +
+ {% endfor %} +
Clear
@@ -16,10 +24,12 @@
-
+ + + {{ filter.qs|length }} résultats
@@ -30,27 +40,33 @@ - + - {% for prospect in prospects %} + {% for prospect in filter.qs %} - + {% endfor %} @@ -59,3 +75,7 @@ {% endblock %} + +{% block extra_js %} + +{% endblock %} diff --git a/crm/urls.py b/crm/urls.py index 5a675b8..adbd5e6 100644 --- a/crm/urls.py +++ b/crm/urls.py @@ -4,12 +4,14 @@ from . import views app_name = 'crm' urlpatterns = [ - path('', views.EventListView.as_view(), name='planned_events'),path('', views.EventListView.as_view(), name='planned-events'), + path('', views.EventListView.as_view(), name='planned_events'),path('', views.EventListView.as_view(), name='events'), path('events/add/', views.EventCreateView.as_view(), name='add-event'), + path('events/add//', views.EventCreateView.as_view(), name='add-event-for-prospect'), path('events//edit/', views.EditEventView.as_view(), name='edit_event'), path('events//start/', views.StartEventView.as_view(), name='start_event'), path('prospects/', views.ProspectListView.as_view(), name='prospect-list'), - path('add-prospect/', views.add_prospect, name='add-prospect'), + path('prospect/add/', views.prospect_form, name='add-prospect'), + path('prospect//edit/', views.prospect_form, name='edit-prospect'), path('prospects/import/', views.CSVImportView.as_view(), name='csv-import'), path('email/send/', views.SendBulkEmailView.as_view(), name='send-bulk-email'), ] diff --git a/crm/views.py b/crm/views.py index 0ee8c43..5cd72c6 100644 --- a/crm/views.py +++ b/crm/views.py @@ -17,7 +17,7 @@ from django.db import IntegrityError from .models import Event, Prospect, EmailTracker, EmailCampaign, EventType from .filters import ProspectFilter -from .forms import CSVImportForm, BulkEmailForm, EventForm +from .forms import ProspectForm, CSVImportForm, BulkEmailForm, EventForm from .mixins import CRMAccessMixin @@ -26,38 +26,67 @@ from io import TextIOWrapper from datetime import datetime @permission_required('crm.view_crm', raise_exception=True) -def add_prospect(request): - if request.method == 'POST': - entity_name = request.POST.get('entity_name') - first_name = request.POST.get('first_name') - last_name = request.POST.get('last_name') - email = request.POST.get('email') - phone = request.POST.get('phone') - address = request.POST.get('address') - zip_code = request.POST.get('zip_code') - city = request.POST.get('city') - # region = request.POST.get('region') - - try: - prospect = Prospect.objects.create( - entity_name=entity_name, - first_name=first_name, - last_name=last_name, - email=email, - phone=phone, - address=address, - zip_code=zip_code, - city=city, - # region=region, - created_by=request.user, - modified_by=request.user - ) - messages.success(request, f'Prospect {name} has been added successfully!') - return redirect('crm:events') # or wherever you want to redirect after success - except Exception as e: - messages.error(request, f'Error adding prospect: {str(e)}') +def prospect_form(request, pk=None): + # Get the prospect instance if pk is provided (edit mode) + prospect = get_object_or_404(Prospect, pk=pk) if pk else None - return render(request, 'crm/add_prospect.html') + if request.method == 'POST': + form = ProspectForm(request.POST, instance=prospect) + if form.is_valid(): + prospect = form.save(commit=False) + if not pk: # New prospect + prospect.created_by = request.user + prospect.modified_by = request.user + prospect.save() + + action = 'updated' if pk else 'added' + messages.success(request, + f'Prospect {prospect.entity_name} has been {action} successfully!') + return redirect('crm:events') + else: + form = ProspectForm(instance=prospect) + + context = { + 'form': form, + 'is_edit': prospect is not None, + 'first_title': prospect.entity_name if prospect else 'Add Prospect', + 'second_title': prospect.full_name() if prospect else None + } + return render(request, 'crm/prospect_form.html', context) + +# @permission_required('crm.view_crm', raise_exception=True) +# def add_prospect(request): +# if request.method == 'POST': +# entity_name = request.POST.get('entity_name') +# first_name = request.POST.get('first_name') +# last_name = request.POST.get('last_name') +# email = request.POST.get('email') +# phone = request.POST.get('phone') +# address = request.POST.get('address') +# zip_code = request.POST.get('zip_code') +# city = request.POST.get('city') +# # region = request.POST.get('region') + +# try: +# prospect = Prospect.objects.create( +# entity_name=entity_name, +# first_name=first_name, +# last_name=last_name, +# email=email, +# phone=phone, +# address=address, +# zip_code=zip_code, +# city=city, +# # region=region, +# created_by=request.user, +# modified_by=request.user +# ) +# messages.success(request, f'Prospect {name} has been added successfully!') +# return redirect('crm:events') # or wherever you want to redirect after success +# except Exception as e: +# messages.error(request, f'Error adding prospect: {str(e)}') + +# return render(request, 'crm/add_prospect.html') class EventCreateView(CRMAccessMixin, CreateView): model = Event @@ -65,6 +94,13 @@ class EventCreateView(CRMAccessMixin, CreateView): template_name = 'crm/event_form.html' success_url = reverse_lazy('crm:planned_events') + def get_initial(self): + initial = super().get_initial() + prospect_id = self.kwargs.get('prospect_id') + if prospect_id: + initial['prospects'] = [prospect_id] + return initial + def form_valid(self, form): form.instance.created_by = self.request.user form.instance.modified_by = self.request.user @@ -131,6 +167,9 @@ class ProspectListView(CRMAccessMixin, ListView): context_object_name = 'prospects' filterset_class = ProspectFilter + def get_queryset(self): + return super().get_queryset().prefetch_related('prospectstatus_set__status') + def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context['filter'] = self.filterset_class( @@ -139,7 +178,6 @@ class ProspectListView(CRMAccessMixin, ListView): ) return context - class CSVImportView(CRMAccessMixin, FormView): template_name = 'crm/csv_import.html' form_class = CSVImportForm diff --git a/tournaments/static/tournaments/css/style.css b/tournaments/static/tournaments/css/style.css index 7f0e7a1..45127ff 100644 --- a/tournaments/static/tournaments/css/style.css +++ b/tournaments/static/tournaments/css/style.css @@ -714,3 +714,44 @@ h-margin { .right-content { margin-left: auto; } + +/* CRM form */ +.filter-form { + display: flex; + flex-wrap: wrap; + gap: 10px; + align-items: center; + margin-bottom: 20px; +} + +.filter-group { + display: flex; + align-items: center; +} + +.filter-label { + margin-right: 5px; + font-size: 0.9em; +} + +.filter-buttons { + display: flex; + gap: 5px; +} + +.btn { + padding: 5px 10px; + border: 1px solid #ccc; + border-radius: 4px; + cursor: pointer; +} + +.btn-primary { + background-color: #007bff; + color: white; +} + +.btn-secondary { + background-color: #6c757d; + color: white; +}
Prénom Nom EmailTéléphoneVille Statut Actions
{{ prospect.entity_name }} {{ prospect.first_name }} {{ prospect.last_name }} {{ prospect.email }}{{ prospect.phone }}{{ prospect.city }} ({{ prospect.zip_code }}) {% for status in prospect.prospectstatus_set.all %} {{ status.status.name }} {% endfor %} - + + + + + + +