Skip to content

Commit 47cf4e0

Browse files
Merge pull request #2025 from betagouv/feat/rework-crm-user-list
✅ Refonte de la liste des utilisteurs.rices dans le crm
2 parents 57aa082 + 4fb9615 commit 47cf4e0

13 files changed

Lines changed: 245 additions & 121 deletions

File tree

recoco/apps/conversations/templates/conversations/messages/fragments/resource_preview_panel.html

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,7 @@
2929
<!-- Scrollable content area -->
3030
<div class="resource-preview-panel__body">
3131
{% include "conversations/messages/fragments/preview_panel_message.html" %}
32-
<iframe class="w-100 d-flex flex-column rounded"
33-
style="border: none;
34-
overflow: hidden"
32+
<iframe class="w-100 d-flex flex-column rounded border-0 overflow-hidden"
3533
scrolling="no"
3634
@load="$el.style.height = $el.contentDocument.body.scrollHeight + 'px'"
3735
:src="resourcePreviewUrl($store.resourcePreviewPanel.recommendation.resource.id, $store.resourcePreviewPanel.recommendation.id)"></iframe>

recoco/apps/crm/filters.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,9 @@ class UserFilter(django_filters.FilterSet):
4242

4343
ROLE_CHOICES = [
4444
(1, "Conseiller·ère"),
45-
(2, "Équipe"),
45+
(2, "Staff"),
4646
(3, "Administrateur·rice"),
47-
(4, "Autres"),
47+
(4, "Demandeur"),
4848
]
4949

