mirror of
https://git.v0id.ovh/n3wt-innov/n3wt-school.git
synced 2026-01-28 23:43:22 +00:00
feat: Sauvegarde des compétences d'un élève [#16]
This commit is contained in:
@ -14,22 +14,6 @@ class Category(models.Model):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
class Competency(models.Model):
|
|
||||||
name = models.TextField()
|
|
||||||
end_of_cycle = models.BooleanField(default=False, null=True, blank=True)
|
|
||||||
level = models.CharField(max_length=50, null=True, blank=True)
|
|
||||||
category = models.ForeignKey(Category, on_delete=models.CASCADE, related_name='competencies')
|
|
||||||
|
|
||||||
establishments = models.ManyToManyField(
|
|
||||||
'Establishment.Establishment',
|
|
||||||
through='School.EstablishmentCompetency',
|
|
||||||
related_name='competencies',
|
|
||||||
blank=True
|
|
||||||
)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.name
|
|
||||||
|
|
||||||
class PaymentPlanType(models.Model):
|
class PaymentPlanType(models.Model):
|
||||||
code = models.CharField(max_length=50, unique=True)
|
code = models.CharField(max_length=50, unique=True)
|
||||||
label = models.CharField(max_length=255)
|
label = models.CharField(max_length=255)
|
||||||
|
|||||||
@ -1,8 +1,7 @@
|
|||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from Common.models import (
|
from Common.models import (
|
||||||
Domain,
|
Domain,
|
||||||
Category,
|
Category
|
||||||
Competency
|
|
||||||
)
|
)
|
||||||
|
|
||||||
class DomainSerializer(serializers.ModelSerializer):
|
class DomainSerializer(serializers.ModelSerializer):
|
||||||
@ -13,9 +12,4 @@ class DomainSerializer(serializers.ModelSerializer):
|
|||||||
class CategorySerializer(serializers.ModelSerializer):
|
class CategorySerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Category
|
model = Category
|
||||||
fields = '__all__'
|
|
||||||
|
|
||||||
class CompetencySerializer(serializers.ModelSerializer):
|
|
||||||
class Meta:
|
|
||||||
model = Competency
|
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
@ -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 Common.models import Domain, Category, Competency, PaymentModeType, PaymentPlanType, Cycle, Level
|
from Common.models import Domain, Category, PaymentModeType, PaymentPlanType, Cycle, Level
|
||||||
|
from School.models import Competency
|
||||||
|
|
||||||
@receiver(post_migrate)
|
@receiver(post_migrate)
|
||||||
def common_post_migrate(sender, **kwargs):
|
def common_post_migrate(sender, **kwargs):
|
||||||
|
|||||||
@ -3,7 +3,6 @@ from django.urls import path, re_path
|
|||||||
from .views import (
|
from .views import (
|
||||||
DomainListCreateView, DomainDetailView,
|
DomainListCreateView, DomainDetailView,
|
||||||
CategoryListCreateView, CategoryDetailView,
|
CategoryListCreateView, CategoryDetailView,
|
||||||
CompetencyListCreateView, CompetencyDetailView,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
@ -12,7 +11,4 @@ urlpatterns = [
|
|||||||
|
|
||||||
re_path(r'^categories$', CategoryListCreateView.as_view(), name="category_list_create"),
|
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'^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"),
|
|
||||||
]
|
]
|
||||||
@ -3,15 +3,14 @@ from django.views.decorators.csrf import ensure_csrf_cookie, csrf_protect
|
|||||||
from django.utils.decorators import method_decorator
|
from django.utils.decorators import method_decorator
|
||||||
from rest_framework.parsers import JSONParser
|
from rest_framework.parsers import JSONParser
|
||||||
from rest_framework.views import APIView
|
from rest_framework.views import APIView
|
||||||
|
from rest_framework import status
|
||||||
from .models import (
|
from .models import (
|
||||||
Domain,
|
Domain,
|
||||||
Category,
|
Category
|
||||||
Competency
|
|
||||||
)
|
)
|
||||||
from .serializers import (
|
from .serializers import (
|
||||||
DomainSerializer,
|
DomainSerializer,
|
||||||
CategorySerializer,
|
CategorySerializer
|
||||||
CompetencySerializer
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@method_decorator(csrf_protect, name='dispatch')
|
@method_decorator(csrf_protect, name='dispatch')
|
||||||
@ -109,57 +108,3 @@ class CategoryDetailView(APIView):
|
|||||||
return JsonResponse({'message': 'Deleted'}, safe=False)
|
return JsonResponse({'message': 'Deleted'}, safe=False)
|
||||||
except Category.DoesNotExist:
|
except Category.DoesNotExist:
|
||||||
return JsonResponse({'error': 'No object found'}, status=status.HTTP_404_NOT_FOUND)
|
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)
|
|
||||||
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)
|
|
||||||
|
|||||||
@ -7,8 +7,7 @@ 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
|
from N3wtSchool.bdd import delete_object, getAllObjects
|
||||||
from School.models import EstablishmentCompetency
|
from School.models import EstablishmentCompetency, Competency
|
||||||
from Common.models import Competency
|
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
|
|
||||||
@method_decorator(csrf_protect, name='dispatch')
|
@method_decorator(csrf_protect, name='dispatch')
|
||||||
|
|||||||
@ -116,6 +116,22 @@ class PaymentMode(models.Model):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.mode.label} - {self.get_type_display()}"
|
return f"{self.mode.label} - {self.get_type_display()}"
|
||||||
|
|
||||||
|
class Competency(models.Model):
|
||||||
|
name = models.TextField()
|
||||||
|
end_of_cycle = models.BooleanField(default=False, null=True, blank=True)
|
||||||
|
level = models.CharField(max_length=50, null=True, blank=True)
|
||||||
|
category = models.ForeignKey('Common.Category', on_delete=models.CASCADE, related_name='competencies')
|
||||||
|
|
||||||
|
establishments = models.ManyToManyField(
|
||||||
|
'Establishment.Establishment',
|
||||||
|
through='School.EstablishmentCompetency',
|
||||||
|
related_name='competencies',
|
||||||
|
blank=True
|
||||||
|
)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
class EstablishmentCompetency(models.Model):
|
class EstablishmentCompetency(models.Model):
|
||||||
"""
|
"""
|
||||||
Relation entre un établissement et une compétence.
|
Relation entre un établissement et une compétence.
|
||||||
@ -123,7 +139,7 @@ class EstablishmentCompetency(models.Model):
|
|||||||
"""
|
"""
|
||||||
establishment = models.ForeignKey('Establishment.Establishment', on_delete=models.CASCADE)
|
establishment = models.ForeignKey('Establishment.Establishment', on_delete=models.CASCADE)
|
||||||
competency = models.ForeignKey(
|
competency = models.ForeignKey(
|
||||||
'Common.Competency', on_delete=models.CASCADE, null=True, blank=True,
|
'School.Competency', on_delete=models.CASCADE, null=True, blank=True,
|
||||||
help_text="Compétence de référence (optionnelle si custom)"
|
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_name = models.TextField(null=True, blank=True, help_text="Nom de la compétence custom")
|
||||||
|
|||||||
@ -9,7 +9,8 @@ from .models import (
|
|||||||
Fee,
|
Fee,
|
||||||
PaymentPlan,
|
PaymentPlan,
|
||||||
PaymentMode,
|
PaymentMode,
|
||||||
EstablishmentCompetency
|
EstablishmentCompetency,
|
||||||
|
Competency
|
||||||
)
|
)
|
||||||
from Auth.models import Profile, ProfileRole
|
from Auth.models import Profile, ProfileRole
|
||||||
from Subscriptions.models import Student
|
from Subscriptions.models import Student
|
||||||
@ -19,6 +20,12 @@ from N3wtSchool import settings
|
|||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
import pytz
|
import pytz
|
||||||
|
|
||||||
|
|
||||||
|
class CompetencySerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = Competency
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
class EstablishmentCompetencySerializer(serializers.ModelSerializer):
|
class EstablishmentCompetencySerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = EstablishmentCompetency
|
model = EstablishmentCompetency
|
||||||
|
|||||||
@ -9,6 +9,7 @@ from .views import (
|
|||||||
DiscountListCreateView, DiscountDetailView,
|
DiscountListCreateView, DiscountDetailView,
|
||||||
PaymentPlanListCreateView, PaymentPlanDetailView,
|
PaymentPlanListCreateView, PaymentPlanDetailView,
|
||||||
PaymentModeListCreateView, PaymentModeDetailView,
|
PaymentModeListCreateView, PaymentModeDetailView,
|
||||||
|
CompetencyListCreateView, CompetencyDetailView,
|
||||||
EstablishmentCompetencyListCreateView, EstablishmentCompetencyDetailView,
|
EstablishmentCompetencyListCreateView, EstablishmentCompetencyDetailView,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -37,6 +38,9 @@ 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'^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$', EstablishmentCompetencyListCreateView.as_view(), name="establishment_competency_list_create"),
|
||||||
re_path(r'^establishmentCompetencies/(?P<id>[0-9]+)$', EstablishmentCompetencyDetailView.as_view(), name="establishment_competency_detail"),
|
re_path(r'^establishmentCompetencies/(?P<id>[0-9]+)$', EstablishmentCompetencyDetailView.as_view(), name="establishment_competency_detail"),
|
||||||
]
|
]
|
||||||
@ -13,7 +13,8 @@ from .models import (
|
|||||||
Fee,
|
Fee,
|
||||||
PaymentPlan,
|
PaymentPlan,
|
||||||
PaymentMode,
|
PaymentMode,
|
||||||
EstablishmentCompetency
|
EstablishmentCompetency,
|
||||||
|
Competency
|
||||||
)
|
)
|
||||||
from .serializers import (
|
from .serializers import (
|
||||||
TeacherSerializer,
|
TeacherSerializer,
|
||||||
@ -24,12 +25,14 @@ from .serializers import (
|
|||||||
FeeSerializer,
|
FeeSerializer,
|
||||||
PaymentPlanSerializer,
|
PaymentPlanSerializer,
|
||||||
PaymentModeSerializer,
|
PaymentModeSerializer,
|
||||||
EstablishmentCompetencySerializer
|
EstablishmentCompetencySerializer,
|
||||||
|
CompetencySerializer
|
||||||
)
|
)
|
||||||
from Common.models import Domain, Category, Competency
|
from Common.models import Domain, Category
|
||||||
from N3wtSchool.bdd import delete_object, getAllObjects, getObject
|
from N3wtSchool.bdd import delete_object, getAllObjects, getObject
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
from Subscriptions.models import Student, StudentCompetency
|
||||||
|
|
||||||
@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')
|
||||||
@ -417,6 +420,60 @@ 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 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)
|
||||||
|
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(csrf_protect, name='dispatch')
|
||||||
@method_decorator(ensure_csrf_cookie, name='dispatch')
|
@method_decorator(ensure_csrf_cookie, name='dispatch')
|
||||||
class EstablishmentCompetencyListCreateView(APIView):
|
class EstablishmentCompetencyListCreateView(APIView):
|
||||||
@ -541,6 +598,14 @@ class EstablishmentCompetencyListCreateView(APIView):
|
|||||||
custom_category=category,
|
custom_category=category,
|
||||||
is_required=False
|
is_required=False
|
||||||
)
|
)
|
||||||
|
# Associer à tous les élèves de l'établissement
|
||||||
|
students = Student.objects.filter(associated_class__establishment_id=establishment_id)
|
||||||
|
for student in students:
|
||||||
|
StudentCompetency.objects.get_or_create(
|
||||||
|
student=student,
|
||||||
|
establishment_competency=ec
|
||||||
|
)
|
||||||
|
|
||||||
created.append({
|
created.append({
|
||||||
"competence_id": ec.id,
|
"competence_id": ec.id,
|
||||||
"nom": ec.custom_name,
|
"nom": ec.custom_name,
|
||||||
@ -558,6 +623,7 @@ class EstablishmentCompetencyListCreateView(APIView):
|
|||||||
def delete(self, request):
|
def delete(self, request):
|
||||||
"""
|
"""
|
||||||
Supprime une ou plusieurs compétences custom (EstablishmentCompetency) à partir d'une liste d'IDs.
|
Supprime une ou plusieurs compétences custom (EstablishmentCompetency) à partir d'une liste d'IDs.
|
||||||
|
Supprime aussi les StudentCompetency associés.
|
||||||
Attendu dans le body :
|
Attendu dans le body :
|
||||||
{
|
{
|
||||||
"ids": [1, 2, 3, ...]
|
"ids": [1, 2, 3, ...]
|
||||||
@ -571,6 +637,8 @@ class EstablishmentCompetencyListCreateView(APIView):
|
|||||||
for ec_id in ids:
|
for ec_id in ids:
|
||||||
try:
|
try:
|
||||||
ec = EstablishmentCompetency.objects.get(id=ec_id)
|
ec = EstablishmentCompetency.objects.get(id=ec_id)
|
||||||
|
# Supprimer les StudentCompetency associés
|
||||||
|
StudentCompetency.objects.filter(establishment_competency=ec).delete()
|
||||||
ec.delete()
|
ec.delete()
|
||||||
deleted.append(ec_id)
|
deleted.append(ec_id)
|
||||||
except EstablishmentCompetency.DoesNotExist:
|
except EstablishmentCompetency.DoesNotExist:
|
||||||
|
|||||||
@ -324,15 +324,19 @@ class RegistrationSchoolFileTemplate(models.Model):
|
|||||||
|
|
||||||
class StudentCompetency(models.Model):
|
class StudentCompetency(models.Model):
|
||||||
student = models.ForeignKey('Subscriptions.Student', on_delete=models.CASCADE, related_name='competency_scores')
|
student = models.ForeignKey('Subscriptions.Student', on_delete=models.CASCADE, related_name='competency_scores')
|
||||||
competency = models.ForeignKey('Common.Competency', on_delete=models.CASCADE, related_name='student_scores')
|
establishment_competency = models.ForeignKey('School.EstablishmentCompetency', on_delete=models.CASCADE, related_name='student_scores')
|
||||||
score = models.IntegerField(null=True, blank=True)
|
score = models.IntegerField(null=True, blank=True)
|
||||||
comment = models.TextField(blank=True, null=True)
|
comment = models.TextField(blank=True, null=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
unique_together = ('student', 'competency')
|
unique_together = ('student', 'establishment_competency')
|
||||||
|
|
||||||
|
indexes = [
|
||||||
|
models.Index(fields=['student', 'establishment_competency']),
|
||||||
|
]
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.student} - {self.competency.name} - Score: {self.score}"
|
return f"{self.student} - {self.establishment_competency} - Score: {self.score}"
|
||||||
|
|
||||||
####### Parent files templates (par dossier d'inscription) #######
|
####### Parent files templates (par dossier d'inscription) #######
|
||||||
class RegistrationParentFileTemplate(models.Model):
|
class RegistrationParentFileTemplate(models.Model):
|
||||||
|
|||||||
@ -17,7 +17,6 @@ import Subscriptions.util as util
|
|||||||
from Subscriptions.serializers import RegistrationFormSerializer, RegistrationSchoolFileTemplateSerializer, RegistrationParentFileTemplateSerializer
|
from Subscriptions.serializers import RegistrationFormSerializer, RegistrationSchoolFileTemplateSerializer, RegistrationParentFileTemplateSerializer
|
||||||
from Subscriptions.pagination import CustomSubscriptionPagination
|
from Subscriptions.pagination import CustomSubscriptionPagination
|
||||||
from Subscriptions.models import (
|
from Subscriptions.models import (
|
||||||
Student,
|
|
||||||
Guardian,
|
Guardian,
|
||||||
RegistrationForm,
|
RegistrationForm,
|
||||||
RegistrationSchoolFileTemplate,
|
RegistrationSchoolFileTemplate,
|
||||||
@ -26,7 +25,7 @@ from Subscriptions.models import (
|
|||||||
StudentCompetency
|
StudentCompetency
|
||||||
)
|
)
|
||||||
from Subscriptions.automate import updateStateMachine
|
from Subscriptions.automate import updateStateMachine
|
||||||
from Common.models import Competency
|
from School.models import EstablishmentCompetency
|
||||||
|
|
||||||
from N3wtSchool import settings, bdd
|
from N3wtSchool import settings, bdd
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
@ -246,6 +245,7 @@ class RegisterFormWithIdView(APIView):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
studentForm_data = request.data.get('data', '{}')
|
studentForm_data = request.data.get('data', '{}')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
data = json.loads(studentForm_data)
|
data = json.loads(studentForm_data)
|
||||||
except json.JSONDecodeError:
|
except json.JSONDecodeError:
|
||||||
@ -306,13 +306,13 @@ class RegisterFormWithIdView(APIView):
|
|||||||
# L'école doit désormais valider le dossier d'inscription
|
# L'école doit désormais valider le dossier d'inscription
|
||||||
try:
|
try:
|
||||||
# Génération de la fiche d'inscription au format PDF
|
# Génération de la fiche d'inscription au format PDF
|
||||||
base_dir = os.path.join(settings.MEDIA_ROOT, f"registration_files/dossier_rf_{registerForm.pk}")
|
# base_dir = os.path.join(settings.MEDIA_ROOT, f"registration_files/dossier_rf_{registerForm.pk}")
|
||||||
os.makedirs(base_dir, exist_ok=True)
|
# os.makedirs(base_dir, exist_ok=True)
|
||||||
|
|
||||||
# Fichier PDF initial
|
# # Fichier PDF initial
|
||||||
initial_pdf = f"{base_dir}/Inscription_{registerForm.student.last_name}_{registerForm.student.first_name}.pdf"
|
# initial_pdf = f"{base_dir}/Inscription_{registerForm.student.last_name}_{registerForm.student.first_name}.pdf"
|
||||||
registerForm.registration_file = util.rfToPDF(registerForm, initial_pdf)
|
# registerForm.registration_file = util.rfToPDF(registerForm, initial_pdf)
|
||||||
registerForm.save()
|
# registerForm.save()
|
||||||
|
|
||||||
# Mise à jour de l'automate
|
# Mise à jour de l'automate
|
||||||
# Vérification de la présence du fichier SEPA
|
# Vérification de la présence du fichier SEPA
|
||||||
@ -376,7 +376,6 @@ class RegisterFormWithIdView(APIView):
|
|||||||
File(merged_pdf_content),
|
File(merged_pdf_content),
|
||||||
save=True
|
save=True
|
||||||
)
|
)
|
||||||
|
|
||||||
# Valorisation des StudentCompetency pour l'élève
|
# Valorisation des StudentCompetency pour l'élève
|
||||||
try:
|
try:
|
||||||
student = registerForm.student
|
student = registerForm.student
|
||||||
@ -384,15 +383,19 @@ class RegisterFormWithIdView(APIView):
|
|||||||
if student.level:
|
if student.level:
|
||||||
cycle = student.level.cycle.number
|
cycle = student.level.cycle.number
|
||||||
if cycle:
|
if cycle:
|
||||||
competencies = Competency.objects.filter(
|
# Récupérer les EstablishmentCompetency de l'établissement et du cycle de l'élève
|
||||||
category__domain__cycle=cycle
|
establishment_competencies = EstablishmentCompetency.objects.filter(
|
||||||
).filter(
|
establishment=registerForm.establishment,
|
||||||
Q(end_of_cycle=True) | Q(level=student.level.name)
|
custom_category__domain__cycle=cycle
|
||||||
|
) | EstablishmentCompetency.objects.filter(
|
||||||
|
establishment=registerForm.establishment,
|
||||||
|
competency__category__domain__cycle=cycle
|
||||||
)
|
)
|
||||||
for comp in competencies:
|
establishment_competencies = establishment_competencies.distinct()
|
||||||
|
for ec in establishment_competencies:
|
||||||
StudentCompetency.objects.get_or_create(
|
StudentCompetency.objects.get_or_create(
|
||||||
student=student,
|
student=student,
|
||||||
competency=comp
|
establishment_competency=ec
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Erreur lors de la valorisation des StudentCompetency: {e}")
|
logger.error(f"Erreur lors de la valorisation des StudentCompetency: {e}")
|
||||||
|
|||||||
@ -6,7 +6,8 @@ from drf_yasg import openapi
|
|||||||
from django.views.decorators.csrf import ensure_csrf_cookie, csrf_protect
|
from django.views.decorators.csrf import ensure_csrf_cookie, csrf_protect
|
||||||
from django.utils.decorators import method_decorator
|
from django.utils.decorators import method_decorator
|
||||||
from Subscriptions.models import StudentCompetency, Student
|
from Subscriptions.models import StudentCompetency, Student
|
||||||
from Common.models import Domain, Competency
|
from Common.models import Domain
|
||||||
|
from School.models import Competency
|
||||||
from N3wtSchool.bdd import delete_object
|
from N3wtSchool.bdd import delete_object
|
||||||
|
|
||||||
@method_decorator(csrf_protect, name='dispatch')
|
@method_decorator(csrf_protect, name='dispatch')
|
||||||
@ -21,10 +22,14 @@ class StudentCompetencyListCreateView(APIView):
|
|||||||
except Student.DoesNotExist:
|
except Student.DoesNotExist:
|
||||||
return JsonResponse({'error': 'Élève introuvable'}, status=404)
|
return JsonResponse({'error': 'Élève introuvable'}, status=404)
|
||||||
|
|
||||||
student_competencies = StudentCompetency.objects.filter(student=student).select_related('competency', 'competency__category', 'competency__category__domain')
|
student_competencies = StudentCompetency.objects.filter(student=student).select_related(
|
||||||
|
'establishment_competency',
|
||||||
# On ne garde que les IDs des compétences de l'élève
|
'establishment_competency__competency',
|
||||||
student_competency_ids = set(sc.competency_id for sc in student_competencies)
|
'establishment_competency__competency__category',
|
||||||
|
'establishment_competency__competency__category__domain',
|
||||||
|
'establishment_competency__custom_category',
|
||||||
|
'establishment_competency__custom_category__domain',
|
||||||
|
)
|
||||||
|
|
||||||
result = []
|
result = []
|
||||||
total_competencies = 0
|
total_competencies = 0
|
||||||
@ -44,15 +49,26 @@ class StudentCompetencyListCreateView(APIView):
|
|||||||
}
|
}
|
||||||
# On ne boucle que sur les compétences du student pour cette catégorie
|
# On ne boucle que sur les compétences du student pour cette catégorie
|
||||||
for sc in student_competencies:
|
for sc in student_competencies:
|
||||||
comp = sc.competency
|
ec = sc.establishment_competency
|
||||||
if comp.category_id == categorie.id:
|
# Cas compétence de référence
|
||||||
|
if ec.competency and ec.competency.category_id == categorie.id:
|
||||||
|
comp = ec.competency
|
||||||
categorie_dict["competences"].append({
|
categorie_dict["competences"].append({
|
||||||
"competence_id": comp.id,
|
"competence_id": ec.id, # <-- retourne l'id de l'EstablishmentCompetency
|
||||||
"nom": comp.name,
|
"nom": comp.name,
|
||||||
"score": sc.score,
|
"score": sc.score,
|
||||||
"comment": sc.comment or "",
|
"comment": sc.comment or "",
|
||||||
})
|
})
|
||||||
total_competencies += 1
|
total_competencies += 1
|
||||||
|
# Cas compétence custom
|
||||||
|
elif ec.competency is None and ec.custom_category_id == categorie.id:
|
||||||
|
categorie_dict["competences"].append({
|
||||||
|
"competence_id": ec.id, # <-- retourne l'id de l'EstablishmentCompetency
|
||||||
|
"nom": ec.custom_name,
|
||||||
|
"score": sc.score,
|
||||||
|
"comment": sc.comment or "",
|
||||||
|
})
|
||||||
|
total_competencies += 1
|
||||||
if categorie_dict["competences"]:
|
if categorie_dict["competences"]:
|
||||||
domaine_dict["categories"].append(categorie_dict)
|
domaine_dict["categories"].append(categorie_dict)
|
||||||
if domaine_dict["categories"]:
|
if domaine_dict["categories"]:
|
||||||
@ -77,14 +93,13 @@ class StudentCompetencyListCreateView(APIView):
|
|||||||
comp_id = item.get("competenceId")
|
comp_id = item.get("competenceId")
|
||||||
grade = item.get("grade")
|
grade = item.get("grade")
|
||||||
student_id = item.get('studentId')
|
student_id = item.get('studentId')
|
||||||
print(f'lecture des données : {comp_id} - {grade} - {student_id}')
|
|
||||||
if comp_id is None or grade is None:
|
if comp_id is None or grade is None:
|
||||||
errors.append({"competenceId": comp_id, "error": "champ manquant"})
|
errors.append({"competenceId": comp_id, "error": "champ manquant"})
|
||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
# Ajoute le filtre student_id
|
# Ajoute le filtre student_id
|
||||||
sc = StudentCompetency.objects.get(
|
sc = StudentCompetency.objects.get(
|
||||||
competency_id=comp_id,
|
establishment_competency_id=comp_id,
|
||||||
student_id=student_id
|
student_id=student_id
|
||||||
)
|
)
|
||||||
sc.score = grade
|
sc.score = grade
|
||||||
|
|||||||
16
Front-End/package-lock.json
generated
16
Front-End/package-lock.json
generated
@ -23,6 +23,7 @@
|
|||||||
"next-logger": "^5.0.1",
|
"next-logger": "^5.0.1",
|
||||||
"pino": "^9.6.0",
|
"pino": "^9.6.0",
|
||||||
"react": "^18",
|
"react": "^18",
|
||||||
|
"react-circular-progressbar": "^2.2.0",
|
||||||
"react-cookie": "^7.2.0",
|
"react-cookie": "^7.2.0",
|
||||||
"react-dnd": "^16.0.1",
|
"react-dnd": "^16.0.1",
|
||||||
"react-dnd-html5-backend": "^16.0.1",
|
"react-dnd-html5-backend": "^16.0.1",
|
||||||
@ -5015,6 +5016,15 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-circular-progressbar": {
|
||||||
|
"version": "2.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-circular-progressbar/-/react-circular-progressbar-2.2.0.tgz",
|
||||||
|
"integrity": "sha512-cgyqEHOzB0nWMZjKfWN3MfSa1LV3OatcDjPz68lchXQUEiBD5O1WsAtoVK4/DSL0B4USR//cTdok4zCBkq8X5g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=0.14.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-cookie": {
|
"node_modules/react-cookie": {
|
||||||
"version": "7.2.2",
|
"version": "7.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/react-cookie/-/react-cookie-7.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/react-cookie/-/react-cookie-7.2.2.tgz",
|
||||||
@ -10037,6 +10047,12 @@
|
|||||||
"loose-envify": "^1.1.0"
|
"loose-envify": "^1.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"react-circular-progressbar": {
|
||||||
|
"version": "2.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-circular-progressbar/-/react-circular-progressbar-2.2.0.tgz",
|
||||||
|
"integrity": "sha512-cgyqEHOzB0nWMZjKfWN3MfSa1LV3OatcDjPz68lchXQUEiBD5O1WsAtoVK4/DSL0B4USR//cTdok4zCBkq8X5g==",
|
||||||
|
"requires": {}
|
||||||
|
},
|
||||||
"react-cookie": {
|
"react-cookie": {
|
||||||
"version": "7.2.2",
|
"version": "7.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/react-cookie/-/react-cookie-7.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/react-cookie/-/react-cookie-7.2.2.tgz",
|
||||||
|
|||||||
@ -26,6 +26,7 @@
|
|||||||
"next-logger": "^5.0.1",
|
"next-logger": "^5.0.1",
|
||||||
"pino": "^9.6.0",
|
"pino": "^9.6.0",
|
||||||
"react": "^18",
|
"react": "^18",
|
||||||
|
"react-circular-progressbar": "^2.2.0",
|
||||||
"react-cookie": "^7.2.0",
|
"react-cookie": "^7.2.0",
|
||||||
"react-dnd": "^16.0.1",
|
"react-dnd": "^16.0.1",
|
||||||
"react-dnd-html5-backend": "^16.0.1",
|
"react-dnd-html5-backend": "^16.0.1",
|
||||||
|
|||||||
@ -8,11 +8,15 @@ import WorkPlan from '@/components/Grades/WorkPlan';
|
|||||||
import Homeworks from '@/components/Grades/Homeworks';
|
import Homeworks from '@/components/Grades/Homeworks';
|
||||||
import SpecificEvaluations from '@/components/Grades/SpecificEvaluations';
|
import SpecificEvaluations from '@/components/Grades/SpecificEvaluations';
|
||||||
import Orientation from '@/components/Grades/Orientation';
|
import Orientation from '@/components/Grades/Orientation';
|
||||||
|
import GradesStatsCircle from '@/components/Grades/GradesStatsCircle';
|
||||||
import Button from '@/components/Button';
|
import Button from '@/components/Button';
|
||||||
|
import logger from '@/utils/logger';
|
||||||
import { FE_ADMIN_GRADES_STUDENT_COMPETENCIES_URL } from '@/utils/Url';
|
import { FE_ADMIN_GRADES_STUDENT_COMPETENCIES_URL } from '@/utils/Url';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { fetchStudents } from '@/app/actions/subscriptionAction';
|
import {
|
||||||
|
fetchStudents,
|
||||||
|
fetchStudentCompetencies,
|
||||||
|
} from '@/app/actions/subscriptionAction';
|
||||||
import { useEstablishment } from '@/context/EstablishmentContext';
|
import { useEstablishment } from '@/context/EstablishmentContext';
|
||||||
import { useClasses } from '@/context/ClassesContext';
|
import { useClasses } from '@/context/ClassesContext';
|
||||||
|
|
||||||
@ -25,6 +29,8 @@ export default function Page() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const [students, setStudents] = useState([]);
|
const [students, setStudents] = useState([]);
|
||||||
|
const [studentCompetencies, setStudentCompetencies] = useState(null);
|
||||||
|
const [grades, setGrades] = useState({});
|
||||||
|
|
||||||
const academicResults = [
|
const academicResults = [
|
||||||
{
|
{
|
||||||
@ -106,6 +112,34 @@ export default function Page() {
|
|||||||
}
|
}
|
||||||
}, [selectedEstablishmentId]);
|
}, [selectedEstablishmentId]);
|
||||||
|
|
||||||
|
// Charger les compétences et générer les grades à chaque changement d'élève sélectionné
|
||||||
|
useEffect(() => {
|
||||||
|
if (formData.selectedStudent) {
|
||||||
|
fetchStudentCompetencies(formData.selectedStudent)
|
||||||
|
.then((data) => {
|
||||||
|
setStudentCompetencies(data);
|
||||||
|
// Générer les grades à partir du retour API
|
||||||
|
if (data && data.data) {
|
||||||
|
const initialGrades = {};
|
||||||
|
data.data.forEach((domaine) => {
|
||||||
|
domaine.categories.forEach((cat) => {
|
||||||
|
cat.competences.forEach((comp) => {
|
||||||
|
initialGrades[comp.competence_id] = comp.score ?? 0;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
setGrades(initialGrades);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) =>
|
||||||
|
logger.error('Error fetching studentCompetencies:', error)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
setGrades({});
|
||||||
|
setStudentCompetencies(null);
|
||||||
|
}
|
||||||
|
}, [formData.selectedStudent]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="p-8 space-y-8">
|
<div className="p-8 space-y-8">
|
||||||
{/* Sélection de l'élève */}
|
{/* Sélection de l'élève */}
|
||||||
@ -145,13 +179,14 @@ export default function Page() {
|
|||||||
|
|
||||||
{formData.selectedStudent && (
|
{formData.selectedStudent && (
|
||||||
<>
|
<>
|
||||||
<AcademicResults results={academicResults} />
|
{/* <AcademicResults results={academicResults} /> */}
|
||||||
<Attendance absences={absences} />
|
<Attendance absences={absences} />
|
||||||
<Remarks remarks={remarks} />
|
<GradesStatsCircle grades={grades} />
|
||||||
|
{/* <Remarks remarks={remarks} />
|
||||||
<WorkPlan workPlan={workPlan} />
|
<WorkPlan workPlan={workPlan} />
|
||||||
<Homeworks homeworks={homeworks} />
|
<Homeworks homeworks={homeworks} />
|
||||||
<SpecificEvaluations specificEvaluations={specificEvaluations} />
|
<SpecificEvaluations specificEvaluations={specificEvaluations} />
|
||||||
<Orientation orientation={orientation} />
|
<Orientation orientation={orientation} /> */}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -11,18 +11,13 @@ import {
|
|||||||
import SectionHeader from '@/components/SectionHeader';
|
import SectionHeader from '@/components/SectionHeader';
|
||||||
import { Award } from 'lucide-react';
|
import { Award } from 'lucide-react';
|
||||||
import { useCsrfToken } from '@/context/CsrfContext';
|
import { useCsrfToken } from '@/context/CsrfContext';
|
||||||
|
import { useNotification } from '@/context/NotificationContext';
|
||||||
// À remplacer par un fetch réel des compétences selon l'élève
|
|
||||||
const mockCompetencies = [
|
|
||||||
{ id: 1, name: 'Lire un texte court', score: null },
|
|
||||||
{ id: 2, name: 'Résoudre un problème simple', score: null },
|
|
||||||
{ id: 3, name: 'Exprimer une idée à l’oral', score: null },
|
|
||||||
];
|
|
||||||
|
|
||||||
export default function StudentCompetenciesPage() {
|
export default function StudentCompetenciesPage() {
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const csrfToken = useCsrfToken();
|
const csrfToken = useCsrfToken();
|
||||||
|
const { showNotification } = useNotification();
|
||||||
const [studentCompetencies, setStudentCompetencies] = useState([]);
|
const [studentCompetencies, setStudentCompetencies] = useState([]);
|
||||||
const [grades, setGrades] = useState({});
|
const [grades, setGrades] = useState({});
|
||||||
const studentId = searchParams.get('studentId');
|
const studentId = searchParams.get('studentId');
|
||||||
@ -70,14 +65,21 @@ export default function StudentCompetenciesPage() {
|
|||||||
competenceId,
|
competenceId,
|
||||||
grade: score,
|
grade: score,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
editStudentCompetencies(data, csrfToken)
|
editStudentCompetencies(data, csrfToken)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
alert('Bilan de compétence enregistré !');
|
showNotification(
|
||||||
|
'Bilan de compétence sauvegardé avec succès',
|
||||||
|
'success',
|
||||||
|
'Succès'
|
||||||
|
);
|
||||||
router.back();
|
router.back();
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
alert("Erreur lors de l'enregistrement du bilan");
|
showNotification(
|
||||||
|
"Erreur lors de l'enregistrement du bilan de compétence",
|
||||||
|
'error',
|
||||||
|
'Erreur'
|
||||||
|
);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -75,7 +75,9 @@ export default function GradeView({ data, grades, onGradeChange }) {
|
|||||||
{data.map((domaine) => (
|
{data.map((domaine) => (
|
||||||
<div key={domaine.domaine_id} className="mb-8">
|
<div key={domaine.domaine_id} className="mb-8">
|
||||||
<div
|
<div
|
||||||
className={'flex items-center justify-between cursor-pointer px-6 py-4 rounded-lg transition bg-emerald-50 border border-emerald-200 shadow-sm hover:bg-emerald-100'}
|
className={
|
||||||
|
'flex items-center justify-between cursor-pointer px-6 py-4 rounded-lg transition bg-emerald-50 border border-emerald-200 shadow-sm hover:bg-emerald-100'
|
||||||
|
}
|
||||||
onClick={() => toggleDomain(domaine.domaine_id)}
|
onClick={() => toggleDomain(domaine.domaine_id)}
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
|
|||||||
40
Front-End/src/components/Grades/GradesStatsCircle.js
Normal file
40
Front-End/src/components/Grades/GradesStatsCircle.js
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { CircularProgressbar, buildStyles } from 'react-circular-progressbar';
|
||||||
|
import 'react-circular-progressbar/dist/styles.css';
|
||||||
|
|
||||||
|
export default function GradesStatsCircle({ grades }) {
|
||||||
|
// grades : { [competence_id]: grade }
|
||||||
|
const total = Object.keys(grades).length;
|
||||||
|
const acquired = Object.values(grades).filter((g) => g === 3).length;
|
||||||
|
const inProgress = Object.values(grades).filter((g) => g === 2).length;
|
||||||
|
const notAcquired = Object.values(grades).filter((g) => g === 1).length;
|
||||||
|
const notEvaluated = Object.values(grades).filter((g) => g === 0).length;
|
||||||
|
|
||||||
|
// Pourcentage d'acquis
|
||||||
|
const percent = total ? Math.round((acquired / total) * 100) : 0;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col items-center gap-4 bg-white p-6 rounded-lg shadow-sm border border-gray-200">
|
||||||
|
<h2 className="text-xl font-semibold mb-2">Statistiques globales</h2>
|
||||||
|
<div style={{ width: 120, height: 120 }}>
|
||||||
|
<CircularProgressbar
|
||||||
|
value={percent}
|
||||||
|
text={`${percent}%`}
|
||||||
|
styles={buildStyles({
|
||||||
|
textColor: '#059669',
|
||||||
|
pathColor: '#059669',
|
||||||
|
trailColor: '#d1fae5',
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col items-center text-sm mt-2">
|
||||||
|
<span className="text-emerald-700 font-semibold">
|
||||||
|
{acquired} acquis
|
||||||
|
</span>
|
||||||
|
<span className="text-yellow-600">{inProgress} en cours</span>
|
||||||
|
<span className="text-red-500">{notAcquired} non acquis</span>
|
||||||
|
<span className="text-gray-400">{notEvaluated} non évalués</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user