Browse Source

push 18112025

master
andrymodeste 3 weeks ago
parent
commit
bd0999dce6
  1. 2
      database/Models/Etudiants.backup.js
  2. 6
      database/Models/Etudiants.js
  3. 16
      database/database.js
  4. 266
      database/import/Etudiants.js
  5. BIN
      src/renderer/src/assets/logorelever.png
  6. 23
      src/renderer/src/components/AddNotes.jsx
  7. 140
      src/renderer/src/components/ReleverNotes.jsx
  8. 165
      src/renderer/src/components/Student.jsx

2
database/Models/Etudiants.backup.js

@ -62,7 +62,7 @@ async function insertEtudiant(
* @returns JSON
*/
async function getAllEtudiants() {
const query = database.prepare('SELECT * FROM etudiants ORDER BY annee_scolaire DESC')
const query = database.prepare('SELECT e.*, m.uniter AS mentionUnite, m.nom As nomMention FROM etudiants e JOIN mentions m ON e.mention_id = m.id ORDER BY annee_scolaire DESC')
try {
let response = await query.all()

6
database/Models/Etudiants.js

@ -65,7 +65,7 @@ async function insertEtudiant(
* @returns JSON
*/
async function getAllEtudiants() {
const sql = 'SELECT * FROM etudiants ORDER BY annee_scolaire DESC'
const sql = 'SELECT e.*, m.uniter AS mentionUnite, m.nom As nomMention FROM etudiants e JOIN mentions m ON e.mention_id = m.id ORDER BY annee_scolaire DESC'
try {
let [rows] = await pool.query(sql)
@ -83,7 +83,7 @@ async function getAllEtudiants() {
* @returns Promise
*/
async function getSingleEtudiant(id) {
const sql = 'SELECT e.*, m.uniter AS mentionUnite FROM etudiants e JOIN mentions m ON e.mention_id = m.id WHERE e.id = ?'
const sql = 'SELECT e.*, m.uniter AS mentionUnite, m.nom As nomMention FROM etudiants e JOIN mentions m ON e.mention_id = m.id WHERE e.id = ?'
try {
const [rows] = await pool.query(sql, [id])
@ -100,7 +100,7 @@ async function getSingleEtudiant(id) {
* @returns JSON
*/
async function FilterDataByNiveau(niveau) {
const sql = 'SELECT * FROM etudiants WHERE niveau = ? ORDER BY annee_scolaire DESC'
const sql = 'SELECT e.*, m.uniter AS mentionUnite, m.nom As nomMention FROM etudiants e JOIN mentions m ON e.mention_id = m.id WHERE niveau = ? ORDER BY annee_scolaire DESC'
try {
let [rows] = await pool.query(sql, [niveau])

16
database/database.js

@ -1,14 +1,14 @@
const mysql = require('mysql2/promise')
const bcrypt = require('bcryptjs')
const pool = mysql.createPool({
host: '192.168.200.200',
user: 'root',
password: 'stephane1313',
database: 'university',
waitForConnections: true,
connectionLimit: 10,
queueLimit: 0
const pool = mysql.createPool({
host: '192.168.200.200',
user: 'root',
password: 'stephane1313',
database: 'university',
waitForConnections: true,
connectionLimit: 10,
queueLimit: 0
})
async function createTables() {

266
database/import/Etudiants.js

@ -8,7 +8,7 @@ const dayjs = require('dayjs');
const customParseFormat = require('dayjs/plugin/customParseFormat');
dayjs.extend(customParseFormat);
// ✅ Fonction de correction d'encodage
// ---------- Fonctions utilitaires ----------
function fixEncoding(str) {
if (typeof str !== 'string') return str;
return str
@ -21,156 +21,105 @@ function fixEncoding(str) {
.replace(/â€/g, '…')
.replace(/â€/g, '-');
}
function convertToISODate(input) {
if (!input) return null;
console.log('🔍 Input original:', input, 'Type:', typeof input);
// Si input est un objet Date valide
if (input instanceof Date && !isNaN(input)) {
const result = dayjs(input).format('YYYY-MM-DD');
console.log('📅 Date object convertie:', result);
return result;
return dayjs(input).format('YYYY-MM-DD');
}
// Si input est un nombre (numéro de série Excel)
if (typeof input === 'number') {
// Formule Excel: (numéro - 25569) * 86400 * 1000
const excelDate = new Date((input - 25569) * 86400 * 1000);
const result = dayjs(excelDate).format('YYYY-MM-DD');
console.log('📊 Numéro Excel', input, 'converti en:', result);
return result;
return dayjs(new Date((input - 25569) * 86400 * 1000)).format('YYYY-MM-DD');
}
// Si input est une chaîne
if (typeof input === 'string') {
const cleanInput = input.trim();
// Cas spécial "vers YYYY"
const versMatch = cleanInput.match(/vers\s*(\d{4})/i);
if (versMatch) {
const result = `${versMatch[1]}-01-01`;
console.log('📝 "Vers" détecté:', result);
return result;
}
if (versMatch) return `${versMatch[1]}-01-01`;
// Formats à tester dans l'ordre de priorité
const formats = [
'DD/MM/YYYY', 'D/M/YYYY', // Format français prioritaire
'YYYY-MM-DD', // Format ISO
'DD-MM-YYYY', 'D-M-YYYY', // Format français avec tirets
'MM/DD/YYYY', 'M/D/YYYY', // Format américain
'MM-DD-YYYY', 'M-D-YYYY', // Format américain avec tirets
'DD/MM/YY', 'D/M/YY', // Années courtes
'DD/MM/YYYY', 'D/M/YYYY',
'YYYY-MM-DD',
'DD-MM-YYYY', 'D-M-YYYY',
'MM/DD/YYYY', 'M/D/YYYY',
'MM-DD-YYYY', 'M-D-YYYY',
'DD/MM/YY', 'D/M/YY',
'MM/DD/YY', 'M/D/YY',
'DD-MM-YY', 'D-M-YY',
'MM-DD-YY', 'M-D-YY'
];
// Test avec parsing strict pour éviter les interprétations erronées
for (const format of formats) {
const parsedDate = dayjs(cleanInput, format, true); // true = strict parsing
if (parsedDate.isValid()) {
const result = parsedDate.format('YYYY-MM-DD');
console.log(`✅ Format "${format}" réussi:`, cleanInput, '->', result);
// Vérification supplémentaire pour les dates invalides comme 29/02 en année non-bissextile
if (format.includes('DD/MM') || format.includes('D/M')) {
const day = parsedDate.date();
const month = parsedDate.month() + 1; // dayjs month is 0-indexed
const year = parsedDate.year();
// Vérifier si c'est le 29 février d'une année non-bissextile
if (month === 2 && day === 29 && !isLeapYear(year)) {
console.warn('⚠️ Date invalide détectée: 29 février en année non-bissextile');
return null; // ou retourner une date par défaut
}
}
return result;
}
for (const fmt of formats) {
const parsed = dayjs(cleanInput, fmt, true);
if (parsed.isValid()) return parsed.format('YYYY-MM-DD');
}
// Si aucun format strict ne fonctionne, essayer le parsing libre en dernier recours
const freeParseDate = dayjs(cleanInput);
if (freeParseDate.isValid()) {
const result = freeParseDate.format('YYYY-MM-DD');
console.log('🆓 Parsing libre réussi:', cleanInput, '->', result);
return result;
}
const freeParse = dayjs(cleanInput);
if (freeParse.isValid()) return freeParse.format('YYYY-MM-DD');
}
console.error('❌ Impossible de convertir:', input);
return null;
}
// Fonction utilitaire pour vérifier les années bissextiles
// Vérifie année bissextile
function isLeapYear(year) {
return (year % 4 === 0 && year % 100 !== 0) || (year % 400 === 0);
}
// ✅ Mise à jour d'un étudiant existant
// ---------- UPDATE étudiant ----------
async function updateEtudiant(row) {
const sql = `
UPDATE etudiants SET
nom = ?,
prenom = ?,
photos = ?,
date_de_naissances = ?,
niveau = ?,
annee_scolaire = ?,
status = ?,
mention_id = ?,
num_inscription = ?,
sexe = ?,
date_delivrance = ?,
nationalite = ?,
annee_bacc = ?,
serie = ?,
boursier = ?,
domaine = ?,
contact = ?,
parcours = ?
WHERE cin = ? OR (LOWER(nom) = ? AND LOWER(prenom) = ?)
`;
const fields = [];
const params = [];
const params = [
row.nom,
row.prenom,
getCompressedDefaultImage(),
convertToISODate(row.date_naissance),
row.niveau,
row.annee_scolaire,
row.code_redoublement,
row.mention,
row.num_inscription.toString(),
row.sexe,
convertToISODate(row.date_de_delivrance),
row.nationaliter,
parseInt(row.annee_baccalaureat, 10),
row.serie,
row.boursier,
fixEncoding(row.domaine),
row.contact,
null,
row.cin,
row.nom.toLowerCase().trim(),
row.prenom.toLowerCase().trim()
];
function addFieldIfValue(field, value) {
if (value !== undefined && value !== null && value !== '') {
fields.push(`${field} = ?`);
params.push(value);
}
}
addFieldIfValue('nom', row.nom);
addFieldIfValue('prenom', row.prenom);
addFieldIfValue('date_de_naissances', convertToISODate(row.date_naissance));
addFieldIfValue('niveau', row.niveau);
addFieldIfValue('annee_scolaire', row.annee_scolaire);
addFieldIfValue('status', row.code_redoublement);
addFieldIfValue('mention_id', row.mention);
addFieldIfValue('num_inscription', row.num_inscription?.toString());
addFieldIfValue('sexe', row.sexe);
addFieldIfValue('date_delivrance', convertToISODate(row.date_de_delivrance));
addFieldIfValue('nationalite', row.nationaliter);
addFieldIfValue('annee_bacc', parseInt(row.annee_baccalaureat, 10));
addFieldIfValue('serie', row.serie);
addFieldIfValue('boursier', row.boursier);
addFieldIfValue('domaine', fixEncoding(row.domaine));
addFieldIfValue('contact', row.contact);
if (fields.length === 0) return { success: false, error: 'Aucun champ valide à mettre à jour' };
let sql, whereParams;
if (row.cin && row.cin.toString().trim() !== '') {
sql = `UPDATE etudiants SET ${fields.join(', ')} WHERE cin = ?`;
whereParams = [row.cin];
} else {
sql = `UPDATE etudiants SET ${fields.join(', ')} WHERE LOWER(TRIM(nom)) = ? AND LOWER(TRIM(prenom)) = ?`;
whereParams = [row.nom.toLowerCase().trim(), row.prenom.toLowerCase().trim()];
}
try {
const [result] = await pool.query(sql, params);
console.log(`Update effectué pour CIN ${row.cin} ou nom ${row.nom} ${row.prenom}, affectedRows=${result.affectedRows}`);
const [result] = await pool.query(sql, [...params, ...whereParams]);
return { success: true, affectedRows: result.affectedRows };
} catch (error) {
console.error('❌ Erreur MySQL update :', error.message);
return { success: false, error: error.message };
}
}
// ✅ Insertion réelle multiple
// ---------- INSERT multiple étudiants ----------
async function insertMultipleEtudiants(etudiants) {
if (!etudiants || etudiants.length === 0) return { success: true, affectedRows: 0 };
const sql = `
INSERT INTO etudiants (
nom, prenom, photos, date_de_naissances, niveau, annee_scolaire, status,
@ -190,7 +139,7 @@ async function insertMultipleEtudiants(etudiants) {
row.mention,
row.num_inscription.toString(),
row.sexe,
row.cin,
row.cin || null,
convertToISODate(row.date_de_delivrance),
row.nationaliter,
parseInt(row.annee_baccalaureat, 10),
@ -205,31 +154,26 @@ async function insertMultipleEtudiants(etudiants) {
const [result] = await pool.query(sql, [values]);
return { success: true, affectedRows: result.affectedRows };
} catch (error) {
console.error('❌ Erreur MySQL :', error.message);
return { success: false, error: error.message };
}
}
// ✅ Import fichier vers base
// ---------- IMPORT fichier ----------
async function importFileToDatabase(filePath) {
const fileExtension = path.extname(filePath).toLowerCase();
let records;
const ext = path.extname(filePath).toLowerCase();
if (fileExtension === '.xlsx') {
if (ext === '.xlsx') {
const workbook = XLSX.readFile(filePath);
const worksheet = workbook.Sheets[workbook.SheetNames[0]];
// raw: true pour garder les valeurs brutes, surtout pour les dates
records = XLSX.utils.sheet_to_json(worksheet, { defval: ''});
} else if (fileExtension === '.csv') {
const fileContent = fs.readFileSync(filePath, 'utf8');
records = parse(fileContent, { columns: true, skip_empty_lines: true });
}else {
console.error('Unsupported file format.');
return { error: true, message: 'Format de fichier non supporté.' };
records = XLSX.utils.sheet_to_json(worksheet, { defval: '' });
} else if (ext === '.csv') {
const content = fs.readFileSync(filePath, 'utf8');
records = parse(content, { columns: true, skip_empty_lines: true });
} else {
return { error: true, message: 'Format de fichier non supporté' };
}
console.log(`📄 Nombre de lignes : ${records.length}`);
// Vérifier champs obligatoires
const requiredFields = [
'nom', 'date_naissance', 'niveau', 'annee_scolaire',
@ -239,12 +183,8 @@ async function importFileToDatabase(filePath) {
];
for (const [i, row] of records.entries()) {
for (const field of requiredFields) {
if (!row[field]) {
const msg = `Le champ '${field}' est manquant à la ligne ${i + 2}`;
console.error(msg);
return { error: true, message: msg };
}
for (const f of requiredFields) {
if (!row[f]) return { error: true, message: `Le champ '${f}' est manquant à la ligne ${i + 2}` };
}
}
@ -253,64 +193,48 @@ async function importFileToDatabase(filePath) {
const etudiantsToInsert = [];
const doublons = [];
console.log(records);
for (const row of records) {
// Mapping mention
console.log('Avant conversion date_naissance:', row.date_naissance);
row.date_naissance = convertToISODate(row.date_naissance);
console.log('Après conversion date_naissance:', row.date_naissance);
// Mapping mention
const matchedMention = mentionRows.find(
m => m.nom.toUpperCase() === row.mention.toUpperCase() ||
m.uniter.toUpperCase() === row.mention.toUpperCase()
m => m.nom.toUpperCase() === row.mention.toUpperCase() || m.uniter.toUpperCase() === row.mention.toUpperCase()
);
if (matchedMention) row.mention = matchedMention.id;
// Gestion code_redoublement -> status id
if (row.code_redoublement) {
row.code_redoublement = row.code_redoublement.trim().substring(0, 1);
} else {
row.code_redoublement = 'N';
}
const statusMatch = statusRows.find(
s => s.nom.toLowerCase().startsWith(row.code_redoublement.toLowerCase())
);
// Mapping status
row.code_redoublement = (row.code_redoublement ? row.code_redoublement.trim().substring(0, 1) : 'N');
const statusMatch = statusRows.find(s => s.nom.toLowerCase().startsWith(row.code_redoublement.toLowerCase()));
if (statusMatch) row.code_redoublement = statusMatch.id;
// Vérification doublons (extraction complet)
const nomComplet = (row.nom + ' ' + row.prenom).toLowerCase().trim();
const [existing] = await pool.query(
'SELECT * FROM etudiants WHERE LOWER(CONCAT(nom, " ", prenom)) = ? OR cin = ?',
[nomComplet, row.cin]
);
// Détection doublons (ignorer CIN vide)
let existing;
if (row.cin && row.cin.toString().trim() !== '') {
[existing] = await pool.query('SELECT * FROM etudiants WHERE cin = ?', [row.cin]);
} else {
[existing] = await pool.query(
'SELECT * FROM etudiants WHERE LOWER(TRIM(nom)) = ? AND LOWER(TRIM(prenom)) = ?',
[row.nom.toLowerCase().trim(), row.prenom.toLowerCase().trim()]
);
}
if (existing.length > 0) {
doublons.push({ nom: row.nom, prenom: row.prenom, cin: row.cin });
// Mise à jour
const updateResult = await updateEtudiant(row);
if (!updateResult.success) {
return { error: true, message: `Erreur lors de la mise à jour de ${row.nom} ${row.prenom} : ${updateResult.error}` };
}
continue;
if (!updateResult.success) return { error: true, message: `Erreur update ${row.nom} ${row.prenom}: ${updateResult.error}` };
} else {
etudiantsToInsert.push(row);
}
etudiantsToInsert.push(row);
}
console.log(etudiantsToInsert);
console.log('✅ Nouveaux à insérer :', etudiantsToInsert.map(e => e.nom + ' ' + e.prenom));
console.log('🔄 Étudiants mis à jour :', doublons.map(e => e.nom + ' ' + e.prenom));
// Insertion des nouveaux
let insertResult = { success: true, affectedRows: 0 };
if (etudiantsToInsert.length > 0) {
insertResult = await insertMultipleEtudiants(etudiantsToInsert);
if (!insertResult.success) {
return { error: true, message: `Erreur lors de l'insertion : ${insertResult.error}` };
}
}
const insertResult = await insertMultipleEtudiants(etudiantsToInsert);
if (!insertResult.success) return { error: true, message: insertResult.error };
let msg = `Importation réussie. ${etudiantsToInsert.length} nouvel(le)(s) étudiant(s) inséré(s). ${doublons.length} étudiant(s) mis à jour.`;
return { error: false, message: msg };
return { error: false, message: `Importation réussie. ${etudiantsToInsert.length} nouvel(le)(s) inséré(s), ${doublons.length} mis à jour.` };
}
module.exports = { importFileToDatabase };

BIN
src/renderer/src/assets/logorelever.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.8 KiB

After

Width:  |  Height:  |  Size: 39 KiB

23
src/renderer/src/components/AddNotes.jsx

@ -10,6 +10,7 @@ import svgSuccess from '../assets/success.svg'
import svgError from '../assets/error.svg'
import validateAddNote from './validation/AddNote'
import ModalUpdateParcoursEtudiant from './ModalUpdateParcoursEtudiant'
import { useLocation } from 'react-router-dom'
const AddNotes = () => {
const { id, niveau, mention_id, parcours } = useParams()
@ -22,6 +23,9 @@ const AddNotes = () => {
setOpenModal1(false)
}
const location = useLocation()
const previousFilter = location.state?.selectedNiveau
/**
* Fetching the matieres
*/
@ -83,7 +87,7 @@ const AddNotes = () => {
let valid = validateAddNote()
let annee_scolaire = AnneeScolaireEtudiant
let mention_id = etudiants.mention_id
if (valid) {
let response = await window.notes.insertNote({
etudiant_id,
@ -92,21 +96,33 @@ const AddNotes = () => {
formData,
annee_scolaire
})
if (response.success) {
// Ici on sauvegarde le filtre avant d'ouvrir le modal
if (previousFilter) {
localStorage.setItem('selectedNiveau', previousFilter)
}
setOpen(true)
setStatut(200)
setDisabled(true)
// Reset du formulaire
const resetFormData = matieres.reduce((acc, mat) => {
acc[mat.id] = '' // Reset each field to an empty string
acc[mat.id] = ''
return acc
}, {})
setFormData(resetFormData)
} else {
setOpen(true)
setStatut(400)
}
} else {
setOpen(true)
setStatut(400)
}
}
const [statut, setStatut] = useState(200)
@ -123,9 +139,10 @@ const AddNotes = () => {
}
const handleClose2 = () => {
navigate('/notes')
navigate('/student', { state: { selectedNiveau: previousFilter } })
setOpen(false)
}
/**
* function to return the view Modal

140
src/renderer/src/components/ReleverNotes.jsx

@ -192,10 +192,10 @@ const ReleverNotes = ({ id, anneescolaire, niveau, sessionType = 'ensemble', ref
{Object.entries(groupedDataBySemestre).map(([semestre, matieres]) => {
// Group by unite_enseignement inside each semestre
const groupedByUnite = matieres.reduce((acc, matiere) => {
if (!acc[matiere.unite_enseignement]) {
acc[matiere.unite_enseignement] = []
if (!acc[matiere.ue]) {
acc[matiere.ue] = []
}
acc[matiere.unite_enseignement].push(matiere)
acc[matiere.ue].push(matiere)
return acc
}, {})
@ -218,7 +218,6 @@ const ReleverNotes = ({ id, anneescolaire, niveau, sessionType = 'ensemble', ref
paddingTop: '8px',
borderRight: 'solid 1px black',
borderBottom: 'solid 1px black',
background: '#bdbcbc',
borderLeft: 'solid 1px black'
}}
>
@ -255,6 +254,24 @@ const ReleverNotes = ({ id, anneescolaire, niveau, sessionType = 'ensemble', ref
<td style={{ borderRight: 'solid 1px black', borderTop: 'solid 1px black' }}>
{matiere.note}
</td>
{/* Moyenne UE pour session normale */}
{matiereIndex === 0 && (
<td
rowSpan={matieres.length}
style={{
fontWeight: 'bold',
textAlign: 'center',
borderRight: 'solid 1px black',
borderTop: 'solid 1px black'
}}
className="moyenneUENormale"
>
{(
matieres.reduce((total, matiere) => total + matiere.note, 0) /
matieres.length
).toFixed(2)}
</td>
)}
</>
)}
@ -266,6 +283,24 @@ const ReleverNotes = ({ id, anneescolaire, niveau, sessionType = 'ensemble', ref
<td style={{ borderRight: 'solid 1px black', borderTop: 'solid 1px black' }}>
{matiere.noterepech}
</td>
{/* Moyenne UE pour session rattrapage */}
{matiereIndex === 0 && (
<td
rowSpan={matieres.length}
style={{
fontWeight: 'bold',
textAlign: 'center',
borderRight: 'solid 1px black',
borderTop: 'solid 1px black'
}}
className="moyenneUERattrapage"
>
{(
matieres.reduce((total, matiere) => total + matiere.noterepech, 0) /
matieres.length
).toFixed(2)}
</td>
)}
</>
)}
@ -289,7 +324,7 @@ const ReleverNotes = ({ id, anneescolaire, niveau, sessionType = 'ensemble', ref
matieres.reduce((total, matiere) => total + matiere.noterepech, 0) /
matieres.length
).toFixed(2),
sessionType // MODIFICATION: Passer le sessionType
sessionType
)}
</td>
)}
@ -298,7 +333,7 @@ const ReleverNotes = ({ id, anneescolaire, niveau, sessionType = 'ensemble', ref
{/* Add Total Row for 'unite_enseignement' */}
<tr
style={{ background: '#bdbcbc', border: 'none', borderLeft: 'solid 1px black' }}
style={{ border: 'none', borderLeft: 'solid 1px black' }}
>
<td
colSpan={2}
@ -311,7 +346,7 @@ const ReleverNotes = ({ id, anneescolaire, niveau, sessionType = 'ensemble', ref
borderTop: 'solid 1px black'
}}
>
Total de Credit et Moyenne des Notes
Total de Credit
</td>
{sessionType !== 'rattrapage' && (
@ -327,19 +362,14 @@ const ReleverNotes = ({ id, anneescolaire, niveau, sessionType = 'ensemble', ref
{matieres.reduce((total, matiere) => total + matiere.credit, 0)}
</td>
<td
colSpan={2}
style={{
textAlign: 'center',
fontWeight: 'bold',
borderRight: 'solid 1px black',
borderTop: 'solid 1px black'
}}
className="moyenneNotes"
>
{(
matieres.reduce((total, matiere) => total + matiere.note, 0) /
matieres.length
).toFixed(2)}
</td>
></td>
</>
)}
@ -356,19 +386,14 @@ const ReleverNotes = ({ id, anneescolaire, niveau, sessionType = 'ensemble', ref
{matieres.reduce((total, matiere) => total + matiere.credit, 0)}
</td>
<td
colSpan={2}
style={{
textAlign: 'center',
fontWeight: 'bold',
borderRight: 'solid 1px black',
borderTop: 'solid 1px black'
}}
className="moyenneNotesRattrapage"
>
{(
matieres.reduce((total, matiere) => total + matiere.noterepech, 0) /
matieres.length
).toFixed(2)}
</td>
></td>
</>
)}
@ -390,20 +415,20 @@ const ReleverNotes = ({ id, anneescolaire, niveau, sessionType = 'ensemble', ref
)
}
// MODIFICATION: Fonction totalNotes mise à jour pour tenir compte du sessionType
// MODIFICATION: Fonction totalNotes mise à jour pour utiliser les nouvelles classes
const totalNotes = () => {
let totalNotes = document.querySelectorAll('.moyenneNotes')
let totalNotesRepech = document.querySelectorAll('.moyenneNotesRattrapage')
let totalNotesNormale = document.querySelectorAll('.moyenneUENormale')
let totalNotesRattrapage = document.querySelectorAll('.moyenneUERattrapage')
let TotalNoteNumber = 0
let TotalNoteNumberRepech = 0
totalNotes.forEach((notes) => {
TotalNoteNumber += Number(notes.textContent) / totalNotes.length
totalNotesNormale.forEach((notes) => {
TotalNoteNumber += Number(notes.textContent) / totalNotesNormale.length
})
totalNotesRepech.forEach((notes) => {
TotalNoteNumberRepech += Number(notes.textContent) / totalNotesRepech.length
totalNotesRattrapage.forEach((notes) => {
TotalNoteNumberRepech += Number(notes.textContent) / totalNotesRattrapage.length
})
// Retourner la note selon le type de session
@ -440,12 +465,32 @@ const ReleverNotes = ({ id, anneescolaire, niveau, sessionType = 'ensemble', ref
ref={Telever}
>
<div style={{ width: '80%' }}>
<div
style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}
>
<img src={logoRelerev1} alt="image en tete" width={70} />
<img src={logoRelerev2} alt="image en tete" width={70} />
<div
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
textAlign: 'center'
}}
>
<img src={logoRelerev2} alt="logo gauche" width={90} />
<div style={{ flex: 1, margin: '0 20px' }}>
<h5 style={{ margin: 0, fontWeight: 'bold', textTransform: 'uppercase',fontSize: '16px' }}>
REPOBLIKAN'I MADAGASIKARA
</h5>
<p style={{ margin: 0, fontStyle: 'italic',fontSize: '11px' }}>Fitiavana Tanindrazana Fandrosoana</p>
<p style={{ margin: 0, fontWeight: 'bold',fontSize: '11px' }}>
MINISTÈRE DE L'ENSEIGNEMENT SUPÉRIEUR <br />
ET DE LA RECHERCHE SCIENTIFIQUE
</p>
<p style={{ margin: 0, fontWeight: 'bold',fontSize: '16px' }}>UNIVERSITÉ DE TOAMASINA</p>
<p style={{ margin: 0, fontWeight: 'bold',fontSize: '16px' }}>ÉCOLE SUPÉRIEURE POLYTECHNIQUE</p>
</div>
<img src={logoRelerev1} alt="logo droite" width={90} />
</div>
<hr style={{ margin: 0, border: 'solid 1px black' }} />
<h4 style={{ textTransform: 'uppercase', textAlign: 'center', marginBottom: 0 }}>
Releve de notes
@ -489,7 +534,7 @@ const ReleverNotes = ({ id, anneescolaire, niveau, sessionType = 'ensemble', ref
{/* droite gauche */}
<div style={{ width: '30%' }}>
<span>
<b>Annee U</b>
<b>Annee Sco</b>
</span>
<br />
<span>
@ -517,9 +562,8 @@ const ReleverNotes = ({ id, anneescolaire, niveau, sessionType = 'ensemble', ref
<tr style={{ borderTop: 'solid 1px black', textAlign: 'center' }}>
<th colSpan={3}></th>
<th
colSpan={sessionType === 'ensemble' ? 4 : 2}
colSpan={sessionType === 'ensemble' ? 6 : 3}
style={{
background: '#bdbcbc',
borderLeft: 'solid 1px black',
borderRight: 'solid 1px black'
}}
@ -535,8 +579,8 @@ const ReleverNotes = ({ id, anneescolaire, niveau, sessionType = 'ensemble', ref
{sessionType !== 'rattrapage' && (
<th
colSpan={shouldShowCredits() ? 2 : 1}
style={{ background: '#bdbcbc', borderLeft: 'solid 1px black' }}
colSpan={3}
style={{ borderLeft: 'solid 1px black' }}
>
Normale
</th>
@ -544,8 +588,8 @@ const ReleverNotes = ({ id, anneescolaire, niveau, sessionType = 'ensemble', ref
{sessionType !== 'normale' && (
<th
colSpan={shouldShowCredits() ? 2 : 1}
style={{ background: '#bdbcbc', borderLeft: 'solid 1px black' }}
colSpan={3}
style={{ borderLeft: 'solid 1px black' }}
>
Rattrapage
</th>
@ -558,20 +602,21 @@ const ReleverNotes = ({ id, anneescolaire, niveau, sessionType = 'ensemble', ref
<tr
style={{
borderTop: 'solid 1px black',
background: '#bdbcbc',
textAlign: 'center'
textAlign: 'center',
padding:'20px',
}}
>
<th style={{ borderLeft: 'solid 1px black', borderBottom: 'solid 1px black' }}>
<th style={{padding: '1%', borderLeft: 'solid 1px black', borderBottom: 'solid 1px black' }}>
semestre
</th>
<th style={{ borderLeft: 'solid 1px black' }}>UE</th>
<th style={{ borderLeft: 'solid 1px black' }}>EC</th>
<th style={{ borderLeft: 'solid 1px black' }}>Unités <br /> d'Enseignement <br />(UE) </th>
<th style={{ borderLeft: 'solid 1px black' }}>Éléments <br /> constitutifs <br />(EC)</th>
{sessionType !== 'rattrapage' && (
<>
<th style={{ borderLeft: 'solid 1px black', padding: '0 5px' }}>crédit</th>
<th style={{ borderLeft: 'solid 1px black', padding: '0 5px' }}>Notes</th>
<th style={{ borderLeft: 'solid 1px black', padding: '0 5px' }}>Moyenne</th>
</>
)}
@ -579,6 +624,7 @@ const ReleverNotes = ({ id, anneescolaire, niveau, sessionType = 'ensemble', ref
<>
<th style={{ borderLeft: 'solid 1px black', padding: '0 5px' }}>crédit</th>
<th style={{ borderLeft: 'solid 1px black', padding: '0 5px' }}>Notes</th>
<th style={{ borderLeft: 'solid 1px black', padding: '0 5px' }}>Moyenne</th>
</>
)}
@ -602,7 +648,7 @@ const ReleverNotes = ({ id, anneescolaire, niveau, sessionType = 'ensemble', ref
</td>
<td style={{ borderRight: 'solid 1px black' }}>{note.toFixed(2)}</td>
<td style={{ borderRight: 'solid 1px black' }}>/20</td>
<td colSpan={sessionType === 'ensemble' ? 3 : 2}></td>
<td colSpan={sessionType === 'ensemble' ? 5 : 3}></td>
</tr>
<tr style={{ border: 'solid 1px black' }}>
<td
@ -616,7 +662,7 @@ const ReleverNotes = ({ id, anneescolaire, niveau, sessionType = 'ensemble', ref
Mention:{' '}
<span style={{ marginLeft: '3%' }}>{getmentionAfterNotes(note)}</span>
</td>
<td colSpan={sessionType === 'ensemble' ? 4 : 3} style={{ textAlign: 'left', paddingLeft: '1%' }}>
<td colSpan={sessionType === 'ensemble' ? 6 : 4} style={{ textAlign: 'left', paddingLeft: '1%' }}>
Décision du Jury:{' '}
</td>
</tr>

165
src/renderer/src/components/Student.jsx

@ -27,6 +27,7 @@ import { MdVerified } from 'react-icons/md'
import warning from '../assets/warning.svg'
import success from '../assets/success.svg'
import { useAuthContext } from '../contexts/AuthContext' // Import du contexte d'authentification
import { useLocation } from 'react-router-dom'
const Student = () => {
// Récupération de l'utilisateur connecté
@ -59,22 +60,56 @@ const Student = () => {
const [status, setStatus] = useState([])
const [mention, setMention] = useState([])
const [filterModel, setFilterModel] = useState({ items: [] })
const [sortModel, setSortModel] = useState([])
const location = useLocation()
const savedFilter = localStorage.getItem('selectedNiveau') || ''
const initialFilter = location.state?.selectedNiveau || savedFilter
const [selectedNiveau, setSelectedNiveau] = useState(initialFilter)
useEffect(() => {
if (initialFilter) {
setSelectedNiveau(initialFilter)
FilterData({ target: { value: initialFilter } }) // applique le filtre initial
}
}, [initialFilter])
/**
* hook for displaying the students
*/
const [allEtudiants, setAllEtudiants] = useState([])
const [etudiants, setEtudiants] = useState([])
const [notes, setNotes] = useState([])
useEffect(() => {
window.etudiants.getEtudiants().then((response) => {
setEtudiants(response)
setAllEtudiants(response)
if (selectedNiveau && selectedNiveau !== '') {
setEtudiants(response.filter(e => e.niveau === selectedNiveau))
} else {
setEtudiants(response)
}
})
window.notes.getMoyenneVerify().then((response) => {
setNotes(response)
})
}, [selectedNiveau])
useEffect(() => {
const savedFilters = localStorage.getItem('datagridFilters')
const savedSort = localStorage.getItem('datagridSort')
const savedPagination = localStorage.getItem('datagridPagination')
if (savedFilters) setFilterModel(JSON.parse(savedFilters))
if (savedSort) setSortModel(JSON.parse(savedSort))
if (savedPagination) setPaginationModel(JSON.parse(savedPagination))
}, [])
const [niveaus, setNiveau] = useState([])
@ -222,15 +257,21 @@ const Student = () => {
</Link>
) : (
<Link
to={`/addnotes/${params.value}/${params.row.niveau}/${params.row.mention_id}/${params.row.parcour}`}
>
<Button color="warning" variant="contained" className={`note${params.value}`}>
<CgNotes style={{ fontSize: '20px', color: 'white' }} />
</Button>
<Tooltip anchorSelect={`.note${params.value}`} className="custom-tooltip" place="top">
Ajouter un notes à cet étudiant
</Tooltip>
</Link>
to={`/addnotes/${params.value}/${params.row.niveau}/${params.row.mention_id}/${params.row.parcour}`}
state={{ selectedNiveau: selectedNiveau }} // <-- on envoie le filtre
>
<Button color="warning" variant="contained" className={`note${params.value}`}>
<CgNotes style={{ fontSize: '20px', color: 'white' }} />
</Button>
<Tooltip
anchorSelect={`.note${params.value}`}
className="custom-tooltip"
place="top"
>
Ajouter un notes à cet étudiant
</Tooltip>
</Link>
)}
</div>
)
@ -277,6 +318,7 @@ const Student = () => {
{ field: 'status', headerName: 'Status', width: 140 },
{ field: 'num_inscription', headerName: "Numéro d'inscription", width: 160 },
{ field: 'parcour', headerName: 'Parcours', width: 150 },
{ field: 'nomMention', headerName: 'Mention', width: 150 },
{
field: 'photos',
headerName: 'Image',
@ -501,6 +543,7 @@ const Student = () => {
contact: etudiant.contact,
mention_id: etudiant.mention_id,
mentionUnite: etudiant.mentionUnite,
nomMention: etudiant.nomMention,
action: etudiant.id // Ensure this is a valid URL for the image
}))
@ -518,21 +561,20 @@ const Student = () => {
/**
* Fonction de filtrage avec reset de pagination
*/
const FilterData = async (e) => {
let niveau = e.target.value
if (niveau !== '') {
let data = await window.etudiants.FilterDataByNiveau({ niveau })
setEtudiants(data)
// Reset vers la première page après filtrage
setPaginationModel(prev => ({ ...prev, page: 0 }))
const FilterData = (e) => {
const niveau = e.target.value
setSelectedNiveau(niveau)
if (niveau === '') {
setEtudiants(allEtudiants)
} else {
window.etudiants.getEtudiants().then((response) => {
setEtudiants(response)
// Reset vers la première page
setPaginationModel(prev => ({ ...prev, page: 0 }))
})
const filtered = allEtudiants.filter(student => student.niveau === niveau)
setEtudiants(filtered)
}
setPaginationModel(prev => ({ ...prev, page: 0 }))
}
const [openModal, setOpenModal] = useState(false)
const [openModal2, setOpenModal2] = useState(false)
@ -645,36 +687,31 @@ const Student = () => {
Niveau
</InputLabel>
<Select
labelId="demo-select-small-label"
id="demo-select-small"
label="Niveau"
color="warning"
name="niveau"
defaultValue={''}
onChange={FilterData}
startAdornment={
<InputAdornment position="start">
<FaGraduationCap />
</InputAdornment>
}
sx={{
background: 'white',
display: 'flex',
alignItems: 'center', // Align icon and text vertically
'& .MuiSelect-icon': {
marginLeft: 'auto' // Keep the dropdown arrow to the right
}
}}
>
<MenuItem value="">
<em>Tous les étudiants</em>
</MenuItem>
{niveaus.map((niveau) => (
<MenuItem value={niveau.nom} key={niveau.id}>
{niveau.nom}
</MenuItem>
))}
</Select>
labelId="demo-select-small-label"
id="demo-select-small"
label="Niveau"
color="warning"
name="niveau"
value={selectedNiveau} // ici
onChange={FilterData} // ici
startAdornment={
<InputAdornment position="start">
<FaGraduationCap />
</InputAdornment>
}
sx={{ background: 'white', display: 'flex', alignItems: 'center', '& .MuiSelect-icon': { marginLeft: 'auto' } }}
>
<MenuItem value="">
<em>Tous les étudiants</em>
</MenuItem>
{niveaus.map((niveau) => (
<MenuItem value={niveau.nom} key={niveau.id}>
{niveau.nom}
</MenuItem>
))}
</Select>
</FormControl>
</div>
</div>
@ -700,11 +737,23 @@ const Student = () => {
}}
>
<DataGrid
rows={dataRow}
columns={columns}
pageSizeOptions={pageSizeOptions}
paginationModel={paginationModel} // Utilise l'état complet
onPaginationModelChange={handlePaginationModelChange} // Gère page ET pageSize
rows={dataRow}
columns={columns}
filterModel={filterModel} // Restaure les filtres
onFilterModelChange={(newModel) => {
setFilterModel(newModel)
localStorage.setItem('datagridFilters', JSON.stringify(newModel))
}}
sortModel={sortModel} // Restaure le tri
onSortModelChange={(newModel) => {
setSortModel(newModel)
localStorage.setItem('datagridSort', JSON.stringify(newModel))
}}
paginationModel={paginationModel}
onPaginationModelChange={(newModel) => {
handlePaginationModelChange(newModel)
localStorage.setItem('datagridPagination', JSON.stringify(newModel))
}}
sx={{
border: 0,
width: 'auto', // Ensures the DataGrid takes full width

Loading…
Cancel
Save