fix: On n'historise plus les matières ni les enseignants

This commit is contained in:
N3WT DE COMPET
2026-04-05 11:41:52 +02:00
parent 12939fca85
commit f9c0585b30
5 changed files with 93 additions and 169 deletions

View File

@ -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):

View File

@ -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)

View File

@ -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' }] : []),
];

View File

@ -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}

View File

@ -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' }] : []),
];