mirror of
https://git.v0id.ovh/n3wt-innov/n3wt-school.git
synced 2026-01-29 07:53:23 +00:00
Compare commits
1 Commits
9481a0132d
...
A64_TestsA
| Author | SHA1 | Date | |
|---|---|---|---|
| 0a2b23f260 |
2
Back-End/.gitignore
vendored
2
Back-End/.gitignore
vendored
@ -5,3 +5,5 @@ data
|
|||||||
*.dmp
|
*.dmp
|
||||||
staticfiles
|
staticfiles
|
||||||
/*/Configuration/application*.json
|
/*/Configuration/application*.json
|
||||||
|
rapport_tests_back.json
|
||||||
|
tests_automatiques.json
|
||||||
@ -66,8 +66,11 @@ 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
|
||||||
daphne==4.1.0
|
daphne==4.1.0
|
||||||
|
pytest
|
||||||
|
djangorestframework
|
||||||
|
pytest-django
|
||||||
|
pytest-json-report
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
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)
|
||||||
@ -12,7 +11,6 @@ def run_command(command):
|
|||||||
return process.returncode
|
return process.returncode
|
||||||
|
|
||||||
test_mode = os.getenv('test_mode', 'false').lower() == '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"],
|
||||||
@ -34,17 +32,6 @@ test_commands = [
|
|||||||
["python", "manage.py", "init_mock_datas"]
|
["python", "manage.py", "init_mock_datas"]
|
||||||
]
|
]
|
||||||
|
|
||||||
def run_daphne():
|
|
||||||
try:
|
|
||||||
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 __name__ == "__main__":
|
|
||||||
for command in commands:
|
for command in commands:
|
||||||
if run_command(command) != 0:
|
if run_command(command) != 0:
|
||||||
exit(1)
|
exit(1)
|
||||||
@ -54,35 +41,14 @@ if __name__ == "__main__":
|
|||||||
# if run_command(test_command) != 0:
|
# if run_command(test_command) != 0:
|
||||||
# exit(1)
|
# exit(1)
|
||||||
|
|
||||||
if watch_mode:
|
# Lancer les processus en parallèle
|
||||||
celery_worker = subprocess.Popen(["celery", "-A", "N3wtSchool", "worker", "--loglevel=info"])
|
|
||||||
celery_beat = subprocess.Popen(["celery", "-A", "N3wtSchool", "beat", "--loglevel=info", "--scheduler", "django_celery_beat.schedulers:DatabaseScheduler"])
|
|
||||||
try:
|
|
||||||
run_process(
|
|
||||||
'.',
|
|
||||||
target=run_daphne
|
|
||||||
)
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
print("Arrêt demandé (KeyboardInterrupt)")
|
|
||||||
finally:
|
|
||||||
celery_worker.terminate()
|
|
||||||
celery_beat.terminate()
|
|
||||||
celery_worker.wait()
|
|
||||||
celery_beat.wait()
|
|
||||||
else:
|
|
||||||
processes = [
|
processes = [
|
||||||
subprocess.Popen([
|
subprocess.Popen(["daphne", "-b", "0.0.0.0", "-p", "8080", "N3wtSchool.asgi:application"]),
|
||||||
"daphne", "-b", "0.0.0.0", "-p", "8080", "N3wtSchool.asgi:application"
|
|
||||||
]),
|
|
||||||
subprocess.Popen(["celery", "-A", "N3wtSchool", "worker", "--loglevel=info"]),
|
subprocess.Popen(["celery", "-A", "N3wtSchool", "worker", "--loglevel=info"]),
|
||||||
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:
|
|
||||||
for process in processes:
|
# Attendre la fin des processus
|
||||||
process.wait()
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
print("Arrêt demandé (KeyboardInterrupt)")
|
|
||||||
for process in processes:
|
|
||||||
process.terminate()
|
|
||||||
for process in processes:
|
for process in processes:
|
||||||
process.wait()
|
process.wait()
|
||||||
39
Back-End/test_all_endpoints.py
Normal file
39
Back-End/test_all_endpoints.py
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
"""
|
||||||
|
Starter de tests automatiques pour valider tous les endpoints définis dans les fichiers urls.py du projet Django N3WT-SCHOOL.
|
||||||
|
Chaque endpoint est testé pour la réponse HTTP attendue (200, 401, 403, 404, etc.).
|
||||||
|
"""
|
||||||
|
import pytest
|
||||||
|
from django.urls import reverse, resolve
|
||||||
|
from rest_framework.test import APIClient
|
||||||
|
from django.conf import settings
|
||||||
|
from django.urls import get_resolver
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
class TestAllEndpoints:
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
def setup(self):
|
||||||
|
self.client = APIClient()
|
||||||
|
|
||||||
|
def get_all_url_patterns(self):
|
||||||
|
resolver = get_resolver()
|
||||||
|
patterns = resolver.url_patterns
|
||||||
|
urls = []
|
||||||
|
def extract(patterns, prefix=""):
|
||||||
|
for p in patterns:
|
||||||
|
if hasattr(p, 'url_patterns'):
|
||||||
|
extract(p.url_patterns, prefix + str(p.pattern))
|
||||||
|
else:
|
||||||
|
urls.append(prefix + str(p.pattern))
|
||||||
|
extract(patterns)
|
||||||
|
return urls
|
||||||
|
|
||||||
|
def test_all_endpoints_anonymous(self):
|
||||||
|
urls = self.get_all_url_patterns()
|
||||||
|
for url in urls:
|
||||||
|
if '<' in url: # skip dynamic urls for starter
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
response = self.client.get(url)
|
||||||
|
assert response.status_code in [200, 401, 403, 404]
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Erreur sur {url}: {e}")
|
||||||
124
Back-End/test_auth_endpoints.py
Normal file
124
Back-End/test_auth_endpoints.py
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
"""
|
||||||
|
Tests automatiques pour les endpoints Auth de l'API N3WT-SCHOOL.
|
||||||
|
- Teste les endpoints GET, y compris dynamiques.
|
||||||
|
- Teste l'authentification (login JWT) et l'accès aux endpoints protégés.
|
||||||
|
- Vérifie la structure JSON des réponses principales.
|
||||||
|
"""
|
||||||
|
import pytest
|
||||||
|
from django.urls import reverse
|
||||||
|
from rest_framework.test import APIClient
|
||||||
|
from Auth.models import Profile, ProfileRole
|
||||||
|
from Establishment.models import Establishment
|
||||||
|
from django.contrib.auth.hashers import make_password
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
class TestAuthEndpoints:
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
def setup(self, db):
|
||||||
|
self.client = APIClient()
|
||||||
|
# Création d'un établissement de test
|
||||||
|
self.establishment = Establishment.objects.create(
|
||||||
|
name="Etablissement Test",
|
||||||
|
address="1 rue du test",
|
||||||
|
total_capacity=100,
|
||||||
|
establishment_type=[1],
|
||||||
|
evaluation_frequency=1,
|
||||||
|
licence_code="LIC123",
|
||||||
|
is_active=True
|
||||||
|
)
|
||||||
|
# Création d'un utilisateur de test
|
||||||
|
self.test_email = 'testuser@example.com'
|
||||||
|
self.test_password = 'testpass123'
|
||||||
|
self.profile = Profile.objects.create(
|
||||||
|
email=self.test_email,
|
||||||
|
username=self.test_email,
|
||||||
|
password=make_password(self.test_password)
|
||||||
|
)
|
||||||
|
self.profile_role = ProfileRole.objects.create(
|
||||||
|
profile=self.profile,
|
||||||
|
role_type=1, # ADMIN
|
||||||
|
establishment=self.establishment,
|
||||||
|
is_active=True
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_csrf(self):
|
||||||
|
response = self.client.get('/Auth/csrf')
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert 'csrfToken' in response.json()
|
||||||
|
|
||||||
|
def test_login(self):
|
||||||
|
response = self.client.post('/Auth/login', {
|
||||||
|
'email': self.test_email,
|
||||||
|
'password': self.test_password
|
||||||
|
}, format='json')
|
||||||
|
assert response.status_code in [200, 401]
|
||||||
|
if response.status_code == 200:
|
||||||
|
assert 'access' in response.json() or 'token' in response.json()
|
||||||
|
|
||||||
|
def test_profiles(self):
|
||||||
|
# GET /Auth/profiles
|
||||||
|
response = self.client.get(f'/Auth/profiles')
|
||||||
|
assert response.status_code in [200, 401, 403]
|
||||||
|
if response.status_code == 200:
|
||||||
|
# Vérifie que le profil de test existe dans la liste
|
||||||
|
emails = [p.get('email') for p in response.json() if isinstance(p, dict)]
|
||||||
|
assert self.test_email in emails
|
||||||
|
|
||||||
|
def test_profiles_id(self):
|
||||||
|
# GET /Auth/profiles/<id>
|
||||||
|
response = self.client.get(f'/Auth/profiles/{self.profile.id}')
|
||||||
|
assert response.status_code in [200, 401, 403, 404]
|
||||||
|
if response.status_code == 200:
|
||||||
|
data = response.json()
|
||||||
|
assert data.get('email') == self.test_email
|
||||||
|
|
||||||
|
def test_profile_roles(self):
|
||||||
|
# GET /Auth/profileRoles avec paramètres requis
|
||||||
|
params = {
|
||||||
|
'establishment_id': self.establishment.id,
|
||||||
|
'filter': 'school'
|
||||||
|
}
|
||||||
|
response = self.client.get('/Auth/profileRoles', params)
|
||||||
|
assert response.status_code in [200, 401, 403, 400]
|
||||||
|
if response.status_code == 200:
|
||||||
|
results = response.json()
|
||||||
|
# Adapter à la structure réelle de la réponse : clé 'profilesRoles'
|
||||||
|
if isinstance(results, dict) and 'profilesRoles' in results:
|
||||||
|
results = results['profilesRoles']
|
||||||
|
found = any(
|
||||||
|
r.get('profile') == self.profile.id and r.get('role_type') == 1
|
||||||
|
for r in results if isinstance(r, dict)
|
||||||
|
)
|
||||||
|
assert found
|
||||||
|
|
||||||
|
def test_profile_roles_id(self):
|
||||||
|
# GET /Auth/profileRoles/<id>
|
||||||
|
response = self.client.get(f'/Auth/profileRoles/{self.profile_role.id}')
|
||||||
|
assert response.status_code in [200, 401, 403, 404]
|
||||||
|
if response.status_code == 200:
|
||||||
|
data = response.json()
|
||||||
|
assert data.get('profile') == self.profile.id
|
||||||
|
assert data.get('role_type') == 1
|
||||||
|
|
||||||
|
def test_reset_password(self):
|
||||||
|
# POST /Auth/resetPassword/<code> (méthode attendue)
|
||||||
|
response = self.client.post('/Auth/resetPassword/ABCDEF', {
|
||||||
|
'password1': 'newpass123',
|
||||||
|
'password2': 'newpass123'
|
||||||
|
}, format='json')
|
||||||
|
assert response.status_code in [200, 400, 404]
|
||||||
|
# 400 attendu si le code est invalide ou expiré
|
||||||
|
|
||||||
|
def test_info_session(self):
|
||||||
|
# GET /Auth/infoSession (protégé)
|
||||||
|
login = self.client.post('/Auth/login', {
|
||||||
|
'email': self.test_email,
|
||||||
|
'password': self.test_password
|
||||||
|
}, format='json')
|
||||||
|
if login.status_code == 200 and ('access' in login.json() or 'token' in login.json()):
|
||||||
|
token = login.json().get('access') or login.json().get('token')
|
||||||
|
self.client.credentials(HTTP_AUTHORIZATION=f'Bearer {token}')
|
||||||
|
response = self.client.get('/Auth/infoSession')
|
||||||
|
assert response.status_code in [200, 401, 403]
|
||||||
|
else:
|
||||||
|
pytest.skip('Impossible de s’authentifier pour tester infoSession')
|
||||||
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.3",
|
"version": "0.0.1",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "n3wt-school-front-end",
|
"name": "n3wt-school-front-end",
|
||||||
"version": "0.0.3",
|
"version": "0.0.1",
|
||||||
"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,7 +29,6 @@
|
|||||||
"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"
|
||||||
@ -8835,21 +8834,6 @@
|
|||||||
"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",
|
||||||
@ -17176,12 +17160,6 @@
|
|||||||
"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",
|
||||||
|
|||||||
@ -35,20 +35,19 @@
|
|||||||
"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/jest-dom": "^5.16.5",
|
|
||||||
"@testing-library/react": "^13.4.0",
|
"@testing-library/react": "^13.4.0",
|
||||||
|
"@testing-library/jest-dom": "^5.16.5",
|
||||||
"@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/Form/SelectChoice';
|
import SelectChoice from '@/components/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/Form/Button';
|
import Button from '@/components/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/Form/InputText';
|
import InputText from '@/components/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/Form/Button';
|
import Button from '@/components/Button';
|
||||||
import GradeView from '@/components/Grades/GradeView';
|
import GradeView from '@/components/Grades/GradeView';
|
||||||
import {
|
import {
|
||||||
fetchStudentCompetencies,
|
fetchStudentCompetencies,
|
||||||
|
|||||||
@ -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/Form/Button';
|
import Button from '@/components/Button';
|
||||||
import InputText from '@/components/Form/InputText';
|
import InputText from '@/components/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 {
|
||||||
|
|||||||
@ -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/Form/Button';
|
import Button from '@/components/Button';
|
||||||
import SelectChoice from '@/components/Form/SelectChoice';
|
import SelectChoice from '@/components/SelectChoice';
|
||||||
import CheckBox from '@/components/CheckBox';
|
import CheckBox from '@/components/CheckBox';
|
||||||
import {
|
import {
|
||||||
fetchAbsences,
|
fetchAbsences,
|
||||||
|
|||||||
@ -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/Form/InputTextIcon';
|
import InputTextIcon from '@/components/InputTextIcon';
|
||||||
import ToggleSwitch from '@/components/Form/ToggleSwitch';
|
import ToggleSwitch from '@/components/ToggleSwitch';
|
||||||
import Button from '@/components/Form/Button';
|
import Button from '@/components/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/Form/InputPhone';
|
import InputPhone from '@/components/InputPhone';
|
||||||
import CheckBox from '@/components/CheckBox';
|
import CheckBox from '@/components/CheckBox';
|
||||||
import RadioList from '@/components/Form/RadioList';
|
import RadioList from '@/components/RadioList';
|
||||||
import SelectChoice from '@/components/Form/SelectChoice';
|
import SelectChoice from '@/components/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';
|
||||||
|
|||||||
@ -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/Form/PhoneLabel';
|
import { PhoneLabel } from '@/components/PhoneLabel';
|
||||||
import FileUpload from '@/components/Form/FileUpload';
|
import FileUpload from '@/components/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';
|
||||||
|
|
||||||
@ -250,12 +250,7 @@ export default function Page({ params: { locale } }) {
|
|||||||
}, 500); // Debounce la recherche
|
}, 500); // Debounce la recherche
|
||||||
return () => clearTimeout(timeoutId);
|
return () => clearTimeout(timeoutId);
|
||||||
}
|
}
|
||||||
}, [
|
}, [searchTerm, selectedEstablishmentId, currentSchoolYearPage, itemsPerPage]);
|
||||||
searchTerm,
|
|
||||||
selectedEstablishmentId,
|
|
||||||
currentSchoolYearPage,
|
|
||||||
itemsPerPage,
|
|
||||||
]);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* UseEffect to update page count of tab
|
* UseEffect to update page count of tab
|
||||||
|
|||||||
@ -1,9 +1,8 @@
|
|||||||
'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/Form/Button';
|
import Button from '@/components/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');
|
||||||
@ -14,7 +13,6 @@ 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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,7 +11,7 @@ import {
|
|||||||
CalendarDays,
|
CalendarDays,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import StatusLabel from '@/components/StatusLabel';
|
import StatusLabel from '@/components/StatusLabel';
|
||||||
import FileUpload from '@/components/Form/FileUpload';
|
import FileUpload from '@/components/FileUpload';
|
||||||
import { FE_PARENTS_EDIT_SUBSCRIPTION_URL } from '@/utils/Url';
|
import { FE_PARENTS_EDIT_SUBSCRIPTION_URL } from '@/utils/Url';
|
||||||
import {
|
import {
|
||||||
fetchChildren,
|
fetchChildren,
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
'use client';
|
'use client';
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import Button from '@/components/Form/Button';
|
import Button from '@/components/Button';
|
||||||
import InputText from '@/components/Form/InputText';
|
import InputText from '@/components/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/Form/InputTextIcon';
|
import InputTextIcon from '@/components/InputTextIcon';
|
||||||
import Loader from '@/components/Loader'; // Importez le composant Loader
|
import Loader from '@/components/Loader'; // Importez le composant Loader
|
||||||
import Button from '@/components/Form/Button'; // Importez le composant Button
|
import Button from '@/components/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';
|
||||||
@ -35,7 +35,11 @@ export default function Page() {
|
|||||||
logger.debug('Sign In Result', result);
|
logger.debug('Sign In Result', result);
|
||||||
|
|
||||||
if (result.error) {
|
if (result.error) {
|
||||||
showNotification(result.error, 'error', 'Erreur');
|
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
|
||||||
@ -46,7 +50,11 @@ export default function Page() {
|
|||||||
if (url) {
|
if (url) {
|
||||||
router.push(url);
|
router.push(url);
|
||||||
} else {
|
} else {
|
||||||
showNotification('Type de rôle non géré', 'error', 'Erreur');
|
showNotification(
|
||||||
|
'Type de rôle non géré',
|
||||||
|
'error',
|
||||||
|
'Erreur'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
|
|||||||
@ -3,9 +3,9 @@
|
|||||||
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 InputTextIcon from '@/components/Form/InputTextIcon';
|
import InputTextIcon from '@/components/InputTextIcon';
|
||||||
import Loader from '@/components/Loader';
|
import Loader from '@/components/Loader';
|
||||||
import Button from '@/components/Form/Button';
|
import Button from '@/components/Button';
|
||||||
import { User } from 'lucide-react';
|
import { User } from 'lucide-react';
|
||||||
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';
|
||||||
@ -25,13 +25,25 @@ export default function Page() {
|
|||||||
.then((data) => {
|
.then((data) => {
|
||||||
logger.debug('Success:', data);
|
logger.debug('Success:', data);
|
||||||
if (data.message !== '') {
|
if (data.message !== '') {
|
||||||
showNotification(data.message, 'success', 'Succès');
|
showNotification(
|
||||||
|
data.message,
|
||||||
|
'success',
|
||||||
|
'Succès'
|
||||||
|
);
|
||||||
router.push(`${FE_USERS_LOGIN_URL}`);
|
router.push(`${FE_USERS_LOGIN_URL}`);
|
||||||
} else {
|
} else {
|
||||||
if (data.errorMessage) {
|
if (data.errorMessage) {
|
||||||
showNotification(data.errorMessage, 'error', 'Erreur');
|
showNotification(
|
||||||
|
data.errorMessage,
|
||||||
|
'error',
|
||||||
|
'Erreur'
|
||||||
|
);
|
||||||
} else if (data.errorFields) {
|
} else if (data.errorFields) {
|
||||||
showNotification(data.errorFields.email, 'error', 'Erreur');
|
showNotification(
|
||||||
|
data.errorFields.email,
|
||||||
|
'error',
|
||||||
|
'Erreur'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
|
|||||||
@ -5,9 +5,9 @@ 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/Form/InputTextIcon';
|
import InputTextIcon from '@/components/InputTextIcon';
|
||||||
import Loader from '@/components/Loader';
|
import Loader from '@/components/Loader';
|
||||||
import Button from '@/components/Form/Button';
|
import Button from '@/components/Button';
|
||||||
import { FE_USERS_LOGIN_URL } from '@/utils/Url';
|
import { FE_USERS_LOGIN_URL } from '@/utils/Url';
|
||||||
import { KeySquare } from 'lucide-react';
|
import { KeySquare } from 'lucide-react';
|
||||||
import { useCsrfToken } from '@/context/CsrfContext';
|
import { useCsrfToken } from '@/context/CsrfContext';
|
||||||
@ -33,12 +33,21 @@ export default function Page() {
|
|||||||
resetPassword(uuid, data, csrfToken)
|
resetPassword(uuid, data, csrfToken)
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
if (data.message !== '') {
|
if (data.message !== '') {
|
||||||
|
|
||||||
logger.debug('Success:', data);
|
logger.debug('Success:', data);
|
||||||
showNotification(data.message, 'success', 'Succès');
|
showNotification(
|
||||||
|
data.message,
|
||||||
|
'success',
|
||||||
|
'Succès'
|
||||||
|
);
|
||||||
router.push(`${FE_USERS_LOGIN_URL}`);
|
router.push(`${FE_USERS_LOGIN_URL}`);
|
||||||
} else {
|
} else {
|
||||||
if (data.errorMessage) {
|
if (data.errorMessage) {
|
||||||
showNotification(data.errorMessage, 'error', 'Erreur');
|
showNotification(
|
||||||
|
data.errorMessage,
|
||||||
|
'error',
|
||||||
|
'Erreur'
|
||||||
|
);
|
||||||
} else if (data.errorFields) {
|
} else if (data.errorFields) {
|
||||||
showNotification(
|
showNotification(
|
||||||
data.errorFields.password1 || data.errorFields.password2,
|
data.errorFields.password1 || data.errorFields.password2,
|
||||||
|
|||||||
@ -4,9 +4,9 @@ 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/Form/InputTextIcon';
|
import InputTextIcon from '@/components/InputTextIcon';
|
||||||
import Loader from '@/components/Loader';
|
import Loader from '@/components/Loader';
|
||||||
import Button from '@/components/Form/Button';
|
import Button from '@/components/Button';
|
||||||
import { User, KeySquare } from 'lucide-react';
|
import { User, KeySquare } from 'lucide-react';
|
||||||
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';
|
||||||
@ -36,16 +36,22 @@ export default function Page() {
|
|||||||
.then((data) => {
|
.then((data) => {
|
||||||
logger.debug('Success:', data);
|
logger.debug('Success:', data);
|
||||||
if (data.message !== '') {
|
if (data.message !== '') {
|
||||||
showNotification(data.message, 'success', 'Succès');
|
showNotification(
|
||||||
|
data.message,
|
||||||
|
'success',
|
||||||
|
'Succès'
|
||||||
|
);
|
||||||
router.push(`${FE_USERS_LOGIN_URL}`);
|
router.push(`${FE_USERS_LOGIN_URL}`);
|
||||||
} else {
|
} else {
|
||||||
if (data.errorMessage) {
|
if (data.errorMessage) {
|
||||||
showNotification(data.errorMessage, 'error', 'Erreur');
|
showNotification(
|
||||||
|
data.errorMessage,
|
||||||
|
'error',
|
||||||
|
'Erreur'
|
||||||
|
);
|
||||||
} else if (data.errorFields) {
|
} else if (data.errorFields) {
|
||||||
showNotification(
|
showNotification(
|
||||||
data.errorFields.email ||
|
data.errorFields.email || data.errorFields.password1 || data.errorFields.password2,
|
||||||
data.errorFields.password1 ||
|
|
||||||
data.errorFields.password2,
|
|
||||||
'error',
|
'error',
|
||||||
'Erreur'
|
'Erreur'
|
||||||
);
|
);
|
||||||
|
|||||||
@ -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/Form/WisiwigTextArea';
|
import WisiwigTextArea from '@/components/WisiwigTextArea';
|
||||||
import logger from '@/utils/logger';
|
import logger from '@/utils/logger';
|
||||||
import InputText from '@/components/Form/InputText';
|
import InputText from '@/components/InputText';
|
||||||
import Button from '@/components/Form/Button';
|
import Button from '@/components/Button';
|
||||||
|
|
||||||
export default function EmailSender({ csrfToken }) {
|
export default function EmailSender({ csrfToken }) {
|
||||||
const [recipients, setRecipients] = useState([]);
|
const [recipients, setRecipients] = useState([]);
|
||||||
|
|||||||
@ -1,194 +0,0 @@
|
|||||||
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,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/Form/ToggleSwitch';
|
import ToggleSwitch from '@/components/ToggleSwitch';
|
||||||
import Button from '@/components/Form/Button';
|
import Button from '@/components/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/Form/RadioList';
|
import RadioList from '@/components/RadioList';
|
||||||
|
|
||||||
const LEVELS = [
|
const LEVELS = [
|
||||||
{ value: 0, label: 'Non évalué' },
|
{ value: 0, label: 'Non évalué' },
|
||||||
|
|||||||
@ -1,5 +1,3 @@
|
|||||||
import React from 'react';
|
|
||||||
|
|
||||||
export default function InputTextIcon({
|
export default function InputTextIcon({
|
||||||
name,
|
name,
|
||||||
type,
|
type,
|
||||||
@ -33,11 +31,9 @@ export default function InputTextIcon({
|
|||||||
!enable ? 'bg-gray-100 cursor-not-allowed' : ''
|
!enable ? 'bg-gray-100 cursor-not-allowed' : ''
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{IconItem ? (
|
|
||||||
<span className="inline-flex min-h-9 items-center px-3 rounded-l-md bg-gray-50 text-gray-500 text-sm">
|
<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>
|
</span>
|
||||||
) : null}
|
|
||||||
<input
|
<input
|
||||||
type={type}
|
type={type}
|
||||||
id={name}
|
id={name}
|
||||||
@ -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/Form/FileUpload';
|
import FileUpload from '@/components/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';
|
||||||
|
|||||||
@ -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/Form/Button';
|
import Button from '@/components/Button';
|
||||||
import DjangoCSRFToken from '@/components/DjangoCSRFToken';
|
import DjangoCSRFToken from '@/components/DjangoCSRFToken';
|
||||||
import {
|
import {
|
||||||
fetchSchoolFileTemplatesFromRegistrationFiles,
|
fetchSchoolFileTemplatesFromRegistrationFiles,
|
||||||
@ -220,7 +220,9 @@ export default function InscriptionFormShared({
|
|||||||
.then((data) => {
|
.then((data) => {
|
||||||
setProfiles(data);
|
setProfiles(data);
|
||||||
})
|
})
|
||||||
.catch((error) => logger.error('Error fetching profiles : ', error));
|
.catch((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/Form/SelectChoice';
|
import SelectChoice from '@/components/SelectChoice';
|
||||||
import RadioList from '@/components/Form/RadioList';
|
import RadioList from '@/components/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/Form/InputText';
|
import InputText from '@/components/InputText';
|
||||||
import InputPhone from '@/components/Form/InputPhone';
|
import InputPhone from '@/components/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/Form/InputText';
|
import InputText from '@/components/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/Form/InputText';
|
import InputText from '@/components/InputText';
|
||||||
import SelectChoice from '@/components/Form/SelectChoice';
|
import SelectChoice from '@/components/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/Form/FileUpload';
|
import FileUpload from '@/components/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';
|
||||||
|
|
||||||
@ -112,10 +112,13 @@ export default function StudentInfoForm({
|
|||||||
(field === 'birth_place' &&
|
(field === 'birth_place' &&
|
||||||
(!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)) ||
|
isNaN(Number(formData.birth_postal_code)) ||
|
||||||
!Number.isInteger(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/Form/ToggleSwitch';
|
import ToggleSwitch from '@/components/ToggleSwitch';
|
||||||
import SelectChoice from '@/components/Form/SelectChoice';
|
import SelectChoice from '@/components/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/Form/Button';
|
import Button from '@/components/Button';
|
||||||
|
|
||||||
export default function ValidateSubscription({
|
export default function ValidateSubscription({
|
||||||
studentId,
|
studentId,
|
||||||
|
|||||||
@ -2,9 +2,9 @@ import React, { useState, useRef, useCallback } from 'react';
|
|||||||
import TreeView from '@/components/Structure/Competencies/TreeView';
|
import TreeView from '@/components/Structure/Competencies/TreeView';
|
||||||
import SectionHeader from '@/components/SectionHeader';
|
import SectionHeader from '@/components/SectionHeader';
|
||||||
import { Award, CheckCircle } from 'lucide-react';
|
import { Award, CheckCircle } from 'lucide-react';
|
||||||
import SelectChoice from '@/components/Form/SelectChoice';
|
import SelectChoice from '@/components/SelectChoice';
|
||||||
import CheckBox from '@/components/CheckBox';
|
import CheckBox from '@/components/CheckBox';
|
||||||
import Button from '@/components/Form/Button';
|
import Button from '@/components/Button';
|
||||||
import { useEstablishment } from '@/context/EstablishmentContext';
|
import { useEstablishment } from '@/context/EstablishmentContext';
|
||||||
import {
|
import {
|
||||||
fetchEstablishmentCompetencies,
|
fetchEstablishmentCompetencies,
|
||||||
|
|||||||
@ -2,10 +2,10 @@ import { Trash2, Edit3, ZoomIn, Users, Check, X, Hand } from 'lucide-react';
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import Table from '@/components/Table';
|
import Table from '@/components/Table';
|
||||||
import Popup from '@/components/Popup';
|
import Popup from '@/components/Popup';
|
||||||
import InputText from '@/components/Form/InputText';
|
import InputText from '@/components/InputText';
|
||||||
import SelectChoice from '@/components/Form/SelectChoice';
|
import SelectChoice from '@/components/SelectChoice';
|
||||||
import TeacherItem from '@/components/Structure/Configuration/TeacherItem';
|
import TeacherItem from '@/components/Structure/Configuration/TeacherItem';
|
||||||
import MultiSelect from '@/components/Form/MultiSelect';
|
import MultiSelect from '@/components/MultiSelect';
|
||||||
import LevelLabel from '@/components/CustomLabels/LevelLabel';
|
import LevelLabel from '@/components/CustomLabels/LevelLabel';
|
||||||
import { DndProvider, useDrop } from 'react-dnd';
|
import { DndProvider, useDrop } from 'react-dnd';
|
||||||
import { HTML5Backend } from 'react-dnd-html5-backend';
|
import { HTML5Backend } from 'react-dnd-html5-backend';
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import { Trash2, Edit3, Check, X, BookOpen } from 'lucide-react';
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import Table from '@/components/Table';
|
import Table from '@/components/Table';
|
||||||
import Popup from '@/components/Popup';
|
import Popup from '@/components/Popup';
|
||||||
import InputTextWithColorIcon from '@/components/Form/InputTextWithColorIcon';
|
import InputTextWithColorIcon from '@/components/InputTextWithColorIcon';
|
||||||
import { DndProvider } from 'react-dnd';
|
import { DndProvider } from 'react-dnd';
|
||||||
import { HTML5Backend } from 'react-dnd-html5-backend';
|
import { HTML5Backend } from 'react-dnd-html5-backend';
|
||||||
import SpecialityItem from '@/components/Structure/Configuration/SpecialityItem';
|
import SpecialityItem from '@/components/Structure/Configuration/SpecialityItem';
|
||||||
|
|||||||
@ -2,11 +2,11 @@ import React, { useState, useEffect } from 'react';
|
|||||||
import { Edit3, Trash2, GraduationCap, Check, X, Hand } from 'lucide-react';
|
import { Edit3, Trash2, GraduationCap, Check, X, Hand } from 'lucide-react';
|
||||||
import Table from '@/components/Table';
|
import Table from '@/components/Table';
|
||||||
import Popup from '@/components/Popup';
|
import Popup from '@/components/Popup';
|
||||||
import ToggleSwitch from '@/components/Form/ToggleSwitch';
|
import ToggleSwitch from '@/components/ToggleSwitch';
|
||||||
import { useCsrfToken } from '@/context/CsrfContext';
|
import { useCsrfToken } from '@/context/CsrfContext';
|
||||||
import { DndProvider, useDrag, useDrop } from 'react-dnd';
|
import { DndProvider, useDrag, useDrop } from 'react-dnd';
|
||||||
import { HTML5Backend } from 'react-dnd-html5-backend';
|
import { HTML5Backend } from 'react-dnd-html5-backend';
|
||||||
import InputText from '@/components/Form/InputText';
|
import InputText from '@/components/InputText';
|
||||||
import SpecialityItem from '@/components/Structure/Configuration/SpecialityItem';
|
import SpecialityItem from '@/components/Structure/Configuration/SpecialityItem';
|
||||||
import TeacherItem from './TeacherItem';
|
import TeacherItem from './TeacherItem';
|
||||||
import logger from '@/utils/logger';
|
import logger from '@/utils/logger';
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import {
|
|||||||
} from '@/app/actions/registerFileGroupAction';
|
} from '@/app/actions/registerFileGroupAction';
|
||||||
import { DocusealBuilder } from '@docuseal/react';
|
import { DocusealBuilder } from '@docuseal/react';
|
||||||
import logger from '@/utils/logger';
|
import logger from '@/utils/logger';
|
||||||
import MultiSelect from '@/components/Form/MultiSelect'; // Import du composant MultiSelect
|
import MultiSelect from '@/components/MultiSelect'; // Import du composant MultiSelect
|
||||||
import { useCsrfToken } from '@/context/CsrfContext';
|
import { useCsrfToken } from '@/context/CsrfContext';
|
||||||
import { useEstablishment } from '@/context/EstablishmentContext';
|
import { useEstablishment } from '@/context/EstablishmentContext';
|
||||||
import Popup from '@/components/Popup';
|
import Popup from '@/components/Popup';
|
||||||
@ -121,13 +121,7 @@ export default function FileUploadDocuSeal({
|
|||||||
|
|
||||||
guardianDetails.forEach((guardian, index) => {
|
guardianDetails.forEach((guardian, index) => {
|
||||||
logger.debug('creation du clone avec required : ', is_required);
|
logger.debug('creation du clone avec required : ', is_required);
|
||||||
cloneTemplate(
|
cloneTemplate(templateMaster?.id, guardian.email, is_required, selectedEstablishmentId, apiDocuseal)
|
||||||
templateMaster?.id,
|
|
||||||
guardian.email,
|
|
||||||
is_required,
|
|
||||||
selectedEstablishmentId,
|
|
||||||
apiDocuseal
|
|
||||||
)
|
|
||||||
.then((clonedDocument) => {
|
.then((clonedDocument) => {
|
||||||
// Sauvegarde des schoolFileTemplates clonés dans la base de données
|
// Sauvegarde des schoolFileTemplates clonés dans la base de données
|
||||||
const data = {
|
const data = {
|
||||||
|
|||||||
@ -1,14 +1,14 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { Plus, Edit3, Trash2, Check, X, FileText } from 'lucide-react';
|
import { Plus, Edit3, Trash2, Check, X, FileText } from 'lucide-react';
|
||||||
import Table from '@/components/Table';
|
import Table from '@/components/Table';
|
||||||
import InputText from '@/components/Form/InputText';
|
import InputText from '@/components/InputText';
|
||||||
import MultiSelect from '@/components/Form/MultiSelect';
|
import MultiSelect from '@/components/MultiSelect';
|
||||||
import Popup from '@/components/Popup';
|
import Popup from '@/components/Popup';
|
||||||
import logger from '@/utils/logger';
|
import logger from '@/utils/logger';
|
||||||
import { createRegistrationParentFileTemplate } from '@/app/actions/registerFileGroupAction';
|
import { createRegistrationParentFileTemplate } from '@/app/actions/registerFileGroupAction';
|
||||||
import { useCsrfToken } from '@/context/CsrfContext';
|
import { useCsrfToken } from '@/context/CsrfContext';
|
||||||
import SectionHeader from '@/components/SectionHeader';
|
import SectionHeader from '@/components/SectionHeader';
|
||||||
import ToggleSwitch from '@/components/Form/ToggleSwitch';
|
import ToggleSwitch from '@/components/ToggleSwitch';
|
||||||
import { useNotification } from '@/context/NotificationContext';
|
import { useNotification } from '@/context/NotificationContext';
|
||||||
import AlertMessage from '@/components/AlertMessage';
|
import AlertMessage from '@/components/AlertMessage';
|
||||||
|
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import { Trash2, Edit3, Check, X, Percent, EuroIcon, Tag } from 'lucide-react';
|
|||||||
import Table from '@/components/Table';
|
import Table from '@/components/Table';
|
||||||
import Popup from '@/components/Popup';
|
import Popup from '@/components/Popup';
|
||||||
import CheckBox from '@/components/CheckBox';
|
import CheckBox from '@/components/CheckBox';
|
||||||
import InputText from '@/components/Form/InputText';
|
import InputText from '@/components/InputText';
|
||||||
import logger from '@/utils/logger';
|
import logger from '@/utils/logger';
|
||||||
import SectionHeader from '@/components/SectionHeader';
|
import SectionHeader from '@/components/SectionHeader';
|
||||||
import { useEstablishment } from '@/context/EstablishmentContext';
|
import { useEstablishment } from '@/context/EstablishmentContext';
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import { Trash2, Edit3, Check, X, EyeOff, Eye, CreditCard } from 'lucide-react';
|
|||||||
import Table from '@/components/Table';
|
import Table from '@/components/Table';
|
||||||
import Popup from '@/components/Popup';
|
import Popup from '@/components/Popup';
|
||||||
import CheckBox from '@/components/CheckBox';
|
import CheckBox from '@/components/CheckBox';
|
||||||
import InputText from '@/components/Form/InputText';
|
import InputText from '@/components/InputText';
|
||||||
import logger from '@/utils/logger';
|
import logger from '@/utils/logger';
|
||||||
import SectionHeader from '@/components/SectionHeader';
|
import SectionHeader from '@/components/SectionHeader';
|
||||||
import { useEstablishment } from '@/context/EstablishmentContext';
|
import { useEstablishment } from '@/context/EstablishmentContext';
|
||||||
|
|||||||
@ -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 = 'Zone de Texte',
|
label = 'Mail',
|
||||||
value,
|
value,
|
||||||
onChange,
|
onChange,
|
||||||
placeholder = 'Ecrivez votre texte ici...',
|
placeholder = 'Ecrivez votre mail ici...',
|
||||||
className = 'h-64',
|
className = 'h-64',
|
||||||
required = false,
|
required = false,
|
||||||
errorMsg,
|
errorMsg,
|
||||||
59
docs/tests_automatiques.md
Normal file
59
docs/tests_automatiques.md
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
# Documentation des tests automatiques
|
||||||
|
|
||||||
|
Ce document décrit la structure et l’utilisation du starter de tests automatiques pour valider tous les endpoints exposés par les fichiers `urls.py` des applications Django du Back-End.
|
||||||
|
|
||||||
|
## Objectif
|
||||||
|
|
||||||
|
- Vérifier automatiquement que chaque route définie dans le projet répond bien à une requête HTTP (statut attendu : 200, 401, 403, 404).
|
||||||
|
- Permettre une validation rapide de la couverture des endpoints lors de chaque pipeline CI.
|
||||||
|
|
||||||
|
## Structure
|
||||||
|
|
||||||
|
- Le fichier principal est `Back-End/test_all_endpoints.py`.
|
||||||
|
- Ce test utilise `pytest` et `pytest-django` pour parcourir toutes les routes du projet et effectuer une requête GET anonyme.
|
||||||
|
- Les routes dynamiques (avec paramètres) sont ignorées dans ce starter.
|
||||||
|
|
||||||
|
## Exécution
|
||||||
|
|
||||||
|
1. Installer les dépendances si besoin :
|
||||||
|
```sh
|
||||||
|
pip install pytest pytest-django djangorestframework
|
||||||
|
```
|
||||||
|
2. Lancer les tests :
|
||||||
|
```sh
|
||||||
|
pytest Back-End/test_all_endpoints.py
|
||||||
|
```
|
||||||
|
|
||||||
|
## Intégration CI
|
||||||
|
|
||||||
|
- Ajouter la commande de test dans votre pipeline CI (GitHub Actions, GitLab CI, Jenkins, etc.) :
|
||||||
|
```sh
|
||||||
|
pytest Back-End/test_all_endpoints.py
|
||||||
|
```
|
||||||
|
|
||||||
|
## Personnalisation
|
||||||
|
|
||||||
|
- Pour tester les routes nécessitant une authentification ou des paramètres, compléter le test avec des cas spécifiques.
|
||||||
|
- Pour chaque nouvelle route, le test s’exécutera automatiquement.
|
||||||
|
|
||||||
|
## Ajout
|
||||||
|
|
||||||
|
- Ajout d'un fichier de tests automatiques dédié à Auth (`test_auth_endpoints.py`) pour tester tous les endpoints Auth (GET, dynamiques, login JWT, accès protégé, structure JSON).
|
||||||
|
- Les tests créent un utilisateur et un profilRole de test, réalisent un login, et vérifient les accès aux endpoints principaux, y compris les routes dynamiques et protégées.
|
||||||
|
|
||||||
|
## Utilisation
|
||||||
|
|
||||||
|
- Lancer les tests avec :
|
||||||
|
```bash
|
||||||
|
pytest --json-report --json-report-file=tests_automatiques.json
|
||||||
|
```
|
||||||
|
- Le rapport inclura les résultats détaillés pour chaque endpoint Auth.
|
||||||
|
|
||||||
|
## Extension
|
||||||
|
|
||||||
|
- Étendre sur le même modèle pour les autres applications (School, Subscriptions, etc.)
|
||||||
|
- Ajouter des tests POST/PUT/DELETE et des cas d’erreur/permissions.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Pour toute question ou évolution, se référer au ticket associé.
|
||||||
Reference in New Issue
Block a user