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. 8
      database/database.js
  4. 266
      database/import/Etudiants.js
  5. BIN
      src/renderer/src/assets/logorelever.png
  6. 21
      src/renderer/src/components/AddNotes.jsx
  7. 136
      src/renderer/src/components/ReleverNotes.jsx
  8. 109
      src/renderer/src/components/Student.jsx

2
database/Models/Etudiants.backup.js

@ -62,7 +62,7 @@ async function insertEtudiant(
* @returns JSON * @returns JSON
*/ */
async function getAllEtudiants() { 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 { try {
let response = await query.all() let response = await query.all()

6
database/Models/Etudiants.js

@ -65,7 +65,7 @@ async function insertEtudiant(
* @returns JSON * @returns JSON
*/ */
async function getAllEtudiants() { 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 { try {
let [rows] = await pool.query(sql) let [rows] = await pool.query(sql)
@ -83,7 +83,7 @@ async function getAllEtudiants() {
* @returns Promise * @returns Promise
*/ */
async function getSingleEtudiant(id) { 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 { try {
const [rows] = await pool.query(sql, [id]) const [rows] = await pool.query(sql, [id])
@ -100,7 +100,7 @@ async function getSingleEtudiant(id) {
* @returns JSON * @returns JSON
*/ */
async function FilterDataByNiveau(niveau) { 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 { try {
let [rows] = await pool.query(sql, [niveau]) let [rows] = await pool.query(sql, [niveau])

8
database/database.js

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

266
database/import/Etudiants.js

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

21
src/renderer/src/components/AddNotes.jsx

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

136
src/renderer/src/components/ReleverNotes.jsx

@ -192,10 +192,10 @@ const ReleverNotes = ({ id, anneescolaire, niveau, sessionType = 'ensemble', ref
{Object.entries(groupedDataBySemestre).map(([semestre, matieres]) => { {Object.entries(groupedDataBySemestre).map(([semestre, matieres]) => {
// Group by unite_enseignement inside each semestre // Group by unite_enseignement inside each semestre
const groupedByUnite = matieres.reduce((acc, matiere) => { const groupedByUnite = matieres.reduce((acc, matiere) => {
if (!acc[matiere.unite_enseignement]) { if (!acc[matiere.ue]) {
acc[matiere.unite_enseignement] = [] acc[matiere.ue] = []
} }
acc[matiere.unite_enseignement].push(matiere) acc[matiere.ue].push(matiere)
return acc return acc
}, {}) }, {})
@ -218,7 +218,6 @@ const ReleverNotes = ({ id, anneescolaire, niveau, sessionType = 'ensemble', ref
paddingTop: '8px', paddingTop: '8px',
borderRight: 'solid 1px black', borderRight: 'solid 1px black',
borderBottom: 'solid 1px black', borderBottom: 'solid 1px black',
background: '#bdbcbc',
borderLeft: 'solid 1px black' 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' }}> <td style={{ borderRight: 'solid 1px black', borderTop: 'solid 1px black' }}>
{matiere.note} {matiere.note}
</td> </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' }}> <td style={{ borderRight: 'solid 1px black', borderTop: 'solid 1px black' }}>
{matiere.noterepech} {matiere.noterepech}
</td> </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.reduce((total, matiere) => total + matiere.noterepech, 0) /
matieres.length matieres.length
).toFixed(2), ).toFixed(2),
sessionType // MODIFICATION: Passer le sessionType sessionType
)} )}
</td> </td>
)} )}
@ -298,7 +333,7 @@ const ReleverNotes = ({ id, anneescolaire, niveau, sessionType = 'ensemble', ref
{/* Add Total Row for 'unite_enseignement' */} {/* Add Total Row for 'unite_enseignement' */}
<tr <tr
style={{ background: '#bdbcbc', border: 'none', borderLeft: 'solid 1px black' }} style={{ border: 'none', borderLeft: 'solid 1px black' }}
> >
<td <td
colSpan={2} colSpan={2}
@ -311,7 +346,7 @@ const ReleverNotes = ({ id, anneescolaire, niveau, sessionType = 'ensemble', ref
borderTop: 'solid 1px black' borderTop: 'solid 1px black'
}} }}
> >
Total de Credit et Moyenne des Notes Total de Credit
</td> </td>
{sessionType !== 'rattrapage' && ( {sessionType !== 'rattrapage' && (
@ -327,19 +362,14 @@ const ReleverNotes = ({ id, anneescolaire, niveau, sessionType = 'ensemble', ref
{matieres.reduce((total, matiere) => total + matiere.credit, 0)} {matieres.reduce((total, matiere) => total + matiere.credit, 0)}
</td> </td>
<td <td
colSpan={2}
style={{ style={{
textAlign: 'center', textAlign: 'center',
fontWeight: 'bold', fontWeight: 'bold',
borderRight: 'solid 1px black', borderRight: 'solid 1px black',
borderTop: 'solid 1px black' borderTop: 'solid 1px black'
}} }}
className="moyenneNotes" ></td>
>
{(
matieres.reduce((total, matiere) => total + matiere.note, 0) /
matieres.length
).toFixed(2)}
</td>
</> </>
)} )}
@ -356,19 +386,14 @@ const ReleverNotes = ({ id, anneescolaire, niveau, sessionType = 'ensemble', ref
{matieres.reduce((total, matiere) => total + matiere.credit, 0)} {matieres.reduce((total, matiere) => total + matiere.credit, 0)}
</td> </td>
<td <td
colSpan={2}
style={{ style={{
textAlign: 'center', textAlign: 'center',
fontWeight: 'bold', fontWeight: 'bold',
borderRight: 'solid 1px black', borderRight: 'solid 1px black',
borderTop: 'solid 1px black' borderTop: 'solid 1px black'
}} }}
className="moyenneNotesRattrapage" ></td>
>
{(
matieres.reduce((total, matiere) => total + matiere.noterepech, 0) /
matieres.length
).toFixed(2)}
</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 = () => { const totalNotes = () => {
let totalNotes = document.querySelectorAll('.moyenneNotes') let totalNotesNormale = document.querySelectorAll('.moyenneUENormale')
let totalNotesRepech = document.querySelectorAll('.moyenneNotesRattrapage') let totalNotesRattrapage = document.querySelectorAll('.moyenneUERattrapage')
let TotalNoteNumber = 0 let TotalNoteNumber = 0
let TotalNoteNumberRepech = 0 let TotalNoteNumberRepech = 0
totalNotes.forEach((notes) => { totalNotesNormale.forEach((notes) => {
TotalNoteNumber += Number(notes.textContent) / totalNotes.length TotalNoteNumber += Number(notes.textContent) / totalNotesNormale.length
}) })
totalNotesRepech.forEach((notes) => { totalNotesRattrapage.forEach((notes) => {
TotalNoteNumberRepech += Number(notes.textContent) / totalNotesRepech.length TotalNoteNumberRepech += Number(notes.textContent) / totalNotesRattrapage.length
}) })
// Retourner la note selon le type de session // Retourner la note selon le type de session
@ -441,11 +466,31 @@ const ReleverNotes = ({ id, anneescolaire, niveau, sessionType = 'ensemble', ref
> >
<div style={{ width: '80%' }}> <div style={{ width: '80%' }}>
<div <div
style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }} style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
textAlign: 'center'
}}
> >
<img src={logoRelerev1} alt="image en tete" width={70} /> <img src={logoRelerev2} alt="logo gauche" width={90} />
<img src={logoRelerev2} alt="image en tete" width={70} />
<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> </div>
<img src={logoRelerev1} alt="logo droite" width={90} />
</div>
<hr style={{ margin: 0, border: 'solid 1px black' }} /> <hr style={{ margin: 0, border: 'solid 1px black' }} />
<h4 style={{ textTransform: 'uppercase', textAlign: 'center', marginBottom: 0 }}> <h4 style={{ textTransform: 'uppercase', textAlign: 'center', marginBottom: 0 }}>
Releve de notes Releve de notes
@ -489,7 +534,7 @@ const ReleverNotes = ({ id, anneescolaire, niveau, sessionType = 'ensemble', ref
{/* droite gauche */} {/* droite gauche */}
<div style={{ width: '30%' }}> <div style={{ width: '30%' }}>
<span> <span>
<b>Annee U</b> <b>Annee Sco</b>
</span> </span>
<br /> <br />
<span> <span>
@ -517,9 +562,8 @@ const ReleverNotes = ({ id, anneescolaire, niveau, sessionType = 'ensemble', ref
<tr style={{ borderTop: 'solid 1px black', textAlign: 'center' }}> <tr style={{ borderTop: 'solid 1px black', textAlign: 'center' }}>
<th colSpan={3}></th> <th colSpan={3}></th>
<th <th
colSpan={sessionType === 'ensemble' ? 4 : 2} colSpan={sessionType === 'ensemble' ? 6 : 3}
style={{ style={{
background: '#bdbcbc',
borderLeft: 'solid 1px black', borderLeft: 'solid 1px black',
borderRight: 'solid 1px black' borderRight: 'solid 1px black'
}} }}
@ -535,8 +579,8 @@ const ReleverNotes = ({ id, anneescolaire, niveau, sessionType = 'ensemble', ref
{sessionType !== 'rattrapage' && ( {sessionType !== 'rattrapage' && (
<th <th
colSpan={shouldShowCredits() ? 2 : 1} colSpan={3}
style={{ background: '#bdbcbc', borderLeft: 'solid 1px black' }} style={{ borderLeft: 'solid 1px black' }}
> >
Normale Normale
</th> </th>
@ -544,8 +588,8 @@ const ReleverNotes = ({ id, anneescolaire, niveau, sessionType = 'ensemble', ref
{sessionType !== 'normale' && ( {sessionType !== 'normale' && (
<th <th
colSpan={shouldShowCredits() ? 2 : 1} colSpan={3}
style={{ background: '#bdbcbc', borderLeft: 'solid 1px black' }} style={{ borderLeft: 'solid 1px black' }}
> >
Rattrapage Rattrapage
</th> </th>
@ -558,20 +602,21 @@ const ReleverNotes = ({ id, anneescolaire, niveau, sessionType = 'ensemble', ref
<tr <tr
style={{ style={{
borderTop: 'solid 1px black', 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 semestre
</th> </th>
<th style={{ borderLeft: 'solid 1px black' }}>UE</th> <th style={{ borderLeft: 'solid 1px black' }}>Unités <br /> d'Enseignement <br />(UE) </th>
<th style={{ borderLeft: 'solid 1px black' }}>EC</th> <th style={{ borderLeft: 'solid 1px black' }}>Éléments <br /> constitutifs <br />(EC)</th>
{sessionType !== 'rattrapage' && ( {sessionType !== 'rattrapage' && (
<> <>
<th style={{ borderLeft: 'solid 1px black', padding: '0 5px' }}>crédit</th> <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' }}>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' }}>crédit</th>
<th style={{ borderLeft: 'solid 1px black', padding: '0 5px' }}>Notes</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>
<td style={{ borderRight: 'solid 1px black' }}>{note.toFixed(2)}</td> <td style={{ borderRight: 'solid 1px black' }}>{note.toFixed(2)}</td>
<td style={{ borderRight: 'solid 1px black' }}>/20</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>
<tr style={{ border: 'solid 1px black' }}> <tr style={{ border: 'solid 1px black' }}>
<td <td
@ -616,7 +662,7 @@ const ReleverNotes = ({ id, anneescolaire, niveau, sessionType = 'ensemble', ref
Mention:{' '} Mention:{' '}
<span style={{ marginLeft: '3%' }}>{getmentionAfterNotes(note)}</span> <span style={{ marginLeft: '3%' }}>{getmentionAfterNotes(note)}</span>
</td> </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:{' '} Décision du Jury:{' '}
</td> </td>
</tr> </tr>

109
src/renderer/src/components/Student.jsx

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

Loading…
Cancel
Save