mirror of
https://git.v0id.ovh/n3wt-innov/n3wt-school.git
synced 2026-01-28 23:43:22 +00:00
feat: Configuration des compétences par cycle [#16]
This commit is contained in:
@ -6,7 +6,9 @@ from rest_framework.views import APIView
|
|||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
from .models import Establishment
|
from .models import Establishment
|
||||||
from .serializers import EstablishmentSerializer
|
from .serializers import EstablishmentSerializer
|
||||||
from N3wtSchool.bdd import delete_object, getAllObjects, getObject
|
from N3wtSchool.bdd import delete_object, getAllObjects
|
||||||
|
from School.models import Competency, EstablishmentCompetency
|
||||||
|
from django.db.models import Q
|
||||||
|
|
||||||
@method_decorator(csrf_protect, name='dispatch')
|
@method_decorator(csrf_protect, name='dispatch')
|
||||||
@method_decorator(ensure_csrf_cookie, name='dispatch')
|
@method_decorator(ensure_csrf_cookie, name='dispatch')
|
||||||
@ -20,7 +22,17 @@ class EstablishmentListCreateView(APIView):
|
|||||||
establishment_data = JSONParser().parse(request)
|
establishment_data = JSONParser().parse(request)
|
||||||
establishment_serializer = EstablishmentSerializer(data=establishment_data)
|
establishment_serializer = EstablishmentSerializer(data=establishment_data)
|
||||||
if establishment_serializer.is_valid():
|
if establishment_serializer.is_valid():
|
||||||
establishment_serializer.save()
|
establishment = establishment_serializer.save()
|
||||||
|
# Création des EstablishmentCompetency pour chaque compétence existante
|
||||||
|
competencies = Competency.objects.filter(
|
||||||
|
Q(end_of_cycle=True) | ~Q(level=None)
|
||||||
|
)
|
||||||
|
for competency in competencies:
|
||||||
|
EstablishmentCompetency.objects.get_or_create(
|
||||||
|
establishment=establishment,
|
||||||
|
competency=competency,
|
||||||
|
defaults={'is_required': True}
|
||||||
|
)
|
||||||
return JsonResponse(establishment_serializer.data, safe=False, status=status.HTTP_201_CREATED)
|
return JsonResponse(establishment_serializer.data, safe=False, status=status.HTTP_201_CREATED)
|
||||||
return JsonResponse(establishment_serializer.errors, safe=False, status=status.HTTP_400_BAD_REQUEST)
|
return JsonResponse(establishment_serializer.errors, safe=False, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
from django.db import models
|
from django.db import models
|
||||||
from Auth.models import Profile
|
from Auth.models import Profile
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
class Messagerie(models.Model):
|
class Messagerie(models.Model):
|
||||||
id = models.AutoField(primary_key=True)
|
id = models.AutoField(primary_key=True)
|
||||||
|
|||||||
@ -165,31 +165,24 @@ class Competency(models.Model):
|
|||||||
class EstablishmentCompetency(models.Model):
|
class EstablishmentCompetency(models.Model):
|
||||||
"""
|
"""
|
||||||
Relation entre un établissement et une compétence.
|
Relation entre un établissement et une compétence.
|
||||||
Permet de définir quelles compétences sont sélectionnées par un établissement.
|
Peut référencer une compétence de référence OU une compétence custom propre à l'établissement.
|
||||||
"""
|
"""
|
||||||
establishment = models.ForeignKey('Establishment.Establishment', on_delete=models.CASCADE)
|
establishment = models.ForeignKey('Establishment.Establishment', on_delete=models.CASCADE)
|
||||||
competency = models.ForeignKey(Competency, on_delete=models.CASCADE)
|
competency = models.ForeignKey(
|
||||||
is_selected = models.BooleanField(default=False)
|
Competency, on_delete=models.CASCADE, null=True, blank=True,
|
||||||
|
help_text="Compétence de référence (optionnelle si custom)"
|
||||||
|
)
|
||||||
|
custom_name = models.TextField(null=True, blank=True, help_text="Nom de la compétence custom")
|
||||||
|
custom_category = models.ForeignKey(
|
||||||
|
Category, on_delete=models.CASCADE, null=True, blank=True,
|
||||||
|
help_text="Catégorie de la compétence custom"
|
||||||
|
)
|
||||||
|
is_required = models.BooleanField(default=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
unique_together = ('establishment', 'competency')
|
unique_together = ('establishment', 'competency', 'custom_name', 'custom_category')
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
|
if self.competency:
|
||||||
return f"{self.establishment.name} - {self.competency.name}"
|
return f"{self.establishment.name} - {self.competency.name}"
|
||||||
|
return f"{self.establishment.name} - {self.custom_name} (custom)"
|
||||||
|
|
||||||
# class StudentCompetency(models.Model):
|
|
||||||
# """
|
|
||||||
# Relation entre un élève et une compétence.
|
|
||||||
# Permet d'attribuer une note à un élève pour une compétence.
|
|
||||||
# """
|
|
||||||
# student = models.ForeignKey('Subscriptions.Student', on_delete=models.CASCADE, related_name='competency_scores')
|
|
||||||
# competency = models.ForeignKey(Competency, on_delete=models.CASCADE, related_name='student_scores')
|
|
||||||
# score = models.DecimalField(max_digits=5, decimal_places=2, null=True, blank=True) # Note attribuée
|
|
||||||
# comment = models.TextField(blank=True, null=True) # Commentaire facultatif
|
|
||||||
|
|
||||||
# class Meta:
|
|
||||||
# unique_together = ('student', 'competency')
|
|
||||||
|
|
||||||
# def __str__(self):
|
|
||||||
# return f"{self.student} - {self.competency.name} - Score: {self.score}"
|
|
||||||
@ -1,13 +1,47 @@
|
|||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from .models import Teacher, Speciality, SchoolClass, Planning, LEVEL_CHOICES, Discount, Fee, PaymentPlan, PaymentMode
|
from .models import (
|
||||||
|
Teacher,
|
||||||
|
Speciality,
|
||||||
|
SchoolClass,
|
||||||
|
Planning,
|
||||||
|
LEVEL_CHOICES,
|
||||||
|
Discount,
|
||||||
|
Fee,
|
||||||
|
PaymentPlan,
|
||||||
|
PaymentMode,
|
||||||
|
Domain,
|
||||||
|
Category,
|
||||||
|
Competency,
|
||||||
|
EstablishmentCompetency
|
||||||
|
)
|
||||||
from Auth.models import Profile, ProfileRole
|
from Auth.models import Profile, ProfileRole
|
||||||
from Subscriptions.models import Student
|
from Subscriptions.models import Student
|
||||||
from Establishment.models import Establishment
|
from Establishment.models import Establishment
|
||||||
from Auth.serializers import ProfileRoleSerializer
|
from Auth.serializers import ProfileRoleSerializer
|
||||||
from N3wtSchool import settings, bdd
|
from N3wtSchool import settings
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
import pytz
|
import pytz
|
||||||
|
|
||||||
|
class DomainSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = Domain
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
class CategorySerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = Category
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
class CompetencySerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = Competency
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
class EstablishmentCompetencySerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = EstablishmentCompetency
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
class SpecialitySerializer(serializers.ModelSerializer):
|
class SpecialitySerializer(serializers.ModelSerializer):
|
||||||
updated_date_formatted = serializers.SerializerMethodField()
|
updated_date_formatted = serializers.SerializerMethodField()
|
||||||
|
|
||||||
|
|||||||
@ -2,7 +2,8 @@ import json
|
|||||||
import os
|
import os
|
||||||
from django.db.models.signals import post_migrate
|
from django.db.models.signals import post_migrate
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
from School.models import Domain, Category, Competency
|
from Establishment.models import Establishment
|
||||||
|
from School.models import Domain, Category, Competency, EstablishmentCompetency
|
||||||
|
|
||||||
@receiver(post_migrate)
|
@receiver(post_migrate)
|
||||||
def load_json_data(sender, **kwargs):
|
def load_json_data(sender, **kwargs):
|
||||||
@ -32,15 +33,15 @@ def load_json_data(sender, **kwargs):
|
|||||||
|
|
||||||
for domain_data in data['domaines']:
|
for domain_data in data['domaines']:
|
||||||
# Vérifiez si le domaine existe déjà
|
# Vérifiez si le domaine existe déjà
|
||||||
domain, created = Domain.objects.get_or_create(name=domain_data['nom'], cycle=cycle)
|
domain, _ = Domain.objects.get_or_create(name=domain_data['nom'], cycle=cycle)
|
||||||
|
|
||||||
for category_data in domain_data['categories']:
|
for category_data in domain_data['categories']:
|
||||||
# Vérifiez si la catégorie existe déjà
|
# Vérifiez si la catégorie existe déjà
|
||||||
category, created = Category.objects.get_or_create(name=category_data['nom'], domain=domain)
|
category, _ = Category.objects.get_or_create(name=category_data['nom'], domain=domain)
|
||||||
|
|
||||||
for competency_data in category_data['competences']:
|
for competency_data in category_data['competences']:
|
||||||
# Vérifiez si la compétence existe déjà
|
# Vérifiez si la compétence existe déjà
|
||||||
Competency.objects.get_or_create(
|
competency, _ = Competency.objects.get_or_create(
|
||||||
name=competency_data['nom'],
|
name=competency_data['nom'],
|
||||||
end_of_cycle=competency_data.get('fin_cycle', False),
|
end_of_cycle=competency_data.get('fin_cycle', False),
|
||||||
level=competency_data.get('niveau'),
|
level=competency_data.get('niveau'),
|
||||||
|
|||||||
@ -1,22 +1,18 @@
|
|||||||
from django.urls import path, re_path
|
from django.urls import path, re_path
|
||||||
|
|
||||||
from .views import (
|
from .views import (
|
||||||
TeacherListCreateView,
|
TeacherListCreateView, TeacherDetailView,
|
||||||
TeacherDetailView,
|
SpecialityListCreateView, SpecialityDetailView,
|
||||||
SpecialityListCreateView,
|
SchoolClassListCreateView, SchoolClassDetailView,
|
||||||
SpecialityDetailView,
|
PlanningListCreateView, PlanningDetailView,
|
||||||
SchoolClassListCreateView,
|
FeeListCreateView, FeeDetailView,
|
||||||
SchoolClassDetailView,
|
DiscountListCreateView, DiscountDetailView,
|
||||||
PlanningListCreateView,
|
PaymentPlanListCreateView, PaymentPlanDetailView,
|
||||||
PlanningDetailView,
|
PaymentModeListCreateView, PaymentModeDetailView,
|
||||||
FeeListCreateView,
|
DomainListCreateView, DomainDetailView,
|
||||||
FeeDetailView,
|
CategoryListCreateView, CategoryDetailView,
|
||||||
DiscountListCreateView,
|
CompetencyListCreateView, CompetencyDetailView,
|
||||||
DiscountDetailView,
|
EstablishmentCompetencyListCreateView, EstablishmentCompetencyDetailView,
|
||||||
PaymentPlanListCreateView,
|
|
||||||
PaymentPlanDetailView,
|
|
||||||
PaymentModeListCreateView,
|
|
||||||
PaymentModeDetailView,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
@ -43,4 +39,16 @@ urlpatterns = [
|
|||||||
|
|
||||||
re_path(r'^paymentModes$', PaymentModeListCreateView.as_view(), name="payment_mode_list_create"),
|
re_path(r'^paymentModes$', PaymentModeListCreateView.as_view(), name="payment_mode_list_create"),
|
||||||
re_path(r'^paymentModes/(?P<id>[0-9]+)$', PaymentModeDetailView.as_view(), name="payment_mode_detail"),
|
re_path(r'^paymentModes/(?P<id>[0-9]+)$', PaymentModeDetailView.as_view(), name="payment_mode_detail"),
|
||||||
|
|
||||||
|
re_path(r'^domains$', DomainListCreateView.as_view(), name="domain_list_create"),
|
||||||
|
re_path(r'^domains/(?P<id>[0-9]+)$', DomainDetailView.as_view(), name="domain_detail"),
|
||||||
|
|
||||||
|
re_path(r'^categories$', CategoryListCreateView.as_view(), name="category_list_create"),
|
||||||
|
re_path(r'^categories/(?P<id>[0-9]+)$', CategoryDetailView.as_view(), name="category_detail"),
|
||||||
|
|
||||||
|
re_path(r'^competencies$', CompetencyListCreateView.as_view(), name="competency_list_create"),
|
||||||
|
re_path(r'^competencies/(?P<id>[0-9]+)$', CompetencyDetailView.as_view(), name="competency_detail"),
|
||||||
|
|
||||||
|
re_path(r'^establishmentCompetencies$', EstablishmentCompetencyListCreateView.as_view(), name="establishment_competency_list_create"),
|
||||||
|
re_path(r'^establishmentCompetencies/(?P<id>[0-9]+)$', EstablishmentCompetencyDetailView.as_view(), name="establishment_competency_detail"),
|
||||||
]
|
]
|
||||||
@ -12,7 +12,11 @@ from .models import (
|
|||||||
Discount,
|
Discount,
|
||||||
Fee,
|
Fee,
|
||||||
PaymentPlan,
|
PaymentPlan,
|
||||||
PaymentMode
|
PaymentMode,
|
||||||
|
Domain,
|
||||||
|
Category,
|
||||||
|
Competency,
|
||||||
|
EstablishmentCompetency
|
||||||
)
|
)
|
||||||
from .serializers import (
|
from .serializers import (
|
||||||
TeacherSerializer,
|
TeacherSerializer,
|
||||||
@ -22,9 +26,15 @@ from .serializers import (
|
|||||||
DiscountSerializer,
|
DiscountSerializer,
|
||||||
FeeSerializer,
|
FeeSerializer,
|
||||||
PaymentPlanSerializer,
|
PaymentPlanSerializer,
|
||||||
PaymentModeSerializer
|
PaymentModeSerializer,
|
||||||
|
DomainSerializer,
|
||||||
|
CategorySerializer,
|
||||||
|
CompetencySerializer,
|
||||||
|
EstablishmentCompetencySerializer
|
||||||
)
|
)
|
||||||
from N3wtSchool.bdd import delete_object, getAllObjects, getObject
|
from N3wtSchool.bdd import delete_object, getAllObjects, getObject
|
||||||
|
from django.db.models import Q
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
@method_decorator(csrf_protect, name='dispatch')
|
@method_decorator(csrf_protect, name='dispatch')
|
||||||
@method_decorator(ensure_csrf_cookie, name='dispatch')
|
@method_decorator(ensure_csrf_cookie, name='dispatch')
|
||||||
@ -411,3 +421,349 @@ class PaymentModeDetailView(APIView):
|
|||||||
|
|
||||||
def delete(self, request, id):
|
def delete(self, request, id):
|
||||||
return delete_object(PaymentMode, id)
|
return delete_object(PaymentMode, id)
|
||||||
|
|
||||||
|
@method_decorator(csrf_protect, name='dispatch')
|
||||||
|
@method_decorator(ensure_csrf_cookie, name='dispatch')
|
||||||
|
class DomainListCreateView(APIView):
|
||||||
|
def get(self, request):
|
||||||
|
domains = Domain.objects.all()
|
||||||
|
serializer = DomainSerializer(domains, many=True)
|
||||||
|
return JsonResponse(serializer.data, safe=False)
|
||||||
|
|
||||||
|
def post(self, request):
|
||||||
|
data = JSONParser().parse(request)
|
||||||
|
serializer = DomainSerializer(data=data)
|
||||||
|
if serializer.is_valid():
|
||||||
|
serializer.save()
|
||||||
|
return JsonResponse(serializer.data, safe=False, status=status.HTTP_201_CREATED)
|
||||||
|
return JsonResponse(serializer.errors, safe=False, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
@method_decorator(csrf_protect, name='dispatch')
|
||||||
|
@method_decorator(ensure_csrf_cookie, name='dispatch')
|
||||||
|
class DomainDetailView(APIView):
|
||||||
|
def get(self, request, id):
|
||||||
|
try:
|
||||||
|
domain = Domain.objects.get(id=id)
|
||||||
|
serializer = DomainSerializer(domain)
|
||||||
|
return JsonResponse(serializer.data, safe=False)
|
||||||
|
except Domain.DoesNotExist:
|
||||||
|
return JsonResponse({'error': 'No object found'}, status=status.HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
|
def put(self, request, id):
|
||||||
|
try:
|
||||||
|
domain = Domain.objects.get(id=id)
|
||||||
|
except Domain.DoesNotExist:
|
||||||
|
return JsonResponse({'error': 'No object found'}, status=status.HTTP_404_NOT_FOUND)
|
||||||
|
data = JSONParser().parse(request)
|
||||||
|
serializer = DomainSerializer(domain, data=data, partial=True)
|
||||||
|
if serializer.is_valid():
|
||||||
|
serializer.save()
|
||||||
|
return JsonResponse(serializer.data, safe=False)
|
||||||
|
return JsonResponse(serializer.errors, safe=False, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
def delete(self, request, id):
|
||||||
|
try:
|
||||||
|
domain = Domain.objects.get(id=id)
|
||||||
|
domain.delete()
|
||||||
|
return JsonResponse({'message': 'Deleted'}, safe=False)
|
||||||
|
except Domain.DoesNotExist:
|
||||||
|
return JsonResponse({'error': 'No object found'}, status=status.HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
|
# Répète la même logique pour Category, Competency, EstablishmentCompetency
|
||||||
|
|
||||||
|
@method_decorator(csrf_protect, name='dispatch')
|
||||||
|
@method_decorator(ensure_csrf_cookie, name='dispatch')
|
||||||
|
class CategoryListCreateView(APIView):
|
||||||
|
def get(self, request):
|
||||||
|
categories = Category.objects.all()
|
||||||
|
serializer = CategorySerializer(categories, many=True)
|
||||||
|
return JsonResponse(serializer.data, safe=False)
|
||||||
|
|
||||||
|
def post(self, request):
|
||||||
|
data = JSONParser().parse(request)
|
||||||
|
serializer = CategorySerializer(data=data)
|
||||||
|
if serializer.is_valid():
|
||||||
|
serializer.save()
|
||||||
|
return JsonResponse(serializer.data, safe=False, status=status.HTTP_201_CREATED)
|
||||||
|
return JsonResponse(serializer.errors, safe=False, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
@method_decorator(csrf_protect, name='dispatch')
|
||||||
|
@method_decorator(ensure_csrf_cookie, name='dispatch')
|
||||||
|
class CategoryDetailView(APIView):
|
||||||
|
def get(self, request, id):
|
||||||
|
try:
|
||||||
|
category = Category.objects.get(id=id)
|
||||||
|
serializer = CategorySerializer(category)
|
||||||
|
return JsonResponse(serializer.data, safe=False)
|
||||||
|
except Category.DoesNotExist:
|
||||||
|
return JsonResponse({'error': 'No object found'}, status=status.HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
|
def put(self, request, id):
|
||||||
|
try:
|
||||||
|
category = Category.objects.get(id=id)
|
||||||
|
except Category.DoesNotExist:
|
||||||
|
return JsonResponse({'error': 'No object found'}, status=status.HTTP_404_NOT_FOUND)
|
||||||
|
data = JSONParser().parse(request)
|
||||||
|
serializer = CategorySerializer(category, data=data, partial=True)
|
||||||
|
if serializer.is_valid():
|
||||||
|
serializer.save()
|
||||||
|
return JsonResponse(serializer.data, safe=False)
|
||||||
|
return JsonResponse(serializer.errors, safe=False, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
def delete(self, request, id):
|
||||||
|
try:
|
||||||
|
category = Category.objects.get(id=id)
|
||||||
|
category.delete()
|
||||||
|
return JsonResponse({'message': 'Deleted'}, safe=False)
|
||||||
|
except Category.DoesNotExist:
|
||||||
|
return JsonResponse({'error': 'No object found'}, status=status.HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
|
@method_decorator(csrf_protect, name='dispatch')
|
||||||
|
@method_decorator(ensure_csrf_cookie, name='dispatch')
|
||||||
|
class CompetencyListCreateView(APIView):
|
||||||
|
def get(self, request):
|
||||||
|
cycle = request.GET.get('cycle')
|
||||||
|
if cycle is None:
|
||||||
|
return JsonResponse({'error': 'cycle est requis'}, safe=False, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
competencies_list = getAllObjects(Competency)
|
||||||
|
competencies_list = competencies_list.filter(
|
||||||
|
category__domain__cycle=cycle
|
||||||
|
).distinct()
|
||||||
|
serializer = CompetencySerializer(competencies_list, many=True)
|
||||||
|
print(f'len : {competencies_list.count()}')
|
||||||
|
return JsonResponse(serializer.data, safe=False)
|
||||||
|
|
||||||
|
def post(self, request):
|
||||||
|
data = JSONParser().parse(request)
|
||||||
|
serializer = CompetencySerializer(data=data)
|
||||||
|
if serializer.is_valid():
|
||||||
|
serializer.save()
|
||||||
|
return JsonResponse(serializer.data, safe=False, status=status.HTTP_201_CREATED)
|
||||||
|
return JsonResponse(serializer.errors, safe=False, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
@method_decorator(csrf_protect, name='dispatch')
|
||||||
|
@method_decorator(ensure_csrf_cookie, name='dispatch')
|
||||||
|
class CompetencyDetailView(APIView):
|
||||||
|
def get(self, request, id):
|
||||||
|
try:
|
||||||
|
competency = Competency.objects.get(id=id)
|
||||||
|
serializer = CompetencySerializer(competency)
|
||||||
|
return JsonResponse(serializer.data, safe=False)
|
||||||
|
except Competency.DoesNotExist:
|
||||||
|
return JsonResponse({'error': 'No object found'}, status=status.HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
|
def put(self, request, id):
|
||||||
|
try:
|
||||||
|
competency = Competency.objects.get(id=id)
|
||||||
|
except Competency.DoesNotExist:
|
||||||
|
return JsonResponse({'error': 'No object found'}, status=status.HTTP_404_NOT_FOUND)
|
||||||
|
data = JSONParser().parse(request)
|
||||||
|
serializer = CompetencySerializer(competency, data=data, partial=True)
|
||||||
|
if serializer.is_valid():
|
||||||
|
serializer.save()
|
||||||
|
return JsonResponse(serializer.data, safe=False)
|
||||||
|
return JsonResponse(serializer.errors, safe=False, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
def delete(self, request, id):
|
||||||
|
try:
|
||||||
|
competency = Competency.objects.get(id=id)
|
||||||
|
competency.delete()
|
||||||
|
return JsonResponse({'message': 'Deleted'}, safe=False)
|
||||||
|
except Competency.DoesNotExist:
|
||||||
|
return JsonResponse({'error': 'No object found'}, status=status.HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
|
@method_decorator(csrf_protect, name='dispatch')
|
||||||
|
@method_decorator(ensure_csrf_cookie, name='dispatch')
|
||||||
|
class EstablishmentCompetencyListCreateView(APIView):
|
||||||
|
def get(self, request):
|
||||||
|
establishment_id = request.GET.get('establishment_id')
|
||||||
|
cycle = request.GET.get('cycle')
|
||||||
|
if not establishment_id or not cycle:
|
||||||
|
return JsonResponse({'error': 'establishment_id et cycle sont requis'}, safe=False, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
# Toutes les compétences du cycle
|
||||||
|
competencies = Competency.objects.filter(category__domain__cycle=cycle).select_related('category', 'category__domain')
|
||||||
|
|
||||||
|
# Récupérer les EstablishmentCompetency pour l'établissement et le cycle
|
||||||
|
ec_qs = EstablishmentCompetency.objects.filter(
|
||||||
|
Q(competency__in=competencies) | Q(competency__isnull=True),
|
||||||
|
establishment_id=establishment_id
|
||||||
|
).select_related('competency', 'custom_category')
|
||||||
|
|
||||||
|
# Required = compétences de référence requises
|
||||||
|
required_ids = set(ec_qs.filter(is_required=True, competency__isnull=False).values_list('competency_id', flat=True))
|
||||||
|
|
||||||
|
# Custom = compétences custom (pas de lien vers Competency)
|
||||||
|
custom_ecs = ec_qs.filter(is_required=False, competency__isnull=True)
|
||||||
|
|
||||||
|
# Organisation par domaine > catégorie
|
||||||
|
result = []
|
||||||
|
selected_count = 0
|
||||||
|
domaines = Domain.objects.filter(cycle=cycle)
|
||||||
|
for domaine in domaines:
|
||||||
|
domaine_dict = {
|
||||||
|
"domaine_id": domaine.id,
|
||||||
|
"domaine_nom": domaine.name,
|
||||||
|
"categories": []
|
||||||
|
}
|
||||||
|
categories = domaine.categories.all()
|
||||||
|
for categorie in categories:
|
||||||
|
categorie_dict = {
|
||||||
|
"categorie_id": categorie.id,
|
||||||
|
"categorie_nom": categorie.name,
|
||||||
|
"competences": []
|
||||||
|
}
|
||||||
|
|
||||||
|
# Liste des noms de compétences custom pour cette catégorie
|
||||||
|
custom_for_cat = custom_ecs.filter(custom_category=categorie)
|
||||||
|
custom_names = set(ec.custom_name.strip().lower() for ec in custom_for_cat if ec.custom_name)
|
||||||
|
|
||||||
|
# Compétences de référence (on saute celles qui sont déjà en custom)
|
||||||
|
competences = categorie.competencies.all()
|
||||||
|
for comp in competences:
|
||||||
|
if comp.name.strip().lower() in custom_names:
|
||||||
|
continue # On n'affiche pas la compétence de référence si une custom du même nom existe
|
||||||
|
if comp.id in required_ids:
|
||||||
|
state = "required"
|
||||||
|
selected_count += 1
|
||||||
|
else:
|
||||||
|
state = "none"
|
||||||
|
categorie_dict["competences"].append({
|
||||||
|
"competence_id": comp.id,
|
||||||
|
"nom": comp.name,
|
||||||
|
"state": state
|
||||||
|
})
|
||||||
|
|
||||||
|
# Ajout des compétences custom
|
||||||
|
for ec in custom_for_cat:
|
||||||
|
categorie_dict["competences"].append({
|
||||||
|
"competence_id": ec.id,
|
||||||
|
"nom": ec.custom_name,
|
||||||
|
"state": "custom"
|
||||||
|
})
|
||||||
|
selected_count += 1
|
||||||
|
|
||||||
|
domaine_dict["categories"].append(categorie_dict)
|
||||||
|
result.append(domaine_dict)
|
||||||
|
|
||||||
|
return JsonResponse({
|
||||||
|
"selected_count": selected_count,
|
||||||
|
"data": result
|
||||||
|
}, safe=False)
|
||||||
|
|
||||||
|
def post(self, request):
|
||||||
|
"""
|
||||||
|
Crée une ou plusieurs compétences custom pour un établissement (is_required=False)
|
||||||
|
Attendu dans le body :
|
||||||
|
[
|
||||||
|
{ "establishment_id": ..., "category_id": ..., "nom": ... },
|
||||||
|
...
|
||||||
|
]
|
||||||
|
"""
|
||||||
|
data = JSONParser().parse(request)
|
||||||
|
# Si data est un dict (un seul objet), on le met dans une liste
|
||||||
|
if isinstance(data, dict):
|
||||||
|
data = [data]
|
||||||
|
|
||||||
|
created = []
|
||||||
|
errors = []
|
||||||
|
|
||||||
|
for item in data:
|
||||||
|
establishment_id = item.get("establishment_id")
|
||||||
|
category_id = item.get("category_id")
|
||||||
|
nom = item.get("nom")
|
||||||
|
|
||||||
|
if not establishment_id or not category_id or not nom:
|
||||||
|
errors.append({"error": "establishment_id, category_id et nom sont requis", "item": item})
|
||||||
|
continue
|
||||||
|
|
||||||
|
try:
|
||||||
|
category = Category.objects.get(id=category_id)
|
||||||
|
# Vérifier si une compétence custom du même nom existe déjà pour cet établissement et cette catégorie
|
||||||
|
ec_exists = EstablishmentCompetency.objects.filter(
|
||||||
|
establishment_id=establishment_id,
|
||||||
|
competency__isnull=True,
|
||||||
|
custom_name=nom,
|
||||||
|
).exists()
|
||||||
|
if ec_exists:
|
||||||
|
errors.append({"error": "Une compétence custom de ce nom existe déjà pour cet établissement", "item": item})
|
||||||
|
continue
|
||||||
|
|
||||||
|
ec = EstablishmentCompetency.objects.create(
|
||||||
|
establishment_id=establishment_id,
|
||||||
|
competency=None,
|
||||||
|
custom_name=nom,
|
||||||
|
custom_category=category,
|
||||||
|
is_required=False
|
||||||
|
)
|
||||||
|
created.append({
|
||||||
|
"competence_id": ec.id,
|
||||||
|
"nom": ec.custom_name,
|
||||||
|
"state": "custom"
|
||||||
|
})
|
||||||
|
except Exception as e:
|
||||||
|
errors.append({"error": str(e), "item": item})
|
||||||
|
|
||||||
|
status_code = status.HTTP_201_CREATED if created else status.HTTP_400_BAD_REQUEST
|
||||||
|
return JsonResponse({
|
||||||
|
"created": created,
|
||||||
|
"errors": errors
|
||||||
|
}, status=status_code, safe=False)
|
||||||
|
|
||||||
|
def delete(self, request):
|
||||||
|
"""
|
||||||
|
Supprime une ou plusieurs compétences custom (EstablishmentCompetency) à partir d'une liste d'IDs.
|
||||||
|
Attendu dans le body :
|
||||||
|
{
|
||||||
|
"ids": [1, 2, 3, ...]
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
data = JSONParser().parse(request)
|
||||||
|
ids = data.get("ids", [])
|
||||||
|
deleted = []
|
||||||
|
errors = []
|
||||||
|
|
||||||
|
for ec_id in ids:
|
||||||
|
try:
|
||||||
|
ec = EstablishmentCompetency.objects.get(id=ec_id)
|
||||||
|
ec.delete()
|
||||||
|
deleted.append(ec_id)
|
||||||
|
except EstablishmentCompetency.DoesNotExist:
|
||||||
|
errors.append({"id": ec_id, "error": "No object found"})
|
||||||
|
|
||||||
|
return JsonResponse({
|
||||||
|
"deleted": deleted,
|
||||||
|
"errors": errors
|
||||||
|
}, safe=False)
|
||||||
|
|
||||||
|
@method_decorator(csrf_protect, name='dispatch')
|
||||||
|
@method_decorator(ensure_csrf_cookie, name='dispatch')
|
||||||
|
class EstablishmentCompetencyDetailView(APIView):
|
||||||
|
def get(self, request, id):
|
||||||
|
try:
|
||||||
|
ec = EstablishmentCompetency.objects.get(id=id)
|
||||||
|
serializer = EstablishmentCompetencySerializer(ec)
|
||||||
|
return JsonResponse(serializer.data, safe=False)
|
||||||
|
except EstablishmentCompetency.DoesNotExist:
|
||||||
|
return JsonResponse({'error': 'No object found'}, status=status.HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
|
def put(self, request, id):
|
||||||
|
try:
|
||||||
|
ec = EstablishmentCompetency.objects.get(id=id)
|
||||||
|
except EstablishmentCompetency.DoesNotExist:
|
||||||
|
return JsonResponse({'error': 'No object found'}, status=status.HTTP_404_NOT_FOUND)
|
||||||
|
data = JSONParser().parse(request)
|
||||||
|
serializer = EstablishmentCompetencySerializer(ec, data=data, partial=True)
|
||||||
|
if serializer.is_valid():
|
||||||
|
serializer.save()
|
||||||
|
return JsonResponse(serializer.data, safe=False)
|
||||||
|
return JsonResponse(serializer.errors, safe=False, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
def delete(self, request, id):
|
||||||
|
try:
|
||||||
|
ec = EstablishmentCompetency.objects.get(id=id)
|
||||||
|
ec.delete()
|
||||||
|
return JsonResponse({'message': 'Deleted'}, safe=False)
|
||||||
|
except EstablishmentCompetency.DoesNotExist:
|
||||||
|
return JsonResponse({'error': 'No object found'}, status=status.HTTP_404_NOT_FOUND)
|
||||||
|
|||||||
@ -557,13 +557,13 @@ export default function Page() {
|
|||||||
/>
|
/>
|
||||||
{/* Popups */}
|
{/* Popups */}
|
||||||
<Popup
|
<Popup
|
||||||
visible={popupVisible}
|
isOpen={popupVisible}
|
||||||
message={popupMessage}
|
message={popupMessage}
|
||||||
onConfirm={() => setPopupVisible(false)}
|
onConfirm={() => setPopupVisible(false)}
|
||||||
uniqueConfirmButton={true}
|
uniqueConfirmButton={true}
|
||||||
/>
|
/>
|
||||||
<Popup
|
<Popup
|
||||||
visible={confirmPopupVisible}
|
isOpen={confirmPopupVisible}
|
||||||
message={confirmPopupMessage}
|
message={confirmPopupMessage}
|
||||||
onConfirm={confirmPopupOnConfirm}
|
onConfirm={confirmPopupOnConfirm}
|
||||||
onCancel={() => setConfirmPopupVisible(false)}
|
onCancel={() => setConfirmPopupVisible(false)}
|
||||||
|
|||||||
@ -182,7 +182,7 @@ export default function Layout({ children }) {
|
|||||||
<Footer softwareName={softwareName} softwareVersion={softwareVersion} />
|
<Footer softwareName={softwareName} softwareVersion={softwareVersion} />
|
||||||
|
|
||||||
<Popup
|
<Popup
|
||||||
visible={isPopupVisible}
|
isOpen={isPopupVisible}
|
||||||
message="Êtes-vous sûr(e) de vouloir vous déconnecter ?"
|
message="Êtes-vous sûr(e) de vouloir vous déconnecter ?"
|
||||||
onConfirm={confirmDisconnect}
|
onConfirm={confirmDisconnect}
|
||||||
onCancel={() => setIsPopupVisible(false)}
|
onCancel={() => setIsPopupVisible(false)}
|
||||||
|
|||||||
@ -548,7 +548,7 @@ export default function Page() {
|
|||||||
|
|
||||||
{/* Popup */}
|
{/* Popup */}
|
||||||
<Popup
|
<Popup
|
||||||
visible={popupVisible}
|
isOpen={popupVisible}
|
||||||
message={popupMessage}
|
message={popupMessage}
|
||||||
onConfirm={() => setPopupVisible(false)}
|
onConfirm={() => setPopupVisible(false)}
|
||||||
onCancel={() => setPopupVisible(false)}
|
onCancel={() => setPopupVisible(false)}
|
||||||
|
|||||||
@ -22,6 +22,7 @@ import {
|
|||||||
fetchTuitionPaymentPlans,
|
fetchTuitionPaymentPlans,
|
||||||
fetchRegistrationPaymentModes,
|
fetchRegistrationPaymentModes,
|
||||||
fetchTuitionPaymentModes,
|
fetchTuitionPaymentModes,
|
||||||
|
fetchEstablishmentCompetencies,
|
||||||
} from '@/app/actions/schoolAction';
|
} from '@/app/actions/schoolAction';
|
||||||
import { fetchProfiles } from '@/app/actions/authAction';
|
import { fetchProfiles } from '@/app/actions/authAction';
|
||||||
import SidebarTabs from '@/components/SidebarTabs';
|
import SidebarTabs from '@/components/SidebarTabs';
|
||||||
@ -30,6 +31,7 @@ import { fetchRegistrationSchoolFileMasters } from '@/app/actions/registerFileGr
|
|||||||
import logger from '@/utils/logger';
|
import logger from '@/utils/logger';
|
||||||
import { useEstablishment } from '@/context/EstablishmentContext';
|
import { useEstablishment } from '@/context/EstablishmentContext';
|
||||||
import { PlanningProvider, PlanningModes } from '@/context/PlanningContext';
|
import { PlanningProvider, PlanningModes } from '@/context/PlanningContext';
|
||||||
|
import CompetenciesList from '@/components/Structure/Competencies/CompetenciesList';
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
const [specialities, setSpecialities] = useState([]);
|
const [specialities, setSpecialities] = useState([]);
|
||||||
@ -45,6 +47,9 @@ export default function Page() {
|
|||||||
const [registrationPaymentModes, setRegistrationPaymentModes] = useState([]);
|
const [registrationPaymentModes, setRegistrationPaymentModes] = useState([]);
|
||||||
const [tuitionPaymentModes, setTuitionPaymentModes] = useState([]);
|
const [tuitionPaymentModes, setTuitionPaymentModes] = useState([]);
|
||||||
const [profiles, setProfiles] = useState([]);
|
const [profiles, setProfiles] = useState([]);
|
||||||
|
const [establishmentCompetencies, setEstablishmentCompetencies] = useState(
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
const csrfToken = useCsrfToken();
|
const csrfToken = useCsrfToken();
|
||||||
const { selectedEstablishmentId } = useEstablishment();
|
const { selectedEstablishmentId } = useEstablishment();
|
||||||
@ -98,9 +103,22 @@ export default function Page() {
|
|||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
logger.error('Error fetching profileRoles:', error);
|
logger.error('Error fetching profileRoles:', error);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Fetch data for establishment competencies
|
||||||
|
handleEstablishmentCompetencies();
|
||||||
}
|
}
|
||||||
}, [selectedEstablishmentId]);
|
}, [selectedEstablishmentId]);
|
||||||
|
|
||||||
|
const handleEstablishmentCompetencies = (cycle = 1) => {
|
||||||
|
fetchEstablishmentCompetencies(selectedEstablishmentId, cycle)
|
||||||
|
.then((data) => {
|
||||||
|
setEstablishmentCompetencies(data);
|
||||||
|
})
|
||||||
|
.catch((error) =>
|
||||||
|
logger.error('Error fetching setEstablishmentCompetencies:', error)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const handleSpecialities = () => {
|
const handleSpecialities = () => {
|
||||||
fetchSpecialities(selectedEstablishmentId)
|
fetchSpecialities(selectedEstablishmentId)
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
@ -339,6 +357,18 @@ export default function Page() {
|
|||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'Competencies',
|
||||||
|
label: 'Compétences',
|
||||||
|
content: (
|
||||||
|
<div className="h-full overflow-y-auto p-4">
|
||||||
|
<CompetenciesList
|
||||||
|
establishmentCompetencies={establishmentCompetencies}
|
||||||
|
onChangeCycle={handleEstablishmentCompetencies}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -837,13 +837,13 @@ export default function Page({ params: { locale } }) {
|
|||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
<Popup
|
<Popup
|
||||||
visible={popupVisible}
|
isOpen={popupVisible}
|
||||||
message={popupMessage}
|
message={popupMessage}
|
||||||
onConfirm={() => setPopupVisible(false)}
|
onConfirm={() => setPopupVisible(false)}
|
||||||
uniqueConfirmButton={true}
|
uniqueConfirmButton={true}
|
||||||
/>
|
/>
|
||||||
<Popup
|
<Popup
|
||||||
visible={confirmPopupVisible}
|
isOpen={confirmPopupVisible}
|
||||||
message={confirmPopupMessage}
|
message={confirmPopupMessage}
|
||||||
onConfirm={confirmPopupOnConfirm}
|
onConfirm={confirmPopupOnConfirm}
|
||||||
onCancel={() => setConfirmPopupVisible(false)}
|
onCancel={() => setConfirmPopupVisible(false)}
|
||||||
|
|||||||
@ -113,7 +113,7 @@ export default function Page() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Popup
|
<Popup
|
||||||
visible={popupVisible}
|
isOpen={popupVisible}
|
||||||
message={popupMessage}
|
message={popupMessage}
|
||||||
onConfirm={popupConfirmAction}
|
onConfirm={popupConfirmAction}
|
||||||
onCancel={() => setPopupVisible(false)}
|
onCancel={() => setPopupVisible(false)}
|
||||||
|
|||||||
@ -8,9 +8,46 @@ import {
|
|||||||
BE_SCHOOL_PAYMENT_PLANS_URL,
|
BE_SCHOOL_PAYMENT_PLANS_URL,
|
||||||
BE_SCHOOL_PAYMENT_MODES_URL,
|
BE_SCHOOL_PAYMENT_MODES_URL,
|
||||||
BE_SCHOOL_ESTABLISHMENT_URL,
|
BE_SCHOOL_ESTABLISHMENT_URL,
|
||||||
|
BE_SCHOOL_ESTABLISHMENT_COMPETENCIES_URL,
|
||||||
} from '@/utils/Url';
|
} from '@/utils/Url';
|
||||||
import { errorHandler, requestResponseHandler } from './actionsHandlers';
|
import { errorHandler, requestResponseHandler } from './actionsHandlers';
|
||||||
|
|
||||||
|
export const deleteEstablishmentCompetencies = (ids, csrfToken) => {
|
||||||
|
return fetch(BE_SCHOOL_ESTABLISHMENT_COMPETENCIES_URL, {
|
||||||
|
method: 'DELETE',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-CSRFToken': csrfToken,
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ ids }),
|
||||||
|
credentials: 'include',
|
||||||
|
})
|
||||||
|
.then(requestResponseHandler)
|
||||||
|
.catch(errorHandler);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createEstablishmentCompetencies = (newData, csrfToken) => {
|
||||||
|
return fetch(BE_SCHOOL_ESTABLISHMENT_COMPETENCIES_URL, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-CSRFToken': csrfToken,
|
||||||
|
},
|
||||||
|
body: JSON.stringify(newData),
|
||||||
|
credentials: 'include',
|
||||||
|
})
|
||||||
|
.then(requestResponseHandler)
|
||||||
|
.catch(errorHandler);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const fetchEstablishmentCompetencies = (establishment, cycle = 1) => {
|
||||||
|
return fetch(
|
||||||
|
`${BE_SCHOOL_ESTABLISHMENT_COMPETENCIES_URL}?establishment_id=${establishment}&cycle=${cycle}`
|
||||||
|
)
|
||||||
|
.then(requestResponseHandler)
|
||||||
|
.catch(errorHandler);
|
||||||
|
};
|
||||||
|
|
||||||
export const fetchSpecialities = (establishment) => {
|
export const fetchSpecialities = (establishment) => {
|
||||||
return fetch(
|
return fetch(
|
||||||
`${BE_SCHOOL_SPECIALITIES_URL}?establishment_id=${establishment}`
|
`${BE_SCHOOL_SPECIALITIES_URL}?establishment_id=${establishment}`
|
||||||
|
|||||||
@ -9,8 +9,6 @@ const CheckBox = ({
|
|||||||
itemLabelFunc = () => null,
|
itemLabelFunc = () => null,
|
||||||
horizontal,
|
horizontal,
|
||||||
}) => {
|
}) => {
|
||||||
logger.debug(formData);
|
|
||||||
|
|
||||||
// Vérifier si formData[fieldName] est un tableau ou une valeur booléenne
|
// Vérifier si formData[fieldName] est un tableau ou une valeur booléenne
|
||||||
const isChecked = Array.isArray(formData[fieldName])
|
const isChecked = Array.isArray(formData[fieldName])
|
||||||
? formData[fieldName].includes(parseInt(item.id)) // Si c'est un tableau, vérifier si l'élément est inclus
|
? formData[fieldName].includes(parseInt(item.id)) // Si c'est un tableau, vérifier si l'élément est inclus
|
||||||
|
|||||||
@ -84,7 +84,7 @@ const DateTab = ({
|
|||||||
</div>
|
</div>
|
||||||
{popupVisible && (
|
{popupVisible && (
|
||||||
<Popup
|
<Popup
|
||||||
visible={popupVisible}
|
isOpen={popupVisible}
|
||||||
message={popupMessage}
|
message={popupMessage}
|
||||||
onConfirm={() => setPopupVisible(false)}
|
onConfirm={() => setPopupVisible(false)}
|
||||||
onCancel={() => setPopupVisible(false)}
|
onCancel={() => setPopupVisible(false)}
|
||||||
|
|||||||
@ -248,14 +248,14 @@ export default function FilesToUpload({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<Popup
|
<Popup
|
||||||
visible={popupVisible}
|
isOpen={popupVisible}
|
||||||
message={popupMessage}
|
message={popupMessage}
|
||||||
onConfirm={() => setPopupVisible(false)}
|
onConfirm={() => setPopupVisible(false)}
|
||||||
onCancel={() => setPopupVisible(false)}
|
onCancel={() => setPopupVisible(false)}
|
||||||
uniqueConfirmButton={true}
|
uniqueConfirmButton={true}
|
||||||
/>
|
/>
|
||||||
<Popup
|
<Popup
|
||||||
visible={removePopupVisible}
|
isOpen={removePopupVisible}
|
||||||
message={removePopupMessage}
|
message={removePopupMessage}
|
||||||
onConfirm={removePopupOnConfirm}
|
onConfirm={removePopupOnConfirm}
|
||||||
onCancel={() => setRemovePopupVisible(false)}
|
onCancel={() => setRemovePopupVisible(false)}
|
||||||
|
|||||||
@ -1,12 +1,6 @@
|
|||||||
import * as Dialog from '@radix-ui/react-dialog';
|
import * as Dialog from '@radix-ui/react-dialog';
|
||||||
|
|
||||||
const Modal = ({
|
const Modal = ({ isOpen, setIsOpen, title, children, modalClassName }) => {
|
||||||
isOpen,
|
|
||||||
setIsOpen,
|
|
||||||
title,
|
|
||||||
children,
|
|
||||||
modalClassName,
|
|
||||||
}) => {
|
|
||||||
return (
|
return (
|
||||||
<Dialog.Root open={isOpen} onOpenChange={setIsOpen}>
|
<Dialog.Root open={isOpen} onOpenChange={setIsOpen}>
|
||||||
<Dialog.Portal>
|
<Dialog.Portal>
|
||||||
|
|||||||
@ -330,7 +330,7 @@ const PaymentPlanSelector = ({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<Popup
|
<Popup
|
||||||
visible={popupVisible}
|
isOpen={popupVisible}
|
||||||
message={popupMessage}
|
message={popupMessage}
|
||||||
onConfirm={() => setPopupVisible(false)}
|
onConfirm={() => setPopupVisible(false)}
|
||||||
onCancel={() => setPopupVisible(false)}
|
onCancel={() => setPopupVisible(false)}
|
||||||
|
|||||||
@ -0,0 +1,310 @@
|
|||||||
|
import React, { useState, useRef, useCallback } from 'react';
|
||||||
|
import TreeView from '@/components/Structure/Competencies/TreeView';
|
||||||
|
import SectionHeader from '@/components/SectionHeader';
|
||||||
|
import { Award, CheckCircle } from 'lucide-react';
|
||||||
|
import SelectChoice from '@/components/SelectChoice';
|
||||||
|
import CheckBox from '@/components/CheckBox';
|
||||||
|
import Button from '@/components/Button';
|
||||||
|
import { useEstablishment } from '@/context/EstablishmentContext';
|
||||||
|
import {
|
||||||
|
fetchEstablishmentCompetencies,
|
||||||
|
createEstablishmentCompetencies,
|
||||||
|
deleteEstablishmentCompetencies,
|
||||||
|
} from '@/app/actions/schoolAction';
|
||||||
|
import { useCsrfToken } from '@/context/CsrfContext';
|
||||||
|
import { useNotification } from '@/context/NotificationContext';
|
||||||
|
|
||||||
|
const cycles = [
|
||||||
|
{ id: 1, label: 'Cycle 1' },
|
||||||
|
{ id: 2, label: 'Cycle 2' },
|
||||||
|
{ id: 3, label: 'Cycle 3' },
|
||||||
|
{ id: 4, label: 'Cycle 4' },
|
||||||
|
];
|
||||||
|
|
||||||
|
export default function CompetenciesList({
|
||||||
|
establishmentCompetencies,
|
||||||
|
onChangeCycle,
|
||||||
|
}) {
|
||||||
|
const [selectedCycle, setSelectedCycle] = useState(cycles[0].id);
|
||||||
|
const [showSelectedOnlyByCycle, setShowSelectedOnlyByCycle] = useState({
|
||||||
|
1: true,
|
||||||
|
2: true,
|
||||||
|
3: true,
|
||||||
|
4: true,
|
||||||
|
});
|
||||||
|
const [expandAllByCycle, setExpandAllByCycle] = useState({
|
||||||
|
1: false,
|
||||||
|
2: false,
|
||||||
|
3: false,
|
||||||
|
4: false,
|
||||||
|
});
|
||||||
|
const [hasSelectionByCycle, setHasSelectionByCycle] = useState({
|
||||||
|
1: false,
|
||||||
|
2: false,
|
||||||
|
3: false,
|
||||||
|
4: false,
|
||||||
|
});
|
||||||
|
const { selectedEstablishmentId } = useEstablishment();
|
||||||
|
const csrfToken = useCsrfToken();
|
||||||
|
const { showNotification } = useNotification();
|
||||||
|
|
||||||
|
// Référence vers le composant TreeView pour récupérer les compétences sélectionnées
|
||||||
|
const treeViewRef = useRef();
|
||||||
|
|
||||||
|
// Met à jour l'état de sélection à chaque changement dans TreeView
|
||||||
|
const handleSelectionChange = useCallback(
|
||||||
|
(selectedCompetencies) => {
|
||||||
|
setHasSelectionByCycle((prev) => ({
|
||||||
|
...prev,
|
||||||
|
[selectedCycle]: selectedCompetencies.length > 0,
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
[selectedCycle]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Filtrage : si showSelectedOnly, on affiche uniquement les compétences de l'établissement (state !== "none")
|
||||||
|
// sinon, on affiche toutes les compétences du cycle
|
||||||
|
const filteredData = (establishmentCompetencies.data || []).map(
|
||||||
|
(domaine) => ({
|
||||||
|
...domaine,
|
||||||
|
categories: domaine.categories.map((cat) => ({
|
||||||
|
...cat,
|
||||||
|
competences: showSelectedOnlyByCycle[selectedCycle]
|
||||||
|
? cat.competences.filter((c) => c.state !== 'none')
|
||||||
|
: cat.competences,
|
||||||
|
})),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const showSelectedOnly = showSelectedOnlyByCycle[selectedCycle];
|
||||||
|
const expandAll = expandAllByCycle[selectedCycle];
|
||||||
|
|
||||||
|
const handleShowSelectedOnlyChange = () => {
|
||||||
|
setShowSelectedOnlyByCycle((prev) => ({
|
||||||
|
...prev,
|
||||||
|
[selectedCycle]: !prev[selectedCycle],
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleExpandAllChange = () => {
|
||||||
|
setExpandAllByCycle((prev) => ({
|
||||||
|
...prev,
|
||||||
|
[selectedCycle]: !prev[selectedCycle],
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCycleChange = (e) => {
|
||||||
|
const value = Number(e.target.value);
|
||||||
|
setSelectedCycle(value);
|
||||||
|
setHasSelectionByCycle((prev) => ({
|
||||||
|
...prev,
|
||||||
|
[value]: false,
|
||||||
|
}));
|
||||||
|
// Réinitialise la sélection visuelle dans le TreeView
|
||||||
|
if (treeViewRef.current && treeViewRef.current.clearSelection) {
|
||||||
|
treeViewRef.current.clearSelection();
|
||||||
|
}
|
||||||
|
onChangeCycle(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = () => {
|
||||||
|
if (!treeViewRef.current || !treeViewRef.current.getSelectedCompetencies)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const selectedIds = treeViewRef.current.getSelectedCompetencies();
|
||||||
|
|
||||||
|
const toCreate = [];
|
||||||
|
const toDelete = [];
|
||||||
|
|
||||||
|
const selectedCustomKeys = new Set(
|
||||||
|
(establishmentCompetencies.data || []).flatMap((domaine) =>
|
||||||
|
domaine.categories.flatMap((cat) =>
|
||||||
|
cat.competences
|
||||||
|
.filter(
|
||||||
|
(c) =>
|
||||||
|
c.state === 'custom' &&
|
||||||
|
(selectedIds.includes(String(c.competence_id)) ||
|
||||||
|
selectedIds.includes(Number(c.competence_id)))
|
||||||
|
)
|
||||||
|
.map((c) => `${cat.categorie_id}__${c.nom.trim().toLowerCase()}`)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
(establishmentCompetencies.data || []).forEach((domaine) => {
|
||||||
|
domaine.categories.forEach((cat) => {
|
||||||
|
cat.competences.forEach((competence) => {
|
||||||
|
const isSelected =
|
||||||
|
selectedIds.includes(String(competence.competence_id)) ||
|
||||||
|
selectedIds.includes(Number(competence.competence_id));
|
||||||
|
const key = `${cat.categorie_id}__${competence.nom.trim().toLowerCase()}`;
|
||||||
|
|
||||||
|
// "none" sélectionné => à créer, sauf si une custom du même nom/catégorie est déjà sélectionnée
|
||||||
|
if (
|
||||||
|
competence.state === 'none' &&
|
||||||
|
isSelected &&
|
||||||
|
!selectedCustomKeys.has(key)
|
||||||
|
) {
|
||||||
|
toCreate.push({
|
||||||
|
category_id: cat.categorie_id,
|
||||||
|
establishment_id: selectedEstablishmentId,
|
||||||
|
nom: competence.nom,
|
||||||
|
});
|
||||||
|
} else if (competence.state === 'custom' && isSelected) {
|
||||||
|
// Suppression d'une compétence custom
|
||||||
|
toDelete.push({
|
||||||
|
competence_id: competence.competence_id, // id de EstablishmentCompetency
|
||||||
|
nom: competence.nom,
|
||||||
|
category_id: cat.categorie_id,
|
||||||
|
establishment_id: selectedEstablishmentId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const afterSuccess = () => {
|
||||||
|
if (treeViewRef.current && treeViewRef.current.clearSelection) {
|
||||||
|
treeViewRef.current.clearSelection();
|
||||||
|
}
|
||||||
|
setHasSelectionByCycle((prev) => ({
|
||||||
|
...prev,
|
||||||
|
[selectedCycle]: false,
|
||||||
|
}));
|
||||||
|
onChangeCycle(selectedCycle);
|
||||||
|
showNotification('Opération effectuée avec succès', 'success', 'Succès');
|
||||||
|
};
|
||||||
|
|
||||||
|
if (toCreate.length > 0 && toDelete.length > 0) {
|
||||||
|
Promise.all([
|
||||||
|
createEstablishmentCompetencies(toCreate, csrfToken),
|
||||||
|
deleteEstablishmentCompetencies(
|
||||||
|
toDelete.map((item) => item.competence_id),
|
||||||
|
csrfToken
|
||||||
|
),
|
||||||
|
])
|
||||||
|
.then(afterSuccess)
|
||||||
|
.catch((error) => {
|
||||||
|
showNotification(
|
||||||
|
error.message ||
|
||||||
|
'Erreur apparue lors de la mise à jour des compétences',
|
||||||
|
'error',
|
||||||
|
'Erreur'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
} else if (toCreate.length > 0) {
|
||||||
|
createEstablishmentCompetencies(toCreate, csrfToken)
|
||||||
|
.then(afterSuccess)
|
||||||
|
.catch((error) => {
|
||||||
|
showNotification(
|
||||||
|
error.message ||
|
||||||
|
'Erreur apparue lors de la mise à jour des compétences',
|
||||||
|
'error',
|
||||||
|
'Erreur'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
} else if (toDelete.length > 0) {
|
||||||
|
deleteEstablishmentCompetencies(
|
||||||
|
toDelete.map((item) => item.competence_id),
|
||||||
|
csrfToken
|
||||||
|
)
|
||||||
|
.then(afterSuccess)
|
||||||
|
.catch((error) => {
|
||||||
|
showNotification(
|
||||||
|
error.message ||
|
||||||
|
'Erreur apparue lors de la mise à jour des compétences',
|
||||||
|
'error',
|
||||||
|
'Erreur'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const hasSelection = hasSelectionByCycle[selectedCycle];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="h-full flex flex-col">
|
||||||
|
<SectionHeader
|
||||||
|
icon={Award}
|
||||||
|
title="Liste des compétences"
|
||||||
|
description="Gérez les compétences par cycle"
|
||||||
|
/>
|
||||||
|
{/* Zone filtres centrée et plus large */}
|
||||||
|
<div className="mb-6 flex justify-center">
|
||||||
|
<div className="w-full max-w-3xl flex flex-col gap-4 p-6 rounded-lg border border-emerald-200 shadow-sm bg-white/80 backdrop-blur-sm">
|
||||||
|
<div className="flex flex-col sm:flex-row sm:items-start gap-8">
|
||||||
|
{/* Select cycle */}
|
||||||
|
<div className="flex-1 min-w-[220px]">
|
||||||
|
<SelectChoice
|
||||||
|
name="cycle"
|
||||||
|
label="Cycle"
|
||||||
|
placeHolder="Sélectionnez un cycle"
|
||||||
|
choices={cycles.map((cycle) => ({
|
||||||
|
value: cycle.id,
|
||||||
|
label: cycle.label,
|
||||||
|
}))}
|
||||||
|
selected={selectedCycle}
|
||||||
|
callback={handleCycleChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/* Cases à cocher l'une sous l'autre */}
|
||||||
|
<div className="flex flex-col gap-4 min-w-[220px]">
|
||||||
|
<CheckBox
|
||||||
|
item={{ id: 'showSelectedOnly' }}
|
||||||
|
formData={{ showSelectedOnly }}
|
||||||
|
handleChange={handleShowSelectedOnlyChange}
|
||||||
|
fieldName="showSelectedOnly"
|
||||||
|
itemLabelFunc={() => 'Uniquement les compétences sélectionnées'}
|
||||||
|
horizontal={false}
|
||||||
|
/>
|
||||||
|
<CheckBox
|
||||||
|
item={{ id: 'expandAll' }}
|
||||||
|
formData={{ expandAll }}
|
||||||
|
handleChange={handleExpandAllChange}
|
||||||
|
fieldName="expandAll"
|
||||||
|
itemLabelFunc={() => 'Tout dérouler'}
|
||||||
|
horizontal={false}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/* Zone scrollable pour le TreeView */}
|
||||||
|
<div className="flex-1 min-h-0 overflow-y-auto">
|
||||||
|
<TreeView
|
||||||
|
ref={treeViewRef}
|
||||||
|
data={filteredData}
|
||||||
|
expandAll={expandAll}
|
||||||
|
onSelectionChange={handleSelectionChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/* Bouton submit centré en bas */}
|
||||||
|
<div className="flex justify-center mb-2 mt-6">
|
||||||
|
<Button
|
||||||
|
text="Sauvegarder"
|
||||||
|
className={`px-6 py-2 rounded-md shadow ${
|
||||||
|
!hasSelection
|
||||||
|
? 'bg-gray-300 text-gray-500 cursor-not-allowed'
|
||||||
|
: 'bg-emerald-500 text-white hover:bg-emerald-600'
|
||||||
|
}`}
|
||||||
|
onClick={handleSubmit}
|
||||||
|
primary
|
||||||
|
disabled={!hasSelection}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/* Légende en dessous du bouton, alignée à gauche */}
|
||||||
|
<div className="flex flex-row items-center gap-4 mb-4">
|
||||||
|
<span className="flex items-center gap-2 text-emerald-700 font-bold">
|
||||||
|
<CheckCircle className="w-4 h-4 text-emerald-500" />
|
||||||
|
Compétence requise
|
||||||
|
</span>
|
||||||
|
<span className="flex items-center gap-2 text-emerald-600 font-semibold">
|
||||||
|
Compétence sélectionnée ou créée
|
||||||
|
</span>
|
||||||
|
<span className="flex items-center gap-2 text-gray-500">
|
||||||
|
Compétence ignorée
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
144
Front-End/src/components/Structure/Competencies/TreeView.js
Normal file
144
Front-End/src/components/Structure/Competencies/TreeView.js
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
import React, {
|
||||||
|
useState,
|
||||||
|
useEffect,
|
||||||
|
forwardRef,
|
||||||
|
useImperativeHandle,
|
||||||
|
} from 'react';
|
||||||
|
import { CheckCircle, Circle } from 'lucide-react';
|
||||||
|
|
||||||
|
const TreeView = forwardRef(function TreeView(
|
||||||
|
{ data, expandAll, onSelectionChange },
|
||||||
|
ref
|
||||||
|
) {
|
||||||
|
const [openDomains, setOpenDomains] = useState({});
|
||||||
|
const [openCategories, setOpenCategories] = useState({});
|
||||||
|
const [selectedCompetencies, setSelectedCompetencies] = useState({}); // { [competence_id]: true }
|
||||||
|
|
||||||
|
// N'ouvre ou ne ferme tout que si expandAll change explicitement
|
||||||
|
useEffect(() => {
|
||||||
|
if (!data) return;
|
||||||
|
const allDomains = {};
|
||||||
|
const allCategories = {};
|
||||||
|
if (expandAll) {
|
||||||
|
data.forEach((domaine) => {
|
||||||
|
allDomains[domaine.domaine_id] = true;
|
||||||
|
domaine.categories.forEach((cat) => {
|
||||||
|
allCategories[cat.categorie_id] = true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
setOpenDomains(allDomains);
|
||||||
|
setOpenCategories(allCategories);
|
||||||
|
} else {
|
||||||
|
// On ne ferme tout que si l'utilisateur décoche explicitement "Tout dérouler"
|
||||||
|
setOpenDomains({});
|
||||||
|
setOpenCategories({});
|
||||||
|
}
|
||||||
|
}, [expandAll]); // <-- uniquement expandAll
|
||||||
|
|
||||||
|
// Appelle le callback à chaque changement de sélection
|
||||||
|
useEffect(() => {
|
||||||
|
if (onSelectionChange) {
|
||||||
|
const selected = Object.entries(selectedCompetencies)
|
||||||
|
.filter(([_, selected]) => selected)
|
||||||
|
.map(([id]) => id);
|
||||||
|
onSelectionChange(selected);
|
||||||
|
}
|
||||||
|
}, [selectedCompetencies, onSelectionChange]);
|
||||||
|
|
||||||
|
const toggleDomain = (id) =>
|
||||||
|
setOpenDomains((prev) => ({ ...prev, [id]: !prev[id] }));
|
||||||
|
const toggleCategory = (id) =>
|
||||||
|
setOpenCategories((prev) => ({ ...prev, [id]: !prev[id] }));
|
||||||
|
|
||||||
|
const handleCompetenceClick = (competence) => {
|
||||||
|
if (competence.state === 'required') return;
|
||||||
|
setSelectedCompetencies((prev) => {
|
||||||
|
const next = {
|
||||||
|
...prev,
|
||||||
|
[competence.competence_id]: !prev[competence.competence_id],
|
||||||
|
};
|
||||||
|
console.log(competence);
|
||||||
|
return next;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Pour exposer la sélection au parent
|
||||||
|
useImperativeHandle(ref, () => ({
|
||||||
|
getSelectedCompetencies: () => {
|
||||||
|
const selected = Object.entries(selectedCompetencies)
|
||||||
|
.filter(([_, selected]) => selected)
|
||||||
|
.map(([id]) => id);
|
||||||
|
return selected;
|
||||||
|
},
|
||||||
|
clearSelection: () => setSelectedCompetencies({}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{data.map((domaine) => (
|
||||||
|
<div key={domaine.domaine_id} className="mb-4">
|
||||||
|
<button
|
||||||
|
className="w-full text-left px-3 py-2 bg-emerald-100 hover:bg-emerald-200 rounded font-semibold text-emerald-800"
|
||||||
|
onClick={() => toggleDomain(domaine.domaine_id)}
|
||||||
|
>
|
||||||
|
{openDomains[domaine.domaine_id] ? '▼' : '►'} {domaine.domaine_nom}
|
||||||
|
</button>
|
||||||
|
{openDomains[domaine.domaine_id] && (
|
||||||
|
<div className="ml-4">
|
||||||
|
{domaine.categories.map((categorie) => (
|
||||||
|
<div key={categorie.categorie_id} className="mb-2">
|
||||||
|
<button
|
||||||
|
className="w-full text-left px-2 py-1 bg-emerald-50 hover:bg-emerald-100 rounded text-emerald-700"
|
||||||
|
onClick={() => toggleCategory(categorie.categorie_id)}
|
||||||
|
>
|
||||||
|
{openCategories[categorie.categorie_id] ? '▼' : '►'}
|
||||||
|
{categorie.categorie_nom}
|
||||||
|
</button>
|
||||||
|
{openCategories[categorie.categorie_id] && (
|
||||||
|
<ul className="ml-4">
|
||||||
|
{categorie.competences.map((competence) => {
|
||||||
|
const isSelected =
|
||||||
|
selectedCompetencies[competence.competence_id];
|
||||||
|
return (
|
||||||
|
<li
|
||||||
|
key={competence.competence_id}
|
||||||
|
className={`py-1 flex items-center gap-2 ${
|
||||||
|
competence.state === 'required'
|
||||||
|
? 'text-emerald-700 font-bold'
|
||||||
|
: competence.state === 'custom'
|
||||||
|
? isSelected
|
||||||
|
? 'text-gray-500 cursor-pointer hover:text-emerald-600'
|
||||||
|
: 'text-emerald-600 font-semibold cursor-pointer'
|
||||||
|
: isSelected
|
||||||
|
? 'text-emerald-600 font-semibold cursor-pointer'
|
||||||
|
: 'text-gray-500 cursor-pointer hover:text-emerald-600'
|
||||||
|
}`}
|
||||||
|
onClick={() => handleCompetenceClick(competence)}
|
||||||
|
style={{
|
||||||
|
cursor:
|
||||||
|
competence.state === 'required'
|
||||||
|
? 'default'
|
||||||
|
: 'pointer',
|
||||||
|
userSelect: 'none',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{competence.state === 'required' && (
|
||||||
|
<CheckCircle className="w-4 h-4 text-emerald-500" />
|
||||||
|
)}
|
||||||
|
{competence.nom}
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default TreeView;
|
||||||
@ -469,7 +469,7 @@ const ClassesSection = ({
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
setRemovePopupVisible(true);
|
setRemovePopupVisible(true);
|
||||||
setRemovePopupMessage(
|
setRemovePopupMessage(
|
||||||
'Attentions ! \nVous êtes sur le point de supprimer la classe ' +
|
'Attention ! \nVous êtes sur le point de supprimer la classe ' +
|
||||||
classe.atmosphere_name +
|
classe.atmosphere_name +
|
||||||
".\nÊtes-vous sûr(e) de vouloir poursuivre l'opération ?"
|
".\nÊtes-vous sûr(e) de vouloir poursuivre l'opération ?"
|
||||||
);
|
);
|
||||||
@ -554,14 +554,14 @@ const ClassesSection = ({
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Popup
|
<Popup
|
||||||
visible={popupVisible}
|
isOpen={popupVisible}
|
||||||
message={popupMessage}
|
message={popupMessage}
|
||||||
onConfirm={() => setPopupVisible(false)}
|
onConfirm={() => setPopupVisible(false)}
|
||||||
onCancel={() => setPopupVisible(false)}
|
onCancel={() => setPopupVisible(false)}
|
||||||
uniqueConfirmButton={true}
|
uniqueConfirmButton={true}
|
||||||
/>
|
/>
|
||||||
<Popup
|
<Popup
|
||||||
visible={removePopupVisible}
|
isOpen={removePopupVisible}
|
||||||
message={removePopupMessage}
|
message={removePopupMessage}
|
||||||
onConfirm={removePopupOnConfirm}
|
onConfirm={removePopupOnConfirm}
|
||||||
onCancel={() => setRemovePopupVisible(false)}
|
onCancel={() => setRemovePopupVisible(false)}
|
||||||
|
|||||||
@ -197,7 +197,7 @@ const SpecialitiesSection = ({
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
setRemovePopupVisible(true);
|
setRemovePopupVisible(true);
|
||||||
setRemovePopupMessage(
|
setRemovePopupMessage(
|
||||||
'Attentions ! \nVous êtes sur le point de supprimer la spécialité ' +
|
'Attention ! \nVous êtes sur le point de supprimer la spécialité ' +
|
||||||
speciality.name +
|
speciality.name +
|
||||||
".\nÊtes-vous sûr(e) de vouloir poursuivre l'opération ?"
|
".\nÊtes-vous sûr(e) de vouloir poursuivre l'opération ?"
|
||||||
);
|
);
|
||||||
@ -265,14 +265,14 @@ const SpecialitiesSection = ({
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Popup
|
<Popup
|
||||||
visible={popupVisible}
|
isOpen={popupVisible}
|
||||||
message={popupMessage}
|
message={popupMessage}
|
||||||
onConfirm={() => setPopupVisible(false)}
|
onConfirm={() => setPopupVisible(false)}
|
||||||
onCancel={() => setPopupVisible(false)}
|
onCancel={() => setPopupVisible(false)}
|
||||||
uniqueConfirmButton={true}
|
uniqueConfirmButton={true}
|
||||||
/>
|
/>
|
||||||
<Popup
|
<Popup
|
||||||
visible={removePopupVisible}
|
isOpen={removePopupVisible}
|
||||||
message={removePopupMessage}
|
message={removePopupMessage}
|
||||||
onConfirm={removePopupOnConfirm}
|
onConfirm={removePopupOnConfirm}
|
||||||
onCancel={() => setRemovePopupVisible(false)}
|
onCancel={() => setRemovePopupVisible(false)}
|
||||||
|
|||||||
@ -512,7 +512,7 @@ const TeachersSection = ({
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
setRemovePopupVisible(true);
|
setRemovePopupVisible(true);
|
||||||
setRemovePopupMessage(
|
setRemovePopupMessage(
|
||||||
"Attentions ! \nVous êtes sur le point de supprimer l'enseignant " +
|
"Attention ! \nVous êtes sur le point de supprimer l'enseignant " +
|
||||||
teacher.last_name +
|
teacher.last_name +
|
||||||
' ' +
|
' ' +
|
||||||
teacher.first_name +
|
teacher.first_name +
|
||||||
@ -589,14 +589,14 @@ const TeachersSection = ({
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Popup
|
<Popup
|
||||||
visible={popupVisible}
|
isOpen={popupVisible}
|
||||||
message={popupMessage}
|
message={popupMessage}
|
||||||
onConfirm={() => setPopupVisible(false)}
|
onConfirm={() => setPopupVisible(false)}
|
||||||
onCancel={() => setPopupVisible(false)}
|
onCancel={() => setPopupVisible(false)}
|
||||||
uniqueConfirmButton={true}
|
uniqueConfirmButton={true}
|
||||||
/>
|
/>
|
||||||
<Popup
|
<Popup
|
||||||
visible={removePopupVisible}
|
isOpen={removePopupVisible}
|
||||||
message={removePopupMessage}
|
message={removePopupMessage}
|
||||||
onConfirm={removePopupOnConfirm}
|
onConfirm={removePopupOnConfirm}
|
||||||
onCancel={() => setRemovePopupVisible(false)}
|
onCancel={() => setRemovePopupVisible(false)}
|
||||||
|
|||||||
@ -155,7 +155,7 @@ export default function FileUploadDocuSeal({
|
|||||||
return (
|
return (
|
||||||
<div className="h-full flex flex-col mt-4 space-y-6">
|
<div className="h-full flex flex-col mt-4 space-y-6">
|
||||||
<Popup
|
<Popup
|
||||||
visible={popupVisible}
|
isOpen={popupVisible}
|
||||||
message={popupMessage}
|
message={popupMessage}
|
||||||
onConfirm={() => setPopupVisible(false)}
|
onConfirm={() => setPopupVisible(false)}
|
||||||
onCancel={() => setPopupVisible(false)}
|
onCancel={() => setPopupVisible(false)}
|
||||||
|
|||||||
@ -592,7 +592,7 @@ export default function FilesGroupsManagement({
|
|||||||
handleDelete={handleDelete}
|
handleDelete={handleDelete}
|
||||||
/>
|
/>
|
||||||
<Popup
|
<Popup
|
||||||
visible={removePopupVisible}
|
isOpen={removePopupVisible}
|
||||||
message={removePopupMessage}
|
message={removePopupMessage}
|
||||||
onConfirm={removePopupOnConfirm}
|
onConfirm={removePopupOnConfirm}
|
||||||
onCancel={() => setRemovePopupVisible(false)}
|
onCancel={() => setRemovePopupVisible(false)}
|
||||||
|
|||||||
@ -348,7 +348,7 @@ export default function ParentFilesSection({
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Popup
|
<Popup
|
||||||
visible={removePopupVisible}
|
isOpen={removePopupVisible}
|
||||||
message={removePopupMessage}
|
message={removePopupMessage}
|
||||||
onConfirm={removePopupOnConfirm}
|
onConfirm={removePopupOnConfirm}
|
||||||
onCancel={() => setRemovePopupVisible(false)}
|
onCancel={() => setRemovePopupVisible(false)}
|
||||||
|
|||||||
@ -384,14 +384,14 @@ const DiscountsSection = ({
|
|||||||
emptyMessage={emptyMessage}
|
emptyMessage={emptyMessage}
|
||||||
/>
|
/>
|
||||||
<Popup
|
<Popup
|
||||||
visible={popupVisible}
|
isOpen={popupVisible}
|
||||||
message={popupMessage}
|
message={popupMessage}
|
||||||
onConfirm={() => setPopupVisible(false)}
|
onConfirm={() => setPopupVisible(false)}
|
||||||
onCancel={() => setPopupVisible(false)}
|
onCancel={() => setPopupVisible(false)}
|
||||||
uniqueConfirmButton={true}
|
uniqueConfirmButton={true}
|
||||||
/>
|
/>
|
||||||
<Popup
|
<Popup
|
||||||
visible={removePopupVisible}
|
isOpen={removePopupVisible}
|
||||||
message={removePopupMessage}
|
message={removePopupMessage}
|
||||||
onConfirm={removePopupOnConfirm}
|
onConfirm={removePopupOnConfirm}
|
||||||
onCancel={() => setRemovePopupVisible(false)}
|
onCancel={() => setRemovePopupVisible(false)}
|
||||||
|
|||||||
@ -42,6 +42,7 @@ export const BE_SCHOOL_FEES_URL = `${BASE_URL}/School/fees`;
|
|||||||
export const BE_SCHOOL_DISCOUNTS_URL = `${BASE_URL}/School/discounts`;
|
export const BE_SCHOOL_DISCOUNTS_URL = `${BASE_URL}/School/discounts`;
|
||||||
export const BE_SCHOOL_PAYMENT_PLANS_URL = `${BASE_URL}/School/paymentPlans`;
|
export const BE_SCHOOL_PAYMENT_PLANS_URL = `${BASE_URL}/School/paymentPlans`;
|
||||||
export const BE_SCHOOL_PAYMENT_MODES_URL = `${BASE_URL}/School/paymentModes`;
|
export const BE_SCHOOL_PAYMENT_MODES_URL = `${BASE_URL}/School/paymentModes`;
|
||||||
|
export const BE_SCHOOL_ESTABLISHMENT_COMPETENCIES_URL = `${BASE_URL}/School/establishmentCompetencies`;
|
||||||
|
|
||||||
// ESTABLISHMENT
|
// ESTABLISHMENT
|
||||||
export const BE_SCHOOL_ESTABLISHMENT_URL = `${BASE_URL}/Establishment/establishments`;
|
export const BE_SCHOOL_ESTABLISHMENT_URL = `${BASE_URL}/Establishment/establishments`;
|
||||||
|
|||||||
Reference in New Issue
Block a user