diff --git a/Back-End/School/serializers.py b/Back-End/School/serializers.py index 4a0bfd9..9d44e8a 100644 --- a/Back-End/School/serializers.py +++ b/Back-End/School/serializers.py @@ -1,6 +1,7 @@ from rest_framework import serializers from .models import Teacher, Speciality, SchoolClass, Planning, LEVEL_CHOICES, Discount, Fee, PaymentPlan, PaymentMode, Establishment from Auth.models import Profile +from Subscriptions.models import Student from N3wtSchool import settings, bdd from django.utils import timezone import pytz @@ -71,9 +72,9 @@ class TeacherSerializer(serializers.ModelSerializer): return obj.associated_profile.droit return None - def get_specialities_details(self, obj): + def get_specialities_details(self, obj): return [{'id': speciality.id, 'name': speciality.name, 'color_code': speciality.color_code} for speciality in obj.specialities.all()] - + class PlanningSerializer(serializers.ModelSerializer): class Meta: model = Planning @@ -89,6 +90,7 @@ class SchoolClassSerializer(serializers.ModelSerializer): teachers = serializers.PrimaryKeyRelatedField(queryset=Teacher.objects.all(), many=True, required=False) establishment = serializers.PrimaryKeyRelatedField(queryset=Establishment.objects.all(), required=False) teachers_details = serializers.SerializerMethodField() + students = serializers.PrimaryKeyRelatedField(queryset=Student.objects.all(), many=True, required=False) class Meta: model = SchoolClass @@ -98,7 +100,7 @@ class SchoolClassSerializer(serializers.ModelSerializer): teachers_data = validated_data.pop('teachers', []) levels_data = validated_data.pop('levels', []) plannings_data = validated_data.pop('plannings', []) - + school_class = SchoolClass.objects.create( atmosphere_name=validated_data.get('atmosphere_name', ''), age_range=validated_data.get('age_range', []), @@ -161,7 +163,7 @@ class SchoolClassSerializer(serializers.ModelSerializer): return instance - def get_teachers_details(self, obj): + def get_teachers_details(self, obj): return [{'id': teacher.id, 'last_name': teacher.last_name, 'first_name': teacher.first_name} for teacher in obj.teachers.all()] def get_updated_date_formatted(self, obj): diff --git a/Front-End/messages/en/dashboard.json b/Front-End/messages/en/dashboard.json index 5c10d64..60bee0d 100644 --- a/Front-End/messages/en/dashboard.json +++ b/Front-End/messages/en/dashboard.json @@ -1,9 +1,10 @@ { "dashboard": "Dashboard", "totalStudents": "Total Students", - "averageInscriptionTime": "Average Registration Time", + "pendingRegistrations": "Pending Registration", "reInscriptionRate": "Re-enrollment Rate", "structureCapacity": "Structure Capacity", + "capacityRate": "Capacity Rate", "inscriptionTrends": "Enrollment Trends", "upcomingEvents": "Upcoming Events" } \ No newline at end of file diff --git a/Front-End/messages/fr/dashboard.json b/Front-End/messages/fr/dashboard.json index 4ac34ef..2bee5a0 100644 --- a/Front-End/messages/fr/dashboard.json +++ b/Front-End/messages/fr/dashboard.json @@ -1,9 +1,10 @@ { "dashboard": "Tableau de bord", "totalStudents": "Total des étudiants", - "averageInscriptionTime": "Temps moyen d'inscription", + "pendingRegistrations": "Inscriptions en attente", "reInscriptionRate": "Taux de réinscription", - "structureCapacity": "Remplissage de la structure", + "structureCapacity": "Capacité de la structure", + "capacityRate": "Remplissage de la structure", "inscriptionTrends": "Tendances d'inscription", "upcomingEvents": "Événements à venir" } \ No newline at end of file diff --git a/Front-End/src/app/[locale]/admin/page.js b/Front-End/src/app/[locale]/admin/page.js index 3924f07..7f63247 100644 --- a/Front-End/src/app/[locale]/admin/page.js +++ b/Front-End/src/app/[locale]/admin/page.js @@ -6,26 +6,10 @@ import { Users, Clock, CalendarCheck, School, TrendingUp, UserCheck } from 'luci import Loader from '@/components/Loader'; import ClasseDetails from '@/components/ClasseDetails'; import { fetchClasses } from '@/app/actions/schoolAction'; +import StatCard from '@/components/StatCard'; +import logger from '@/utils/logger'; +import { fetchRegisterForms } from '@/app/actions/subscriptionAction'; -// Composant StatCard pour afficher une statistique -const StatCard = ({ title, value, icon, change, color = "blue" }) => ( -
-
-
-

{title}

-

{value}

- {change && ( -

0 ? 'text-green-500' : 'text-red-500'}`}> - {change > 0 ? '+' : ''}{change}% depuis le mois dernier -

- )} -
-
- {icon} -
-
-
-); // Composant EventCard pour afficher les événements const EventCard = ({ title, date, description, type }) => ( @@ -44,18 +28,16 @@ const EventCard = ({ title, date, description, type }) => ( export default function DashboardPage() { const t = useTranslations('dashboard'); const [isLoading, setIsLoading] = useState(true); - const [stats, setStats] = useState({ - totalStudents: 0, - averageInscriptionTime: 0, - reInscriptionRate: 0, - structureCapacity: 0, - upcomingEvents: [], - monthlyStats: { - inscriptions: [], - completionRate: 0 - } + const [totalStudents, setTotalStudents] = useState(0); + const [pendingRegistration, setPendingRegistration] = useState(0); + const [structureCapacity, setStructureCapacity] = useState(0); + const [upcomingEvents, setUpcomingEvents] = useState([]); + const [monthlyStats, setMonthlyStats] = useState({ + inscriptions: [], + completionRate: 0 }); + const [classes, setClasses] = useState([]); @@ -63,40 +45,50 @@ export default function DashboardPage() { // Fetch data for classes fetchClasses().then(data => { setClasses(data); + logger.info('Classes fetched:', data); + const nbMaxStudents = data.reduce((acc, classe) => acc + classe.number_of_students, 0); + const nbStudents = data.reduce((acc, classe) => acc + classe.students.length, 0); + setStructureCapacity(nbMaxStudents); + setTotalStudents(nbStudents); + }) .catch(error => { - console.error('Error fetching classes:', error); + logger.error('Error fetching classes:', error); + }); + + fetchRegisterForms().then(data => { + logger.info('Pending registrations fetched:', data); + setPendingRegistration(data.count); + }) + .catch(error => { + logger.error('Error fetching pending registrations:', error); }); // Simulation de chargement des données setTimeout(() => { - setStats({ - totalStudents: 245, - averageInscriptionTime: 3.5, - reInscriptionRate: 85, - structureCapacity: 300, - upcomingEvents: [ - { - title: "Réunion de rentrée", - date: "2024-09-01", - description: "Présentation de l'année scolaire", - type: "meeting" - }, - { - title: "Date limite inscriptions", - date: "2024-08-15", - description: "Clôture des inscriptions", - type: "deadline" - } - ], - monthlyStats: { - inscriptions: [150, 180, 210, 245], - completionRate: 78 + setUpcomingEvents([ + { + title: "Réunion de rentrée", + date: "2024-09-01", + description: "Présentation de l'année scolaire", + type: "meeting" + }, + { + title: "Date limite inscriptions", + date: "2024-08-15", + description: "Clôture des inscriptions", + type: "deadline" } + ]); + setMonthlyStats({ + inscriptions: [150, 180, 210, 245], + completionRate: 78 }); setIsLoading(false); }, 1000); - }, []); + } + , []); + if (isLoading) return ; @@ -108,25 +100,24 @@ export default function DashboardPage() {
} - change={12} /> } color="green" /> - } - color="purple" - /> } + color="emerald" + /> + } color="orange" /> @@ -146,7 +137,7 @@ export default function DashboardPage() { {/* Événements à venir */}

{t('upcomingEvents')}

- {stats.upcomingEvents.map((event, index) => ( + {upcomingEvents.map((event, index) => ( ))}
diff --git a/Front-End/src/components/ClasseDetails.js b/Front-End/src/components/ClasseDetails.js index 37d0689..80a15dc 100644 --- a/Front-End/src/components/ClasseDetails.js +++ b/Front-End/src/components/ClasseDetails.js @@ -58,11 +58,11 @@ const ClasseDetails = ({ classe }) => {
row.nom }, - { name: 'PRENOM', transform: (row) => row.prenom }, + { name: 'NOM', transform: (row) => row.name }, + { name: 'PRENOM', transform: (row) => row.first_name }, { name: 'AGE', transform: (row) => `${row.age}` } ]} - data={classe.eleves} + data={classe.students} /> diff --git a/Front-End/src/components/Popup.js b/Front-End/src/components/Popup.js index cbb4e72..ac7e04f 100644 --- a/Front-End/src/components/Popup.js +++ b/Front-End/src/components/Popup.js @@ -4,21 +4,41 @@ import ReactDOM from 'react-dom'; const Popup = ({ visible, message, onConfirm, onCancel, uniqueConfirmButton = false }) => { if (!visible) return null; - // Diviser le message en lignes - const messageLines = message.split('\n'); + // Vérifier si le message est une chaîne de caractères + const isStringMessage = typeof message === 'string'; + // Diviser le message en lignes seulement si c'est une chaîne + const messageLines = isStringMessage ? message.split('\n') : null; return ReactDOM.createPortal(
-
- {messageLines.map((line, index) => ( -

{line}

- ))} -
- {!uniqueConfirmButton && ( - +
+
+ {isStringMessage ? ( + // Afficher le message sous forme de lignes si c'est une chaîne + messageLines.map((line, index) => ( +

+ {line} +

+ )) + ) : ( + // Sinon, afficher directement le contenu React + message )} -
+
+ {!uniqueConfirmButton && ( + + )} +
diff --git a/Front-End/src/components/StatCard.js b/Front-End/src/components/StatCard.js new file mode 100644 index 0000000..823de62 --- /dev/null +++ b/Front-End/src/components/StatCard.js @@ -0,0 +1,16 @@ +// Composant StatCard pour afficher une statistique +const StatCard = ({ title, value, icon, color = "blue" }) => ( +
+
+
+

{title}

+

{value}

+
+
+ {icon} +
+
+
+); + +export default StatCard; \ No newline at end of file diff --git a/Front-End/src/components/Structure/Configuration/ClassesSection.js b/Front-End/src/components/Structure/Configuration/ClassesSection.js index ae80594..44d9859 100644 --- a/Front-End/src/components/Structure/Configuration/ClassesSection.js +++ b/Front-End/src/components/Structure/Configuration/ClassesSection.js @@ -11,6 +11,7 @@ import { DndProvider, useDrop } from 'react-dnd'; import { HTML5Backend } from 'react-dnd-html5-backend'; import { ESTABLISHMENT_ID } from '@/utils/Url'; import logger from '@/utils/logger'; +import ClasseDetails from '@/components/ClasseDetails'; const ItemTypes = { TEACHER: 'teacher', @@ -100,6 +101,8 @@ const ClassesSection = ({ classes, setClasses, teachers, handleCreate, handleEdi const [removePopupVisible, setRemovePopupVisible] = useState(false); const [removePopupMessage, setRemovePopupMessage] = useState(""); const [removePopupOnConfirm, setRemovePopupOnConfirm] = useState(() => {}); + const [detailsModalVisible, setDetailsModalVisible] = useState(false); + const [selectedClass, setSelectedClass] = useState(null); const niveauxPremierCycle = [ { id: 1, name: 'TPS', age: 2 }, @@ -252,6 +255,11 @@ const ClassesSection = ({ classes, setClasses, teachers, handleCreate, handleEdi } }; + const openEditModalDetails = (classe) => { + setSelectedClass(classe); + setDetailsModalVisible(true); + }; + const renderClassCell = (classe, column) => { const isEditing = editingClass === classe.id; const isCreating = newClass && newClass.id === classe.id; @@ -449,6 +457,13 @@ const ClassesSection = ({ classes, setClasses, teachers, handleCreate, handleEdi columns={columns} renderCell={renderClassCell} /> + : null} + onConfirm={() => setDetailsModalVisible(false)} + onCancel={() => setDetailsModalVisible(false)} + uniqueConfirmButton={true} + />