mirror of
https://git.v0id.ovh/n3wt-innov/n3wt-school.git
synced 2026-01-29 07:53:23 +00:00
Compare commits
41 Commits
WIP_Docker
...
9481a0132d
| Author | SHA1 | Date | |
|---|---|---|---|
| 9481a0132d | |||
| 482e8c1357 | |||
| 0e0141d155 | |||
| 7f002e2e6a | |||
| 0064b8d35a | |||
| ec2c1daebc | |||
| 67cea2f1c6 | |||
| 5785bfae46 | |||
| a17078709b | |||
| d58155da06 | |||
| 043d93dcc4 | |||
| 6bc24055cd | |||
| 2f6d30b85b | |||
| c161fa7e75 | |||
| 789816e986 | |||
| 6bedf715cc | |||
| 59a0d40130 | |||
| 25e2799c0f | |||
| 017c0290dd | |||
| fe2d4d4513 | |||
| f93c428259 | |||
| e61cd51ce2 | |||
| 6a0b90e98f | |||
| 8a71fa1830 | |||
| f265540da2 | |||
| 5be5f9f70d | |||
| 68a6a63c4f | |||
| af30ae33b5 | |||
| e509625811 | |||
| 3a2455f918 | |||
| e74f9c98a2 | |||
| 8f0cf16f70 | |||
| 78d96f82f9 | |||
| c117f96e52 | |||
| e4668ef1e5 | |||
| ec2630a6e4 | |||
| d65b171da8 | |||
| 4a6b7ce379 | |||
| 170f7c4fa8 | |||
| ce83e02f7b | |||
| a69498dd06 |
1
.gitignore
vendored
1
.gitignore
vendored
@ -2,3 +2,4 @@
|
|||||||
.env
|
.env
|
||||||
node_modules/
|
node_modules/
|
||||||
hardcoded-strings-report.md
|
hardcoded-strings-report.md
|
||||||
|
backend.env
|
||||||
@ -1 +1 @@
|
|||||||
node scripts/prepare-commit-msg.js "$1" "$2"
|
#node scripts/prepare-commit-msg.js "$1" "$2"
|
||||||
75
Back-End/Auth/migrations/0001_initial.py
Normal file
75
Back-End/Auth/migrations/0001_initial.py
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
# Generated by Django 5.1.3 on 2025-05-28 11:14
|
||||||
|
|
||||||
|
import django.contrib.auth.models
|
||||||
|
import django.contrib.auth.validators
|
||||||
|
import django.core.validators
|
||||||
|
import django.db.models.deletion
|
||||||
|
import django.utils.timezone
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('Establishment', '0001_initial'),
|
||||||
|
('auth', '0012_alter_user_first_name_max_length'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='ProfileRole',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('role_type', models.IntegerField(choices=[(-1, 'NON DEFINI'), (0, 'ECOLE'), (1, 'ADMIN'), (2, 'PARENT')], default=-1)),
|
||||||
|
('is_active', models.BooleanField(default=False)),
|
||||||
|
('updated_date', models.DateTimeField(auto_now=True)),
|
||||||
|
('establishment', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='profile_roles', to='Establishment.establishment')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Directeur',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('last_name', models.CharField(max_length=100)),
|
||||||
|
('first_name', models.CharField(max_length=100)),
|
||||||
|
('profile_role', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='directeur_profile', to='Auth.profilerole')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Profile',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('password', models.CharField(max_length=128, verbose_name='password')),
|
||||||
|
('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
|
||||||
|
('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
|
||||||
|
('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')),
|
||||||
|
('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')),
|
||||||
|
('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')),
|
||||||
|
('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
|
||||||
|
('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
|
||||||
|
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
|
||||||
|
('email', models.EmailField(default='', max_length=255, unique=True, validators=[django.core.validators.EmailValidator()])),
|
||||||
|
('roleIndexLoginDefault', models.IntegerField(default=0)),
|
||||||
|
('code', models.CharField(blank=True, default='', max_length=200)),
|
||||||
|
('datePeremption', models.CharField(blank=True, default='', max_length=200)),
|
||||||
|
('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')),
|
||||||
|
('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'user',
|
||||||
|
'verbose_name_plural': 'users',
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
managers=[
|
||||||
|
('objects', django.contrib.auth.models.UserManager()),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='profilerole',
|
||||||
|
name='profile',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='roles', to=settings.AUTH_USER_MODEL),
|
||||||
|
),
|
||||||
|
]
|
||||||
0
Back-End/Auth/migrations/__init__.py
Normal file
0
Back-End/Auth/migrations/__init__.py
Normal file
@ -223,14 +223,29 @@ def makeToken(user):
|
|||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
# Récupérer tous les rôles de l'utilisateur actifs
|
# Récupérer tous les rôles de l'utilisateur actifs
|
||||||
roles = ProfileRole.objects.filter(profile=user, is_active=True).values('role_type', 'establishment__id', 'establishment__name', 'establishment__evaluation_frequency', 'establishment__total_capacity', 'establishment__api_docuseal')
|
roles_qs = ProfileRole.objects.filter(profile=user, is_active=True).select_related('establishment')
|
||||||
|
roles = []
|
||||||
|
for role in roles_qs:
|
||||||
|
logo_url = ""
|
||||||
|
if role.establishment.logo:
|
||||||
|
# Construit l'URL complète pour le logo
|
||||||
|
logo_url = f"{role.establishment.logo.url}"
|
||||||
|
roles.append({
|
||||||
|
"role_type": role.role_type,
|
||||||
|
"establishment__id": role.establishment.id,
|
||||||
|
"establishment__name": role.establishment.name,
|
||||||
|
"establishment__evaluation_frequency": role.establishment.evaluation_frequency,
|
||||||
|
"establishment__total_capacity": role.establishment.total_capacity,
|
||||||
|
"establishment__api_docuseal": role.establishment.api_docuseal,
|
||||||
|
"establishment__logo": logo_url,
|
||||||
|
})
|
||||||
|
|
||||||
# Générer le JWT avec la bonne syntaxe datetime
|
# Générer le JWT avec la bonne syntaxe datetime
|
||||||
access_payload = {
|
access_payload = {
|
||||||
'user_id': user.id,
|
'user_id': user.id,
|
||||||
'email': user.email,
|
'email': user.email,
|
||||||
'roleIndexLoginDefault':user.roleIndexLoginDefault,
|
'roleIndexLoginDefault': user.roleIndexLoginDefault,
|
||||||
'roles': list(roles),
|
'roles': roles,
|
||||||
'type': 'access',
|
'type': 'access',
|
||||||
'exp': datetime.utcnow() + settings.SIMPLE_JWT['ACCESS_TOKEN_LIFETIME'],
|
'exp': datetime.utcnow() + settings.SIMPLE_JWT['ACCESS_TOKEN_LIFETIME'],
|
||||||
'iat': datetime.utcnow(),
|
'iat': datetime.utcnow(),
|
||||||
@ -361,7 +376,7 @@ class SubscribeView(APIView):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
def post(self, request):
|
def post(self, request):
|
||||||
retourErreur = error.returnMessage[error.BAD_URL]
|
retourErreur = ''
|
||||||
retour = ''
|
retour = ''
|
||||||
newProfilConnection = JSONParser().parse(request)
|
newProfilConnection = JSONParser().parse(request)
|
||||||
establishment_id = newProfilConnection['establishment_id']
|
establishment_id = newProfilConnection['establishment_id']
|
||||||
@ -437,7 +452,7 @@ class NewPasswordView(APIView):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
def post(self, request):
|
def post(self, request):
|
||||||
retourErreur = error.returnMessage[error.BAD_URL]
|
retourErreur = ''
|
||||||
retour = ''
|
retour = ''
|
||||||
newProfilConnection = JSONParser().parse(request)
|
newProfilConnection = JSONParser().parse(request)
|
||||||
|
|
||||||
@ -487,7 +502,7 @@ class ResetPasswordView(APIView):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
def post(self, request, code):
|
def post(self, request, code):
|
||||||
retourErreur = error.returnMessage[error.BAD_URL]
|
retourErreur = ''
|
||||||
retour = ''
|
retour = ''
|
||||||
newProfilConnection = JSONParser().parse(request)
|
newProfilConnection = JSONParser().parse(request)
|
||||||
|
|
||||||
@ -498,7 +513,7 @@ class ResetPasswordView(APIView):
|
|||||||
if profil:
|
if profil:
|
||||||
|
|
||||||
if datetime.strptime(util.convertToStr(util._now(), '%d-%m-%Y %H:%M'), '%d-%m-%Y %H:%M') > datetime.strptime(profil.datePeremption, '%d-%m-%Y %H:%M'):
|
if datetime.strptime(util.convertToStr(util._now(), '%d-%m-%Y %H:%M'), '%d-%m-%Y %H:%M') > datetime.strptime(profil.datePeremption, '%d-%m-%Y %H:%M'):
|
||||||
retourErreur = error.returnMessage[error.EXPIRED_URL] % (_uuid)
|
retourErreur = error.returnMessage[error.EXPIRED_URL]
|
||||||
elif validationOk:
|
elif validationOk:
|
||||||
retour = error.returnMessage[error.PASSWORD_CHANGED]
|
retour = error.returnMessage[error.PASSWORD_CHANGED]
|
||||||
|
|
||||||
|
|||||||
63
Back-End/Common/migrations/0001_initial.py
Normal file
63
Back-End/Common/migrations/0001_initial.py
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
# Generated by Django 5.1.3 on 2025-05-28 11:14
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Cycle',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('number', models.IntegerField(unique=True)),
|
||||||
|
('label', models.CharField(max_length=50)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Domain',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('name', models.CharField(max_length=255)),
|
||||||
|
('cycle', models.IntegerField(choices=[(1, 'Cycle 1'), (2, 'Cycle 2'), (3, 'Cycle 3'), (4, 'Cycle 4')])),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='PaymentModeType',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('code', models.CharField(max_length=50, unique=True)),
|
||||||
|
('label', models.CharField(max_length=255)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='PaymentPlanType',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('code', models.CharField(max_length=50, unique=True)),
|
||||||
|
('label', models.CharField(max_length=255)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Category',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('name', models.CharField(max_length=255)),
|
||||||
|
('domain', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='categories', to='Common.domain')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Level',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('name', models.CharField(max_length=50)),
|
||||||
|
('cycle', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='levels', to='Common.cycle')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
||||||
0
Back-End/Common/migrations/__init__.py
Normal file
0
Back-End/Common/migrations/__init__.py
Normal file
29
Back-End/Establishment/migrations/0001_initial.py
Normal file
29
Back-End/Establishment/migrations/0001_initial.py
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
# Generated by Django 5.1.3 on 2025-05-28 11:14
|
||||||
|
|
||||||
|
import django.contrib.postgres.fields
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Establishment',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('name', models.CharField(max_length=255)),
|
||||||
|
('address', models.CharField(max_length=255)),
|
||||||
|
('total_capacity', models.IntegerField()),
|
||||||
|
('establishment_type', django.contrib.postgres.fields.ArrayField(base_field=models.IntegerField(choices=[(1, 'Maternelle'), (2, 'Primaire'), (3, 'Secondaire')]), size=None)),
|
||||||
|
('evaluation_frequency', models.IntegerField(choices=[(1, 'Trimestre'), (2, 'Semestre'), (3, 'Année')], default=1)),
|
||||||
|
('licence_code', models.CharField(blank=True, max_length=100)),
|
||||||
|
('is_active', models.BooleanField(default=True)),
|
||||||
|
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 5.1.3 on 2025-05-30 07:39
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('Establishment', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='establishment',
|
||||||
|
name='api_docuseal',
|
||||||
|
field=models.CharField(blank=True, max_length=255, null=True),
|
||||||
|
),
|
||||||
|
]
|
||||||
19
Back-End/Establishment/migrations/0003_establishment_logo.py
Normal file
19
Back-End/Establishment/migrations/0003_establishment_logo.py
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
# Generated by Django 5.1.3 on 2025-05-31 09:56
|
||||||
|
|
||||||
|
import Establishment.models
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('Establishment', '0002_establishment_api_docuseal'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='establishment',
|
||||||
|
name='logo',
|
||||||
|
field=models.FileField(blank=True, null=True, upload_to=Establishment.models.registration_logo_upload_to),
|
||||||
|
),
|
||||||
|
]
|
||||||
0
Back-End/Establishment/migrations/__init__.py
Normal file
0
Back-End/Establishment/migrations/__init__.py
Normal file
@ -2,6 +2,12 @@ from django.db import models
|
|||||||
from django.contrib.postgres.fields import ArrayField
|
from django.contrib.postgres.fields import ArrayField
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
def registration_logo_upload_to(instance, filename):
|
||||||
|
ext = os.path.splitext(filename)[1]
|
||||||
|
return f"logos/school_{instance.pk}/logo{ext}"
|
||||||
|
|
||||||
class StructureType(models.IntegerChoices):
|
class StructureType(models.IntegerChoices):
|
||||||
MATERNELLE = 1, _('Maternelle')
|
MATERNELLE = 1, _('Maternelle')
|
||||||
PRIMAIRE = 2, _('Primaire')
|
PRIMAIRE = 2, _('Primaire')
|
||||||
@ -22,6 +28,11 @@ class Establishment(models.Model):
|
|||||||
is_active = models.BooleanField(default=True)
|
is_active = models.BooleanField(default=True)
|
||||||
created_at = models.DateTimeField(auto_now_add=True)
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
api_docuseal = models.CharField(max_length=255, blank=True, null=True)
|
api_docuseal = models.CharField(max_length=255, blank=True, null=True)
|
||||||
|
logo = models.FileField(
|
||||||
|
upload_to=registration_logo_upload_to,
|
||||||
|
null=True,
|
||||||
|
blank=True
|
||||||
|
)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
@ -1,16 +1,19 @@
|
|||||||
from django.http.response import JsonResponse
|
from django.http.response import JsonResponse
|
||||||
from django.views.decorators.csrf import ensure_csrf_cookie, csrf_protect
|
from django.views.decorators.csrf import ensure_csrf_cookie, csrf_protect
|
||||||
from django.utils.decorators import method_decorator
|
from django.utils.decorators import method_decorator
|
||||||
from rest_framework.parsers import JSONParser
|
from rest_framework.parsers import JSONParser, MultiPartParser, FormParser
|
||||||
from rest_framework.views import APIView
|
from rest_framework.views import APIView
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
from .models import Establishment
|
from .models import Establishment
|
||||||
from .serializers import EstablishmentSerializer
|
from .serializers import EstablishmentSerializer
|
||||||
from N3wtSchool.bdd import delete_object, getAllObjects
|
from N3wtSchool.bdd import delete_object, getAllObjects, getObject
|
||||||
from School.models import EstablishmentCompetency, Competency
|
from School.models import EstablishmentCompetency, Competency
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from Auth.models import Profile, ProfileRole, Directeur
|
from Auth.models import Profile, ProfileRole, Directeur
|
||||||
from Settings.models import SMTPSettings
|
from Settings.models import SMTPSettings
|
||||||
|
import N3wtSchool.mailManager as mailer
|
||||||
|
import os
|
||||||
|
from N3wtSchool import settings
|
||||||
|
|
||||||
@method_decorator(csrf_protect, name='dispatch')
|
@method_decorator(csrf_protect, name='dispatch')
|
||||||
@method_decorator(ensure_csrf_cookie, name='dispatch')
|
@method_decorator(ensure_csrf_cookie, name='dispatch')
|
||||||
@ -41,6 +44,8 @@ class EstablishmentListCreateView(APIView):
|
|||||||
@method_decorator(csrf_protect, name='dispatch')
|
@method_decorator(csrf_protect, name='dispatch')
|
||||||
@method_decorator(ensure_csrf_cookie, name='dispatch')
|
@method_decorator(ensure_csrf_cookie, name='dispatch')
|
||||||
class EstablishmentDetailView(APIView):
|
class EstablishmentDetailView(APIView):
|
||||||
|
parser_classes = [MultiPartParser, FormParser]
|
||||||
|
|
||||||
def get(self, request, id=None):
|
def get(self, request, id=None):
|
||||||
try:
|
try:
|
||||||
establishment = Establishment.objects.get(id=id)
|
establishment = Establishment.objects.get(id=id)
|
||||||
@ -50,15 +55,20 @@ class EstablishmentDetailView(APIView):
|
|||||||
return JsonResponse({'error': 'No object found'}, status=status.HTTP_404_NOT_FOUND)
|
return JsonResponse({'error': 'No object found'}, status=status.HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
def put(self, request, id):
|
def put(self, request, id):
|
||||||
establishment_data = JSONParser().parse(request)
|
"""
|
||||||
|
Met à jour un établissement existant.
|
||||||
|
Accepte les données en multipart/form-data pour permettre l'upload de fichiers (ex : logo).
|
||||||
|
"""
|
||||||
try:
|
try:
|
||||||
establishment = Establishment.objects.get(id=id)
|
establishment = Establishment.objects.get(id=id)
|
||||||
except Establishment.DoesNotExist:
|
except Establishment.DoesNotExist:
|
||||||
return JsonResponse({'error': 'No object found'}, status=status.HTTP_404_NOT_FOUND)
|
return JsonResponse({'error': 'No object found'}, status=status.HTTP_404_NOT_FOUND)
|
||||||
establishment_serializer = EstablishmentSerializer(establishment, data=establishment_data, partial=True)
|
|
||||||
|
# Utilise request.data pour supporter multipart/form-data (fichiers et champs classiques)
|
||||||
|
establishment_serializer = EstablishmentSerializer(establishment, data=request.data, partial=True)
|
||||||
if establishment_serializer.is_valid():
|
if establishment_serializer.is_valid():
|
||||||
establishment_serializer.save()
|
establishment_serializer.save()
|
||||||
return JsonResponse(establishment_serializer.data, safe=False)
|
return JsonResponse(establishment_serializer.data, safe=False, status=status.HTTP_200_OK)
|
||||||
return JsonResponse(establishment_serializer.errors, safe=False, status=status.HTTP_400_BAD_REQUEST)
|
return JsonResponse(establishment_serializer.errors, safe=False, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
def delete(self, request, id):
|
def delete(self, request, id):
|
||||||
@ -66,6 +76,7 @@ class EstablishmentDetailView(APIView):
|
|||||||
|
|
||||||
def create_establishment_with_directeur(establishment_data):
|
def create_establishment_with_directeur(establishment_data):
|
||||||
# Extraction des sous-objets
|
# Extraction des sous-objets
|
||||||
|
# school_name = establishment_data.get("name")
|
||||||
directeur_data = establishment_data.pop("directeur", None)
|
directeur_data = establishment_data.pop("directeur", None)
|
||||||
smtp_settings_data = establishment_data.pop("smtp_settings", {})
|
smtp_settings_data = establishment_data.pop("smtp_settings", {})
|
||||||
|
|
||||||
@ -90,6 +101,8 @@ def create_establishment_with_directeur(establishment_data):
|
|||||||
# Création de l'établissement
|
# Création de l'établissement
|
||||||
establishment_serializer = EstablishmentSerializer(data=establishment_data)
|
establishment_serializer = EstablishmentSerializer(data=establishment_data)
|
||||||
establishment_serializer.is_valid(raise_exception=True)
|
establishment_serializer.is_valid(raise_exception=True)
|
||||||
|
# base_dir = os.path.join(settings.MEDIA_ROOT, f"logo/school_{school_name}")
|
||||||
|
# os.makedirs(base_dir, exist_ok=True)
|
||||||
establishment = establishment_serializer.save()
|
establishment = establishment_serializer.save()
|
||||||
|
|
||||||
# Création ou récupération du ProfileRole ADMIN pour ce profil et cet établissement
|
# Création ou récupération du ProfileRole ADMIN pour ce profil et cet établissement
|
||||||
@ -97,7 +110,7 @@ def create_establishment_with_directeur(establishment_data):
|
|||||||
profile=profile,
|
profile=profile,
|
||||||
establishment=establishment,
|
establishment=establishment,
|
||||||
role_type=ProfileRole.RoleType.PROFIL_ADMIN,
|
role_type=ProfileRole.RoleType.PROFIL_ADMIN,
|
||||||
defaults={"is_active": True}
|
defaults={"is_active": False}
|
||||||
)
|
)
|
||||||
|
|
||||||
# Création ou mise à jour du Directeur lié à ce ProfileRole
|
# Création ou mise à jour du Directeur lié à ce ProfileRole
|
||||||
@ -114,4 +127,6 @@ def create_establishment_with_directeur(establishment_data):
|
|||||||
smtp_settings_data["establishment"] = establishment
|
smtp_settings_data["establishment"] = establishment
|
||||||
SMTPSettings.objects.create(**smtp_settings_data)
|
SMTPSettings.objects.create(**smtp_settings_data)
|
||||||
|
|
||||||
|
# Envoi du mail
|
||||||
|
mailer.sendRegistrationDirector(directeur_email, establishment.pk)
|
||||||
return establishment, establishment_serializer.data
|
return establishment, establishment_serializer.data
|
||||||
30
Back-End/GestionMessagerie/migrations/0001_initial.py
Normal file
30
Back-End/GestionMessagerie/migrations/0001_initial.py
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
# Generated by Django 5.1.3 on 2025-05-28 11:14
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Messagerie',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(primary_key=True, serialize=False)),
|
||||||
|
('objet', models.CharField(blank=True, default='', max_length=200)),
|
||||||
|
('corpus', models.CharField(blank=True, default='', max_length=200)),
|
||||||
|
('date_envoi', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('is_read', models.BooleanField(default=False)),
|
||||||
|
('conversation_id', models.CharField(blank=True, default='', max_length=100)),
|
||||||
|
('destinataire', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='messages_recus', to=settings.AUTH_USER_MODEL)),
|
||||||
|
('emetteur', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='messages_envoyes', to=settings.AUTH_USER_MODEL)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -0,0 +1,87 @@
|
|||||||
|
# Generated by Django 5.1.3 on 2025-05-30 07:40
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
import django.utils.timezone
|
||||||
|
import uuid
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('GestionMessagerie', '0001_initial'),
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Conversation',
|
||||||
|
fields=[
|
||||||
|
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||||
|
('name', models.CharField(blank=True, max_length=255, null=True)),
|
||||||
|
('conversation_type', models.CharField(choices=[('private', 'Privée'), ('group', 'Groupe')], default='private', max_length=10)),
|
||||||
|
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('updated_at', models.DateTimeField(auto_now=True)),
|
||||||
|
('last_activity', models.DateTimeField(default=django.utils.timezone.now)),
|
||||||
|
('is_active', models.BooleanField(default=True)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Message',
|
||||||
|
fields=[
|
||||||
|
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||||
|
('content', models.TextField()),
|
||||||
|
('message_type', models.CharField(choices=[('text', 'Texte'), ('file', 'Fichier'), ('image', 'Image'), ('system', 'Système')], default='text', max_length=10)),
|
||||||
|
('file_url', models.URLField(blank=True, null=True)),
|
||||||
|
('file_name', models.CharField(blank=True, max_length=255, null=True)),
|
||||||
|
('file_size', models.BigIntegerField(blank=True, null=True)),
|
||||||
|
('file_type', models.CharField(blank=True, max_length=100, null=True)),
|
||||||
|
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('updated_at', models.DateTimeField(auto_now=True)),
|
||||||
|
('is_edited', models.BooleanField(default=False)),
|
||||||
|
('is_deleted', models.BooleanField(default=False)),
|
||||||
|
('conversation', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='messages', to='GestionMessagerie.conversation')),
|
||||||
|
('sender', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='sent_messages', to=settings.AUTH_USER_MODEL)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'ordering': ['created_at'],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='UserPresence',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('status', models.CharField(choices=[('online', 'En ligne'), ('away', 'Absent'), ('busy', 'Occupé'), ('offline', 'Hors ligne')], default='offline', max_length=10)),
|
||||||
|
('last_seen', models.DateTimeField(default=django.utils.timezone.now)),
|
||||||
|
('is_typing_in', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='typing_users', to='GestionMessagerie.conversation')),
|
||||||
|
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='presence', to=settings.AUTH_USER_MODEL)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='ConversationParticipant',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('joined_at', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('last_read_at', models.DateTimeField(default=django.utils.timezone.now)),
|
||||||
|
('is_active', models.BooleanField(default=True)),
|
||||||
|
('conversation', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='participants', to='GestionMessagerie.conversation')),
|
||||||
|
('participant', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='conversation_participants', to=settings.AUTH_USER_MODEL)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'unique_together': {('conversation', 'participant')},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='MessageRead',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('read_at', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('message', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='read_by', to='GestionMessagerie.message')),
|
||||||
|
('participant', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='read_messages', to=settings.AUTH_USER_MODEL)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'unique_together': {('message', 'participant')},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
0
Back-End/GestionMessagerie/migrations/__init__.py
Normal file
0
Back-End/GestionMessagerie/migrations/__init__.py
Normal file
28
Back-End/GestionNotification/migrations/0001_initial.py
Normal file
28
Back-End/GestionNotification/migrations/0001_initial.py
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
# Generated by Django 5.1.3 on 2025-05-28 11:14
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Notification',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('message', models.CharField(max_length=255)),
|
||||||
|
('is_read', models.BooleanField(default=False)),
|
||||||
|
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('typeNotification', models.IntegerField(choices=[(0, 'Aucune notification'), (1, 'Un message a été reçu'), (2, "Le dossier d'inscription a été mis à jour")], default=0)),
|
||||||
|
('user', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
||||||
0
Back-End/GestionNotification/migrations/__init__.py
Normal file
0
Back-End/GestionNotification/migrations/__init__.py
Normal file
@ -1,4 +1,5 @@
|
|||||||
from typing import Final
|
from typing import Final
|
||||||
|
from N3wtSchool import settings
|
||||||
|
|
||||||
WRONG_ID: Final = 1
|
WRONG_ID: Final = 1
|
||||||
INCOMPLETE: Final = 2
|
INCOMPLETE: Final = 2
|
||||||
@ -8,11 +9,14 @@ DIFFERENT_PASWWORD: Final = 5
|
|||||||
PROFIL_NOT_EXISTS: Final = 6
|
PROFIL_NOT_EXISTS: Final = 6
|
||||||
MESSAGE_REINIT_PASSWORD: Final = 7
|
MESSAGE_REINIT_PASSWORD: Final = 7
|
||||||
EXPIRED_URL: Final = 8
|
EXPIRED_URL: Final = 8
|
||||||
PASSWORD_CHANGED: Final = 8
|
PASSWORD_CHANGED: Final = 9
|
||||||
WRONG_MAIL_FORMAT: Final = 9
|
WRONG_MAIL_FORMAT: Final = 10
|
||||||
PROFIL_INACTIVE: Final = 10
|
PROFIL_INACTIVE: Final = 11
|
||||||
MESSAGE_ACTIVATION_PROFILE: Final = 11
|
MESSAGE_ACTIVATION_PROFILE: Final = 12
|
||||||
PROFIL_ACTIVE: Final = 12
|
PROFIL_ACTIVE: Final = 13
|
||||||
|
|
||||||
|
def get_expired_url_message():
|
||||||
|
return f"L'URL a expiré. Effectuer à nouveau la demande de réinitialisation de mot de passe : {settings.BASE_URL}/password/new"
|
||||||
|
|
||||||
returnMessage = {
|
returnMessage = {
|
||||||
WRONG_ID:'Identifiants invalides',
|
WRONG_ID:'Identifiants invalides',
|
||||||
@ -22,7 +26,7 @@ returnMessage = {
|
|||||||
DIFFERENT_PASWWORD: 'Les mots de passe ne correspondent pas',
|
DIFFERENT_PASWWORD: 'Les mots de passe ne correspondent pas',
|
||||||
PROFIL_NOT_EXISTS: 'Aucun profil associé à cet utilisateur',
|
PROFIL_NOT_EXISTS: 'Aucun profil associé à cet utilisateur',
|
||||||
MESSAGE_REINIT_PASSWORD: 'Un mail a été envoyé à l\'adresse \'%s\'',
|
MESSAGE_REINIT_PASSWORD: 'Un mail a été envoyé à l\'adresse \'%s\'',
|
||||||
EXPIRED_URL:'L\'URL a expiré. Effectuer à nouveau la demande de réinitialisation de mot de passe : http://localhost:3000/password/reset?uuid=%s',
|
EXPIRED_URL: get_expired_url_message(),
|
||||||
PASSWORD_CHANGED: 'Le mot de passe a été réinitialisé',
|
PASSWORD_CHANGED: 'Le mot de passe a été réinitialisé',
|
||||||
WRONG_MAIL_FORMAT: 'L\'adresse mail est mal formatée',
|
WRONG_MAIL_FORMAT: 'L\'adresse mail est mal formatée',
|
||||||
PROFIL_INACTIVE: 'Le profil n\'est pas actif',
|
PROFIL_INACTIVE: 'Le profil n\'est pas actif',
|
||||||
|
|||||||
@ -101,6 +101,27 @@ def envoieReinitMotDePasse(recipients, code):
|
|||||||
|
|
||||||
return errorMessage
|
return errorMessage
|
||||||
|
|
||||||
|
def sendRegistrationDirector(recipients, establishment_id):
|
||||||
|
errorMessage = ''
|
||||||
|
try:
|
||||||
|
# Préparation du contexte pour le template
|
||||||
|
EMAIL_INSCRIPTION_SUBJECT = '[N3WT-SCHOOL] Bienvenue dans la communauté !'
|
||||||
|
context = {
|
||||||
|
'BASE_URL': settings.BASE_URL,
|
||||||
|
'URL_DJANGO': settings.URL_DJANGO,
|
||||||
|
'email': recipients,
|
||||||
|
'establishment': establishment_id
|
||||||
|
}
|
||||||
|
subject = EMAIL_INSCRIPTION_SUBJECT
|
||||||
|
html_message = render_to_string('emails/subscribeDirector.html', context)
|
||||||
|
sendMail(subject=subject, message=html_message, recipients=recipients)
|
||||||
|
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
errorMessage = str(e)
|
||||||
|
|
||||||
|
return errorMessage
|
||||||
|
|
||||||
|
|
||||||
def sendRegisterForm(recipients, establishment_id):
|
def sendRegisterForm(recipients, establishment_id):
|
||||||
errorMessage = ''
|
errorMessage = ''
|
||||||
|
|||||||
@ -1,8 +1,11 @@
|
|||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
|
||||||
class ContentSecurityPolicyMiddleware:
|
class ContentSecurityPolicyMiddleware:
|
||||||
def __init__(self, get_response):
|
def __init__(self, get_response):
|
||||||
self.get_response = get_response
|
self.get_response = get_response
|
||||||
|
|
||||||
def __call__(self, request):
|
def __call__(self, request):
|
||||||
response = self.get_response(request)
|
response = self.get_response(request)
|
||||||
response['Content-Security-Policy'] = "frame-ancestors 'self' http://localhost:3000"
|
response['Content-Security-Policy'] = f"frame-ancestors 'self' {settings.BASE_URL}"
|
||||||
return response
|
return response
|
||||||
|
|||||||
@ -24,18 +24,16 @@ BASE_DIR = Path(__file__).resolve().parent.parent
|
|||||||
MEDIA_URL = '/data/'
|
MEDIA_URL = '/data/'
|
||||||
MEDIA_ROOT = os.path.join(BASE_DIR, 'data')
|
MEDIA_ROOT = os.path.join(BASE_DIR, 'data')
|
||||||
|
|
||||||
BASE_URL = os.getenv('CORS_ALLOWED_ORIGINS', 'http://localhost:3000')
|
BASE_URL = os.getenv('BASE_URL', 'http://localhost:3000')
|
||||||
|
|
||||||
|
|
||||||
LOGIN_REDIRECT_URL = '/Subscriptions/registerForms'
|
LOGIN_REDIRECT_URL = '/Subscriptions/registerForms'
|
||||||
|
|
||||||
# Quick-start development settings - unsuitable for production
|
# Quick-start development settings - unsuitable for production
|
||||||
# See https://docs.djangoproject.com/en/5.0/howto/deployment/checklist/
|
# See https://docs.djangoproject.com/en/5.0/howto/deployment/checklist/
|
||||||
|
|
||||||
# SECURITY WARNING: keep the secret key used in production secret!
|
|
||||||
SECRET_KEY = 'django-insecure-afjm6kvigncxzx6jjjf(qb0n(*qvi#je79r=gqflcn007d_ve9'
|
|
||||||
|
|
||||||
# SECURITY WARNING: don't run with debug turned on in production!
|
# SECURITY WARNING: don't run with debug turned on in production!
|
||||||
DEBUG = True
|
DEBUG = os.getenv('DJANGO_DEBUG', True)
|
||||||
|
|
||||||
ALLOWED_HOSTS = ['*']
|
ALLOWED_HOSTS = ['*']
|
||||||
|
|
||||||
@ -211,8 +209,6 @@ USE_I18N = True
|
|||||||
# Static files (CSS, JavaScript, Images)
|
# Static files (CSS, JavaScript, Images)
|
||||||
# https://docs.djangoproject.com/en/5.0/howto/static-files/
|
# https://docs.djangoproject.com/en/5.0/howto/static-files/
|
||||||
|
|
||||||
DEBUG = True
|
|
||||||
|
|
||||||
STATIC_URL = 'static/'
|
STATIC_URL = 'static/'
|
||||||
|
|
||||||
STATICFILES_DIRS = [
|
STATICFILES_DIRS = [
|
||||||
@ -232,33 +228,18 @@ DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
|||||||
########################################################################
|
########################################################################
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
DJANGO_SUPERUSER_PASSWORD='admin'
|
|
||||||
DJANGO_SUPERUSER_USERNAME='admin'
|
|
||||||
DJANGO_SUPERUSER_EMAIL='admin@n3wtschool.com'
|
|
||||||
# Configuration de l'email de l'application
|
# Configuration de l'email de l'application
|
||||||
smtp_config_file = 'N3wtSchool/Configuration/application.json'
|
EMAIL_HOST = os.getenv('EMAIL_HOST', 'smtp.example.com')
|
||||||
|
EMAIL_PORT = os.getenv('EMAIL_PORT', 587)
|
||||||
if os.path.exists(smtp_config_file):
|
EMAIL_HOST_USER = os.getenv('EMAIL_HOST_USER', '')
|
||||||
try:
|
EMAIL_HOST_PASSWORD = os.getenv('EMAIL_HOST_PASSWORD', '')
|
||||||
with open(smtp_config_file, 'r') as f:
|
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
|
||||||
smtpSettings = json.load(f)
|
EMAIL_USE_TLS = os.getenv('EMAIL_USE_TLS', 'true').lower() == 'true'
|
||||||
EMAIL_HOST = smtpSettings.get('hostSMTP', '')
|
EMAIL_USE_SSL = os.getenv('EMAIL_USE_SSL', 'false').lower() == 'true'
|
||||||
EMAIL_PORT = smtpSettings.get('portSMTP', 587)
|
|
||||||
EMAIL_HOST_USER = smtpSettings.get('username', '')
|
|
||||||
EMAIL_HOST_PASSWORD = smtpSettings.get('password', '')
|
|
||||||
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
|
|
||||||
EMAIL_USE_TLS = smtpSettings.get('useTLS', True)
|
|
||||||
EMAIL_USE_SSL = smtpSettings.get('useSSL', False)
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Erreur lors de la lecture du fichier de configuration SMTP : {e}")
|
|
||||||
else:
|
|
||||||
logger.error(f"Fichier de configuration SMTP introuvable : {smtp_config_file}")
|
|
||||||
|
|
||||||
DOCUMENT_DIR = 'documents'
|
DOCUMENT_DIR = 'documents'
|
||||||
|
|
||||||
# Configuration CORS temporaire pour debug
|
# Configuration CORS temporaire pour debug
|
||||||
CORS_ALLOW_ALL_HEADERS = True
|
|
||||||
CORS_ALLOW_CREDENTIALS = True
|
CORS_ALLOW_CREDENTIALS = True
|
||||||
|
|
||||||
# Configuration CORS spécifique pour la production
|
# Configuration CORS spécifique pour la production
|
||||||
@ -291,20 +272,26 @@ CORS_ALLOWED_METHODS = [
|
|||||||
CSRF_TRUSTED_ORIGINS = os.getenv('CSRF_TRUSTED_ORIGINS', 'http://localhost:3000,http://localhost:8080').split(',')
|
CSRF_TRUSTED_ORIGINS = os.getenv('CSRF_TRUSTED_ORIGINS', 'http://localhost:3000,http://localhost:8080').split(',')
|
||||||
|
|
||||||
CSRF_COOKIE_HTTPONLY = False
|
CSRF_COOKIE_HTTPONLY = False
|
||||||
CSRF_COOKIE_SECURE = False
|
CSRF_COOKIE_SECURE = os.getenv('CSRF_COOKIE_SECURE', 'false').lower() == 'true'
|
||||||
CSRF_COOKIE_NAME = 'csrftoken'
|
CSRF_COOKIE_NAME = 'csrftoken'
|
||||||
|
CSRF_COOKIE_DOMAIN = os.getenv('CSRF_COOKIE_DOMAIN', '')
|
||||||
|
|
||||||
USE_TZ = True
|
USE_TZ = True
|
||||||
TZ_APPLI = 'Europe/Paris'
|
TZ_APPLI = 'Europe/Paris'
|
||||||
|
|
||||||
|
DB_NAME = os.getenv('DB_NAME', 'school')
|
||||||
|
DB_USER = os.getenv('DB_USER', 'postgres')
|
||||||
|
DB_PASSWORD = os.getenv('DB_PASSWORD', 'postgres')
|
||||||
|
DB_HOST = os.getenv('DB_HOST', 'database')
|
||||||
|
DB_PORT = os.getenv('DB_PORT', '5432')
|
||||||
DATABASES = {
|
DATABASES = {
|
||||||
'default': {
|
'default': {
|
||||||
'ENGINE': 'django.db.backends.postgresql',
|
'ENGINE': 'django.db.backends.postgresql',
|
||||||
"NAME": "school",
|
"NAME": DB_NAME,
|
||||||
"USER": "postgres",
|
"USER": DB_USER,
|
||||||
"PASSWORD": "postgres",
|
"PASSWORD": DB_PASSWORD,
|
||||||
"HOST": "database",
|
"HOST": DB_HOST,
|
||||||
"PORT": "5432",
|
"PORT": DB_PORT,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -339,14 +326,14 @@ CELERY_RESULT_SERIALIZER = 'json'
|
|||||||
CELERY_TIMEZONE = 'Europe/Paris'
|
CELERY_TIMEZONE = 'Europe/Paris'
|
||||||
CELERY_BROKER_CONNECTION_RETRY_ON_STARTUP = True
|
CELERY_BROKER_CONNECTION_RETRY_ON_STARTUP = True
|
||||||
|
|
||||||
URL_DJANGO = 'http://localhost:8080/'
|
URL_DJANGO = os.getenv('URL_DJANGO', 'http://localhost:8080/')
|
||||||
|
|
||||||
REDIS_HOST = 'redis'
|
REDIS_HOST = 'redis'
|
||||||
REDIS_PORT = 6379
|
REDIS_PORT = 6379
|
||||||
REDIS_DB = 0
|
REDIS_DB = 0
|
||||||
REDIS_PASSWORD = None
|
REDIS_PASSWORD = None
|
||||||
|
|
||||||
SECRET_KEY = 'QWQ8bYlCz1NpQ9G0vR5kxMnvWszfH2y3'
|
SECRET_KEY = os.getenv('SECRET_KEY', 'QWQ8bYlCz1NpQ9G0vR5kxMnvWszfH2y3')
|
||||||
SIMPLE_JWT = {
|
SIMPLE_JWT = {
|
||||||
'ACCESS_TOKEN_LIFETIME': timedelta(minutes=15),
|
'ACCESS_TOKEN_LIFETIME': timedelta(minutes=15),
|
||||||
'REFRESH_TOKEN_LIFETIME': timedelta(days=1),
|
'REFRESH_TOKEN_LIFETIME': timedelta(days=1),
|
||||||
|
|||||||
44
Back-End/Planning/migrations/0001_initial.py
Normal file
44
Back-End/Planning/migrations/0001_initial.py
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
# Generated by Django 5.1.3 on 2025-05-28 11:14
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('Establishment', '0001_initial'),
|
||||||
|
('School', '__first__'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Planning',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('name', models.CharField(max_length=255)),
|
||||||
|
('description', models.TextField(blank=True, default='', null=True)),
|
||||||
|
('color', models.CharField(default='#000000', max_length=255)),
|
||||||
|
('establishment', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='Establishment.establishment')),
|
||||||
|
('school_class', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='planning', to='School.schoolclass')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Events',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('title', models.CharField(max_length=255)),
|
||||||
|
('description', models.TextField(blank=True, default='', null=True)),
|
||||||
|
('start', models.DateTimeField()),
|
||||||
|
('end', models.DateTimeField()),
|
||||||
|
('recursionType', models.IntegerField(choices=[(0, 'Aucune'), (1, 'Quotidienne'), (2, 'Hebdomadaire'), (3, 'Mensuel'), (4, 'Personnalisé')], default=0)),
|
||||||
|
('recursionEnd', models.DateTimeField(blank=True, default=None, null=True)),
|
||||||
|
('color', models.CharField(max_length=255)),
|
||||||
|
('location', models.CharField(blank=True, default='', max_length=255, null=True)),
|
||||||
|
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('planning', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='Planning.planning')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
||||||
0
Back-End/Planning/migrations/__init__.py
Normal file
0
Back-End/Planning/migrations/__init__.py
Normal file
@ -147,7 +147,7 @@ class EventsWithIdView(APIView):
|
|||||||
return JsonResponse({'error': 'Event not found'}, status=404)
|
return JsonResponse({'error': 'Event not found'}, status=404)
|
||||||
|
|
||||||
event.delete()
|
event.delete()
|
||||||
return JsonResponse({'message': 'Event deleted'}, status=204)
|
return JsonResponse({'message': 'Event deleted'}, status=200)
|
||||||
|
|
||||||
class UpcomingEventsView(APIView):
|
class UpcomingEventsView(APIView):
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
|
|||||||
145
Back-End/School/migrations/0001_initial.py
Normal file
145
Back-End/School/migrations/0001_initial.py
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
# Generated by Django 5.1.3 on 2025-05-28 11:14
|
||||||
|
|
||||||
|
import django.contrib.postgres.fields
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('Auth', '0001_initial'),
|
||||||
|
('Common', '0001_initial'),
|
||||||
|
('Establishment', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Competency',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('name', models.TextField()),
|
||||||
|
('end_of_cycle', models.BooleanField(blank=True, default=False, null=True)),
|
||||||
|
('level', models.CharField(blank=True, max_length=50, null=True)),
|
||||||
|
('category', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='competencies', to='Common.category')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Discount',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('name', models.CharField(blank=True, max_length=255, null=True)),
|
||||||
|
('amount', models.DecimalField(decimal_places=2, default=0, max_digits=10)),
|
||||||
|
('description', models.TextField(blank=True)),
|
||||||
|
('discount_type', models.IntegerField(choices=[(0, 'Currency'), (1, 'Percent')], default=0)),
|
||||||
|
('type', models.IntegerField(choices=[(0, 'Registration Fee'), (1, 'Tuition Fee')], default=0)),
|
||||||
|
('updated_at', models.DateTimeField(auto_now=True)),
|
||||||
|
('establishment', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='discounts', to='Establishment.establishment')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='EstablishmentCompetency',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('custom_name', models.TextField(blank=True, help_text='Nom de la compétence custom', null=True)),
|
||||||
|
('is_required', models.BooleanField(default=True)),
|
||||||
|
('competency', models.ForeignKey(blank=True, help_text='Compétence de référence (optionnelle si custom)', null=True, on_delete=django.db.models.deletion.CASCADE, to='School.competency')),
|
||||||
|
('custom_category', models.ForeignKey(blank=True, help_text='Catégorie de la compétence custom', null=True, on_delete=django.db.models.deletion.CASCADE, to='Common.category')),
|
||||||
|
('establishment', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='Establishment.establishment')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'unique_together': {('establishment', 'competency', 'custom_name', 'custom_category')},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='competency',
|
||||||
|
name='establishments',
|
||||||
|
field=models.ManyToManyField(blank=True, related_name='competencies', through='School.EstablishmentCompetency', to='Establishment.establishment'),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Fee',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('name', models.CharField(blank=True, max_length=255, null=True)),
|
||||||
|
('base_amount', models.DecimalField(decimal_places=2, default=0, max_digits=10)),
|
||||||
|
('description', models.TextField(blank=True)),
|
||||||
|
('is_active', models.BooleanField(default=True)),
|
||||||
|
('updated_at', models.DateTimeField(auto_now=True)),
|
||||||
|
('type', models.IntegerField(choices=[(0, 'Registration Fee'), (1, 'Tuition Fee')], default=0)),
|
||||||
|
('establishment', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='fees', to='Establishment.establishment')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='PaymentMode',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('type', models.IntegerField(choices=[(0, 'Registration Fee'), (1, 'Tuition Fee')], default=0)),
|
||||||
|
('establishment', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='payment_modes', to='Establishment.establishment')),
|
||||||
|
('mode', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='payment_modes', to='Common.paymentmodetype')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='PaymentPlan',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('due_dates', django.contrib.postgres.fields.ArrayField(base_field=models.DateField(), blank=True, null=True, size=None)),
|
||||||
|
('type', models.IntegerField(choices=[(0, 'Registration Fee'), (1, 'Tuition Fee')], default=0)),
|
||||||
|
('establishment', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='payment_plans', to='Establishment.establishment')),
|
||||||
|
('plan_type', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='payment_plans', to='Common.paymentplantype')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='SchoolClass',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('atmosphere_name', models.CharField(blank=True, max_length=255, null=True)),
|
||||||
|
('age_range', models.JSONField(blank=True, null=True)),
|
||||||
|
('number_of_students', models.PositiveIntegerField(blank=True, null=True)),
|
||||||
|
('teaching_language', models.CharField(blank=True, max_length=255)),
|
||||||
|
('school_year', models.CharField(blank=True, max_length=9)),
|
||||||
|
('updated_date', models.DateTimeField(auto_now=True)),
|
||||||
|
('type', models.IntegerField(choices=[(1, 'Annuel'), (2, 'Semestriel'), (3, 'Trimestriel')], default=1)),
|
||||||
|
('time_range', models.JSONField(default=list)),
|
||||||
|
('opening_days', django.contrib.postgres.fields.ArrayField(base_field=models.IntegerField(), default=list, size=None)),
|
||||||
|
('establishment', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='school_classes', to='Establishment.establishment')),
|
||||||
|
('levels', models.ManyToManyField(blank=True, related_name='school_classes', to='Common.level')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Planning',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('level', models.IntegerField(blank=True, choices=[(1, 'Très Petite Section (TPS)'), (2, 'Petite Section (PS)'), (3, 'Moyenne Section (MS)'), (4, 'Grande Section (GS)'), (5, 'Cours Préparatoire (CP)'), (6, 'Cours Élémentaire 1 (CE1)'), (7, 'Cours Élémentaire 2 (CE2)'), (8, 'Cours Moyen 1 (CM1)'), (9, 'Cours Moyen 2 (CM2)')], null=True)),
|
||||||
|
('schedule', models.JSONField(default=dict)),
|
||||||
|
('school_class', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='plannings', to='School.schoolclass')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Speciality',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('name', models.CharField(max_length=100)),
|
||||||
|
('updated_date', models.DateTimeField(auto_now=True)),
|
||||||
|
('color_code', models.CharField(default='#FFFFFF', max_length=7)),
|
||||||
|
('establishment', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='specialities', to='Establishment.establishment')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Teacher',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('last_name', models.CharField(max_length=100)),
|
||||||
|
('first_name', models.CharField(max_length=100)),
|
||||||
|
('updated_date', models.DateTimeField(auto_now=True)),
|
||||||
|
('profile_role', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='teacher_profile', to='Auth.profilerole')),
|
||||||
|
('specialities', models.ManyToManyField(blank=True, to='School.speciality')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='schoolclass',
|
||||||
|
name='teachers',
|
||||||
|
field=models.ManyToManyField(blank=True, to='School.teacher'),
|
||||||
|
),
|
||||||
|
]
|
||||||
0
Back-End/School/migrations/__init__.py
Normal file
0
Back-End/School/migrations/__init__.py
Normal file
@ -33,6 +33,10 @@ from N3wtSchool.bdd import delete_object, getAllObjects, getObject
|
|||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from Subscriptions.models import Student, StudentCompetency
|
from Subscriptions.models import Student, StudentCompetency
|
||||||
|
from Subscriptions.util import getCurrentSchoolYear
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@method_decorator(csrf_protect, name='dispatch')
|
@method_decorator(csrf_protect, name='dispatch')
|
||||||
@method_decorator(ensure_csrf_cookie, name='dispatch')
|
@method_decorator(ensure_csrf_cookie, name='dispatch')
|
||||||
@ -581,7 +585,6 @@ class EstablishmentCompetencyListCreateView(APIView):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
category = Category.objects.get(id=category_id)
|
category = Category.objects.get(id=category_id)
|
||||||
# Vérifier si une compétence custom du même nom existe déjà pour cet établissement et cette catégorie
|
|
||||||
ec_exists = EstablishmentCompetency.objects.filter(
|
ec_exists = EstablishmentCompetency.objects.filter(
|
||||||
establishment_id=establishment_id,
|
establishment_id=establishment_id,
|
||||||
competency__isnull=True,
|
competency__isnull=True,
|
||||||
@ -598,13 +601,32 @@ class EstablishmentCompetencyListCreateView(APIView):
|
|||||||
custom_category=category,
|
custom_category=category,
|
||||||
is_required=False
|
is_required=False
|
||||||
)
|
)
|
||||||
# Associer à tous les élèves de l'établissement
|
|
||||||
|
# Récupérer l'établissement et sa fréquence d'évaluation
|
||||||
|
establishment = ec.establishment
|
||||||
|
evaluation_frequency = establishment.evaluation_frequency # 1=Trimestre, 2=Semestre, 3=Année
|
||||||
|
|
||||||
|
# Déterminer l'année scolaire courante
|
||||||
|
school_year = getCurrentSchoolYear()
|
||||||
|
|
||||||
|
# Générer les périodes selon la fréquence
|
||||||
|
periods = []
|
||||||
|
if evaluation_frequency == 1: # Trimestre
|
||||||
|
periods = [f"T{i+1}_{school_year}" for i in range(3)]
|
||||||
|
elif evaluation_frequency == 2: # Semestre
|
||||||
|
periods = [f"S{i+1}_{school_year}" for i in range(2)]
|
||||||
|
elif evaluation_frequency == 3: # Année
|
||||||
|
periods = [f"A_{school_year}"]
|
||||||
|
|
||||||
|
# Associer à tous les élèves de l'établissement pour chaque période
|
||||||
students = Student.objects.filter(associated_class__establishment_id=establishment_id)
|
students = Student.objects.filter(associated_class__establishment_id=establishment_id)
|
||||||
for student in students:
|
for student in students:
|
||||||
StudentCompetency.objects.get_or_create(
|
for period in periods:
|
||||||
student=student,
|
StudentCompetency.objects.get_or_create(
|
||||||
establishment_competency=ec
|
student=student,
|
||||||
)
|
establishment_competency=ec,
|
||||||
|
period=period
|
||||||
|
)
|
||||||
|
|
||||||
created.append({
|
created.append({
|
||||||
"competence_id": ec.id,
|
"competence_id": ec.id,
|
||||||
|
|||||||
29
Back-End/Settings/migrations/0001_initial.py
Normal file
29
Back-End/Settings/migrations/0001_initial.py
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
# Generated by Django 5.1.3 on 2025-05-28 11:14
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('Establishment', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='SMTPSettings',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('smtp_server', models.CharField(max_length=255)),
|
||||||
|
('smtp_port', models.PositiveIntegerField()),
|
||||||
|
('smtp_user', models.CharField(max_length=255)),
|
||||||
|
('smtp_password', models.CharField(max_length=255)),
|
||||||
|
('use_tls', models.BooleanField(default=True)),
|
||||||
|
('use_ssl', models.BooleanField(default=False)),
|
||||||
|
('establishment', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='Establishment.establishment')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
||||||
0
Back-End/Settings/migrations/__init__.py
Normal file
0
Back-End/Settings/migrations/__init__.py
Normal file
213
Back-End/Subscriptions/migrations/0001_initial.py
Normal file
213
Back-End/Subscriptions/migrations/0001_initial.py
Normal file
@ -0,0 +1,213 @@
|
|||||||
|
# Generated by Django 5.1.3 on 2025-05-28 11:14
|
||||||
|
|
||||||
|
import Subscriptions.models
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('Auth', '__first__'),
|
||||||
|
('Common', '0001_initial'),
|
||||||
|
('Establishment', '0001_initial'),
|
||||||
|
('School', '__first__'),
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Language',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(primary_key=True, serialize=False)),
|
||||||
|
('label', models.CharField(default='', max_length=200)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Student',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('photo', models.FileField(blank=True, null=True, upload_to=Subscriptions.models.registration_photo_upload_to)),
|
||||||
|
('last_name', models.CharField(default='', max_length=200)),
|
||||||
|
('first_name', models.CharField(default='', max_length=200)),
|
||||||
|
('gender', models.IntegerField(blank=True, choices=[(0, 'Sélection du genre'), (1, 'Garçon'), (2, 'Fille')], default=0)),
|
||||||
|
('nationality', models.CharField(blank=True, default='', max_length=200)),
|
||||||
|
('address', models.CharField(blank=True, default='', max_length=200)),
|
||||||
|
('birth_date', models.DateField(blank=True, null=True)),
|
||||||
|
('birth_place', models.CharField(blank=True, default='', max_length=200)),
|
||||||
|
('birth_postal_code', models.IntegerField(blank=True, default=0)),
|
||||||
|
('attending_physician', models.CharField(blank=True, default='', max_length=200)),
|
||||||
|
('associated_class', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='students', to='School.schoolclass')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='RegistrationSchoolFileTemplate',
|
||||||
|
fields=[
|
||||||
|
('id', models.IntegerField(primary_key=True, serialize=False)),
|
||||||
|
('slug', models.CharField(default='', max_length=255)),
|
||||||
|
('name', models.CharField(default='', max_length=255)),
|
||||||
|
('file', models.FileField(blank=True, null=True, upload_to=Subscriptions.models.registration_school_file_upload_to)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Sibling',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('last_name', models.CharField(blank=True, max_length=200, null=True)),
|
||||||
|
('first_name', models.CharField(blank=True, max_length=200, null=True)),
|
||||||
|
('birth_date', models.DateField(blank=True, null=True)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Guardian',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('last_name', models.CharField(blank=True, max_length=200, null=True)),
|
||||||
|
('first_name', models.CharField(blank=True, max_length=200, null=True)),
|
||||||
|
('birth_date', models.DateField(blank=True, null=True)),
|
||||||
|
('address', models.CharField(blank=True, default='', max_length=200)),
|
||||||
|
('phone', models.CharField(blank=True, default='', max_length=200)),
|
||||||
|
('profession', models.CharField(blank=True, default='', max_length=200)),
|
||||||
|
('profile_role', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='guardian_profile', to='Auth.profilerole')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='RegistrationFileGroup',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('name', models.CharField(default='', max_length=255)),
|
||||||
|
('description', models.TextField(blank=True, null=True)),
|
||||||
|
('establishment', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='file_group', to='Establishment.establishment')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='RegistrationForm',
|
||||||
|
fields=[
|
||||||
|
('student', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, serialize=False, to='Subscriptions.student')),
|
||||||
|
('status', models.IntegerField(choices=[(0, "Pas de dossier d'inscription"), (1, "Dossier d'inscription initialisé"), (2, "Dossier d'inscription envoyé"), (3, "Dossier d'inscription en cours de validation"), (4, "Dossier d'inscription à relancer"), (5, "Dossier d'inscription validé"), (6, "Dossier d'inscription archivé"), (7, 'Mandat SEPA envoyé'), (8, 'Mandat SEPA à envoyer')], default=0)),
|
||||||
|
('last_update', models.DateTimeField(auto_now=True)),
|
||||||
|
('school_year', models.CharField(blank=True, default='', max_length=9)),
|
||||||
|
('notes', models.CharField(blank=True, max_length=200)),
|
||||||
|
('registration_link_code', models.CharField(blank=True, default='', max_length=200)),
|
||||||
|
('registration_file', models.FileField(blank=True, null=True, upload_to=Subscriptions.models.registration_file_path)),
|
||||||
|
('sepa_file', models.FileField(blank=True, null=True, upload_to=Subscriptions.models.registration_file_path)),
|
||||||
|
('fusion_file', models.FileField(blank=True, null=True, upload_to=Subscriptions.models.registration_file_path)),
|
||||||
|
('associated_rf', models.CharField(blank=True, default='', max_length=200)),
|
||||||
|
('discounts', models.ManyToManyField(blank=True, related_name='register_forms', to='School.discount')),
|
||||||
|
('establishment', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='register_forms', to='Establishment.establishment')),
|
||||||
|
('fees', models.ManyToManyField(blank=True, related_name='register_forms', to='School.fee')),
|
||||||
|
('fileGroup', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='register_forms', to='Subscriptions.registrationfilegroup')),
|
||||||
|
('registration_payment', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='registration_payment_modes_forms', to='School.paymentmode')),
|
||||||
|
('registration_payment_plan', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='registration_payment_plans_forms', to='School.paymentplan')),
|
||||||
|
('tuition_payment', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='tuition_payment_modes_forms', to='School.paymentmode')),
|
||||||
|
('tuition_payment_plan', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='tuition_payment_plans_forms', to='School.paymentplan')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='student',
|
||||||
|
name='guardians',
|
||||||
|
field=models.ManyToManyField(blank=True, to='Subscriptions.guardian'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='student',
|
||||||
|
name='level',
|
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='students', to='Common.level'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='student',
|
||||||
|
name='profiles',
|
||||||
|
field=models.ManyToManyField(blank=True, to=settings.AUTH_USER_MODEL),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='student',
|
||||||
|
name='spoken_languages',
|
||||||
|
field=models.ManyToManyField(blank=True, to='Subscriptions.language'),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='BilanCompetence',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('file', models.FileField(blank=True, null=True, upload_to=Subscriptions.models.registration_bilan_form_upload_to)),
|
||||||
|
('period', models.CharField(help_text='Période ex: T1-2024_2025, S1-2024_2025, A-2024_2025', max_length=20)),
|
||||||
|
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('student', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='bilans', to='Subscriptions.student')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='AbsenceManagement',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('day', models.DateField(blank=True, null=True)),
|
||||||
|
('moment', models.IntegerField(choices=[(1, 'Morning'), (2, 'Afternoon'), (3, 'Total')], default=3)),
|
||||||
|
('reason', models.IntegerField(choices=[(1, 'Justified Absence'), (2, 'Unjustified Absence'), (3, 'Justified Late'), (4, 'Unjustified Late')], default=2)),
|
||||||
|
('commentaire', models.TextField(blank=True, null=True)),
|
||||||
|
('establishment', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='absences', to='Establishment.establishment')),
|
||||||
|
('student', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='absences', to='Subscriptions.student')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='RegistrationParentFileMaster',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('name', models.CharField(default='', max_length=255)),
|
||||||
|
('description', models.CharField(blank=True, null=True)),
|
||||||
|
('is_required', models.BooleanField(default=False)),
|
||||||
|
('groups', models.ManyToManyField(blank=True, related_name='parent_file_masters', to='Subscriptions.registrationfilegroup')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='RegistrationSchoolFileMaster',
|
||||||
|
fields=[
|
||||||
|
('id', models.IntegerField(primary_key=True, serialize=False)),
|
||||||
|
('name', models.CharField(default='', max_length=255)),
|
||||||
|
('is_required', models.BooleanField(default=False)),
|
||||||
|
('groups', models.ManyToManyField(blank=True, related_name='school_file_masters', to='Subscriptions.registrationfilegroup')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='student',
|
||||||
|
name='registration_files',
|
||||||
|
field=models.ManyToManyField(blank=True, related_name='students', to='Subscriptions.registrationschoolfiletemplate'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='registrationschoolfiletemplate',
|
||||||
|
name='master',
|
||||||
|
field=models.ForeignKey(blank=True, on_delete=django.db.models.deletion.CASCADE, related_name='school_file_templates', to='Subscriptions.registrationschoolfilemaster'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='student',
|
||||||
|
name='siblings',
|
||||||
|
field=models.ManyToManyField(blank=True, to='Subscriptions.sibling'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='registrationschoolfiletemplate',
|
||||||
|
name='registration_form',
|
||||||
|
field=models.ForeignKey(blank=True, on_delete=django.db.models.deletion.CASCADE, related_name='school_file_templates', to='Subscriptions.registrationform'),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='RegistrationParentFileTemplate',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('file', models.FileField(blank=True, null=True, upload_to=Subscriptions.models.registration_parent_file_upload_to)),
|
||||||
|
('master', models.ForeignKey(blank=True, on_delete=django.db.models.deletion.CASCADE, related_name='parent_file_templates', to='Subscriptions.registrationparentfilemaster')),
|
||||||
|
('registration_form', models.ForeignKey(blank=True, on_delete=django.db.models.deletion.CASCADE, related_name='parent_file_templates', to='Subscriptions.registrationform')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='StudentCompetency',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('score', models.IntegerField(blank=True, null=True)),
|
||||||
|
('comment', models.TextField(blank=True, null=True)),
|
||||||
|
('period', models.CharField(blank=True, default='', help_text="Période d'évaluation ex: T1-2024_2025, S1-2024_2025, A-2024_2025", max_length=20)),
|
||||||
|
('establishment_competency', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='student_scores', to='School.establishmentcompetency')),
|
||||||
|
('student', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='competency_scores', to='Subscriptions.student')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'unique_together': {('student', 'establishment_competency', 'period')},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 5.1.3 on 2025-05-30 07:39
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('Subscriptions', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='registrationparentfilemaster',
|
||||||
|
name='description',
|
||||||
|
field=models.CharField(blank=True, max_length=500, null=True),
|
||||||
|
),
|
||||||
|
]
|
||||||
0
Back-End/Subscriptions/migrations/__init__.py
Normal file
0
Back-End/Subscriptions/migrations/__init__.py
Normal file
@ -6,6 +6,9 @@ from django.utils.translation import gettext_lazy as _
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logger = logging.getLogger("SubscriptionModels")
|
||||||
|
|
||||||
class Language(models.Model):
|
class Language(models.Model):
|
||||||
"""
|
"""
|
||||||
@ -394,7 +397,8 @@ class RegistrationParentFileTemplate(models.Model):
|
|||||||
registration_files = RegistrationParentFileTemplate.objects.filter(registration_form=register_form_id)
|
registration_files = RegistrationParentFileTemplate.objects.filter(registration_form=register_form_id)
|
||||||
filenames = []
|
filenames = []
|
||||||
for reg_file in registration_files:
|
for reg_file in registration_files:
|
||||||
filenames.append(reg_file.file.path)
|
if reg_file.file and hasattr(reg_file.file, 'path'):
|
||||||
|
filenames.append(reg_file.file.path)
|
||||||
return filenames
|
return filenames
|
||||||
|
|
||||||
class AbsenceMoment(models.IntegerChoices):
|
class AbsenceMoment(models.IntegerChoices):
|
||||||
|
|||||||
@ -381,16 +381,20 @@ class RegistrationFormSerializer(serializers.ModelSerializer):
|
|||||||
|
|
||||||
class StudentByParentSerializer(serializers.ModelSerializer):
|
class StudentByParentSerializer(serializers.ModelSerializer):
|
||||||
id = serializers.IntegerField(required=False)
|
id = serializers.IntegerField(required=False)
|
||||||
|
associated_class_name = serializers.SerializerMethodField()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Student
|
model = Student
|
||||||
fields = ['id', 'last_name', 'first_name', 'level', 'photo']
|
fields = ['id', 'last_name', 'first_name', 'level', 'photo', 'associated_class_name']
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(StudentByParentSerializer, self).__init__(*args, **kwargs)
|
super(StudentByParentSerializer, self).__init__(*args, **kwargs)
|
||||||
for field in self.fields:
|
for field in self.fields:
|
||||||
self.fields[field].required = False
|
self.fields[field].required = False
|
||||||
|
|
||||||
|
def get_associated_class_name(self, obj):
|
||||||
|
return obj.associated_class.atmosphere_name if obj.associated_class else None
|
||||||
|
|
||||||
class RegistrationFormByParentSerializer(serializers.ModelSerializer):
|
class RegistrationFormByParentSerializer(serializers.ModelSerializer):
|
||||||
student = StudentByParentSerializer(many=False, required=True)
|
student = StudentByParentSerializer(many=False, required=True)
|
||||||
|
|
||||||
|
|||||||
@ -33,6 +33,7 @@
|
|||||||
<body>
|
<body>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="header">
|
<div class="header">
|
||||||
|
<img src="{{URL_DJANGO}}static/img/logo_min.svg" alt="Logo N3wt School" class="logo" />
|
||||||
<h1>Confirmation d'inscription</h1>
|
<h1>Confirmation d'inscription</h1>
|
||||||
</div>
|
</div>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
|
|||||||
@ -0,0 +1,59 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Confirmation de souscription</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
max-width: 600px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
.header {
|
||||||
|
background-color: #f4f4f4;
|
||||||
|
padding: 10px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.logo {
|
||||||
|
width: 120px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
.content {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
.footer {
|
||||||
|
font-size: 12px;
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 30px;
|
||||||
|
color: #777;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="header">
|
||||||
|
<img src="{{URL_DJANGO}}/static/img/logo_min.svg" alt="Logo N3wt School" class="logo" />
|
||||||
|
<h1>Confirmation de souscription</h1>
|
||||||
|
</div>
|
||||||
|
<div class="content">
|
||||||
|
<p>Bonjour,</p>
|
||||||
|
<p>Nous sommes ravis de vous compter parmi les utilisateurs de N3wt School et vous remercions pour votre confiance</p>
|
||||||
|
<p>Vous trouverez ci-joint le lien vers la page d'authentification : <a href="{{BASE_URL}}/users/login">{{BASE_URL}}/users/login</a></p>
|
||||||
|
<p>S'il s'agit de votre première connexion, veuillez procéder à l'activation de votre compte à cette url : <a href="{{BASE_URL}}/users/subscribe?establishment_id={{establishment}}">{{BASE_URL}}/users/subscribe</a></p>
|
||||||
|
<p>votre identifiant est : {{ email }}</p>
|
||||||
|
<p>Notre équipe est à votre disposition pour vous aider à tirer pleinement parti des fonctionnalités offertes par Newt School.</p>
|
||||||
|
<p>N'hésitez pas à nous contacter pour toute question ou besoin d'assistance.</p>
|
||||||
|
<p>Cordialement,</p>
|
||||||
|
<p>L'équipe N3wt School</p>
|
||||||
|
</div>
|
||||||
|
<div class="footer">
|
||||||
|
<p>Ce message est généré automatiquement, merci de ne pas y répondre.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@ -1,233 +1,228 @@
|
|||||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<title>Fiche élève de {{ student.last_name }} {{ student.first_name }}</title>
|
<title>Fiche élève de {{ student.last_name }} {{ student.first_name }}</title>
|
||||||
<style>
|
<style>
|
||||||
@page {
|
@page {
|
||||||
size: A4;
|
size: A4;
|
||||||
margin: 2cm;
|
margin: 2cm;
|
||||||
}
|
}
|
||||||
body {
|
body {
|
||||||
font-family: 'Arial', sans-serif;
|
font-family: 'Arial', sans-serif;
|
||||||
font-size: 12pt;
|
font-size: 12pt;
|
||||||
line-height: 1.6;
|
color: #222;
|
||||||
margin: 0;
|
background: #fff;
|
||||||
padding: 0;
|
margin: 0;
|
||||||
color: #333;
|
padding: 0;
|
||||||
background-color: #f9f9f9;
|
}
|
||||||
}
|
.container {
|
||||||
.container {
|
width: 100%;
|
||||||
width: 100%;
|
padding: 0;
|
||||||
padding: 20px;
|
background: #fff;
|
||||||
background-color: #fff;
|
}
|
||||||
border-radius: 10px;
|
.header {
|
||||||
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
|
text-align: center;
|
||||||
}
|
margin-bottom: 24px;
|
||||||
.header {
|
border-bottom: 2px solid #4CAF50;
|
||||||
text-align: center;
|
padding-bottom: 12px;
|
||||||
margin-bottom: 30px;
|
position: relative;
|
||||||
border-bottom: 3px solid #4CAF50;
|
}
|
||||||
padding-bottom: 15px;
|
.title {
|
||||||
position: relative;
|
font-size: 22pt;
|
||||||
}
|
font-weight: bold;
|
||||||
.title {
|
color: #4CAF50;
|
||||||
font-size: 20pt;
|
margin: 0;
|
||||||
font-weight: bold;
|
}
|
||||||
color: #4CAF50;
|
.photo {
|
||||||
margin: 0;
|
position: absolute;
|
||||||
}
|
top: 0;
|
||||||
.photo {
|
right: 0;
|
||||||
position: absolute;
|
width: 90px;
|
||||||
top: 0;
|
height: 90px;
|
||||||
right: 0;
|
object-fit: cover;
|
||||||
width: 100px;
|
border: 1px solid #4CAF50;
|
||||||
height: 100px;
|
border-radius: 8px;
|
||||||
object-fit: cover;
|
}
|
||||||
border: 2px solid #4CAF50;
|
.section {
|
||||||
border-radius: 10px;
|
margin-bottom: 32px; /* Espacement augmenté entre les sections */
|
||||||
}
|
}
|
||||||
.section {
|
.section-title {
|
||||||
margin-bottom: 20px;
|
font-size: 15pt;
|
||||||
padding: 15px;
|
font-weight: bold;
|
||||||
border: 1px solid #ddd;
|
color: #4CAF50;
|
||||||
border-radius: 8px;
|
margin-bottom: 18px; /* Espacement sous le titre de section */
|
||||||
background-color: #fefefe;
|
border-bottom: 1px solid #4CAF50;
|
||||||
}
|
padding-bottom: 2px;
|
||||||
.section-title {
|
}
|
||||||
font-size: 18pt;
|
table {
|
||||||
font-weight: bold;
|
width: 100%;
|
||||||
color: #4CAF50;
|
border-collapse: collapse;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 8px;
|
||||||
text-align: left;
|
}
|
||||||
border-bottom: 2px solid #4CAF50;
|
th, td {
|
||||||
padding-bottom: 5px;
|
border: 1px solid #bbb;
|
||||||
}
|
padding: 6px 8px;
|
||||||
table {
|
text-align: left;
|
||||||
width: 100%;
|
}
|
||||||
border-collapse: collapse;
|
th {
|
||||||
margin-top: 10px;
|
background: #f3f3f3;
|
||||||
}
|
font-weight: bold;
|
||||||
td {
|
}
|
||||||
padding: 8px;
|
tr:nth-child(even) {
|
||||||
vertical-align: top;
|
background: #fafafa;
|
||||||
}
|
}
|
||||||
.label-cell {
|
.label-cell {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: #555;
|
width: 30%;
|
||||||
width: 35%;
|
background: #f3f3f3;
|
||||||
text-align: right;
|
}
|
||||||
padding-right: 10px;
|
.value-cell {
|
||||||
}
|
width: 70%;
|
||||||
.value-cell {
|
}
|
||||||
color: #333;
|
.signature {
|
||||||
width: 65%;
|
margin-top: 30px;
|
||||||
text-align: left;
|
text-align: right;
|
||||||
}
|
font-style: italic;
|
||||||
.phone {
|
color: #555;
|
||||||
white-space: nowrap;
|
}
|
||||||
}
|
.signature-text {
|
||||||
.signature {
|
font-weight: bold;
|
||||||
margin-top: 30px;
|
color: #333;
|
||||||
text-align: right;
|
}
|
||||||
font-style: italic;
|
.subsection-title {
|
||||||
color: #555;
|
font-size: 12pt;
|
||||||
}
|
color: #333;
|
||||||
.signature-text {
|
margin: 8px 0 4px 0;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: #333;
|
}
|
||||||
}
|
</style>
|
||||||
</style>
|
</head>
|
||||||
</head>
|
<body>
|
||||||
<body>
|
{% load myTemplateTag %}
|
||||||
{% load myTemplateTag %}
|
<div class="container">
|
||||||
<div class="container">
|
<!-- Header Section -->
|
||||||
<!-- Header Section -->
|
<div class="header">
|
||||||
<div class="header">
|
<h1 class="title">Fiche élève de {{ student.last_name }} {{ student.first_name }}</h1>
|
||||||
<h1 class="title">Fiche élève de {{ student.last_name }} {{ student.first_name }}</h1>
|
{% if student.photo %}
|
||||||
{% if student.photo %}
|
<img src="{{ student.get_photo_url }}" alt="Photo de l'élève" class="photo" />
|
||||||
<img
|
{% else %}
|
||||||
src="{{ student.get_photo_url }}"
|
<img src="/static/img/default-photo.jpg" alt="Photo par défaut" class="photo" />
|
||||||
alt="Photo de l'élève"
|
{% endif %}
|
||||||
class="photo"
|
|
||||||
/>
|
|
||||||
{% else %}
|
|
||||||
<img
|
|
||||||
src="/static/img/default-photo.jpg"
|
|
||||||
alt="Photo par défaut"
|
|
||||||
class="photo"
|
|
||||||
/>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Student Section -->
|
|
||||||
<div class="section">
|
|
||||||
<h2 class="section-title">ÉLÈVE</h2>
|
|
||||||
<table>
|
|
||||||
<tr>
|
|
||||||
<td class="label-cell">Nom :</td>
|
|
||||||
<td class="value-cell">{{ student.last_name }}</td>
|
|
||||||
<td class="label-cell">Prénom :</td>
|
|
||||||
<td class="value-cell">{{ student.first_name }}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td class="label-cell">Adresse :</td>
|
|
||||||
<td colspan="3" class="value-cell">{{ student.address }}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td class="label-cell">Genre :</td>
|
|
||||||
<td class="value-cell">{{ student|getStudentGender }}</td>
|
|
||||||
<td class="label-cell">Né(e) le :</td>
|
|
||||||
<td class="value-cell">{{ student.birth_date }}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td class="label-cell">À :</td>
|
|
||||||
<td colspan="3" class="value-cell">{{ student.birth_place }} ({{ student.birth_postal_code }})</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td class="label-cell">Nationalité :</td>
|
|
||||||
<td class="value-cell">{{ student.nationality }}</td>
|
|
||||||
<td class="label-cell">Niveau :</td>
|
|
||||||
<td class="value-cell">{{ student|getStudentLevel }}</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Guardians Section -->
|
|
||||||
<div class="section">
|
|
||||||
<h2 class="section-title">RESPONSABLES</h2>
|
|
||||||
{% for guardian in student.getGuardians %}
|
|
||||||
<div class="subsection">
|
|
||||||
<h3 class="subsection-title">Responsable {{ forloop.counter }}</h3>
|
|
||||||
<table>
|
|
||||||
<tr>
|
|
||||||
<td class="label-cell">Nom :</td>
|
|
||||||
<td class="value-cell">{{ guardian.last_name }}</td>
|
|
||||||
<td class="label-cell">Prénom :</td>
|
|
||||||
<td class="value-cell">{{ guardian.first_name }}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td class="label-cell">Adresse :</td>
|
|
||||||
<td colspan="3" class="value-cell">{{ guardian.address }}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td class="label-cell">Né(e) le :</td>
|
|
||||||
<td class="value-cell">{{ guardian.birth_date }}</td>
|
|
||||||
<td class="label-cell">Email :</td>
|
|
||||||
<td class="value-cell">{{ guardian.email }}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td class="label-cell">Téléphone :</td>
|
|
||||||
<td class="value-cell phone">{{ guardian.phone|phone_format }}</td>
|
|
||||||
<td class="label-cell">Profession :</td>
|
|
||||||
<td class="value-cell">{{ guardian.profession }}</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Siblings Section -->
|
|
||||||
<div class="section">
|
|
||||||
<h2 class="section-title">FRATRIE</h2>
|
|
||||||
{% for sibling in student.getSiblings %}
|
|
||||||
<div class="subsection">
|
|
||||||
<h3 class="subsection-title">Frère/Soeur {{ forloop.counter }}</h3>
|
|
||||||
<table>
|
|
||||||
<tr>
|
|
||||||
<td class="label-cell">Nom :</td>
|
|
||||||
<td class="value-cell">{{ sibling.last_name }}</td>
|
|
||||||
<td class="label-cell">Prénom :</td>
|
|
||||||
<td class="value-cell">{{ sibling.first_name }}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td class="label-cell">Né(e) le :</td>
|
|
||||||
<td colspan="3" class="value-cell">{{ sibling.birth_date }}</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Payment Section -->
|
|
||||||
<div class="section">
|
|
||||||
<h2 class="section-title">MODALITÉS DE PAIEMENT</h2>
|
|
||||||
<table>
|
|
||||||
<tr>
|
|
||||||
<td class="label-cell">Frais d'inscription :</td>
|
|
||||||
<td class="value-cell">{{ student|getRegistrationPaymentMethod }} en {{ student|getRegistrationPaymentPlan }}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td class="label-cell">Frais de scolarité :</td>
|
|
||||||
<td class="value-cell">{{ student|getTuitionPaymentMethod }} en {{ student|getTuitionPaymentPlan }}</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Signature Section -->
|
|
||||||
<div class="signature">
|
|
||||||
Fait le <span class="signature-text">{{ signatureDate }}</span> à <span class="signature-text">{{ signatureTime }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</body>
|
|
||||||
|
<!-- Élève -->
|
||||||
|
<div class="section">
|
||||||
|
<div class="section-title">ÉLÈVE</div>
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td class="label-cell">Nom</td>
|
||||||
|
<td class="value-cell">{{ student.last_name }}</td>
|
||||||
|
<td class="label-cell">Prénom</td>
|
||||||
|
<td class="value-cell">{{ student.first_name }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="label-cell">Adresse</td>
|
||||||
|
<td class="value-cell" colspan="3">{{ student.address }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="label-cell">Genre</td>
|
||||||
|
<td class="value-cell">{{ student|getStudentGender }}</td>
|
||||||
|
<td class="label-cell">Né(e) le</td>
|
||||||
|
<td class="value-cell">{{ student.birth_date }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="label-cell">À</td>
|
||||||
|
<td class="value-cell">{{ student.birth_place }} ({{ student.birth_postal_code }})</td>
|
||||||
|
<td class="label-cell">Nationalité</td>
|
||||||
|
<td class="value-cell">{{ student.nationality }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="label-cell">Niveau</td>
|
||||||
|
<td class="value-cell">{{ student|getStudentLevel }}</td>
|
||||||
|
<td class="label-cell"></td>
|
||||||
|
<td class="value-cell"></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Responsables -->
|
||||||
|
<div class="section">
|
||||||
|
<div class="section-title">RESPONSABLES</div>
|
||||||
|
{% for guardian in student.getGuardians %}
|
||||||
|
<div>
|
||||||
|
<div class="subsection-title">Responsable {{ forloop.counter }}</div>
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td class="label-cell">Nom</td>
|
||||||
|
<td class="value-cell">{{ guardian.last_name }}</td>
|
||||||
|
<td class="label-cell">Prénom</td>
|
||||||
|
<td class="value-cell">{{ guardian.first_name }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="label-cell">Adresse</td>
|
||||||
|
<td class="value-cell" colspan="3">{{ guardian.address }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="label-cell">Email</td>
|
||||||
|
<td class="value-cell" colspan="3">{{ guardian.email }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="label-cell">Né(e) le</td>
|
||||||
|
<td class="value-cell">{{ guardian.birth_date }}</td>
|
||||||
|
<td class="label-cell">Téléphone</td>
|
||||||
|
<td class="value-cell">{{ guardian.phone|phone_format }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="label-cell">Profession</td>
|
||||||
|
<td class="value-cell" colspan="3">{{ guardian.profession }}</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Fratrie -->
|
||||||
|
<div class="section">
|
||||||
|
<div class="section-title">FRATRIE</div>
|
||||||
|
{% for sibling in student.getSiblings %}
|
||||||
|
<div>
|
||||||
|
<div class="subsection-title">Frère/Soeur {{ forloop.counter }}</div>
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td class="label-cell">Nom</td>
|
||||||
|
<td class="value-cell">{{ sibling.last_name }}</td>
|
||||||
|
<td class="label-cell">Prénom</td>
|
||||||
|
<td class="value-cell">{{ sibling.first_name }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="label-cell">Né(e) le</td>
|
||||||
|
<td class="value-cell" colspan="3">{{ sibling.birth_date }}</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Paiement -->
|
||||||
|
<div class="section">
|
||||||
|
<div class="section-title">MODALITÉS DE PAIEMENT</div>
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td class="label-cell">Frais d'inscription</td>
|
||||||
|
<td class="value-cell">{{ student|getRegistrationPaymentMethod }} en {{ student|getRegistrationPaymentPlan }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="label-cell">Frais de scolarité</td>
|
||||||
|
<td class="value-cell">{{ student|getTuitionPaymentMethod }} en {{ student|getTuitionPaymentPlan }}</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Signature -->
|
||||||
|
<div class="signature">
|
||||||
|
Fait le <span class="signature-text">{{ signatureDate }}</span> à <span class="signature-text">{{ signatureTime }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@ -88,6 +88,7 @@ class RegisterFormView(APIView):
|
|||||||
filter = request.GET.get('filter', '').strip()
|
filter = request.GET.get('filter', '').strip()
|
||||||
page_size = request.GET.get('page_size', None)
|
page_size = request.GET.get('page_size', None)
|
||||||
establishment_id = request.GET.get('establishment_id', None)
|
establishment_id = request.GET.get('establishment_id', None)
|
||||||
|
search = request.GET.get('search', '').strip() # <-- Ajout du paramètre search
|
||||||
|
|
||||||
# Gestion du page_size
|
# Gestion du page_size
|
||||||
if page_size is not None:
|
if page_size is not None:
|
||||||
@ -113,7 +114,14 @@ class RegisterFormView(APIView):
|
|||||||
registerForms_List = None
|
registerForms_List = None
|
||||||
|
|
||||||
if registerForms_List:
|
if registerForms_List:
|
||||||
registerForms_List = registerForms_List.filter(establishment=establishment_id).order_by('-last_update')
|
registerForms_List = registerForms_List.filter(establishment=establishment_id)
|
||||||
|
# Ajout du filtre search sur le nom et prénom de l'élève
|
||||||
|
if search:
|
||||||
|
registerForms_List = registerForms_List.filter(
|
||||||
|
Q(student__first_name__icontains=search) |
|
||||||
|
Q(student__last_name__icontains=search)
|
||||||
|
)
|
||||||
|
registerForms_List = registerForms_List.order_by('-last_update')
|
||||||
|
|
||||||
if not registerForms_List:
|
if not registerForms_List:
|
||||||
return JsonResponse({'error': 'aucune donnée trouvée', 'count': 0}, safe=False)
|
return JsonResponse({'error': 'aucune donnée trouvée', 'count': 0}, safe=False)
|
||||||
|
|||||||
@ -12,11 +12,27 @@ from N3wtSchool import bdd
|
|||||||
|
|
||||||
class RegistrationSchoolFileMasterView(APIView):
|
class RegistrationSchoolFileMasterView(APIView):
|
||||||
@swagger_auto_schema(
|
@swagger_auto_schema(
|
||||||
operation_description="Récupère tous les masters de templates d'inscription",
|
operation_description="Récupère tous les masters de templates d'inscription pour un établissement donné",
|
||||||
|
manual_parameters=[
|
||||||
|
openapi.Parameter(
|
||||||
|
'establishment_id',
|
||||||
|
openapi.IN_QUERY,
|
||||||
|
description="ID de l'établissement",
|
||||||
|
type=openapi.TYPE_INTEGER,
|
||||||
|
required=True
|
||||||
|
)
|
||||||
|
],
|
||||||
responses={200: RegistrationSchoolFileMasterSerializer(many=True)}
|
responses={200: RegistrationSchoolFileMasterSerializer(many=True)}
|
||||||
)
|
)
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
masters = RegistrationSchoolFileMaster.objects.all()
|
establishment_id = request.GET.get('establishment_id')
|
||||||
|
if not establishment_id:
|
||||||
|
return Response({'error': "Paramètre 'establishment_id' requis"}, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
# Filtrer les masters liés à l'établissement via groups.establishment
|
||||||
|
masters = RegistrationSchoolFileMaster.objects.filter(
|
||||||
|
groups__establishment__id=establishment_id
|
||||||
|
).distinct()
|
||||||
serializer = RegistrationSchoolFileMasterSerializer(masters, many=True)
|
serializer = RegistrationSchoolFileMasterSerializer(masters, many=True)
|
||||||
return Response(serializer.data)
|
return Response(serializer.data)
|
||||||
|
|
||||||
@ -86,11 +102,27 @@ class RegistrationSchoolFileMasterSimpleView(APIView):
|
|||||||
|
|
||||||
class RegistrationSchoolFileTemplateView(APIView):
|
class RegistrationSchoolFileTemplateView(APIView):
|
||||||
@swagger_auto_schema(
|
@swagger_auto_schema(
|
||||||
operation_description="Récupère tous les templates d'inscription",
|
operation_description="Récupère tous les templates d'inscription pour un établissement donné",
|
||||||
|
manual_parameters=[
|
||||||
|
openapi.Parameter(
|
||||||
|
'establishment_id',
|
||||||
|
openapi.IN_QUERY,
|
||||||
|
description="ID de l'établissement",
|
||||||
|
type=openapi.TYPE_INTEGER,
|
||||||
|
required=True
|
||||||
|
)
|
||||||
|
],
|
||||||
responses={200: RegistrationSchoolFileTemplateSerializer(many=True)}
|
responses={200: RegistrationSchoolFileTemplateSerializer(many=True)}
|
||||||
)
|
)
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
templates = RegistrationSchoolFileTemplate.objects.all()
|
establishment_id = request.GET.get('establishment_id')
|
||||||
|
if not establishment_id:
|
||||||
|
return Response({'error': "Paramètre 'establishment_id' requis"}, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
# Filtrer les templates liés à l'établissement via master.groups.establishment
|
||||||
|
templates = RegistrationSchoolFileTemplate.objects.filter(
|
||||||
|
master__groups__establishment__id=establishment_id
|
||||||
|
).distinct()
|
||||||
serializer = RegistrationSchoolFileTemplateSerializer(templates, many=True)
|
serializer = RegistrationSchoolFileTemplateSerializer(templates, many=True)
|
||||||
return Response(serializer.data)
|
return Response(serializer.data)
|
||||||
|
|
||||||
@ -160,11 +192,27 @@ class RegistrationSchoolFileTemplateSimpleView(APIView):
|
|||||||
|
|
||||||
class RegistrationParentFileMasterView(APIView):
|
class RegistrationParentFileMasterView(APIView):
|
||||||
@swagger_auto_schema(
|
@swagger_auto_schema(
|
||||||
operation_description="Récupère tous les fichiers parents",
|
operation_description="Récupère tous les fichiers parents pour un établissement donné",
|
||||||
|
manual_parameters=[
|
||||||
|
openapi.Parameter(
|
||||||
|
'establishment_id',
|
||||||
|
openapi.IN_QUERY,
|
||||||
|
description="ID de l'établissement",
|
||||||
|
type=openapi.TYPE_INTEGER,
|
||||||
|
required=True
|
||||||
|
)
|
||||||
|
],
|
||||||
responses={200: RegistrationParentFileMasterSerializer(many=True)}
|
responses={200: RegistrationParentFileMasterSerializer(many=True)}
|
||||||
)
|
)
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
templates = RegistrationParentFileMaster.objects.all()
|
establishment_id = request.GET.get('establishment_id')
|
||||||
|
if not establishment_id:
|
||||||
|
return Response({'error': "Paramètre 'establishment_id' requis"}, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
# Filtrer les fichiers parents liés à l'établissement
|
||||||
|
templates = RegistrationParentFileMaster.objects.filter(
|
||||||
|
groups__establishment__id=establishment_id
|
||||||
|
).distinct()
|
||||||
serializer = RegistrationParentFileMasterSerializer(templates, many=True)
|
serializer = RegistrationParentFileMasterSerializer(templates, many=True)
|
||||||
return Response(serializer.data)
|
return Response(serializer.data)
|
||||||
|
|
||||||
@ -234,11 +282,27 @@ class RegistrationParentFileMasterSimpleView(APIView):
|
|||||||
|
|
||||||
class RegistrationParentFileTemplateView(APIView):
|
class RegistrationParentFileTemplateView(APIView):
|
||||||
@swagger_auto_schema(
|
@swagger_auto_schema(
|
||||||
operation_description="Récupère tous les templates d'inscription",
|
operation_description="Récupère tous les templates parents pour un établissement donné",
|
||||||
|
manual_parameters=[
|
||||||
|
openapi.Parameter(
|
||||||
|
'establishment_id',
|
||||||
|
openapi.IN_QUERY,
|
||||||
|
description="ID de l'établissement",
|
||||||
|
type=openapi.TYPE_INTEGER,
|
||||||
|
required=True
|
||||||
|
)
|
||||||
|
],
|
||||||
responses={200: RegistrationParentFileTemplateSerializer(many=True)}
|
responses={200: RegistrationParentFileTemplateSerializer(many=True)}
|
||||||
)
|
)
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
templates = RegistrationParentFileTemplate.objects.all()
|
establishment_id = request.GET.get('establishment_id')
|
||||||
|
if not establishment_id:
|
||||||
|
return Response({'error': "Paramètre 'establishment_id' requis"}, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
# Filtrer les templates parents liés à l'établissement via master.groups.establishment
|
||||||
|
templates = RegistrationParentFileTemplate.objects.filter(
|
||||||
|
master__groups__establishment__id=establishment_id
|
||||||
|
).distinct()
|
||||||
serializer = RegistrationParentFileTemplateSerializer(templates, many=True)
|
serializer = RegistrationParentFileTemplateSerializer(templates, many=True)
|
||||||
return Response(serializer.data)
|
return Response(serializer.data)
|
||||||
|
|
||||||
|
|||||||
@ -1 +1 @@
|
|||||||
__version__ = "0.0.1"
|
__version__ = "0.0.3"
|
||||||
|
|||||||
@ -66,6 +66,7 @@ urllib3==2.2.3
|
|||||||
vine==5.1.0
|
vine==5.1.0
|
||||||
wcwidth==0.2.13
|
wcwidth==0.2.13
|
||||||
webencodings==0.5.1
|
webencodings==0.5.1
|
||||||
|
watchfiles
|
||||||
xhtml2pdf==0.2.16
|
xhtml2pdf==0.2.16
|
||||||
channels==4.0.0
|
channels==4.0.0
|
||||||
channels-redis==4.1.0
|
channels-redis==4.1.0
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import subprocess
|
import subprocess
|
||||||
import os
|
import os
|
||||||
|
from watchfiles import run_process
|
||||||
|
|
||||||
def run_command(command):
|
def run_command(command):
|
||||||
process = subprocess.Popen(command, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
process = subprocess.Popen(command, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||||
@ -10,21 +11,22 @@ def run_command(command):
|
|||||||
print(f"stderr: {stderr.decode()}")
|
print(f"stderr: {stderr.decode()}")
|
||||||
return process.returncode
|
return process.returncode
|
||||||
|
|
||||||
test_mode = os.getenv('TEST_MODE', 'False') == 'True'
|
test_mode = os.getenv('test_mode', 'false').lower() == 'true'
|
||||||
|
watch_mode = os.getenv('DJANGO_WATCH', 'false').lower() == 'true'
|
||||||
|
|
||||||
commands = [
|
commands = [
|
||||||
["python", "manage.py", "collectstatic", "--noinput"],
|
["python", "manage.py", "collectstatic", "--noinput"],
|
||||||
["python", "manage.py", "flush", "--noinput"],
|
#["python", "manage.py", "flush", "--noinput"],
|
||||||
["python", "manage.py", "makemigrations", "Common", "--noinput"],
|
# ["python", "manage.py", "makemigrations", "Common", "--noinput"],
|
||||||
["python", "manage.py", "makemigrations", "Establishment", "--noinput"],
|
# ["python", "manage.py", "makemigrations", "Establishment", "--noinput"],
|
||||||
["python", "manage.py", "makemigrations", "Settings", "--noinput"],
|
# ["python", "manage.py", "makemigrations", "Settings", "--noinput"],
|
||||||
["python", "manage.py", "makemigrations", "Subscriptions", "--noinput"],
|
# ["python", "manage.py", "makemigrations", "Subscriptions", "--noinput"],
|
||||||
["python", "manage.py", "makemigrations", "Planning", "--noinput"],
|
# ["python", "manage.py", "makemigrations", "Planning", "--noinput"],
|
||||||
["python", "manage.py", "makemigrations", "GestionNotification", "--noinput"],
|
# ["python", "manage.py", "makemigrations", "GestionNotification", "--noinput"],
|
||||||
["python", "manage.py", "makemigrations", "GestionEmail", "--noinput"],
|
# ["python", "manage.py", "makemigrations", "GestionEmail", "--noinput"],
|
||||||
["python", "manage.py", "makemigrations", "GestionMessagerie", "--noinput"],
|
# ["python", "manage.py", "makemigrations", "GestionMessagerie", "--noinput"],
|
||||||
["python", "manage.py", "makemigrations", "Auth", "--noinput"],
|
# ["python", "manage.py", "makemigrations", "Auth", "--noinput"],
|
||||||
["python", "manage.py", "makemigrations", "School", "--noinput"],
|
# ["python", "manage.py", "makemigrations", "School", "--noinput"],
|
||||||
["python", "manage.py", "migrate", "--noinput"]
|
["python", "manage.py", "migrate", "--noinput"]
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -32,23 +34,55 @@ test_commands = [
|
|||||||
["python", "manage.py", "init_mock_datas"]
|
["python", "manage.py", "init_mock_datas"]
|
||||||
]
|
]
|
||||||
|
|
||||||
for command in commands:
|
def run_daphne():
|
||||||
if run_command(command) != 0:
|
try:
|
||||||
exit(1)
|
result = subprocess.run([
|
||||||
|
"daphne", "-b", "0.0.0.0", "-p", "8080", "N3wtSchool.asgi:application"
|
||||||
|
])
|
||||||
|
return result.returncode
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("Arrêt de Daphne (KeyboardInterrupt)")
|
||||||
|
return 0
|
||||||
|
|
||||||
#if test_mode:
|
if __name__ == "__main__":
|
||||||
# for test_command in test_commands:
|
for command in commands:
|
||||||
# if run_command(test_command) != 0:
|
if run_command(command) != 0:
|
||||||
# exit(1)
|
exit(1)
|
||||||
|
|
||||||
# Lancer les processus en parallèle
|
#if test_mode:
|
||||||
|
# for test_command in test_commands:
|
||||||
|
# if run_command(test_command) != 0:
|
||||||
|
# exit(1)
|
||||||
|
|
||||||
processes = [
|
if watch_mode:
|
||||||
subprocess.Popen(["daphne", "-b", "0.0.0.0", "-p", "8080", "N3wtSchool.asgi:application"]),
|
celery_worker = subprocess.Popen(["celery", "-A", "N3wtSchool", "worker", "--loglevel=info"])
|
||||||
subprocess.Popen(["celery", "-A", "N3wtSchool", "worker", "--loglevel=info"]),
|
celery_beat = subprocess.Popen(["celery", "-A", "N3wtSchool", "beat", "--loglevel=info", "--scheduler", "django_celery_beat.schedulers:DatabaseScheduler"])
|
||||||
subprocess.Popen(["celery", "-A", "N3wtSchool", "beat", "--loglevel=info", "--scheduler", "django_celery_beat.schedulers:DatabaseScheduler"])
|
try:
|
||||||
]
|
run_process(
|
||||||
|
'.',
|
||||||
# Attendre la fin des processus
|
target=run_daphne
|
||||||
for process in processes:
|
)
|
||||||
process.wait()
|
except KeyboardInterrupt:
|
||||||
|
print("Arrêt demandé (KeyboardInterrupt)")
|
||||||
|
finally:
|
||||||
|
celery_worker.terminate()
|
||||||
|
celery_beat.terminate()
|
||||||
|
celery_worker.wait()
|
||||||
|
celery_beat.wait()
|
||||||
|
else:
|
||||||
|
processes = [
|
||||||
|
subprocess.Popen([
|
||||||
|
"daphne", "-b", "0.0.0.0", "-p", "8080", "N3wtSchool.asgi:application"
|
||||||
|
]),
|
||||||
|
subprocess.Popen(["celery", "-A", "N3wtSchool", "worker", "--loglevel=info"]),
|
||||||
|
subprocess.Popen(["celery", "-A", "N3wtSchool", "beat", "--loglevel=info", "--scheduler", "django_celery_beat.schedulers:DatabaseScheduler"])
|
||||||
|
]
|
||||||
|
try:
|
||||||
|
for process in processes:
|
||||||
|
process.wait()
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("Arrêt demandé (KeyboardInterrupt)")
|
||||||
|
for process in processes:
|
||||||
|
process.terminate()
|
||||||
|
for process in processes:
|
||||||
|
process.wait()
|
||||||
@ -1,8 +1,42 @@
|
|||||||
<svg width="38" height="120" viewBox="0 0 38 120" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="565" height="609" viewBox="0 0 565 609" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path d="M38 79.0995V40L29.9458 49.0478V72.3137L1.27073 99.134C-2.59496 103.012 3.20408 109.151 7.39397 106.243L38 79.0995Z" fill="#D9006D" fill-opacity="0.8"/>
|
<path d="M170.999 374.501C167.799 383.301 172.332 402.834 174.999 411.501C189.998 452.002 218.216 463.317 223 464C237 466 238.5 464 246.5 459.5C248.1 451.9 238.333 445 234 443C224 439.8 213.833 431 210 427C246.4 422.6 255.833 416.167 256 413.5C246.8 395.9 213.5 396.834 198 399.501C181.2 372.301 172.999 371.501 170.999 374.501Z" fill="#003625"/>
|
||||||
<path d="M0 79.0995V40L8.05422 49.0478V72.3137L36.7293 99.134C40.595 103.012 34.7959 109.151 30.606 106.243L0 79.0995Z" fill="#038ECE" fill-opacity="0.8"/>
|
<path d="M166 84.5L208 94.5L206 112.5C203 121 196.9 138.5 196.5 140.5C196.1 142.5 174 145.333 163 146.5L112 166.5C96.3333 177.833 64.7 200.8 63.5 202C62.3 203.2 42.3333 237.5 32.5 254.5L23 320L21 389.5C29.8333 416.333 47.7 470.6 48.5 473C49.5 476 66.9995 505.5 65.9995 505.5C65.1995 505.5 92.6662 529.5 106.5 541.5C116.666 549.667 137.4 566.2 139 567C140.6 567.8 156.333 574.667 164 578L221 584.5H247.5L291.5 572.5L341 546L388 490.5L397 475L411.5 447.5L414.5 425L406.5 400L386.5 377.5L366 371L367.5 381L375 402L377 419L367.5 440.5L360.5 456.5L337 475C327.166 480.167 307.5 489.9 307.5 487.5C307.5 484.5 269 494 264 494.5C259 495 231 493 227.5 490.5C224.7 488.5 183 471.667 162.5 463.5L104 282L199 314C219.5 315.5 261.2 318.5 264 318.5C266.8 318.5 275.5 315.5 279.5 314L276 307.5L272.5 299L264 289.5C266.833 288.167 273.9 285.5 279.5 285.5C285.1 285.5 281.833 279.5 279.5 276.5L272.5 266.5L236 261L227.5 248.5V227L264 185L279.5 157.5L288.5 142.5L295 130L307.5 120.5L328 112.5L392.5 103.5L457.5 87.5H476L481 84.5V73L476 60.5L470.5 50C467 47.6667 459.5 42.4 457.5 40C455.5 37.6 449.666 33 447 31H435.5L425.5 19.5L416 16L406 11H392.5L377 16H366.5L314 19.5L264 25.5L199 55L166 84.5Z" fill="#10B981"/>
|
||||||
<path d="M19 83L12 89.6585L19 96L26 89.6585L19 83Z" fill="#011922"/>
|
<path d="M215 304C195.4 294 147.167 248.5 125.5 227L119.5 230.5C116.5 235.333 110.5 245.3 110.5 246.5C110.5 247.7 109.833 257 109.5 261.5C112 266.167 117.5 276.4 119.5 280C122 284.5 131.5 284.5 142 290C152.5 295.5 171 297 173.5 298C176 299 192 311 194.5 313C196.5 314.6 200.667 313.667 202.5 313L215 304Z" fill="#059669"/>
|
||||||
<path d="M14 28.8736H24V72.1264H14V28.8736Z" fill="#001821"/>
|
<path d="M33.5 381.5C32 386.167 29 396.9 29 402.5V415V427.5C41.1667 449.167 65.9 493 67.5 495C69.5 497.5 84 521 87.5 524.5C91 528 105 542.5 107.5 546C109.5 548.8 124.667 560.833 132 566.5L184.5 575C281.7 595 343 545 361.5 517.5L359.5 511C275.1 571.8 199.333 562.333 172 550C178.4 550 184.333 546.333 186.5 544.5C83.3 509.3 41.5 421.167 33.5 381.5Z" fill="#059669"/>
|
||||||
<path d="M24 28.8736C24 31.5652 21.7614 33.7471 19 33.7471C16.2386 33.7471 14 31.5652 14 28.8736C14 26.182 16.2386 24 19 24C21.7614 24 24 26.182 24 28.8736Z" fill="#001821"/>
|
<path d="M346 184C315.2 203.2 291.5 245.333 283.5 264C283.5 259.599 239.5 254.166 217.5 252C232.064 267.745 261.219 267.834 273.898 267.992C273.587 267.653 273.741 267.591 274.5 268C274.304 267.997 274.103 267.995 273.898 267.992C274.473 268.621 276.642 270.201 279.5 271.5C283.9 273.5 279.5 278 274.5 276C272.1 276 265.5 279.333 262.5 281H258C250 283.8 229 272.167 219.5 266C167.1 228.8 137.333 222.5 129 224L145.5 214C149.5 210.4 184.5 230.5 201.5 241C205.9 203.4 251 177.333 273 169C251.4 135 283 116.167 301.5 111C308.7 107.4 404.5 96.1665 451.5 90.9997C464.3 86.5997 465.5 92.833 464.5 96.4997C444.9 129.7 377.334 168.666 346 184Z" fill="white"/>
|
||||||
<path d="M24 72.1264C24 74.818 21.7614 77 19 77C16.2386 77 14 74.818 14 72.1264C14 69.4348 16.2386 67.2529 19 67.2529C21.7614 67.2529 24 69.4348 24 72.1264Z" fill="#001821"/>
|
<path d="M299 33.9991C400.2 9.99913 455.167 48.3325 470 70.4991C471 68.3328 473.1 63.7 473.5 62.5C474 61 467 48.5 466 47C465 45.5 459 43 457.5 42C456 41 449.5 37.5 448 36C446.5 34.5 437 32.4987 436 32.4987C435 32.4987 430 28.4987 428.5 28.4987C427 28.4987 424 22.5 422.5 20.9987C421 19.4974 411.5 15 410.5 14C409.5 13 391.5 13 389 13C387 13 381.167 15.9991 378.5 17.4987C374.833 17.9987 366.9 18.6987 364.5 17.4987C361.5 15.9987 334 17.4987 331.5 17.4987C329 17.4987 300 20.9987 299 20.9987C298 20.9987 273 27.4987 270 28.4987C267.6 29.2987 249.667 31.4987 241 32.4987L188.5 65.9987V70.4991C223.3 46.4995 254 40.499 265 40.4987C250.2 48.8987 242.833 65.332 241 72.4987C255 53.2987 285.5 38.8323 299 33.9991Z" fill="white"/>
|
||||||
|
<path d="M273 295.001C268.2 296.601 269.667 300.667 271 302.501C259.001 296.9 259 286.168 260.5 281.502C273.5 276.502 274.5 275.501 275 272.502C275.4 270.102 257.833 270.502 249 271.002C239.8 271.802 224.5 259.335 218 253.002C224 253.002 245.833 256.001 256 257.5C264.8 257.5 278.333 260.833 284 262.5C303.2 218.9 334.667 191 348 182.5C447.6 126.099 466.833 98.9995 464 92.5C466 88.5 454.833 90.8333 449 92.5C379 113.7 318.167 118 296.5 117.5L318.5 107C338.899 107 428.333 82.3333 470.5 70C469.7 44.8 439.834 32.5 425.001 29.5C407.001 11.5 385.834 16 377.501 20.5C341.901 19.3 323.334 21 318.5 22C226.1 32.8 185.333 67.1667 176.5 83C206.1 80.2 220.167 85.8333 223.5 89C213.5 90.6 209.667 99.3333 209 103.5C206.2 108.3 201.5 129.5 199.5 139.5L188.501 145.5C96.9026 141.9 49.001 215.668 36.5 253.002C15.7 329.003 27.8333 401.334 36.5 428C74.9 542.799 158.167 574.5 195 576C276.6 586.8 339.333 541.167 360.5 517L358.5 510C389.7 488.4 403.167 450 406 433.5C410.799 391.9 382.666 377.833 368 376C397.999 417.999 367.834 457.833 349.001 472.5C289.226 524.9 200.428 504 163.5 487C43.5 421 68.1667 314.834 95.5 270.002C95.9 203.203 141 199.834 163.5 206.5C140.7 210.9 125 227 120 234.5C97.2 262.5 125.833 278.5 143 283.001C145 281.8 167.5 290.5 178.5 295.001C179.7 293.001 193 304.501 199.5 310.501L202 309.501C207.6 295.503 218.667 301.334 223.5 306L246 348.5C248.8 360.1 242.167 362 238.5 361.5C225.3 361.9 210.667 337.667 205 325.5L206.5 350C207.7 360 202.333 363.167 199.5 363.5C185.9 363.5 180.833 340.167 180 328.5C182 313.7 173.167 311.667 168.5 312.5C156.1 322.899 153 357.5 153 373.5C157 445.899 206.667 474.333 231 479.5C311.8 500.7 354.334 451.333 365.501 424C369.101 377.2 332.334 364.167 313.5 363.5C329.899 351.1 348.667 350.667 356.001 352C422.401 360 433.001 425.667 430.001 457.5C409.201 575.899 293.667 607.833 238.5 609C96.1 601.4 31.1676 490.5 16.5015 436C-33.0985 282.8 46.1681 182.833 92.0015 152C124.001 130.4 159.668 126.333 173.501 127C185.501 127.8 188.501 122 188.501 119C192.901 106.2 165.668 105.667 151.501 107C134.701 109 134.501 98.1667 136.501 92.5C202.101 14.1 314.501 5.49998 362.501 11C404.501 -15 432.667 12.5 441.501 29.5C481.101 33.1 489.667 60 489.001 73C486.201 106.2 465.834 129.5 456.001 137C449.201 145 419.167 165.333 405.001 174.5L344.001 209L344.501 211C356.501 219 371.501 242.334 377.501 253.002C386.301 251.402 402.167 254.335 409.001 256.002C419.001 260.002 427.167 267.002 430.001 270.002V275.002C429.601 276.202 426.501 277.168 425.001 277.502C422.601 278.702 407.334 273.668 400.001 271.002H398.001V272.502L409.001 281.502C415.801 287.102 420.501 295.835 422.001 299.501C424.001 305.102 422.167 308.501 421.001 309.501C418.201 312.702 414.501 311.501 413.001 310.501L386.001 284.502L385.001 284.002L392.001 303.001C394.001 307.001 394.167 315.335 394.001 319.001C391.201 331.001 384.167 327.001 381.001 323.501L365.501 286.501L365.001 310.501C363.801 318.901 358.501 322.335 356.001 323.001C351.201 323.001 349.334 320.335 349.001 319.001C347.401 316.202 346.667 301.168 346.501 294.001C344.101 286.801 334.834 281.001 330.501 279.001C322.501 274.201 302.167 274.667 293.001 275.501C293.001 279.901 292.334 282.334 292.001 283.001L273 295.001Z" fill="#003625"/>
|
||||||
|
<path d="M255 296.5C239.8 285.3 233.667 289.5 232.5 293C226.1 301.8 235.167 312.667 240.5 317C251.3 327 272.667 334.167 282 336.5C295.6 337.3 296 327.833 294.5 323C290.1 316.2 266.333 302.5 255 296.5Z" fill="#003625"/>
|
||||||
|
<path d="M283.499 263.5C278.299 258.7 255.999 254.5 245.499 253C241.899 249.401 294.666 208.834 321.499 189L303.499 185.5C331.899 183.5 362.333 153.334 373.999 138.5C417.499 142 460.999 87.5004 462.999 90.5004C464.599 92.9004 464.333 95.1671 463.999 96.0004C448.799 127.2 382.666 165 351.499 180C321.499 194.8 293.666 241.834 283.499 263.5Z" fill="#EEEDE8"/>
|
||||||
|
<path d="M201.5 147L198.5 139C191.7 143.8 184.5 143.833 182 144.5C141.6 139.3 104.5 164.5 91 176.5C37.8 218.1 21.6667 289.833 21 320.5C36.6 234.1 88.8333 186.5 112 173.5C135.2 156.3 171.667 153 187 153.5C189.8 153.5 197.833 149.167 201.5 147Z" fill="white"/>
|
||||||
|
<path d="M331.248 39C354.848 39.4 364.414 59.5 366.248 69.5C364.081 68.8333 359.848 67.6 360.248 68C360.648 68.4 359.415 72.1667 358.748 74C352.748 89.2 336.582 93 329.248 93C311.248 92.2 303.081 77.3333 301.248 70C298.448 46 320.081 39.3333 331.248 39Z" fill="#003625"/>
|
||||||
|
<path d="M310.749 63.4997C316.749 48.2997 331.582 50.1664 338.249 52.9997C325.849 55.7994 323.748 58.4998 324.248 59.5C329.448 70.3 323.748 73.3333 320.249 73.5C311.849 73.5 310.415 66.8331 310.749 63.4997Z" fill="white"/>
|
||||||
|
<circle cx="226" cy="107" r="10" fill="#003625"/>
|
||||||
|
<circle cx="226" cy="107" r="10" fill="#003625"/>
|
||||||
|
<circle cx="432.5" cy="60.5" r="7.5" fill="#003625"/>
|
||||||
|
<circle cx="432.5" cy="60.5" r="7.5" fill="#003625"/>
|
||||||
|
<circle cx="209" cy="147" r="10" fill="#003625"/>
|
||||||
|
<circle cx="209" cy="147" r="10" fill="#003625"/>
|
||||||
|
<circle cx="214.5" cy="126.5" r="4.5" fill="#003625"/>
|
||||||
|
<circle cx="214.5" cy="126.5" r="4.5" fill="#003625"/>
|
||||||
|
<circle cx="220.5" cy="77.5" r="4.5" fill="#003625"/>
|
||||||
|
<circle cx="220.5" cy="77.5" r="4.5" fill="#003625"/>
|
||||||
|
<circle cx="228.5" cy="59.5" r="4.5" fill="#003625"/>
|
||||||
|
<circle cx="228.5" cy="59.5" r="4.5" fill="#003625"/>
|
||||||
|
<ellipse cx="213" cy="63.5" rx="3" ry="3.5" fill="#003625"/>
|
||||||
|
<ellipse cx="213" cy="63.5" rx="3" ry="3.5" fill="#003625"/>
|
||||||
|
<ellipse cx="198.5" cy="74.5" rx="5.5" ry="3.5" fill="#003625"/>
|
||||||
|
<ellipse cx="198.5" cy="74.5" rx="5.5" ry="3.5" fill="#003625"/>
|
||||||
|
<circle cx="189.5" cy="161.5" r="3.5" fill="#003625"/>
|
||||||
|
<circle cx="189.5" cy="161.5" r="3.5" fill="#003625"/>
|
||||||
|
<circle cx="169.5" cy="159.5" r="3.5" fill="#003625"/>
|
||||||
|
<circle cx="169.5" cy="159.5" r="3.5" fill="#003625"/>
|
||||||
|
<circle cx="189.5" cy="161.5" r="4.5" fill="#003625"/>
|
||||||
|
<circle cx="189.5" cy="161.5" r="4.5" fill="#003625"/>
|
||||||
|
<circle cx="133.5" cy="173.5" r="3.5" fill="#003625"/>
|
||||||
|
<circle cx="133.5" cy="173.5" r="3.5" fill="#003625"/>
|
||||||
|
<circle cx="117.5" cy="187.5" r="3.5" fill="#003625"/>
|
||||||
|
<circle cx="117.5" cy="187.5" r="3.5" fill="#003625"/>
|
||||||
|
<circle cx="102.5" cy="186.5" r="3.5" fill="#003625"/>
|
||||||
|
<circle cx="102.5" cy="186.5" r="3.5" fill="#003625"/>
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 903 B After Width: | Height: | Size: 9.6 KiB |
267
CHANGELOG.md
267
CHANGELOG.md
@ -0,0 +1,267 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
Toutes les modifications notables apportées à ce projet seront documentées dans ce fichier.
|
||||||
|
|
||||||
|
### [0.0.3](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/compare/0.0.2...0.0.3) (2025-06-01)
|
||||||
|
|
||||||
|
|
||||||
|
### Corrections de bugs
|
||||||
|
|
||||||
|
* Ajout d'un '/' en fin d'URL ([67cea2f](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/67cea2f1c6edae8eed5e024c79b1e19d08788d4c))
|
||||||
|
|
||||||
|
### 0.0.2 (2025-06-01)
|
||||||
|
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
|
||||||
|
* mise à jour de la doc swagger ([11fc446](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/11fc446b904cc64d63154ad5c6711a8296a7fc51))
|
||||||
|
|
||||||
|
|
||||||
|
### Refactorisations
|
||||||
|
|
||||||
|
* "registerFilesTemplates" -> "registrerFileTemplate" ([83f4d67](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/83f4d67a6fc3f786803343957b276f8419f3058d))
|
||||||
|
* adaptation mobile ([4b8f85e](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/4b8f85e68dc95585d96a4cbad219ad068cbc8acf))
|
||||||
|
* Affichage des notifications dans la partie "Users" ([af30ae3](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/af30ae33b5660c55fa6824498f4325aab3de3c5a))
|
||||||
|
* Affichage des notifications dans la partie "Users" ([e509625](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/e5096258110faac37b9457705dd1b51bc231983f))
|
||||||
|
* Augmentation du nombre de données ([95c154a](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/95c154a4a2d746c4350887bb697af142152ed8d7))
|
||||||
|
* changement de la philosophie de logging ([c7723ec](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/c7723eceee650de86eea3263d44d374ad9844282))
|
||||||
|
* Changement des IconTextInput en TextInput, modification du composant step ([a248898](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/a248898203286213c3447333611e1a9981dff64a))
|
||||||
|
* Composant *InscriptionForm* ([56e2762](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/56e27628f897920659a6ce186539ddec7e94a05a))
|
||||||
|
* Creation d'un provider et d'un systeme de middleware ([5088479](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/508847940c8c35fd982ab935f4d69371869eed5a))
|
||||||
|
* Création de composants et uniformisation des modales ([#2](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/issues/2)) ([d51778b](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/d51778ba54e95283aa6ad7821fda673813c7c7a0))
|
||||||
|
* Création de nouveaux composants / update formulaire de ([7acae47](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/7acae479da658707fb3e073ebcdfee023d18500b)), closes [#2](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/issues/2)
|
||||||
|
* Deplacement du JWT dans le back ([eb89a32](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/eb89a324abbdf69091e5c78530ec62f2c2ccbcd1))
|
||||||
|
* Document Ecole/Parent ([7564865](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/7564865d8f414fbefa0731c4ca472a100efb6036))
|
||||||
|
* gestion des erreurs ([f3490a4](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/f3490a4e9584b959450ca45c8e74e430396425b3))
|
||||||
|
* Injection des env var dans le frontend ([aae5d27](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/aae5d27d8c556c5687951f3a04e01d42f69f3085))
|
||||||
|
* je suis une merde ([c4d4542](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/c4d45426b520a498f409be8617c7936224195290))
|
||||||
|
* Mise à jour de la doc swagger / URL ([4c95b6a](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/4c95b6a83f15a9989fac5f69a9386664d25ec9f6))
|
||||||
|
* Modification de l'url de l'api Auth ([9bf9c5f](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/9bf9c5f62df1a6482ba27b897da498592b57e04f))
|
||||||
|
* Modification de la construction docker ([2d128aa](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/2d128aaf30e60813c0c5caa244a93ff46e3985f3))
|
||||||
|
* Partie "School" ([58fe509](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/58fe509734a3b5dc6e0b5c6aa3fd713fd4dc821e))
|
||||||
|
* Partie FRONT / School ([24352ef](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/24352efad304dee7418dc846681a4b38047431f6))
|
||||||
|
* Refactoring de la section ClassSection ([1a8ef26](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/1a8ef26f5883abe4855949a54aa50defb98c852d))
|
||||||
|
* refactoring du FRONT page subscribe ([427b6c7](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/427b6c758892469d07579159511e7ce1ceed20d0))
|
||||||
|
* Refactorisation du login et de admin/subscription ([41aa9d5](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/41aa9d55d388c0ddf189c7b9ab6057487f86484b))
|
||||||
|
* Remplacement de quelques popup par les notifications ([ce83e02](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/ce83e02f7b3e53ef2b859436432784d6eb69200d))
|
||||||
|
* Renommage du menu "Eleves" en "Inscriptions" ([692e845](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/692e8454bf9840ada3f8e052d7ef13cbf1b0d9c0))
|
||||||
|
* Revue de la modale permettant de créer un dossier ([cb3f909](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/cb3f909fa4e7a53148cd13cf190c13b0670d35de))
|
||||||
|
* Revue de la modale permettant de créer un dossier ([665625e](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/665625e0280683fef056e9c950fc6555d889643e))
|
||||||
|
* SpecialitySection + TeacherSection (en cours) ([72dd769](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/72dd7699d6bd61e17b4c3dc0098ca0989a94b2c8))
|
||||||
|
* Suppression des paramètres mail mot de passes des settings ([ec2630a](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/ec2630a6e40dedaaa8f41a04b44e5ec1f6b2a1e0))
|
||||||
|
* Traduction en anglais des modules "GestionInscription" et ([2b414b8](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/2b414b83913c2f0f81cf226b78577ad522443d7b))
|
||||||
|
* Transformation des requetes vers le back en action ajout des ([147a701](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/147a70135d2f10ac16961c098d85da0a1bcafb38))
|
||||||
|
* Utilisation d'une application "Common" pour tous les modèles ([e65e310](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/e65e31014d2b89afe1e5f077e8d4109f07d40d0b))
|
||||||
|
|
||||||
|
|
||||||
|
### Nouvelles fonctionnalités
|
||||||
|
|
||||||
|
* A la signature d'un document, on récupère l'URL du PDF [[#22](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/issues/22)] ([2ac4832](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/2ac48329851f91f6bb02a44e02ad5a90b4ae504c))
|
||||||
|
* Affichage d'icones dans le tableau des inscriptions dans la ([9559db5](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/9559db59eb418d233682217ef72f315bccc6fe1d))
|
||||||
|
* Ajout d'un composant permettant de visualiser les fichiers signés ([7f442b9](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/7f442b9cae008dab4f18438f9ee46be21ed037b0))
|
||||||
|
* Ajout d'un nouveau status avec envoi de mandat SEPA + envoi de ([4c2e2f8](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/4c2e2f87565dc6e2be501839c274a5aa6969a9ec))
|
||||||
|
* Ajout d'un nouvel état dans l'automatique lorsqu'un mandat SEPA ([545349c](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/545349c7db7d0f653f3ae06b10d441ef975b0cc0))
|
||||||
|
* Ajout d'une colonne dans le tableau des pièces jointes indiquant ([3c0806e](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/3c0806e26c116dbccd808bd8c8b170c5c4d9bc5b))
|
||||||
|
* Ajout d'une fonction de dissociation entre un responsable et un ([3bcc620](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/3bcc620ee103690a2ee5f79e6203aba880bda9b7))
|
||||||
|
* Ajout d'une fonction de logout ([c2bba1a](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/c2bba1abbfafbb7aca1bb07e8019d7fa244a808e))
|
||||||
|
* Ajout d'une fonction de logout ([0ef6a2b](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/0ef6a2b1192dbd3ecc59ce0e8cbba233ccc9c821))
|
||||||
|
* Ajout de l'emploi du temps sur la page parent ([78d96f8](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/78d96f82f91ed777073250b960eee8f326cccb43))
|
||||||
|
* Ajout de l'envoie de mail [[#17](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/issues/17)] ([99a882a](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/99a882a64acfd9340d6849edd1766de5173a2341))
|
||||||
|
* Ajout de l'option d'envoi automatique [[#1](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/issues/1)] ([a77dd8e](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/a77dd8ec64bd78ab9c42aad3f93a181e64719d06))
|
||||||
|
* Ajout de la configuration des tarifs de l'école [[#18](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/issues/18)] ([5a0e65b](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/5a0e65bb752a80781517394d7b2a673788f7595e))
|
||||||
|
* Ajout de la fratrie [[#27](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/issues/27)] ([4a382d5](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/4a382d523ccfd4cf8fa7e672e9315b86dbdbbb14))
|
||||||
|
* Ajout de la fratrie / Gestion des index de fratrie / Gestion des ([2ab1684](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/2ab1684791377804dd03c8467a94dbc1244e102f))
|
||||||
|
* Ajout de la gestion des fichier d'inscription [[#1](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/issues/1)] ([3c27133](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/3c27133cdb9943c5e20b81c03f9e2fa47077dbbb))
|
||||||
|
* Ajout de la photo pour le dossier de l'élève + correction ([5851341](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/5851341235998647a4142bdf1996ddc9db21762d))
|
||||||
|
* Ajout de la possibilité de supprimer une association ([c9350a7](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/c9350a796b65ea4eef0e38390ab9fb1d88196210))
|
||||||
|
* Ajout de la sélection des modes de paiements / refactoring de ([5a7661d](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/5a7661db93454b9a73b9f6bd46646c6135a0f203))
|
||||||
|
* Ajout des Bundles de fichiers [[#24](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/issues/24)] ([ffc6ce8](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/ffc6ce8de835e9caf547b6c4a893436aa93513ba))
|
||||||
|
* ajout des documents d'inscription [[#20](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/issues/20)] ([b8ef34a](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/b8ef34a04b14c8a8fb980fcd9255296ceb699ec6))
|
||||||
|
* Ajout des évenements à venir ([c03fa0b](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/c03fa0ba42d69918501beb5bb98637a449eb2da0))
|
||||||
|
* Ajout des frais d'inscription lors de la création d'un RF [[#18](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/issues/18)] ([ece23de](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/ece23deb19483c50d9999541a482e3378db19d23))
|
||||||
|
* Ajout des frais de scolarité dans le dossier d'inscription [[#18](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/issues/18)] ([0c2e0b9](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/0c2e0b92f43f223adc22db36ecad7fd864737a98))
|
||||||
|
* Ajout des modes de paiements + création d'une commande dans le ([0c5e3aa](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/0c5e3aa0988a16b6f9f8c0b411c2c1b443c972a7))
|
||||||
|
* Ajout des payementPlans dans le formulaire / ajout de la photo ([d37aed5](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/d37aed5f6496b8c8ca5519689dfc811d9626e09e))
|
||||||
|
* Ajout du logo de l'école ([6a0b90e](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/6a0b90e98fbcc707756ae7fbbff921e480f2c695))
|
||||||
|
* Ajout du logo N3wt dans les mails ([8a71fa1](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/8a71fa1830d0c0fb11467208bc98dc4f71598199))
|
||||||
|
* Ajout du suivi de version dans le footer du Front ([fb7fbaf](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/fb7fbaf8394ebf41e6f3f31897e6d009c537a481))
|
||||||
|
* Amélioration de la fiche élève pour y ajouter la fratrie et les ([256f995](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/256f995698e79572eb3d51ea60b96b6fad47d553))
|
||||||
|
* Amélioration du dashboard ([eb48523](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/eb48523f7d466698faa268b8b25e6f1ed90bdfd7))
|
||||||
|
* Amorçage de la gestion des absences [[#16](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/issues/16)] ([cb4fe74](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/cb4fe74a9e316a92c6b5e1d2550aaf2b1036a744))
|
||||||
|
* Aussi pour la table des parents tant qu'à faire ([a3182c0](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/a3182c0ba7c6ea9f99a4fe34a4a00079b4676d59))
|
||||||
|
* **backend:** Ajout du logger django [[#7](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/issues/7)] ([b8511f9](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/b8511f94b633b9bf5bd764b3706c53b74b3a6648))
|
||||||
|
* Bilan de compétence d'un élève [[#16](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/issues/16)] ([5760c89](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/5760c89105f38f4481e2cc6fa788bb0c39e8caa8))
|
||||||
|
* Champ de recherche de l'élève [[#16](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/issues/16)] ([eb7805e](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/eb7805e54e41f6eaefad81fea1616f0613365e8c))
|
||||||
|
* Configuration des compétences par cycle [[#16](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/issues/16)] ([4e5aab6](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/4e5aab6db74a8d1dfdfb4928f60ad47da52c89e8))
|
||||||
|
* Configuration et gestion du planning [[#2](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/issues/2)] ([830d9a4](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/830d9a48c003e1cca469b1cf4082305e16685181))
|
||||||
|
* Création d'un annuaire / mise à jour du subscribe ([6bd5704](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/6bd5704983282264bc50c73677495740f7d7e8a9))
|
||||||
|
* Création d'un profile selector [[#37](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/issues/37),[#38](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/issues/38)] ([89b01b7](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/89b01b79db884c393db29332b95f570e47d20ed1))
|
||||||
|
* création d'une tooltip pour les informations supplémentaires de ([9197615](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/91976157e44e319b23fd35fa89859164bab71202))
|
||||||
|
* création de 4 JSON de compétences en attendant de les mettre en ([69405c5](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/69405c577e7af3d07654fca96015d21f475e700d))
|
||||||
|
* Création de clones lors de la création de RF [[#22](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/issues/22)] ([d1a0067](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/d1a0067f7b7125e453ff6fc75efead881a7af37d))
|
||||||
|
* Création nouveau style / pagination profils annuaires ([760ee00](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/760ee0009e983776dfd500df7465ae66593dc85d))
|
||||||
|
* Dockerisation d'un serveur docuseal + initialisation d'un compte ([8897d52](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/8897d523dc23fd2d89a0ec66b5cc7fa15b69db5b))
|
||||||
|
* Envoie d'un mail de bienvue au directeur ([5be5f9f](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/5be5f9f70d4fcf56da29afb19187806ff2e6e428))
|
||||||
|
* Evolution des modèles pour intégrer un planning et du m2m ([85d4c00](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/85d4c007cb2091ae1911ca1998f1b830470b8310))
|
||||||
|
* Formulaire de création RF sur une seule pag ([76f9a7d](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/76f9a7dd14d7065f4add01718fda499fbb9183c7))
|
||||||
|
* Génération d'une page de suivi pédagogique + fix utilisation ([2a6b3bd](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/2a6b3bdf63ddc13509b66690ea5d76eac77d1090))
|
||||||
|
* Génération du bilan de compétence en PDF [[#16](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/issues/16)] ([0fe6c76](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/0fe6c761892097d043902f4f051b9fdb5fef29d0))
|
||||||
|
* Gestion de la création d'un nouveau guardian, de l'association ([fb73f9e](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/fb73f9e9a86430d7498aa8a10e5abc46325b7b2c))
|
||||||
|
* Gestion de la mise à jour des profiles / roles / lors de l'édition ([dfd707d](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/dfd707d7a0c7f4514f5583f07803d20e3c2d6bd7))
|
||||||
|
* Gestion de la sauvegarde du fichier d'inscription / affichage du ([d6edf25](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/d6edf250bbc1cc1a9862e26174bc24ca4f9ee4c1))
|
||||||
|
* Gestion de la validation du dossier d'inscription ([b23264c](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/b23264c0d4008a4317c009a73ae11f57ee6917e2))
|
||||||
|
* Gestion des absences du jour [[#16](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/issues/16)] ([030d19d](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/030d19d411af8f0d87a3cb72cb401d9dd5fa96ce))
|
||||||
|
* Gestion des documents nécessitant des signatures électroniques et ([e3879f5](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/e3879f516b81b7e4b784049668b2507f12e8155f)), closes [#22](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/issues/22)
|
||||||
|
* Gestion des documents parent ([59aee80](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/59aee80c2e7592a7cdb119d1d30a5ad2c8bb20b0))
|
||||||
|
* Gestion des documents signés durant l'inscription / possibilité de ([905b95f](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/905b95f3a364f0d1ce8348d086870045d942bf92))
|
||||||
|
* gestion des no data dans les table [[#33](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/issues/33)] ([2888f8d](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/2888f8dcce8d593df8f81a635eaac94af4603829))
|
||||||
|
* Gestion des pièces à fournir par les parents (configuration école) ([a65bd47](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/a65bd47905cc33c44416c1def0413579b96d820d))
|
||||||
|
* Gestion des profils ADMIN/ECOLE (création des enseignants) ([e0bfd3e](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/e0bfd3e11579c512aa6ad63c73e00e40be4eaf06))
|
||||||
|
* Gestion des profils des enseignants / Visualisation d'une classe [[#4](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/issues/4)] ([81d1dfa](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/81d1dfa9a70d0cd8d80e7d951a74c9355bba5238))
|
||||||
|
* Gestion des rattachements de Guardian à des RF déjà existants ([7d1b9c5](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/7d1b9c5657439d2fff287f60b9aba79a5dfdf089))
|
||||||
|
* Gestion du planning [3] ([58144ba](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/58144ba0d0f1b53e9313f4cd4d3fbc3e6bfdd274))
|
||||||
|
* Gestion multi-profil multi-école ([1617829](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/16178296ec4dd4843be26b6e09b9c0f080df7ee4))
|
||||||
|
* Harmonisation des fees / ajout de type de réduction / mise à jour ([5462306](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/5462306a6020493cf747ea3bb8edb3240c36286f)), closes [#18](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/issues/18)
|
||||||
|
* Merge remote-tracking branch 'origin/WIP_style' into develop ([f887ae1](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/f887ae18862b740fa904d8ca04a3932eec455908))
|
||||||
|
* Messagerie WIP [[#17](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/issues/17)] ([23a593d](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/23a593dbc77b6f544a17de5a451ff60316f50292))
|
||||||
|
* Mise à jour des Dockerfile préparation d'un environnement de démo [[#12](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/issues/12)] ([32a77c7](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/32a77c780abe8c0aa9846843ac81d13e4b8cf73a))
|
||||||
|
* Mise à jour des Teacher ([173ac47](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/173ac47fb26ba2f101802571621fc4112adb1a9f))
|
||||||
|
* Mise à jour du modèle (possibilité d'associer une réduciton à un ([8d1a41e](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/8d1a41e2693c3704b68e8d75bd32c4a89a6389e5)), closes [#18](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/issues/18)
|
||||||
|
* mise en place de la messagerie [[#17](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/issues/17)] ([d37145b](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/d37145b73e2012f21a329ee97a565189233ca0f8))
|
||||||
|
* Mise en place des actions pour chaque state du RF, possibilité ([8fc9478](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/8fc947878665ca04e1697fa6df140e0d80c5a672))
|
||||||
|
* Mise en place des paiements en plusieurs fois - partie BACK [[#25](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/issues/25)] ([274db24](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/274db249aa25f2a0281638c318a68cf88a721a45))
|
||||||
|
* Mise en place des paiements en plusieurs fois (partie BACK) [[#25](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/issues/25)] ([23203c0](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/23203c0397f6247d32462cceca33d964898223a9))
|
||||||
|
* Mise en place du Backend-messagerie [[#17](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/issues/17)] ([c6bc0d0](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/c6bc0d0b515a5be7b0bf930ff628a5e9b5ebbb33))
|
||||||
|
* Nommage des templates / Intégration dans formulaire d'inscription ([eb81bbb](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/eb81bbba9265b9f3a71e500737436ee5301b7a5e)), closes [#22](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/issues/22)
|
||||||
|
* Ordonnancement de l'inscription sur plusieurs pages + contrôle des ([daad12c](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/daad12cf40ce3b628581892f9a894a0841baa5e3))
|
||||||
|
* Oubli fichier [[#16](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/issues/16)] ([d7fca9e](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/d7fca9e942412a8d2fe379f38052f3b41ed9c0f9))
|
||||||
|
* passage des mail au format HTML ([b97cf6e](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/b97cf6e02b92ba6750662bbf9e9c3af6ad19ab38))
|
||||||
|
* Passage par une variable d'environnement pour les CORS et CSRF ([f9e870e](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/f9e870e11fe3041be53f4c357427d8060f50199f))
|
||||||
|
* Peuplement de la BDD avec les JSON d'entrée [[#16](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/issues/16)] ([c6d7528](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/c6d75281a1fab0d9dc27d4da80f91c6fffb1bc0e))
|
||||||
|
* planning events ([c9b0f0d](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/c9b0f0d77a5ec61a239deb71959738f3b0e82d37))
|
||||||
|
* Pre cablage du dashboard [#] ([1911f79](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/1911f79f4578f8bc3b455308182c46d2d59e5580))
|
||||||
|
* Préparation de la gestion des compétences en énumérant les élèves ([1c75927](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/1c75927bbab497cfc86fc3a9aea11d436318be69)), closes [#16](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/issues/16)
|
||||||
|
* Preparation des modèles Settings pour l'enregistrement SMTP [[#17](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/issues/17)] ([eda6f58](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/eda6f587fb21bf041784209228518c8a6f03b1b5))
|
||||||
|
* preparation du dockerfile pour le frontend [[#13](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/issues/13)] ([9716373](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/9716373fa2d754177d4e71082b9079b71daab971))
|
||||||
|
* Rattachement d'un dossier de compétences à une période scolaire ([7de839e](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/7de839ee5c9b09f7874575bdaf57436ec11b293f)), closes [#16](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/issues/16)
|
||||||
|
* Refactoring de la fonction de création de profil sur guardian côté ([753a8d6](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/753a8d647ec3e45c8aabecba6d38b1a19741e0c0))
|
||||||
|
* Sauvegarde des compétences d'un élève [[#16](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/issues/16)] ([0513603](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/05136035ab1d811c904e35d99ddb884c68b7fd74))
|
||||||
|
* Sauvegarde des fichiers migration ([017c029](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/017c0290dd1fab8afa3a05541e57a321733ff5c9))
|
||||||
|
* Signatures électroniques docuseal [[#22](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/issues/22)] ([c8c8941](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/c8c8941ec875b541cfb55c3504a0e951f36163ef))
|
||||||
|
* Sortie des calculs des montants totaux de la partie configuration + revue du rendu [[#18](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/issues/18)] ([799e1c6](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/799e1c6717fceec4b29edfbdd0af52268b7e8fce))
|
||||||
|
* Suite de la gestion des sessions ([8ea68bb](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/8ea68bbad0646d99209d1821a2b71364630005b3))
|
||||||
|
* Suppression de l'ancienne POPUP de RF ([5927e48](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/5927e48e6544e8819a29766562107834d44e7a5d))
|
||||||
|
* Suppression des localStorage ([023b46e](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/023b46e16e8da56971a8c55c0930e6ab4fbf53ec))
|
||||||
|
* Suppression des templates docuseal [[#22](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/issues/22)] ([081dc06](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/081dc060014ee15ca6881fc83b779679a271326d))
|
||||||
|
* Upload du SEPA par les parents / Création d'un composant header ([8417d3e](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/8417d3eb141b116e2e9f8c6038831ce1bbe30e2a))
|
||||||
|
* Utilisation d'une clef API Docuseal par établissement ([23ab7d0](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/23ab7d04ef0c940b7008e8bc7d4b43b373d16d40))
|
||||||
|
* Utilisation de l'établissement en variable de session / gestion de ([f2ad1de](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/f2ad1de5a4215395f3aa7a0e04ac2eb3edc5ec51))
|
||||||
|
* Utilisation des nouvelles alertes dans la page admin de la gestion ([67193a8](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/67193a8b3601784f37563094f3fdede943523b53))
|
||||||
|
* Validation du dossier d'inscription en affectant l'élève à une ([0f49236](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/0f49236965f575f6af17837b9860fa4481227785))
|
||||||
|
|
||||||
|
|
||||||
|
### Corrections de bugs
|
||||||
|
|
||||||
|
* correction des redirections vers la login page ([2e0fe86](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/2e0fe86c71e9f02e8ee77ccbd80533a63a31ef63))
|
||||||
|
* Ajout d'un champ is_required pour les documents parents facultatifs ([5866427](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/5866427544e24c8e79cb773d38bda683f63f4531))
|
||||||
|
* Ajout d'un message de confirmation lors de la suppression d'un ([9248480](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/92484804f6483eab401612315b5513cc78e6a726))
|
||||||
|
* ajout de credential include dans get CSRF ([c161fa7](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/c161fa7e7568437ba501a565ad53192b9cb3b6f3))
|
||||||
|
* Ajout de l'établissement dans la requête KPI récupérant les ([ada2a44](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/ada2a44c3ec9ba45462bd7e78984dfa38008e231))
|
||||||
|
* Ajout des niveaux scolaires dans le back [[#27](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/issues/27)] ([05542df](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/05542dfc40649fd194ee551f0298f1535753f219))
|
||||||
|
* ajout des urls prod et demo ([043d93d](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/043d93dcc476e5eb3962fdbe0f6a81b937122647))
|
||||||
|
* Ajout du % ou € en mode édition de réduction ([f2628bb](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/f2628bb45a14da42d014e42b1521820ffeedfb33))
|
||||||
|
* Ajout du controle sur le format des dates ([e538ac3](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/e538ac3d56294d4e647a38d730168ea567c76f04))
|
||||||
|
* Ajout du mode Visu ([e1c6073](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/e1c607308c12cf75695e9d4593dc27ebe74e6a4f))
|
||||||
|
* ajustement du handlePhoneChange [[#41](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/issues/41)] ([31fdc61](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/31fdc612b10843ce694b55696f67bd2a80d56769))
|
||||||
|
* Application des périodes à un studentCompetency lors de la création ([d65b171](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/d65b171da8a310acca15936a39e44239763c88b9))
|
||||||
|
* application des recommandations linter es pour générer un build de prod ([d1aa8b5](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/d1aa8b54fb71bb946e95a19105f51f7f29c75fda))
|
||||||
|
* Application du formattage sur les fichiers modifiés ([001a5bc](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/001a5bc83c0bf54061b2b04967da3fc11e2cd8dc))
|
||||||
|
* boucle inifinie dans UseEffect ([f3c4284](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/f3c428477879729d36760bb61dac015311c84fec))
|
||||||
|
* Bug lorsqu'on déselectionne un paiementPlan ([d64500f](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/d64500f4022710423c77d023476065816ecd061d))
|
||||||
|
* build error ([65d5b8c](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/65d5b8c424bf0e0a9da1b39500c8252f683725c7))
|
||||||
|
* Calcul du montant total des tarif par RF + affichage des tarifs ([c269b89](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/c269b89d3d58cc65f254b75f6d713c4fd15f6320)), closes [#26](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/issues/26)
|
||||||
|
* calcul nombre de pages dans chaque tab ([5440f5c](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/5440f5cbdbca8b9435a17914c7e7c4ecc34e6bb3))
|
||||||
|
* Champs requis sur les teachers and classes ([42b4c99](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/42b4c99be86f050ccd76302caf725af5df413d17))
|
||||||
|
* Changement d'icone associé aux documents soumis à validation ([500b6e9](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/500b6e9af7ac76dafa35bd830cd0767cece47d27))
|
||||||
|
* code mort ([4fc061f](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/4fc061fc255b4174f794ac58da1b6849419e9f1a))
|
||||||
|
* Condition de validation d'ajout d'un nouveau document parent / ([9e69790](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/9e69790683fd83b0e48a9f70150661cb06a7b556))
|
||||||
|
* conflits + closeModal lors de la création d'un RF ([1617b13](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/1617b132c4f67fdbaf261808a0a9596b7a72a4dc))
|
||||||
|
* coquille ([c9c7e77](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/c9c7e7715efde8766c3b2ad2c355dc9a9960b19f))
|
||||||
|
* coquille dans les imports ([4ecf25a](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/4ecf25a6ab90a57da0013f6ed603d6cd5bd4eeeb))
|
||||||
|
* Correction de l'affichage des numéros de téléphone [[#41](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/issues/41)] ([4f774c1](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/4f774c18e47fc57e081022a03ea352638e7211d2))
|
||||||
|
* correction de l'ouverture du dashbord [[#39](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/issues/39)] ([a157d53](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/a157d53932d5576fc9768f5c063cf9aafa214d43))
|
||||||
|
* Correction de la désactivation des spécialités lorsqu'on ([afc1632](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/afc1632797c0d35df7da03432eba9ab0f1875f55)), closes [#2](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/issues/2)
|
||||||
|
* Correction dépendances circulaires ([fc9a1ed](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/fc9a1ed252e1e115e4a2f7c4a3a04ee6757be683))
|
||||||
|
* Correction des Protected Routes avec multi role ([dd0884b](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/dd0884bbce6b6549f0f3fca991045f7170889710))
|
||||||
|
* correction des refresh des protected routes [[#36](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/issues/36)] ([839a262](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/839a26257b659a86903d3f982548884cc87366b9))
|
||||||
|
* Correction du Establishment context au refresh ([43e301e](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/43e301ed641a742323a98c430e30e134babc4aa4))
|
||||||
|
* correction fileGroup lors de l'enregistrement d'un nouveau responsable ([dce2114](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/dce2114a7940310e2c4241c2cdbd7e3fd060fb60))
|
||||||
|
* Correction option fusion ([e61cd51](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/e61cd51ce2b0665c18f9497e6d2b1f7b8196723e))
|
||||||
|
* Correction sur le calcul du nombre total de pages [[#1](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/issues/1)] ([5946cbd](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/5946cbdee661527317dac66f99f0abce021c835a))
|
||||||
|
* correction titre mail reset mdp ([cac1519](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/cac1519bf311b660831222d76d4d5165ee4f4d7e))
|
||||||
|
* Correction URL ([170f7c4](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/170f7c4fa80e1cd40079ac861e7e633c62f143df))
|
||||||
|
* Corrrection typo dans description des tableaux frais/réduction ([175932f](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/175932ffa3fb2747cafd158b8142df9b7010a3d4))
|
||||||
|
* csrf ([59a0d40](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/59a0d401301fe77226fd5f294a3cd7e589d46fad))
|
||||||
|
* Division par 0 ([a42cf34](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/a42cf348a0a3cc43c6c6b643b1da158690d67cb8))
|
||||||
|
* double confirmation sur les popup ([677cec1](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/677cec1ec2f7a3582327f4747d088c6bccbd2560))
|
||||||
|
* entrypoint access right ([a041ffa](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/a041ffaee75b74e0d559fb14bc79fbcfae98da14))
|
||||||
|
* faire plaisir à LSO ([9374b00](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/9374b001c9944cb6af1e10451f0e5f236a7890e8))
|
||||||
|
* formulaire sur toute la larguer + initiation à un autre style de bg ([4fd40ac](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/4fd40ac5fc90ea1ddda9d73ea290b588074c6e2f))
|
||||||
|
* Fusion documents ([857b8b2](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/857b8b26c3722171007399dc66cd9980b33151c5))
|
||||||
|
* Generation d'une fiche d'élève avec le nouveau modèle PayementMode ([4f40d1f](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/4f40d1f29d7abd1e0c6bf889b10f811f184ff10d))
|
||||||
|
* Génération uniquement des compétences évaluées dans le PDF ([eca8d7a](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/eca8d7a8d59f39313123166859f4c4bf548d150e))
|
||||||
|
* gestion des codes retours ([7f35527](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/7f3552764979e098ca2e8c3547354c8ae6feaa23))
|
||||||
|
* Gestion des listes d'inscription "vides" [[#1](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/issues/1)] ([edc9724](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/edc97242f2219f48233441b8c7ec97ef9551c60c))
|
||||||
|
* gestion du jour d'échéance ([2576d21](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/2576d2173460267664d927bd093580a21c18725b))
|
||||||
|
* import du Loader ([e2a39ff](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/e2a39ff74dd9671bb1d00de2b6cec1cd3e4ff614))
|
||||||
|
* inject env var ([fc337b1](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/fc337b1e0b4605f3490435f4819b01d38f921156))
|
||||||
|
* Limite du nombre de responsables légaux à 2 [[#27](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/issues/27)] ([1ced4a1](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/1ced4a10696057b8df114dc95adf9868e8d7aa43))
|
||||||
|
* Link documents with establishments ([2f6d30b](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/2f6d30b85b90508cae49081a82eadea5039f60b2))
|
||||||
|
* load the school image eorrectly ([6bc2405](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/6bc24055cd5a79d59d2b56c7e767ac1b30d99fff))
|
||||||
|
* Lors de la création d'un clone, on prend le nom de l'élève et pas ([db8e1d8](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/db8e1d8ab320222370c64d7b7fde3e43c59921e8))
|
||||||
|
* Messages de retour reset/new password ([4a6b7ce](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/4a6b7ce379747565c77205728e7b0d9c8a7c9585))
|
||||||
|
* Mise à jour correcte du fichier après avoir été signé ([5ea3cbb](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/5ea3cbb0790840d823e799cc64766a99ef5591a9))
|
||||||
|
* Mise à jour des upcomming events ([f93c428](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/f93c42825964d91d11af26541eecb9ba5f01e801))
|
||||||
|
* mise à jour settings pour la prod / correction CORS ([25e2799](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/25e2799c0f0e46b1a6d78bcc849cc777e67a01f1))
|
||||||
|
* Mise en page des inscriptions (boutons ajout / barre de recherche) ([cf14431](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/cf144310a13fa4cbd01a292002d8a9963acc4598))
|
||||||
|
* Modèle créé 2 fois par erreur ([49907d7](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/49907d7ec8847017115191e937ba9f68350c92bd))
|
||||||
|
* Modification d'un guardian sans changer d'adresse mail (même ([95b449d](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/95b449ddfde4160a55237e0c50e6bed604dcdfe5))
|
||||||
|
* Ne pas dissocier de responsable s'il n'y en a pas d'autre rattaché ([ac0672f](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/ac0672f3349aa2ac62db0e3927658a3f2d66cebf))
|
||||||
|
* Ne pas retourner d'erreur si pas de dossier d'inscription ([be27fe1](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/be27fe1232c3808b0a65d5f1b265ef454eb35e74))
|
||||||
|
* Nouvelle amélioration ([8b3f963](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/8b3f9637a91fe817c87427015a89ba3e469d525d))
|
||||||
|
* On attend que la session soit mise à jour pour intiialiser le ([ccecd78](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/ccecd78704c3e6db58724401b92dd065a7e733ab))
|
||||||
|
* On commence à la page 1 ([3c62cc9](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/3c62cc9ad2cfa691ee798d27ee6b377676e50bb7))
|
||||||
|
* On empêche la sauvegarde d'un document à signer tant qu'aucun ([be013f0](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/be013f07864345024320114bd734508a033fd5db))
|
||||||
|
* On ne peut sélectionner que les élèves inscrits [[#16](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/issues/16)] ([56c223f](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/56c223f3cc0498b4a6619d68f0185c36482c4ec9))
|
||||||
|
* Ordre des guardians lors de leur création / déselection correcte si ([3b667d3](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/3b667d3b150684a685d7d76cf06d050049ee07cd))
|
||||||
|
* pagination annuaire ([980f169](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/980f169c1d1a46f0d47f4b9ff65fa940ac610023))
|
||||||
|
* PieChart ([fe2d4d4](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/fe2d4d45137df3b1ead4d21c29722fec0bd0fbab))
|
||||||
|
* Positionnement de la variable isSepa ([82573f1](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/82573f1b2333a01675cebf575f33ab77e70e138b))
|
||||||
|
* Possibilité d'ajouter un 2ème guardian, même si son mail est ([8cf2290](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/8cf22905e533a23ee679107cc0bcae1198badb4a))
|
||||||
|
* Récupération d'un template donné ([9b13d52](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/9b13d52e8d8926bcc6f756ff4d2c9d278a0cc387))
|
||||||
|
* Refresh par profil role ([24069b8](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/24069b894ef2009d9fe0ad884e7a39c29a5a9504))
|
||||||
|
* refresh token ([053d524](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/053d524a513adfec8bd9b3467fc358c257776a85))
|
||||||
|
* régression CORS_ALLOWED_ORIGINS ([a69498d](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/a69498dd06649b601a16a509c7a80c9f67c7872e))
|
||||||
|
* régression lors de l'uniformisation des modales ([00f7bfd](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/00f7bfde4abd10d080dc2035d3607d6c35e7db14))
|
||||||
|
* Remise du message de confirmation supprimé par erreur ([efcc5e6](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/efcc5e66722c829dcdf522a6903c616901a14604))
|
||||||
|
* Remise en état du bouton Submit ([e9650c9](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/e9650c992e6c8339d7acde4000bf4f3dd8e98bac))
|
||||||
|
* Remise en place de l'API_KEY docuseal dans le back ([6d80594](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/6d805940fe5524cae1864c0beebcd136bda84eda))
|
||||||
|
* remove lint error ([aef6c19](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/aef6c193b1eaaedbb0642ce7929b2cfe8f47d682))
|
||||||
|
* Remplacement des enum par des modèles pour les payementModes et les ([7fe5346](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/7fe53465acc2df53351f713ccacd12223d6eff1a))
|
||||||
|
* restore du start.py suite à des tests ([de5f7cd](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/de5f7cd41e52b27ee3d8f47cf47fbfdad78216ac))
|
||||||
|
* right ([05f1f16](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/05f1f16727c2510385425b23fa6ab98fa62d07be))
|
||||||
|
* Scroll de l'emploi du temps élève ([f38a441](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/f38a4414c28ec52981201c15b7eda0dccc1f932f))
|
||||||
|
* searchTerm inscription ([8f0cf16](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/8f0cf16f707ac1cd51f0d619fa1c5ea0ba023f68))
|
||||||
|
* Session storage selectedEstablishmentEvaluationFrequency et selectedEstablishmentTotalCapacity ([e30753f](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/e30753f1d6d2911d51bb9dfbf32fbae6f2b62b5d))
|
||||||
|
* Suite du commit précédent ([cd9c10a](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/cd9c10a88af2a05350570c424fb284280c0f65ee))
|
||||||
|
* Suppression d'un profil uniquement s'il ne contient aucun guardian ([330018e](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/330018edfdbdc071c15838bc22b8a4e726773204))
|
||||||
|
* Suppression de la top bar admin [[#34](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/issues/34)] ([3990d75](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/3990d75e521bea007a8f479924507498d9586a71))
|
||||||
|
* Suppression de print inutiles ([43874f8](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/43874f8b9e76b3e9f131f240ee895d958cd73fab))
|
||||||
|
* Suppression event planning ([c117f96](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/c117f96e528244ee68ce69b7685880e171976e32))
|
||||||
|
* Unicité des fees + utilisation de l'establishmentID [[#44](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/issues/44)] ([d37e6c3](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/d37e6c384d0ef16053ed9fcc1e979f7f902cc8d8))
|
||||||
|
* Uniformisation des Modales et Popup [[#35](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/issues/35)] ([f252efd](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/f252efdef4fce1f2d19ac7ca1eb9c049706c0d9f))
|
||||||
|
* Utilisation des bonnes colonnes pour les fees et discounts selon si ([9f1f97e](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/9f1f97e0c56771208305b28a740504c220287053))
|
||||||
|
* Utilisation du signal "post-migrate" pour créer la spécialité par ([e1202c6](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/e1202c6e6d4061552fa7d530e3e09b11384843c3))
|
||||||
|
* Variables booléennes par défaut ([6bedf71](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/6bedf715ccf9bb9bae4f92d735e3d7b714c96849))
|
||||||
|
* variables csrf ([789816e](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/789816e9868685e2ae08b536b6b6ada1a6a64595))
|
||||||
|
* warning sur ouverture modale de fichiers ([889a3a4](https://git.v0id.ovh:5022/n3wt-innov/n3wt-school/commit/889a3a48c5c2a3f6cb65de8ede0efbe639408011))
|
||||||
|
|||||||
@ -1,3 +0,0 @@
|
|||||||
https://localhost:4443, https://127.0.0.1:4443 {
|
|
||||||
reverse_proxy docuseal:3000
|
|
||||||
}
|
|
||||||
@ -18,6 +18,19 @@ const nextConfig = {
|
|||||||
protocol: 'https',
|
protocol: 'https',
|
||||||
hostname: 'www.gravatar.com',
|
hostname: 'www.gravatar.com',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
protocol: 'https',
|
||||||
|
hostname: 'api.demo.n3wtschool.com',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
protocol: 'https',
|
||||||
|
hostname: 'api.prod.n3wtschool.com',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
protocol: 'http',
|
||||||
|
hostname: 'localhost',
|
||||||
|
port: '8080',
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
env: {
|
env: {
|
||||||
|
|||||||
26
Front-End/package-lock.json
generated
26
Front-End/package-lock.json
generated
@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "n3wt-school-front-end",
|
"name": "n3wt-school-front-end",
|
||||||
"version": "0.0.1",
|
"version": "0.0.3",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "n3wt-school-front-end",
|
"name": "n3wt-school-front-end",
|
||||||
"version": "0.0.1",
|
"version": "0.0.3",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@docuseal/react": "^1.0.56",
|
"@docuseal/react": "^1.0.56",
|
||||||
"@radix-ui/react-dialog": "^1.1.2",
|
"@radix-ui/react-dialog": "^1.1.2",
|
||||||
@ -29,6 +29,7 @@
|
|||||||
"react-dnd": "^16.0.1",
|
"react-dnd": "^16.0.1",
|
||||||
"react-dnd-html5-backend": "^16.0.1",
|
"react-dnd-html5-backend": "^16.0.1",
|
||||||
"react-dom": "^18",
|
"react-dom": "^18",
|
||||||
|
"react-hook-form": "^7.62.0",
|
||||||
"react-international-phone": "^4.5.0",
|
"react-international-phone": "^4.5.0",
|
||||||
"react-quill": "^2.0.0",
|
"react-quill": "^2.0.0",
|
||||||
"react-tooltip": "^5.28.0"
|
"react-tooltip": "^5.28.0"
|
||||||
@ -8834,6 +8835,21 @@
|
|||||||
"react": "^18.3.1"
|
"react": "^18.3.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-hook-form": {
|
||||||
|
"version": "7.62.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.62.0.tgz",
|
||||||
|
"integrity": "sha512-7KWFejc98xqG/F4bAxpL41NB3o1nnvQO1RWZT3TqRZYL8RryQETGfEdVnJN2fy1crCiBLLjkRBVK05j24FxJGA==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/react-hook-form"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.8.0 || ^17 || ^18 || ^19"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-international-phone": {
|
"node_modules/react-international-phone": {
|
||||||
"version": "4.5.0",
|
"version": "4.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-international-phone/-/react-international-phone-4.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-international-phone/-/react-international-phone-4.5.0.tgz",
|
||||||
@ -17160,6 +17176,12 @@
|
|||||||
"scheduler": "^0.23.2"
|
"scheduler": "^0.23.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"react-hook-form": {
|
||||||
|
"version": "7.62.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.62.0.tgz",
|
||||||
|
"integrity": "sha512-7KWFejc98xqG/F4bAxpL41NB3o1nnvQO1RWZT3TqRZYL8RryQETGfEdVnJN2fy1crCiBLLjkRBVK05j24FxJGA==",
|
||||||
|
"requires": {}
|
||||||
|
},
|
||||||
"react-international-phone": {
|
"react-international-phone": {
|
||||||
"version": "4.5.0",
|
"version": "4.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-international-phone/-/react-international-phone-4.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-international-phone/-/react-international-phone-4.5.0.tgz",
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "n3wt-school-front-end",
|
"name": "n3wt-school-front-end",
|
||||||
"version": "0.0.1",
|
"version": "0.0.3",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev",
|
"dev": "next dev",
|
||||||
@ -35,19 +35,20 @@
|
|||||||
"react-dnd": "^16.0.1",
|
"react-dnd": "^16.0.1",
|
||||||
"react-dnd-html5-backend": "^16.0.1",
|
"react-dnd-html5-backend": "^16.0.1",
|
||||||
"react-dom": "^18",
|
"react-dom": "^18",
|
||||||
|
"react-hook-form": "^7.62.0",
|
||||||
"react-international-phone": "^4.5.0",
|
"react-international-phone": "^4.5.0",
|
||||||
"react-quill": "^2.0.0",
|
"react-quill": "^2.0.0",
|
||||||
"react-tooltip": "^5.28.0"
|
"react-tooltip": "^5.28.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@testing-library/react": "^13.4.0",
|
|
||||||
"@testing-library/jest-dom": "^5.16.5",
|
"@testing-library/jest-dom": "^5.16.5",
|
||||||
|
"@testing-library/react": "^13.4.0",
|
||||||
"@testing-library/user-event": "^14.4.3",
|
"@testing-library/user-event": "^14.4.3",
|
||||||
"jest": "^29.7.0",
|
|
||||||
"jest-environment-jsdom": "^29.7.0",
|
|
||||||
"autoprefixer": "^10.4.20",
|
"autoprefixer": "^10.4.20",
|
||||||
"eslint": "^8",
|
"eslint": "^8",
|
||||||
"eslint-config-next": "14.2.11",
|
"eslint-config-next": "14.2.11",
|
||||||
|
"jest": "^29.7.0",
|
||||||
|
"jest-environment-jsdom": "^29.7.0",
|
||||||
"postcss": "^8.4.47",
|
"postcss": "^8.4.47",
|
||||||
"tailwindcss": "^3.4.14"
|
"tailwindcss": "^3.4.14"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
'use client';
|
'use client';
|
||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import SelectChoice from '@/components/SelectChoice';
|
import SelectChoice from '@/components/Form/SelectChoice';
|
||||||
import AcademicResults from '@/components/Grades/AcademicResults';
|
import AcademicResults from '@/components/Grades/AcademicResults';
|
||||||
import Attendance from '@/components/Grades/Attendance';
|
import Attendance from '@/components/Grades/Attendance';
|
||||||
import Remarks from '@/components/Grades/Remarks';
|
import Remarks from '@/components/Grades/Remarks';
|
||||||
@ -9,7 +9,7 @@ import Homeworks from '@/components/Grades/Homeworks';
|
|||||||
import SpecificEvaluations from '@/components/Grades/SpecificEvaluations';
|
import SpecificEvaluations from '@/components/Grades/SpecificEvaluations';
|
||||||
import Orientation from '@/components/Grades/Orientation';
|
import Orientation from '@/components/Grades/Orientation';
|
||||||
import GradesStatsCircle from '@/components/Grades/GradesStatsCircle';
|
import GradesStatsCircle from '@/components/Grades/GradesStatsCircle';
|
||||||
import Button from '@/components/Button';
|
import Button from '@/components/Form/Button';
|
||||||
import logger from '@/utils/logger';
|
import logger from '@/utils/logger';
|
||||||
import {
|
import {
|
||||||
FE_ADMIN_GRADES_STUDENT_COMPETENCIES_URL,
|
FE_ADMIN_GRADES_STUDENT_COMPETENCIES_URL,
|
||||||
@ -29,7 +29,7 @@ import { useClasses } from '@/context/ClassesContext';
|
|||||||
import { Award, FileText } from 'lucide-react';
|
import { Award, FileText } from 'lucide-react';
|
||||||
import SectionHeader from '@/components/SectionHeader';
|
import SectionHeader from '@/components/SectionHeader';
|
||||||
import GradesDomainBarChart from '@/components/Grades/GradesDomainBarChart';
|
import GradesDomainBarChart from '@/components/Grades/GradesDomainBarChart';
|
||||||
import InputText from '@/components/InputText';
|
import InputText from '@/components/Form/InputText';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { useCsrfToken } from '@/context/CsrfContext';
|
import { useCsrfToken } from '@/context/CsrfContext';
|
||||||
|
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { useSearchParams, useRouter } from 'next/navigation';
|
import { useSearchParams, useRouter } from 'next/navigation';
|
||||||
import Button from '@/components/Button';
|
import Button from '@/components/Form/Button';
|
||||||
import GradeView from '@/components/Grades/GradeView';
|
import GradeView from '@/components/Grades/GradeView';
|
||||||
import {
|
import {
|
||||||
fetchStudentCompetencies,
|
fetchStudentCompetencies,
|
||||||
|
|||||||
@ -1,9 +1,8 @@
|
|||||||
'use client';
|
'use client';
|
||||||
import React, { useEffect } from 'react';
|
import React from 'react';
|
||||||
import SidebarTabs from '@/components/SidebarTabs';
|
import SidebarTabs from '@/components/SidebarTabs';
|
||||||
import EmailSender from '@/components/Admin/EmailSender';
|
import EmailSender from '@/components/Admin/EmailSender';
|
||||||
import InstantMessaging from '@/components/Admin/InstantMessaging';
|
import InstantMessaging from '@/components/Admin/InstantMessaging';
|
||||||
import AnnouncementScheduler from '@/components/Admin/AnnouncementScheduler';
|
|
||||||
import logger from '@/utils/logger';
|
import logger from '@/utils/logger';
|
||||||
|
|
||||||
export default function MessageriePage({ csrfToken }) {
|
export default function MessageriePage({ csrfToken }) {
|
||||||
@ -18,11 +17,6 @@ export default function MessageriePage({ csrfToken }) {
|
|||||||
label: 'Messagerie Instantanée',
|
label: 'Messagerie Instantanée',
|
||||||
content: <InstantMessaging csrfToken={csrfToken} />,
|
content: <InstantMessaging csrfToken={csrfToken} />,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
id: 'announcement',
|
|
||||||
label: 'Planifier une Annonce',
|
|
||||||
content: <AnnouncementScheduler csrfToken={csrfToken} />,
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -1,9 +1,17 @@
|
|||||||
'use client';
|
'use client';
|
||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { useTranslations } from 'next-intl';
|
import { useTranslations } from 'next-intl';
|
||||||
import { Users, Clock, CalendarCheck, School, AlertTriangle, CheckCircle2 } from 'lucide-react';
|
import {
|
||||||
|
Users,
|
||||||
|
Clock,
|
||||||
|
CalendarCheck,
|
||||||
|
School,
|
||||||
|
AlertTriangle,
|
||||||
|
CheckCircle2,
|
||||||
|
} from 'lucide-react';
|
||||||
import Loader from '@/components/Loader';
|
import Loader from '@/components/Loader';
|
||||||
import StatCard from '@/components/StatCard';
|
import StatCard from '@/components/StatCard';
|
||||||
|
import EventCard from '@/components/EventCard';
|
||||||
import logger from '@/utils/logger';
|
import logger from '@/utils/logger';
|
||||||
import {
|
import {
|
||||||
fetchRegisterForms,
|
fetchRegisterForms,
|
||||||
@ -15,20 +23,6 @@ import Attendance from '@/components/Grades/Attendance';
|
|||||||
import LineChart from '@/components/Charts/LineChart';
|
import LineChart from '@/components/Charts/LineChart';
|
||||||
import PieChart from '@/components/Charts/PieChart';
|
import PieChart from '@/components/Charts/PieChart';
|
||||||
|
|
||||||
// Composant EventCard pour afficher les événements
|
|
||||||
const EventCard = ({ title, date, description, type }) => (
|
|
||||||
<div className="bg-stone-50 p-4 rounded-lg shadow-sm border border-gray-100 mb-4">
|
|
||||||
<div className="flex items-center gap-3">
|
|
||||||
<CalendarCheck className="text-blue-500" size={20} />
|
|
||||||
<div>
|
|
||||||
<h4 className="font-medium">{title}</h4>
|
|
||||||
<p className="text-sm text-gray-500">{date}</p>
|
|
||||||
<p className="text-sm mt-1">{description}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
const mockCompletionRate = 72; // en pourcentage
|
const mockCompletionRate = 72; // en pourcentage
|
||||||
|
|
||||||
export default function DashboardPage() {
|
export default function DashboardPage() {
|
||||||
@ -39,8 +33,11 @@ export default function DashboardPage() {
|
|||||||
const [upcomingEvents, setUpcomingEvents] = useState([]);
|
const [upcomingEvents, setUpcomingEvents] = useState([]);
|
||||||
|
|
||||||
const [absencesToday, setAbsencesToday] = useState([]);
|
const [absencesToday, setAbsencesToday] = useState([]);
|
||||||
const { selectedEstablishmentId, selectedEstablishmentTotalCapacity, apiDocuseal } =
|
const {
|
||||||
useEstablishment();
|
selectedEstablishmentId,
|
||||||
|
selectedEstablishmentTotalCapacity,
|
||||||
|
apiDocuseal,
|
||||||
|
} = useEstablishment();
|
||||||
|
|
||||||
const [statusDistribution, setStatusDistribution] = useState([
|
const [statusDistribution, setStatusDistribution] = useState([
|
||||||
{ label: 'Non envoyé', value: 0 },
|
{ label: 'Non envoyé', value: 0 },
|
||||||
@ -220,34 +217,36 @@ export default function DashboardPage() {
|
|||||||
|
|
||||||
{/* Événements et KPIs */}
|
{/* Événements et KPIs */}
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
|
||||||
{/* Graphique des inscriptions */}
|
{/* Colonne de gauche : Graphique des inscriptions + Présence */}
|
||||||
<div className="lg:col-span-1 bg-stone-50 p-6 rounded-lg shadow-sm border border-gray-100">
|
<div className="flex flex-col gap-6">
|
||||||
<h2 className="text-lg font-semibold mb-6">
|
{/* Graphique des inscriptions */}
|
||||||
{t('inscriptionTrends')}
|
<div className="bg-stone-50 p-6 rounded-lg shadow-sm border border-gray-100 flex-1">
|
||||||
</h2>
|
<h2 className="text-lg font-semibold mb-6">
|
||||||
<div className="flex flex-row gap-4">
|
{t('inscriptionTrends')}
|
||||||
<div className="flex-1 p-6">
|
</h2>
|
||||||
<LineChart data={monthlyRegistrations} />
|
<div className="flex flex-row gap-4">
|
||||||
</div>
|
<div className="flex-1 p-6">
|
||||||
<div className="flex-1 flex items-center justify-center">
|
<LineChart data={monthlyRegistrations} />
|
||||||
<PieChart data={statusDistribution} />
|
</div>
|
||||||
|
<div className="flex-1 flex items-center justify-center">
|
||||||
|
<PieChart data={statusDistribution} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{/* Présence et assiduité */}
|
||||||
|
<div className="bg-stone-50 p-6 rounded-lg shadow-sm border border-gray-100 flex-1">
|
||||||
|
<Attendance absences={absencesToday} readOnly={true} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Événements à venir */}
|
{/* Colonne de droite : Événements à venir */}
|
||||||
<div className="bg-stone-50 p-6 rounded-lg shadow-sm border border-gray-100">
|
<div className="bg-stone-50 p-6 rounded-lg shadow-sm border border-gray-100 flex-1 h-full">
|
||||||
<h2 className="text-lg font-semibold mb-4">{t('upcomingEvents')}</h2>
|
<h2 className="text-lg font-semibold mb-4">{t('upcomingEvents')}</h2>
|
||||||
{upcomingEvents.map((event, index) => (
|
{upcomingEvents.map((event, index) => (
|
||||||
<EventCard key={index} {...event} />
|
<EventCard key={index} {...event} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Ajout du composant Attendance en dessous, en lecture seule */}
|
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
|
|
||||||
<Attendance absences={absencesToday} readOnly={true} />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,8 +2,8 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import Tab from '@/components/Tab';
|
import Tab from '@/components/Tab';
|
||||||
import TabContent from '@/components/TabContent';
|
import TabContent from '@/components/TabContent';
|
||||||
import Button from '@/components/Button';
|
import Button from '@/components/Form/Button';
|
||||||
import InputText from '@/components/InputText';
|
import InputText from '@/components/Form/InputText';
|
||||||
import CheckBox from '@/components/CheckBox'; // Import du composant CheckBox
|
import CheckBox from '@/components/CheckBox'; // Import du composant CheckBox
|
||||||
import logger from '@/utils/logger';
|
import logger from '@/utils/logger';
|
||||||
import {
|
import {
|
||||||
@ -16,7 +16,7 @@ import { useNotification } from '@/context/NotificationContext';
|
|||||||
import { useSearchParams } from 'next/navigation'; // Ajoute cet import
|
import { useSearchParams } from 'next/navigation'; // Ajoute cet import
|
||||||
|
|
||||||
export default function SettingsPage() {
|
export default function SettingsPage() {
|
||||||
const [activeTab, setActiveTab] = useState('structure');
|
const [activeTab, setActiveTab] = useState('smtp');
|
||||||
const [email, setEmail] = useState('');
|
const [email, setEmail] = useState('');
|
||||||
const [password, setPassword] = useState('');
|
const [password, setPassword] = useState('');
|
||||||
const [confirmPassword, setConfirmPassword] = useState('');
|
const [confirmPassword, setConfirmPassword] = useState('');
|
||||||
@ -40,8 +40,6 @@ export default function SettingsPage() {
|
|||||||
const tabParam = searchParams.get('tab');
|
const tabParam = searchParams.get('tab');
|
||||||
if (tabParam === 'smtp') {
|
if (tabParam === 'smtp') {
|
||||||
setActiveTab('smtp');
|
setActiveTab('smtp');
|
||||||
} else if (tabParam === 'structure') {
|
|
||||||
setActiveTab('structure');
|
|
||||||
}
|
}
|
||||||
}, [searchParams]);
|
}, [searchParams]);
|
||||||
|
|
||||||
@ -79,18 +77,6 @@ export default function SettingsPage() {
|
|||||||
}
|
}
|
||||||
}, [activeTab, csrfToken]); // Ajouter csrfToken comme dépendance
|
}, [activeTab, csrfToken]); // Ajouter csrfToken comme dépendance
|
||||||
|
|
||||||
const handleEmailChange = (e) => {
|
|
||||||
setEmail(e.target.value);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handlePasswordChange = (e) => {
|
|
||||||
setPassword(e.target.value);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleConfirmPasswordChange = (e) => {
|
|
||||||
setConfirmPassword(e.target.value);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSmtpServerChange = (e) => {
|
const handleSmtpServerChange = (e) => {
|
||||||
setSmtpServer(e.target.value);
|
setSmtpServer(e.target.value);
|
||||||
};
|
};
|
||||||
@ -107,26 +93,6 @@ export default function SettingsPage() {
|
|||||||
setSmtpPassword(e.target.value);
|
setSmtpPassword(e.target.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleUseTlsChange = (e) => {
|
|
||||||
setUseTls(e.target.checked);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleUseSslChange = (e) => {
|
|
||||||
setUseSsl(e.target.checked);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSubmit = (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
if (password !== confirmPassword) {
|
|
||||||
showNotification(
|
|
||||||
'Les mots de passe ne correspondent pas',
|
|
||||||
'error',
|
|
||||||
'Erreur'
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSmtpSubmit = (e) => {
|
const handleSmtpSubmit = (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const smtpData = {
|
const smtpData = {
|
||||||
@ -164,11 +130,6 @@ export default function SettingsPage() {
|
|||||||
return (
|
return (
|
||||||
<div className="p-8">
|
<div className="p-8">
|
||||||
<div className="flex space-x-4 mb-4">
|
<div className="flex space-x-4 mb-4">
|
||||||
<Tab
|
|
||||||
text="Informations de la structure"
|
|
||||||
active={activeTab === 'structure'}
|
|
||||||
onClick={() => handleTabClick('structure')}
|
|
||||||
/>
|
|
||||||
<Tab
|
<Tab
|
||||||
text="Paramètres SMTP"
|
text="Paramètres SMTP"
|
||||||
active={activeTab === 'smtp'}
|
active={activeTab === 'smtp'}
|
||||||
@ -176,28 +137,6 @@ export default function SettingsPage() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-4">
|
<div className="mt-4">
|
||||||
<TabContent isActive={activeTab === 'structure'}>
|
|
||||||
<form onSubmit={handleSubmit}>
|
|
||||||
<InputText
|
|
||||||
label="Email"
|
|
||||||
value={email}
|
|
||||||
onChange={handleEmailChange}
|
|
||||||
/>
|
|
||||||
<InputText
|
|
||||||
label="Mot de passe"
|
|
||||||
type="password"
|
|
||||||
value={password}
|
|
||||||
onChange={handlePasswordChange}
|
|
||||||
/>
|
|
||||||
<InputText
|
|
||||||
label="Confirmer le mot de passe"
|
|
||||||
type="password"
|
|
||||||
value={confirmPassword}
|
|
||||||
onChange={handleConfirmPasswordChange}
|
|
||||||
/>
|
|
||||||
<Button type="submit" primary text="Mettre à jour"></Button>
|
|
||||||
</form>
|
|
||||||
</TabContent>
|
|
||||||
<TabContent isActive={activeTab === 'smtp'}>
|
<TabContent isActive={activeTab === 'smtp'}>
|
||||||
<form onSubmit={handleSmtpSubmit}>
|
<form onSubmit={handleSmtpSubmit}>
|
||||||
<div className="grid grid-cols-2 gap-4">
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
|||||||
@ -8,8 +8,8 @@ import { fetchClasse } from '@/app/actions/schoolAction';
|
|||||||
import { useSearchParams } from 'next/navigation';
|
import { useSearchParams } from 'next/navigation';
|
||||||
import logger from '@/utils/logger';
|
import logger from '@/utils/logger';
|
||||||
import { useClasses } from '@/context/ClassesContext';
|
import { useClasses } from '@/context/ClassesContext';
|
||||||
import Button from '@/components/Button';
|
import Button from '@/components/Form/Button';
|
||||||
import SelectChoice from '@/components/SelectChoice';
|
import SelectChoice from '@/components/Form/SelectChoice';
|
||||||
import CheckBox from '@/components/CheckBox';
|
import CheckBox from '@/components/CheckBox';
|
||||||
import {
|
import {
|
||||||
fetchAbsences,
|
fetchAbsences,
|
||||||
|
|||||||
@ -78,7 +78,7 @@ export default function Page() {
|
|||||||
handleTuitionFees();
|
handleTuitionFees();
|
||||||
|
|
||||||
// Fetch data for registration file schoolFileTemplates
|
// Fetch data for registration file schoolFileTemplates
|
||||||
fetchRegistrationSchoolFileMasters()
|
fetchRegistrationSchoolFileMasters(selectedEstablishmentId)
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
setFichiers(data);
|
setFichiers(data);
|
||||||
})
|
})
|
||||||
|
|||||||
@ -2,17 +2,17 @@
|
|||||||
|
|
||||||
import React, { useState, useRef, useEffect } from 'react';
|
import React, { useState, useRef, useEffect } from 'react';
|
||||||
import { User, Mail } from 'lucide-react';
|
import { User, Mail } from 'lucide-react';
|
||||||
import InputTextIcon from '@/components/InputTextIcon';
|
import InputTextIcon from '@/components/Form/InputTextIcon';
|
||||||
import ToggleSwitch from '@/components/ToggleSwitch';
|
import ToggleSwitch from '@/components/Form/ToggleSwitch';
|
||||||
import Button from '@/components/Button';
|
import Button from '@/components/Form/Button';
|
||||||
import Table from '@/components/Table';
|
import Table from '@/components/Table';
|
||||||
import FeesSection from '@/components/Structure/Tarification/FeesSection';
|
import FeesSection from '@/components/Structure/Tarification/FeesSection';
|
||||||
import DiscountsSection from '@/components/Structure/Tarification/DiscountsSection';
|
import DiscountsSection from '@/components/Structure/Tarification/DiscountsSection';
|
||||||
import SectionTitle from '@/components/SectionTitle';
|
import SectionTitle from '@/components/SectionTitle';
|
||||||
import InputPhone from '@/components/InputPhone';
|
import InputPhone from '@/components/Form/InputPhone';
|
||||||
import CheckBox from '@/components/CheckBox';
|
import CheckBox from '@/components/CheckBox';
|
||||||
import RadioList from '@/components/RadioList';
|
import RadioList from '@/components/Form/RadioList';
|
||||||
import SelectChoice from '@/components/SelectChoice';
|
import SelectChoice from '@/components/Form/SelectChoice';
|
||||||
import Loader from '@/components/Loader';
|
import Loader from '@/components/Loader';
|
||||||
import { getCurrentSchoolYear, getNextSchoolYear } from '@/utils/Date';
|
import { getCurrentSchoolYear, getNextSchoolYear } from '@/utils/Date';
|
||||||
import logger from '@/utils/logger';
|
import logger from '@/utils/logger';
|
||||||
@ -313,13 +313,13 @@ export default function CreateSubscriptionPage() {
|
|||||||
})
|
})
|
||||||
.catch(requestErrorHandler);
|
.catch(requestErrorHandler);
|
||||||
|
|
||||||
fetchRegistrationSchoolFileMasters()
|
fetchRegistrationSchoolFileMasters(selectedEstablishmentId)
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
setSchoolFileMasters(data);
|
setSchoolFileMasters(data);
|
||||||
})
|
})
|
||||||
.catch(requestErrorHandler);
|
.catch(requestErrorHandler);
|
||||||
|
|
||||||
fetchRegistrationParentFileMasters()
|
fetchRegistrationParentFileMasters(selectedEstablishmentId)
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
setParentFileMasters(data);
|
setParentFileMasters(data);
|
||||||
})
|
})
|
||||||
@ -502,7 +502,11 @@ export default function CreateSubscriptionPage() {
|
|||||||
// Mode édition
|
// Mode édition
|
||||||
editRegisterForm(registerFormID, formData, csrfToken)
|
editRegisterForm(registerFormID, formData, csrfToken)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
logger.debug('Dossier mis à jour avec succès:', response);
|
showNotification(
|
||||||
|
"Dossier d'inscription mis à jour avec succès",
|
||||||
|
'success',
|
||||||
|
'Succès'
|
||||||
|
);
|
||||||
router.push(FE_ADMIN_SUBSCRIPTIONS_URL);
|
router.push(FE_ADMIN_SUBSCRIPTIONS_URL);
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
@ -613,6 +617,11 @@ export default function CreateSubscriptionPage() {
|
|||||||
Promise.all([...clonePromises, ...parentClonePromises])
|
Promise.all([...clonePromises, ...parentClonePromises])
|
||||||
.then(() => {
|
.then(() => {
|
||||||
// Redirection après succès
|
// Redirection après succès
|
||||||
|
showNotification(
|
||||||
|
"Dossier d'inscription créé avec succès",
|
||||||
|
'success',
|
||||||
|
'Succès'
|
||||||
|
);
|
||||||
router.push(FE_ADMIN_SUBSCRIPTIONS_URL);
|
router.push(FE_ADMIN_SUBSCRIPTIONS_URL);
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
|
|||||||
@ -8,9 +8,11 @@ import { useEstablishment } from '@/context/EstablishmentContext';
|
|||||||
import { editRegisterForm } from '@/app/actions/subscriptionAction';
|
import { editRegisterForm } from '@/app/actions/subscriptionAction';
|
||||||
import logger from '@/utils/logger';
|
import logger from '@/utils/logger';
|
||||||
import Loader from '@/components/Loader';
|
import Loader from '@/components/Loader';
|
||||||
|
import { useNotification } from '@/context/NotificationContext';
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const { showNotification } = useNotification();
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
const studentId = searchParams.get('studentId');
|
const studentId = searchParams.get('studentId');
|
||||||
const enable = searchParams.get('enabled') === 'true';
|
const enable = searchParams.get('enabled') === 'true';
|
||||||
@ -26,11 +28,21 @@ export default function Page() {
|
|||||||
.then((result) => {
|
.then((result) => {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
logger.debug('Success:', result);
|
logger.debug('Success:', result);
|
||||||
|
showNotification(
|
||||||
|
"Dossier d'inscription soumis avec succès",
|
||||||
|
'success',
|
||||||
|
'Succès'
|
||||||
|
);
|
||||||
router.push(FE_ADMIN_SUBSCRIPTIONS_URL);
|
router.push(FE_ADMIN_SUBSCRIPTIONS_URL);
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
logger.error('Error:', error.message);
|
logger.error('Error:', error.message);
|
||||||
|
showNotification(
|
||||||
|
"Erreur lors de la soumission du dossier d'inscription",
|
||||||
|
'error',
|
||||||
|
'Erreur'
|
||||||
|
);
|
||||||
if (error.details) {
|
if (error.details) {
|
||||||
logger.error('Form errors:', error.details);
|
logger.error('Form errors:', error.details);
|
||||||
setFormErrors(error.details);
|
setFormErrors(error.details);
|
||||||
|
|||||||
@ -40,8 +40,8 @@ import {
|
|||||||
import DjangoCSRFToken from '@/components/DjangoCSRFToken';
|
import DjangoCSRFToken from '@/components/DjangoCSRFToken';
|
||||||
import { useCsrfToken } from '@/context/CsrfContext';
|
import { useCsrfToken } from '@/context/CsrfContext';
|
||||||
import logger from '@/utils/logger';
|
import logger from '@/utils/logger';
|
||||||
import { PhoneLabel } from '@/components/PhoneLabel';
|
import { PhoneLabel } from '@/components/Form/PhoneLabel';
|
||||||
import FileUpload from '@/components/FileUpload';
|
import FileUpload from '@/components/Form/FileUpload';
|
||||||
import FilesModal from '@/components/Inscription/FilesModal';
|
import FilesModal from '@/components/Inscription/FilesModal';
|
||||||
import { getCurrentSchoolYear, getNextSchoolYear } from '@/utils/Date';
|
import { getCurrentSchoolYear, getNextSchoolYear } from '@/utils/Date';
|
||||||
|
|
||||||
@ -52,6 +52,7 @@ import {
|
|||||||
HISTORICAL_FILTER,
|
HISTORICAL_FILTER,
|
||||||
} from '@/utils/constants';
|
} from '@/utils/constants';
|
||||||
import AlertMessage from '@/components/AlertMessage';
|
import AlertMessage from '@/components/AlertMessage';
|
||||||
|
import { useNotification } from '@/context/NotificationContext';
|
||||||
|
|
||||||
export default function Page({ params: { locale } }) {
|
export default function Page({ params: { locale } }) {
|
||||||
const t = useTranslations('subscriptions');
|
const t = useTranslations('subscriptions');
|
||||||
@ -91,8 +92,6 @@ export default function Page({ params: { locale } }) {
|
|||||||
const [isFilesModalOpen, setIsFilesModalOpen] = useState(false);
|
const [isFilesModalOpen, setIsFilesModalOpen] = useState(false);
|
||||||
const [selectedRegisterForm, setSelectedRegisterForm] = useState([]);
|
const [selectedRegisterForm, setSelectedRegisterForm] = useState([]);
|
||||||
|
|
||||||
const [popupVisible, setPopupVisible] = useState(false);
|
|
||||||
const [popupMessage, setPopupMessage] = useState('');
|
|
||||||
const [confirmPopupVisible, setConfirmPopupVisible] = useState(false);
|
const [confirmPopupVisible, setConfirmPopupVisible] = useState(false);
|
||||||
const [confirmPopupMessage, setConfirmPopupMessage] = useState('');
|
const [confirmPopupMessage, setConfirmPopupMessage] = useState('');
|
||||||
const [confirmPopupOnConfirm, setConfirmPopupOnConfirm] = useState(() => {});
|
const [confirmPopupOnConfirm, setConfirmPopupOnConfirm] = useState(() => {});
|
||||||
@ -103,6 +102,7 @@ export default function Page({ params: { locale } }) {
|
|||||||
const csrfToken = useCsrfToken();
|
const csrfToken = useCsrfToken();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { selectedEstablishmentId } = useEstablishment();
|
const { selectedEstablishmentId } = useEstablishment();
|
||||||
|
const { showNotification } = useNotification();
|
||||||
|
|
||||||
const openSepaUploadModal = (row) => {
|
const openSepaUploadModal = (row) => {
|
||||||
setSelectedRowForUpload(row);
|
setSelectedRowForUpload(row);
|
||||||
@ -133,9 +133,8 @@ export default function Page({ params: { locale } }) {
|
|||||||
const registerFormCurrrentYearDataHandler = (data) => {
|
const registerFormCurrrentYearDataHandler = (data) => {
|
||||||
if (data) {
|
if (data) {
|
||||||
const { registerForms, count, page_size } = data;
|
const { registerForms, count, page_size } = data;
|
||||||
if (registerForms) {
|
setRegistrationFormsDataCurrentYear(registerForms);
|
||||||
setRegistrationFormsDataCurrentYear(registerForms);
|
|
||||||
}
|
|
||||||
const calculatedTotalPages =
|
const calculatedTotalPages =
|
||||||
count === 0 ? count : Math.ceil(count / page_size);
|
count === 0 ? count : Math.ceil(count / page_size);
|
||||||
setTotalCurrentYear(count);
|
setTotalCurrentYear(count);
|
||||||
@ -153,9 +152,8 @@ export default function Page({ params: { locale } }) {
|
|||||||
const registerFormNextYearDataHandler = (data) => {
|
const registerFormNextYearDataHandler = (data) => {
|
||||||
if (data) {
|
if (data) {
|
||||||
const { registerForms, count, page_size } = data;
|
const { registerForms, count, page_size } = data;
|
||||||
if (registerForms) {
|
setRegistrationFormsDataNextYear(registerForms);
|
||||||
setRegistrationFormsDataNextYear(registerForms);
|
|
||||||
}
|
|
||||||
const calculatedTotalPages =
|
const calculatedTotalPages =
|
||||||
count === 0 ? count : Math.ceil(count / page_size);
|
count === 0 ? count : Math.ceil(count / page_size);
|
||||||
setTotalNextYear(count);
|
setTotalNextYear(count);
|
||||||
@ -173,9 +171,7 @@ export default function Page({ params: { locale } }) {
|
|||||||
const registerFormHistoricalDataHandler = (data) => {
|
const registerFormHistoricalDataHandler = (data) => {
|
||||||
if (data) {
|
if (data) {
|
||||||
const { registerForms, count, page_size } = data;
|
const { registerForms, count, page_size } = data;
|
||||||
if (registerForms) {
|
setRegistrationFormsDataHistorical(registerForms);
|
||||||
setRegistrationFormsDataHistorical(registerForms);
|
|
||||||
}
|
|
||||||
|
|
||||||
const calculatedTotalPages =
|
const calculatedTotalPages =
|
||||||
count === 0 ? count : Math.ceil(count / page_size);
|
count === 0 ? count : Math.ceil(count / page_size);
|
||||||
@ -225,12 +221,11 @@ export default function Page({ params: { locale } }) {
|
|||||||
|
|
||||||
fetchDataAndSetState();
|
fetchDataAndSetState();
|
||||||
}
|
}
|
||||||
}, [selectedEstablishmentId, reloadFetch, currentSchoolYearPage, searchTerm]);
|
}, [selectedEstablishmentId, reloadFetch]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (selectedEstablishmentId) {
|
if (selectedEstablishmentId) {
|
||||||
const fetchDataAndSetState = () => {
|
const fetchDataAndSetState = () => {
|
||||||
setIsLoading(true);
|
|
||||||
fetchRegisterForms(
|
fetchRegisterForms(
|
||||||
selectedEstablishmentId,
|
selectedEstablishmentId,
|
||||||
CURRENT_YEAR_FILTER,
|
CURRENT_YEAR_FILTER,
|
||||||
@ -247,7 +242,6 @@ export default function Page({ params: { locale } }) {
|
|||||||
.then(registerFormHistoricalDataHandler)
|
.then(registerFormHistoricalDataHandler)
|
||||||
.catch(requestErrorHandler);
|
.catch(requestErrorHandler);
|
||||||
|
|
||||||
setIsLoading(false);
|
|
||||||
setReloadFetch(false);
|
setReloadFetch(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -256,7 +250,12 @@ export default function Page({ params: { locale } }) {
|
|||||||
}, 500); // Debounce la recherche
|
}, 500); // Debounce la recherche
|
||||||
return () => clearTimeout(timeoutId);
|
return () => clearTimeout(timeoutId);
|
||||||
}
|
}
|
||||||
}, [searchTerm]);
|
}, [
|
||||||
|
searchTerm,
|
||||||
|
selectedEstablishmentId,
|
||||||
|
currentSchoolYearPage,
|
||||||
|
itemsPerPage,
|
||||||
|
]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* UseEffect to update page count of tab
|
* UseEffect to update page count of tab
|
||||||
@ -294,15 +293,21 @@ export default function Page({ params: { locale } }) {
|
|||||||
editRegisterForm(row.student.id, formData, csrfToken)
|
editRegisterForm(row.student.id, formData, csrfToken)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
logger.debug('Mandat SEPA uploadé avec succès :', response);
|
logger.debug('Mandat SEPA uploadé avec succès :', response);
|
||||||
setPopupMessage('Le mandat SEPA a été uploadé avec succès.');
|
showNotification(
|
||||||
setPopupVisible(true);
|
'Le mandat SEPA a été uploadé avec succès.',
|
||||||
|
'success',
|
||||||
|
'Succès'
|
||||||
|
);
|
||||||
setReloadFetch(true);
|
setReloadFetch(true);
|
||||||
closeSepaUploadModal();
|
closeSepaUploadModal();
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
logger.error("Erreur lors de l'upload du mandat SEPA :", error);
|
logger.error("Erreur lors de l'upload du mandat SEPA :", error);
|
||||||
setPopupMessage("Erreur lors de l'upload du mandat SEPA.");
|
showNotification(
|
||||||
setPopupVisible(true);
|
"Erreur lors de l'upload du mandat SEPA.",
|
||||||
|
'error',
|
||||||
|
'Erreur'
|
||||||
|
);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -321,21 +326,22 @@ export default function Page({ params: { locale } }) {
|
|||||||
archiveRegisterForm(id)
|
archiveRegisterForm(id)
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
logger.debug('Success:', data);
|
logger.debug('Success:', data);
|
||||||
setPopupMessage(
|
showNotification(
|
||||||
"Le dossier d'inscription a été correctement archivé"
|
"Le dossier d'inscription a été correctement archivé",
|
||||||
|
'success',
|
||||||
|
'Succès'
|
||||||
);
|
);
|
||||||
setPopupVisible(true);
|
|
||||||
setRegistrationForms(
|
setRegistrationForms(
|
||||||
registrationForms.filter((fiche) => fiche.id !== id)
|
registrationForms.filter((fiche) => fiche.id !== id)
|
||||||
);
|
);
|
||||||
setReloadFetch(true);
|
setReloadFetch(true);
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
logger.error('Error archiving data:', error);
|
showNotification(
|
||||||
setPopupMessage(
|
"Erreur lors de l'archivage du dossier d'inscription",
|
||||||
"Erreur lors de l'archivage du dossier d'inscription.\nContactez l'administrateur."
|
'error',
|
||||||
|
'Erreur'
|
||||||
);
|
);
|
||||||
setPopupVisible(true);
|
|
||||||
});
|
});
|
||||||
setConfirmPopupVisible(false);
|
setConfirmPopupVisible(false);
|
||||||
});
|
});
|
||||||
@ -350,16 +356,20 @@ export default function Page({ params: { locale } }) {
|
|||||||
sendRegisterForm(id)
|
sendRegisterForm(id)
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
logger.debug('Success:', data);
|
logger.debug('Success:', data);
|
||||||
setPopupMessage("Le dossier d'inscription a été envoyé avec succès");
|
showNotification(
|
||||||
setPopupVisible(true);
|
"Le dossier d'inscription a été envoyé avec succès",
|
||||||
|
'success',
|
||||||
|
'Succès'
|
||||||
|
);
|
||||||
setReloadFetch(true);
|
setReloadFetch(true);
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
logger.error('Error archiving data:', error);
|
logger.error('Error archiving data:', error);
|
||||||
setPopupMessage(
|
showNotification(
|
||||||
"Erreur lors de l'envoi du dossier d'inscription.\nContactez l'administrateur."
|
"Erreur lors de l'envoi du dossier d'inscription",
|
||||||
|
'error',
|
||||||
|
'Erreur'
|
||||||
);
|
);
|
||||||
setPopupVisible(true);
|
|
||||||
});
|
});
|
||||||
setConfirmPopupVisible(false);
|
setConfirmPopupVisible(false);
|
||||||
});
|
});
|
||||||
@ -694,7 +704,7 @@ export default function Page({ params: { locale } }) {
|
|||||||
];
|
];
|
||||||
|
|
||||||
let emptyMessage;
|
let emptyMessage;
|
||||||
if (activeTab === CURRENT_YEAR_FILTER) {
|
if (activeTab === CURRENT_YEAR_FILTER && searchTerm === '') {
|
||||||
emptyMessage = (
|
emptyMessage = (
|
||||||
<AlertMessage
|
<AlertMessage
|
||||||
type="warning"
|
type="warning"
|
||||||
@ -702,7 +712,7 @@ export default function Page({ params: { locale } }) {
|
|||||||
message="Veuillez procéder à la création d'un nouveau dossier d'inscription pour l'année scolaire en cours."
|
message="Veuillez procéder à la création d'un nouveau dossier d'inscription pour l'année scolaire en cours."
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
} else if (activeTab === NEXT_YEAR_FILTER) {
|
} else if (activeTab === NEXT_YEAR_FILTER && searchTerm === '') {
|
||||||
emptyMessage = (
|
emptyMessage = (
|
||||||
<AlertMessage
|
<AlertMessage
|
||||||
type="info"
|
type="info"
|
||||||
@ -710,7 +720,7 @@ export default function Page({ params: { locale } }) {
|
|||||||
message="Aucun dossier n'a encore été créé pour la prochaine année scolaire."
|
message="Aucun dossier n'a encore été créé pour la prochaine année scolaire."
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
} else if (activeTab === HISTORICAL_FILTER) {
|
} else if (activeTab === HISTORICAL_FILTER && searchTerm === '') {
|
||||||
emptyMessage = (
|
emptyMessage = (
|
||||||
<AlertMessage
|
<AlertMessage
|
||||||
type="info"
|
type="info"
|
||||||
@ -836,12 +846,6 @@ export default function Page({ params: { locale } }) {
|
|||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
<Popup
|
|
||||||
isOpen={popupVisible}
|
|
||||||
message={popupMessage}
|
|
||||||
onConfirm={() => setPopupVisible(false)}
|
|
||||||
uniqueConfirmButton={true}
|
|
||||||
/>
|
|
||||||
<Popup
|
<Popup
|
||||||
isOpen={confirmPopupVisible}
|
isOpen={confirmPopupVisible}
|
||||||
message={confirmPopupMessage}
|
message={confirmPopupMessage}
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import logger from '@/utils/logger';
|
|||||||
import Loader from '@/components/Loader';
|
import Loader from '@/components/Loader';
|
||||||
import { useEstablishment } from '@/context/EstablishmentContext';
|
import { useEstablishment } from '@/context/EstablishmentContext';
|
||||||
import { FE_ADMIN_SUBSCRIPTIONS_URL } from '@/utils/Url';
|
import { FE_ADMIN_SUBSCRIPTIONS_URL } from '@/utils/Url';
|
||||||
|
import { useNotification } from '@/context/NotificationContext';
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
@ -30,6 +31,7 @@ export default function Page() {
|
|||||||
|
|
||||||
const csrfToken = useCsrfToken();
|
const csrfToken = useCsrfToken();
|
||||||
const { selectedEstablishmentId } = useEstablishment();
|
const { selectedEstablishmentId } = useEstablishment();
|
||||||
|
const { showNotification } = useNotification();
|
||||||
|
|
||||||
const requestErrorHandler = (err) => {
|
const requestErrorHandler = (err) => {
|
||||||
logger.error('Error fetching data:', err);
|
logger.error('Error fetching data:', err);
|
||||||
@ -63,11 +65,20 @@ export default function Page() {
|
|||||||
editRegisterForm(studentId, formData, csrfToken)
|
editRegisterForm(studentId, formData, csrfToken)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
logger.debug('RF mis à jour avec succès:', response);
|
logger.debug('RF mis à jour avec succès:', response);
|
||||||
|
showNotification(
|
||||||
|
'Le dossier d\'inscription a été validé avec succès',
|
||||||
|
'success',
|
||||||
|
'Succès'
|
||||||
|
);
|
||||||
router.push(FE_ADMIN_SUBSCRIPTIONS_URL);
|
router.push(FE_ADMIN_SUBSCRIPTIONS_URL);
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
// Logique supplémentaire après la mise à jour (par exemple, redirection ou notification)
|
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
|
showNotification(
|
||||||
|
"Erreur lors de la validation du dossier d'inscription",
|
||||||
|
'error',
|
||||||
|
'Erreur'
|
||||||
|
);
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
logger.error('Erreur lors de la mise à jour du RF:', error);
|
logger.error('Erreur lors de la mise à jour du RF:', error);
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,8 +1,9 @@
|
|||||||
'use client';
|
'use client';
|
||||||
import { useTranslations } from 'next-intl';
|
import { useTranslations } from 'next-intl';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Button from '@/components/Button';
|
import Button from '@/components/Form/Button';
|
||||||
import Logo from '@/components/Logo'; // Import du composant Logo
|
import Logo from '@/components/Logo'; // Import du composant Logo
|
||||||
|
import FormRenderer from '@/components/Form/FormRenderer';
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
const t = useTranslations('homePage');
|
const t = useTranslations('homePage');
|
||||||
@ -13,6 +14,7 @@ export default function Home() {
|
|||||||
<h1 className="text-4xl font-bold mb-4">{t('welcomeParents')}</h1>
|
<h1 className="text-4xl font-bold mb-4">{t('welcomeParents')}</h1>
|
||||||
<p className="text-lg mb-8">{t('pleaseLogin')}</p>
|
<p className="text-lg mb-8">{t('pleaseLogin')}</p>
|
||||||
<Button text={t('loginButton')} primary href="/users/login" />
|
<Button text={t('loginButton')} primary href="/users/login" />
|
||||||
|
<FormRenderer />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,25 +7,47 @@ import { useEstablishment } from '@/context/EstablishmentContext';
|
|||||||
import { FE_PARENTS_HOME_URL } from '@/utils/Url';
|
import { FE_PARENTS_HOME_URL } from '@/utils/Url';
|
||||||
import { editRegisterForm } from '@/app/actions/subscriptionAction';
|
import { editRegisterForm } from '@/app/actions/subscriptionAction';
|
||||||
import logger from '@/utils/logger';
|
import logger from '@/utils/logger';
|
||||||
|
import Loader from '@/components/Loader';
|
||||||
|
import { useNotification } from '@/context/NotificationContext';
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
|
const { showNotification } = useNotification();
|
||||||
const studentId = searchParams.get('studentId');
|
const studentId = searchParams.get('studentId');
|
||||||
const enable = searchParams.get('enabled') === 'true';
|
const enable = searchParams.get('enabled') === 'true';
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const csrfToken = useCsrfToken();
|
const csrfToken = useCsrfToken();
|
||||||
const { selectedEstablishmentId, apiDocuseal } = useEstablishment();
|
const { selectedEstablishmentId, apiDocuseal } = useEstablishment();
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
|
||||||
const handleSubmit = async (data) => {
|
const handleSubmit = (data) => {
|
||||||
try {
|
setIsLoading(true);
|
||||||
const result = await editRegisterForm(studentId, data, csrfToken);
|
editRegisterForm(studentId, data, csrfToken)
|
||||||
logger.debug('Success:', result);
|
.then((result) => {
|
||||||
router.push(FE_PARENTS_HOME_URL);
|
setIsLoading(false);
|
||||||
} catch (error) {
|
logger.debug('Success:', result);
|
||||||
logger.error('Error:', error);
|
showNotification(
|
||||||
}
|
"Dossier d'inscription soumis avec succès",
|
||||||
|
'success',
|
||||||
|
'Succès'
|
||||||
|
);
|
||||||
|
router.push(FE_PARENTS_HOME_URL);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
setIsLoading(false);
|
||||||
|
showNotification(
|
||||||
|
"Erreur lors de la soumission du dossier d'inscription",
|
||||||
|
'error',
|
||||||
|
'Erreur'
|
||||||
|
);
|
||||||
|
logger.error('Error:', error);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (isLoading === true) {
|
||||||
|
return <Loader />; // Affichez le composant Loader
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<InscriptionFormShared
|
<InscriptionFormShared
|
||||||
studentId={studentId}
|
studentId={studentId}
|
||||||
|
|||||||
@ -6,8 +6,7 @@ import { useRouter, usePathname } from 'next/navigation';
|
|||||||
import { MessageSquare, Settings, Home, Menu } from 'lucide-react';
|
import { MessageSquare, Settings, Home, Menu } from 'lucide-react';
|
||||||
import {
|
import {
|
||||||
FE_PARENTS_HOME_URL,
|
FE_PARENTS_HOME_URL,
|
||||||
FE_PARENTS_MESSAGERIE_URL,
|
FE_PARENTS_MESSAGERIE_URL
|
||||||
FE_PARENTS_SETTINGS_URL,
|
|
||||||
} from '@/utils/Url';
|
} from '@/utils/Url';
|
||||||
import ProtectedRoute from '@/components/ProtectedRoute';
|
import ProtectedRoute from '@/components/ProtectedRoute';
|
||||||
import { disconnect } from '@/app/actions/authAction';
|
import { disconnect } from '@/app/actions/authAction';
|
||||||
@ -41,13 +40,7 @@ export default function Layout({ children }) {
|
|||||||
name: 'Messagerie',
|
name: 'Messagerie',
|
||||||
url: FE_PARENTS_MESSAGERIE_URL,
|
url: FE_PARENTS_MESSAGERIE_URL,
|
||||||
icon: MessageSquare,
|
icon: MessageSquare,
|
||||||
},
|
}
|
||||||
{
|
|
||||||
id: 'settings',
|
|
||||||
name: 'Paramètres',
|
|
||||||
url: FE_PARENTS_SETTINGS_URL,
|
|
||||||
icon: Settings,
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|
||||||
// Déterminer la page actuelle pour la sidebar
|
// Déterminer la page actuelle pour la sidebar
|
||||||
|
|||||||
@ -2,19 +2,31 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import Table from '@/components/Table';
|
import Table from '@/components/Table';
|
||||||
import { Edit3, Users, Download, Eye, Upload } from 'lucide-react';
|
import {
|
||||||
|
Edit3,
|
||||||
|
Users,
|
||||||
|
Download,
|
||||||
|
Eye,
|
||||||
|
Upload,
|
||||||
|
CalendarDays,
|
||||||
|
} from 'lucide-react';
|
||||||
import StatusLabel from '@/components/StatusLabel';
|
import StatusLabel from '@/components/StatusLabel';
|
||||||
import FileUpload from '@/components/FileUpload';
|
import FileUpload from '@/components/Form/FileUpload';
|
||||||
import { FE_PARENTS_EDIT_SUBSCRIPTION_URL } from '@/utils/Url';
|
import { FE_PARENTS_EDIT_SUBSCRIPTION_URL } from '@/utils/Url';
|
||||||
import {
|
import {
|
||||||
fetchChildren,
|
fetchChildren,
|
||||||
editRegisterForm,
|
editRegisterForm,
|
||||||
} from '@/app/actions/subscriptionAction';
|
} from '@/app/actions/subscriptionAction';
|
||||||
|
import { fetchUpcomingEvents } from '@/app/actions/planningAction';
|
||||||
import logger from '@/utils/logger';
|
import logger from '@/utils/logger';
|
||||||
import { BASE_URL } from '@/utils/Url';
|
import { BASE_URL } from '@/utils/Url';
|
||||||
import { useEstablishment } from '@/context/EstablishmentContext';
|
import { useEstablishment } from '@/context/EstablishmentContext';
|
||||||
import { useCsrfToken } from '@/context/CsrfContext';
|
import { useCsrfToken } from '@/context/CsrfContext';
|
||||||
import { useClasses } from '@/context/ClassesContext';
|
import { useClasses } from '@/context/ClassesContext';
|
||||||
|
import { PlanningProvider, PlanningModes } from '@/context/PlanningContext';
|
||||||
|
import SectionHeader from '@/components/SectionHeader';
|
||||||
|
import ParentPlanningSection from '@/components/ParentPlanningSection';
|
||||||
|
import EventCard from '@/components/EventCard';
|
||||||
|
|
||||||
export default function ParentHomePage() {
|
export default function ParentHomePage() {
|
||||||
const [children, setChildren] = useState([]);
|
const [children, setChildren] = useState([]);
|
||||||
@ -22,6 +34,9 @@ export default function ParentHomePage() {
|
|||||||
const [uploadingStudentId, setUploadingStudentId] = useState(null); // ID de l'étudiant pour l'upload
|
const [uploadingStudentId, setUploadingStudentId] = useState(null); // ID de l'étudiant pour l'upload
|
||||||
const [uploadedFile, setUploadedFile] = useState(null); // Fichier uploadé
|
const [uploadedFile, setUploadedFile] = useState(null); // Fichier uploadé
|
||||||
const [uploadState, setUploadState] = useState('off'); // État "on" ou "off" pour l'affichage du composant
|
const [uploadState, setUploadState] = useState('off'); // État "on" ou "off" pour l'affichage du composant
|
||||||
|
const [showPlanning, setShowPlanning] = useState(false);
|
||||||
|
const [planningClassName, setPlanningClassName] = useState(null);
|
||||||
|
const [upcomingEvents, setUpcomingEvents] = useState([]);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const csrfToken = useCsrfToken();
|
const csrfToken = useCsrfToken();
|
||||||
const [reloadFetch, setReloadFetch] = useState(false);
|
const [reloadFetch, setReloadFetch] = useState(false);
|
||||||
@ -35,7 +50,20 @@ export default function ParentHomePage() {
|
|||||||
});
|
});
|
||||||
setReloadFetch(false);
|
setReloadFetch(false);
|
||||||
}
|
}
|
||||||
}, [selectedEstablishmentId, reloadFetch]);
|
}, [selectedEstablishmentId, reloadFetch, user]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (selectedEstablishmentId) {
|
||||||
|
// Fetch des événements à venir
|
||||||
|
fetchUpcomingEvents(selectedEstablishmentId)
|
||||||
|
.then((data) => {
|
||||||
|
setUpcomingEvents(data);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
logger.error('Error fetching upcoming events:', error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [selectedEstablishmentId]);
|
||||||
|
|
||||||
function handleView(eleveId) {
|
function handleView(eleveId) {
|
||||||
logger.debug(`View dossier for student id: ${eleveId}`);
|
logger.debug(`View dossier for student id: ${eleveId}`);
|
||||||
@ -97,6 +125,13 @@ export default function ParentHomePage() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const showClassPlanning = (student) => {
|
||||||
|
setPlanningClassName(
|
||||||
|
`${student.associated_class_name} - ${getNiveauLabel(student.level)}`
|
||||||
|
);
|
||||||
|
setShowPlanning(true);
|
||||||
|
};
|
||||||
|
|
||||||
const childrenColumns = [
|
const childrenColumns = [
|
||||||
{
|
{
|
||||||
name: 'photo',
|
name: 'photo',
|
||||||
@ -127,6 +162,12 @@ export default function ParentHomePage() {
|
|||||||
},
|
},
|
||||||
{ name: 'Nom', transform: (row) => `${row.student.last_name}` },
|
{ name: 'Nom', transform: (row) => `${row.student.last_name}` },
|
||||||
{ name: 'Prénom', transform: (row) => `${row.student.first_name}` },
|
{ name: 'Prénom', transform: (row) => `${row.student.first_name}` },
|
||||||
|
{
|
||||||
|
name: 'Classe',
|
||||||
|
transform: (row) => (
|
||||||
|
<div className="text-center">{row.student.associated_class_name}</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'Niveau',
|
name: 'Niveau',
|
||||||
transform: (row) => (
|
transform: (row) => (
|
||||||
@ -192,7 +233,6 @@ export default function ParentHomePage() {
|
|||||||
>
|
>
|
||||||
<Download className="h-5 w-5" />
|
<Download className="h-5 w-5" />
|
||||||
</a>
|
</a>
|
||||||
{/* Nouvelle action Upload */}
|
|
||||||
<button
|
<button
|
||||||
className={`flex items-center justify-center w-8 h-8 rounded-full ${
|
className={`flex items-center justify-center w-8 h-8 rounded-full ${
|
||||||
uploadingStudentId === row.student.id && uploadState === 'on'
|
uploadingStudentId === row.student.id && uploadState === 'on'
|
||||||
@ -201,7 +241,7 @@ export default function ParentHomePage() {
|
|||||||
}`}
|
}`}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
toggleUpload(row.student.id); // Activer ou désactiver l'upload pour cet étudiant
|
toggleUpload(row.student.id);
|
||||||
}}
|
}}
|
||||||
aria-label="Uploader un fichier"
|
aria-label="Uploader un fichier"
|
||||||
>
|
>
|
||||||
@ -211,16 +251,28 @@ export default function ParentHomePage() {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{row.status === 5 && (
|
{row.status === 5 && (
|
||||||
<button
|
<>
|
||||||
className="text-purple-500 hover:text-purple-700"
|
<button
|
||||||
onClick={(e) => {
|
className="text-purple-500 hover:text-purple-700"
|
||||||
e.stopPropagation();
|
onClick={(e) => {
|
||||||
handleView(row.student.id);
|
e.stopPropagation();
|
||||||
}}
|
handleView(row.student.id);
|
||||||
aria-label="Visualiser le dossier"
|
}}
|
||||||
>
|
aria-label="Visualiser le dossier"
|
||||||
<Eye className="h-5 w-5" />
|
>
|
||||||
</button>
|
<Eye className="h-5 w-5" />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="text-emerald-500 hover:text-emerald-700 ml-1"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
showClassPlanning(row.student);
|
||||||
|
}}
|
||||||
|
aria-label="Voir le planning de la classe"
|
||||||
|
>
|
||||||
|
<CalendarDays className="h-5 w-5" />
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
@ -228,40 +280,77 @@ export default function ParentHomePage() {
|
|||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="px-2 py-4 md:px-4 max-w-full">
|
<div className="w-full h-full">
|
||||||
<div>
|
{showPlanning && planningClassName ? (
|
||||||
<h2 className="text-xl font-semibold mb-3 px-1 flex items-center gap-2">
|
// Affichage grand format mais respectant la sidebar
|
||||||
<Users className="h-6 w-6 text-emerald-600" />
|
<>
|
||||||
Enfants
|
<div className="p-4 flex items-center border-b">
|
||||||
</h2>
|
|
||||||
<div className="overflow-x-auto">
|
|
||||||
<Table
|
|
||||||
data={children}
|
|
||||||
columns={childrenColumns}
|
|
||||||
defaultTheme="bg-gray-50"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{/* Composant FileUpload et bouton Valider en dessous du tableau */}
|
|
||||||
{uploadState === 'on' && uploadingStudentId && (
|
|
||||||
<div className="mt-4">
|
|
||||||
<FileUpload
|
|
||||||
selectionMessage="Sélectionnez un fichier à uploader"
|
|
||||||
onFileSelect={handleFileUpload}
|
|
||||||
/>
|
|
||||||
<button
|
<button
|
||||||
className={`mt-4 px-6 py-2 rounded-md ${
|
className="text-emerald-600 hover:text-emerald-800 font-semibold flex items-center"
|
||||||
uploadedFile
|
onClick={() => setShowPlanning(false)}
|
||||||
? 'bg-emerald-500 text-white hover:bg-emerald-600'
|
|
||||||
: 'bg-gray-300 text-gray-700 cursor-not-allowed'
|
|
||||||
}`}
|
|
||||||
onClick={handleSubmit}
|
|
||||||
disabled={!uploadedFile}
|
|
||||||
>
|
>
|
||||||
Valider
|
← Retour
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
<div className="flex-1 flex overflow-hidden">
|
||||||
</div>
|
<PlanningProvider
|
||||||
|
establishmentId={selectedEstablishmentId}
|
||||||
|
modeSet={PlanningModes.CLASS_SCHEDULE}
|
||||||
|
readOnly={true}
|
||||||
|
>
|
||||||
|
<ParentPlanningSection planningClassName={planningClassName} />
|
||||||
|
</PlanningProvider>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
// Affichage classique avec le tableau des enfants
|
||||||
|
<div>
|
||||||
|
{/* Section des événements à venir */}
|
||||||
|
{upcomingEvents.length > 0 && (
|
||||||
|
<div className="mb-6">
|
||||||
|
<SectionHeader
|
||||||
|
icon={CalendarDays}
|
||||||
|
title="Événements à venir"
|
||||||
|
description="Prochains événements de l'établissement"
|
||||||
|
/>
|
||||||
|
<div className="bg-stone-50 p-4 rounded-lg shadow-sm border border-gray-100">
|
||||||
|
{upcomingEvents.slice(0, 3).map((event, index) => (
|
||||||
|
<EventCard key={index} {...event} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<SectionHeader
|
||||||
|
icon={Users}
|
||||||
|
title="Vos enfants"
|
||||||
|
description="Suivez le parcours de vos enfants"
|
||||||
|
/>
|
||||||
|
<div className="overflow-x-auto">
|
||||||
|
<Table data={children} columns={childrenColumns} />
|
||||||
|
</div>
|
||||||
|
{/* Composant FileUpload et bouton Valider en dessous du tableau */}
|
||||||
|
{uploadState === 'on' && uploadingStudentId && (
|
||||||
|
<div className="mt-4">
|
||||||
|
<FileUpload
|
||||||
|
selectionMessage="Sélectionnez un fichier à uploader"
|
||||||
|
onFileSelect={handleFileUpload}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
className={`mt-4 px-6 py-2 rounded-md ${
|
||||||
|
uploadedFile
|
||||||
|
? 'bg-emerald-500 text-white hover:bg-emerald-600'
|
||||||
|
: 'bg-gray-300 text-gray-700 cursor-not-allowed'
|
||||||
|
}`}
|
||||||
|
onClick={handleSubmit}
|
||||||
|
disabled={!uploadedFile}
|
||||||
|
>
|
||||||
|
Valider
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
'use client';
|
'use client';
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import Button from '@/components/Button';
|
import Button from '@/components/Form/Button';
|
||||||
import InputText from '@/components/InputText';
|
import InputText from '@/components/Form/InputText';
|
||||||
import logger from '@/utils/logger';
|
import logger from '@/utils/logger';
|
||||||
import { useNotification } from '@/context/NotificationContext';
|
import { useNotification } from '@/context/NotificationContext';
|
||||||
|
|
||||||
|
|||||||
@ -3,9 +3,9 @@ import React, { useState } from 'react';
|
|||||||
import DjangoCSRFToken from '@/components/DjangoCSRFToken';
|
import DjangoCSRFToken from '@/components/DjangoCSRFToken';
|
||||||
import Logo from '@/components/Logo';
|
import Logo from '@/components/Logo';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import InputTextIcon from '@/components/InputTextIcon';
|
import InputTextIcon from '@/components/Form/InputTextIcon';
|
||||||
import Loader from '@/components/Loader'; // Importez le composant Loader
|
import Loader from '@/components/Loader'; // Importez le composant Loader
|
||||||
import Button from '@/components/Button'; // Importez le composant Button
|
import Button from '@/components/Form/Button'; // Importez le composant Button
|
||||||
import { User, KeySquare } from 'lucide-react'; // Importez directement les icônes nécessaires
|
import { User, KeySquare } from 'lucide-react'; // Importez directement les icônes nécessaires
|
||||||
import { FE_USERS_NEW_PASSWORD_URL, getRedirectUrlFromRole } from '@/utils/Url';
|
import { FE_USERS_NEW_PASSWORD_URL, getRedirectUrlFromRole } from '@/utils/Url';
|
||||||
import { login } from '@/app/actions/authAction';
|
import { login } from '@/app/actions/authAction';
|
||||||
@ -14,20 +14,18 @@ import { useCsrfToken } from '@/context/CsrfContext'; // Importez le hook useCsr
|
|||||||
import logger from '@/utils/logger';
|
import logger from '@/utils/logger';
|
||||||
|
|
||||||
import { useEstablishment } from '@/context/EstablishmentContext';
|
import { useEstablishment } from '@/context/EstablishmentContext';
|
||||||
|
import { useNotification } from '@/context/NotificationContext';
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
const [errorMessage, setErrorMessage] = useState('');
|
|
||||||
const [userFieldError, setUserFieldError] = useState('');
|
|
||||||
const [passwordFieldError, setPasswordFieldError] = useState('');
|
|
||||||
const { initializeContextWithSession } = useEstablishment();
|
const { initializeContextWithSession } = useEstablishment();
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const csrfToken = useCsrfToken(); // Utilisez le hook useCsrfToken
|
const csrfToken = useCsrfToken(); // Utilisez le hook useCsrfToken
|
||||||
|
const { showNotification } = useNotification();
|
||||||
|
|
||||||
function handleFormLogin(formData) {
|
function handleFormLogin(formData) {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
setErrorMessage('');
|
|
||||||
|
|
||||||
login({
|
login({
|
||||||
email: formData.get('login'),
|
email: formData.get('login'),
|
||||||
@ -37,7 +35,7 @@ export default function Page() {
|
|||||||
logger.debug('Sign In Result', result);
|
logger.debug('Sign In Result', result);
|
||||||
|
|
||||||
if (result.error) {
|
if (result.error) {
|
||||||
setErrorMessage(result.error);
|
showNotification(result.error, 'error', 'Erreur');
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
} else {
|
} else {
|
||||||
// On initialise le contexte establishement avec la session
|
// On initialise le contexte establishement avec la session
|
||||||
@ -48,7 +46,7 @@ export default function Page() {
|
|||||||
if (url) {
|
if (url) {
|
||||||
router.push(url);
|
router.push(url);
|
||||||
} else {
|
} else {
|
||||||
setErrorMessage('Type de rôle non géré');
|
showNotification('Type de rôle non géré', 'error', 'Erreur');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
@ -59,8 +57,10 @@ export default function Page() {
|
|||||||
error
|
error
|
||||||
);
|
);
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
setErrorMessage(
|
showNotification(
|
||||||
'Une erreur est survenue lors de la récupération de la session.'
|
'Une erreur est survenue lors de la récupération de la session.',
|
||||||
|
'error',
|
||||||
|
'Erreur'
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -68,7 +68,11 @@ export default function Page() {
|
|||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
logger.error('Erreur lors de la connexion:', error);
|
logger.error('Erreur lors de la connexion:', error);
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
setErrorMessage('Une erreur est survenue lors de la connexion.');
|
showNotification(
|
||||||
|
'Une erreur est survenue lors de la connexion.',
|
||||||
|
'error',
|
||||||
|
'Erreur'
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -98,7 +102,6 @@ export default function Page() {
|
|||||||
IconItem={User}
|
IconItem={User}
|
||||||
label="Identifiant"
|
label="Identifiant"
|
||||||
placeholder="Identifiant"
|
placeholder="Identifiant"
|
||||||
errorMsg={userFieldError}
|
|
||||||
className="w-full mb-5"
|
className="w-full mb-5"
|
||||||
/>
|
/>
|
||||||
<InputTextIcon
|
<InputTextIcon
|
||||||
@ -107,11 +110,9 @@ export default function Page() {
|
|||||||
IconItem={KeySquare}
|
IconItem={KeySquare}
|
||||||
label="Mot de passe"
|
label="Mot de passe"
|
||||||
placeholder="Mot de passe"
|
placeholder="Mot de passe"
|
||||||
errorMsg={passwordFieldError}
|
|
||||||
className="w-full mb-5"
|
className="w-full mb-5"
|
||||||
/>
|
/>
|
||||||
<div className="input-group mb-4"></div>
|
<div className="input-group mb-4"></div>
|
||||||
<label className="text-red-500">{errorMessage}</label>
|
|
||||||
<label>
|
<label>
|
||||||
<a
|
<a
|
||||||
className="float-right mb-4"
|
className="float-right mb-4"
|
||||||
|
|||||||
@ -3,68 +3,49 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import DjangoCSRFToken from '@/components/DjangoCSRFToken';
|
import DjangoCSRFToken from '@/components/DjangoCSRFToken';
|
||||||
import Logo from '@/components/Logo';
|
import Logo from '@/components/Logo';
|
||||||
import { useSearchParams } from 'next/navigation';
|
import InputTextIcon from '@/components/Form/InputTextIcon';
|
||||||
import InputTextIcon from '@/components/InputTextIcon';
|
import Loader from '@/components/Loader';
|
||||||
import Loader from '@/components/Loader'; // Importez le composant Loader
|
import Button from '@/components/Form/Button';
|
||||||
import Button from '@/components/Button'; // Importez le composant Button
|
import { User } from 'lucide-react';
|
||||||
import Popup from '@/components/Popup'; // Importez le composant Popup
|
|
||||||
import { User } from 'lucide-react'; // Importez directement les icônes nécessaires
|
|
||||||
import { FE_USERS_LOGIN_URL } from '@/utils/Url';
|
import { FE_USERS_LOGIN_URL } from '@/utils/Url';
|
||||||
import { useCsrfToken } from '@/context/CsrfContext';
|
import { useCsrfToken } from '@/context/CsrfContext';
|
||||||
import { sendNewPassword } from '@/app/actions/authAction';
|
import { sendNewPassword } from '@/app/actions/authAction';
|
||||||
import logger from '@/utils/logger';
|
import logger from '@/utils/logger';
|
||||||
|
import { useNotification } from '@/context/NotificationContext';
|
||||||
const useFakeData = process.env.NEXT_PUBLIC_USE_FAKE_DATA === 'true';
|
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
const searchParams = useSearchParams();
|
const { showNotification } = useNotification();
|
||||||
const [errorMessage, setErrorMessage] = useState('');
|
|
||||||
const [userFieldError, setUserFieldError] = useState('');
|
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [popupVisible, setPopupVisible] = useState(false);
|
|
||||||
const [popupMessage, setPopupMessage] = useState('');
|
|
||||||
const [popupConfirmAction, setPopupConfirmAction] = useState(null);
|
|
||||||
const csrfToken = useCsrfToken();
|
const csrfToken = useCsrfToken();
|
||||||
|
|
||||||
function validate(formData) {
|
function validate(formData) {
|
||||||
if (useFakeData) {
|
const data = { email: formData.get('email') };
|
||||||
setTimeout(() => {
|
setIsLoading(true);
|
||||||
setUserFieldError('');
|
sendNewPassword(data, csrfToken)
|
||||||
setErrorMessage('');
|
.then((data) => {
|
||||||
setPopupMessage('Mot de passe réinitialisé avec succès !');
|
logger.debug('Success:', data);
|
||||||
setPopupConfirmAction(() => () => setPopupVisible(false));
|
if (data.message !== '') {
|
||||||
setPopupVisible(true);
|
showNotification(data.message, 'success', 'Succès');
|
||||||
}, 1000); // Simule un délai de traitement
|
router.push(`${FE_USERS_LOGIN_URL}`);
|
||||||
} else {
|
} else {
|
||||||
const data = { email: formData.get('email') };
|
if (data.errorMessage) {
|
||||||
sendNewPassword(data, csrfToken)
|
showNotification(data.errorMessage, 'error', 'Erreur');
|
||||||
.then((data) => {
|
} else if (data.errorFields) {
|
||||||
logger.debug('Success:', data);
|
showNotification(data.errorFields.email, 'error', 'Erreur');
|
||||||
setUserFieldError('');
|
|
||||||
setErrorMessage('');
|
|
||||||
if (data.errorMessage === '') {
|
|
||||||
setPopupMessage(data.message);
|
|
||||||
setPopupConfirmAction(() => () => setPopupVisible(false));
|
|
||||||
setPopupVisible(true);
|
|
||||||
} else {
|
|
||||||
if (data.errorFields) {
|
|
||||||
setUserFieldError(data.errorFields.email);
|
|
||||||
}
|
|
||||||
if (data.errorMessage) {
|
|
||||||
setErrorMessage(data.errorMessage);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
.catch((error) => {
|
setIsLoading(false);
|
||||||
logger.error('Error fetching data:', error);
|
})
|
||||||
error = error.errorMessage;
|
.catch((error) => {
|
||||||
logger.debug(error);
|
logger.error('Error fetching data:', error);
|
||||||
});
|
setIsLoading(false);
|
||||||
}
|
error = error.errorMessage;
|
||||||
|
logger.debug(error);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isLoading === true) {
|
if (isLoading === true) {
|
||||||
return <Loader />; // Affichez le composant Loader
|
return <Loader />;
|
||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -89,10 +70,8 @@ export default function Page() {
|
|||||||
IconItem={User}
|
IconItem={User}
|
||||||
label="Identifiant"
|
label="Identifiant"
|
||||||
placeholder="Identifiant"
|
placeholder="Identifiant"
|
||||||
errorMsg={userFieldError}
|
|
||||||
className="w-full"
|
className="w-full"
|
||||||
/>
|
/>
|
||||||
<p className="text-red-500">{errorMessage}</p>
|
|
||||||
<div className="form-group-submit mt-4">
|
<div className="form-group-submit mt-4">
|
||||||
<Button
|
<Button
|
||||||
text="Réinitialiser"
|
text="Réinitialiser"
|
||||||
@ -112,12 +91,6 @@ export default function Page() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Popup
|
|
||||||
isOpen={popupVisible}
|
|
||||||
message={popupMessage}
|
|
||||||
onConfirm={popupConfirmAction}
|
|
||||||
onCancel={() => setPopupVisible(false)}
|
|
||||||
/>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,111 +5,63 @@ import React, { useState, useEffect } from 'react';
|
|||||||
import DjangoCSRFToken from '@/components/DjangoCSRFToken';
|
import DjangoCSRFToken from '@/components/DjangoCSRFToken';
|
||||||
import Logo from '@/components/Logo';
|
import Logo from '@/components/Logo';
|
||||||
import { useSearchParams, useRouter } from 'next/navigation';
|
import { useSearchParams, useRouter } from 'next/navigation';
|
||||||
import InputTextIcon from '@/components/InputTextIcon';
|
import InputTextIcon from '@/components/Form/InputTextIcon';
|
||||||
import Loader from '@/components/Loader'; // Importez le composant Loader
|
import Loader from '@/components/Loader';
|
||||||
import Button from '@/components/Button'; // Importez le composant Button
|
import Button from '@/components/Form/Button';
|
||||||
import Popup from '@/components/Popup';
|
|
||||||
import { FE_USERS_LOGIN_URL } from '@/utils/Url';
|
import { FE_USERS_LOGIN_URL } from '@/utils/Url';
|
||||||
import { KeySquare } from 'lucide-react'; // Importez directement les icônes nécessaires
|
import { KeySquare } from 'lucide-react';
|
||||||
import { useCsrfToken } from '@/context/CsrfContext';
|
import { useCsrfToken } from '@/context/CsrfContext';
|
||||||
import { getResetPassword, resetPassword } from '@/app/actions/authAction';
|
import { resetPassword } from '@/app/actions/authAction';
|
||||||
import logger from '@/utils/logger';
|
import logger from '@/utils/logger';
|
||||||
|
import { useNotification } from '@/context/NotificationContext';
|
||||||
const useFakeData = process.env.NEXT_PUBLIC_USE_FAKE_DATA === 'true';
|
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
|
const { showNotification } = useNotification();
|
||||||
const uuid = searchParams.get('uuid');
|
const uuid = searchParams.get('uuid');
|
||||||
const [errorMessage, setErrorMessage] = useState('');
|
|
||||||
const [password1FieldError, setPassword1FieldError] = useState('');
|
|
||||||
const [password2FieldError, setPassword2FieldError] = useState('');
|
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [popupVisible, setPopupVisible] = useState(false);
|
|
||||||
const [popupMessage, setPopupMessage] = useState('');
|
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const csrfToken = useCsrfToken();
|
const csrfToken = useCsrfToken();
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (useFakeData) {
|
|
||||||
setTimeout(() => {
|
|
||||||
setIsLoading(false);
|
|
||||||
}, 1000);
|
|
||||||
} else {
|
|
||||||
getResetPassword(uuid)
|
|
||||||
.then((data) => {
|
|
||||||
logger.debug('Success:', data);
|
|
||||||
setIsLoading(true);
|
|
||||||
if (data.errorFields) {
|
|
||||||
setPassword1FieldError(data.errorFields.password1);
|
|
||||||
setPassword2FieldError(data.errorFields.password2);
|
|
||||||
}
|
|
||||||
if (data.errorMessage) {
|
|
||||||
setErrorMessage(data.errorMessage);
|
|
||||||
}
|
|
||||||
setIsLoading(false);
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
logger.error('Error fetching data:', error);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
function validate(formData) {
|
function validate(formData) {
|
||||||
if (useFakeData) {
|
const data = {
|
||||||
setTimeout(() => {
|
password1: formData.get('password1'),
|
||||||
setPopupMessage('Mot de passe réinitialisé avec succès');
|
password2: formData.get('password2'),
|
||||||
setPopupVisible(true);
|
};
|
||||||
}, 1000);
|
setIsLoading(true);
|
||||||
} else {
|
resetPassword(uuid, data, csrfToken)
|
||||||
const data = {
|
.then((data) => {
|
||||||
password1: formData.get('password1'),
|
if (data.message !== '') {
|
||||||
password2: formData.get('password2'),
|
|
||||||
};
|
|
||||||
resetPassword(uuid, data, csrfToken)
|
|
||||||
.then((data) => {
|
|
||||||
logger.debug('Success:', data);
|
logger.debug('Success:', data);
|
||||||
setPassword1FieldError('');
|
showNotification(data.message, 'success', 'Succès');
|
||||||
setPassword2FieldError('');
|
router.push(`${FE_USERS_LOGIN_URL}`);
|
||||||
setErrorMessage('');
|
} else {
|
||||||
if (data.errorMessage === '') {
|
if (data.errorMessage) {
|
||||||
setPopupMessage(data.message);
|
showNotification(data.errorMessage, 'error', 'Erreur');
|
||||||
setPopupVisible(true);
|
} else if (data.errorFields) {
|
||||||
} else {
|
showNotification(
|
||||||
if (data.errorMessage) {
|
data.errorFields.password1 || data.errorFields.password2,
|
||||||
setErrorMessage(data.errorMessage);
|
'error',
|
||||||
}
|
'Erreur'
|
||||||
if (data.errorFields) {
|
);
|
||||||
setPassword1FieldError(data.errorFields.password1);
|
|
||||||
setPassword2FieldError(data.errorFields.password2);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
.catch((error) => {
|
setIsLoading(false);
|
||||||
logger.error('Error fetching data:', error);
|
})
|
||||||
error = error.errorMessage;
|
.catch((error) => {
|
||||||
logger.debug(error);
|
logger.error('Error fetching data:', error);
|
||||||
});
|
error = error.errorMessage;
|
||||||
}
|
logger.debug(error);
|
||||||
|
setIsLoading(false);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isLoading === true) {
|
if (isLoading === true) {
|
||||||
return <Loader />; // Affichez le composant Loader
|
return <Loader />;
|
||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Popup
|
|
||||||
isOpen={popupVisible}
|
|
||||||
setIsOpen={setPopupVisible}
|
|
||||||
message={popupMessage}
|
|
||||||
onConfirm={() => {
|
|
||||||
setPopupVisible(false);
|
|
||||||
router.push(`${FE_USERS_LOGIN_URL}`);
|
|
||||||
}}
|
|
||||||
onCancel={() => setPopupVisible(false)}
|
|
||||||
uniqueConfirmButton={true}
|
|
||||||
popupClassName="w-full max-w-xs sm:max-w-md"
|
|
||||||
/>
|
|
||||||
<div className="container max mx-auto p-4">
|
<div className="container max mx-auto p-4">
|
||||||
<div className="flex justify-center mb-4">
|
<div className="flex justify-center mb-4">
|
||||||
<Logo className="h-150 w-150" />
|
<Logo className="h-150 w-150" />
|
||||||
@ -131,7 +83,6 @@ export default function Page() {
|
|||||||
IconItem={KeySquare}
|
IconItem={KeySquare}
|
||||||
label="Mot de passe"
|
label="Mot de passe"
|
||||||
placeholder="Mot de passe"
|
placeholder="Mot de passe"
|
||||||
errorMsg={password1FieldError}
|
|
||||||
className="w-full mb-5"
|
className="w-full mb-5"
|
||||||
/>
|
/>
|
||||||
<InputTextIcon
|
<InputTextIcon
|
||||||
@ -140,10 +91,8 @@ export default function Page() {
|
|||||||
IconItem={KeySquare}
|
IconItem={KeySquare}
|
||||||
label="Confirmation mot de passe"
|
label="Confirmation mot de passe"
|
||||||
placeholder="Confirmation mot de passe"
|
placeholder="Confirmation mot de passe"
|
||||||
errorMsg={password2FieldError}
|
|
||||||
className="w-full"
|
className="w-full"
|
||||||
/>
|
/>
|
||||||
<label className="text-red-500">{errorMessage}</label>
|
|
||||||
<div className="form-group-submit mt-4">
|
<div className="form-group-submit mt-4">
|
||||||
<Button
|
<Button
|
||||||
text="Enregistrer"
|
text="Enregistrer"
|
||||||
|
|||||||
@ -1,40 +1,29 @@
|
|||||||
'use client';
|
'use client';
|
||||||
// src/app/pages/subscribe.js
|
|
||||||
|
|
||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import DjangoCSRFToken from '@/components/DjangoCSRFToken';
|
import DjangoCSRFToken from '@/components/DjangoCSRFToken';
|
||||||
import Logo from '@/components/Logo';
|
import Logo from '@/components/Logo';
|
||||||
import { useSearchParams, useRouter } from 'next/navigation';
|
import { useSearchParams, useRouter } from 'next/navigation';
|
||||||
import InputTextIcon from '@/components/InputTextIcon';
|
import InputTextIcon from '@/components/Form/InputTextIcon';
|
||||||
import Loader from '@/components/Loader'; // Importez le composant Loader
|
import Loader from '@/components/Loader';
|
||||||
import Button from '@/components/Button'; // Importez le composant Button
|
import Button from '@/components/Form/Button';
|
||||||
import Popup from '@/components/Popup'; // Importez le composant Popup
|
import { User, KeySquare } from 'lucide-react';
|
||||||
import { User, KeySquare } from 'lucide-react'; // Importez directement les icônes nécessaires
|
|
||||||
import { FE_USERS_LOGIN_URL } from '@/utils/Url';
|
import { FE_USERS_LOGIN_URL } from '@/utils/Url';
|
||||||
import { useCsrfToken } from '@/context/CsrfContext';
|
import { useCsrfToken } from '@/context/CsrfContext';
|
||||||
import { subscribe } from '@/app/actions/authAction';
|
import { subscribe } from '@/app/actions/authAction';
|
||||||
import logger from '@/utils/logger';
|
import logger from '@/utils/logger';
|
||||||
|
import { useNotification } from '@/context/NotificationContext';
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
|
const { showNotification } = useNotification();
|
||||||
const [errorMessage, setErrorMessage] = useState('');
|
|
||||||
const [userFieldError, setUserFieldError] = useState('');
|
|
||||||
const [password1FieldError, setPassword1FieldError] = useState('');
|
|
||||||
const [password2FieldError, setPassword2FieldError] = useState('');
|
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [popupVisible, setPopupVisible] = useState(false);
|
|
||||||
const [popupMessage, setPopupMessage] = useState('');
|
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const csrfToken = useCsrfToken();
|
const csrfToken = useCsrfToken();
|
||||||
|
|
||||||
const establishment_id = searchParams.get('establishment_id');
|
const establishment_id = searchParams.get('establishment_id');
|
||||||
|
|
||||||
function isOK(data) {
|
|
||||||
return data.errorMessage === '';
|
|
||||||
}
|
|
||||||
|
|
||||||
function subscribeFormSubmit(formData) {
|
function subscribeFormSubmit(formData) {
|
||||||
const data = {
|
const data = {
|
||||||
email: formData.get('login'),
|
email: formData.get('login'),
|
||||||
@ -46,25 +35,23 @@ export default function Page() {
|
|||||||
subscribe(data, csrfToken)
|
subscribe(data, csrfToken)
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
logger.debug('Success:', data);
|
logger.debug('Success:', data);
|
||||||
setUserFieldError('');
|
if (data.message !== '') {
|
||||||
setPassword1FieldError('');
|
showNotification(data.message, 'success', 'Succès');
|
||||||
setPassword2FieldError('');
|
router.push(`${FE_USERS_LOGIN_URL}`);
|
||||||
setErrorMessage('');
|
|
||||||
if (isOK(data)) {
|
|
||||||
setIsLoading(false);
|
|
||||||
setPopupMessage(data.message);
|
|
||||||
setPopupVisible(true);
|
|
||||||
} else {
|
} else {
|
||||||
setIsLoading(false);
|
|
||||||
if (data.errorMessage) {
|
if (data.errorMessage) {
|
||||||
setErrorMessage(data.errorMessage);
|
showNotification(data.errorMessage, 'error', 'Erreur');
|
||||||
}
|
} else if (data.errorFields) {
|
||||||
if (data.errorFields) {
|
showNotification(
|
||||||
setUserFieldError(data.errorFields.email);
|
data.errorFields.email ||
|
||||||
setPassword1FieldError(data.errorFields.password1);
|
data.errorFields.password1 ||
|
||||||
setPassword2FieldError(data.errorFields.password2);
|
data.errorFields.password2,
|
||||||
|
'error',
|
||||||
|
'Erreur'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
setIsLoading(false);
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
@ -75,7 +62,7 @@ export default function Page() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isLoading === true) {
|
if (isLoading === true) {
|
||||||
return <Loader />; // Affichez le composant Loader
|
return <Loader />;
|
||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -100,7 +87,6 @@ export default function Page() {
|
|||||||
IconItem={User}
|
IconItem={User}
|
||||||
label="Identifiant"
|
label="Identifiant"
|
||||||
placeholder="Identifiant"
|
placeholder="Identifiant"
|
||||||
errorMsg={userFieldError}
|
|
||||||
className="w-full mb-5"
|
className="w-full mb-5"
|
||||||
/>
|
/>
|
||||||
<InputTextIcon
|
<InputTextIcon
|
||||||
@ -109,7 +95,6 @@ export default function Page() {
|
|||||||
IconItem={KeySquare}
|
IconItem={KeySquare}
|
||||||
label="Mot de passe"
|
label="Mot de passe"
|
||||||
placeholder="Mot de passe"
|
placeholder="Mot de passe"
|
||||||
errorMsg={password1FieldError}
|
|
||||||
className="w-full mb-5"
|
className="w-full mb-5"
|
||||||
/>
|
/>
|
||||||
<InputTextIcon
|
<InputTextIcon
|
||||||
@ -118,10 +103,8 @@ export default function Page() {
|
|||||||
IconItem={KeySquare}
|
IconItem={KeySquare}
|
||||||
label="Confirmation mot de passe"
|
label="Confirmation mot de passe"
|
||||||
placeholder="Confirmation mot de passe"
|
placeholder="Confirmation mot de passe"
|
||||||
errorMsg={password2FieldError}
|
|
||||||
className="w-full"
|
className="w-full"
|
||||||
/>
|
/>
|
||||||
<p className="text-red-500">{errorMessage}</p>
|
|
||||||
<div className="form-group-submit mt-4">
|
<div className="form-group-submit mt-4">
|
||||||
<Button
|
<Button
|
||||||
text="Enregistrer"
|
text="Enregistrer"
|
||||||
@ -143,18 +126,6 @@ export default function Page() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Popup
|
|
||||||
isOpen={popupVisible}
|
|
||||||
setIsOpen={setPopupVisible}
|
|
||||||
message={popupMessage}
|
|
||||||
onConfirm={() => {
|
|
||||||
setPopupVisible(false);
|
|
||||||
router.push(`${FE_USERS_LOGIN_URL}`);
|
|
||||||
}}
|
|
||||||
onCancel={() => setPopupVisible(false)}
|
|
||||||
uniqueConfirmButton={true}
|
|
||||||
popupClassName="w-full max-w-xs sm:max-w-md"
|
|
||||||
/>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -199,14 +199,3 @@ export const resetPassword = (uuid, data, csrfToken) => {
|
|||||||
});
|
});
|
||||||
return fetch(request).then(requestResponseHandler).catch(errorHandler);
|
return fetch(request).then(requestResponseHandler).catch(errorHandler);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getResetPassword = (uuid) => {
|
|
||||||
const url = `${BE_AUTH_RESET_PASSWORD_URL}/${uuid}`;
|
|
||||||
return fetch(url, {
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.then(requestResponseHandler)
|
|
||||||
.catch(errorHandler);
|
|
||||||
};
|
|
||||||
|
|||||||
@ -47,11 +47,8 @@ export const fetchRegistrationFileFromGroup = async (groupId) => {
|
|||||||
return response.json();
|
return response.json();
|
||||||
};
|
};
|
||||||
|
|
||||||
export const fetchRegistrationSchoolFileMasters = (id = null) => {
|
export const fetchRegistrationSchoolFileMasters = (establishment) => {
|
||||||
let url = `${BE_SUBSCRIPTION_REGISTRATION_SCHOOL_FILE_MASTERS_URL}`;
|
let url = `${BE_SUBSCRIPTION_REGISTRATION_SCHOOL_FILE_MASTERS_URL}?establishment_id=${establishment}`;
|
||||||
if (id) {
|
|
||||||
url = `${BE_SUBSCRIPTION_REGISTRATION_SCHOOL_FILE_MASTERS_URL}/${id}`;
|
|
||||||
}
|
|
||||||
const request = new Request(`${url}`, {
|
const request = new Request(`${url}`, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: {
|
headers: {
|
||||||
@ -61,11 +58,8 @@ export const fetchRegistrationSchoolFileMasters = (id = null) => {
|
|||||||
return fetch(request).then(requestResponseHandler).catch(errorHandler);
|
return fetch(request).then(requestResponseHandler).catch(errorHandler);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const fetchRegistrationParentFileMasters = (id = null) => {
|
export const fetchRegistrationParentFileMasters = (establishment) => {
|
||||||
let url = `${BE_SUBSCRIPTION_REGISTRATION_PARENT_FILE_MASTERS_URL}`;
|
let url = `${BE_SUBSCRIPTION_REGISTRATION_PARENT_FILE_MASTERS_URL}?establishment_id=${establishment}`;
|
||||||
if (id) {
|
|
||||||
url = `${BE_SUBSCRIPTION_REGISTRATION_PARENT_FILE_MASTERS_URL}/${id}`;
|
|
||||||
}
|
|
||||||
const request = new Request(`${url}`, {
|
const request = new Request(`${url}`, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: {
|
headers: {
|
||||||
@ -75,11 +69,8 @@ export const fetchRegistrationParentFileMasters = (id = null) => {
|
|||||||
return fetch(request).then(requestResponseHandler).catch(errorHandler);
|
return fetch(request).then(requestResponseHandler).catch(errorHandler);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const fetchRegistrationSchoolFileTemplates = (id = null) => {
|
export const fetchRegistrationSchoolFileTemplates = (establishment) => {
|
||||||
let url = `${BE_SUBSCRIPTION_REGISTRATION_SCHOOL_FILE_TEMPLATES_URL}`;
|
let url = `${BE_SUBSCRIPTION_REGISTRATION_SCHOOL_FILE_TEMPLATES_URL}?establishment_id=${establishment}`;
|
||||||
if (id) {
|
|
||||||
url = `${BE_SUBSCRIPTION_REGISTRATION_SCHOOL_FILE_TEMPLATES_URL}/${id}`;
|
|
||||||
}
|
|
||||||
const request = new Request(`${url}`, {
|
const request = new Request(`${url}`, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: {
|
headers: {
|
||||||
|
|||||||
@ -9,10 +9,10 @@ import { useEstablishment } from '@/context/EstablishmentContext';
|
|||||||
import AlertMessage from '@/components/AlertMessage';
|
import AlertMessage from '@/components/AlertMessage';
|
||||||
import RecipientInput from '@/components/RecipientInput';
|
import RecipientInput from '@/components/RecipientInput';
|
||||||
import { useRouter } from 'next/navigation'; // Ajoute cette ligne
|
import { useRouter } from 'next/navigation'; // Ajoute cette ligne
|
||||||
import WisiwigTextArea from '@/components/WisiwigTextArea';
|
import WisiwigTextArea from '@/components/Form/WisiwigTextArea';
|
||||||
import logger from '@/utils/logger';
|
import logger from '@/utils/logger';
|
||||||
import InputText from '@/components/InputText';
|
import InputText from '@/components/Form/InputText';
|
||||||
import Button from '@/components/Button';
|
import Button from '@/components/Form/Button';
|
||||||
|
|
||||||
export default function EmailSender({ csrfToken }) {
|
export default function EmailSender({ csrfToken }) {
|
||||||
const [recipients, setRecipients] = useState([]);
|
const [recipients, setRecipients] = useState([]);
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { usePlanning } from '@/context/PlanningContext';
|
import { usePlanning, PlanningModes } from '@/context/PlanningContext';
|
||||||
import WeekView from '@/components/Calendar/WeekView';
|
import WeekView from '@/components/Calendar/WeekView';
|
||||||
import MonthView from '@/components/Calendar/MonthView';
|
import MonthView from '@/components/Calendar/MonthView';
|
||||||
import YearView from '@/components/Calendar/YearView';
|
import YearView from '@/components/Calendar/YearView';
|
||||||
@ -22,7 +22,7 @@ import { fr } from 'date-fns/locale';
|
|||||||
import { AnimatePresence, motion } from 'framer-motion'; // Ajouter cet import
|
import { AnimatePresence, motion } from 'framer-motion'; // Ajouter cet import
|
||||||
import logger from '@/utils/logger';
|
import logger from '@/utils/logger';
|
||||||
|
|
||||||
const Calendar = ({ modeSet, onDateClick, onEventClick }) => {
|
const Calendar = ({ modeSet, onDateClick, onEventClick, planningClassName='' }) => {
|
||||||
const {
|
const {
|
||||||
currentDate,
|
currentDate,
|
||||||
setCurrentDate,
|
setCurrentDate,
|
||||||
@ -30,6 +30,8 @@ const Calendar = ({ modeSet, onDateClick, onEventClick }) => {
|
|||||||
setViewType,
|
setViewType,
|
||||||
events,
|
events,
|
||||||
hiddenSchedules,
|
hiddenSchedules,
|
||||||
|
planningMode,
|
||||||
|
parentView
|
||||||
} = usePlanning();
|
} = usePlanning();
|
||||||
const [visibleEvents, setVisibleEvents] = useState([]);
|
const [visibleEvents, setVisibleEvents] = useState([]);
|
||||||
const [showDatePicker, setShowDatePicker] = useState(false);
|
const [showDatePicker, setShowDatePicker] = useState(false);
|
||||||
@ -91,98 +93,107 @@ const Calendar = ({ modeSet, onDateClick, onEventClick }) => {
|
|||||||
<div className="flex-1 flex flex-col">
|
<div className="flex-1 flex flex-col">
|
||||||
<div className="flex items-center justify-between p-4 bg-white sticky top-0 z-30 border-b shadow-sm h-[64px]">
|
<div className="flex items-center justify-between p-4 bg-white sticky top-0 z-30 border-b shadow-sm h-[64px]">
|
||||||
{/* Navigation à gauche */}
|
{/* Navigation à gauche */}
|
||||||
<div className="flex items-center gap-4">
|
{planningMode === PlanningModes.PLANNING && (
|
||||||
<button
|
<div className="flex items-center gap-4">
|
||||||
onClick={() => setCurrentDate(new Date())}
|
|
||||||
className="px-3 py-1.5 text-sm font-medium text-gray-700 hover:text-gray-900 bg-gray-100 hover:bg-gray-200 rounded-md transition-colors"
|
|
||||||
>
|
|
||||||
Aujourd'hui
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
onClick={() => navigateDate('prev')}
|
|
||||||
className="p-2 hover:bg-gray-100 rounded-full"
|
|
||||||
>
|
|
||||||
<ChevronLeft className="w-5 h-5" />
|
|
||||||
</button>
|
|
||||||
|
|
||||||
{/* Menu déroulant pour le mois/année */}
|
|
||||||
<div className="relative">
|
|
||||||
<button
|
<button
|
||||||
onClick={() => setShowDatePicker(!showDatePicker)}
|
onClick={() => setCurrentDate(new Date())}
|
||||||
className="flex items-center gap-1 px-2 py-1 hover:bg-gray-100 rounded-md"
|
className="px-3 py-1.5 text-sm font-medium text-gray-700 hover:text-gray-900 bg-gray-100 hover:bg-gray-200 rounded-md transition-colors"
|
||||||
>
|
>
|
||||||
<h2 className="text-xl font-semibold">
|
Aujourd'hui
|
||||||
{format(
|
|
||||||
currentDate,
|
|
||||||
viewType === 'year' ? 'yyyy' : 'MMMM yyyy',
|
|
||||||
{ locale: fr }
|
|
||||||
)}
|
|
||||||
</h2>
|
|
||||||
<ChevronDown className="w-4 h-4" />
|
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
{/* Menu de sélection du mois/année */}
|
onClick={() => navigateDate('prev')}
|
||||||
{showDatePicker && (
|
className="p-2 hover:bg-gray-100 rounded-full"
|
||||||
<div className="absolute top-full left-0 mt-1 bg-white border border-gray-200 rounded-lg shadow-lg z-50 w-64">
|
>
|
||||||
{viewType !== 'year' && (
|
<ChevronLeft className="w-5 h-5" />
|
||||||
<div className="p-2 border-b">
|
</button>
|
||||||
|
<div className="relative">
|
||||||
|
<button
|
||||||
|
onClick={() => setShowDatePicker(!showDatePicker)}
|
||||||
|
className="flex items-center gap-1 px-2 py-1 hover:bg-gray-100 rounded-md"
|
||||||
|
>
|
||||||
|
<h2 className="text-xl font-semibold">
|
||||||
|
{format(
|
||||||
|
currentDate,
|
||||||
|
viewType === 'year' ? 'yyyy' : 'MMMM yyyy',
|
||||||
|
{ locale: fr }
|
||||||
|
)}
|
||||||
|
</h2>
|
||||||
|
<ChevronDown className="w-4 h-4" />
|
||||||
|
</button>
|
||||||
|
{showDatePicker && (
|
||||||
|
<div className="absolute top-full left-0 mt-1 bg-white border border-gray-200 rounded-lg shadow-lg z-50 w-64">
|
||||||
|
{viewType !== 'year' && (
|
||||||
|
<div className="p-2 border-b">
|
||||||
|
<div className="grid grid-cols-3 gap-1">
|
||||||
|
{months.map((month) => (
|
||||||
|
<button
|
||||||
|
key={month.value}
|
||||||
|
onClick={() => handleMonthSelect(month.value)}
|
||||||
|
className="p-2 text-sm hover:bg-gray-100 rounded-md"
|
||||||
|
>
|
||||||
|
{month.label}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className="p-2">
|
||||||
<div className="grid grid-cols-3 gap-1">
|
<div className="grid grid-cols-3 gap-1">
|
||||||
{months.map((month) => (
|
{years.map((year) => (
|
||||||
<button
|
<button
|
||||||
key={month.value}
|
key={year.value}
|
||||||
onClick={() => handleMonthSelect(month.value)}
|
onClick={() => handleYearSelect(year.value)}
|
||||||
className="p-2 text-sm hover:bg-gray-100 rounded-md"
|
className="p-2 text-sm hover:bg-gray-100 rounded-md"
|
||||||
>
|
>
|
||||||
{month.label}
|
{year.label}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
<div className="p-2">
|
|
||||||
<div className="grid grid-cols-3 gap-1">
|
|
||||||
{years.map((year) => (
|
|
||||||
<button
|
|
||||||
key={year.value}
|
|
||||||
onClick={() => handleYearSelect(year.value)}
|
|
||||||
className="p-2 text-sm hover:bg-gray-100 rounded-md"
|
|
||||||
>
|
|
||||||
{year.label}
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)}
|
||||||
)}
|
</div>
|
||||||
</div>
|
<button
|
||||||
|
onClick={() => navigateDate('next')}
|
||||||
<button
|
className="p-2 hover:bg-gray-100 rounded-full"
|
||||||
onClick={() => navigateDate('next')}
|
>
|
||||||
className="p-2 hover:bg-gray-100 rounded-full"
|
<ChevronRight className="w-5 h-5" />
|
||||||
>
|
</button>
|
||||||
<ChevronRight className="w-5 h-5" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Numéro de semaine au centre */}
|
|
||||||
{viewType === 'week' && (
|
|
||||||
<div className="flex items-center gap-1 text-sm font-medium text-gray-600">
|
|
||||||
<span>Semaine</span>
|
|
||||||
<span className="px-2 py-1 bg-gray-100 rounded-md">
|
|
||||||
{getWeek(currentDate, { weekStartsOn: 1 })}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Centre : numéro de semaine ou classe/niveau */}
|
||||||
|
<div className="flex-1 flex justify-center">
|
||||||
|
{((planningMode === PlanningModes.PLANNING || planningMode === PlanningModes.CLASS_SCHEDULE) && viewType === 'week' && !parentView) && (
|
||||||
|
<div className="flex items-center gap-1 text-sm font-medium text-gray-600">
|
||||||
|
<span>Semaine</span>
|
||||||
|
<span className="px-2 py-1 bg-gray-100 rounded-md">
|
||||||
|
{getWeek(currentDate, { weekStartsOn: 1 })}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{parentView && (
|
||||||
|
<span className="px-2 py-1 bg-gray-100 rounded-md text-base font-semibold">
|
||||||
|
{/* À adapter selon les props disponibles */}
|
||||||
|
{planningClassName}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Contrôles à droite */}
|
{/* Contrôles à droite */}
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
<ToggleView viewType={viewType} setViewType={setViewType} />
|
{planningMode === PlanningModes.PLANNING && (
|
||||||
<button
|
<ToggleView viewType={viewType} setViewType={setViewType} />
|
||||||
onClick={onDateClick}
|
)}
|
||||||
className="w-10 h-10 flex items-center justify-center bg-emerald-600 text-white rounded-full hover:bg-emerald-700 shadow-md transition-colors"
|
{(planningMode === PlanningModes.PLANNING || (planningMode === PlanningModes.CLASS_SCHEDULE && !parentView)) && (
|
||||||
>
|
<button
|
||||||
<Plus className="w-5 h-5" />
|
onClick={onDateClick}
|
||||||
</button>
|
className="w-10 h-10 flex items-center justify-center bg-emerald-600 text-white rounded-full hover:bg-emerald-700 shadow-md transition-colors"
|
||||||
|
>
|
||||||
|
<Plus className="w-5 h-5" />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -1,12 +1,13 @@
|
|||||||
import React, { useEffect, useState, useRef } from 'react';
|
import React, { useEffect, useState, useRef } from 'react';
|
||||||
import { usePlanning } from '@/context/PlanningContext';
|
import { usePlanning, PlanningModes } from '@/context/PlanningContext';
|
||||||
import { format, startOfWeek, addDays, isSameDay } from 'date-fns';
|
import { format, startOfWeek, addDays, isSameDay } from 'date-fns';
|
||||||
import { fr } from 'date-fns/locale';
|
import { fr } from 'date-fns/locale';
|
||||||
import { getWeekEvents } from '@/utils/events';
|
import { getWeekEvents } from '@/utils/events';
|
||||||
import { isToday } from 'date-fns';
|
import { isToday } from 'date-fns';
|
||||||
|
|
||||||
|
|
||||||
const WeekView = ({ onDateClick, onEventClick, events }) => {
|
const WeekView = ({ onDateClick, onEventClick, events }) => {
|
||||||
const { currentDate } = usePlanning();
|
const { currentDate, planningMode, parentView } = usePlanning();
|
||||||
const [currentTime, setCurrentTime] = useState(new Date());
|
const [currentTime, setCurrentTime] = useState(new Date());
|
||||||
const scrollContainerRef = useRef(null); // Ajouter cette référence
|
const scrollContainerRef = useRef(null); // Ajouter cette référence
|
||||||
|
|
||||||
@ -106,10 +107,14 @@ const WeekView = ({ onDateClick, onEventClick, events }) => {
|
|||||||
key={event.id}
|
key={event.id}
|
||||||
className="rounded-sm overflow-hidden cursor-pointer hover:shadow-lg"
|
className="rounded-sm overflow-hidden cursor-pointer hover:shadow-lg"
|
||||||
style={eventStyle}
|
style={eventStyle}
|
||||||
onClick={(e) => {
|
onClick={
|
||||||
e.stopPropagation();
|
parentView
|
||||||
onEventClick(event);
|
? undefined
|
||||||
}}
|
: (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
onEventClick(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<div className="p-1">
|
<div className="p-1">
|
||||||
<div
|
<div
|
||||||
@ -198,11 +203,15 @@ const WeekView = ({ onDateClick, onEventClick, events }) => {
|
|||||||
className={`h-20 relative border-b border-gray-100
|
className={`h-20 relative border-b border-gray-100
|
||||||
${isWeekend(day) ? 'bg-gray-50' : 'bg-white'}
|
${isWeekend(day) ? 'bg-gray-50' : 'bg-white'}
|
||||||
${isToday(day) ? 'bg-emerald-100/50 border-x border-emerald-600' : ''}`}
|
${isToday(day) ? 'bg-emerald-100/50 border-x border-emerald-600' : ''}`}
|
||||||
onClick={() => {
|
onClick={
|
||||||
const date = new Date(day);
|
parentView
|
||||||
date.setHours(hour);
|
? undefined
|
||||||
onDateClick(date);
|
: () => {
|
||||||
}}
|
const date = new Date(day);
|
||||||
|
date.setHours(hour);
|
||||||
|
onDateClick(date);
|
||||||
|
}
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<div className="grid gap-1">
|
<div className="grid gap-1">
|
||||||
{dayEvents
|
{dayEvents
|
||||||
|
|||||||
@ -1,10 +1,11 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
|
// Utilisation de couleurs hexadécimales pour le SVG
|
||||||
const COLORS = [
|
const COLORS = [
|
||||||
'fill-blue-400 text-blue-400',
|
'#60a5fa', // bleu-400
|
||||||
'fill-orange-400 text-orange-400',
|
'#fb923c', // orange-400
|
||||||
'fill-purple-400 text-purple-400',
|
'#a78bfa', // violet-400
|
||||||
'fill-emerald-400 text-emerald-400',
|
'#34d399', // émeraude-400
|
||||||
];
|
];
|
||||||
|
|
||||||
export default function PieChart({ data }) {
|
export default function PieChart({ data }) {
|
||||||
@ -23,7 +24,20 @@ export default function PieChart({ data }) {
|
|||||||
<div className="flex items-center justify-center w-full">
|
<div className="flex items-center justify-center w-full">
|
||||||
<svg width={100} height={100} viewBox="0 0 32 32">
|
<svg width={100} height={100} viewBox="0 0 32 32">
|
||||||
{data.map((slice, idx) => {
|
{data.map((slice, idx) => {
|
||||||
|
if (slice.value === 0) return null;
|
||||||
const value = (slice.value / total) * 100;
|
const value = (slice.value / total) * 100;
|
||||||
|
if (value === 100) {
|
||||||
|
// Cas 100% : on dessine un cercle plein
|
||||||
|
return (
|
||||||
|
<circle
|
||||||
|
key={idx}
|
||||||
|
cx="16"
|
||||||
|
cy="16"
|
||||||
|
r="16"
|
||||||
|
fill={COLORS[idx % COLORS.length]}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
const startAngle = (cumulative / 100) * 360;
|
const startAngle = (cumulative / 100) * 360;
|
||||||
const endAngle = ((cumulative + value) / 100) * 360;
|
const endAngle = ((cumulative + value) / 100) * 360;
|
||||||
const largeArc = value > 50 ? 1 : 0;
|
const largeArc = value > 50 ? 1 : 0;
|
||||||
@ -42,7 +56,7 @@ export default function PieChart({ data }) {
|
|||||||
<path
|
<path
|
||||||
key={idx}
|
key={idx}
|
||||||
d={pathData}
|
d={pathData}
|
||||||
className={COLORS[idx % COLORS.length].split(' ')[0]}
|
fill={COLORS[idx % COLORS.length]}
|
||||||
stroke="#fff"
|
stroke="#fff"
|
||||||
strokeWidth="0.5"
|
strokeWidth="0.5"
|
||||||
/>
|
/>
|
||||||
@ -50,17 +64,21 @@ export default function PieChart({ data }) {
|
|||||||
})}
|
})}
|
||||||
</svg>
|
</svg>
|
||||||
<div className="ml-4 flex flex-col space-y-1">
|
<div className="ml-4 flex flex-col space-y-1">
|
||||||
{data.map((slice, idx) => (
|
{data.map((slice, idx) =>
|
||||||
<div
|
slice.value > 0 && (
|
||||||
key={idx}
|
<div
|
||||||
className={`flex items-center text-xs font-semibold ${COLORS[idx % COLORS.length].split(' ')[1]}`}
|
key={idx}
|
||||||
>
|
className="flex items-center text-xs font-semibold"
|
||||||
<span
|
style={{ color: COLORS[idx % COLORS.length] }}
|
||||||
className={`inline-block w-3 h-3 mr-2 rounded ${COLORS[idx % COLORS.length].split(' ')[0]}`}
|
>
|
||||||
/>
|
<span
|
||||||
{slice.label} : {slice.value}
|
className="inline-block w-3 h-3 mr-2 rounded"
|
||||||
</div>
|
style={{ backgroundColor: COLORS[idx % COLORS.length] }}
|
||||||
))}
|
/>
|
||||||
|
{slice.label} : {slice.value}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
131
Front-End/src/components/EventCard.js
Normal file
131
Front-End/src/components/EventCard.js
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { CalendarCheck } from 'lucide-react';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formate une date en format français avec jour de la semaine
|
||||||
|
* @param {string} startDateString - Date de début au format ISO
|
||||||
|
* @param {string} endDateString - Date de fin au format ISO (optionnel)
|
||||||
|
* @returns {object} Objet contenant la date formatée et le jour
|
||||||
|
*/
|
||||||
|
const formatEventDate = (startDateString, endDateString) => {
|
||||||
|
if (!startDateString)
|
||||||
|
return { formattedDate: '', dayName: '', timeIndicator: '', timeRange: '' };
|
||||||
|
|
||||||
|
try {
|
||||||
|
const startDate = new Date(startDateString);
|
||||||
|
const endDate = endDateString ? new Date(endDateString) : null;
|
||||||
|
const now = new Date();
|
||||||
|
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
||||||
|
const eventDate = new Date(
|
||||||
|
startDate.getFullYear(),
|
||||||
|
startDate.getMonth(),
|
||||||
|
startDate.getDate()
|
||||||
|
);
|
||||||
|
|
||||||
|
// Options pour le formatage de la date
|
||||||
|
const dateOptions = {
|
||||||
|
day: 'numeric',
|
||||||
|
month: 'long',
|
||||||
|
year: 'numeric',
|
||||||
|
};
|
||||||
|
|
||||||
|
const dayOptions = {
|
||||||
|
weekday: 'long',
|
||||||
|
};
|
||||||
|
|
||||||
|
const timeOptions = {
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
};
|
||||||
|
|
||||||
|
const formattedDate = startDate.toLocaleDateString('fr-FR', dateOptions);
|
||||||
|
const dayName = startDate.toLocaleDateString('fr-FR', dayOptions);
|
||||||
|
|
||||||
|
// Formatage de l'heure
|
||||||
|
let timeRange = '';
|
||||||
|
if (endDate) {
|
||||||
|
const startTime = startDate.toLocaleTimeString('fr-FR', timeOptions);
|
||||||
|
const endTime = endDate.toLocaleTimeString('fr-FR', timeOptions);
|
||||||
|
timeRange = `${startTime} - ${endTime}`;
|
||||||
|
} else {
|
||||||
|
timeRange = startDate.toLocaleTimeString('fr-FR', timeOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calcul des jours restants
|
||||||
|
const diffTime = eventDate - today;
|
||||||
|
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
|
||||||
|
|
||||||
|
let timeIndicator = '';
|
||||||
|
if (diffDays === 0) {
|
||||||
|
timeIndicator = "Aujourd'hui";
|
||||||
|
} else if (diffDays === 1) {
|
||||||
|
timeIndicator = 'Demain';
|
||||||
|
} else if (diffDays > 0 && diffDays <= 7) {
|
||||||
|
timeIndicator = `Dans ${diffDays} jours`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
formattedDate,
|
||||||
|
dayName: dayName.charAt(0).toUpperCase() + dayName.slice(1),
|
||||||
|
timeIndicator,
|
||||||
|
timeRange,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
// logger.error('Erreur lors du formatage de la date:', error);
|
||||||
|
return {
|
||||||
|
formattedDate: startDateString,
|
||||||
|
dayName: '',
|
||||||
|
timeIndicator: '',
|
||||||
|
timeRange: '',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Composant EventCard pour afficher les événements à venir
|
||||||
|
* @param {string} title - Titre de l'événement
|
||||||
|
* @param {string} start - Date de début de l'événement (format ISO)
|
||||||
|
* @param {string} end - Date de fin de l'événement (format ISO)
|
||||||
|
* @param {string} date - Date de l'événement (pour compatibilité)
|
||||||
|
* @param {string} description - Description de l'événement
|
||||||
|
* @param {string} type - Type d'événement (optionnel)
|
||||||
|
* @returns {JSX.Element} Carte d'événement
|
||||||
|
*/
|
||||||
|
const EventCard = ({ title, start, end, date, description, type }) => {
|
||||||
|
// Utiliser start si disponible, sinon date pour compatibilité
|
||||||
|
const eventDate = start || date;
|
||||||
|
const { formattedDate, dayName, timeIndicator, timeRange } = formatEventDate(
|
||||||
|
eventDate,
|
||||||
|
end
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="bg-white p-4 rounded-lg shadow-sm border border-gray-200 mb-3 hover:shadow-md transition-shadow">
|
||||||
|
<div className="flex items-start gap-3">
|
||||||
|
<div className="flex-shrink-0 mt-1">
|
||||||
|
<CalendarCheck className="text-emerald-500" size={20} />
|
||||||
|
</div>
|
||||||
|
<div className="flex-1">
|
||||||
|
<h4 className="font-medium text-gray-900 mb-1">{title}</h4>
|
||||||
|
<div className="flex flex-col text-sm text-gray-500">
|
||||||
|
<span className="font-medium text-emerald-600">{dayName}</span>
|
||||||
|
<span>{formattedDate}</span>
|
||||||
|
{timeRange && (
|
||||||
|
<span className="text-xs text-gray-600 mt-1">{timeRange}</span>
|
||||||
|
)}
|
||||||
|
{timeIndicator && (
|
||||||
|
<span className="text-xs text-blue-600 mt-1 font-medium">
|
||||||
|
{timeIndicator}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{description && (
|
||||||
|
<p className="text-sm mt-2 text-gray-700">{description}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EventCard;
|
||||||
@ -33,12 +33,19 @@ export default function FlashNotification({
|
|||||||
const [isVisible, setIsVisible] = useState(true);
|
const [isVisible, setIsVisible] = useState(true);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const timer = setTimeout(() => {
|
setIsVisible(true);
|
||||||
setIsVisible(false); // Déclenche la disparition
|
}, [message, type, errorCode, onClose]);
|
||||||
setTimeout(onClose, 300); // Appelle onClose après l'animation
|
|
||||||
}, displayPeriod); // Notification visible pendant 3 secondes par défaut
|
useEffect(() => {
|
||||||
return () => clearTimeout(timer);
|
if (type !== 'error') {
|
||||||
}, [onClose, displayPeriod]);
|
const timer = setTimeout(() => {
|
||||||
|
setIsVisible(false); // Déclenche la disparition
|
||||||
|
setTimeout(onClose, 300); // Appelle onClose après l'animation
|
||||||
|
}, displayPeriod);
|
||||||
|
return () => clearTimeout(timer);
|
||||||
|
}
|
||||||
|
// Pour les erreurs, pas de timeout : la notification reste affichée
|
||||||
|
}, [onClose, displayPeriod, type]);
|
||||||
|
|
||||||
if (!message || !isVisible) return null;
|
if (!message || !isVisible) return null;
|
||||||
|
|
||||||
|
|||||||
194
Front-End/src/components/Form/FormRenderer.js
Normal file
194
Front-End/src/components/Form/FormRenderer.js
Normal file
@ -0,0 +1,194 @@
|
|||||||
|
import logger from '@/utils/logger';
|
||||||
|
import { useForm, Controller } from 'react-hook-form';
|
||||||
|
import SelectChoice from './SelectChoice';
|
||||||
|
import InputTextIcon from './InputTextIcon';
|
||||||
|
import * as LucideIcons from 'lucide-react';
|
||||||
|
import Button from './Button';
|
||||||
|
import DjangoCSRFToken from '../DjangoCSRFToken';
|
||||||
|
import WisiwigTextArea from './WisiwigTextArea';
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Récupère une icône Lucide par son nom.
|
||||||
|
*/
|
||||||
|
export function getIcon(name) {
|
||||||
|
if (Object.keys(LucideIcons).includes(name)) {
|
||||||
|
const Icon = LucideIcons[name];
|
||||||
|
return Icon ?? null;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const formConfigTest = {
|
||||||
|
id: 0,
|
||||||
|
title: 'Mon formulaire dynamique',
|
||||||
|
submitLabel: 'Envoyer',
|
||||||
|
fields: [
|
||||||
|
{ id: 'name', label: 'Nom', type: 'text', required: true },
|
||||||
|
{ id: 'email', label: 'Email', type: 'email' },
|
||||||
|
{
|
||||||
|
id: 'email2',
|
||||||
|
label: 'Email',
|
||||||
|
type: 'text',
|
||||||
|
icon: 'Mail',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'role',
|
||||||
|
label: 'Rôle',
|
||||||
|
type: 'select',
|
||||||
|
options: ['Admin', 'Utilisateur', 'Invité'],
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'paragraph',
|
||||||
|
text: "Bonjour, Bienvenue dans ce formulaire d'inscription haha",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'birthdate',
|
||||||
|
label: 'Date de naissance',
|
||||||
|
type: 'date',
|
||||||
|
icon: 'Calendar',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'textarea',
|
||||||
|
label: 'toto',
|
||||||
|
type: 'textarea',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function FormRenderer({
|
||||||
|
formConfig = formConfigTest,
|
||||||
|
csrfToken,
|
||||||
|
}) {
|
||||||
|
const {
|
||||||
|
handleSubmit,
|
||||||
|
control,
|
||||||
|
formState: { errors },
|
||||||
|
reset,
|
||||||
|
} = useForm();
|
||||||
|
|
||||||
|
const onSubmit = (data) => {
|
||||||
|
logger.debug('=== DÉBUT onSubmit ===');
|
||||||
|
logger.debug('Réponses :', data);
|
||||||
|
|
||||||
|
const formattedData = {
|
||||||
|
//TODO: idDossierInscriptions: 123,
|
||||||
|
formId: formConfig.id,
|
||||||
|
responses: { ...data },
|
||||||
|
};
|
||||||
|
|
||||||
|
//TODO: ENVOYER LES DONNÉES AU BACKEND
|
||||||
|
alert('Données reçues : ' + JSON.stringify(formattedData, null, 2));
|
||||||
|
reset(); // Réinitialiser le formulaire après soumission
|
||||||
|
logger.debug('=== FIN onSubmit ===');
|
||||||
|
};
|
||||||
|
|
||||||
|
const onError = (errors) => {
|
||||||
|
logger.error('=== ERREURS DE VALIDATION ===');
|
||||||
|
logger.error('Erreurs :', errors);
|
||||||
|
alert('Erreurs de validation : ' + JSON.stringify(errors, null, 2));
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form
|
||||||
|
onSubmit={handleSubmit(onSubmit, onError)}
|
||||||
|
className="max-w-md mx-auto"
|
||||||
|
>
|
||||||
|
{csrfToken ? <DjangoCSRFToken csrfToken={csrfToken} /> : null}
|
||||||
|
<h2 className="text-2xl font-bold text-center mb-4">
|
||||||
|
{formConfig.title}
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
{formConfig.fields.map((field) => (
|
||||||
|
<div key={field.id} className="flex flex-col mt-4">
|
||||||
|
{field.type === 'paragraph' && <p>{field.text}</p>}
|
||||||
|
|
||||||
|
{(field.type === 'text' ||
|
||||||
|
field.type === 'email' ||
|
||||||
|
field.type === 'date') && (
|
||||||
|
<Controller
|
||||||
|
name={field.id}
|
||||||
|
control={control}
|
||||||
|
rules={{ required: field.required }}
|
||||||
|
render={({ field: { onChange, value, name } }) => (
|
||||||
|
<InputTextIcon
|
||||||
|
label={field.label}
|
||||||
|
required={field.required}
|
||||||
|
IconItem={field.icon ? getIcon(field.icon) : null}
|
||||||
|
type={field.type}
|
||||||
|
name={name}
|
||||||
|
value={value || ''}
|
||||||
|
onChange={onChange}
|
||||||
|
errorMsg={
|
||||||
|
errors[field.id]
|
||||||
|
? field.required
|
||||||
|
? `${field.label} est requis`
|
||||||
|
: 'Champ invalide'
|
||||||
|
: ''
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{field.type === 'select' && (
|
||||||
|
<Controller
|
||||||
|
name={field.id}
|
||||||
|
control={control}
|
||||||
|
rules={{ required: field.required }}
|
||||||
|
render={({ field: { onChange, value, name } }) => (
|
||||||
|
<SelectChoice
|
||||||
|
label={field.label}
|
||||||
|
required={field.required}
|
||||||
|
name={name}
|
||||||
|
selected={value || ''}
|
||||||
|
callback={onChange}
|
||||||
|
choices={field.options.map((e) => ({ label: e, value: e }))}
|
||||||
|
placeHolder={`Sélectionner ${field.label.toLowerCase()}`}
|
||||||
|
errorMsg={
|
||||||
|
errors[field.id]
|
||||||
|
? field.required
|
||||||
|
? `${field.label} est requis`
|
||||||
|
: 'Champ invalide'
|
||||||
|
: ''
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{field.type === 'textarea' && (
|
||||||
|
<Controller
|
||||||
|
name={field.id}
|
||||||
|
control={control}
|
||||||
|
rules={{ required: field.required }}
|
||||||
|
render={({ field: { onChange, value } }) => (
|
||||||
|
<WisiwigTextArea
|
||||||
|
label={field.label}
|
||||||
|
placeholder={field.placeholder}
|
||||||
|
value={value || ''}
|
||||||
|
onChange={onChange}
|
||||||
|
required={field.required}
|
||||||
|
errorMsg={
|
||||||
|
errors[field.id]
|
||||||
|
? field.required
|
||||||
|
? `${field.label} est requis`
|
||||||
|
: 'Champ invalide'
|
||||||
|
: ''
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
<div className="form-group-submit mt-4">
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
primary
|
||||||
|
text={formConfig.submitLabel ? formConfig.submitLabel : 'Envoyer'}
|
||||||
|
className="mb-1 px-4 py-2 rounded-md shadow bg-emerald-500 text-white hover:bg-emerald-600 w-full"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -1,3 +1,5 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
export default function InputTextIcon({
|
export default function InputTextIcon({
|
||||||
name,
|
name,
|
||||||
type,
|
type,
|
||||||
@ -31,9 +33,11 @@ export default function InputTextIcon({
|
|||||||
!enable ? 'bg-gray-100 cursor-not-allowed' : ''
|
!enable ? 'bg-gray-100 cursor-not-allowed' : ''
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<span className="inline-flex min-h-9 items-center px-3 rounded-l-md bg-gray-50 text-gray-500 text-sm">
|
{IconItem ? (
|
||||||
{IconItem && <IconItem />}
|
<span className="inline-flex min-h-9 items-center px-3 rounded-l-md bg-gray-50 text-gray-500 text-sm">
|
||||||
</span>
|
<IconItem />
|
||||||
|
</span>
|
||||||
|
) : null}
|
||||||
<input
|
<input
|
||||||
type={type}
|
type={type}
|
||||||
id={name}
|
id={name}
|
||||||
@ -4,10 +4,10 @@ import 'react-quill/dist/quill.snow.css';
|
|||||||
const ReactQuill = dynamic(() => import('react-quill'), { ssr: false });
|
const ReactQuill = dynamic(() => import('react-quill'), { ssr: false });
|
||||||
|
|
||||||
export default function WisiwigTextArea({
|
export default function WisiwigTextArea({
|
||||||
label = 'Mail',
|
label = 'Zone de Texte',
|
||||||
value,
|
value,
|
||||||
onChange,
|
onChange,
|
||||||
placeholder = 'Ecrivez votre mail ici...',
|
placeholder = 'Ecrivez votre texte ici...',
|
||||||
className = 'h-64',
|
className = 'h-64',
|
||||||
required = false,
|
required = false,
|
||||||
errorMsg,
|
errorMsg,
|
||||||
@ -1,7 +1,7 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { Trash2 } from 'lucide-react';
|
import { Trash2 } from 'lucide-react';
|
||||||
import ToggleSwitch from '@/components/ToggleSwitch';
|
import ToggleSwitch from '@/components/Form/ToggleSwitch';
|
||||||
import Button from '@/components/Button';
|
import Button from '@/components/Form/Button';
|
||||||
import Popup from '@/components/Popup';
|
import Popup from '@/components/Popup';
|
||||||
import { useNotification } from '@/context/NotificationContext';
|
import { useNotification } from '@/context/NotificationContext';
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import React, { useState, useMemo, useEffect } from 'react';
|
import React, { useState, useMemo, useEffect } from 'react';
|
||||||
import { BookOpen, CheckCircle, AlertCircle, Clock } from 'lucide-react';
|
import { BookOpen, CheckCircle, AlertCircle, Clock } from 'lucide-react';
|
||||||
import RadioList from '@/components/RadioList';
|
import RadioList from '@/components/Form/RadioList';
|
||||||
|
|
||||||
const LEVELS = [
|
const LEVELS = [
|
||||||
{ value: 0, label: 'Non évalué' },
|
{ value: 0, label: 'Non évalué' },
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import Table from '@/components/Table';
|
import Table from '@/components/Table';
|
||||||
import FileUpload from '@/components/FileUpload';
|
import FileUpload from '@/components/Form/FileUpload';
|
||||||
import { Upload, Eye, Trash2, FileText } from 'lucide-react';
|
import { Upload, Eye, Trash2, FileText } from 'lucide-react';
|
||||||
import { BASE_URL } from '@/utils/Url';
|
import { BASE_URL } from '@/utils/Url';
|
||||||
import Popup from '@/components/Popup';
|
import Popup from '@/components/Popup';
|
||||||
@ -230,7 +230,7 @@ export default function FilesToUpload({
|
|||||||
<div className="mt-4">
|
<div className="mt-4">
|
||||||
{actionType === 'view' && selectedFile.fileName ? (
|
{actionType === 'view' && selectedFile.fileName ? (
|
||||||
<iframe
|
<iframe
|
||||||
src={`${BASE_URL}/${selectedFile.fileName}`}
|
src={`${BASE_URL}${selectedFile.fileName}`}
|
||||||
title="Document Viewer"
|
title="Document Viewer"
|
||||||
className="w-full"
|
className="w-full"
|
||||||
style={{
|
style={{
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
// Import des dépendances nécessaires
|
// Import des dépendances nécessaires
|
||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import Button from '@/components/Button';
|
import Button from '@/components/Form/Button';
|
||||||
import DjangoCSRFToken from '@/components/DjangoCSRFToken';
|
import DjangoCSRFToken from '@/components/DjangoCSRFToken';
|
||||||
import {
|
import {
|
||||||
fetchSchoolFileTemplatesFromRegistrationFiles,
|
fetchSchoolFileTemplatesFromRegistrationFiles,
|
||||||
@ -220,9 +220,7 @@ export default function InscriptionFormShared({
|
|||||||
.then((data) => {
|
.then((data) => {
|
||||||
setProfiles(data);
|
setProfiles(data);
|
||||||
})
|
})
|
||||||
.catch((error) =>
|
.catch((error) => logger.error('Error fetching profiles : ', error));
|
||||||
logger.error('Error fetching profiles : ', error)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (selectedEstablishmentId) {
|
if (selectedEstablishmentId) {
|
||||||
// Fetch data for registration payment modes
|
// Fetch data for registration payment modes
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import React, { useEffect } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import SelectChoice from '@/components/SelectChoice';
|
import SelectChoice from '@/components/Form/SelectChoice';
|
||||||
import RadioList from '@/components/RadioList';
|
import RadioList from '@/components/Form/RadioList';
|
||||||
import logger from '@/utils/logger';
|
import logger from '@/utils/logger';
|
||||||
|
|
||||||
export default function PaymentMethodSelector({
|
export default function PaymentMethodSelector({
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import InputText from '@/components/InputText';
|
import InputText from '@/components/Form/InputText';
|
||||||
import InputPhone from '@/components/InputPhone';
|
import InputPhone from '@/components/Form/InputPhone';
|
||||||
import React, { useEffect } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import { useTranslations } from 'next-intl';
|
import { useTranslations } from 'next-intl';
|
||||||
import { Trash2, Plus, Users } from 'lucide-react';
|
import { Trash2, Plus, Users } from 'lucide-react';
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import InputText from '@/components/InputText';
|
import InputText from '@/components/Form/InputText';
|
||||||
import React, { useEffect } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import { Trash2, Plus, Users } from 'lucide-react';
|
import { Trash2, Plus, Users } from 'lucide-react';
|
||||||
import SectionHeader from '@/components/SectionHeader';
|
import SectionHeader from '@/components/SectionHeader';
|
||||||
|
|||||||
@ -1,12 +1,12 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import InputText from '@/components/InputText';
|
import InputText from '@/components/Form/InputText';
|
||||||
import SelectChoice from '@/components/SelectChoice';
|
import SelectChoice from '@/components/Form/SelectChoice';
|
||||||
import Loader from '@/components/Loader';
|
import Loader from '@/components/Loader';
|
||||||
import { fetchRegisterForm } from '@/app/actions/subscriptionAction';
|
import { fetchRegisterForm } from '@/app/actions/subscriptionAction';
|
||||||
import logger from '@/utils/logger';
|
import logger from '@/utils/logger';
|
||||||
import SectionHeader from '@/components/SectionHeader';
|
import SectionHeader from '@/components/SectionHeader';
|
||||||
import { User } from 'lucide-react';
|
import { User } from 'lucide-react';
|
||||||
import FileUpload from '@/components/FileUpload';
|
import FileUpload from '@/components/Form/FileUpload';
|
||||||
import { BASE_URL } from '@/utils/Url';
|
import { BASE_URL } from '@/utils/Url';
|
||||||
import { levels, genders } from '@/utils/constants';
|
import { levels, genders } from '@/utils/constants';
|
||||||
|
|
||||||
@ -113,7 +113,9 @@ export default function StudentInfoForm({
|
|||||||
(!formData.birth_place || formData.birth_place.trim() === '')) ||
|
(!formData.birth_place || formData.birth_place.trim() === '')) ||
|
||||||
(field === 'birth_postal_code' &&
|
(field === 'birth_postal_code' &&
|
||||||
(!formData.birth_postal_code ||
|
(!formData.birth_postal_code ||
|
||||||
String(formData.birth_postal_code).trim() === '')) ||
|
String(formData.birth_postal_code).trim() === '' ||
|
||||||
|
isNaN(Number(formData.birth_postal_code)) ||
|
||||||
|
!Number.isInteger(Number(formData.birth_postal_code)))) ||
|
||||||
(field === 'address' &&
|
(field === 'address' &&
|
||||||
(!formData.address || formData.address.trim() === '')) ||
|
(!formData.address || formData.address.trim() === '')) ||
|
||||||
(field === 'attending_physician' &&
|
(field === 'attending_physician' &&
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
'use client';
|
'use client';
|
||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import ToggleSwitch from '@/components/ToggleSwitch';
|
import ToggleSwitch from '@/components/Form/ToggleSwitch';
|
||||||
import SelectChoice from '@/components/SelectChoice';
|
import SelectChoice from '@/components/Form/SelectChoice';
|
||||||
import { BASE_URL } from '@/utils/Url';
|
import { BASE_URL } from '@/utils/Url';
|
||||||
import {
|
import {
|
||||||
fetchSchoolFileTemplatesFromRegistrationFiles,
|
fetchSchoolFileTemplatesFromRegistrationFiles,
|
||||||
@ -10,7 +10,7 @@ import {
|
|||||||
import logger from '@/utils/logger';
|
import logger from '@/utils/logger';
|
||||||
import { School, CheckCircle, Hourglass, FileText } from 'lucide-react';
|
import { School, CheckCircle, Hourglass, FileText } from 'lucide-react';
|
||||||
import SectionHeader from '@/components/SectionHeader';
|
import SectionHeader from '@/components/SectionHeader';
|
||||||
import Button from '@/components/Button';
|
import Button from '@/components/Form/Button';
|
||||||
|
|
||||||
export default function ValidateSubscription({
|
export default function ValidateSubscription({
|
||||||
studentId,
|
studentId,
|
||||||
@ -81,14 +81,13 @@ export default function ValidateSubscription({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleAssignClass = () => {
|
const handleAssignClass = () => {
|
||||||
const fusionParam = mergeDocuments ? 'true' : 'false';
|
|
||||||
if (formData.associated_class) {
|
if (formData.associated_class) {
|
||||||
const data = {
|
const data = {
|
||||||
student: {
|
student: {
|
||||||
associated_class: formData.associated_class,
|
associated_class: formData.associated_class,
|
||||||
},
|
},
|
||||||
status: 5,
|
status: 5,
|
||||||
fusionParam: fusionParam,
|
fusionParam: mergeDocuments,
|
||||||
};
|
};
|
||||||
|
|
||||||
onAccept(data);
|
onAccept(data);
|
||||||
@ -147,7 +146,7 @@ export default function ValidateSubscription({
|
|||||||
{allTemplates[currentTemplateIndex].name || 'Document sans nom'}
|
{allTemplates[currentTemplateIndex].name || 'Document sans nom'}
|
||||||
</h3>
|
</h3>
|
||||||
<iframe
|
<iframe
|
||||||
src={`${BASE_URL}/${allTemplates[currentTemplateIndex].file}`}
|
src={`${BASE_URL}${allTemplates[currentTemplateIndex].file}`}
|
||||||
title={
|
title={
|
||||||
allTemplates[currentTemplateIndex].type === 'main'
|
allTemplates[currentTemplateIndex].type === 'main'
|
||||||
? 'Document Principal'
|
? 'Document Principal'
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user