mirror of
https://git.v0id.ovh/n3wt-innov/n3wt-school.git
synced 2026-06-04 13:26:11 +00:00
Compare commits
5 Commits
90b0d14418
...
409cf05f1a
| Author | SHA1 | Date | |
|---|---|---|---|
| 409cf05f1a | |||
| b0e04e3adc | |||
| 3c7266608d | |||
| 5bbbcb9dc1 | |||
| 053140c8be |
@ -9,6 +9,7 @@ from .models import Planning, Events, RecursionType
|
||||
from .serializers import PlanningSerializer, EventsSerializer
|
||||
|
||||
from N3wtSchool import bdd
|
||||
from Subscriptions.util import getCurrentSchoolYear
|
||||
|
||||
|
||||
class PlanningView(APIView):
|
||||
@ -17,6 +18,7 @@ class PlanningView(APIView):
|
||||
def get(self, request):
|
||||
establishment_id = request.GET.get('establishment_id', None)
|
||||
planning_mode = request.GET.get('planning_mode', None)
|
||||
current_school_year = getCurrentSchoolYear()
|
||||
|
||||
plannings = bdd.getAllObjects(Planning)
|
||||
|
||||
@ -25,7 +27,10 @@ class PlanningView(APIView):
|
||||
|
||||
# Filtrer en fonction du planning_mode
|
||||
if planning_mode == "classSchedule":
|
||||
plannings = plannings.filter(school_class__isnull=False)
|
||||
plannings = plannings.filter(
|
||||
school_class__isnull=False,
|
||||
school_class__school_year=current_school_year,
|
||||
)
|
||||
elif planning_mode == "planning":
|
||||
plannings = plannings.filter(school_class__isnull=True)
|
||||
|
||||
@ -79,6 +84,7 @@ class EventsView(APIView):
|
||||
def get(self, request):
|
||||
establishment_id = request.GET.get('establishment_id', None)
|
||||
planning_mode = request.GET.get('planning_mode', None)
|
||||
current_school_year = getCurrentSchoolYear()
|
||||
filterParams = {}
|
||||
plannings=[]
|
||||
events = Events.objects.all()
|
||||
@ -86,6 +92,8 @@ class EventsView(APIView):
|
||||
filterParams['establishment'] = establishment_id
|
||||
if planning_mode is not None:
|
||||
filterParams['school_class__isnull'] = (planning_mode!="classSchedule")
|
||||
if planning_mode == "classSchedule":
|
||||
filterParams['school_class__school_year'] = current_school_year
|
||||
if filterParams:
|
||||
plannings = Planning.objects.filter(**filterParams)
|
||||
events = Events.objects.filter(planning__in=plannings)
|
||||
|
||||
@ -21,7 +21,6 @@ from N3wtSchool import settings
|
||||
from django.utils import timezone
|
||||
import pytz
|
||||
import Subscriptions.util as util
|
||||
from N3wtSchool.mailManager import sendRegisterForm
|
||||
|
||||
class AbsenceManagementSerializer(serializers.ModelSerializer):
|
||||
student_name = serializers.SerializerMethodField()
|
||||
@ -228,14 +227,6 @@ class StudentSerializer(serializers.ModelSerializer):
|
||||
profile_role_serializer = ProfileRoleSerializer(data=profile_role_data)
|
||||
profile_role_serializer.is_valid(raise_exception=True)
|
||||
profile_role = profile_role_serializer.save()
|
||||
# Envoi du mail d'inscription si un nouveau profil vient d'être créé
|
||||
email = None
|
||||
if profile_data and 'email' in profile_data:
|
||||
email = profile_data['email']
|
||||
elif profile_role and profile_role.profile:
|
||||
email = profile_role.profile.email
|
||||
if email:
|
||||
sendRegisterForm(email, establishment_id)
|
||||
elif profile_role:
|
||||
# Récupérer un ProfileRole existant par son ID
|
||||
profile_role = ProfileRole.objects.get(id=profile_role.id)
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
'use client';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { getCurrentSchoolYear } from '@/utils/Date';
|
||||
|
||||
import StructureManagement from '@/components/Structure/Configuration/StructureManagement';
|
||||
import ScheduleManagement from '@/components/Structure/Planning/ScheduleManagement';
|
||||
@ -54,6 +55,17 @@ export default function Page() {
|
||||
|
||||
const csrfToken = useCsrfToken();
|
||||
const { selectedEstablishmentId, profileRole } = useEstablishment();
|
||||
const currentSchoolYear = getCurrentSchoolYear();
|
||||
|
||||
const scheduleClasses = classes.filter(
|
||||
(classe) => classe?.school_year === currentSchoolYear
|
||||
);
|
||||
const scheduleSpecialities = specialities.filter(
|
||||
(speciality) => speciality?.school_year === currentSchoolYear
|
||||
);
|
||||
const scheduleTeachers = teachers.filter(
|
||||
(teacher) => teacher?.school_year === currentSchoolYear
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedEstablishmentId) {
|
||||
@ -299,9 +311,9 @@ export default function Page() {
|
||||
<ClassesProvider>
|
||||
<ScheduleManagement
|
||||
handleUpdatePlanning={handleUpdatePlanning}
|
||||
classes={classes}
|
||||
specialities={specialities}
|
||||
teachers={teachers}
|
||||
classes={scheduleClasses}
|
||||
specialities={scheduleSpecialities}
|
||||
teachers={scheduleTeachers}
|
||||
/>
|
||||
</ClassesProvider>
|
||||
</div>
|
||||
|
||||
@ -55,6 +55,11 @@ export default function DynamicFormsList({
|
||||
return cleaned;
|
||||
};
|
||||
|
||||
const isDynamicForm = (template) =>
|
||||
template.formTemplateData &&
|
||||
Array.isArray(template.formTemplateData.fields) &&
|
||||
template.formTemplateData.fields.length > 0;
|
||||
|
||||
const hasLocalCompletion = (templateId) => {
|
||||
if (formsValidation[templateId] === true) return true;
|
||||
|
||||
@ -65,11 +70,22 @@ export default function DynamicFormsList({
|
||||
}
|
||||
|
||||
const savedResponses = existingResponses[templateId];
|
||||
return !!(
|
||||
if (
|
||||
savedResponses &&
|
||||
typeof savedResponses === 'object' &&
|
||||
Object.keys(savedResponses).length > 0
|
||||
);
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Pour les formulaires non dynamiques (upload de fichier),
|
||||
// vérifier si un fichier a déjà été uploadé sur le template
|
||||
const template = schoolFileTemplates.find((tpl) => tpl.id === templateId);
|
||||
if (template && template.file && !isDynamicForm(template)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
// Initialiser les données avec les réponses existantes
|
||||
@ -233,11 +249,6 @@ export default function DynamicFormsList({
|
||||
}
|
||||
};
|
||||
|
||||
const isDynamicForm = (template) =>
|
||||
template.formTemplateData &&
|
||||
Array.isArray(template.formTemplateData.fields) &&
|
||||
template.formTemplateData.fields.length > 0;
|
||||
|
||||
if (!schoolFileTemplates || schoolFileTemplates.length === 0) {
|
||||
return (
|
||||
<div className="text-center py-8">
|
||||
@ -497,10 +508,10 @@ export default function DynamicFormsList({
|
||||
{/* Cas non validé : bouton télécharger + upload */}
|
||||
{currentTemplate.isValidated !== true && (
|
||||
<div className="flex flex-col items-center gap-4 w-full">
|
||||
{/* Bouton télécharger le document source */}
|
||||
{currentTemplate.file && (
|
||||
{/* Bouton télécharger le document source (fichier maître) */}
|
||||
{(currentTemplate.master_file_url || currentTemplate.file) && (
|
||||
<a
|
||||
href={getSecureFileUrl(currentTemplate.file)}
|
||||
href={getSecureFileUrl(currentTemplate.master_file_url || currentTemplate.file)}
|
||||
className="flex items-center gap-2 px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 transition"
|
||||
download
|
||||
>
|
||||
@ -517,6 +528,7 @@ export default function DynamicFormsList({
|
||||
onFileSelect={(file) =>
|
||||
handleUpload(file, currentTemplate)
|
||||
}
|
||||
existingFile={currentTemplate.file_url || currentTemplate.file}
|
||||
required
|
||||
enable={true}
|
||||
/>
|
||||
|
||||
@ -67,11 +67,17 @@ const FilesModal = ({
|
||||
: null,
|
||||
schoolFiles: fetchedSchoolFiles.map((file) => ({
|
||||
name: file.name || 'Document scolaire',
|
||||
url: file.file ? getSecureFileUrl(file.file) : null,
|
||||
url:
|
||||
file.file_url || file.file
|
||||
? getSecureFileUrl(file.file_url || file.file)
|
||||
: null,
|
||||
})),
|
||||
parentFiles: parentFiles.map((file) => ({
|
||||
name: file.master_name || 'Document parent',
|
||||
url: file.file ? getSecureFileUrl(file.file) : null,
|
||||
url:
|
||||
file.file_url || file.file
|
||||
? getSecureFileUrl(file.file_url || file.file)
|
||||
: null,
|
||||
})),
|
||||
sepaFile: selectedRegisterForm.sepa_file
|
||||
? {
|
||||
|
||||
@ -11,6 +11,7 @@ import {
|
||||
} from '@/app/actions/subscriptionAction';
|
||||
import {
|
||||
editRegistrationSchoolFileTemplates,
|
||||
editRegistrationParentFileTemplates,
|
||||
} from '@/app/actions/registerFileGroupAction';
|
||||
import {
|
||||
fetchRegistrationPaymentModes,
|
||||
@ -415,7 +416,8 @@ export default function InscriptionFormShared({
|
||||
const templateData = await fetchFormResponses(template.id);
|
||||
if (templateData && templateData.formTemplateData) {
|
||||
if (templateData.formTemplateData.responses) {
|
||||
responsesMap[template.id] = templateData.formTemplateData.responses;
|
||||
responsesMap[template.id] =
|
||||
templateData.formTemplateData.responses;
|
||||
} else {
|
||||
// Extraire les réponses depuis les champs
|
||||
const responses = {};
|
||||
@ -554,7 +556,7 @@ export default function InscriptionFormShared({
|
||||
const updateData = new FormData();
|
||||
updateData.append('file', file, finalFileName);
|
||||
|
||||
return editRegistrationSchoolFileTemplates(
|
||||
return editRegistrationParentFileTemplates(
|
||||
selectedFile.id,
|
||||
updateData,
|
||||
csrfToken
|
||||
@ -596,6 +598,46 @@ export default function InscriptionFormShared({
|
||||
});
|
||||
};
|
||||
|
||||
const handleSchoolFileUpload = (file, selectedFile) => {
|
||||
if (!file || !selectedFile) {
|
||||
logger.error('Données manquantes pour le téléversement.');
|
||||
return Promise.reject(
|
||||
new Error('Données manquantes pour le téléversement.')
|
||||
);
|
||||
}
|
||||
|
||||
const updateData = new FormData();
|
||||
updateData.append('file', file);
|
||||
|
||||
return editRegistrationSchoolFileTemplates(
|
||||
selectedFile.id,
|
||||
updateData,
|
||||
csrfToken
|
||||
)
|
||||
.then((response) => {
|
||||
logger.debug('School file template mis à jour avec succès :', response);
|
||||
|
||||
// Mettre à jour uniquement schoolFileTemplates
|
||||
setSchoolFileTemplates((prevTemplates) =>
|
||||
prevTemplates.map((template) =>
|
||||
template.id === selectedFile.id
|
||||
? {
|
||||
...template,
|
||||
file: response.data.file,
|
||||
file_url: response.data.file_url,
|
||||
}
|
||||
: template
|
||||
)
|
||||
);
|
||||
|
||||
return response;
|
||||
})
|
||||
.catch((error) => {
|
||||
logger.error('Erreur lors de la mise à jour du school file :', error);
|
||||
throw error;
|
||||
});
|
||||
};
|
||||
|
||||
const handleDeleteFile = (templateId) => {
|
||||
const fileToDelete = uploadedFiles.find(
|
||||
(file) => parseInt(file.id) === templateId && file.fileName
|
||||
@ -827,7 +869,7 @@ export default function InscriptionFormShared({
|
||||
onFormSubmit={handleDynamicFormSubmit}
|
||||
onValidationChange={handleDynamicFormsValidationChange}
|
||||
enable={enable}
|
||||
onFileUpload={handleFileUpload}
|
||||
onFileUpload={handleSchoolFileUpload}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
||||
@ -6,7 +6,7 @@ import React, {
|
||||
useRef,
|
||||
useCallback,
|
||||
} from 'react';
|
||||
import { useSession } from 'next-auth/react';
|
||||
import { useSession, getSession } from 'next-auth/react';
|
||||
import logger from '@/utils/logger';
|
||||
import { WS_CHAT_URL } from '@/utils/Url';
|
||||
|
||||
@ -54,14 +54,15 @@ export const ChatConnectionProvider = ({ children }) => {
|
||||
}, []);
|
||||
|
||||
// Configuration WebSocket
|
||||
const getWebSocketUrl = (userId) => {
|
||||
const getWebSocketUrl = async (userId) => {
|
||||
if (!userId) {
|
||||
logger.warn('ChatConnection: No user ID provided for WebSocket URL');
|
||||
return null;
|
||||
}
|
||||
|
||||
// Récupérer le token d'authentification depuis NextAuth session
|
||||
const token = session?.user?.token;
|
||||
// Forcer un refresh de session pour obtenir un token JWT valide
|
||||
const freshSession = await getSession();
|
||||
const token = freshSession?.user?.token;
|
||||
|
||||
if (!token) {
|
||||
logger.warn(
|
||||
@ -78,7 +79,7 @@ export const ChatConnectionProvider = ({ children }) => {
|
||||
};
|
||||
|
||||
// Connexion WebSocket
|
||||
const connectToChat = (userId = null) => {
|
||||
const connectToChat = async (userId = null) => {
|
||||
const userIdToUse = userId || currentUserId;
|
||||
|
||||
// Vérifier que la session est chargée
|
||||
@ -107,7 +108,7 @@ export const ChatConnectionProvider = ({ children }) => {
|
||||
setConnectionStatus('connecting');
|
||||
|
||||
try {
|
||||
const wsUrl = getWebSocketUrl(userIdToUse);
|
||||
const wsUrl = await getWebSocketUrl(userIdToUse);
|
||||
|
||||
if (!wsUrl) {
|
||||
throw new Error(
|
||||
|
||||
@ -27,5 +27,13 @@ export const getSecureFileUrl = (filePath) => {
|
||||
}
|
||||
}
|
||||
|
||||
// Décoder le chemin au cas où il est déjà URL-encodé (ex: %20 pour les espaces)
|
||||
// puis ré-encoder proprement pour éviter le double encodage (%2520).
|
||||
try {
|
||||
filePath = decodeURIComponent(filePath);
|
||||
} catch {
|
||||
// Si le décodage échoue, le chemin n'était pas encodé : on le garde tel quel.
|
||||
}
|
||||
|
||||
return `/api/download?path=${encodeURIComponent(filePath)}`;
|
||||
};
|
||||
|
||||
21
conf/create-establishment.sample.json
Normal file
21
conf/create-establishment.sample.json
Normal file
@ -0,0 +1,21 @@
|
||||
{
|
||||
"establishments": [
|
||||
{
|
||||
"name": "Ecole Demo",
|
||||
"address": "1 rue de la Paix, Paris",
|
||||
"total_capacity": 300,
|
||||
"establishment_type": [
|
||||
1,
|
||||
2
|
||||
],
|
||||
"evaluation_frequency": 1,
|
||||
"licence_code": "LIC-DEMO-001",
|
||||
"directeur": {
|
||||
"email": "luc.sorignet@gmail.com",
|
||||
"password": "a",
|
||||
"last_name": "Dupont",
|
||||
"first_name": "Jean"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user