feat: Ajout des Bundles de fichiers [#24]

This commit is contained in:
Luc SORIGNET
2025-02-10 18:35:24 +01:00
parent fb7fbaf839
commit ffc6ce8de8
25 changed files with 1736 additions and 743 deletions

View File

@ -161,6 +161,13 @@ class Student(models.Model):
return self.birth_date.strftime('%d-%m-%Y')
return None
class RegistrationFileGroup(models.Model):
name = models.CharField(max_length=255)
description = models.TextField(blank=True, null=True)
def __str__(self):
return self.name
def registration_file_path(instance, filename):
# Génère le chemin : registration_files/dossier_rf_{student_id}/filename
return f'registration_files/dossier_rf_{instance.student_id}/{filename}'
@ -196,6 +203,11 @@ class RegistrationForm(models.Model):
# Many-to-Many Relationship
discounts = models.ManyToManyField(Discount, blank=True, related_name='register_forms')
fileGroup = models.ForeignKey(RegistrationFileGroup,
on_delete=models.CASCADE,
related_name='file_group',
null=True,
blank=True)
def __str__(self):
return "RF_" + self.student.last_name + "_" + self.student.first_name
@ -209,6 +221,7 @@ class RegistrationFileTemplate(models.Model):
order = models.PositiveIntegerField(default=0) # Ajout du champ order
date_added = models.DateTimeField(auto_now_add=True)
is_required = models.BooleanField(default=False)
group = models.ForeignKey(RegistrationFileGroup, on_delete=models.CASCADE, related_name='file_templates')
@property
def formatted_date_added(self):

View File

@ -1,5 +1,5 @@
from rest_framework import serializers
from .models import RegistrationFileTemplate, RegistrationFile, RegistrationForm, Student, Guardian, Sibling, Language
from .models import RegistrationFileTemplate, RegistrationFile, RegistrationFileGroup, RegistrationForm, Student, Guardian, Sibling, Language
from School.models import SchoolClass, Fee, Discount, FeeType
from School.serializers import FeeSerializer, DiscountSerializer
from Auth.models import Profile
@ -11,6 +11,11 @@ from django.utils import timezone
import pytz
from datetime import datetime
class RegistrationFileGroupSerializer(serializers.ModelSerializer):
class Meta:
model = RegistrationFileGroup
fields = '__all__'
class RegistrationFileSerializer(serializers.ModelSerializer):
class Meta:
model = RegistrationFile

View File

@ -3,95 +3,205 @@
<head>
<meta charset="UTF-8">
<title>{{ pdf_title }}</title>
<style type="text/css">
<style>
@page {
size: A4;
margin: 2cm;
}
body {
font-weight: 200;
font-size: 14px;
font-family: Helvetica, Arial, sans-serif;
font-size: 11pt;
line-height: 1.3;
margin: 0;
padding: 0;
color:#333;
}
.container {
width: 100%;
}
.header {
font-size: 20px;
font-weight: 100;
text-align: center;
color: #007cae;
margin-bottom: 30px;
border-bottom: 2px solid #333;
padding-bottom: 10px;
}
.title {
font-size: 22px;
font-weight: 100;
/* text-align: right;*/
padding: 10px 20px 0px 20px;
font-size: 18pt;
font-weight: bold;
margin: 0;
padding: 0;
}
.title span {
color: #007cae;
.section {
margin-bottom: 15px; /* Réduit de 25px à 15px */
padding: 10px; /* Réduit de 15px à 10px */
page-break-inside: avoid;
}
.details {
padding: 10px 20px 0px 20px;
text-align: left !important;
/*margin-left: 40%;*/
.section-title {
font-size: 16pt;
font-weight: bold;
margin: 0 0 10px 0; /* Réduit de 15px à 10px */
padding-bottom: 3px; /* Réduit de 5px à 3px */
border-bottom: 1px solid #333;
text-align: center;
}
.hrItem {
border: none;
height: 1px;
/* Set the hr color */
color: #333; /* old IE */
background-color: #fff; /* Modern Browsers */
table {
width: 100%;
border-collapse: collapse;
margin-bottom: 10px;
}
td {
padding: 4px;
vertical-align: top;
}
.label-cell {
font-weight: bold;
width: 150px;
}
.value-cell {
width: auto;
}
.half-row td {
width: 50%;
}
.subsection-title {
font-size: 12pt;
font-weight: bold;
margin-bottom: 5px; /* Réduit de 10px à 5px */
}
.signature {
margin-top: 30px;
text-align: right;
font-style: italic;
}
.signature-text {
font-weight: bold;
}
.clearfix::after {
content: "";
clear: both;
display: table;
}
p {
margin: 0; /* Ajout de margin 0 pour les paragraphes */
padding: 0; /* Ajout de padding 0 pour les paragraphes */
}
</style>
</head>
<body>
{% load myTemplateTag %}
<div class='wrapper'>
<div class='header'>
<p class='title'>{{ pdf_title }}</p>
<div class="container">
<div class="header">
<h1 class="title">{{ pdf_title }}</h1>
</div>
<div>
<div class='details'>
Signé le : <b>{{ signatureDate }}</b> <br/>
A : <b>{{ signatureTime }}</b>
<hr class='hrItem' />
<h1>ELEVE</h1>
<div class="section">
<h2 class="section-title">ÉLÈVE</h2>
{% with level=student|getStudentLevel %}
{% with gender=student|getStudentGender %}
NOM : <b>{{ student.last_name }}</b> <br/>
PRENOM : <b>{{ student.first_name }}</b> <br/>
ADRESSE : <b>{{ student.address }}</b> <br/>
GENRE : <b>{{ gender }}</b> <br/>
NE(E) LE : <b>{{ student.birth_date }}</b> <br/>
A : <b>{{ student.birth_place }} ({{ student.birth_postal_code }})</b> <br/>
NATIONALITE : <b>{{ student.nationality }}</b> <br/>
NIVEAU : <b>{{ level }}</b> <br/>
MEDECIN TRAITANT : <b>{{ student.attending_physician }}</b> <br/>
<table>
<tr class="half-row">
<td class="label-cell">NOM :</td>
<td>{{ student.last_name }}</td>
<td class="label-cell">PRÉNOM :</td>
<td>{{ student.first_name }}</td>
</tr>
<tr>
<td class="label-cell">ADRESSE :</td>
<td colspan="3">{{ student.address }}</td>
</tr>
<tr class="half-row">
<td class="label-cell">GENRE :</td>
<td>{{ gender }}</td>
<td class="label-cell">NÉ(E) LE :</td>
<td>{{ student.birth_date }}</td>
</tr>
<tr>
<td class="label-cell">À :</td>
<td colspan="3">{{ student.birth_place }} ({{ student.birth_postal_code }})</td>
</tr>
<tr class="half-row">
<td class="label-cell">NATIONALITÉ :</td>
<td>{{ student.nationality }}</td>
<td class="label-cell">NIVEAU :</td>
<td>{{ level }}</td>
</tr>
<tr>
<td class="label-cell">MÉDECIN TRAITANT :</td>
<td colspan="3">{{ student.attending_physician }}</td>
</tr>
</table>
{% endwith %}
{% endwith %}
<hr class='hrItem' />
<h1>RESPONSABLES</h1>
</div>
<div class="section">
<h2 class="section-title">RESPONSABLES</h2>
{% with guardians=student.getGuardians %}
{% with siblings=student.getGuardians %}
{% for guardian in guardians%}
<h2>Guardian {{ forloop.counter }}</h2>
NOM : <b>{{ guardian.last_name }}</b> <br/>
PRENOM : <b>{{ guardian.first_name }}</b> <br/>
ADRESSE : <b>{{ guardian.address }}</b> <br/>
NE(E) LE : <b>{{ guardian.birth_date }}</b> <br/>
MAIL : <b>{{ guardian.email }}</b> <br/>
TEL : <b>{{ guardian.phone }}</b> <br/>
PROFESSION : <b>{{ guardian.profession }}</b> <br/>
<div class="subsection">
<h3 class="subsection-title">Responsable {{ forloop.counter }}</h3>
<table>
<tr class="half-row">
<td class="label-cell">NOM :</td>
<td>{{ guardian.last_name }}</td>
<td class="label-cell">PRÉNOM :</td>
<td>{{ guardian.first_name }}</td>
</tr>
<tr>
<td class="label-cell">ADRESSE :</td>
<td colspan="3">{{ guardian.address }}</td>
</tr>
<tr class="half-row">
<td class="label-cell">NÉ(E) LE :</td>
<td>{{ guardian.birth_date }}</td>
<td class="label-cell">EMAIL :</td>
<td>{{ guardian.email }}</td>
</tr>
<tr class="half-row">
<td class="label-cell">TÉLÉPHONE :</td>
<td>{{ guardian.phone }}</td>
<td class="label-cell">PROFESSION :</td>
<td>{{ guardian.profession }}</td>
</tr>
</table>
</div>
{% endfor %}
<hr class='hrItem' />
<h1>FRATRIE</h1>
{% endwith %}
</div>
<div class="section">
<h2 class="section-title">FRATRIE</h2>
{% with siblings=student.getGuardians %}
{% for sibling in siblings%}
<h2>Frère - Soeur {{ forloop.counter }}</h2>
NOM : <b>{{ sibling.last_name }}</b> <br/>
PRENOM : <b>{{ sibling.first_name }}</b> <br/>
NE(E) LE : <b>{{ sibling.birth_date }}</b> <br/>
<div class="subsection">
<h3 class="subsection-title">Frère/Sœur {{ forloop.counter }}</h3>
<table>
<tr class="half-row">
<td class="label-cell">NOM :</td>
<td>{{ sibling.last_name }}</td>
<td class="label-cell">PRÉNOM :</td>
<td>{{ sibling.first_name }}</td>
</tr>
<tr>
<td class="label-cell">NÉ(E) LE :</td>
<td colspan="3">{{ sibling.birth_date }}</td>
</tr>
</table>
</div>
{% endfor %}
<hr class='hrItem' />
<h1>MODALITES DE PAIEMENT</h1>
{% endwith %}
</div>
<div class="section">
<h2 class="section-title">MODALITÉS DE PAIEMENT</h2>
{% with paymentMethod=student|getStudentPaymentMethod %}
<b>{{ paymentMethod }}</b> <br/>
{% endwith %}
{% endwith %}
<p>{{ paymentMethod }}</p>
{% endwith %}
</div>
<div class="signature">
Fait le <span class="signature-text">{{ signatureDate }}</span> à <span class="signature-text">{{ signatureTime }}</span>
</div>
</div>
</body>
</html>

View File

@ -1,41 +1,41 @@
from django.urls import path, re_path
from . import views
from .views import RegistrationFileTemplateView, RegisterFormListView, RegisterFormView, StudentView, GuardianView, ChildrenListView, StudentListView, RegistrationFileView
# RF
from .views import RegisterFormView, RegisterFormWithIdView, send, resend, archive
# SubClasses
from .views import StudentView, GuardianView, ChildrenListView, StudentListView
# Files
from .views import RegistrationFileTemplateView, RegistrationFileTemplateSimpleView, RegistrationFileView, RegistrationFileSimpleView
from .views import RegistrationFileGroupView, RegistrationFileGroupSimpleView, get_registration_files_by_group
urlpatterns = [
re_path(r'^registerForms/(?P<_filter>[a-zA-z]+)$', RegisterFormListView.as_view(), name="registerForms"),
re_path(r'^registerForm$', RegisterFormView.as_view(), name="registerForm"),
re_path(r'^registerForm/(?P<_id>[0-9]+)$', RegisterFormView.as_view(), name="registerForm"),
# Page de formulaire d'inscription - ELEVE
re_path(r'^student/(?P<_id>[0-9]+)$', StudentView.as_view(), name="students"),
# Page de formulaire d'inscription - RESPONSABLE
re_path(r'^lastGuardian$', GuardianView.as_view(), name="lastGuardian"),
# Envoi d'un dossier d'inscription
re_path(r'^send/(?P<_id>[0-9]+)$', views.send, name="send"),
# Archivage d'un dossier d'inscription
re_path(r'^archive/(?P<_id>[0-9]+)$', views.archive, name="archive"),
# Envoi d'une relance de dossier d'inscription
re_path(r'^sendRelance/(?P<_id>[0-9]+)$', views.relance, name="sendRelance"),
# Page PARENT - Liste des children
re_path(r'^children/(?P<_id>[0-9]+)$', ChildrenListView.as_view(), name="children"),
re_path(r'^registerForms/(?P<id>[0-9]+)/archive$', archive, name="archive"),
re_path(r'^registerForms/(?P<id>[0-9]+)/resend$', resend, name="resend"),
re_path(r'^registerForms/(?P<id>[0-9]+)/send$', send, name="send"),
re_path(r'^registerForms/(?P<id>[0-9]+)$', RegisterFormWithIdView.as_view(), name="registerForm"),
re_path(r'^registerForms$', RegisterFormView.as_view(), name="registerForms"),
# Page INSCRIPTION - Liste des élèves
re_path(r'^students$', StudentListView.as_view(), name="students"),
# Page de formulaire d'inscription - ELEVE
re_path(r'^students/(?P<id>[0-9]+)$', StudentView.as_view(), name="students"),
# Page PARENT - Liste des children
re_path(r'^children/(?P<id>[0-9]+)$', ChildrenListView.as_view(), name="children"),
# Page de formulaire d'inscription - RESPONSABLE
re_path(r'^lastGuardianId$', GuardianView.as_view(), name="lastGuardianId"),
# modèles de fichiers d'inscription
re_path(r'^registrationFileTemplates/(?P<id>[0-9]+)$', RegistrationFileTemplateSimpleView.as_view(), name="registrationFileTemplate"),
re_path(r'^registrationFileTemplates$', RegistrationFileTemplateView.as_view(), name='registrationFileTemplates'),
re_path(r'^registrationFileTemplates/(?P<_id>[0-9]+)$', RegistrationFileTemplateView.as_view(), name="registrationFileTemplate"),
# fichiers d'inscription
re_path(r'^registrationFiles/(?P<_id>[0-9]+)$', RegistrationFileView.as_view(), name='registrationFiles'),
re_path(r'^registrationFiles', RegistrationFileView.as_view(), name="registrationFiles"),
re_path(r'^registrationFiles/(?P<id>[0-9]+)$', RegistrationFileSimpleView.as_view(), name='registrationFiles'),
re_path(r'^registrationFiles$', RegistrationFileView.as_view(), name="registrationFiles"),
re_path(r'^registrationFileGroups/(?P<id>[0-9]+)$', RegistrationFileGroupSimpleView.as_view(), name='registrationFileGroupDetail'),
re_path(r'^registrationFileGroups/(?P<id>[0-9]+)/registrationFiles$', get_registration_files_by_group, name="get_registration_files_by_group"),
re_path(r'^registrationFileGroups$', RegistrationFileGroupView.as_view(), name='registrationFileGroups'),
]

View File

@ -1,450 +0,0 @@
from django.http.response import JsonResponse
from django.contrib.auth import login, authenticate, get_user_model
from django.views.decorators.csrf import ensure_csrf_cookie, csrf_protect
from django.utils.decorators import method_decorator
from django.core.cache import cache
from django.core.paginator import Paginator
from django.core.files import File
from django.db.models import Q # Ajout de cet import
from rest_framework.parsers import JSONParser,MultiPartParser, FormParser
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework import status
from drf_yasg.utils import swagger_auto_schema
from drf_yasg import openapi
import json
from pathlib import Path
import os
from io import BytesIO
import Subscriptions.mailManager as mailer
import Subscriptions.util as util
from Subscriptions.automate import Automate_RF_Register, load_config, getStateMachineObjectState, updateStateMachine
from .serializers import RegistrationFormSerializer, StudentSerializer, RegistrationFormByParentSerializer, StudentByRFCreationSerializer, RegistrationFileSerializer, RegistrationFileTemplateSerializer, RegistrationFormByParentSerializer, StudentByRFCreationSerializer
from .pagination import CustomPagination
from .signals import clear_cache
from .models import Student, Guardian, RegistrationForm, RegistrationFileTemplate, RegistrationFile
from .automate import Automate_RF_Register, load_config, getStateMachineObjectState, updateStateMachine
from Auth.models import Profile
from N3wtSchool import settings, renderers, bdd
class RegisterFormListView(APIView):
"""
Gère la liste des dossiers dinscription, lecture et création.
"""
pagination_class = CustomPagination
def get_register_form(self, _filter, search=None):
"""
Récupère les fiches d'inscriptions en fonction du filtre passé.
_filter: Filtre pour déterminer l'état des fiches ('pending', 'archived', 'subscribed')
search: Terme de recherche (optionnel)
"""
if _filter == 'pending':
exclude_states = [RegistrationForm.RegistrationFormStatus.RF_VALIDATED, RegistrationForm.RegistrationFormStatus.RF_ARCHIVED]
return bdd.searchObjects(RegistrationForm, search, _excludeStates=exclude_states)
elif _filter == 'archived':
return bdd.getObjects(RegistrationForm, 'status', RegistrationForm.RegistrationFormStatus.RF_ARCHIVED)
elif _filter == 'subscribed':
return bdd.getObjects(RegistrationForm, 'status', RegistrationForm.RegistrationFormStatus.RF_VALIDATED)
return None
@swagger_auto_schema(
manual_parameters=[
openapi.Parameter('_filter', openapi.IN_PATH, description="filtre", type=openapi.TYPE_STRING, enum=['pending', 'archived', 'subscribed'], required=True),
openapi.Parameter('search', openapi.IN_QUERY, description="search", type=openapi.TYPE_STRING, required=False),
openapi.Parameter('page_size', openapi.IN_QUERY, description="limite de page lors de la pagination", type=openapi.TYPE_INTEGER, required=False),
],
responses={200: RegistrationFormSerializer(many=True)}
)
def get(self, request, _filter):
"""
Récupère les fiches d'inscriptions en fonction du filtre passé.
"""
# Récupération des paramètres
search = request.GET.get('search', '').strip()
page_size = request.GET.get('page_size', None)
# Gestion du page_size
if page_size is not None:
try:
page_size = int(page_size)
except ValueError:
page_size = settings.NB_RESULT_PER_PAGE
# Définir le cache_key en fonction du filtre
page_number = request.GET.get('page', 1)
cache_key = f'N3WT_ficheInscriptions_{_filter}_page_{page_number}_search_{search if _filter == "pending" else ""}'
cached_page = cache.get(cache_key)
if cached_page:
return JsonResponse(cached_page, safe=False)
# Récupérer les fiches d'inscriptions en fonction du filtre
registerForms_List = self.get_register_form(_filter, search)
if not registerForms_List:
return JsonResponse({'error' : 'aucune donnée trouvée', 'count' :0}, safe=False)
# Pagination
paginator = self.pagination_class()
page = paginator.paginate_queryset(registerForms_List, request)
if page is not None:
registerForms_serializer = RegistrationFormSerializer(page, many=True)
response_data = paginator.get_paginated_response(registerForms_serializer.data)
cache.set(cache_key, response_data, timeout=60*15)
return JsonResponse(response_data, safe=False)
return JsonResponse({'error' : 'aucune donnée trouvée', 'count' :0}, safe=False)
@swagger_auto_schema(
manual_parameters=[
],
responses={200: RegistrationFormSerializer(many=True)}
)
def post(self, request):
studentFormList_serializer=JSONParser().parse(request)
for studentForm_data in studentFormList_serializer:
# Ajout de la date de mise à jour
studentForm_data["last_update"] = util.convertToStr(util._now(), '%d-%m-%Y %H:%M')
json.dumps(studentForm_data)
# Ajout du code d'inscription
code = util.genereRandomCode(12)
studentForm_data["codeLienInscription"] = code
studentForm_serializer = RegistrationFormSerializer(data=studentForm_data)
if studentForm_serializer.is_valid():
studentForm_serializer.save()
return JsonResponse(studentForm_serializer.errors, safe=False)
@method_decorator(csrf_protect, name='dispatch')
@method_decorator(ensure_csrf_cookie, name='dispatch')
class RegisterFormView(APIView):
"""
Gère la lecture, création, modification et suppression dun dossier dinscription.
"""
pagination_class = CustomPagination
def get(self, request, _id):
"""
Récupère un dossier d'inscription donné.
"""
registerForm=bdd.getObject(RegistrationForm, "student__id", _id)
registerForm_serializer=RegistrationFormSerializer(registerForm)
return JsonResponse(registerForm_serializer.data, safe=False)
def post(self, request):
"""
Crée un dossier d'inscription.
"""
studentForm_data=JSONParser().parse(request)
# Ajout de la date de mise à jour
studentForm_data["last_update"] = util.convertToStr(util._now(), '%d-%m-%Y %H:%M')
json.dumps(studentForm_data)
# Ajout du code d'inscription
code = util.genereRandomCode(12)
studentForm_data["codeLienInscription"] = code
guardiansId = studentForm_data.pop('idGuardians', [])
studentForm_serializer = RegistrationFormSerializer(data=studentForm_data)
if studentForm_serializer.is_valid():
di = studentForm_serializer.save()
# Mise à jour de l'automate
updateStateMachine(di, 'creationDI')
# Récupération du reponsable associé
for guardianId in guardiansId:
guardian = Guardian.objects.get(id=guardianId)
di.student.guardians.add(guardian)
di.save()
return JsonResponse(studentForm_serializer.data, safe=False)
return JsonResponse(studentForm_serializer.errors, safe=False, status=status.HTTP_400_BAD_REQUEST)
def put(self, request, _id):
"""
Modifie un dossier d'inscription donné.
"""
studentForm_data=JSONParser().parse(request)
_status = studentForm_data.pop('status', 0)
studentForm_data["last_update"] = str(util.convertToStr(util._now(), '%d-%m-%Y %H:%M'))
registerForm = bdd.getObject(_objectName=RegistrationForm, _columnName='student__id', _value=_id)
if _status == RegistrationForm.RegistrationFormStatus.RF_UNDER_REVIEW:
try:
# Génération de la fiche d'inscription au format PDF
base_dir = f"registration_files/dossier_rf_{registerForm.pk}"
os.makedirs(base_dir, exist_ok=True)
# Fichier PDF initial
initial_pdf = f"{base_dir}/rf_{registerForm.student.last_name}_{registerForm.student.first_name}.pdf"
registerForm.registration_file = util.rfToPDF(registerForm, initial_pdf)
registerForm.save()
# Récupération des fichiers d'inscription
fileNames = RegistrationFile.get_files_from_rf(registerForm.pk)
if registerForm.registration_file:
fileNames.insert(0, registerForm.registration_file.path)
# Création du fichier PDF Fusionné
merged_pdf = f"{base_dir}/dossier_complet_{registerForm.pk}.pdf"
util.merge_files_pdf(fileNames, merged_pdf)
# Mise à jour du champ registration_file avec le fichier fusionné
with open(merged_pdf, 'rb') as f:
registerForm.registration_file.save(
os.path.basename(merged_pdf),
File(f),
save=True
)
# Mise à jour de l'automate
updateStateMachine(registerForm, 'saisiDI')
except Exception as e:
return JsonResponse({'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
elif _status == RegistrationForm.RegistrationFormStatus.RF_VALIDATED:
# L'école a validé le dossier d'inscription
# Mise à jour de l'automate
updateStateMachine(registerForm, 'valideDI')
studentForm_serializer = RegistrationFormSerializer(registerForm, data=studentForm_data)
if studentForm_serializer.is_valid():
studentForm_serializer.save()
return JsonResponse(studentForm_serializer.data, safe=False)
return JsonResponse(studentForm_serializer.errors, safe=False, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request, id):
"""
Supprime un dossier d'inscription donné.
"""
register_form = bdd.getObject(_objectName=RegistrationForm, _columnName='student__id', _value=id)
if register_form != None:
student = register_form.student
student.guardians.clear()
student.profiles.clear()
student.registration_files.clear()
student.delete()
clear_cache()
return JsonResponse("La suppression du dossier a été effectuée avec succès", safe=False)
return JsonResponse({"errorMessage":'Aucun dossier d\'inscription rattaché à l\'élève'}, safe=False, status=status.HTTP_404_NOT_FOUND)
class StudentView(APIView):
"""
Gère la lecture dun élève donné.
"""
def get(self, request, _id):
student = bdd.getObject(_objectName=Student, _columnName='id', _value=_id)
if student is None:
return JsonResponse({"errorMessage":'Aucun élève trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND)
student_serializer = StudentSerializer(student)
return JsonResponse(student_serializer.data, safe=False)
class GuardianView(APIView):
"""
Récupère le dernier ID de responsable légal créé.
"""
def get(self, request):
lastGuardian = bdd.getLastId(Guardian)
return JsonResponse({"lastid":lastGuardian}, safe=False)
def send(request, _id):
"""
Envoie le dossier dinscription par e-mail.
"""
register_form = bdd.getObject(_objectName=RegistrationForm, _columnName='student__id', _value=_id)
if register_form != None:
student = register_form.student
guardian = student.getMainGuardian()
email = guardian.email
errorMessage = mailer.sendRegisterForm(email)
if errorMessage == '':
register_form.last_update=util.convertToStr(util._now(), '%d-%m-%Y %H:%M')
# Mise à jour de l'automate
updateStateMachine(register_form, 'envoiDI')
return JsonResponse({"message": f"Le dossier d'inscription a bien été envoyé à l'addresse {email}"}, safe=False)
return JsonResponse({"errorMessage":errorMessage}, safe=False, status=status.HTTP_400_BAD_REQUEST)
return JsonResponse({"errorMessage":'Aucun dossier d\'inscription rattaché à l\'élève'}, safe=False, status=status.HTTP_404_NOT_FOUND)
def archive(request, _id):
"""
Archive le dossier dinscription visé.
"""
register_form = bdd.getObject(_objectName=RegistrationForm, _columnName='student__id', _value=_id)
if register_form != None:
register_form.last_update=util.convertToStr(util._now(), '%d-%m-%Y %H:%M')
# Mise à jour de l'automate
updateStateMachine(register_form, 'archiveDI')
return JsonResponse({"errorMessage":''}, safe=False, status=status.HTTP_400_BAD_REQUEST)
return JsonResponse({"errorMessage":'Aucun dossier d\'inscription rattaché à l\'élève'}, safe=False, status=status.HTTP_404_NOT_FOUND)
def relance(request, _id):
"""
Relance un dossier dinscription par e-mail.
"""
register_form = bdd.getObject(_objectName=RegistrationForm, _columnName='student__id', _value=_id)
if register_form != None:
student = register_form.student
guardian = student.getMainGuardian()
email = guardian.email
errorMessage = mailer.envoieRelanceDossierInscription(email, register_form.codeLienInscription)
if errorMessage == '':
register_form.status=RegistrationForm.RegistrationFormStatus.RF_SENT
register_form.last_update=util.convertToStr(util._now(), '%d-%m-%Y %H:%M')
register_form.save()
return JsonResponse({"errorMessage":errorMessage}, safe=False, status=status.HTTP_400_BAD_REQUEST)
return JsonResponse({"errorMessage":'Aucun dossier d\'inscription rattaché à l\'élève'}, safe=False, status=status.HTTP_404_NOT_FOUND)
# API utilisée pour la vue parent
class ChildrenListView(APIView):
"""
Pour la vue parent : liste les élèves rattachés à un profil donné.
"""
# Récupération des élèves d'un parent
# idProfile : identifiant du profil connecté rattaché aux fiches d'élèves
def get(self, request, _id):
students = bdd.getObjects(_objectName=RegistrationForm, _columnName='student__guardians__associated_profile__id', _value=_id)
students_serializer = RegistrationFormByParentSerializer(students, many=True)
return JsonResponse(students_serializer.data, safe=False)
# API utilisée pour la vue de création d'un DI
class StudentListView(APIView):
"""
Pour la vue de création dun dossier dinscription : liste les élèves disponibles.
"""
# Récupération de la liste des élèves inscrits ou en cours d'inscriptions
def get(self, request):
students = bdd.getAllObjects(_objectName=Student)
students_serializer = StudentByRFCreationSerializer(students, many=True)
return JsonResponse(students_serializer.data, safe=False)
class RegistrationFileTemplateView(APIView):
"""
Gère les fichiers templates pour les dossiers dinscription.
"""
parser_classes = (MultiPartParser, FormParser)
def get(self, request, _id=None):
"""
Récupère les fichiers templates pour les dossiers dinscription.
"""
if _id is None:
files = RegistrationFileTemplate.objects.all()
serializer = RegistrationFileTemplateSerializer(files, many=True)
return Response(serializer.data)
else :
registationFileTemplate = bdd.getObject(_objectName=RegistrationFileTemplate, _columnName='id', _value=_id)
if registationFileTemplate is None:
return JsonResponse({"errorMessage":'Le fichier d\'inscription n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND)
serializer = RegistrationFileTemplateSerializer(registationFileTemplate)
return JsonResponse(serializer.data, safe=False)
def put(self, request, _id):
"""
Met à jour un fichier template existant.
"""
registationFileTemplate = bdd.getObject(_objectName=RegistrationFileTemplate, _columnName='id', _value=_id)
if registationFileTemplate is None:
return JsonResponse({'erreur': 'Le fichier d\'inscription n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND)
serializer = RegistrationFileTemplateSerializer(registationFileTemplate,data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def post(self, request):
"""
Crée un fichier template pour les dossiers dinscription.
"""
serializer = RegistrationFileTemplateSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request, _id):
"""
Supprime un fichier template existant.
"""
registrationFileTemplate = bdd.getObject(_objectName=RegistrationFileTemplate, _columnName='id', _value=_id)
if registrationFileTemplate is not None:
registrationFileTemplate.file.delete() # Supprimer le fichier uploadé
registrationFileTemplate.delete()
return JsonResponse({'message': 'La suppression du fichier d\'inscription a été effectuée avec succès'}, safe=False, status=status.HTTP_204_NO_CONTENT)
else:
return JsonResponse({'erreur': 'Le fichier d\'inscription n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND)
class RegistrationFileView(APIView):
"""
Gère la création, mise à jour et suppression de fichiers liés à un dossier dinscription.
"""
parser_classes = (MultiPartParser, FormParser)
def get(self, request, _id=None):
"""
Récupère les fichiers liés à un dossier dinscription donné.
"""
if (_id is None):
files = RegistrationFile.objects.all()
serializer = RegistrationFileSerializer(files, many=True)
return Response(serializer.data)
else:
registationFile = bdd.getObject(_objectName=RegistrationFile, _columnName='id', _value=_id)
if registationFile is None:
return JsonResponse({"errorMessage":'Le fichier d\'inscription n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND)
serializer = RegistrationFileSerializer(registationFile)
return JsonResponse(serializer.data, safe=False)
def post(self, request):
"""
Crée un RegistrationFile pour le RegistrationForm associé.
"""
serializer = RegistrationFileSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def put(self, request, fileId):
"""
Met à jour un RegistrationFile existant.
"""
registrationFile = bdd.getObject(_objectName=RegistrationFile, _columnName='id', _value=fileId)
if registrationFile is None:
return JsonResponse({'erreur': 'Le fichier d\'inscription n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND)
serializer = RegistrationFileSerializer(registrationFile, data=request.data)
if serializer.is_valid():
serializer.save()
return Response({'message': 'Fichier mis à jour avec succès', 'data': serializer.data}, status=status.HTTP_200_OK)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request, _id):
"""
Supprime un RegistrationFile existant.
"""
registrationFile = bdd.getObject(_objectName=RegistrationFile, _columnName='id', _value=_id)
if registrationFile is not None:
registrationFile.file.delete() # Supprimer le fichier uploadé
registrationFile.delete()
return JsonResponse({'message': 'La suppression du fichier a été effectuée avec succès'}, safe=False)
else:
return JsonResponse({'erreur': 'Le fichier n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND)

View File

@ -0,0 +1,24 @@
from .register_form_views import RegisterFormView, RegisterFormWithIdView, send, resend, archive
from .registration_file_views import RegistrationFileTemplateView, RegistrationFileTemplateSimpleView, RegistrationFileView, RegistrationFileSimpleView
from .registration_file_group_views import RegistrationFileGroupView, RegistrationFileGroupSimpleView, get_registration_files_by_group
from .student_views import StudentView, StudentListView, ChildrenListView
from .guardian_views import GuardianView
__all__ = [
'RegisterFormView',
'RegisterFormWithIdView',
'send',
'resend',
'archive',
'RegistrationFileView',
'RegistrationFileSimpleView',
'RegistrationFileTemplateView',
'RegistrationFileTemplateSimpleView',
'RegistrationFileGroupView',
'RegistrationFileGroupSimpleView',
'get_registration_files_by_group',
'StudentView',
'StudentListView',
'ChildrenListView',
'GuardianView',
]

View File

@ -0,0 +1,34 @@
from django.http.response import JsonResponse
from rest_framework.views import APIView
from drf_yasg.utils import swagger_auto_schema
from drf_yasg import openapi
from Subscriptions.models import Guardian
from N3wtSchool import bdd
class GuardianView(APIView):
"""
Gestion des responsables légaux.
"""
@swagger_auto_schema(
operation_description="Récupère le dernier ID de responsable légal créé",
operation_summary="Récupèrer le dernier ID de responsable légal créé",
responses={
200: openapi.Response(
description="Dernier ID du responsable légal",
schema=openapi.Schema(
type=openapi.TYPE_OBJECT,
properties={
'lastid': openapi.Schema(
type=openapi.TYPE_INTEGER,
description="Dernier ID créé"
)
}
)
)
}
)
def get(self, request):
lastGuardian = bdd.getLastId(Guardian)
return JsonResponse({"lastid":lastGuardian}, safe=False)

View File

@ -0,0 +1,385 @@
from django.http.response import JsonResponse
from django.views.decorators.csrf import ensure_csrf_cookie, csrf_protect
from django.utils.decorators import method_decorator
from django.core.cache import cache
from rest_framework.parsers import JSONParser
from rest_framework.views import APIView
from rest_framework.decorators import action, api_view
from rest_framework import status
from drf_yasg.utils import swagger_auto_schema
from drf_yasg import openapi
import json
import os
from django.core.files import File
import Subscriptions.mailManager as mailer
import Subscriptions.util as util
from Subscriptions.serializers import RegistrationFormSerializer
from Subscriptions.pagination import CustomPagination
from Subscriptions.signals import clear_cache
from Subscriptions.models import Student, Guardian, RegistrationForm, RegistrationFile, RegistrationFileGroup
from Subscriptions.automate import updateStateMachine
from N3wtSchool import settings, bdd
import logging
logger = logging.getLogger(__name__)
# /Subscriptions/registerForms
class RegisterFormView(APIView):
"""
Gère la liste des dossiers dinscription, lecture et création.
"""
pagination_class = CustomPagination
@swagger_auto_schema(
manual_parameters=[
openapi.Parameter('filter', openapi.IN_QUERY, description="filtre", type=openapi.TYPE_STRING, enum=['pending', 'archived', 'subscribed'], required=True),
openapi.Parameter('search', openapi.IN_QUERY, description="search", type=openapi.TYPE_STRING, required=False),
openapi.Parameter('page_size', openapi.IN_QUERY, description="limite de page lors de la pagination", type=openapi.TYPE_INTEGER, required=False),
],
responses={200: RegistrationFormSerializer(many=True)},
operation_description="Récupère les dossier d'inscriptions en fonction du filtre passé.",
operation_summary="Récupérer les dossier d'inscriptions",
examples={
"application/json": [
{
"id": 1,
"student": {
"id": 1,
"first_name": "John",
"last_name": "Doe",
"date_of_birth": "2010-01-01"
},
"status": "pending",
"last_update": "10-02-2025 10:00"
},
{
"id": 2,
"student": {
"id": 2,
"first_name": "Jane",
"last_name": "Doe",
"date_of_birth": "2011-02-02"
},
"status": "archived",
"last_update": "09-02-2025 09:00"
}
]
}
)
def get(self, request):
"""
Récupère les fiches d'inscriptions en fonction du filtre passé.
"""
# Récupération des paramètres
filter = request.GET.get('filter', '').strip()
search = request.GET.get('search', '').strip()
page_size = request.GET.get('page_size', None)
# Gestion du page_size
if page_size is not None:
try:
page_size = int(page_size)
except ValueError:
page_size = settings.NB_RESULT_PER_PAGE
# Définir le cache_key en fonction du filtre
page_number = request.GET.get('page', 1)
cache_key = f'N3WT_ficheInscriptions_{filter}_page_{page_number}_search_{search if filter == "pending" else ""}'
cached_page = cache.get(cache_key)
if cached_page:
return JsonResponse(cached_page, safe=False)
# Récupérer les dossier d'inscriptions en fonction du filtre
registerForms_List = None
if filter == 'pending':
exclude_states = [RegistrationForm.RegistrationFormStatus.RF_VALIDATED, RegistrationForm.RegistrationFormStatus.RF_ARCHIVED]
registerForms_List = bdd.searchObjects(RegistrationForm, search, _excludeStates=exclude_states)
elif filter == 'archived':
registerForms_List = bdd.getObjects(RegistrationForm, 'status', RegistrationForm.RegistrationFormStatus.RF_ARCHIVED)
elif filter == 'subscribed':
registerForms_List = bdd.getObjects(RegistrationForm, 'status', RegistrationForm.RegistrationFormStatus.RF_VALIDATED)
else:
registerForms_List = None
if not registerForms_List:
return JsonResponse({'error': 'aucune donnée trouvée', 'count': 0}, safe=False)
# Pagination
paginator = self.pagination_class()
page = paginator.paginate_queryset(registerForms_List, request)
if page is not None:
registerForms_serializer = RegistrationFormSerializer(page, many=True)
response_data = paginator.get_paginated_response(registerForms_serializer.data)
cache.set(cache_key, response_data, timeout=60 * 15)
return JsonResponse(response_data, safe=False)
return JsonResponse({'error': 'aucune donnée trouvée', 'count': 0}, safe=False)
@swagger_auto_schema(
request_body=RegistrationFormSerializer,
responses={200: RegistrationFormSerializer()},
operation_description="Crée un dossier d'inscription.",
operation_summary="Créer un dossier d'inscription",
examples={
"application/json": {
"student": {
"id": 1,
"first_name": "John",
"last_name": "Doe",
"date_of_birth": "2010-01-01"
},
"status": "pending",
"last_update": "10-02-2025 10:00",
"codeLienInscription": "ABC123XYZ456"
}
}
)
@method_decorator(csrf_protect, name='dispatch')
@method_decorator(ensure_csrf_cookie, name='dispatch')
def post(self, request):
"""
Crée un dossier d'inscription.
"""
regiterFormData = request.data.copy()
logger.info(f"Création d'un dossier d'inscription {request}")
# Ajout de la date de mise à jour
regiterFormData["last_update"] = util.convertToStr(util._now(), '%d-%m-%Y %H:%M')
# Ajout du code d'inscription
code = util.genereRandomCode(12)
regiterFormData["codeLienInscription"] = code
guardiansId = regiterFormData.pop('idGuardians', [])
registerForm_serializer = RegistrationFormSerializer(data=regiterFormData)
fileGroupId = regiterFormData.pop('fileGroup', None)
if registerForm_serializer.is_valid():
di = registerForm_serializer.save()
# Mise à jour de l'automate
updateStateMachine(di, 'creationDI')
# Récupération du reponsable associé
for guardianId in guardiansId:
guardian = Guardian.objects.get(id=guardianId)
di.student.guardians.add(guardian)
di.save()
if fileGroupId:
di.fileGroup = RegistrationFileGroup.objects.get(id=fileGroupId)
di.save()
return JsonResponse(registerForm_serializer.data, safe=False)
else:
logger.error(f"Erreur lors de la validation des données {regiterFormData}")
return JsonResponse(registerForm_serializer.errors, safe=False, status=status.HTTP_400_BAD_REQUEST)
# /Subscriptions/registerForms/{id}
class RegisterFormWithIdView(APIView):
"""
Gère la lecture, création, modification et suppression dun dossier dinscription.
"""
pagination_class = CustomPagination
@swagger_auto_schema(
responses={200: RegistrationFormSerializer()},
operation_description="Récupère un dossier d'inscription donné.",
operation_summary="Récupérer un dossier d'inscription",
examples={
"application/json": {
"id": 1,
"student": {
"id": 1,
"first_name": "John",
"last_name": "Doe",
"date_of_birth": "2010-01-01"
},
}
}
)
def get(self, request, id):
"""
Récupère un dossier d'inscription donné.
"""
registerForm = bdd.getObject(RegistrationForm, "student__id", id)
if registerForm is None:
return JsonResponse({"errorMessage":'Le dossier d\'inscription n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND)
registerForm_serializer = RegistrationFormSerializer(registerForm)
return JsonResponse(registerForm_serializer.data, safe=False)
@swagger_auto_schema(
request_body=RegistrationFormSerializer,
responses={200: RegistrationFormSerializer()},
operation_description="Modifie un dossier d'inscription donné.",
operation_summary="Modifier un dossier d'inscription",
examples={
"application/json": {
"id": 1,
"student": {
"id": 1,
"first_name": "John",
"last_name": "Doe",
"date_of_birth": "2010-01-01"
},
"status": "under_review",
"last_update": "10-02-2025 10:00"
}
}
)
@method_decorator(csrf_protect, name='dispatch')
@method_decorator(ensure_csrf_cookie, name='dispatch')
def put(self, request, id):
"""
Modifie un dossier d'inscription donné.
"""
studentForm_data = JSONParser().parse(request)
_status = studentForm_data.pop('status', 0)
studentForm_data["last_update"] = str(util.convertToStr(util._now(), '%d-%m-%Y %H:%M'))
registerForm = bdd.getObject(_objectName=RegistrationForm, _columnName='student__id', _value=id)
if _status == RegistrationForm.RegistrationFormStatus.RF_UNDER_REVIEW:
try:
# Génération de la fiche d'inscription au format PDF
base_dir = f"data/registration_files/dossier_rf_{registerForm.pk}"
os.makedirs(base_dir, exist_ok=True)
# Fichier PDF initial
initial_pdf = f"{base_dir}/rf_{registerForm.student.last_name}_{registerForm.student.first_name}.pdf"
registerForm.registration_file = util.rfToPDF(registerForm, initial_pdf)
registerForm.save()
# Récupération des fichiers d'inscription
fileNames = RegistrationFile.get_files_from_rf(registerForm.pk)
if registerForm.registration_file:
fileNames.insert(0, registerForm.registration_file.path)
# Création du fichier PDF Fusionné
merged_pdf = f"{base_dir}/dossier_complet_{registerForm.pk}.pdf"
util.merge_files_pdf(fileNames, merged_pdf)
# Mise à jour du champ registration_file avec le fichier fusionné
with open(merged_pdf, 'rb') as f:
registerForm.registration_file.save(
os.path.basename(merged_pdf),
File(f),
save=True
)
# Mise à jour de l'automate
updateStateMachine(registerForm, 'saisiDI')
except Exception as e:
return JsonResponse({'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
elif _status == RegistrationForm.RegistrationFormStatus.RF_VALIDATED:
# L'école a validé le dossier d'inscription
# Mise à jour de l'automate
updateStateMachine(registerForm, 'valideDI')
studentForm_serializer = RegistrationFormSerializer(registerForm, data=studentForm_data)
if studentForm_serializer.is_valid():
studentForm_serializer.save()
return JsonResponse(studentForm_serializer.data, safe=False)
return JsonResponse(studentForm_serializer.errors, safe=False, status=status.HTTP_400_BAD_REQUEST)
@swagger_auto_schema(
responses={204: 'No Content'},
operation_description="Supprime un dossier d'inscription donné.",
operation_summary="Supprimer un dossier d'inscription"
)
@method_decorator(csrf_protect, name='dispatch')
@method_decorator(ensure_csrf_cookie, name='dispatch')
def delete(self, request, id):
"""
Supprime un dossier d'inscription donné.
"""
register_form = bdd.getObject(_objectName=RegistrationForm, _columnName='student__id', _value=id)
if register_form != None:
student = register_form.student
student.guardians.clear()
student.profiles.clear()
student.registration_files.clear()
student.delete()
clear_cache()
return JsonResponse("La suppression du dossier a été effectuée avec succès", safe=False)
return JsonResponse({"errorMessage":'Aucun dossier d\'inscription rattaché à l\'élève'}, safe=False, status=status.HTTP_404_NOT_FOUND)
@swagger_auto_schema(
method='get',
responses={200: openapi.Response('Success', schema=openapi.Schema(
type=openapi.TYPE_OBJECT,
properties={
'message': openapi.Schema(type=openapi.TYPE_STRING)
}
))},
operation_description="Envoie le dossier d'inscription par e-mail",
operation_summary="Envoyer un dossier d'inscription"
)
@api_view(['GET'])
def send(request,id):
"""Envoie le dossier d'inscription par e-mail."""
register_form = bdd.getObject(_objectName=RegistrationForm, _columnName='student__id', _value=id)
if register_form != None:
student = register_form.student
guardian = student.getMainGuardian()
email = guardian.email
errorMessage = mailer.sendRegisterForm(email)
if errorMessage == '':
register_form.last_update=util.convertToStr(util._now(), '%d-%m-%Y %H:%M')
updateStateMachine(register_form, 'envoiDI')
return JsonResponse({"message": f"Le dossier d'inscription a bien été envoyé à l'addresse {email}"}, safe=False)
return JsonResponse({"errorMessage":errorMessage}, safe=False, status=status.HTTP_400_BAD_REQUEST)
return JsonResponse({"errorMessage":'Dossier d\'inscription non trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND)
@swagger_auto_schema(
method='get',
responses={200: openapi.Response('Success', schema=openapi.Schema(
type=openapi.TYPE_OBJECT,
properties={
'message': openapi.Schema(type=openapi.TYPE_STRING)
}
))},
operation_description="Archive le dossier d'inscription",
operation_summary="Archiver un dossier d'inscription"
)
@api_view(['GET'])
def archive(request,id):
"""Archive le dossier d'inscription."""
register_form = bdd.getObject(_objectName=RegistrationForm, _columnName='student__id', _value=id)
if register_form != None:
register_form.last_update=util.convertToStr(util._now(), '%d-%m-%Y %H:%M')
updateStateMachine(register_form, 'archiveDI')
return JsonResponse({"message": "Le dossier a été archivé avec succès"}, safe=False)
return JsonResponse({"errorMessage":'Dossier d\'inscription non trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND)
@swagger_auto_schema(
method='get',
responses={200: openapi.Response('Success', schema=openapi.Schema(
type=openapi.TYPE_OBJECT,
properties={
'message': openapi.Schema(type=openapi.TYPE_STRING)
}
))},
operation_description="Relance un dossier d'inscription par e-mail",
operation_summary="Relancer un dossier d'inscription"
)
@api_view(['GET'])
def resend(request,id):
"""Relance un dossier d'inscription par e-mail."""
register_form = bdd.getObject(_objectName=RegistrationForm, _columnName='student__id', _value=id)
if register_form != None:
student = register_form.student
guardian = student.getMainGuardian()
email = guardian.email
errorMessage = mailer.envoieRelanceDossierInscription(email, register_form.codeLienInscription)
if errorMessage == '':
register_form.status=RegistrationForm.RegistrationFormStatus.RF_SENT
register_form.last_update=util.convertToStr(util._now(), '%d-%m-%Y %H:%M')
register_form.save()
return JsonResponse({"message": f"Le dossier a été renvoyé à l'adresse {email}"}, safe=False)
return JsonResponse({"errorMessage":errorMessage}, safe=False, status=status.HTTP_400_BAD_REQUEST)
return JsonResponse({"errorMessage":'Dossier d\'inscription non trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND)

View File

@ -0,0 +1,125 @@
from django.http.response import JsonResponse
from drf_yasg.utils import swagger_auto_schema
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework import status
from rest_framework.decorators import action, api_view
from drf_yasg.utils import swagger_auto_schema
from drf_yasg import openapi
from Subscriptions.serializers import RegistrationFileGroupSerializer
from Subscriptions.models import RegistrationFileGroup, RegistrationFileTemplate
from N3wtSchool import bdd
class RegistrationFileGroupView(APIView):
@swagger_auto_schema(
operation_description="Récupère tous les groupes de fichiers d'inscription",
responses={200: RegistrationFileGroupSerializer(many=True)}
)
def get(self, request):
"""
Récupère tous les groupes de fichiers d'inscription.
"""
groups = RegistrationFileGroup.objects.all()
serializer = RegistrationFileGroupSerializer(groups, many=True)
return Response(serializer.data)
@swagger_auto_schema(
operation_description="Crée un nouveau groupe de fichiers d'inscription",
request_body=RegistrationFileGroupSerializer,
responses={
201: RegistrationFileGroupSerializer,
400: "Données invalides"
}
)
def post(self, request):
"""
Crée un nouveau groupe de fichiers d'inscription.
"""
serializer = RegistrationFileGroupSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
class RegistrationFileGroupSimpleView(APIView):
@swagger_auto_schema(
operation_description="Récupère un groupe de fichiers d'inscription spécifique",
responses={
200: RegistrationFileGroupSerializer,
404: "Groupe non trouvé"
}
)
def get(self, request, id):
"""
Récupère un groupe de fichiers d'inscription spécifique.
"""
group = bdd.getObject(_objectName=RegistrationFileGroup, _columnName='id', _value=id)
if group is None:
return JsonResponse({"errorMessage": "Le groupe de fichiers n'a pas été trouvé"},
status=status.HTTP_404_NOT_FOUND)
serializer = RegistrationFileGroupSerializer(group)
return JsonResponse(serializer.data)
@swagger_auto_schema(
operation_description="Met à jour un groupe de fichiers d'inscription",
request_body=RegistrationFileGroupSerializer,
responses={
200: RegistrationFileGroupSerializer,
400: "Données invalides",
404: "Groupe non trouvé"
}
)
def put(self, request, id):
"""
Met à jour un groupe de fichiers d'inscription existant.
"""
group = bdd.getObject(_objectName=RegistrationFileGroup, _columnName='id', _value=id)
if group is None:
return JsonResponse({'erreur': "Le groupe de fichiers n'a pas été trouvé"},
status=status.HTTP_404_NOT_FOUND)
serializer = RegistrationFileGroupSerializer(group, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
@swagger_auto_schema(
operation_description="Supprime un groupe de fichiers d'inscription",
responses={
204: "Suppression réussie",
404: "Groupe non trouvé"
}
)
def delete(self, request, id):
"""
Supprime un groupe de fichiers d'inscription.
"""
group = bdd.getObject(_objectName=RegistrationFileGroup, _columnName='id', _value=id)
if group is not None:
group.delete()
return JsonResponse({'message': 'La suppression du groupe a été effectuée avec succès'},
status=status.HTTP_204_NO_CONTENT)
return JsonResponse({'erreur': "Le groupe de fichiers n'a pas été trouvé"},
status=status.HTTP_404_NOT_FOUND)
@swagger_auto_schema(
method='get',
responses={200: openapi.Response('Success', schema=openapi.Schema(
type=openapi.TYPE_OBJECT,
properties={
'message': openapi.Schema(type=openapi.TYPE_STRING)
}
))},
operation_description="Récupère les fichiers d'inscription d'un groupe donné",
operation_summary="Récupèrer les fichiers d'inscription d'un groupe donné"
)
@api_view(['GET'])
def get_registration_files_by_group(request, id):
try:
group = RegistrationFileGroup.objects.get(id=id)
templates = RegistrationFileTemplate.objects.filter(group=group)
templates_data = list(templates.values())
return JsonResponse(templates_data, safe=False)
except RegistrationFileGroup.DoesNotExist:
return JsonResponse({'error': 'Le groupe de fichiers n\'a pas été trouvé'}, status=404)

View File

@ -0,0 +1,211 @@
from django.http.response import JsonResponse
from django.core.files import File
from drf_yasg.utils import swagger_auto_schema
from drf_yasg import openapi
from rest_framework.parsers import MultiPartParser, FormParser
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework import status
import os
from Subscriptions.serializers import RegistrationFileTemplateSerializer, RegistrationFileSerializer
from Subscriptions.models import RegistrationFileTemplate, RegistrationFile
from N3wtSchool import bdd
class RegistrationFileTemplateView(APIView):
@swagger_auto_schema(
operation_description="Récupère tous les fichiers templates pour les dossiers d'inscription",
responses={200: RegistrationFileTemplateSerializer(many=True)}
)
def get(self, request):
"""
Récupère les fichiers templates pour les dossiers dinscription.
"""
files = RegistrationFileTemplate.objects.all()
serializer = RegistrationFileTemplateSerializer(files, many=True)
return Response(serializer.data)
@swagger_auto_schema(
operation_description="Crée un nouveau fichier template pour les dossiers d'inscription",
request_body=RegistrationFileTemplateSerializer,
responses={
201: RegistrationFileTemplateSerializer,
400: "Données invalides"
}
)
def post(self, request):
"""
Crée un fichier template pour les dossiers dinscription.
"""
serializer = RegistrationFileTemplateSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
class RegistrationFileTemplateSimpleView(APIView):
"""
Gère les fichiers templates pour les dossiers dinscription.
"""
parser_classes = (MultiPartParser, FormParser)
@swagger_auto_schema(
operation_description="Récupère un fichier template spécifique",
responses={
200: RegistrationFileTemplateSerializer,
404: "Fichier template non trouvé"
}
)
def get(self, request, id):
"""
Récupère les fichiers templates pour les dossiers dinscription.
"""
registationFileTemplate = bdd.getObject(_objectName=RegistrationFileTemplate, _columnName='id', _value=id)
if registationFileTemplate is None:
return JsonResponse({"errorMessage":'Le fichier d\'inscription n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND)
serializer = RegistrationFileTemplateSerializer(registationFileTemplate)
return JsonResponse(serializer.data, safe=False)
@swagger_auto_schema(
operation_description="Met à jour un fichier template existant",
request_body=RegistrationFileTemplateSerializer,
responses={
201: RegistrationFileTemplateSerializer,
400: "Données invalides",
404: "Fichier template non trouvé"
}
)
def put(self, request, id):
"""
Met à jour un fichier template existant.
"""
registationFileTemplate = bdd.getObject(_objectName=RegistrationFileTemplate, _columnName='id', _value=id)
if registationFileTemplate is None:
return JsonResponse({'erreur': 'Le fichier d\'inscription n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND)
serializer = RegistrationFileTemplateSerializer(registationFileTemplate,data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
@swagger_auto_schema(
operation_description="Supprime un fichier template",
responses={
204: "Suppression réussie",
404: "Fichier template non trouvé"
}
)
def delete(self, request, id):
"""
Supprime un fichier template existant.
"""
registrationFileTemplate = bdd.getObject(_objectName=RegistrationFileTemplate, _columnName='id', _value=id)
if registrationFileTemplate is not None:
registrationFileTemplate.file.delete() # Supprimer le fichier uploadé
registrationFileTemplate.delete()
return JsonResponse({'message': 'La suppression du fichier d\'inscription a été effectuée avec succès'}, safe=False, status=status.HTTP_204_NO_CONTENT)
else:
return JsonResponse({'erreur': 'Le fichier d\'inscription n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND)
class RegistrationFileView(APIView):
@swagger_auto_schema(
operation_description="Récupère tous les fichiers d'inscription",
responses={200: RegistrationFileSerializer(many=True)}
)
def get(self, request):
"""
Récupère les fichiers liés à un dossier dinscription donné.
"""
files = RegistrationFile.objects.all()
serializer = RegistrationFileSerializer(files, many=True)
return Response(serializer.data)
@swagger_auto_schema(
operation_description="Crée un nouveau fichier d'inscription",
request_body=RegistrationFileSerializer,
responses={
201: RegistrationFileSerializer,
400: "Données invalides"
}
)
def post(self, request):
"""
Crée un RegistrationFile pour le RegistrationForm associé.
"""
serializer = RegistrationFileSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
class RegistrationFileSimpleView(APIView):
"""
Gère la création, mise à jour et suppression de fichiers liés à un dossier dinscription.
"""
parser_classes = (MultiPartParser, FormParser)
@swagger_auto_schema(
operation_description="Récupère un fichier d'inscription spécifique",
responses={
200: RegistrationFileSerializer,
404: "Fichier non trouvé"
}
)
def get(self, request, id):
"""
Récupère les fichiers liés à un dossier dinscription donné.
"""
registationFile = bdd.getObject(_objectName=RegistrationFile, _columnName='id', _value=id)
if registationFile is None:
return JsonResponse({"errorMessage":'Le fichier d\'inscription n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND)
serializer = RegistrationFileSerializer(registationFile)
return JsonResponse(serializer.data, safe=False)
@swagger_auto_schema(
operation_description="Met à jour un fichier d'inscription existant",
request_body=RegistrationFileSerializer,
responses={
200: openapi.Response(
description="Fichier mis à jour avec succès",
schema=RegistrationFileSerializer
),
400: "Données invalides",
404: "Fichier non trouvé"
}
)
def put(self, request, id):
"""
Met à jour un RegistrationFile existant.
"""
registrationFile = bdd.getObject(_objectName=RegistrationFile, _columnName='id', _value=id)
if registrationFile is None:
return JsonResponse({'erreur': 'Le fichier d\'inscription n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND)
serializer = RegistrationFileSerializer(registrationFile, data=request.data)
if serializer.is_valid():
serializer.save()
return Response({'message': 'Fichier mis à jour avec succès', 'data': serializer.data}, status=status.HTTP_200_OK)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
@swagger_auto_schema(
operation_description="Supprime un fichier d'inscription",
responses={
200: "Suppression réussie",
404: "Fichier non trouvé"
}
)
def delete(self, request, id):
"""
Supprime un RegistrationFile existant.
"""
registrationFile = bdd.getObject(_objectName=RegistrationFile, _columnName='id', _value=id)
if registrationFile is not None:
registrationFile.file.delete() # Supprimer le fichier uploadé
registrationFile.delete()
return JsonResponse({'message': 'La suppression du fichier a été effectuée avec succès'}, safe=False)
else:
return JsonResponse({'erreur': 'Le fichier n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND)

View File

@ -0,0 +1,83 @@
from django.http.response import JsonResponse
from rest_framework.views import APIView
from rest_framework import status
from drf_yasg.utils import swagger_auto_schema
from drf_yasg import openapi
from Subscriptions.serializers import StudentByRFCreationSerializer, RegistrationFormByParentSerializer, StudentSerializer
from Subscriptions.models import Student, RegistrationForm
from N3wtSchool import bdd
class StudentView(APIView):
"""
Gère la lecture dun élève donné.
"""
@swagger_auto_schema(
operation_summary="Récupérer les informations d'un élève",
operation_description="Retourne les détails d'un élève spécifique à partir de son ID",
responses={
200: openapi.Response('Détails de l\'élève', StudentSerializer),
404: openapi.Response('Élève non trouvé')
},
manual_parameters=[
openapi.Parameter(
'id', openapi.IN_PATH,
description="ID de l'élève",
type=openapi.TYPE_INTEGER,
required=True
)
]
)
def get(self, request, id):
student = bdd.getObject(_objectName=Student, _columnName='id', _value=id)
if student is None:
return JsonResponse({"errorMessage":'Aucun élève trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND)
student_serializer = StudentSerializer(student)
return JsonResponse(student_serializer.data, safe=False)
# API utilisée pour la vue de création d'un DI
class StudentListView(APIView):
"""
Pour la vue de création dun dossier dinscription : liste les élèves disponibles.
"""
@swagger_auto_schema(
operation_summary="Lister tous les élèves",
operation_description="Retourne la liste de tous les élèves inscrits ou en cours d'inscription",
responses={
200: openapi.Response('Liste des élèves', StudentByRFCreationSerializer(many=True))
}
)
# Récupération de la liste des élèves inscrits ou en cours d'inscriptions
def get(self, request):
students = bdd.getAllObjects(_objectName=Student)
students_serializer = StudentByRFCreationSerializer(students, many=True)
return JsonResponse(students_serializer.data, safe=False)
# API utilisée pour la vue parent
class ChildrenListView(APIView):
"""
Pour la vue parent : liste les élèves rattachés à un profil donné.
"""
@swagger_auto_schema(
operation_summary="Lister les élèves d'un parent",
operation_description="Retourne la liste des élèves associés à un profil parent spécifique",
responses={
200: openapi.Response('Liste des élèves du parent', RegistrationFormByParentSerializer(many=True))
},
manual_parameters=[
openapi.Parameter(
'id', openapi.IN_PATH,
description="ID du profil parent",
type=openapi.TYPE_INTEGER,
required=True
)
]
)
# Récupération des élèves d'un parent
# idProfile : identifiant du profil connecté rattaché aux fiches d'élèves
def get(self, request, id):
students = bdd.getObjects(_objectName=RegistrationForm, _columnName='student__guardians__associated_profile__id', _value=id)
students_serializer = RegistrationFormByParentSerializer(students, many=True)
return JsonResponse(students_serializer.data, safe=False)

0
Back-End/src/app.js Normal file
View File

View File

View File

@ -16,17 +16,25 @@ import { createDatas,
fetchRegistrationDiscounts,
fetchTuitionDiscounts,
fetchRegistrationFees,
fetchTuitionFees } from '@/app/lib/schoolAction';
fetchTuitionFees,
} from '@/app/lib/schoolAction';
import SidebarTabs from '@/components/SidebarTabs';
import FilesManagement from '@/components/Structure/Files/FilesManagement';
import { fetchRegisterFormFileTemplate } from '@/app/lib/subscriptionAction';
export default function Page() {
const [specialities, setSpecialities] = useState([]);
const [classes, setClasses] = useState([]);
const [teachers, setTeachers] = useState([]);
const [schedules, setSchedules] = useState([]); // Add this line
const [registrationDiscounts, setRegistrationDiscounts] = useState([]);
const [tuitionDiscounts, setTuitionDiscounts] = useState([]);
const [registrationFees, setRegistrationFees] = useState([]);
const [tuitionFees, setTuitionFees] = useState([]);
const [fichiers, setFichiers] = useState([]);
const csrfToken = useCsrfToken();
@ -54,6 +62,15 @@ export default function Page() {
// Fetch data for tuition fees
handleTuitionFees();
// Fetch data for registration file templates
fetchRegisterFormFileTemplate()
.then((data)=> {
setFichiers(data)
})
.catch(error => console.error('Error fetching files:', error));
}, []);
const handleSpecialities = () => {
@ -224,6 +241,11 @@ export default function Page() {
handleDelete={handleDelete}
/>
)
},
{
id: 'Files',
label: 'Documents d\'inscription',
content: <FilesManagement csrfToken={csrfToken} />
}
];

View File

@ -11,11 +11,10 @@ import Loader from '@/components/Loader';
import AlertWithModal from '@/components/AlertWithModal';
import DropdownMenu from "@/components/DropdownMenu";
import { formatPhoneNumber } from '@/utils/Telephone';
import { MoreVertical, Send, Edit, Trash2, FileText, CheckCircle, Plus, Download } from 'lucide-react';
import { MoreVertical, Send, Edit, Trash2, FileText, CheckCircle, Plus } from 'lucide-react';
import Modal from '@/components/Modal';
import InscriptionForm from '@/components/Inscription/InscriptionForm'
import AffectationClasseForm from '@/components/AffectationClasseForm'
import FileUpload from './components/FileUpload';
import {
PENDING,
@ -26,9 +25,6 @@ import {
sendRegisterForm,
archiveRegisterForm,
fetchRegisterFormFileTemplate,
deleteRegisterFormFileTemplate,
createRegistrationFormFileTemplate,
editRegistrationFormFileTemplate,
fetchStudents,
editRegisterForm } from "@/app/lib/subscriptionAction"
@ -47,7 +43,7 @@ import {
import DjangoCSRFToken from '@/components/DjangoCSRFToken'
import useCsrfToken from '@/hooks/useCsrfToken';
import { formatDate } from '@/utils/Date';
import { fetchRegistrationFileGroups } from '@/app/lib/registerFileGroupAction';
const useFakeData = process.env.NEXT_PUBLIC_USE_FAKE_DATA === 'true';
@ -77,14 +73,13 @@ export default function Page({ params: { locale } }) {
const [classes, setClasses] = useState([]);
const [students, setEleves] = useState([]);
const [reloadFetch, setReloadFetch] = useState(false);
const [isModalOpen, setIsModalOpen] = useState(false);
const [isEditing, setIsEditing] = useState(false);
const [fileToEdit, setFileToEdit] = useState(null);
const [registrationDiscounts, setRegistrationDiscounts] = useState([]);
const [tuitionDiscounts, setTuitionDiscounts] = useState([]);
const [registrationFees, setRegistrationFees] = useState([]);
const [tuitionFees, setTuitionFees] = useState([]);
const [groups, setGroups] = useState([]);
const csrfToken = useCsrfToken();
@ -228,6 +223,11 @@ const registerFormArchivedDataHandler = (data) => {
setTuitionFees(data);
})
.catch(requestErrorHandler);
fetchRegistrationFileGroups()
.then(data => {
setGroups(data);
})
.catch(error => console.error('Error fetching file groups:', error));
} else {
setTimeout(() => {
setRegistrationFormsDataPending(mockFicheInscription);
@ -357,7 +357,7 @@ useEffect(()=>{
const selectedRegistrationDiscountsIds = updatedData.selectedRegistrationDiscounts.map(discountId => discountId)
const selectedTuitionFeesIds = updatedData.selectedTuitionFees.map(feeId => feeId)
const selectedTuitionDiscountsIds = updatedData.selectedTuitionDiscounts.map(discountId => discountId)
const selectedFileGroup = updatedData.selectedFileGroup
const allFeesIds = [...selectedRegistrationFeesIds, ...selectedTuitionFeesIds];
const allDiscountsds = [...selectedRegistrationDiscountsIds, ...selectedTuitionDiscountsIds];
@ -370,7 +370,8 @@ useEffect(()=>{
},
idGuardians: selectedGuardiansIds,
fees: allFeesIds,
discounts: allDiscountsds
discounts: allDiscountsds,
fileGroup: selectedFileGroup
};
createRegisterForm(data, csrfToken)
@ -567,89 +568,23 @@ const columnsSubscribed = [
];
const handleFileDelete = (fileId) => {
deleteRegisterFormFileTemplate(fileId,csrfToken)
.then(response => {
if (response.ok) {
setFichiers(fichiers.filter(fichier => fichier.id !== fileId));
alert('Fichier supprimé avec succès.');
} else {
alert('Erreur lors de la suppression du fichier.');
}
})
.catch(error => {
console.error('Error deleting file:', error);
alert('Erreur lors de la suppression du fichier.');
});
};
const handleFileEdit = (file) => {
setIsEditing(true);
setFileToEdit(file);
setIsModalOpen(true);
};
const columnsFiles = [
{ name: 'Nom du fichier', transform: (row) => row.name },
{ name: 'Date de création', transform: (row) => formatDate(new Date (row.date_added),"DD/MM/YYYY hh:mm:ss") },
{ name: 'Fichier Obligatoire', transform: (row) => row.is_required ? 'Oui' : 'Non' },
{ name: 'Ordre de fusion', transform: (row) => row.order },
{ name: 'Actions', transform: (row) => (
<div className="flex items-center justify-center gap-2">
{
row.file && (
<a href={`${BASE_URL}${row.file}`} target='_blank' className="text-blue-500 hover:text-blue-700">
<Download size={16} />
</a>)
}
<button onClick={() => handleFileEdit(row)} className="text-blue-500 hover:text-blue-700">
<Edit size={16} />
</button>
<button onClick={() => handleFileDelete(row.id)} className="text-red-500 hover:text-red-700">
<Trash2 size={16} />
</button>
</div>
) },
];
const handleFileUpload = ({file, name, is_required, order}) => {
if (!name) {
alert('Veuillez entrer un nom de fichier.');
return;
}
const formData = new FormData();
if(file){
formData.append('file', file);
}
formData.append('name', name);
formData.append('is_required', is_required);
formData.append('order', order);
if (isEditing && fileToEdit) {
editRegistrationFormFileTemplate(fileToEdit.id, formData, csrfToken)
.then(data => {
setFichiers(prevFichiers =>
prevFichiers.map(f => f.id === fileToEdit.id ? data : f)
);
setIsModalOpen(false);
setFileToEdit(null);
setIsEditing(false);
})
.catch(error => {
console.error('Error editing file:', error);
});
} else {
createRegistrationFormFileTemplate(formData, csrfToken)
.then(data => {
setFichiers([...fichiers, data]);
setIsModalOpen(false);
})
.catch(error => {
console.error('Error uploading file:', error);
});
}
};
const tabs = [
{
id: 'pending',
label: t('pending'),
count: totalPending
},
{
id: 'subscribed',
label: t('subscribed'),
count: totalSubscribed
},
{
id: 'archived',
label: t('archived'),
count: totalArchives
}
];
if (isLoading) {
return <Loader />;
@ -699,16 +634,6 @@ const handleFileUpload = ({file, name, is_required, order}) => {
active={activeTab === 'archived'}
onClick={() => setActiveTab('archived')}
/>
<Tab
text={(
<>
{t('subscribeFiles')}
<span className="ml-2 text-sm text-gray-400">({fichiers.length})</span>
</>
)}
active={activeTab === 'subscribeFiles'}
onClick={() => setActiveTab('subscribeFiles')}
/>
</div>
</div>
<div className="border-b border-gray-200 mb-6 w-full">
@ -758,41 +683,6 @@ const handleFileUpload = ({file, name, is_required, order}) => {
</div>
</React.Fragment>
) : null}
{/*SI STATE == subscribeFiles */}
{activeTab === 'subscribeFiles' && (
<div>
<div className="flex justify-end mb-4">
<button
onClick={() => { setIsModalOpen(true); setIsEditing(false); }}
className="flex items-center bg-emerald-600 text-white p-2 rounded-full shadow hover:bg-emerald-900 transition duration-200"
>
<Plus className="w-5 h-5" />
</button>
</div>
<Modal
isOpen={isModalOpen}
setIsOpen={setIsModalOpen}
title={isEditing ? 'Modifier un fichier' : 'Ajouter un fichier'}
ContentComponent={() => (
<FileUpload
onFileUpload={handleFileUpload}
fileToEdit={fileToEdit}
/>
)}
/>
<div className="mt-8">
<Table
data={fichiers}
columns={columnsFiles}
itemsPerPage={itemsPerPage}
currentPage={currentPage}
totalPages={totalPages}
onPageChange={handlePageChange}
className="mt-8"
/>
</div>
</div>
)}
</div>
<Popup
visible={popup.visible}
@ -815,6 +705,7 @@ const handleFileUpload = ({file, name, is_required, order}) => {
tuitionDiscounts={tuitionDiscounts}
registrationFees={registrationFees.filter(fee => fee.is_active)}
tuitionFees={tuitionFees.filter(fee => fee.is_active)}
groups={groups}
onSubmit={createRF}
/>
)}

View File

@ -0,0 +1,74 @@
import { BE_SUBSCRIPTION_REGISTRATIONFILE_GROUPS_URL } from '@/utils/Url';
export async function fetchRegistrationFileGroups() {
const response = await fetch(`${BE_SUBSCRIPTION_REGISTRATIONFILE_GROUPS_URL}`, {
credentials: 'include',
headers: {
'Accept': 'application/json',
}
});
if (!response.ok) {
throw new Error('Failed to fetch file groups');
}
return response.json();
}
export async function createRegistrationFileGroup(groupData, csrfToken) {
const response = await fetch(`${BE_SUBSCRIPTION_REGISTRATIONFILE_GROUPS_URL}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': csrfToken,
},
body: JSON.stringify(groupData),
credentials: 'include'
});
if (!response.ok) {
throw new Error('Failed to create file group');
}
return response.json();
}
export async function deleteRegistrationFileGroup(groupId, csrfToken) {
const response = await fetch(`${BE_SUBSCRIPTION_REGISTRATIONFILE_GROUPS_URL}/${groupId}`, {
method: 'DELETE',
headers: {
'X-CSRFToken': csrfToken,
},
credentials: 'include'
});
return response;
}
export const editRegistrationFileGroup = async (groupId, groupData, csrfToken) => {
const response = await fetch(`${BE_SUBSCRIPTION_REGISTRATIONFILE_GROUPS_URL}/${groupId}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': csrfToken,
},
body: JSON.stringify(groupData),
});
if (!response.ok) {
throw new Error('Erreur lors de la modification du groupe');
}
return response.json();
};
export const fetchRegistrationFileFromGroup = async (groupId) => {
const response = await fetch(`${BE_SUBSCRIPTION_REGISTRATIONFILE_GROUPS_URL}/${groupId}/registrationFiles`, {
credentials: 'include',
headers: {
'Accept': 'application/json',
}
});
if (!response.ok) {
throw new Error('Erreur lors de la récupération des fichiers associés au groupe');
}
return response.json();
}

View File

@ -1,13 +1,9 @@
import {
BE_SUBSCRIPTION_STUDENTS_URL,
BE_SUBSCRIPTION_STUDENT_URL,
BE_SUBSCRIPTION_ARCHIVE_URL,
BE_SUBSCRIPTION_SEND_URL,
BE_SUBSCRIPTION_CHILDRENS_URL,
BE_SUBSCRIPTION_REGISTERFORM_URL,
BE_SUBSCRIPTION_REGISTERFORMS_URL,
BE_SUBSCRIPTION_REGISTRATIONFORMFILE_TEMPLATE_URL,
BE_SUBSCRIPTION_LAST_GUARDIAN_URL,
BE_SUBSCRIPTION_LAST_GUARDIAN_ID_URL,
BE_SUBSCRIPTION_REGISTRATIONFORMFILE_URL
} from '@/utils/Url';
@ -28,10 +24,10 @@ const requestResponseHandler = async (response) => {
throw error;
}
export const fetchRegisterForms = (type=PENDING, page='', pageSize='', search = '') => {
let url = `${BE_SUBSCRIPTION_REGISTERFORMS_URL}/${type}`;
export const fetchRegisterForms = (filter=PENDING, page='', pageSize='', search = '') => {
let url = `${BE_SUBSCRIPTION_REGISTERFORMS_URL}?filter=${filter}`;
if (page !== '' && pageSize !== '') {
url = `${BE_SUBSCRIPTION_REGISTERFORMS_URL}/${type}?page=${page}&search=${search}`;
url = `${BE_SUBSCRIPTION_REGISTERFORMS_URL}?filter=${filter}&page=${page}&search=${search}`;
}
return fetch(url, {
headers: {
@ -40,18 +36,17 @@ export const fetchRegisterForms = (type=PENDING, page='', pageSize='', search =
}).then(requestResponseHandler)
};
export const fetchRegisterForm = (id) =>{
return fetch(`${BE_SUBSCRIPTION_REGISTERFORM_URL}/${id}`) // Utilisation de studentId au lieu de codeDI
export const fetchRegisterForm = (id) =>{
return fetch(`${BE_SUBSCRIPTION_REGISTERFORMS_URL}/${id}`) // Utilisation de studentId au lieu de codeDI
.then(requestResponseHandler)
}
export const fetchLastGuardian = () =>{
return fetch(`${BE_SUBSCRIPTION_LAST_GUARDIAN_URL}`)
export const fetchLastGuardian = () =>{
return fetch(`${BE_SUBSCRIPTION_LAST_GUARDIAN_ID_URL}`)
.then(requestResponseHandler)
}
export const editRegisterForm=(id, data, csrfToken)=>{
return fetch(`${BE_SUBSCRIPTION_REGISTERFORM_URL}/${id}`, {
return fetch(`${BE_SUBSCRIPTION_REGISTERFORMS_URL}/${id}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
@ -61,11 +56,11 @@ export const editRegisterForm=(id, data, csrfToken)=>{
credentials: 'include'
})
.then(requestResponseHandler)
};
export const createRegisterForm=(data, csrfToken)=>{
const url = `${BE_SUBSCRIPTION_REGISTERFORM_URL}`;
const url = `${BE_SUBSCRIPTION_REGISTERFORMS_URL}`;
return fetch(url, {
method: 'POST',
headers: {
@ -78,8 +73,26 @@ export const createRegisterForm=(data, csrfToken)=>{
.then(requestResponseHandler)
}
export const sendRegisterForm = (id) => {
const url = `${BE_SUBSCRIPTION_REGISTERFORMS_URL}/${id}/send`;
return fetch(url, {
headers: {
'Content-Type': 'application/json',
},
}).then(requestResponseHandler)
}
export const resendRegisterForm = (id) => {
const url = `${BE_SUBSCRIPTION_REGISTERFORMS_URL}/${id}/resend`;
return fetch(url, {
headers: {
'Content-Type': 'application/json',
},
}).then(requestResponseHandler)
}
export const archiveRegisterForm = (id) => {
const url = `${BE_SUBSCRIPTION_ARCHIVE_URL}/${id}`;
const url = `${BE_SUBSCRIPTION_REGISTERFORMS_URL}/${id}/archive`;
return fetch(url, {
method: 'GET',
headers: {
@ -88,18 +101,6 @@ export const archiveRegisterForm = (id) => {
}).then(requestResponseHandler)
}
export const sendRegisterForm = (id) => {
const url = `${BE_SUBSCRIPTION_SEND_URL}/${id}`;
return fetch(url, {
headers: {
'Content-Type': 'application/json',
},
}).then(requestResponseHandler)
}
export const fetchRegisterFormFile = (id = null) => {
let url = `${BE_SUBSCRIPTION_REGISTRATIONFORMFILE_URL}`
if (id) {
@ -204,9 +205,10 @@ export const editRegistrationFormFileTemplate = (fileId, data, csrfToken) => {
.then(requestResponseHandler)
}
export const fetchStudents = () => {
export const fetchStudents = (id) => {
const url = (id)?`${BE_SUBSCRIPTION_STUDENTS_URL}/${id}`:`${BE_SUBSCRIPTION_STUDENTS_URL}`;
const request = new Request(
`${BE_SUBSCRIPTION_STUDENTS_URL}`,
url,
{
method:'GET',
headers: {
@ -230,3 +232,16 @@ export const fetchChildren = (id) =>{
);
return fetch(request).then(requestResponseHandler)
}
export async function getRegisterFormFileTemplate(fileId) {
const response = await fetch(`${BE_SUBSCRIPTION_REGISTERFORM_FILE_TEMPLATE_URL}/${fileId}`, {
credentials: 'include',
headers: {
'Accept': 'application/json',
}
});
if (!response.ok) {
throw new Error('Failed to fetch file template');
}
return response.json();
}

View File

@ -1,18 +1,24 @@
import React, { useState, useEffect } from 'react';
import ToggleSwitch from '@/components/ToggleSwitch'; // Import du composant ToggleSwitch
import DraggableFileUpload from './DraggableFileUpload';
import { fetchRegistrationFileGroups } from '@/app/lib/registerFileGroupAction';
export default function FileUpload({ onFileUpload, fileToEdit = null }) {
const [fileName, setFileName] = useState('');
const [file, setFile] = useState(null);
const [isRequired, setIsRequired] = useState(false); // État pour le toggle isRequired
const [order, setOrder] = useState(0);
const [groups, setGroups] = useState([]);
const [selectedGroup, setSelectedGroup] = useState('');
useEffect(() => {
fetchRegistrationFileGroups().then(data => setGroups(data));
if (fileToEdit) {
setFileName(fileToEdit.name || '');
setIsRequired(fileToEdit.is_required || false);
setOrder(fileToEdit.fusion_order || 0);
setSelectedGroup(fileToEdit.group_id || '');
}
}, [fileToEdit]);
@ -26,11 +32,13 @@ export default function FileUpload({ onFileUpload, fileToEdit = null }) {
name: fileName,
is_required: isRequired,
order: parseInt(order, 10),
groupId: selectedGroup || null
});
setFile(null);
setFileName('');
setIsRequired(false);
setOrder(0);
setSelectedGroup('');
};
return (
@ -72,6 +80,19 @@ export default function FileUpload({ onFileUpload, fileToEdit = null }) {
onChange={() => setIsRequired(!isRequired)}
/>
</div>
<div className="mt-4">
<label className="block mb-2">Groupe</label>
<select
value={selectedGroup}
onChange={(e) => setSelectedGroup(e.target.value)}
className="w-full border rounded p-2"
>
<option value="">Aucun groupe</option>
{groups.map(group => (
<option key={group.id} value={group.id}>{group.name}</option>
))}
</select>
</div>
</div>
);
}

View File

@ -9,7 +9,7 @@ import DiscountsSection from '@/components/Structure/Tarification/DiscountsSecti
import SectionTitle from '@/components/SectionTitle';
import ProgressStep from '@/components/ProgressStep';
const InscriptionForm = ( { students, registrationDiscounts, tuitionDiscounts, registrationFees, tuitionFees, onSubmit, currentStep }) => {
const InscriptionForm = ( { students, registrationDiscounts, tuitionDiscounts, registrationFees, tuitionFees, onSubmit, currentStep, groups }) => {
const [formData, setFormData] = useState({
studentLastName: '',
studentFirstName: '',
@ -21,7 +21,8 @@ const InscriptionForm = ( { students, registrationDiscounts, tuitionDiscounts, r
selectedRegistrationDiscounts: [],
selectedRegistrationFees: registrationFees.map(fee => fee.id),
selectedTuitionDiscounts: [],
selectedTuitionFees: []
selectedTuitionFees: [],
selectedFileGroup: null // Ajout du groupe de fichiers sélectionné
});
const [step, setStep] = useState(currentStep || 1);
@ -35,10 +36,11 @@ const InscriptionForm = ( { students, registrationDiscounts, tuitionDiscounts, r
2: 'Nouveau Responsable',
3: "Frais d'inscription",
4: 'Frais de scolarité',
5: 'Récapitulatif'
5: 'Documents requis',
6: 'Récapitulatif'
};
const steps = ['Élève', 'Responsable', 'Inscription', 'Scolarité', 'Récap'];
const steps = ['Élève', 'Responsable', 'Inscription', 'Scolarité', 'Documents', 'Récap'];
const isStep1Valid = formData.studentLastName && formData.studentFirstName;
const isStep2Valid = (
@ -47,7 +49,8 @@ const InscriptionForm = ( { students, registrationDiscounts, tuitionDiscounts, r
);
const isStep3Valid = formData.selectedRegistrationFees.length > 0;
const isStep4Valid = formData.selectedTuitionFees.length > 0;
const isStep5Valid = isStep1Valid && isStep2Valid && isStep3Valid && isStep4Valid;
const isStep5Valid = formData.selectedFileGroup !== null;
const isStep6Valid = isStep1Valid && isStep2Valid && isStep3Valid && isStep4Valid && isStep5Valid;
const isStepValid = (stepNumber) => {
switch (stepNumber) {
@ -61,6 +64,8 @@ const InscriptionForm = ( { students, registrationDiscounts, tuitionDiscounts, r
return isStep4Valid;
case 5:
return isStep5Valid;
case 6:
return isStep6Valid;
default:
return false;
}
@ -464,6 +469,44 @@ const InscriptionForm = ( { students, registrationDiscounts, tuitionDiscounts, r
</div>
)}
{step === 5 && (
<div>
{groups.length > 0 ? (
<div className="space-y-4">
<h3 className="font-bold">Sélectionnez un groupe de documents</h3>
{groups.map((group) => (
<div key={group.id} className="flex items-center space-x-3">
<input
type="radio"
name="fileGroup"
value={group.id}
checked={formData.selectedFileGroup === group.id}
onChange={(e) => setFormData({
...formData,
selectedFileGroup: parseInt(e.target.value)
})}
className="form-radio h-4 w-4 text-emerald-600"
/>
<label className="text-gray-900">
{group.name}
{group.description && (
<span className="text-sm text-gray-500 ml-2">
({group.description})
</span>
)}
</label>
</div>
))}
</div>
) : (
<p className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative" role="alert">
<strong className="font-bold">Attention!</strong>
<span className="block sm:inline"> Aucun groupe de documents n'a été créé.</span>
</p>
)}
</div>
)}
{step === steps.length && (
<div>
<div className="space-y-4">
@ -553,7 +596,8 @@ const InscriptionForm = ( { students, registrationDiscounts, tuitionDiscounts, r
(step === 1 && !isStep1Valid) ||
(step === 2 && !isStep2Valid) ||
(step === 3 && !isStep3Valid) ||
(step === 4 && !isStep4Valid)
(step === 4 && !isStep4Valid) ||
(step === 5 && !isStep5Valid)
)
? "bg-gray-300 text-gray-700 cursor-not-allowed"
: "bg-emerald-500 text-white hover:bg-emerald-600"
@ -563,7 +607,8 @@ const InscriptionForm = ( { students, registrationDiscounts, tuitionDiscounts, r
(step === 1 && !isStep1Valid) ||
(step === 2 && !isStep2Valid) ||
(step === 3 && !isStep3Valid) ||
(step === 4 && !isStep4Valid)
(step === 4 && !isStep4Valid) ||
(step === 5 && !isStep5Valid)
)
}
primary

View File

@ -8,9 +8,10 @@ import Button from '@/components/Button';
import DjangoCSRFToken from '@/components/DjangoCSRFToken';
import Table from '@/components/Table';
import { fetchRegisterFormFileTemplate, createRegistrationFormFile, fetchRegisterForm, deleteRegisterFormFile } from '@/app/lib/subscriptionAction';
import { fetchRegistrationFileFromGroup } from '@/app/lib/registerFileGroupAction';
import { Download, Upload, Trash2, Eye } from 'lucide-react';
import { BASE_URL } from '@/utils/Url';
import DraggableFileUpload from '@/app/[locale]/admin/subscriptions/components/DraggableFileUpload';
import DraggableFileUpload from '@/components/DraggableFileUpload';
import Modal from '@/components/Modal';
import FileStatusLabel from '@/components/FileStatusLabel';
@ -57,6 +58,7 @@ export default function InscriptionFormShared({
// États pour la gestion des fichiers
const [uploadedFiles, setUploadedFiles] = useState([]);
const [fileTemplates, setFileTemplates] = useState([]);
const [fileGroup, setFileGroup] = useState(null);
const [fileName, setFileName] = useState("");
const [file, setFile] = useState("");
const [showUploadModal, setShowUploadModal] = useState(false);
@ -83,15 +85,21 @@ export default function InscriptionFormShared({
});
setGuardians(data?.student?.guardians || []);
setUploadedFiles(data.registration_files || []);
setFileGroup(data.fileGroup || null);
});
fetchRegisterFormFileTemplate().then((data) => {
setFileTemplates(data);
});
setIsLoading(false);
}
}, [studentId]);
useEffect(() => {
if(fileGroup){
fetchRegistrationFileFromGroup(fileGroup).then((data) => {
setFileTemplates(data);
});
}
}, [fileGroup]);
// Fonctions de gestion du formulaire et des fichiers
const updateFormField = (field, value) => {
setFormData(prev => ({...prev, [field]: value}));

View File

@ -0,0 +1,56 @@
import React, { useState, useEffect } from 'react';
export default function RegistrationFileGroupForm({ onSubmit, initialData }) {
const [name, setName] = useState('');
const [description, setDescription] = useState('');
useEffect(() => {
if (initialData) {
setName(initialData.name);
setDescription(initialData.description);
}
}, [initialData]);
const handleSubmit = (e) => {
e.preventDefault();
onSubmit({ name, description });
};
return (
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Nom du groupe
</label>
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-1 focus:ring-blue-500 focus:border-blue-500"
required
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Description
</label>
<textarea
value={description}
onChange={(e) => setDescription(e.target.value)}
rows={3}
className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-1 focus:ring-blue-500 focus:border-blue-500"
/>
</div>
<div className="flex justify-end">
<button
type="submit"
className="bg-blue-600 text-white px-4 py-2 rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2"
>
{initialData ? 'Modifier le groupe' : 'Créer le groupe'}
</button>
</div>
</form>
);
}

View File

@ -0,0 +1,21 @@
import React, { useEffect, useState } from 'react';
import { fetchRegistrationFileGroups } from '@/app/lib/registerFileGroupAction';
export default function RegistrationFileGroupList() {
const [groups, setGroups] = useState([]);
useEffect(() => {
fetchRegistrationFileGroups().then(data => setGroups(data));
}, []);
return (
<div>
<h2>Groupes de fichiers d'inscription</h2>
<ul>
{groups.map(group => (
<li key={group.id}>{group.name}</li>
))}
</ul>
</div>
);
}

View File

@ -0,0 +1,302 @@
import React, { useState, useEffect } from 'react';
import { Plus, Download, Edit, Trash2, FolderPlus } from 'lucide-react';
import Modal from '@/components/Modal';
import Table from '@/components/Table';
import FileUpload from '@/components/FileUpload';
import { formatDate } from '@/utils/Date';
import { BASE_URL } from '@/utils/Url';
import {
fetchRegisterFormFileTemplate,
createRegistrationFormFileTemplate,
editRegistrationFormFileTemplate,
deleteRegisterFormFileTemplate,
getRegisterFormFileTemplate
} from '@/app/lib/subscriptionAction';
import {
fetchRegistrationFileGroups,
createRegistrationFileGroup,
deleteRegistrationFileGroup,
editRegistrationFileGroup
} from '@/app/lib/registerFileGroupAction';
import RegistrationFileGroupForm from '@/components/RegistrationFileGroupForm';
export default function FilesManagement({ csrfToken }) {
const [fichiers, setFichiers] = useState([]);
const [groups, setGroups] = useState([]);
const [selectedGroup, setSelectedGroup] = useState(null);
const [isModalOpen, setIsModalOpen] = useState(false);
const [isEditing, setIsEditing] = useState(false);
const [fileToEdit, setFileToEdit] = useState(null);
const [isGroupModalOpen, setIsGroupModalOpen] = useState(false);
const [groupToEdit, setGroupToEdit] = useState(null);
// Fonction pour transformer les données des fichiers avec les informations complètes du groupe
const transformFileData = (file, groups) => {
if (!file.group) return file;
const groupInfo = groups.find(g => g.id === file.group);
return {
...file,
group: groupInfo || { id: file.group, name: 'Groupe inconnu' }
};
};
useEffect(() => {
Promise.all([
fetchRegisterFormFileTemplate(),
fetchRegistrationFileGroups()
]).then(([filesData, groupsData]) => {
setGroups(groupsData);
// Sélectionner automatiquement le premier groupe s'il existe
if (groupsData.length > 0) {
setSelectedGroup(groupsData[0].id.toString());
}
// Transformer chaque fichier pour inclure les informations complètes du groupe
const transformedFiles = filesData.map(file => transformFileData(file, groupsData));
setFichiers(transformedFiles);
}).catch(err => {
console.log(err.message);
});
}, []);
const handleFileDelete = (fileId) => {
deleteRegisterFormFileTemplate(fileId, csrfToken)
.then(response => {
if (response.ok) {
setFichiers(fichiers.filter(fichier => fichier.id !== fileId));
alert('Fichier supprimé avec succès.');
} else {
alert('Erreur lors de la suppression du fichier.');
}
})
.catch(error => {
console.error('Error deleting file:', error);
alert('Erreur lors de la suppression du fichier.');
});
};
const handleFileEdit = (file) => {
setIsEditing(true);
setFileToEdit(file);
setIsModalOpen(true);
};
const handleFileUpload = ({file, name, is_required, order, groupId}) => {
if (!name) {
alert('Veuillez entrer un nom de fichier.');
return;
}
const formData = new FormData();
if(file) {
formData.append('file', file);
}
formData.append('name', name);
formData.append('is_required', is_required);
formData.append('order', order);
// Modification ici : vérifier si groupId existe et n'est pas vide
if (groupId && groupId !== '') {
formData.append('group', groupId); // Notez que le nom du champ est 'group' et non 'group_id'
}
if (isEditing && fileToEdit) {
editRegistrationFormFileTemplate(fileToEdit.id, formData, csrfToken)
.then(data => {
// Transformer le fichier mis à jour avec les informations du groupe
const transformedFile = transformFileData(data, groups);
setFichiers(prevFichiers =>
prevFichiers.map(f => f.id === fileToEdit.id ? transformedFile : f)
);
setIsModalOpen(false);
setFileToEdit(null);
setIsEditing(false);
})
.catch(error => {
console.error('Error editing file:', error);
alert('Erreur lors de la modification du fichier');
});
} else {
createRegistrationFormFileTemplate(formData, csrfToken)
.then(data => {
// Transformer le nouveau fichier avec les informations du groupe
const transformedFile = transformFileData(data, groups);
setFichiers(prevFiles => [...prevFiles, transformedFile]);
setIsModalOpen(false);
})
.catch(error => {
console.error('Error uploading file:', error);
});
}
};
const handleGroupSubmit = async (groupData) => {
try {
if (groupToEdit) {
const updatedGroup = await editRegistrationFileGroup(groupToEdit.id, groupData, csrfToken);
setGroups(groups.map(group => group.id === groupToEdit.id ? updatedGroup : group));
setGroupToEdit(null);
} else {
const newGroup = await createRegistrationFileGroup(groupData, csrfToken);
setGroups([...groups, newGroup]);
}
setIsGroupModalOpen(false);
} catch (error) {
console.error('Error handling group:', error);
alert('Erreur lors de l\'opération sur le groupe');
}
};
const handleGroupEdit = (group) => {
setGroupToEdit(group);
setIsGroupModalOpen(true);
};
const handleGroupDelete = (groupId) => {
// Vérifier si des fichiers utilisent ce groupe
const filesInGroup = fichiers.filter(file => file.group && file.group.id === groupId);
if (filesInGroup.length > 0) {
alert('Impossible de supprimer ce groupe car il contient des fichiers. Veuillez d\'abord retirer tous les fichiers de ce groupe.');
return;
}
if (window.confirm('Êtes-vous sûr de vouloir supprimer ce groupe ?')) {
deleteRegistrationFileGroup(groupId, csrfToken)
.then((response) => {
if (response.status === 409) {
throw new Error('Ce groupe est lié à des inscriptions existantes.');
}
if (!response.ok) {
throw new Error('Erreur lors de la suppression du groupe.');
}
setGroups(groups.filter(group => group.id !== groupId));
alert('Groupe supprimé avec succès.');
})
.catch(error => {
console.error('Error deleting group:', error);
alert(error.message || 'Erreur lors de la suppression du groupe. Vérifiez qu\'aucune inscription n\'utilise ce groupe.');
});
}
};
// Ajouter cette fonction de filtrage
const filteredFiles = fichiers.filter(file => {
if (!selectedGroup) return true;
return file.group && file.group.id === parseInt(selectedGroup);
});
const columnsFiles = [
{ name: 'Nom du fichier', transform: (row) => row.name },
{ name: 'Groupe', transform: (row) => row.group ? row.group.name : 'Aucun' },
{ name: 'Date de création', transform: (row) => formatDate(new Date (row.date_added),"DD/MM/YYYY hh:mm:ss") },
{ name: 'Fichier Obligatoire', transform: (row) => row.is_required ? 'Oui' : 'Non' },
{ name: 'Ordre de fusion', transform: (row) => row.order },
{ name: 'Actions', transform: (row) => (
<div className="flex items-center justify-center gap-2">
{row.file && (
<a href={`${BASE_URL}${row.file}`} target='_blank' className="text-blue-500 hover:text-blue-700">
<Download size={16} />
</a>
)}
<button onClick={() => handleFileEdit(row)} className="text-blue-500 hover:text-blue-700">
<Edit size={16} />
</button>
<button onClick={() => handleFileDelete(row.id)} className="text-red-500 hover:text-red-700">
<Trash2 size={16} />
</button>
</div>
)}
];
const columnsGroups = [
{ name: 'Nom du groupe', transform: (row) => row.name },
{ name: 'Description', transform: (row) => row.description },
{ name: 'Actions', transform: (row) => (
<div className="flex items-center justify-center gap-2">
<button onClick={() => handleGroupEdit(row)} className="text-blue-500 hover:text-blue-700">
<Edit size={16} />
</button>
<button onClick={() => handleGroupDelete(row.id)} className="text-red-500 hover:text-red-700">
<Trash2 size={16} />
</button>
</div>
)}
];
return (
<div>
<Modal
isOpen={isModalOpen}
setIsOpen={setIsModalOpen}
title={isEditing ? 'Modifier un fichier' : 'Ajouter un fichier'}
ContentComponent={() => (
<FileUpload
onFileUpload={handleFileUpload}
fileToEdit={fileToEdit}
/>
)}
/>
<Modal
isOpen={isGroupModalOpen}
setIsOpen={setIsGroupModalOpen}
title={groupToEdit ? "Modifier le groupe" : "Ajouter un groupe de fichiers"}
ContentComponent={() => (
<RegistrationFileGroupForm
onSubmit={handleGroupSubmit}
initialData={groupToEdit}
/>
)}
/>
<div className="mt-8 mb-4">
<div className="flex justify-between items-center mb-4">
<h2 className="text-xl font-bold">Groupes de fichiers</h2>
<button
onClick={() => setIsGroupModalOpen(true)}
className="flex items-center bg-blue-600 text-white p-2 rounded-full shadow hover:bg-blue-900 transition duration-200"
>
<FolderPlus className="w-5 h-5" />
</button>
</div>
<Table
data={groups}
columns={columnsGroups}
itemsPerPage={5}
currentPage={1}
totalPages={Math.ceil(groups.length / 5)}
/>
</div>
{groups.length > 0 && (
<div className="mt-8">
<div className="flex justify-between items-center mb-4">
<h2 className="text-xl font-bold">Fichiers</h2>
<div className="flex items-center gap-4">
<select
className="border rounded p-2"
value={selectedGroup || ''}
onChange={(e) => setSelectedGroup(e.target.value)}
>
<option value="">Tous les groupes</option>
{groups.map(group => (
<option key={group.id} value={group.id}>{group.name}</option>
))}
</select>
<button
onClick={() => { setIsModalOpen(true); setIsEditing(false); }}
className="flex items-center bg-emerald-600 text-white p-2 rounded-full shadow hover:bg-emerald-900 transition duration-200"
>
<Plus className="w-5 h-5" />
</button>
</div>
</div>
<Table
data={filteredFiles}
columns={columnsFiles}
itemsPerPage={10}
currentPage={1}
totalPages={Math.ceil(filteredFiles.length / 10)}
/>
</div>
)}
</div>
);
}

View File

@ -15,16 +15,14 @@ export const BE_AUTH_PROFILE_URL = `${BASE_URL}/Auth/profile`
export const BE_AUTH_CSRF_URL = `${BASE_URL}/Auth/csrf`
// GESTION INSCRIPTION
export const BE_SUBSCRIPTION_STUDENT_URL = `${BASE_URL}/Subscriptions/student`
export const BE_SUBSCRIPTION_STUDENTS_URL = `${BASE_URL}/Subscriptions/students` // Récupère la liste des élèves inscrits ou en cours d'inscriptions
export const BE_SUBSCRIPTION_CHILDRENS_URL = `${BASE_URL}/Subscriptions/children` // Récupère la liste des élèves d'un profil
export const BE_SUBSCRIPTION_SEND_URL = `${BASE_URL}/Subscriptions/send`
export const BE_SUBSCRIPTION_ARCHIVE_URL = `${BASE_URL}/Subscriptions/archive`
export const BE_SUBSCRIPTION_REGISTERFORM_URL = `${BASE_URL}/Subscriptions/registerForm`
export const BE_SUBSCRIPTION_REGISTERFORMS_URL = `${BASE_URL}/Subscriptions/registerForms`
export const BE_SUBSCRIPTION_REGISTRATIONFORMFILE_TEMPLATE_URL = `${BASE_URL}/Subscriptions/registrationFileTemplates`
export const BE_SUBSCRIPTION_REGISTRATIONFORMFILE_URL = `${BASE_URL}/Subscriptions/registrationFiles`
export const BE_SUBSCRIPTION_LAST_GUARDIAN_URL = `${BASE_URL}/Subscriptions/lastGuardian`
export const BE_SUBSCRIPTION_REGISTRATIONFILE_GROUPS_URL = `${BASE_URL}/Subscriptions/registrationFileGroups`
export const BE_SUBSCRIPTION_LAST_GUARDIAN_ID_URL = `${BASE_URL}/Subscriptions/lastGuardianId`
//GESTION ENSEIGNANT
export const BE_SCHOOL_SPECIALITY_URL = `${BASE_URL}/School/speciality`