5050
username = django_filters.CharFilter(
@@ -64,6 +64,7 @@ class UserFilter(django_filters.FilterSet):
6464
departments = django_filters.ModelMultipleChoiceFilter(
6565
label="Départements conseillés",
6666
field_name="profile__departments",
67+
to_field_name="code",
6768
queryset=geomatics_models.Department.objects.all(),
6869
)
6970

recoco/apps/crm/templates/crm/user_list.html

Lines changed: 145 additions & 102 deletions
Large diffs are not rendered by default.

recoco/apps/crm/views.py

Lines changed: 65 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
from django.contrib import messages
2424
from django.contrib.auth.decorators import login_required
2525
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
26-
from django.contrib.auth.models import User
26+
from django.contrib.auth.models import Group, User
2727
from django.contrib.contenttypes.models import ContentType
2828
from django.contrib.contenttypes.prefetch import GenericPrefetch
2929
from django.contrib.sites.models import Site
@@ -34,6 +34,7 @@
3434
from django.db import transaction
3535
from django.db.models import (
3636
Count,
37+
Exists,
3738
ExpressionWrapper,
3839
F,
3940
FloatField,
@@ -66,7 +67,12 @@
6667
from recoco.apps.geomatics.serializers import RegionSerializer
6768
from recoco.apps.home import models as home_models
6869
from recoco.apps.onboarding import utils as onboarding_utils
69-
from recoco.apps.projects.models import Project, Topic
70+
from recoco.apps.projects.models import (
71+
Project,
72+
ProjectMember,
73+
ProjectSwitchtender,
74+
Topic,
75+
)
7076
from recoco.apps.reminders import models as reminders_models
7177
from recoco.apps.resources.models import Category
7278
from recoco.apps.tasks.models import Task
@@ -477,14 +483,65 @@ def organization_details(request, organization_id):
477483
def user_list(request):
478484
has_perm_or_403(request.user, "use_crm", request.site)
479485

480-
# filtered users
481-
users = filters.UserFilter(
482-
request.GET,
483-
queryset=User.objects.filter(
484-
profile__sites=request.site, profile__deleted__isnull=True
485-
).prefetch_related("profile__organization"),
486+
site = request.site
487+
advisor_group_name = make_group_name_for_site("advisor", site)
488+
staff_group_name = make_group_name_for_site("staff", site)
489+
admin_group_name = make_group_name_for_site("admin", site)
490+
selected_departments = request.GET.getlist("departments")
491+
492+
base_qs = (
493+
User.objects.filter(profile__sites=site, profile__deleted__isnull=True)
494+
.prefetch_related("profile__organization")
495+
.annotate(
496+
projects_count=(
497+
Subquery(
498+
Project.objects.filter(
499+
Q(
500+
pk__in=Subquery(
501+
ProjectMember.objects.filter(
502+
member_id=OuterRef(OuterRef("id"))
503+
).values("project_id")
504+
)
505+
)
506+
| Q(
507+
pk__in=Subquery(
508+
ProjectSwitchtender.objects.filter(
509+
switchtender_id=OuterRef(OuterRef("id"))
510+
).values("project_id")
511+
)
512+
)
513+
)
514+
.distinct()
515+
.annotate(count=Func(F("id"), function="Count"))
516+
.values("count")
517+
)
518+
),
519+
is_advisor=Exists(
520+
Group.objects.filter(name=advisor_group_name, user=OuterRef("pk"))
521+
),
522+
is_staff_member=Exists(
523+
Group.objects.filter(name=staff_group_name, user=OuterRef("pk"))
524+
),
525+
is_admin=Exists(
526+
Group.objects.filter(name=admin_group_name, user=OuterRef("pk"))
527+
),
528+
)
486529
)
487530

531+
users = filters.UserFilter(request.GET, queryset=base_qs)
532+
533+
has_active_filter = any(
534+
[
535+
request.GET.get("username"),
536+
request.GET.get("role"),
537+
selected_departments,
538+
request.GET.get("inactive"),
539+
]
540+
)
541+
542+
max_users_without_filter = 25
543+
display_qs = users.qs if has_active_filter else users.qs[:max_users_without_filter]
544+
488545
# required by default on crm
489546
search_form = forms.CRMSearchForm()
490547

recoco/apps/projects/templates/projects/project/fragments/departments_selector.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
<div class="input-item position-relative active specific-border-solid1pxE5E5E5"
3131
x-data="DepartmentsSelector({ listZone: {{ zone_objects|default:'null' }}, selectAll : '{{ select_all | default:'False' }}' == 'True', filterByRegions : '{{ filter_by_regions }}' == 'True' , selectedDepartments: {{ selected_departments|default:'null' }}, userDepartments: {{ user_departments|default:'null' }} })"
3232
x-on:click.outside="open && $dispatch('user-close-selector'); open = false; ">
33-
<div class="d-flex align-items-center {% if "inline-chevron" in extra_classes %}fr-pr-1w {% else %}fr-pr-3w {% endif %}bg-white rounded-pill"
33+
<div class="d-flex align-items-center {% if 'inline-chevron' in extra_classes %}fr-pr-1w {% else %}fr-pr-3w {% endif %}bg-white rounded-pill"
3434
x-on:click.prevent="open = !open"
3535
type="button"
3636
:class="{'input-active':open}">

recoco/apps/projects/templates/projects/project/fragments/search_bar.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
name="{{ name|default:'query' }}"
2020
data-cy="search-bar-project"
2121
class="placeholder-position searchbar-input{% if show_search_button %} searchbar-input--with-button{% endif %}"
22-
placeholder="{{ placeholder|default:"Rechercher" }}"
22+
placeholder="{{ placeholder|default:'Rechercher' }}"
2323
{% if value %}value="{{ value }}"{% endif %}
2424
@input.debounce.500ms="{{ action_search }}"
2525
{% if x_model %}x-model="{{ x_model }}"{% endif %}>

recoco/apps/resources/templates/resources/resource/list.html

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
{% vite_asset 'js/styles/multi-select.css.js' %}
1111
{% vite_asset 'js/components/MultiSelect2.js' %}
1212
{% vite_asset 'js/components/ResourceCategoryFilter.js' %}
13+
{% vite_asset 'js/components/DepartmentsFilterWrapper.js' %}
1314
{% endblock js %}
1415
{% block title %}
1516
Liste des ressources
@@ -57,7 +58,7 @@ <h1 class=" align-items-center fr-mb-2w">
5758
</div>
5859
{% endif %}
5960
<div class="w-100 d-flex{% if category_options|length < 1 %} fr-ml-4w{% endif %}">
60-
<div x-data="{ selectedCodes: {{ limit_areas|default:'[]' }}, normalize(payload) { if (!payload) return []; if (Array.isArray(payload) && typeof payload[0] === 'string') return payload; if (Array.isArray(payload) && typeof payload[0] === 'object') { return payload.filter(d => d.active).map(d => d.code); } return []; }}"
61+
<div x-data="DepartmentsFilterWrapper({{ limit_areas|default:'[]' }})"
6162
@selected-departments="selectedCodes = normalize($event.detail)"
6263
@user-close-selector="$nextTick(() => $el.closest('form').submit())">
6364
<template x-for="code in selectedCodes" :key="code">

recoco/frontend/src/css/DSFR/custom-dsfr.scss

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -562,6 +562,9 @@ form > p {
562562
.no-underline {
563563
text-decoration: none !important;
564564
}
565+
.no-outline {
566+
outline: none !important;
567+
}
565568

566569
.fr-link--underline .text-underline {
567570
text-decoration: underline !important;

recoco/frontend/src/css/projects/departments_selector.scss

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
.region-filter {
66
position: absolute;
7-
width: auto;
7+
width: 100%;
88
z-index: 101;
99

1010
left: 0;
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import '../../../css/crm/projectList.scss';
2+
3+
import '../../components/DepartmentsSelector.js';
4+
import '../../components/DepartmentsFilterWrapper.js';

0 commit comments

Comments
 (0)