mirror of
https://git.v0id.ovh/n3wt-innov/n3wt-school.git
synced 2026-01-29 07:53:23 +00:00
feat: Génération d'une page de suivi pédagogique + fix utilisation
certains des composants
This commit is contained in:
@ -4,7 +4,7 @@
|
|||||||
"structure": "Structure",
|
"structure": "Structure",
|
||||||
"directory": "Directory",
|
"directory": "Directory",
|
||||||
"events": "Events",
|
"events": "Events",
|
||||||
"grades": "Grades",
|
"educational_monitoring": "Educational Monitoring",
|
||||||
"settings": "Settings",
|
"settings": "Settings",
|
||||||
"schoolAdmin": "School Administration"
|
"schoolAdmin": "School Administration"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
"structure": "Structure",
|
"structure": "Structure",
|
||||||
"directory": "Annuaire",
|
"directory": "Annuaire",
|
||||||
"events": "Evenements",
|
"events": "Evenements",
|
||||||
"grades": "Notes",
|
"educational_monitoring": "Suivi pédagogique",
|
||||||
"settings": "Paramètres",
|
"settings": "Paramètres",
|
||||||
"schoolAdmin": "Administration Scolaire"
|
"schoolAdmin": "Administration Scolaire"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,10 +1,144 @@
|
|||||||
'use client';
|
'use client';
|
||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState } from 'react';
|
||||||
|
import SelectChoice from '@/components/SelectChoice';
|
||||||
|
import Button from '@/components/Button';
|
||||||
|
import Table from '@/components/Table';
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
|
const [formData, setFormData] = useState({
|
||||||
|
selectedStudent: null,
|
||||||
|
absences: [],
|
||||||
|
competenceReview: [
|
||||||
|
{ competence: 'Lecture', score: null },
|
||||||
|
{ competence: 'Écriture', score: null },
|
||||||
|
{ competence: 'Mathématiques', score: null },
|
||||||
|
{ competence: 'Sciences', score: null },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const students = [
|
||||||
|
{ id: 1, name: 'John Doe', class: 'CM2' },
|
||||||
|
{ id: 2, name: 'Jane Smith', class: 'CE1' },
|
||||||
|
{ id: 3, name: 'Alice Johnson', class: 'CM1' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const absences = [
|
||||||
|
{ date: '2023-09-01', reason: 'Maladie' },
|
||||||
|
{ date: '2023-09-15', reason: 'Vacances' },
|
||||||
|
{ date: '2023-10-05', reason: 'Retard justifié' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const handleChange = (field, value) => {
|
||||||
|
setFormData((prev) => ({ ...prev, [field]: value }));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleScoreChange = (index, score) => {
|
||||||
|
const updatedCompetenceReview = [...formData.competenceReview];
|
||||||
|
updatedCompetenceReview[index].score = score;
|
||||||
|
setFormData((prev) => ({
|
||||||
|
...prev,
|
||||||
|
competenceReview: updatedCompetenceReview,
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="p-8">
|
<div className="p-8 space-y-8">
|
||||||
<h1 className="heading-section">Statistiques</h1>
|
<h1 className="heading-section">Suivi pédagogique</h1>
|
||||||
|
|
||||||
|
{/* Sélection de l'élève */}
|
||||||
|
<div className="bg-white p-6 rounded-lg shadow-sm border border-gray-200">
|
||||||
|
<h2 className="text-xl font-semibold mb-4">Sélectionner un élève</h2>
|
||||||
|
<SelectChoice
|
||||||
|
name="selectedStudent"
|
||||||
|
label="Élève"
|
||||||
|
placeHolder="Sélectionnez un élève"
|
||||||
|
selected={formData.selectedStudent}
|
||||||
|
callback={(e) => handleChange('selectedStudent', e.target.value)}
|
||||||
|
choices={students.map((student) => ({
|
||||||
|
value: student.id,
|
||||||
|
label: `${student.name} - Classe : ${student.class}`,
|
||||||
|
}))}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Liste des absences */}
|
||||||
|
{formData.selectedStudent && (
|
||||||
|
<div className="bg-white p-6 rounded-lg shadow-sm border border-gray-200">
|
||||||
|
<h2 className="text-xl font-semibold mb-4">Liste des absences</h2>
|
||||||
|
<ul className="space-y-2">
|
||||||
|
{absences.map((absence, index) => (
|
||||||
|
<li
|
||||||
|
key={index}
|
||||||
|
className="flex justify-between items-center bg-gray-50 p-4 rounded-md border border-gray-100"
|
||||||
|
>
|
||||||
|
<span className="text-gray-800">{absence.date}</span>
|
||||||
|
<span className="text-gray-500 italic">{absence.reason}</span>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Bilan de compétence */}
|
||||||
|
{formData.selectedStudent && (
|
||||||
|
<div className="bg-white p-6 rounded-lg shadow-sm border border-gray-200">
|
||||||
|
<h2 className="text-xl font-semibold mb-4">Bilan de compétence</h2>
|
||||||
|
<Table
|
||||||
|
data={formData.competenceReview}
|
||||||
|
columns={[
|
||||||
|
{
|
||||||
|
name: 'Compétence',
|
||||||
|
transform: (row) => row.competence,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '1',
|
||||||
|
transform: (row, index) => (
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
name={`score-${index}`}
|
||||||
|
value="1"
|
||||||
|
checked={row.score === '1'}
|
||||||
|
onChange={() => handleScoreChange(index, '1')}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '2',
|
||||||
|
transform: (row, index) => (
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
name={`score-${index}`}
|
||||||
|
value="2"
|
||||||
|
checked={row.score === '2'}
|
||||||
|
onChange={() => handleScoreChange(index, '2')}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '3',
|
||||||
|
transform: (row, index) => (
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
name={`score-${index}`}
|
||||||
|
value="3"
|
||||||
|
checked={row.score === '3'}
|
||||||
|
onChange={() => handleScoreChange(index, '3')}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
<div className="mt-4">
|
||||||
|
<Button
|
||||||
|
text="Enregistrer"
|
||||||
|
onClick={() => console.log('FormData:', formData)}
|
||||||
|
primary
|
||||||
|
className="bg-emerald-500 text-white hover:bg-emerald-600"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -70,7 +70,7 @@ export default function Layout({ children }) {
|
|||||||
},
|
},
|
||||||
grades: {
|
grades: {
|
||||||
id: 'grades',
|
id: 'grades',
|
||||||
name: t('grades'),
|
name: t('educational_monitoring'),
|
||||||
url: FE_ADMIN_GRADES_URL,
|
url: FE_ADMIN_GRADES_URL,
|
||||||
icon: Award,
|
icon: Award,
|
||||||
},
|
},
|
||||||
|
|||||||
@ -6,11 +6,9 @@ const CheckBox = ({
|
|||||||
handleChange,
|
handleChange,
|
||||||
fieldName,
|
fieldName,
|
||||||
itemLabelFunc = () => null,
|
itemLabelFunc = () => null,
|
||||||
labelAttenuated = () => false,
|
|
||||||
horizontal,
|
horizontal,
|
||||||
}) => {
|
}) => {
|
||||||
const isChecked = formData[fieldName].includes(parseInt(item.id));
|
const isChecked = formData[fieldName].includes(parseInt(item.id));
|
||||||
const isAttenuated = labelAttenuated(item) && !isChecked;
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={item.id}
|
key={item.id}
|
||||||
@ -19,7 +17,7 @@ const CheckBox = ({
|
|||||||
{horizontal && (
|
{horizontal && (
|
||||||
<label
|
<label
|
||||||
htmlFor={`${fieldName}-${item.id}`}
|
htmlFor={`${fieldName}-${item.id}`}
|
||||||
className={`block text-sm text-center mb-1 ${isAttenuated ? 'text-gray-300' : 'font-bold text-emerald-600'}`}
|
className="block text-sm text-center mb-1 font-medium text-gray-700"
|
||||||
>
|
>
|
||||||
{itemLabelFunc(item)}
|
{itemLabelFunc(item)}
|
||||||
</label>
|
</label>
|
||||||
@ -37,7 +35,7 @@ const CheckBox = ({
|
|||||||
{!horizontal && (
|
{!horizontal && (
|
||||||
<label
|
<label
|
||||||
htmlFor={`${fieldName}-${item.id}`}
|
htmlFor={`${fieldName}-${item.id}`}
|
||||||
className={`block text-sm ${isAttenuated ? 'text-gray-300' : 'font-bold text-emerald-600'}`}
|
className="block text-sm text-center mb-1 font-medium text-gray-700"
|
||||||
>
|
>
|
||||||
{itemLabelFunc(item)}
|
{itemLabelFunc(item)}
|
||||||
</label>
|
</label>
|
||||||
|
|||||||
@ -1,42 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import CheckBox from '@/components/CheckBox';
|
|
||||||
|
|
||||||
const CheckBoxList = ({
|
|
||||||
items,
|
|
||||||
formData,
|
|
||||||
handleChange,
|
|
||||||
fieldName,
|
|
||||||
label,
|
|
||||||
icon: Icon,
|
|
||||||
className,
|
|
||||||
itemLabelFunc = (item) => item.name,
|
|
||||||
labelAttenuated = () => false,
|
|
||||||
horizontal = false, // Ajouter l'option horizontal
|
|
||||||
}) => {
|
|
||||||
return (
|
|
||||||
<div className={`mb-4 w-full ${className}`}>
|
|
||||||
<label className="block text-sm font-medium text-gray-700 flex items-center">
|
|
||||||
{Icon && <Icon className="w-5 h-5 mr-2" />}
|
|
||||||
{label}
|
|
||||||
</label>
|
|
||||||
<div
|
|
||||||
className={`mt-2 grid ${horizontal ? 'grid-cols-6 gap-2' : 'grid-cols-1 gap-4'}`}
|
|
||||||
>
|
|
||||||
{items.map((item) => (
|
|
||||||
<CheckBox
|
|
||||||
key={`${fieldName}-${item.id}`}
|
|
||||||
item={item}
|
|
||||||
formData={formData}
|
|
||||||
handleChange={handleChange}
|
|
||||||
fieldName={fieldName}
|
|
||||||
itemLabelFunc={itemLabelFunc}
|
|
||||||
labelAttenuated={labelAttenuated}
|
|
||||||
horizontal={horizontal}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default CheckBoxList;
|
|
||||||
@ -12,6 +12,8 @@ import logger from '@/utils/logger';
|
|||||||
import Popup from '@/components/Popup';
|
import Popup from '@/components/Popup';
|
||||||
import InputPhone from '../InputPhone';
|
import InputPhone from '../InputPhone';
|
||||||
import { PhoneLabel } from '../PhoneLabel';
|
import { PhoneLabel } from '../PhoneLabel';
|
||||||
|
import CheckBox from '@/components/CheckBox';
|
||||||
|
import RadioList from '@/components/RadioList';
|
||||||
|
|
||||||
const InscriptionForm = ({
|
const InscriptionForm = ({
|
||||||
students,
|
students,
|
||||||
@ -547,27 +549,35 @@ const InscriptionForm = ({
|
|||||||
{selectedStudent.first_name} :
|
{selectedStudent.first_name} :
|
||||||
</h3>
|
</h3>
|
||||||
{existingGuardians.map((guardian) => (
|
{existingGuardians.map((guardian) => (
|
||||||
<div key={guardian.id}>
|
<div key={guardian.id} className="mt-2">
|
||||||
<label className="flex items-center space-x-3 mt-2">
|
<CheckBox
|
||||||
<input
|
item={{ id: guardian.id }}
|
||||||
type="checkbox"
|
formData={{
|
||||||
checked={formData.selectedGuardians.includes(
|
selectedGuardians: formData.selectedGuardians,
|
||||||
guardian.id
|
}}
|
||||||
)}
|
handleChange={() =>
|
||||||
className="form-checkbox h-5 w-5 text-emerald-600"
|
setFormData((prevData) => {
|
||||||
onChange={() =>
|
const isSelected =
|
||||||
handleResponsableSelection(
|
prevData.selectedGuardians.includes(guardian.id);
|
||||||
guardian.id,
|
const updatedSelectedGuardians = isSelected
|
||||||
guardian.associated_profile_email
|
? prevData.selectedGuardians.filter(
|
||||||
)
|
(id) => id !== guardian.id
|
||||||
|
) // Retirer le guardian si décoché
|
||||||
|
: [...prevData.selectedGuardians, guardian.id]; // Ajouter le guardian si coché
|
||||||
|
|
||||||
|
return {
|
||||||
|
...prevData,
|
||||||
|
selectedGuardians: updatedSelectedGuardians,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
}
|
||||||
|
fieldName="selectedGuardians"
|
||||||
|
itemLabelFunc={() =>
|
||||||
|
guardian.last_name && guardian.first_name
|
||||||
|
? `${guardian.last_name} ${guardian.first_name} - ${guardian.associated_profile_email}`
|
||||||
|
: `${guardian.associated_profile_email}`
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<span className="text-gray-900">
|
|
||||||
{guardian.last_name && guardian.first_name
|
|
||||||
? `${guardian.last_name} ${guardian.first_name} - ${guardian.associated_profile_email}`
|
|
||||||
: `${guardian.associated_profile_email}`}
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@ -719,31 +729,28 @@ const InscriptionForm = ({
|
|||||||
{groups.length > 0 ? (
|
{groups.length > 0 ? (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<h3 className="font-bold">Sélectionnez un groupe de documents</h3>
|
<h3 className="font-bold">Sélectionnez un groupe de documents</h3>
|
||||||
{groups.map((group) => (
|
<RadioList
|
||||||
<div key={group.id} className="flex items-center space-x-3">
|
sectionLabel=""
|
||||||
<input
|
items={groups.map((group) => ({
|
||||||
type="radio"
|
id: group.id,
|
||||||
name="fileGroup"
|
label: `${group.name}${
|
||||||
value={group.id}
|
group.description ? ` (${group.description})` : ''
|
||||||
checked={formData.selectedFileGroup === group.id}
|
}`,
|
||||||
onChange={(e) =>
|
}))}
|
||||||
setFormData({
|
formData={{
|
||||||
...formData,
|
...formData,
|
||||||
selectedFileGroup: parseInt(e.target.value),
|
selectedFileGroup: parseInt(formData.selectedFileGroup, 10),
|
||||||
})
|
}}
|
||||||
}
|
handleChange={(e) => {
|
||||||
className="form-radio h-4 w-4 text-emerald-600"
|
const value = parseInt(e.target.value, 10);
|
||||||
|
setFormData((prevData) => ({
|
||||||
|
...prevData,
|
||||||
|
selectedFileGroup: value,
|
||||||
|
}));
|
||||||
|
}}
|
||||||
|
fieldName="selectedFileGroup"
|
||||||
|
className="mt-4"
|
||||||
/>
|
/>
|
||||||
<label className="text-gray-900">
|
|
||||||
{group.name}
|
|
||||||
{group.description && (
|
|
||||||
<span className="text-sm text-gray-500 ml-2">
|
|
||||||
({group.description})
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<p
|
<p
|
||||||
@ -752,7 +759,6 @@ const InscriptionForm = ({
|
|||||||
>
|
>
|
||||||
<strong className="font-bold">Attention!</strong>
|
<strong className="font-bold">Attention!</strong>
|
||||||
<span className="block sm:inline">
|
<span className="block sm:inline">
|
||||||
{' '}
|
|
||||||
Aucun groupe de documents n'a été créé.
|
Aucun groupe de documents n'a été créé.
|
||||||
</span>
|
</span>
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
Reference in New Issue
Block a user