mirror of
https://git.v0id.ovh/n3wt-innov/n3wt-school.git
synced 2026-04-05 12:41:27 +00:00
fix: On n'historise plus les matières ni les enseignants
This commit is contained in:
@ -21,7 +21,6 @@ class Speciality(models.Model):
|
||||
name = models.CharField(max_length=100)
|
||||
updated_date = models.DateTimeField(auto_now=True)
|
||||
color_code = models.CharField(max_length=7, default='#FFFFFF')
|
||||
school_year = models.CharField(max_length=9, blank=True)
|
||||
establishment = models.ForeignKey('Establishment.Establishment', on_delete=models.CASCADE, related_name='specialities')
|
||||
|
||||
def __str__(self):
|
||||
@ -32,7 +31,6 @@ class Teacher(models.Model):
|
||||
first_name = models.CharField(max_length=100)
|
||||
specialities = models.ManyToManyField(Speciality, blank=True)
|
||||
profile_role = models.OneToOneField('Auth.ProfileRole', on_delete=models.CASCADE, related_name='teacher_profile', null=True, blank=True)
|
||||
school_year = models.CharField(max_length=9, blank=True)
|
||||
updated_date = models.DateTimeField(auto_now=True)
|
||||
|
||||
def __str__(self):
|
||||
|
||||
@ -73,15 +73,12 @@ class SpecialityListCreateView(APIView):
|
||||
|
||||
def get(self, request):
|
||||
establishment_id = request.GET.get('establishment_id', None)
|
||||
school_year = request.GET.get('school_year', None)
|
||||
if establishment_id is None:
|
||||
return JsonResponse({'error': 'establishment_id est requis'}, safe=False, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
specialities_list = getAllObjects(Speciality)
|
||||
if establishment_id:
|
||||
specialities_list = specialities_list.filter(establishment__id=establishment_id).distinct()
|
||||
if school_year:
|
||||
specialities_list = specialities_list.filter(school_year=school_year)
|
||||
specialities_serializer = SpecialitySerializer(specialities_list, many=True)
|
||||
return JsonResponse(specialities_serializer.data, safe=False)
|
||||
|
||||
|
||||
@ -1,14 +1,12 @@
|
||||
import { Trash2, Edit3, Check, X, BookOpen, Download } from 'lucide-react';
|
||||
import { Trash2, Edit3, Check, X, BookOpen, Download } from 'lucide-react';
|
||||
import { useState, useEffect } from 'react';
|
||||
import Table from '@/components/Table';
|
||||
import Popup from '@/components/Popup';
|
||||
import InputTextWithColorIcon from '@/components/Form/InputTextWithColorIcon';
|
||||
import SelectChoice from '@/components/Form/SelectChoice';
|
||||
import { DndProvider } from 'react-dnd';
|
||||
import { HTML5Backend } from 'react-dnd-html5-backend';
|
||||
import SpecialityItem from '@/components/Structure/Configuration/SpecialityItem';
|
||||
import { useEstablishment } from '@/context/EstablishmentContext';
|
||||
import { useSchoolYearFilter } from '@/context/SchoolYearFilterContext';
|
||||
import logger from '@/utils/logger';
|
||||
import SectionHeader from '@/components/SectionHeader';
|
||||
import AlertMessage from '@/components/AlertMessage';
|
||||
@ -34,47 +32,39 @@ const SpecialitiesSection = ({
|
||||
const [removePopupOnConfirm, setRemovePopupOnConfirm] = useState(() => {});
|
||||
const ITEMS_PER_PAGE = 10;
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
useEffect(() => { setCurrentPage(1); }, [specialities]);
|
||||
useEffect(() => { if (newSpeciality) setCurrentPage(1); }, [newSpeciality]);
|
||||
|
||||
useEffect(() => {
|
||||
setCurrentPage(1);
|
||||
}, [specialities]);
|
||||
|
||||
useEffect(() => {
|
||||
if (newSpeciality) setCurrentPage(1);
|
||||
}, [newSpeciality]);
|
||||
|
||||
const totalPages = Math.ceil(specialities.length / ITEMS_PER_PAGE);
|
||||
const pagedSpecialities = specialities.slice((currentPage - 1) * ITEMS_PER_PAGE, currentPage * ITEMS_PER_PAGE);
|
||||
const pagedSpecialities = specialities.slice(
|
||||
(currentPage - 1) * ITEMS_PER_PAGE,
|
||||
currentPage * ITEMS_PER_PAGE
|
||||
);
|
||||
|
||||
const { selectedEstablishmentId, profileRole } = useEstablishment();
|
||||
const { selectedSchoolYear } = useSchoolYearFilter();
|
||||
|
||||
// Fonction pour générer les années scolaires
|
||||
const getSchoolYearChoices = () => {
|
||||
const currentDate = new Date();
|
||||
const currentYear = currentDate.getFullYear();
|
||||
const currentMonth = currentDate.getMonth() + 1;
|
||||
const startYear = currentMonth >= 9 ? currentYear : currentYear - 1;
|
||||
const choices = [];
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const year = startYear + i;
|
||||
choices.push({ value: `${year}-${year + 1}`, label: `${year}-${year + 1}` });
|
||||
}
|
||||
return choices;
|
||||
};
|
||||
|
||||
// Récupération des messages d'erreur
|
||||
const getError = (field) => {
|
||||
return localErrors?.[field]?.[0];
|
||||
};
|
||||
|
||||
const handleAddSpeciality = () => {
|
||||
setNewSpeciality({ id: Date.now(), name: '', color_code: '', school_year: selectedSchoolYear });
|
||||
setNewSpeciality({ id: Date.now(), name: '', color_code: '' });
|
||||
};
|
||||
|
||||
// Export CSV
|
||||
const handleExportCSV = () => {
|
||||
const exportColumns = [
|
||||
{ key: 'id', label: 'ID' },
|
||||
{ key: 'name', label: 'Nom' },
|
||||
{ key: 'color_code', label: 'Code couleur' },
|
||||
{ key: 'school_year', label: 'Année scolaire' },
|
||||
{ key: 'updated_date_formatted', label: 'Date de mise à jour' },
|
||||
];
|
||||
const filename = `specialites_${selectedSchoolYear || 'toutes'}_${new Date().toISOString().split('T')[0]}`;
|
||||
const filename = `specialites_${new Date().toISOString().split('T')[0]}`;
|
||||
exportToCSV(specialities, exportColumns, filename);
|
||||
};
|
||||
|
||||
@ -92,10 +82,9 @@ const SpecialitiesSection = ({
|
||||
|
||||
const handleSaveNewSpeciality = () => {
|
||||
if (newSpeciality.name) {
|
||||
// Ajouter l'ID de l'établissement à la nouvelle spécialité
|
||||
const specialityData = {
|
||||
...newSpeciality,
|
||||
establishment: selectedEstablishmentId, // Inclure l'ID de l'établissement
|
||||
establishment: selectedEstablishmentId,
|
||||
};
|
||||
|
||||
handleCreate(specialityData)
|
||||
@ -120,10 +109,10 @@ const SpecialitiesSection = ({
|
||||
const handleUpdateSpeciality = (id, updatedSpeciality) => {
|
||||
if (updatedSpeciality.name) {
|
||||
handleEdit(id, updatedSpeciality)
|
||||
.then((updatedSpeciality) => {
|
||||
.then((updatedItem) => {
|
||||
setSpecialities(
|
||||
specialities.map((speciality) =>
|
||||
speciality.id === id ? updatedSpeciality : speciality
|
||||
speciality.id === id ? updatedItem : speciality
|
||||
)
|
||||
);
|
||||
setEditingSpeciality(null);
|
||||
@ -144,21 +133,17 @@ const SpecialitiesSection = ({
|
||||
|
||||
const handleChange = (e) => {
|
||||
const { name, value } = e.target;
|
||||
let parsedValue = value;
|
||||
if (name.includes('_color')) {
|
||||
parsedValue = value;
|
||||
}
|
||||
|
||||
const fieldName = name.includes('_color') ? 'color_code' : name;
|
||||
|
||||
if (editingSpeciality) {
|
||||
setFormData((prevData) => ({
|
||||
...prevData,
|
||||
[fieldName]: parsedValue,
|
||||
[fieldName]: value,
|
||||
}));
|
||||
} else if (newSpeciality) {
|
||||
setNewSpeciality((prevData) => ({
|
||||
...prevData,
|
||||
[fieldName]: parsedValue,
|
||||
[fieldName]: value,
|
||||
}));
|
||||
}
|
||||
};
|
||||
@ -182,20 +167,6 @@ const SpecialitiesSection = ({
|
||||
errorMsg={getError('name')}
|
||||
/>
|
||||
);
|
||||
case 'ANNÉE SCOLAIRE':
|
||||
return (
|
||||
<SelectChoice
|
||||
type="select"
|
||||
name="school_year"
|
||||
placeHolder="Sélectionnez une année scolaire"
|
||||
choices={getSchoolYearChoices()}
|
||||
callback={handleChange}
|
||||
selected={currentData.school_year || ''}
|
||||
errorMsg={getError('school_year')}
|
||||
IconItem={null}
|
||||
disabled={false}
|
||||
/>
|
||||
);
|
||||
case 'ACTIONS':
|
||||
return (
|
||||
<div className="flex justify-center space-x-2">
|
||||
@ -226,73 +197,70 @@ const SpecialitiesSection = ({
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
switch (column) {
|
||||
case 'LIBELLE':
|
||||
return <SpecialityItem key={speciality.id} speciality={speciality} />;
|
||||
case 'ANNÉE SCOLAIRE':
|
||||
return speciality.school_year;
|
||||
case 'MISE A JOUR':
|
||||
return speciality.updated_date_formatted;
|
||||
case 'ACTIONS':
|
||||
return (
|
||||
<div className="flex justify-center space-x-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() =>
|
||||
setEditingSpeciality(speciality.id) || setFormData(speciality)
|
||||
}
|
||||
className="text-blue-500 hover:text-blue-700"
|
||||
>
|
||||
<Edit3 className="w-5 h-5" />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setRemovePopupVisible(true);
|
||||
setRemovePopupMessage(
|
||||
'Attention ! \nVous êtes sur le point de supprimer la spécialité ' +
|
||||
speciality.name +
|
||||
".\nÊtes-vous sûr(e) de vouloir poursuivre l'opération ?"
|
||||
);
|
||||
setRemovePopupOnConfirm(() => () => {
|
||||
handleRemoveSpeciality(speciality.id)
|
||||
.then((data) => {
|
||||
logger.debug('Success:', data);
|
||||
setPopupMessage(
|
||||
'La spécialité ' +
|
||||
speciality.name +
|
||||
' a été correctement supprimée'
|
||||
);
|
||||
setPopupVisible(true);
|
||||
setRemovePopupVisible(false);
|
||||
})
|
||||
.catch((error) => {
|
||||
logger.error('Error archiving data:', error);
|
||||
setPopupMessage(
|
||||
'Erreur lors de la suppression de la spécialité ' +
|
||||
speciality.name
|
||||
);
|
||||
setPopupVisible(true);
|
||||
setRemovePopupVisible(false);
|
||||
});
|
||||
});
|
||||
}}
|
||||
className="text-red-500 hover:text-red-700"
|
||||
>
|
||||
<Trash2 className="w-5 h-5" />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
switch (column) {
|
||||
case 'LIBELLE':
|
||||
return <SpecialityItem key={speciality.id} speciality={speciality} />;
|
||||
case 'MISE A JOUR':
|
||||
return speciality.updated_date_formatted;
|
||||
case 'ACTIONS':
|
||||
return (
|
||||
<div className="flex justify-center space-x-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() =>
|
||||
setEditingSpeciality(speciality.id) || setFormData(speciality)
|
||||
}
|
||||
className="text-blue-500 hover:text-blue-700"
|
||||
>
|
||||
<Edit3 className="w-5 h-5" />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setRemovePopupVisible(true);
|
||||
setRemovePopupMessage(
|
||||
'Attention ! \nVous êtes sur le point de supprimer la spécialité ' +
|
||||
speciality.name +
|
||||
".\nÊtes-vous sûr(e) de vouloir poursuivre l\'opération ?"
|
||||
);
|
||||
setRemovePopupOnConfirm(() => () => {
|
||||
handleRemoveSpeciality(speciality.id)
|
||||
.then((data) => {
|
||||
logger.debug('Success:', data);
|
||||
setPopupMessage(
|
||||
'La spécialité ' +
|
||||
speciality.name +
|
||||
' a été correctement supprimée'
|
||||
);
|
||||
setPopupVisible(true);
|
||||
setRemovePopupVisible(false);
|
||||
})
|
||||
.catch((error) => {
|
||||
logger.error('Error archiving data:', error);
|
||||
setPopupMessage(
|
||||
'Erreur lors de la suppression de la spécialité ' +
|
||||
speciality.name
|
||||
);
|
||||
setPopupVisible(true);
|
||||
setRemovePopupVisible(false);
|
||||
});
|
||||
});
|
||||
}}
|
||||
className="text-red-500 hover:text-red-700"
|
||||
>
|
||||
<Trash2 className="w-5 h-5" />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const columns = [
|
||||
{ name: 'LIBELLE', label: 'Libellé' },
|
||||
{ name: 'ANNÉE SCOLAIRE', label: 'Année scolaire' },
|
||||
{ name: 'MISE A JOUR', label: 'Date mise à jour' },
|
||||
...(profileRole !== 0 ? [{ name: 'ACTIONS', label: 'Actions' }] : []),
|
||||
];
|
||||
|
||||
@ -25,21 +25,13 @@ const StructureManagementContent = ({
|
||||
}) => {
|
||||
const { activeYearFilter, setActiveYearFilter, filterByYear } = useSchoolYearFilter();
|
||||
|
||||
// Filtrer les données par année scolaire
|
||||
const filteredSpecialities = useMemo(() => filterByYear(specialities), [specialities, filterByYear]);
|
||||
const filteredTeachers = useMemo(() => filterByYear(teachers), [teachers, filterByYear]);
|
||||
// Les spécialités et enseignants ne sont plus historisés par année scolaire.
|
||||
const filteredSpecialities = specialities;
|
||||
const filteredTeachers = teachers;
|
||||
const filteredClasses = useMemo(() => filterByYear(classes), [classes, filterByYear]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="mb-6">
|
||||
<SchoolYearFilter
|
||||
activeFilter={activeYearFilter}
|
||||
onFilterChange={setActiveYearFilter}
|
||||
showNextYear={true}
|
||||
showHistorical={true}
|
||||
/>
|
||||
</div>
|
||||
{/* Spécialités + Enseignants : côte à côte sur desktop, empilés sur mobile */}
|
||||
<div className="mt-8 flex flex-col xl:flex-row gap-8">
|
||||
<div className="w-full xl:w-2/5">
|
||||
@ -92,6 +84,14 @@ const StructureManagementContent = ({
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-full mt-8 xl:mt-12">
|
||||
<div className="mb-6">
|
||||
<SchoolYearFilter
|
||||
activeFilter={activeYearFilter}
|
||||
onFilterChange={setActiveYearFilter}
|
||||
showNextYear={true}
|
||||
showHistorical={true}
|
||||
/>
|
||||
</div>
|
||||
<ClassesSection
|
||||
classes={filteredClasses}
|
||||
allClasses={classes}
|
||||
|
||||
@ -11,7 +11,6 @@ import SpecialityItem from '@/components/Structure/Configuration/SpecialityItem'
|
||||
import TeacherItem from './TeacherItem';
|
||||
import logger from '@/utils/logger';
|
||||
import { useEstablishment } from '@/context/EstablishmentContext';
|
||||
import { useSchoolYearFilter } from '@/context/SchoolYearFilterContext';
|
||||
import SectionHeader from '@/components/SectionHeader';
|
||||
import AlertMessage from '@/components/AlertMessage';
|
||||
import { exportToCSV } from '@/utils/exportCSV';
|
||||
@ -149,22 +148,6 @@ const TeachersSection = ({
|
||||
const pagedTeachers = teachers.slice((currentPage - 1) * ITEMS_PER_PAGE, currentPage * ITEMS_PER_PAGE);
|
||||
|
||||
const { selectedEstablishmentId, profileRole } = useEstablishment();
|
||||
const { selectedSchoolYear } = useSchoolYearFilter();
|
||||
|
||||
// Génère les choix d'année scolaire (année en cours + 2 suivantes)
|
||||
const getSchoolYearChoices = () => {
|
||||
const currentDate = new Date();
|
||||
const currentYear = currentDate.getFullYear();
|
||||
const currentMonth = currentDate.getMonth() + 1;
|
||||
// Si on est avant septembre, l'année scolaire a commencé l'année précédente
|
||||
const startYear = currentMonth >= 9 ? currentYear : currentYear - 1;
|
||||
const choices = [];
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const year = startYear + i;
|
||||
choices.push({ value: `${year}-${year + 1}`, label: `${year}-${year + 1}` });
|
||||
}
|
||||
return choices;
|
||||
};
|
||||
|
||||
// --- UTILS ---
|
||||
|
||||
@ -217,7 +200,6 @@ const TeachersSection = ({
|
||||
associated_profile_email: '',
|
||||
specialities: [],
|
||||
role_type: 0,
|
||||
school_year: selectedSchoolYear,
|
||||
});
|
||||
setFormData({
|
||||
last_name: '',
|
||||
@ -225,7 +207,6 @@ const TeachersSection = ({
|
||||
associated_profile_email: '',
|
||||
specialities: [],
|
||||
role_type: 0,
|
||||
school_year: selectedSchoolYear,
|
||||
});
|
||||
};
|
||||
|
||||
@ -246,10 +227,9 @@ const TeachersSection = ({
|
||||
label: 'Profil',
|
||||
transform: (value) => value === 1 ? 'Administrateur' : 'Enseignant'
|
||||
},
|
||||
{ key: 'school_year', label: 'Année scolaire' },
|
||||
{ key: 'updated_date_formatted', label: 'Date de mise à jour' },
|
||||
];
|
||||
const filename = `enseignants_${selectedSchoolYear || 'toutes'}_${new Date().toISOString().split('T')[0]}`;
|
||||
const filename = `enseignants_${new Date().toISOString().split('T')[0]}`;
|
||||
exportToCSV(teachers, exportColumns, filename);
|
||||
};
|
||||
|
||||
@ -288,7 +268,6 @@ const TeachersSection = ({
|
||||
},
|
||||
}),
|
||||
},
|
||||
school_year: formData.school_year || selectedSchoolYear,
|
||||
specialities: formData.specialities || [],
|
||||
};
|
||||
|
||||
@ -340,7 +319,6 @@ const TeachersSection = ({
|
||||
handleEdit(id, {
|
||||
last_name: updatedData.last_name,
|
||||
first_name: updatedData.first_name,
|
||||
school_year: updatedData.school_year || selectedSchoolYear,
|
||||
profile_role_data: profileRoleData,
|
||||
specialities: updatedData.specialities || [],
|
||||
})
|
||||
@ -439,20 +417,6 @@ const TeachersSection = ({
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
case 'ANNÉE SCOLAIRE':
|
||||
return (
|
||||
<SelectChoice
|
||||
type="select"
|
||||
name="school_year"
|
||||
placeHolder="Sélectionnez une année scolaire"
|
||||
choices={getSchoolYearChoices()}
|
||||
callback={handleChange}
|
||||
selected={currentData.school_year || ''}
|
||||
errorMsg={getError('school_year')}
|
||||
IconItem={null}
|
||||
disabled={false}
|
||||
/>
|
||||
);
|
||||
case 'ACTIONS':
|
||||
return (
|
||||
<div className="flex justify-center space-x-2">
|
||||
@ -521,8 +485,6 @@ const TeachersSection = ({
|
||||
} else {
|
||||
return <i>Non définie</i>;
|
||||
}
|
||||
case 'ANNÉE SCOLAIRE':
|
||||
return teacher.school_year;
|
||||
case 'MISE A JOUR':
|
||||
return teacher.updated_date_formatted;
|
||||
case 'ACTIONS':
|
||||
@ -590,7 +552,6 @@ const TeachersSection = ({
|
||||
{ name: 'EMAIL', label: 'Email' },
|
||||
{ name: 'SPECIALITES', label: 'Spécialités' },
|
||||
{ name: 'ADMINISTRATEUR', label: 'Profil' },
|
||||
{ name: 'ANNÉE SCOLAIRE', label: 'Année scolaire' },
|
||||
{ name: 'MISE A JOUR', label: 'Mise à jour' },
|
||||
...(profileRole !== 0 ? [{ name: 'ACTIONS', label: 'Actions' }] : []),
|
||||
];
|
||||
|
||||
Reference in New Issue
Block a user