Normalisation BDD : séparation données permanentes / inscriptions annuelles
Module écolage complet : configuration, paiement par tranche, suivi intégré Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
616b16ae29
commit
6e43b5256c
171
README.md
171
README.md
@ -1,34 +1,169 @@
|
||||
# package.json
|
||||
# C-University
|
||||
|
||||
An Electron application with React
|
||||
Application de gestion universitaire desktop, développée avec **Electron + React + Vite**. Elle permet la gestion complète des étudiants, des notes, des matières, des mentions et des années scolaires d'un établissement universitaire.
|
||||
|
||||
## Recommended IDE Setup
|
||||
> Développé par **CPAY COMPANY FOR MADAGASCAR**
|
||||
|
||||
- [VSCode](https://code.visualstudio.com/) + [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) + [Prettier](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode)
|
||||
---
|
||||
|
||||
## Project Setup
|
||||
## Fonctionnalités
|
||||
|
||||
### Install
|
||||
- **Gestion des étudiants** — inscription, consultation, modification, export PDF/Excel, import CSV
|
||||
- **Gestion des notes** — saisie, notes de rattrapage (repêchage), calcul automatique des moyennes
|
||||
- **Résultats** — admis / redoublant / renvoyé selon un système de seuils configurables
|
||||
- **Mentions & Parcours** — gestion des filières et des spécialisations
|
||||
- **Matières** — création, affectation aux mentions et aux semestres, import CSV
|
||||
- **Niveaux académiques** — gestion des niveaux d'étude (L1, L2, M1, M2, etc.)
|
||||
- **Années scolaires** — gestion multi-années avec année courante active
|
||||
- **Tranche d'écolage** — suivi des paiements de scolarité par étudiant
|
||||
- **Export de fiches** — génération de fiches matières avec QR code et PDF
|
||||
- **Statistiques & graphiques** — visualisation via Chart.js
|
||||
- **Gestion des utilisateurs** — authentification sécurisée, rôles admin
|
||||
- **Mise à jour automatique** — via `electron-updater`
|
||||
|
||||
---
|
||||
|
||||
## Stack technique
|
||||
|
||||
| Couche | Technologies |
|
||||
|--------|-------------|
|
||||
| Desktop | Electron 31 |
|
||||
| Frontend | React 18, Vite, React Router DOM v6 |
|
||||
| UI | Material UI (MUI v6), Bootstrap 5, React-Bootstrap |
|
||||
| État | TanStack React Query, Context API |
|
||||
| Base de données | MySQL (mysql2), better-sqlite3 |
|
||||
| API locale | Express.js |
|
||||
| PDF | jsPDF, jsPDF-AutoTable, pdf-lib, html2canvas |
|
||||
| Excel/CSV | xlsx, xlsx-populate, PapaParse, csv-parse |
|
||||
| Authentification | bcryptjs |
|
||||
| Graphiques | Chart.js, react-chartjs-2 |
|
||||
| QR Code | qrcode |
|
||||
| Logs | electron-log |
|
||||
|
||||
---
|
||||
|
||||
## Prérequis
|
||||
|
||||
- [Node.js](https://nodejs.org/) >= 18
|
||||
- [MySQL](https://www.mysql.com/) (serveur local actif)
|
||||
- npm >= 10
|
||||
|
||||
---
|
||||
|
||||
## Installation
|
||||
|
||||
### 1. Cloner le dépôt
|
||||
|
||||
```bash
|
||||
$ npm install
|
||||
git clone <url-du-repo>
|
||||
cd c-university
|
||||
```
|
||||
|
||||
### Development
|
||||
### 2. Installer les dépendances
|
||||
|
||||
```bash
|
||||
$ npm run dev
|
||||
npm install
|
||||
```
|
||||
|
||||
### Build
|
||||
### 3. Configurer la base de données
|
||||
|
||||
Assurez-vous que MySQL est en cours d'exécution. La configuration par défaut dans `database/database.js` est :
|
||||
|
||||
```
|
||||
Host : 127.0.0.1
|
||||
User : root
|
||||
Password : (vide)
|
||||
Database : university
|
||||
```
|
||||
|
||||
> Les tables sont créées automatiquement au premier lancement. Un compte **admin** par défaut est également créé.
|
||||
|
||||
---
|
||||
|
||||
## Lancement
|
||||
|
||||
### Mode développement
|
||||
|
||||
```bash
|
||||
# For windows
|
||||
$ npm run build:win
|
||||
|
||||
# For macOS
|
||||
$ npm run build:mac
|
||||
|
||||
# For Linux
|
||||
$ npm run build:linux
|
||||
npm run dev
|
||||
```
|
||||
|
||||
### Prévisualisation du build
|
||||
|
||||
```bash
|
||||
npm run start
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Build & Distribution
|
||||
|
||||
| Commande | Description |
|
||||
|----------|-------------|
|
||||
| `npm run build` | Build de l'application |
|
||||
| `npm run build:win` | Installateur Windows (.exe via NSIS) |
|
||||
| `npm run build:mac` | Package macOS |
|
||||
| `npm run build:linux` | Package Linux |
|
||||
| `npm run build:unpack` | Build sans package (dossier non compressé) |
|
||||
|
||||
---
|
||||
|
||||
## Compte par défaut
|
||||
|
||||
| Champ | Valeur |
|
||||
|-------|--------|
|
||||
| Utilisateur | `admin` |
|
||||
| Email | `admin@example.com` |
|
||||
| Mot de passe | `123456789` |
|
||||
|
||||
> Il est fortement recommandé de changer le mot de passe après le premier login.
|
||||
|
||||
---
|
||||
|
||||
## Structure du projet
|
||||
|
||||
```
|
||||
c-university/
|
||||
├── database/
|
||||
│ └── database.js # Connexion MySQL + création des tables
|
||||
├── src/
|
||||
│ ├── main/ # Processus principal Electron
|
||||
│ ├── preload/ # Scripts de préchargement
|
||||
│ └── renderer/src/ # Interface React
|
||||
│ ├── components/ # Composants de l'application
|
||||
│ ├── Routes/ # Configuration du routeur
|
||||
│ ├── contexts/ # Contextes React (Auth, Moyenne)
|
||||
│ ├── layouts/ # Layouts (Default, Login)
|
||||
│ └── assets/ # Images et ressources
|
||||
├── resources/ # Icônes et ressources Electron
|
||||
├── electron.vite.config.mjs # Configuration Electron-Vite
|
||||
├── electron-builder.yml # Configuration du packaging
|
||||
└── package.json
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Schéma de base de données (résumé)
|
||||
|
||||
| Table | Description |
|
||||
|-------|-------------|
|
||||
| `users` | Comptes utilisateurs avec rôles |
|
||||
| `etudiants` | Données complètes des étudiants |
|
||||
| `mentions` | Filières / mentions |
|
||||
| `parcours` | Parcours au sein d'une mention |
|
||||
| `niveaux` | Niveaux académiques |
|
||||
| `matieres` | Matières / UE |
|
||||
| `semestres` | Semestres |
|
||||
| `notes` | Notes des étudiants |
|
||||
| `notesrepech` | Notes de rattrapage |
|
||||
| `notesystems` | Seuils admis / redoublant / renvoyé |
|
||||
| `anneescolaire` | Années scolaires |
|
||||
| `trancheecolage` | Tranches de paiement de scolarité |
|
||||
| `matiereEnseignants` | Enseignants par matière |
|
||||
| `status` | Statuts étudiants (Nouveau, Passant, Redoublant…) |
|
||||
|
||||
---
|
||||
|
||||
## Version
|
||||
|
||||
**v4.1.0**
|
||||
|
||||
@ -110,10 +110,10 @@ async function setCurrent(id) {
|
||||
const sql = 'UPDATE anneescolaire SET is_current = 0 WHERE id > 0 AND is_current = 1'
|
||||
const sql2 = 'UPDATE anneescolaire SET is_current = 1 WHERE id = ?'
|
||||
|
||||
pool.query(sql)
|
||||
await pool.query(sql)
|
||||
|
||||
try {
|
||||
const [result] = pool.query(sql2, [id])
|
||||
const [result] = await pool.query(sql2, [id])
|
||||
console.log(result)
|
||||
|
||||
return {
|
||||
|
||||
62
database/Models/ConfigEcolage.js
Normal file
62
database/Models/ConfigEcolage.js
Normal file
@ -0,0 +1,62 @@
|
||||
const { pool } = require('../database')
|
||||
|
||||
async function getAllConfigEcolage() {
|
||||
const sql = `
|
||||
SELECT c.*, m.nom AS mention_nom, m.uniter AS mention_uniter, n.nom AS niveau_nom
|
||||
FROM configecolage c
|
||||
LEFT JOIN mentions m ON c.mention_id = m.id
|
||||
LEFT JOIN niveaus n ON c.niveau_id = n.id
|
||||
ORDER BY n.nom, m.nom
|
||||
`
|
||||
try {
|
||||
const [rows] = await pool.query(sql)
|
||||
return rows
|
||||
} catch (error) {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
async function getConfigEcolageByMentionNiveau(mention_id, niveau_nom) {
|
||||
const sql = `
|
||||
SELECT c.montant_total
|
||||
FROM configecolage c
|
||||
LEFT JOIN niveaus n ON c.niveau_id = n.id
|
||||
WHERE c.mention_id = ? AND n.nom = ?
|
||||
`
|
||||
try {
|
||||
const [rows] = await pool.query(sql, [mention_id, niveau_nom])
|
||||
return rows.length > 0 ? rows[0] : null
|
||||
} catch (error) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
async function upsertConfigEcolage(mention_id, niveau_id, montant_total) {
|
||||
const sql = `
|
||||
INSERT INTO configecolage (mention_id, niveau_id, montant_total)
|
||||
VALUES (?, ?, ?)
|
||||
ON DUPLICATE KEY UPDATE montant_total = ?
|
||||
`
|
||||
try {
|
||||
await pool.query(sql, [mention_id, niveau_id, montant_total, montant_total])
|
||||
return { success: true }
|
||||
} catch (error) {
|
||||
return { success: false, error: error.message }
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteConfigEcolage(id) {
|
||||
try {
|
||||
await pool.query('DELETE FROM configecolage WHERE id = ?', [id])
|
||||
return { success: true }
|
||||
} catch (error) {
|
||||
return { success: false, error: error.message }
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getAllConfigEcolage,
|
||||
getConfigEcolageByMentionNiveau,
|
||||
upsertConfigEcolage,
|
||||
deleteConfigEcolage
|
||||
}
|
||||
@ -1,7 +1,21 @@
|
||||
const { pool } = require('../database')
|
||||
|
||||
// Helper: get annee_scolaire_id from code string
|
||||
async function getAnneeScolaireId(conn, code) {
|
||||
const [rows] = await conn.query('SELECT id FROM anneescolaire WHERE code = ?', [code])
|
||||
return rows.length > 0 ? rows[0].id : null
|
||||
}
|
||||
|
||||
// Helper: SQL to get latest inscription fields per student
|
||||
const inscriptionJoin = `
|
||||
LEFT JOIN inscriptions i ON e.id = i.etudiant_id
|
||||
AND i.id = (SELECT MAX(ii.id) FROM inscriptions ii WHERE ii.etudiant_id = e.id)
|
||||
LEFT JOIN anneescolaire a ON i.annee_scolaire_id = a.id
|
||||
LEFT JOIN mentions m ON i.mention_id = m.id
|
||||
`
|
||||
|
||||
/**
|
||||
* function to insert etudiant into databases
|
||||
* Insert a new student + create their first inscription
|
||||
*/
|
||||
async function insertEtudiant(
|
||||
nom,
|
||||
@ -24,51 +38,65 @@ async function insertEtudiant(
|
||||
contact,
|
||||
parcours
|
||||
) {
|
||||
const sql =
|
||||
'INSERT INTO etudiants (nom, prenom, photos, date_de_naissances, niveau, annee_scolaire, status, mention_id, num_inscription, sexe, cin, date_delivrance, nationalite, annee_bacc, serie, boursier, domaine, contact, parcours) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'
|
||||
|
||||
const conn = await pool.getConnection()
|
||||
try {
|
||||
let [result] = await pool.query(sql, [
|
||||
nom,
|
||||
prenom,
|
||||
photos,
|
||||
date_de_naissances,
|
||||
niveau,
|
||||
annee_scolaire,
|
||||
status,
|
||||
mention_id,
|
||||
num_inscription,
|
||||
sexe,
|
||||
cin,
|
||||
date_delivrence,
|
||||
nationaliter,
|
||||
annee_bacc,
|
||||
serie,
|
||||
boursier,
|
||||
domaine,
|
||||
contact,
|
||||
parcours
|
||||
])
|
||||
await conn.beginTransaction()
|
||||
|
||||
return {
|
||||
success: true,
|
||||
id: result.insertId
|
||||
// 1. Insert permanent info into etudiants
|
||||
const [etudiantResult] = await conn.query(
|
||||
`INSERT INTO etudiants
|
||||
(nom, prenom, photos, date_de_naissances, num_inscription, sexe, cin,
|
||||
date_delivrance, nationalite, annee_bacc, serie, boursier, domaine, contact)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
[nom, prenom, photos, date_de_naissances, num_inscription, sexe, cin,
|
||||
date_delivrence, nationaliter, annee_bacc, serie, boursier, domaine, contact]
|
||||
)
|
||||
const etudiantId = etudiantResult.insertId
|
||||
|
||||
// 2. Get annee_scolaire_id
|
||||
const annee_scolaire_id = await getAnneeScolaireId(conn, annee_scolaire)
|
||||
if (!annee_scolaire_id) {
|
||||
await conn.rollback()
|
||||
return { success: false, message: 'Année scolaire introuvable: ' + annee_scolaire }
|
||||
}
|
||||
|
||||
// 3. Insert into inscriptions
|
||||
await conn.query(
|
||||
`INSERT INTO inscriptions
|
||||
(etudiant_id, annee_scolaire_id, niveau, mention_id, parcours, status, num_inscription)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
||||
[etudiantId, annee_scolaire_id, niveau, mention_id, parcours, status, num_inscription]
|
||||
)
|
||||
|
||||
await conn.commit()
|
||||
return { success: true, id: etudiantId }
|
||||
} catch (error) {
|
||||
await conn.rollback()
|
||||
return error
|
||||
} finally {
|
||||
conn.release()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* function to get all etudiants
|
||||
*
|
||||
* @returns JSON
|
||||
* Get all students filtered by a specific annee_scolaire
|
||||
*/
|
||||
async function getAllEtudiants() {
|
||||
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'
|
||||
async function getAllEtudiantsByAnnee(annee_scolaire) {
|
||||
const sql = `
|
||||
SELECT e.id, e.nom, e.prenom, e.photos, e.date_de_naissances, e.sexe, e.cin,
|
||||
e.date_delivrance, e.nationalite, e.annee_bacc, e.serie, e.boursier, e.domaine, e.contact,
|
||||
i.num_inscription, i.niveau, a.code AS annee_scolaire,
|
||||
CAST(i.status AS SIGNED) AS status,
|
||||
i.mention_id, i.parcours, i.id AS inscription_id,
|
||||
m.uniter AS mentionUnite, m.nom AS nomMention
|
||||
FROM etudiants e
|
||||
INNER JOIN inscriptions i ON e.id = i.etudiant_id
|
||||
INNER JOIN anneescolaire a ON i.annee_scolaire_id = a.id AND a.code = ?
|
||||
LEFT JOIN mentions m ON i.mention_id = m.id
|
||||
ORDER BY e.nom
|
||||
`
|
||||
try {
|
||||
let [rows] = await pool.query(sql)
|
||||
|
||||
const [rows] = await pool.query(sql, [annee_scolaire])
|
||||
return rows
|
||||
} catch (error) {
|
||||
return error
|
||||
@ -76,18 +104,51 @@ async function getAllEtudiants() {
|
||||
}
|
||||
|
||||
/**
|
||||
* function to return a single etudiant
|
||||
* and display it on the screen
|
||||
*
|
||||
* @param {int} id
|
||||
* @returns Promise
|
||||
* Get all students with ALL their inscriptions (une ligne par inscription)
|
||||
*/
|
||||
async function getAllEtudiants() {
|
||||
const sql = `
|
||||
SELECT e.id, e.nom, e.prenom, e.photos, e.date_de_naissances, e.sexe, e.cin,
|
||||
e.date_delivrance, e.nationalite, e.annee_bacc, e.serie, e.boursier, e.domaine, e.contact,
|
||||
i.num_inscription, i.id AS inscription_id,
|
||||
a.code AS annee_scolaire,
|
||||
/* Champs actuels depuis la dernière inscription */
|
||||
cur.niveau, CAST(cur.status AS SIGNED) AS status,
|
||||
cur.mention_id, cur.parcours,
|
||||
m_cur.uniter AS mentionUnite, m_cur.nom AS nomMention
|
||||
FROM etudiants e
|
||||
INNER JOIN inscriptions i ON e.id = i.etudiant_id
|
||||
LEFT JOIN anneescolaire a ON i.annee_scolaire_id = a.id
|
||||
/* Dernière inscription pour les champs actuels */
|
||||
LEFT JOIN inscriptions cur ON e.id = cur.etudiant_id
|
||||
AND cur.id = (SELECT MAX(ii.id) FROM inscriptions ii WHERE ii.etudiant_id = e.id)
|
||||
LEFT JOIN mentions m_cur ON cur.mention_id = m_cur.id
|
||||
ORDER BY e.nom, a.code DESC
|
||||
`
|
||||
try {
|
||||
const [rows] = await pool.query(sql)
|
||||
return rows
|
||||
} catch (error) {
|
||||
return error
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a single student with their latest inscription data
|
||||
*/
|
||||
async function getSingleEtudiant(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 = ?'
|
||||
|
||||
const sql = `
|
||||
SELECT e.*,
|
||||
i.num_inscription, i.niveau, a.code AS annee_scolaire,
|
||||
CAST(i.status AS SIGNED) AS status,
|
||||
i.mention_id, i.parcours,
|
||||
m.uniter AS mentionUnite, m.nom AS nomMention
|
||||
FROM etudiants e
|
||||
${inscriptionJoin}
|
||||
WHERE e.id = ?
|
||||
`
|
||||
try {
|
||||
const [rows] = await pool.query(sql, [id])
|
||||
|
||||
return rows[0]
|
||||
} catch (error) {
|
||||
return error
|
||||
@ -95,15 +156,25 @@ async function getSingleEtudiant(id) {
|
||||
}
|
||||
|
||||
/**
|
||||
* function to get all etudiants M2
|
||||
*
|
||||
* @returns JSON
|
||||
* Filter students by their latest inscription niveau
|
||||
*/
|
||||
async function FilterDataByNiveau(niveau) {
|
||||
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'
|
||||
const sql = `
|
||||
SELECT e.*,
|
||||
i.num_inscription, i.niveau, a.code AS annee_scolaire,
|
||||
CAST(i.status AS SIGNED) AS status,
|
||||
i.mention_id, i.parcours,
|
||||
m.uniter AS mentionUnite, m.nom AS nomMention
|
||||
FROM etudiants e
|
||||
INNER JOIN inscriptions i ON e.id = i.etudiant_id
|
||||
AND i.id = (SELECT MAX(ii.id) FROM inscriptions ii WHERE ii.etudiant_id = e.id)
|
||||
LEFT JOIN anneescolaire a ON i.annee_scolaire_id = a.id
|
||||
LEFT JOIN mentions m ON i.mention_id = m.id
|
||||
WHERE i.niveau = ?
|
||||
ORDER BY a.code DESC
|
||||
`
|
||||
try {
|
||||
let [rows] = await pool.query(sql, [niveau])
|
||||
|
||||
const [rows] = await pool.query(sql, [niveau])
|
||||
return rows
|
||||
} catch (error) {
|
||||
return error
|
||||
@ -111,18 +182,7 @@ async function FilterDataByNiveau(niveau) {
|
||||
}
|
||||
|
||||
/**
|
||||
* function to update etudiants
|
||||
*
|
||||
* @param {*} nom
|
||||
* @param {*} prenom
|
||||
* @param {*} photos
|
||||
* @param {*} date_de_naissances
|
||||
* @param {*} niveau
|
||||
* @param {*} annee_scolaire
|
||||
* @param {*} status
|
||||
* @param {*} num_inscription
|
||||
* @param {*} id
|
||||
* @returns promise
|
||||
* Update a student: permanent fields in etudiants, annual fields in latest inscription
|
||||
*/
|
||||
async function updateEtudiant(
|
||||
nom,
|
||||
@ -146,67 +206,88 @@ async function updateEtudiant(
|
||||
contact,
|
||||
parcours
|
||||
) {
|
||||
const sql =
|
||||
'UPDATE etudiants SET nom = ?, prenom = ?, photos = ?, date_de_naissances = ?, niveau = ?, annee_scolaire = ?, status = ?, mention_id = ?, num_inscription = ?, sexe = ?, cin = ?, date_delivrance = ?, nationalite = ?, annee_bacc = ?, serie = ?, boursier = ?, domaine = ?, contact = ?, parcours = ? WHERE id = ?'
|
||||
|
||||
const conn = await pool.getConnection()
|
||||
try {
|
||||
let [result] = await pool.query(sql, [
|
||||
nom,
|
||||
prenom,
|
||||
photos,
|
||||
date_de_naissances,
|
||||
niveau,
|
||||
annee_scolaire,
|
||||
status,
|
||||
mention_id,
|
||||
num_inscription,
|
||||
sexe,
|
||||
cin,
|
||||
date_delivrence,
|
||||
nationalite,
|
||||
annee_bacc,
|
||||
serie,
|
||||
boursier,
|
||||
domaine,
|
||||
contact,
|
||||
parcours,
|
||||
id
|
||||
])
|
||||
await conn.beginTransaction()
|
||||
|
||||
if (result.affectedRows === 0) {
|
||||
return {
|
||||
success: false,
|
||||
message: 'Année Univesitaire non trouvé.'
|
||||
// Update permanent fields (sans num_inscription car il est propre à chaque année)
|
||||
await conn.query(
|
||||
`UPDATE etudiants SET nom=?, prenom=?, photos=?, date_de_naissances=?,
|
||||
sexe=?, cin=?, date_delivrance=?, nationalite=?,
|
||||
annee_bacc=?, serie=?, boursier=?, domaine=?, contact=?
|
||||
WHERE id=?`,
|
||||
[nom, prenom, photos, date_de_naissances, sexe, cin,
|
||||
date_delivrence, nationalite, annee_bacc, serie, boursier, domaine, contact, id]
|
||||
)
|
||||
|
||||
// Get annee_scolaire_id
|
||||
const annee_scolaire_id = await getAnneeScolaireId(conn, annee_scolaire)
|
||||
|
||||
if (annee_scolaire_id) {
|
||||
// Chercher l'inscription de cette année spécifique
|
||||
const [insc] = await conn.query(
|
||||
'SELECT id, num_inscription FROM inscriptions WHERE etudiant_id = ? AND annee_scolaire_id = ?',
|
||||
[id, annee_scolaire_id]
|
||||
)
|
||||
if (insc.length > 0) {
|
||||
const oldNum = insc[0].num_inscription
|
||||
// Si le num_inscription a changé, sauvegarder l'ancien dans etudiants.num_inscription
|
||||
if (oldNum && oldNum !== num_inscription) {
|
||||
const [etudRow] = await conn.query(
|
||||
'SELECT num_inscription FROM etudiants WHERE id = ?', [id]
|
||||
)
|
||||
const existingNums = etudRow[0]?.num_inscription || ''
|
||||
// Ajouter l'ancien numéro s'il n'est pas déjà dans l'historique
|
||||
const allNums = existingNums ? existingNums.split(',').map(n => n.trim()) : []
|
||||
if (!allNums.includes(oldNum)) {
|
||||
allNums.push(oldNum)
|
||||
}
|
||||
// S'assurer que le nouveau est aussi dedans
|
||||
if (!allNums.includes(num_inscription)) {
|
||||
allNums.push(num_inscription)
|
||||
}
|
||||
await conn.query(
|
||||
'UPDATE etudiants SET num_inscription = ? WHERE id = ?',
|
||||
[allNums.join(','), id]
|
||||
)
|
||||
}
|
||||
await conn.query(
|
||||
`UPDATE inscriptions SET niveau=?, mention_id=?, parcours=?, status=?,
|
||||
num_inscription=? WHERE id=?`,
|
||||
[niveau, mention_id, parcours, status, num_inscription, insc[0].id]
|
||||
)
|
||||
} else {
|
||||
await conn.query(
|
||||
`INSERT INTO inscriptions
|
||||
(etudiant_id, annee_scolaire_id, niveau, mention_id, parcours, status, num_inscription)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
||||
[id, annee_scolaire_id, niveau, mention_id, parcours, status, num_inscription]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: 'Année Univesitaire supprimé avec succès.'
|
||||
}
|
||||
await conn.commit()
|
||||
return { success: true, message: 'Étudiant mis à jour avec succès.' }
|
||||
} catch (error) {
|
||||
await conn.rollback()
|
||||
return error
|
||||
} finally {
|
||||
conn.release()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* function to return the needed data in dashboard
|
||||
*
|
||||
* @returns promise
|
||||
* Get dashboard data
|
||||
*/
|
||||
async function getDataToDashboard() {
|
||||
const query = 'SELECT * FROM niveaus'
|
||||
const query2 = 'SELECT * FROM etudiants'
|
||||
const query3 = 'SELECT DISTINCT annee_scolaire FROM etudiants' // get all Année Univesitaire sans doublan
|
||||
const query3 = 'SELECT DISTINCT a.code AS annee_scolaire FROM inscriptions i JOIN anneescolaire a ON i.annee_scolaire_id = a.id'
|
||||
|
||||
try {
|
||||
let [rows] = await pool.query(query)
|
||||
let niveau = rows
|
||||
;[rows] = await pool.query(query2)
|
||||
let etudiants = rows
|
||||
;[rows] = await pool.query(query3)
|
||||
let anne_scolaire = rows
|
||||
|
||||
const [niveau] = await pool.query(query)
|
||||
const [etudiants] = await pool.query(query2)
|
||||
const [anne_scolaire] = await pool.query(query3)
|
||||
return { niveau, etudiants, anne_scolaire }
|
||||
} catch (error) {
|
||||
return error
|
||||
@ -215,170 +296,273 @@ async function getDataToDashboard() {
|
||||
|
||||
async function changePDP(photos, id) {
|
||||
const sql = 'UPDATE etudiants SET photos = ? WHERE id = ?'
|
||||
|
||||
try {
|
||||
let [result] = await pool.query(sql, [photos, id])
|
||||
|
||||
const [result] = await pool.query(sql, [photos, id])
|
||||
if (result.affectedRows === 0) {
|
||||
return {
|
||||
success: false,
|
||||
message: 'Année Univesitaire non trouvé.'
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: 'Année Univesitaire supprimé avec succès.'
|
||||
return { success: false, message: 'Étudiant non trouvé.' }
|
||||
}
|
||||
return { success: true, message: 'Photo mise à jour avec succès.' }
|
||||
} catch (error) {
|
||||
return error
|
||||
}
|
||||
}
|
||||
|
||||
async function updateParcours(parcours, id) {
|
||||
const sql = 'UPDATE etudiants SET parcours = ? WHERE id = ?'
|
||||
|
||||
// Update parcours in the latest inscription
|
||||
const sql = `
|
||||
UPDATE inscriptions SET parcours = ?
|
||||
WHERE etudiant_id = ? AND id = (SELECT MAX(ii.id) FROM (SELECT id FROM inscriptions WHERE etudiant_id = ?) ii)
|
||||
`
|
||||
try {
|
||||
let [result] = await pool.query(sql, [parcours, id])
|
||||
|
||||
const [result] = await pool.query(sql, [parcours, id, id])
|
||||
if (result.affectedRows === 0) {
|
||||
return {
|
||||
success: false,
|
||||
message: 'Année Univesitaire non trouvé.'
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: 'Année Univesitaire supprimé avec succès.'
|
||||
}
|
||||
} catch (error) {
|
||||
return error
|
||||
}
|
||||
}
|
||||
|
||||
async function createTranche(etudiant_id, tranchename, montant) {
|
||||
const sql = 'INSERT INTO trancheecolage (etudiant_id, tranchename, montant) VALUES (?, ?, ?)'
|
||||
|
||||
try {
|
||||
let [result] = await pool.query(sql, [etudiant_id, tranchename, montant])
|
||||
|
||||
return {
|
||||
success: true,
|
||||
id: result.insertId
|
||||
}
|
||||
} catch (error) {
|
||||
return error
|
||||
}
|
||||
}
|
||||
|
||||
async function getTranche(id) {
|
||||
const sql = 'SELECT * FROM trancheecolage WHERE etudiant_id = ?'
|
||||
|
||||
try {
|
||||
let [rows] = await pool.query(sql, [id])
|
||||
|
||||
return rows
|
||||
} catch (error) {
|
||||
return error
|
||||
}
|
||||
}
|
||||
|
||||
async function updateTranche(id, tranchename, montant) {
|
||||
const sql = 'UPDATE trancheecolage SET tranchename = ?, montant = ? WHERE id = ?'
|
||||
|
||||
try {
|
||||
const [result] = await pool.query(sql, [tranchename, montant, id])
|
||||
console.log('resultat tranche:',result);
|
||||
|
||||
if (result.affectedRows === 0) {
|
||||
return {
|
||||
success: false,
|
||||
message: 'Année Univesitaire non trouvé.'
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: 'Année Univesitaire supprimé avec succès.'
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('resultat error:',error);
|
||||
return error
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteTranche(id) {
|
||||
const sql = 'DELETE FROM trancheecolage WHERE id = ?'
|
||||
|
||||
try {
|
||||
let [result] = await pool.query(sql, [id])
|
||||
|
||||
if (result.affectedRows === 0) {
|
||||
return {
|
||||
success: false,
|
||||
message: 'Année Univesitaire non trouvé.'
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: 'Année Univesitaire supprimé avec succès.'
|
||||
return { success: false, message: 'Inscription non trouvée.' }
|
||||
}
|
||||
return { success: true, message: 'Parcours mis à jour avec succès.' }
|
||||
} catch (error) {
|
||||
return error
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteEtudiant(id) {
|
||||
console.log("id: ", id);
|
||||
const sql = 'DELETE FROM etudiants WHERE id = ?';
|
||||
|
||||
const conn = await pool.getConnection()
|
||||
try {
|
||||
let [result] = await pool.query(sql, [id]);
|
||||
console.log("Résultat DELETE:", result);
|
||||
await conn.beginTransaction()
|
||||
// Delete in correct order to respect FK constraints
|
||||
await conn.query('DELETE FROM notes WHERE etudiant_id = ?', [id])
|
||||
await conn.query('DELETE FROM notesrepech WHERE etudiant_id = ?', [id])
|
||||
await conn.query('DELETE FROM trancheecolage WHERE etudiant_id = ?', [id])
|
||||
await conn.query('DELETE FROM inscriptions WHERE etudiant_id = ?', [id])
|
||||
const [result] = await conn.query('DELETE FROM etudiants WHERE id = ?', [id])
|
||||
await conn.commit()
|
||||
|
||||
if (result.affectedRows === 0) {
|
||||
return {
|
||||
success: false,
|
||||
message: 'Etudiant non trouvée.'
|
||||
};
|
||||
return { success: false, message: 'Étudiant non trouvé.' }
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: 'Matière supprimée avec succès.'
|
||||
};
|
||||
return { success: true, message: 'Étudiant supprimé avec succès.' }
|
||||
} catch (error) {
|
||||
console.log("err: ",+ error)
|
||||
return { success: false, error: 'Erreur, veuillez réessayer: ' + error };
|
||||
|
||||
await conn.rollback()
|
||||
return { success: false, error: 'Erreur, veuillez réessayer: ' + error }
|
||||
} finally {
|
||||
conn.release()
|
||||
}
|
||||
}
|
||||
|
||||
async function getSingleTranche(id) {
|
||||
const sql = 'SELECT * FROM trancheecolage WHERE id = ?'
|
||||
/**
|
||||
* Payer une tranche (Tranche 1, Tranche 2, ou Complète)
|
||||
* 1 seule ligne par étudiant par année scolaire
|
||||
*/
|
||||
async function payerTranche(etudiant_id, annee_scolaire_id, type, montant, num_bordereau) {
|
||||
try {
|
||||
const [rows] = await pool.query(sql, [id])
|
||||
return rows[0]
|
||||
// Vérifier si une ligne existe déjà
|
||||
const [existing] = await pool.query(
|
||||
'SELECT * FROM trancheecolage WHERE etudiant_id = ? AND annee_scolaire_id = ?',
|
||||
[etudiant_id, annee_scolaire_id]
|
||||
)
|
||||
|
||||
if (existing.length === 0) {
|
||||
// Créer une nouvelle ligne
|
||||
let data = { tranche1_montant: 0, tranche1_bordereau: null, tranche2_montant: 0, tranche2_bordereau: null }
|
||||
|
||||
if (type === 'Tranche 1') {
|
||||
data.tranche1_montant = montant
|
||||
data.tranche1_bordereau = num_bordereau
|
||||
} else if (type === 'Tranche 2') {
|
||||
data.tranche2_montant = montant
|
||||
data.tranche2_bordereau = num_bordereau
|
||||
} else {
|
||||
// Tranche Complète : le montant saisi = total, on partage en 2
|
||||
const half = Math.round(Number(montant) / 2)
|
||||
data.tranche1_montant = half
|
||||
data.tranche1_bordereau = num_bordereau
|
||||
data.tranche2_montant = Number(montant) - half
|
||||
data.tranche2_bordereau = num_bordereau
|
||||
}
|
||||
|
||||
const is_paid = (data.tranche1_montant > 0 || data.tranche2_montant > 0) ? 1 : 0
|
||||
await pool.query(
|
||||
'INSERT INTO trancheecolage (etudiant_id, annee_scolaire_id, tranche1_montant, tranche1_bordereau, tranche2_montant, tranche2_bordereau, is_paid) VALUES (?, ?, ?, ?, ?, ?, ?)',
|
||||
[etudiant_id, annee_scolaire_id, data.tranche1_montant, data.tranche1_bordereau, data.tranche2_montant, data.tranche2_bordereau, is_paid]
|
||||
)
|
||||
return { success: true }
|
||||
} else {
|
||||
// Mettre à jour la ligne existante
|
||||
const row = existing[0]
|
||||
let updates = {}
|
||||
|
||||
if (type === 'Tranche 1') {
|
||||
updates.tranche1_montant = montant
|
||||
updates.tranche1_bordereau = num_bordereau
|
||||
} else if (type === 'Tranche 2') {
|
||||
updates.tranche2_montant = montant
|
||||
updates.tranche2_bordereau = num_bordereau
|
||||
} else {
|
||||
// Tranche Complète : le montant saisi = total, on partage en 2
|
||||
const half = Math.round(Number(montant) / 2)
|
||||
updates.tranche1_montant = half
|
||||
updates.tranche1_bordereau = num_bordereau
|
||||
updates.tranche2_montant = Number(montant) - half
|
||||
updates.tranche2_bordereau = num_bordereau
|
||||
}
|
||||
|
||||
const t1 = updates.tranche1_montant !== undefined ? updates.tranche1_montant : row.tranche1_montant
|
||||
const t2 = updates.tranche2_montant !== undefined ? updates.tranche2_montant : row.tranche2_montant
|
||||
const b1 = updates.tranche1_bordereau !== undefined ? updates.tranche1_bordereau : row.tranche1_bordereau
|
||||
const b2 = updates.tranche2_bordereau !== undefined ? updates.tranche2_bordereau : row.tranche2_bordereau
|
||||
const is_paid = (t1 > 0 || t2 > 0) ? 1 : 0
|
||||
|
||||
await pool.query(
|
||||
'UPDATE trancheecolage SET tranche1_montant = ?, tranche1_bordereau = ?, tranche2_montant = ?, tranche2_bordereau = ?, is_paid = ? WHERE id = ?',
|
||||
[t1, b1, t2, b2, is_paid, row.id]
|
||||
)
|
||||
return { success: true }
|
||||
}
|
||||
} catch (error) {
|
||||
return error
|
||||
return { success: false, error: error.message }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupérer la ligne de tranche d'un étudiant (toutes les années)
|
||||
*/
|
||||
async function getTranche(etudiant_id) {
|
||||
const sql = `
|
||||
SELECT t.*, a.code AS annee_scolaire_code
|
||||
FROM trancheecolage t
|
||||
LEFT JOIN anneescolaire a ON t.annee_scolaire_id = a.id
|
||||
WHERE t.etudiant_id = ?
|
||||
ORDER BY a.code DESC
|
||||
`
|
||||
try {
|
||||
const [rows] = await pool.query(sql, [etudiant_id])
|
||||
return rows
|
||||
} catch (error) {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Modifier une ligne de tranche (les 2 tranches d'un coup)
|
||||
*/
|
||||
async function updateTranche(id, tranche1_montant, tranche1_bordereau, tranche2_montant, tranche2_bordereau) {
|
||||
const t1 = Number(tranche1_montant) || 0
|
||||
const t2 = Number(tranche2_montant) || 0
|
||||
const is_paid = (t1 > 0 || t2 > 0) ? 1 : 0
|
||||
try {
|
||||
const [result] = await pool.query(
|
||||
'UPDATE trancheecolage SET tranche1_montant = ?, tranche1_bordereau = ?, tranche2_montant = ?, tranche2_bordereau = ?, is_paid = ? WHERE id = ?',
|
||||
[t1, tranche1_bordereau || null, t2, tranche2_bordereau || null, is_paid, id]
|
||||
)
|
||||
if (result.affectedRows === 0) return { success: false, message: 'Non trouve.' }
|
||||
return { success: true }
|
||||
} catch (error) {
|
||||
return { success: false, error: error.message }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Supprimer une ligne de tranche
|
||||
*/
|
||||
async function deleteTranche(id) {
|
||||
try {
|
||||
const [result] = await pool.query('DELETE FROM trancheecolage WHERE id = ?', [id])
|
||||
if (result.affectedRows === 0) return { success: false, message: 'Non trouve.' }
|
||||
return { success: true }
|
||||
} catch (error) {
|
||||
return { success: false, error: error.message }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Étudiants ayant payé au moins 1 tranche pour une année scolaire
|
||||
*/
|
||||
async function getEtudiantsWithPaidTranche(annee_scolaire_code) {
|
||||
const sql = `
|
||||
SELECT t.etudiant_id
|
||||
FROM trancheecolage t
|
||||
INNER JOIN anneescolaire a ON t.annee_scolaire_id = a.id
|
||||
WHERE a.code = ? AND t.is_paid = 1
|
||||
`
|
||||
try {
|
||||
const [rows] = await pool.query(sql, [annee_scolaire_code])
|
||||
return rows.map(r => r.etudiant_id)
|
||||
} catch (error) {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Réinscription d'un étudiant existant pour une nouvelle année scolaire.
|
||||
* Crée une NOUVELLE inscription sans modifier les inscriptions précédentes.
|
||||
*/
|
||||
async function reinscribeEtudiant(
|
||||
etudiant_id,
|
||||
niveau,
|
||||
annee_scolaire,
|
||||
status,
|
||||
num_inscription,
|
||||
mention_id,
|
||||
parcours
|
||||
) {
|
||||
const conn = await pool.getConnection()
|
||||
try {
|
||||
await conn.beginTransaction()
|
||||
|
||||
// 1. Get annee_scolaire_id
|
||||
const annee_scolaire_id = await getAnneeScolaireId(conn, annee_scolaire)
|
||||
if (!annee_scolaire_id) {
|
||||
await conn.rollback()
|
||||
return { success: false, message: 'Année scolaire introuvable: ' + annee_scolaire }
|
||||
}
|
||||
|
||||
// 2. Vérifier si une inscription existe déjà pour cet étudiant et cette année
|
||||
const [existing] = await conn.query(
|
||||
'SELECT id FROM inscriptions WHERE etudiant_id = ? AND annee_scolaire_id = ?',
|
||||
[etudiant_id, annee_scolaire_id]
|
||||
)
|
||||
|
||||
if (existing.length > 0) {
|
||||
// Même année scolaire : mettre à jour l'inscription existante
|
||||
await conn.query(
|
||||
`UPDATE inscriptions SET niveau=?, mention_id=?, parcours=?, status=?, num_inscription=?
|
||||
WHERE id=?`,
|
||||
[niveau, mention_id, parcours, status, num_inscription, existing[0].id]
|
||||
)
|
||||
} else {
|
||||
// Nouvelle année scolaire : créer une nouvelle inscription
|
||||
await conn.query(
|
||||
`INSERT INTO inscriptions
|
||||
(etudiant_id, annee_scolaire_id, niveau, mention_id, parcours, status, num_inscription)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
||||
[etudiant_id, annee_scolaire_id, niveau, mention_id, parcours, status, num_inscription]
|
||||
)
|
||||
}
|
||||
|
||||
await conn.commit()
|
||||
return { success: true, id: etudiant_id }
|
||||
} catch (error) {
|
||||
await conn.rollback()
|
||||
return { success: false, message: error.message }
|
||||
} finally {
|
||||
conn.release()
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
insertEtudiant,
|
||||
getAllEtudiants,
|
||||
getAllEtudiantsByAnnee,
|
||||
FilterDataByNiveau,
|
||||
getSingleEtudiant,
|
||||
updateEtudiant,
|
||||
getDataToDashboard,
|
||||
changePDP,
|
||||
updateParcours,
|
||||
createTranche,
|
||||
payerTranche,
|
||||
getTranche,
|
||||
updateTranche,
|
||||
deleteTranche,
|
||||
deleteEtudiant,
|
||||
getSingleTranche
|
||||
getEtudiantsWithPaidTranche,
|
||||
reinscribeEtudiant
|
||||
}
|
||||
|
||||
@ -64,17 +64,18 @@ async function getNoteOnline() {
|
||||
*
|
||||
* @returns promise
|
||||
*/
|
||||
async function getNoteRepech(id, niveau) {
|
||||
async function getNoteRepech(id, niveau, annee_scolaire) {
|
||||
const query = `
|
||||
SELECT notesrepech.*, matieres.*
|
||||
FROM notesrepech
|
||||
JOIN matieres ON notesrepech.matiere_id = matieres.id
|
||||
WHERE notesrepech.etudiant_id = ?
|
||||
AND notesrepech.etudiant_niveau = ?
|
||||
AND notesrepech.annee_scolaire = ?
|
||||
`
|
||||
|
||||
try {
|
||||
const [rows] = await pool.query(query, [id, niveau])
|
||||
const [rows] = await pool.query(query, [id, niveau, annee_scolaire])
|
||||
return rows
|
||||
} catch (error) {
|
||||
console.error('Error in getNoteRepech:', error)
|
||||
@ -140,14 +141,14 @@ async function showMoyenRepech(niveau, scolaire) {
|
||||
* @param {string} niveau - The student level
|
||||
* @returns {Promise} - Promise resolving to the database response or an error
|
||||
*/
|
||||
async function updateNoteRepech(formData, niveau, id) {
|
||||
async function updateNoteRepech(formData, niveau, id, annee_scolaire) {
|
||||
const matiere_ids = Object.keys(formData)
|
||||
const values = Object.values(formData)
|
||||
|
||||
const query = `
|
||||
UPDATE notesrepech
|
||||
SET note = ?
|
||||
WHERE etudiant_id = ? AND etudiant_niveau = ? AND matiere_id = ?
|
||||
WHERE etudiant_id = ? AND etudiant_niveau = ? AND matiere_id = ? AND annee_scolaire = ?
|
||||
`
|
||||
|
||||
try {
|
||||
@ -156,17 +157,13 @@ async function updateNoteRepech(formData, niveau, id) {
|
||||
for (let index = 0; index < matiere_ids.length; index++) {
|
||||
let data = values[index]
|
||||
|
||||
// Convert string number with comma to float, e.g. "12,5" => 12.5
|
||||
if (typeof data === 'string') {
|
||||
data = parseFloat(data.replace(',', '.'))
|
||||
} else {
|
||||
data = parseFloat(String(data).replace(',', '.'))
|
||||
}
|
||||
|
||||
// Optional: console log to verify conversion
|
||||
console.log(data)
|
||||
|
||||
const [result] = await pool.query(query, [data, id, niveau, matiere_ids[index]])
|
||||
const [result] = await pool.query(query, [data, id, niveau, matiere_ids[index], annee_scolaire])
|
||||
response = result
|
||||
}
|
||||
|
||||
@ -189,8 +186,8 @@ async function blockShowMoyeneRepech() {
|
||||
const query2 = `
|
||||
SELECT notesrepech.*,
|
||||
etudiants.id AS etudiantsId,
|
||||
etudiants.mention_id AS mentionId,
|
||||
etudiants.niveau,
|
||||
notesrepech.mention_id AS mentionId,
|
||||
notesrepech.etudiant_niveau AS niveau,
|
||||
matieres.*
|
||||
FROM notesrepech
|
||||
INNER JOIN etudiants ON notesrepech.etudiant_id = etudiants.id
|
||||
|
||||
@ -88,34 +88,19 @@ async function getNoteOnline() {
|
||||
*
|
||||
* @returns promise
|
||||
*/
|
||||
async function getNote(id, niveau) {
|
||||
async function getNote(id, niveau, annee_scolaire) {
|
||||
let connection
|
||||
try {
|
||||
connection = await pool.getConnection()
|
||||
|
||||
// 1. Get all notes joined with matieres
|
||||
const [response] = await connection.execute(
|
||||
`
|
||||
SELECT notes.*, matieres.*
|
||||
FROM notes
|
||||
JOIN matieres ON notes.matiere_id = matieres.id
|
||||
WHERE notes.etudiant_id = ? AND notes.etudiant_niveau = ?
|
||||
`,
|
||||
[id, niveau]
|
||||
)
|
||||
|
||||
// 2. Optional: Build list of matiere_id (not used here but kept from original)
|
||||
const arrayResponseIdMatiere = response.map((note) => note.matiere_id)
|
||||
|
||||
// 3. Same query again (as in your original) — this is redundant unless changed
|
||||
const [response2] = await connection.execute(
|
||||
`
|
||||
SELECT notes.*, matieres.*
|
||||
FROM notes
|
||||
JOIN matieres ON notes.matiere_id = matieres.id
|
||||
WHERE notes.etudiant_id = ? AND notes.etudiant_niveau = ?
|
||||
WHERE notes.etudiant_id = ? AND notes.etudiant_niveau = ? AND notes.annee_scolaire = ?
|
||||
`,
|
||||
[id, niveau]
|
||||
[id, niveau, annee_scolaire]
|
||||
)
|
||||
|
||||
return response2
|
||||
@ -164,18 +149,18 @@ async function showMoyen(niveau, scolaire) {
|
||||
|
||||
// 2. Prepare the second query
|
||||
const query2 = `
|
||||
SELECT notes.*, etudiants.*, matieres.id AS matiere_id, matieres.nom AS nomMat, matieres.credit
|
||||
SELECT notes.*, etudiants.*, matieres.id AS matiere_id, matieres.nom AS nomMat, matieres.credit, matieres.ue
|
||||
FROM notes
|
||||
INNER JOIN etudiants ON notes.etudiant_id = etudiants.id
|
||||
INNER JOIN matieres ON notes.matiere_id = matieres.id
|
||||
WHERE notes.etudiant_id = ?
|
||||
WHERE notes.etudiant_id = ? AND notes.annee_scolaire = ?
|
||||
`
|
||||
|
||||
// 3. Loop over each student and fetch their notes
|
||||
for (let index = 0; index < etudiantWithNotes.length; index++) {
|
||||
const etudiantId = etudiantWithNotes[index].etudiant_id
|
||||
const [rows] = await pool.query(query2, [etudiantId])
|
||||
allEtudiantWithNotes.push(rows) // push just the rows, not [rows, fields]
|
||||
const [rows] = await pool.query(query2, [etudiantId, scolaire])
|
||||
allEtudiantWithNotes.push(rows)
|
||||
}
|
||||
|
||||
return allEtudiantWithNotes
|
||||
@ -207,10 +192,10 @@ async function updateNote(formData, niveau, id, mention_id, annee_scolaire) {
|
||||
data = parseFloat(String(data).replace(',', '.'))
|
||||
}
|
||||
|
||||
// 1. Check if already in notesrepech
|
||||
// 1. Check if already in notesrepech for this year
|
||||
const [check] = await pool.query(
|
||||
'SELECT * FROM notesrepech WHERE etudiant_id = ? AND matiere_id = ? AND etudiant_niveau = ?',
|
||||
[id, matiere_id[index], niveau]
|
||||
'SELECT * FROM notesrepech WHERE etudiant_id = ? AND matiere_id = ? AND etudiant_niveau = ? AND annee_scolaire = ?',
|
||||
[id, matiere_id[index], niveau, annee_scolaire]
|
||||
)
|
||||
|
||||
if (data < 10) {
|
||||
@ -223,22 +208,22 @@ async function updateNote(formData, niveau, id, mention_id, annee_scolaire) {
|
||||
)
|
||||
}
|
||||
|
||||
// 3. Update main note anyway
|
||||
// 3. Update main note for the correct year
|
||||
;[response] = await pool.query(
|
||||
'UPDATE notes SET note = ? WHERE etudiant_id = ? AND etudiant_niveau = ? AND matiere_id = ?',
|
||||
[data, id, niveau, matiere_id[index]]
|
||||
'UPDATE notes SET note = ? WHERE etudiant_id = ? AND etudiant_niveau = ? AND matiere_id = ? AND annee_scolaire = ?',
|
||||
[data, id, niveau, matiere_id[index], annee_scolaire]
|
||||
)
|
||||
} else {
|
||||
// 4. Remove from notesrepech if note >= 10
|
||||
await pool.query(
|
||||
'DELETE FROM notesrepech WHERE etudiant_id = ? AND etudiant_niveau = ? AND matiere_id = ?',
|
||||
[id, niveau, matiere_id[index]]
|
||||
'DELETE FROM notesrepech WHERE etudiant_id = ? AND etudiant_niveau = ? AND matiere_id = ? AND annee_scolaire = ?',
|
||||
[id, niveau, matiere_id[index], annee_scolaire]
|
||||
)
|
||||
|
||||
// 5. Update main note
|
||||
// 5. Update main note for the correct year
|
||||
;[response] = await pool.query(
|
||||
'UPDATE notes SET note = ? WHERE etudiant_id = ? AND etudiant_niveau = ? AND matiere_id = ?',
|
||||
[data, id, niveau, matiere_id[index]]
|
||||
'UPDATE notes SET note = ? WHERE etudiant_id = ? AND etudiant_niveau = ? AND matiere_id = ? AND annee_scolaire = ?',
|
||||
[data, id, niveau, matiere_id[index], annee_scolaire]
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -258,8 +243,8 @@ async function blockShowMoyene() {
|
||||
SELECT
|
||||
notes.*,
|
||||
etudiants.id AS etudiantsId,
|
||||
etudiants.mention_id AS mentionId,
|
||||
etudiants.niveau,
|
||||
notes.mention_id AS mentionId,
|
||||
notes.etudiant_niveau AS niveau,
|
||||
matieres.*
|
||||
FROM notes
|
||||
INNER JOIN etudiants ON notes.etudiant_id = etudiants.id
|
||||
|
||||
@ -176,8 +176,11 @@ async function extractFiche(matiere_id) {
|
||||
for (let index = 0; index < newResponse.length; index++) {
|
||||
const [students] = await connection.query(
|
||||
`
|
||||
SELECT * FROM etudiants
|
||||
WHERE niveau LIKE ? AND annee_scolaire LIKE ?
|
||||
SELECT e.*, i.niveau, i.mention_id, i.parcours, i.status, a.code AS annee_scolaire
|
||||
FROM etudiants e
|
||||
INNER JOIN inscriptions i ON e.id = i.etudiant_id
|
||||
INNER JOIN anneescolaire a ON i.annee_scolaire_id = a.id
|
||||
WHERE i.niveau LIKE ? AND a.code LIKE ?
|
||||
`,
|
||||
[`%${newResponse[index]}%`, `%${now}%`]
|
||||
)
|
||||
|
||||
@ -65,13 +65,9 @@ async function createTables() {
|
||||
prenom VARCHAR(250) DEFAULT NULL,
|
||||
photos TEXT DEFAULT NULL,
|
||||
date_de_naissances DATE DEFAULT NULL,
|
||||
niveau VARCHAR(250) NOT NULL,
|
||||
annee_scolaire VARCHAR(20) NOT NULL,
|
||||
status INT DEFAULT NULL,
|
||||
mention_id INT NOT NULL,
|
||||
num_inscription TEXT UNIQUE NOT NULL,
|
||||
num_inscription TEXT DEFAULT NULL,
|
||||
sexe VARCHAR(20) DEFAULT NULL,
|
||||
cin VARCHAR(250) DEFAULT NULL,
|
||||
cin TEXT DEFAULT NULL,
|
||||
date_delivrance TEXT DEFAULT NULL,
|
||||
nationalite VARCHAR(250) DEFAULT NULL,
|
||||
annee_bacc TEXT DEFAULT NULL,
|
||||
@ -79,11 +75,8 @@ async function createTables() {
|
||||
boursier VARCHAR(20) DEFAULT NULL,
|
||||
domaine VARCHAR(250) DEFAULT NULL,
|
||||
contact VARCHAR(20) DEFAULT NULL,
|
||||
parcours VARCHAR(250) DEFAULT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (status) REFERENCES status(id),
|
||||
FOREIGN KEY (mention_id) REFERENCES mentions(id)
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB;
|
||||
`)
|
||||
|
||||
@ -188,6 +181,23 @@ async function createTables() {
|
||||
) ENGINE=InnoDB;
|
||||
`)
|
||||
|
||||
await connection.query(`
|
||||
CREATE TABLE IF NOT EXISTS inscriptions (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
etudiant_id INT NOT NULL,
|
||||
annee_scolaire_id INT NOT NULL,
|
||||
niveau VARCHAR(10) NOT NULL,
|
||||
mention_id INT DEFAULT NULL,
|
||||
parcours VARCHAR(50) DEFAULT NULL,
|
||||
status VARCHAR(20) DEFAULT NULL,
|
||||
num_inscription VARCHAR(50) DEFAULT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (etudiant_id) REFERENCES etudiants(id),
|
||||
FOREIGN KEY (annee_scolaire_id) REFERENCES anneescolaire(id),
|
||||
UNIQUE KEY unique_num_inscription_annee (num_inscription, annee_scolaire_id)
|
||||
) ENGINE=InnoDB;
|
||||
`)
|
||||
|
||||
await connection.query(`
|
||||
CREATE TABLE IF NOT EXISTS traitmentsystem (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
@ -248,8 +258,27 @@ async function createTables() {
|
||||
CREATE TABLE IF NOT EXISTS trancheecolage (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
etudiant_id INT NOT NULL,
|
||||
tranchename VARCHAR(255) NOT NULL,
|
||||
montant DOUBLE NOT NULL
|
||||
annee_scolaire_id INT NOT NULL,
|
||||
tranche1_montant DOUBLE DEFAULT 0,
|
||||
tranche1_bordereau VARCHAR(255) DEFAULT NULL,
|
||||
tranche2_montant DOUBLE DEFAULT 0,
|
||||
tranche2_bordereau VARCHAR(255) DEFAULT NULL,
|
||||
is_paid TINYINT(1) DEFAULT 0,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
UNIQUE KEY unique_etudiant_annee (etudiant_id, annee_scolaire_id)
|
||||
) ENGINE=InnoDB;
|
||||
`)
|
||||
|
||||
await connection.query(`
|
||||
CREATE TABLE IF NOT EXISTS configecolage (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
mention_id INT NOT NULL,
|
||||
niveau_id INT NOT NULL,
|
||||
montant_total DOUBLE NOT NULL DEFAULT 0,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
UNIQUE KEY unique_mention_niveau (mention_id, niveau_id)
|
||||
) ENGINE=InnoDB;
|
||||
`)
|
||||
|
||||
|
||||
@ -68,159 +68,161 @@ async function updateStudents() {
|
||||
const connection = await pool.getConnection()
|
||||
try {
|
||||
await connection.beginTransaction()
|
||||
const today = dayjs().format('YYYY-MM-DD')
|
||||
|
||||
// Get unfinished years (only one record assumed)
|
||||
const [unfinishedYearsRows] = await connection.query(
|
||||
'SELECT * FROM traitmentsystem WHERE is_finished = 0 ORDER BY id ASC LIMIT 1'
|
||||
// 1. Récupérer toutes les années scolaires triées par debut
|
||||
const [allYears] = await connection.query(
|
||||
'SELECT * FROM anneescolaire ORDER BY debut ASC'
|
||||
)
|
||||
if (unfinishedYearsRows.length === 0) {
|
||||
await connection.release()
|
||||
return { message: 'No unfinished years found.' }
|
||||
}
|
||||
const unfinishedYear = unfinishedYearsRows[0]
|
||||
|
||||
// Get all students of that unfinished year
|
||||
const [allEtudiants] = await connection.query(
|
||||
'SELECT * FROM etudiants WHERE annee_scolaire = ?',
|
||||
[unfinishedYear.code]
|
||||
)
|
||||
|
||||
// Get distinct student IDs with notes
|
||||
let etudiantWithNotes = []
|
||||
for (const etudiant of allEtudiants) {
|
||||
const [results] = await connection.query(
|
||||
'SELECT DISTINCT etudiant_id FROM notes WHERE etudiant_niveau = ? AND annee_scolaire = ?',
|
||||
[etudiant.niveau, etudiant.annee_scolaire]
|
||||
)
|
||||
etudiantWithNotes.push(...results)
|
||||
}
|
||||
// Unique IDs
|
||||
const uniqueId = [
|
||||
...new Map(etudiantWithNotes.map((item) => [item.etudiant_id, item])).values()
|
||||
]
|
||||
|
||||
// Get notes details per student
|
||||
let allEtudiantWithNotes = []
|
||||
for (const student of uniqueId) {
|
||||
const [rows] = await connection.query(
|
||||
`SELECT notes.*, etudiants.*, matieres.id, matieres.nom AS nomMat, matieres.credit
|
||||
FROM notes
|
||||
LEFT JOIN etudiants ON notes.etudiant_id = etudiants.id
|
||||
LEFT JOIN matieres ON notes.matiere_id = matieres.id
|
||||
WHERE notes.etudiant_id = ?`,
|
||||
[student.etudiant_id]
|
||||
)
|
||||
allEtudiantWithNotes.push(rows)
|
||||
if (allYears.length < 2) {
|
||||
await connection.rollback()
|
||||
connection.release()
|
||||
return { message: 'Pas assez d\'années scolaires configurées.' }
|
||||
}
|
||||
|
||||
// Get distinct student IDs with notesrepech
|
||||
let etudiantWithNotesRepech = []
|
||||
for (const etudiant of allEtudiants) {
|
||||
const [results] = await connection.query(
|
||||
'SELECT DISTINCT etudiant_id FROM notesrepech WHERE etudiant_niveau = ? AND annee_scolaire = ?',
|
||||
[etudiant.niveau, etudiant.annee_scolaire]
|
||||
)
|
||||
etudiantWithNotesRepech.push(...results)
|
||||
}
|
||||
// Unique IDs for repech
|
||||
const uniqueIdRepech = [
|
||||
...new Map(etudiantWithNotesRepech.map((item) => [item.etudiant_id, item])).values()
|
||||
]
|
||||
|
||||
// Get notesrepech details per student
|
||||
let allEtudiantWithNotesRepech = []
|
||||
for (const student of uniqueIdRepech) {
|
||||
const [rows] = await connection.query(
|
||||
`SELECT notesrepech.*, etudiants.*, matieres.id, matieres.nom AS nomMat, matieres.credit
|
||||
FROM notesrepech
|
||||
INNER JOIN etudiants ON notesrepech.etudiant_id = etudiants.id
|
||||
INNER JOIN matieres ON notesrepech.matiere_id = matieres.id
|
||||
WHERE notesrepech.etudiant_id = ?`,
|
||||
[student.etudiant_id]
|
||||
)
|
||||
allEtudiantWithNotesRepech.push(rows)
|
||||
}
|
||||
|
||||
// Compute averages and prepare data
|
||||
let dataToMap = []
|
||||
for (let i = 0; i < allEtudiantWithNotes.length; i++) {
|
||||
const notesNormal = allEtudiantWithNotes[i]
|
||||
const notesRepech = allEtudiantWithNotesRepech[i] || []
|
||||
|
||||
let total = 0
|
||||
let totalCredit = 0
|
||||
let modelJson = {
|
||||
id: '',
|
||||
nom: '',
|
||||
prenom: '',
|
||||
photos: '',
|
||||
moyenne: '',
|
||||
mention: '',
|
||||
niveau: '',
|
||||
annee_scolaire: ''
|
||||
}
|
||||
|
||||
for (let j = 0; j < notesNormal.length; j++) {
|
||||
const normalNote = notesNormal[j]
|
||||
modelJson.id = normalNote.etudiant_id
|
||||
modelJson.nom = normalNote.nom
|
||||
modelJson.prenom = normalNote.prenom
|
||||
modelJson.photos = normalNote.photos
|
||||
modelJson.mention = normalNote.mention_id
|
||||
modelJson.niveau = normalNote.niveau
|
||||
modelJson.annee_scolaire = normalNote.annee_scolaire
|
||||
|
||||
// Find repech note for same matiere if exists
|
||||
const repechNoteObj = notesRepech.find((r) => r.matiere_id === normalNote.matiere_id)
|
||||
const noteToUse = compareSessionNotes(normalNote.note, checkNull(repechNoteObj))
|
||||
|
||||
total += (noteToUse ?? 0) * normalNote.credit
|
||||
totalCredit += normalNote.credit
|
||||
}
|
||||
|
||||
modelJson.moyenne = (totalCredit > 0 ? total / totalCredit : 0).toFixed(2)
|
||||
dataToMap.push(modelJson)
|
||||
}
|
||||
|
||||
// Get note system thresholds
|
||||
// 2. Seuils de notes
|
||||
const [noteSystemRows] = await connection.query('SELECT * FROM notesystems LIMIT 1')
|
||||
const noteSystem = noteSystemRows[0]
|
||||
|
||||
// Update etudiants based on moyenne
|
||||
for (const student of dataToMap) {
|
||||
let status, newNiveau, newAnnee
|
||||
newAnnee = updateSchoolYear(student.annee_scolaire)
|
||||
let totalProcessed = 0
|
||||
|
||||
if (student.moyenne >= noteSystem.admis) {
|
||||
newNiveau = nextLevel(student.niveau)
|
||||
status = 2 // Passed
|
||||
} else if (student.moyenne >= noteSystem.redouble) {
|
||||
newNiveau = student.niveau
|
||||
status = 3 // Repeat
|
||||
} else {
|
||||
newNiveau = student.niveau
|
||||
status = 4 // Fail
|
||||
}
|
||||
// 3. Pour chaque paire d'années consécutives (annéeN → annéeN+1)
|
||||
for (let i = 0; i < allYears.length - 1; i++) {
|
||||
const prevYear = allYears[i]
|
||||
const nextYear = allYears[i + 1]
|
||||
|
||||
await connection.query(
|
||||
'UPDATE etudiants SET niveau = ?, annee_scolaire = ?, status = ? WHERE id = ?',
|
||||
[newNiveau, newAnnee, status, student.id]
|
||||
// Traiter uniquement si l'année suivante a déjà commencé
|
||||
if (nextYear.debut > today) continue
|
||||
|
||||
// Trouver les étudiants inscrits en annéeN mais PAS encore en annéeN+1
|
||||
const [studentsToProcess] = await connection.query(
|
||||
`SELECT i.*, e.nom, e.prenom, e.photos
|
||||
FROM inscriptions i
|
||||
JOIN etudiants e ON i.etudiant_id = e.id
|
||||
WHERE i.annee_scolaire_id = ?
|
||||
AND i.etudiant_id NOT IN (
|
||||
SELECT etudiant_id FROM inscriptions WHERE annee_scolaire_id = ?
|
||||
)`,
|
||||
[prevYear.id, nextYear.id]
|
||||
)
|
||||
|
||||
if (studentsToProcess.length === 0) continue
|
||||
|
||||
for (const inscription of studentsToProcess) {
|
||||
const inscStatus = parseInt(inscription.status)
|
||||
|
||||
// Renvoyé → pas de nouvelle inscription
|
||||
if (inscStatus === 4) continue
|
||||
|
||||
let newNiveau, newStatus
|
||||
|
||||
// Vérifier si l'étudiant a des notes pour l'année précédente
|
||||
const [notesRows] = await connection.query(
|
||||
`SELECT n.note, n.matiere_id, m.credit
|
||||
FROM notes n
|
||||
JOIN matieres m ON n.matiere_id = m.id
|
||||
WHERE n.etudiant_id = ? AND n.annee_scolaire = ?`,
|
||||
[inscription.etudiant_id, prevYear.code]
|
||||
)
|
||||
|
||||
if (notesRows.length > 0) {
|
||||
// Calculer la moyenne avec prise en compte du rattrapage
|
||||
const [repechRows] = await connection.query(
|
||||
`SELECT n.note, n.matiere_id, m.credit
|
||||
FROM notesrepech n
|
||||
JOIN matieres m ON n.matiere_id = m.id
|
||||
WHERE n.etudiant_id = ? AND n.annee_scolaire = ?`,
|
||||
[inscription.etudiant_id, prevYear.code]
|
||||
)
|
||||
|
||||
let total = 0
|
||||
let totalCredit = 0
|
||||
for (const note of notesRows) {
|
||||
const repNote = repechRows.find(r => r.matiere_id === note.matiere_id)
|
||||
const noteToUse = compareSessionNotes(note.note, checkNull(repNote))
|
||||
total += (noteToUse ?? 0) * note.credit
|
||||
totalCredit += note.credit
|
||||
}
|
||||
const moyenne = totalCredit > 0 ? total / totalCredit : 0
|
||||
|
||||
if (moyenne >= noteSystem.admis) {
|
||||
newNiveau = nextLevel(inscription.niveau)
|
||||
newStatus = 2 // Passant
|
||||
} else if (moyenne >= noteSystem.renvoyer) {
|
||||
newNiveau = inscription.niveau
|
||||
newStatus = 3 // Redoublant
|
||||
} else {
|
||||
newNiveau = inscription.niveau
|
||||
newStatus = 4 // Renvoyé
|
||||
continue // Pas de nouvelle inscription pour les renvoyés
|
||||
}
|
||||
} else {
|
||||
// Pas de notes → utiliser le statut de l'inscription
|
||||
if (inscStatus === 2) {
|
||||
// Passant → niveau supérieur
|
||||
newNiveau = nextLevel(inscription.niveau)
|
||||
newStatus = 2
|
||||
} else {
|
||||
// Redoublant, Nouveau, Ancien → même niveau
|
||||
newNiveau = inscription.niveau
|
||||
newStatus = 3
|
||||
}
|
||||
}
|
||||
|
||||
// Créer l'inscription pour l'année suivante
|
||||
await connection.query(
|
||||
`INSERT INTO inscriptions
|
||||
(etudiant_id, annee_scolaire_id, niveau, mention_id, parcours, status, num_inscription)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
||||
[
|
||||
inscription.etudiant_id, nextYear.id, newNiveau,
|
||||
inscription.mention_id, inscription.parcours, newStatus,
|
||||
inscription.num_inscription
|
||||
]
|
||||
)
|
||||
|
||||
// Pour les redoublants, copier les notes de l'année précédente (seulement si pas encore copiées)
|
||||
if (newStatus === 3) {
|
||||
const [existingNotes] = await connection.query(
|
||||
`SELECT COUNT(*) as cnt FROM notes WHERE etudiant_id = ? AND annee_scolaire = ?`,
|
||||
[inscription.etudiant_id, nextYear.code]
|
||||
)
|
||||
if (existingNotes[0].cnt === 0) {
|
||||
await connection.query(
|
||||
`INSERT INTO notes (etudiant_id, matiere_id, etudiant_niveau, mention_id, note, annee_scolaire)
|
||||
SELECT etudiant_id, matiere_id, etudiant_niveau, mention_id, note, ?
|
||||
FROM notes
|
||||
WHERE etudiant_id = ? AND annee_scolaire = ?`,
|
||||
[nextYear.code, inscription.etudiant_id, prevYear.code]
|
||||
)
|
||||
await connection.query(
|
||||
`INSERT INTO notesrepech (etudiant_id, matiere_id, etudiant_niveau, mention_id, note, annee_scolaire)
|
||||
SELECT etudiant_id, matiere_id, etudiant_niveau, mention_id, note, ?
|
||||
FROM notesrepech
|
||||
WHERE etudiant_id = ? AND annee_scolaire = ?`,
|
||||
[nextYear.code, inscription.etudiant_id, prevYear.code]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
totalProcessed++
|
||||
}
|
||||
}
|
||||
|
||||
// Mark unfinished year as finished
|
||||
await connection.query('UPDATE traitmentsystem SET is_finished = 1 WHERE id = ?', [
|
||||
unfinishedYear.id
|
||||
])
|
||||
// 4. Mettre à jour traitmentsystem (nettoyage)
|
||||
await connection.query(
|
||||
'UPDATE traitmentsystem SET is_finished = 1 WHERE is_finished = 0'
|
||||
)
|
||||
|
||||
await connection.commit()
|
||||
connection.release()
|
||||
|
||||
return { success: true, message: 'Students updated successfully' }
|
||||
console.log(`✅ updateStudents: ${totalProcessed} inscription(s) créée(s).`)
|
||||
return { success: true, message: `${totalProcessed} inscription(s) créée(s).`, totalProcessed }
|
||||
} catch (error) {
|
||||
await connection.rollback()
|
||||
connection.release()
|
||||
console.error(error)
|
||||
console.error('❌ updateStudents error:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,6 +9,12 @@ const customParseFormat = require('dayjs/plugin/customParseFormat');
|
||||
dayjs.extend(customParseFormat);
|
||||
|
||||
// ---------- Fonctions utilitaires ----------
|
||||
function nextLevel(niveau) {
|
||||
const levels = ['L1', 'L2', 'L3', 'M1', 'M2', 'D1', 'D2', 'D3', 'PHD']
|
||||
const idx = levels.indexOf(niveau)
|
||||
return idx === -1 || idx === levels.length - 1 ? niveau : levels[idx + 1]
|
||||
}
|
||||
|
||||
function fixEncoding(str) {
|
||||
if (typeof str !== 'string') return str;
|
||||
return str
|
||||
@ -62,13 +68,8 @@ function convertToISODate(input) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Vérifie année bissextile
|
||||
function isLeapYear(year) {
|
||||
return (year % 4 === 0 && year % 100 !== 0) || (year % 400 === 0);
|
||||
}
|
||||
|
||||
// ---------- UPDATE étudiant ----------
|
||||
async function updateEtudiant(row) {
|
||||
// ---------- UPDATE étudiant existant ----------
|
||||
async function updateEtudiant(row, conn) {
|
||||
const fields = [];
|
||||
const params = [];
|
||||
|
||||
@ -79,13 +80,10 @@ async function updateEtudiant(row) {
|
||||
}
|
||||
}
|
||||
|
||||
// Only permanent fields in etudiants
|
||||
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));
|
||||
@ -99,7 +97,6 @@ async function updateEtudiant(row) {
|
||||
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];
|
||||
@ -109,49 +106,44 @@ async function updateEtudiant(row) {
|
||||
}
|
||||
|
||||
try {
|
||||
const [result] = await pool.query(sql, [...params, ...whereParams]);
|
||||
return { success: true, affectedRows: result.affectedRows };
|
||||
} catch (error) {
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
}
|
||||
const [result] = await conn.query(sql, [...params, ...whereParams]);
|
||||
|
||||
// ---------- INSERT multiple étudiants ----------
|
||||
async function insertMultipleEtudiants(etudiants) {
|
||||
if (!etudiants || etudiants.length === 0) return { success: true, affectedRows: 0 };
|
||||
// Update or create inscription for this year
|
||||
if (result.affectedRows > 0) {
|
||||
// Get the etudiant id
|
||||
let etudiantId;
|
||||
if (row.cin && row.cin.toString().trim() !== '') {
|
||||
const [et] = await conn.query('SELECT id FROM etudiants WHERE cin = ?', [row.cin]);
|
||||
if (et.length > 0) etudiantId = et[0].id;
|
||||
} else {
|
||||
const [et] = await conn.query(
|
||||
'SELECT id FROM etudiants WHERE LOWER(TRIM(nom)) = ? AND LOWER(TRIM(prenom)) = ?',
|
||||
[row.nom.toLowerCase().trim(), row.prenom.toLowerCase().trim()]
|
||||
);
|
||||
if (et.length > 0) etudiantId = et[0].id;
|
||||
}
|
||||
|
||||
const sql = `
|
||||
INSERT INTO etudiants (
|
||||
nom, prenom, photos, date_de_naissances, niveau, annee_scolaire, status,
|
||||
mention_id, num_inscription, sexe, cin, date_delivrance, nationalite,
|
||||
annee_bacc, serie, boursier, domaine, contact, parcours
|
||||
) VALUES ?
|
||||
`;
|
||||
if (etudiantId && row.annee_scolaire_id) {
|
||||
// Check if inscription exists for this year
|
||||
const [existing] = await conn.query(
|
||||
'SELECT id FROM inscriptions WHERE etudiant_id = ? AND annee_scolaire_id = ?',
|
||||
[etudiantId, row.annee_scolaire_id]
|
||||
);
|
||||
if (existing.length > 0) {
|
||||
await conn.query(
|
||||
`UPDATE inscriptions SET niveau=?, mention_id=?, status=?, num_inscription=? WHERE id=?`,
|
||||
[row.niveau, row.mention, row.code_redoublement, row.num_inscription?.toString(), existing[0].id]
|
||||
);
|
||||
} else {
|
||||
await conn.query(
|
||||
`INSERT INTO inscriptions (etudiant_id, annee_scolaire_id, niveau, mention_id, status, num_inscription)
|
||||
VALUES (?, ?, ?, ?, ?, ?)`,
|
||||
[etudiantId, row.annee_scolaire_id, row.niveau, row.mention, row.code_redoublement, row.num_inscription?.toString()]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const values = etudiants.map(row => [
|
||||
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,
|
||||
row.cin || null,
|
||||
convertToISODate(row.date_de_delivrance),
|
||||
row.nationaliter,
|
||||
parseInt(row.annee_baccalaureat, 10),
|
||||
row.serie,
|
||||
row.boursier,
|
||||
fixEncoding(row.domaine),
|
||||
row.contact,
|
||||
null
|
||||
]);
|
||||
|
||||
try {
|
||||
const [result] = await pool.query(sql, [values]);
|
||||
return { success: true, affectedRows: result.affectedRows };
|
||||
} catch (error) {
|
||||
return { success: false, error: error.message };
|
||||
@ -188,53 +180,115 @@ async function importFileToDatabase(filePath) {
|
||||
}
|
||||
}
|
||||
|
||||
const [mentionRows] = await pool.query('SELECT * FROM mentions');
|
||||
const [statusRows] = await pool.query('SELECT * FROM status');
|
||||
const conn = await pool.getConnection();
|
||||
try {
|
||||
await conn.beginTransaction();
|
||||
|
||||
const etudiantsToInsert = [];
|
||||
const doublons = [];
|
||||
const [mentionRows] = await conn.query('SELECT * FROM mentions');
|
||||
const [statusRows] = await conn.query('SELECT * FROM status');
|
||||
|
||||
for (const row of records) {
|
||||
row.date_naissance = convertToISODate(row.date_naissance);
|
||||
const etudiantsToInsert = [];
|
||||
const doublons = [];
|
||||
|
||||
// Mapping mention
|
||||
const matchedMention = mentionRows.find(
|
||||
m => m.nom.toUpperCase() === row.mention.toUpperCase() || m.uniter.toUpperCase() === row.mention.toUpperCase()
|
||||
);
|
||||
if (matchedMention) row.mention = matchedMention.id;
|
||||
for (const row of records) {
|
||||
row.date_naissance = convertToISODate(row.date_naissance);
|
||||
|
||||
// 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;
|
||||
// Mapping mention
|
||||
const matchedMention = mentionRows.find(
|
||||
m => m.nom.toUpperCase() === row.mention.toUpperCase() || m.uniter.toUpperCase() === row.mention.toUpperCase()
|
||||
);
|
||||
if (matchedMention) row.mention = matchedMention.id;
|
||||
|
||||
// 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()]
|
||||
// Mapping status
|
||||
const codeLettre = (row.code_redoublement ? row.code_redoublement.trim().substring(0, 1) : 'N');
|
||||
row.code_redoublement = codeLettre;
|
||||
const statusMatch = statusRows.find(s => s.nom.toLowerCase().startsWith(row.code_redoublement.toLowerCase()));
|
||||
if (statusMatch) row.code_redoublement = statusMatch.id;
|
||||
|
||||
// Auto-progression du niveau selon le statut
|
||||
// Passant (id=2) → niveau supérieur | Redoublant/Nouveau/Renvoyé/Ancien → niveau inchangé
|
||||
if (row.code_redoublement === 2) {
|
||||
row.niveau = nextLevel(row.niveau)
|
||||
}
|
||||
|
||||
// Get annee_scolaire_id
|
||||
const [anneeRows] = await conn.query('SELECT id FROM anneescolaire WHERE code = ?', [row.annee_scolaire]);
|
||||
if (anneeRows.length === 0) {
|
||||
await conn.rollback();
|
||||
return { error: true, message: `Année scolaire '${row.annee_scolaire}' introuvable dans la base de données.` };
|
||||
}
|
||||
row.annee_scolaire_id = anneeRows[0].id;
|
||||
|
||||
// Détection doublons
|
||||
let existing;
|
||||
if (row.cin && row.cin.toString().trim() !== '') {
|
||||
[existing] = await conn.query('SELECT id FROM etudiants WHERE cin = ?', [row.cin]);
|
||||
} else {
|
||||
[existing] = await conn.query(
|
||||
'SELECT id 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 });
|
||||
const updateResult = await updateEtudiant(row, conn);
|
||||
if (!updateResult.success) {
|
||||
await conn.rollback();
|
||||
return { error: true, message: `Erreur update ${row.nom} ${row.prenom}: ${updateResult.error}` };
|
||||
}
|
||||
} else {
|
||||
etudiantsToInsert.push(row);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('✅ Nouveaux à insérer :', etudiantsToInsert.map(e => e.nom + ' ' + e.prenom));
|
||||
console.log('🔄 Étudiants mis à jour :', doublons.map(e => e.nom + ' ' + e.prenom));
|
||||
|
||||
// Insert new students
|
||||
for (const row of etudiantsToInsert) {
|
||||
const [etResult] = await conn.query(
|
||||
`INSERT INTO etudiants
|
||||
(nom, prenom, photos, date_de_naissances, num_inscription, sexe, cin,
|
||||
date_delivrance, nationalite, annee_bacc, serie, boursier, domaine, contact)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
[
|
||||
row.nom,
|
||||
row.prenom,
|
||||
getCompressedDefaultImage(),
|
||||
convertToISODate(row.date_naissance),
|
||||
row.num_inscription.toString(),
|
||||
row.sexe,
|
||||
row.cin || null,
|
||||
convertToISODate(row.date_de_delivrance),
|
||||
row.nationaliter,
|
||||
parseInt(row.annee_baccalaureat, 10),
|
||||
row.serie,
|
||||
row.boursier,
|
||||
fixEncoding(row.domaine),
|
||||
row.contact
|
||||
]
|
||||
);
|
||||
|
||||
// Create inscription
|
||||
await conn.query(
|
||||
`INSERT INTO inscriptions (etudiant_id, annee_scolaire_id, niveau, mention_id, status, num_inscription)
|
||||
VALUES (?, ?, ?, ?, ?, ?)`,
|
||||
[etResult.insertId, row.annee_scolaire_id, row.niveau, row.mention, row.code_redoublement, row.num_inscription.toString()]
|
||||
);
|
||||
}
|
||||
|
||||
if (existing.length > 0) {
|
||||
doublons.push({ nom: row.nom, prenom: row.prenom, cin: row.cin });
|
||||
const updateResult = await updateEtudiant(row);
|
||||
if (!updateResult.success) return { error: true, message: `Erreur update ${row.nom} ${row.prenom}: ${updateResult.error}` };
|
||||
} else {
|
||||
etudiantsToInsert.push(row);
|
||||
}
|
||||
await conn.commit();
|
||||
return {
|
||||
error: false,
|
||||
message: `Importation réussie. ${etudiantsToInsert.length} nouvel(le)(s) inséré(s), ${doublons.length} mis à jour.`
|
||||
};
|
||||
} catch (error) {
|
||||
await conn.rollback();
|
||||
return { error: true, message: error.message };
|
||||
} finally {
|
||||
conn.release();
|
||||
}
|
||||
|
||||
console.log('✅ Nouveaux à insérer :', etudiantsToInsert.map(e => e.nom + ' ' + e.prenom));
|
||||
console.log('🔄 Étudiants mis à jour :', doublons.map(e => e.nom + ' ' + e.prenom));
|
||||
|
||||
const insertResult = await insertMultipleEtudiants(etudiantsToInsert);
|
||||
if (!insertResult.success) return { error: true, message: insertResult.error };
|
||||
|
||||
return { error: false, message: `Importation réussie. ${etudiantsToInsert.length} nouvel(le)(s) inséré(s), ${doublons.length} mis à jour.` };
|
||||
}
|
||||
|
||||
module.exports = { importFileToDatabase };
|
||||
|
||||
184
resume_modifications.txt
Normal file
184
resume_modifications.txt
Normal file
@ -0,0 +1,184 @@
|
||||
============================================================
|
||||
RÉSUMÉ DES MODIFICATIONS - C-UNIVERSITY
|
||||
============================================================
|
||||
|
||||
1. BASE DE DONNÉES / SCHÉMA (database/database.js)
|
||||
------------------------------------------------------------
|
||||
- Refonte de la table "etudiants" : les champs annuels (niveau, année scolaire, status, mention, parcours) ont été retirés
|
||||
- Nouvelle table "inscriptions" : stocke chaque inscription annuelle d'un étudiant (niveau, mention, parcours, status, num_inscription)
|
||||
- Nouvelle table "configecolage" : configure le montant d'écolage par mention/niveau
|
||||
- Restructuration de "trancheecolage" : passe à 2 tranches par ligne (tranche1_montant, tranche1_bordereau, tranche2_montant, tranche2_bordereau, is_paid)
|
||||
|
||||
|
||||
2. MODELS
|
||||
------------------------------------------------------------
|
||||
|
||||
database/Models/Etudiants.js
|
||||
- Refonte complète : toutes les fonctions utilisent maintenant des transactions et la table "inscriptions"
|
||||
- Nouvelles fonctions : reinscribeEtudiant, payerTranche, getEtudiantsWithPaidTranche, getAllEtudiantsByAnnee
|
||||
- insertEtudiant : insère dans "etudiants" puis crée une inscription dans "inscriptions" (transaction)
|
||||
- updateEtudiant : met à jour les données permanentes + cherche/crée l'inscription de l'année
|
||||
- deleteEtudiant : suppression en cascade (notes, notesrepech, trancheecolage, inscriptions, etudiants)
|
||||
- payerTranche (remplace createTranche) : gère Tranche 1, Tranche 2, Tranche Complète avec bordereau
|
||||
- getTranche : jointure avec anneescolaire, tri par année décroissante
|
||||
- updateTranche : signature étendue à 4 paramètres (tranche1_montant, tranche1_bordereau, tranche2_montant, tranche2_bordereau)
|
||||
|
||||
database/Models/AnneeScolaire.js
|
||||
- Correction : ajout des "await" manquants dans setCurrent (bug critique de condition de course)
|
||||
|
||||
database/Models/Notes.js
|
||||
- Ajout du filtre par annee_scolaire dans toutes les requêtes (getNote, showMoyen, updateNote, blockShowMoyene)
|
||||
- blockShowMoyene : utilise notes.mention_id et notes.etudiant_niveau au lieu des champs supprimés de etudiants
|
||||
|
||||
database/Models/NoteRepechage.js
|
||||
- Ajout du filtre par annee_scolaire dans getNoteRepech, updateNoteRepech, blockShowMoyenRepech
|
||||
|
||||
database/Models/Parcours.js
|
||||
- extractFiche : jointure avec inscriptions et anneescolaire au lieu de champs directs sur etudiants
|
||||
|
||||
database/Models/ConfigEcolage.js (NOUVEAU)
|
||||
- getAllConfigEcolage() : liste toutes les configurations avec noms de mention et niveau
|
||||
- getConfigEcolageByMentionNiveau() : retourne le montant pour une combinaison mention/niveau
|
||||
- upsertConfigEcolage() : INSERT ou UPDATE selon existence
|
||||
- deleteConfigEcolage() : suppression d'une configuration
|
||||
|
||||
|
||||
3. BACKEND (main process / preload)
|
||||
------------------------------------------------------------
|
||||
|
||||
src/main/index.js
|
||||
- Nouveaux handlers IPC : getAllConfigEcolage, getConfigEcolageByMentionNiveau, upsertConfigEcolage, deleteConfigEcolage
|
||||
- Remplacement de createTranche par payerTranche
|
||||
- Ajout de getEtudiantsWithPaidTranche et reinscribeEtudiant
|
||||
- Handlers de notes/rattrapage reçoivent maintenant annee_scolaire en paramètre
|
||||
- Démarrage : updateCurrentYears() et updateStudents() enveloppés dans async/await avec gestion d'erreur
|
||||
|
||||
src/preload/index.js
|
||||
- Exposition de getEtudiantsByAnnee sur window.etudiants
|
||||
- Remplacement de createTranche par payerTranche
|
||||
- Remplacement de getSingleTranche par getEtudiantsWithPaidTranche et reinscribeEtudiant
|
||||
- Nouvel objet window.configecolage avec 4 méthodes (getAll, getByMentionNiveau, upsert, delete)
|
||||
|
||||
|
||||
4. FRONTEND - COMPOSANTS MODIFIÉS
|
||||
------------------------------------------------------------
|
||||
|
||||
src/renderer/src/Routes/Routes.jsx
|
||||
- Ajout de la route /configecolage vers le composant ConfigEcolage
|
||||
|
||||
src/renderer/src/components/AddNotes.jsx
|
||||
- Navigation retour transmet selectedAnnee depuis localStorage
|
||||
|
||||
src/renderer/src/components/AddStudent.jsx (refonte majeure)
|
||||
- Ajout barre de recherche d'étudiant existant (par nom, prénom ou numéro d'inscription)
|
||||
- Réinscription : si étudiant existant sélectionné, appel reinscribeEtudiant au lieu de insertEtudiant
|
||||
- Section paiement écolage intégrée (type de paiement, bordereau, montant, montant restant)
|
||||
- Chargement automatique du montant configuré selon mention/niveau
|
||||
|
||||
src/renderer/src/components/AjoutTranche.jsx
|
||||
- Formulaire repensé : sélection année scolaire, type de paiement (Tranche 1/2/Complète), numéro de bordereau
|
||||
- Appel payerTranche au lieu de createTranche
|
||||
|
||||
src/renderer/src/components/AnneeScolaire.jsx
|
||||
- Fonction setCurrent rétablie (était commentée)
|
||||
|
||||
src/renderer/src/components/Home.jsx
|
||||
- Chargement par 3 appels séparés au lieu de getDataToDashboards
|
||||
- Filtre par année via getEtudiantsByAnnee
|
||||
|
||||
src/renderer/src/components/Noteclasse.jsx (refonte calcul)
|
||||
- Nouvelle fonction calculerMoyennePonderee centralisée
|
||||
- Gestion correcte du rattrapage (0 = pas de rattrapage, prend la meilleure note si rattrapage > 0)
|
||||
- Remplacement de l'état "session" par "moyennesCalculees"
|
||||
|
||||
src/renderer/src/components/ReleverNotes.jsx
|
||||
- PDF amélioré : format A4 (210mm x 297mm), redimensionnement automatique
|
||||
- Calcul de moyenne pondérée corrigé avec rattrapage
|
||||
- Décision du jury basée sur le seuil du système de notes (noteSysteme.admis)
|
||||
- Correction noterepech : null si pas de rattrapage (au lieu de la note normale)
|
||||
|
||||
src/renderer/src/components/Resultat.jsx
|
||||
- Ajout des moyennes avec rattrapage (moyennesRattrapage)
|
||||
- calculerMoyennePonderee dupliquée localement
|
||||
- Correction extractMatieresAndUEs : utilise nomMat et Map
|
||||
|
||||
src/renderer/src/components/SingleNotes.jsx
|
||||
- Transmission de annee_scolaire aux appels de notes et notes de rattrapage
|
||||
|
||||
src/renderer/src/components/Student.jsx
|
||||
- Adapté au nouveau modèle avec inscriptions
|
||||
|
||||
src/renderer/src/components/TrancheEcolage.jsx
|
||||
- Adaptation au nouveau format 2 tranches avec bordereau
|
||||
|
||||
src/renderer/src/components/UpdateTranche.jsx
|
||||
- Adaptation au nouveau format (tranche1_montant, tranche1_bordereau, tranche2_montant, tranche2_bordereau)
|
||||
|
||||
src/renderer/src/components/Sidenav.jsx
|
||||
- Ajout du lien vers la page Config Ecolage
|
||||
|
||||
src/renderer/src/components/function/FonctionRelever.js
|
||||
- Récupération du système de notes intégrée dans les calculs du relevé
|
||||
|
||||
|
||||
5. FRONTEND - NOUVEAUX COMPOSANTS
|
||||
------------------------------------------------------------
|
||||
|
||||
src/renderer/src/components/ConfigEcolage.jsx (NOUVEAU)
|
||||
- Interface d'administration des montants d'écolage par mention et par niveau
|
||||
- Formulaire : sélection mention, niveau, saisie montant total
|
||||
- Tableau : affiche Mention, Niveau, Montant total, Tranche 1 (moitié), Tranche 2 (moitié)
|
||||
- Actions : modifier (pré-remplit le formulaire) et supprimer
|
||||
|
||||
src/renderer/src/components/Ecolage.jsx (NOUVEAU)
|
||||
- Tableau de suivi des paiements (DataGrid MUI)
|
||||
- Filtres par année scolaire et par niveau
|
||||
- Colonnes : Nom, Prénom, N° inscription, Niveau, Mention, Année, Statut, Contact, Tranches payées, Photo
|
||||
- Indicateur visuel : vert si tranche payée, rouge sinon
|
||||
|
||||
|
||||
6. SYSTÈME D'IMPORT (database/import/Etudiants.js)
|
||||
------------------------------------------------------------
|
||||
- Import enveloppé dans une transaction complète
|
||||
- Insertions une par une dans "etudiants" + "inscriptions" (au lieu de batch INSERT)
|
||||
- Résolution de annee_scolaire_id depuis le code d'année
|
||||
- Auto-progression du niveau pour les passants (L1->L2->...->M2->D1->...->PHD)
|
||||
- Rollback automatique en cas d'erreur
|
||||
|
||||
|
||||
7. SYSTÈME DE TRAITEMENT (database/function/System.js)
|
||||
------------------------------------------------------------
|
||||
- Refonte de updateStudents :
|
||||
* Récupère toutes les années scolaires triées par date
|
||||
* Pour chaque paire d'années consécutives, traite les étudiants non encore inscrits en année N+1
|
||||
* Calcule la moyenne avec rattrapage pour déterminer le statut
|
||||
* Crée une nouvelle inscription pour l'année suivante
|
||||
* Copie les notes pour les redoublants
|
||||
* Les étudiants renvoyés (status=4) ne reçoivent pas de nouvelle inscription
|
||||
|
||||
|
||||
8. AUTRES
|
||||
------------------------------------------------------------
|
||||
|
||||
README.md
|
||||
- Mise à jour de la documentation
|
||||
|
||||
|
||||
============================================================
|
||||
SYNTHÈSE GLOBALE
|
||||
============================================================
|
||||
|
||||
Ces modifications représentent une REFONTE ARCHITECTURALE MAJEURE avec 2 axes :
|
||||
|
||||
1. NORMALISATION DE LA BASE DE DONNÉES
|
||||
Séparation des données permanentes d'un étudiant et de ses inscriptions
|
||||
annuelles dans deux tables distinctes, permettant de gérer l'historique
|
||||
complet sur plusieurs années sans dupliquer les données personnelles.
|
||||
|
||||
2. MODULE ÉCOLAGE COMPLET
|
||||
Configuration des montants par mention/niveau, paiement par tranche
|
||||
avec numéro de bordereau, suivi intégré, et paiement possible dès
|
||||
l'inscription d'un étudiant.
|
||||
|
||||
Statistiques : 26 fichiers modifiés, 3 nouveaux fichiers
|
||||
+2755 lignes ajoutées, -1926 lignes supprimées
|
||||
@ -22,11 +22,12 @@ const {
|
||||
changePDP,
|
||||
deleteEtudiant,
|
||||
updateParcours,
|
||||
createTranche,
|
||||
payerTranche,
|
||||
getTranche,
|
||||
updateTranche,
|
||||
deleteTranche,
|
||||
getSingleTranche
|
||||
getEtudiantsWithPaidTranche,
|
||||
reinscribeEtudiant
|
||||
} = require('../../database/Models/Etudiants')
|
||||
const {
|
||||
insertNiveau,
|
||||
@ -100,8 +101,14 @@ const {
|
||||
// Declare mainWindow and tray in the global scope
|
||||
let mainWindow
|
||||
let tray = null
|
||||
updateCurrentYears()
|
||||
updateStudents()
|
||||
;(async () => {
|
||||
try {
|
||||
await updateCurrentYears()
|
||||
await updateStudents()
|
||||
} catch (error) {
|
||||
console.error('❌ Startup system error:', error)
|
||||
}
|
||||
})()
|
||||
|
||||
autoUpdater.setFeedURL({
|
||||
provider: 'generic',
|
||||
@ -368,6 +375,31 @@ ipcMain.handle('insertEtudiant', async (event, credentials) => {
|
||||
return insert
|
||||
})
|
||||
|
||||
// event for reinscription (new inscription for existing student)
|
||||
ipcMain.handle('reinscribeEtudiant', async (event, credentials) => {
|
||||
const {
|
||||
etudiant_id,
|
||||
niveau,
|
||||
annee_scolaire,
|
||||
status,
|
||||
num_inscription,
|
||||
mention_id,
|
||||
parcours
|
||||
} = credentials
|
||||
|
||||
const result = await reinscribeEtudiant(
|
||||
etudiant_id,
|
||||
niveau,
|
||||
annee_scolaire,
|
||||
status,
|
||||
num_inscription,
|
||||
mention_id,
|
||||
parcours
|
||||
)
|
||||
|
||||
return result
|
||||
})
|
||||
|
||||
// event for fetching single
|
||||
ipcMain.handle('getByNiveau', async (event, credentials) => {
|
||||
const { niveau } = credentials
|
||||
@ -472,18 +504,18 @@ ipcMain.handle('insertNote', async (event, credentials) => {
|
||||
|
||||
// event for get single note
|
||||
ipcMain.handle('getSingleNote', async (event, credentials) => {
|
||||
const { id, niveau, mention_id } = credentials
|
||||
const { id, niveau, mention_id, annee_scolaire } = credentials
|
||||
|
||||
const get = await getNote(id, niveau, mention_id)
|
||||
const get = await getNote(id, niveau, annee_scolaire)
|
||||
|
||||
return get
|
||||
})
|
||||
|
||||
// event for get single note repech
|
||||
ipcMain.handle('getNotesRepech', async (event, credentials) => {
|
||||
const { id, niveau, mention_id } = credentials
|
||||
const { id, niveau, mention_id, annee_scolaire } = credentials
|
||||
|
||||
const get = await getNoteRepech(id, niveau, mention_id)
|
||||
const get = await getNoteRepech(id, niveau, annee_scolaire)
|
||||
|
||||
return get
|
||||
})
|
||||
@ -499,9 +531,9 @@ ipcMain.handle('updatetNote', async (event, credentials) => {
|
||||
|
||||
// event for updating note repech
|
||||
ipcMain.handle('updatetNoteRepech', async (event, credentials) => {
|
||||
const { formData2, niveau, id } = credentials
|
||||
const { formData2, niveau, id, annee_scolaire } = credentials
|
||||
|
||||
const update = await updateNoteRepech(formData2, niveau, id)
|
||||
const update = await updateNoteRepech(formData2, niveau, id, annee_scolaire)
|
||||
|
||||
return update
|
||||
})
|
||||
@ -846,58 +878,61 @@ ipcMain.handle('changeParcours', async (event, credentials) => {
|
||||
return get
|
||||
})
|
||||
|
||||
ipcMain.handle('createTranche', async (event, credentials) => {
|
||||
const { etudiant_id, tranchename, montant } = credentials
|
||||
// console.log(formData, id);
|
||||
const get = createTranche(etudiant_id, tranchename, montant)
|
||||
|
||||
return get
|
||||
ipcMain.handle('payerTranche', async (event, credentials) => {
|
||||
const { etudiant_id, annee_scolaire_id, type, montant, num_bordereau } = credentials
|
||||
return await payerTranche(etudiant_id, annee_scolaire_id, type, montant, num_bordereau)
|
||||
})
|
||||
|
||||
ipcMain.handle('getTranche', async (event, credentials) => {
|
||||
const { id } = credentials
|
||||
// console.log(formData, id);
|
||||
const get = getTranche(id)
|
||||
|
||||
return get
|
||||
return await getTranche(id)
|
||||
})
|
||||
|
||||
ipcMain.handle('updateTranche', async (event, credentials) => {
|
||||
const { id, tranchename, montant } = credentials
|
||||
// console.log(formData, id);
|
||||
const get = updateTranche(id, tranchename, montant)
|
||||
|
||||
return get
|
||||
const { id, tranche1_montant, tranche1_bordereau, tranche2_montant, tranche2_bordereau } = credentials
|
||||
return await updateTranche(id, tranche1_montant, tranche1_bordereau, tranche2_montant, tranche2_bordereau)
|
||||
})
|
||||
|
||||
ipcMain.handle('deleteTranche', async (event, credentials) => {
|
||||
const { id } = credentials
|
||||
console.log(id)
|
||||
const get = deleteTranche(id)
|
||||
|
||||
return get
|
||||
return await deleteTranche(id)
|
||||
})
|
||||
|
||||
ipcMain.handle('getSingleTranche', async (event, credentials) => {
|
||||
const { id } = credentials
|
||||
// console.log(formData, id);
|
||||
const get = getSingleTranche(id)
|
||||
ipcMain.handle('getEtudiantsWithPaidTranche', async (event, credentials) => {
|
||||
const { annee_scolaire } = credentials
|
||||
return await getEtudiantsWithPaidTranche(annee_scolaire)
|
||||
})
|
||||
|
||||
return get
|
||||
// --- Config Ecolage ---
|
||||
const { getAllConfigEcolage, getConfigEcolageByMentionNiveau, upsertConfigEcolage, deleteConfigEcolage } = require('../../database/Models/ConfigEcolage')
|
||||
|
||||
ipcMain.handle('getAllConfigEcolage', async () => {
|
||||
return await getAllConfigEcolage()
|
||||
})
|
||||
|
||||
ipcMain.handle('getConfigEcolageByMentionNiveau', async (event, credentials) => {
|
||||
const { mention_id, niveau_nom } = credentials
|
||||
return await getConfigEcolageByMentionNiveau(mention_id, niveau_nom)
|
||||
})
|
||||
|
||||
ipcMain.handle('upsertConfigEcolage', async (event, credentials) => {
|
||||
const { mention_id, niveau_id, montant_total } = credentials
|
||||
return await upsertConfigEcolage(mention_id, niveau_id, montant_total)
|
||||
})
|
||||
|
||||
ipcMain.handle('deleteConfigEcolage', async (event, credentials) => {
|
||||
const { id } = credentials
|
||||
return await deleteConfigEcolage(id)
|
||||
})
|
||||
|
||||
ipcMain.handle('createIPConfig', async (event, credentials) => {
|
||||
const { ipname } = credentials
|
||||
// console.log(formData, id);
|
||||
const get = createConfigIp(ipname)
|
||||
|
||||
return get
|
||||
})
|
||||
|
||||
ipcMain.handle('updateIPConfig', async (event, credentials) => {
|
||||
const { id, ipname } = credentials
|
||||
// console.log(formData, id);
|
||||
const get = updateIPConfig(id, ipname)
|
||||
|
||||
return get
|
||||
})
|
||||
|
||||
@ -2,7 +2,7 @@ import { contextBridge, ipcRenderer } from 'electron'
|
||||
import { electronAPI } from '@electron-toolkit/preload'
|
||||
const { getNessesarytable } = require('../../database/function/System')
|
||||
const { getAllUsers } = require('../../database/Models/Users')
|
||||
const { getAllEtudiants, getDataToDashboard } = require('../../database/Models/Etudiants')
|
||||
const { getAllEtudiants, getAllEtudiantsByAnnee, getDataToDashboard } = require('../../database/Models/Etudiants')
|
||||
const { verifyEtudiantIfHeHasNotes, blockShowMoyene } = require('../../database/Models/Notes')
|
||||
const { getMatiere, getSemestre, getEnseignants } = require('../../database/Models/Matieres')
|
||||
const { getSysteme } = require('../../database/Models/NoteSysrem')
|
||||
@ -71,6 +71,7 @@ if (process.contextIsolated) {
|
||||
contextBridge.exposeInMainWorld('etudiants', {
|
||||
insertEtudiant: (credentials) => ipcRenderer.invoke('insertEtudiant', credentials),
|
||||
getEtudiants: () => getAllEtudiants(),
|
||||
getEtudiantsByAnnee: (annee) => getAllEtudiantsByAnnee(annee),
|
||||
FilterDataByNiveau: (credential) => ipcRenderer.invoke('getByNiveau', credential),
|
||||
getSingle: (credential) => ipcRenderer.invoke('single', credential),
|
||||
updateEtudiants: (credentials) => ipcRenderer.invoke('updateETudiants', credentials),
|
||||
@ -78,12 +79,13 @@ if (process.contextIsolated) {
|
||||
updateEtudiantsPDP: (credentials) => ipcRenderer.invoke('updateETudiantsPDP', credentials),
|
||||
importExcel: (credentials) => ipcRenderer.invoke('importexcel', credentials),
|
||||
changeParcours: (credentials) => ipcRenderer.invoke('changeParcours', credentials),
|
||||
createTranche: (credentials) => ipcRenderer.invoke('createTranche', credentials),
|
||||
payerTranche: (credentials) => ipcRenderer.invoke('payerTranche', credentials),
|
||||
getTranche: (credentials) => ipcRenderer.invoke('getTranche', credentials),
|
||||
updateTranche: (credentials) => ipcRenderer.invoke('updateTranche', credentials),
|
||||
deleteTranche: (credentials) => ipcRenderer.invoke('deleteTranche', credentials),
|
||||
deleteEtudiant: (id) => ipcRenderer.invoke('deleteEtudiant', id),
|
||||
getSingleTranche: (credentials) => ipcRenderer.invoke('getSingleTranche', credentials)
|
||||
getEtudiantsWithPaidTranche: (credentials) => ipcRenderer.invoke('getEtudiantsWithPaidTranche', credentials),
|
||||
reinscribeEtudiant: (credentials) => ipcRenderer.invoke('reinscribeEtudiant', credentials)
|
||||
})
|
||||
|
||||
/**
|
||||
@ -168,6 +170,16 @@ if (process.contextIsolated) {
|
||||
updateIPConfig: (credentials) => ipcRenderer.invoke('updateIPConfig', credentials)
|
||||
})
|
||||
|
||||
/**
|
||||
* contextbridge for configecolage
|
||||
*/
|
||||
contextBridge.exposeInMainWorld('configecolage', {
|
||||
getAll: () => ipcRenderer.invoke('getAllConfigEcolage'),
|
||||
getByMentionNiveau: (credentials) => ipcRenderer.invoke('getConfigEcolageByMentionNiveau', credentials),
|
||||
upsert: (credentials) => ipcRenderer.invoke('upsertConfigEcolage', credentials),
|
||||
delete: (credentials) => ipcRenderer.invoke('deleteConfigEcolage', credentials)
|
||||
})
|
||||
|
||||
/**
|
||||
* contextbridge for status
|
||||
*/
|
||||
|
||||
@ -39,6 +39,7 @@ import Parcours from '../components/Parcours'
|
||||
import ModalExportFichr from '../components/ModalExportFichr'
|
||||
import Resultat from '../components/Resultat'
|
||||
import TrancheEcolage from '../components/TrancheEcolage'
|
||||
import ConfigEcolage from '../components/ConfigEcolage'
|
||||
|
||||
// Use createHashRouter instead of createBrowserRouter because the desktop app is in local machine
|
||||
const Router = createHashRouter([
|
||||
@ -178,6 +179,10 @@ const Router = createHashRouter([
|
||||
path: '/resultat/:niveau/:scolaire',
|
||||
element: <Resultat />
|
||||
},
|
||||
{
|
||||
path: '/configecolage',
|
||||
element: <ConfigEcolage />
|
||||
},
|
||||
{
|
||||
path: '/tranche/:id',
|
||||
element: <TrancheEcolage />
|
||||
|
||||
@ -139,7 +139,7 @@ const previousFilter = location.state?.selectedNiveau
|
||||
}
|
||||
|
||||
const handleClose2 = () => {
|
||||
navigate('/student', { state: { selectedNiveau: previousFilter } })
|
||||
navigate('/student', { state: { selectedNiveau: previousFilter, selectedAnnee: localStorage.getItem('selectedAnnee') || '' } })
|
||||
setOpen(false)
|
||||
}
|
||||
|
||||
|
||||
@ -45,37 +45,31 @@ import { MdChangeCircle, MdGrade, MdRule } from 'react-icons/md'
|
||||
import ModalRecepice from './ModalRecepice'
|
||||
import { FaLeftLong, FaRightLong } from 'react-icons/fa6'
|
||||
import { Tooltip } from 'react-tooltip'
|
||||
import Radio from '@mui/material/Radio'
|
||||
import RadioGroup from '@mui/material/RadioGroup'
|
||||
import FormControlLabel from '@mui/material/FormControlLabel'
|
||||
import Paper from '@mui/material/Paper'
|
||||
|
||||
const AddStudent = () => {
|
||||
const navigate = useNavigate()
|
||||
|
||||
const [niveaus, setNiveau] = useState([])
|
||||
const [status, setStatus] = useState([])
|
||||
const [scolaire, setScolaire] = useState([])
|
||||
const [mention, setMention] = useState([])
|
||||
const [parcours, setParcours] = useState([])
|
||||
const [isExistingStudent, setIsExistingStudent] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
window.niveaus.getNiveau().then((response) => {
|
||||
setNiveau(response)
|
||||
})
|
||||
// Recherche etudiant existant
|
||||
const [searchQuery, setSearchQuery] = useState('')
|
||||
const [searchResults, setSearchResults] = useState([])
|
||||
const [allStudents, setAllStudents] = useState([])
|
||||
|
||||
window.statuss.getStatus().then((response) => {
|
||||
setStatus(response)
|
||||
})
|
||||
|
||||
window.anneescolaire.getAnneeScolaire().then((response) => {
|
||||
setScolaire(response)
|
||||
})
|
||||
|
||||
window.mention.getMention().then((response) => {
|
||||
setMention(response)
|
||||
})
|
||||
|
||||
window.notesysteme.getParcours().then((response) => {
|
||||
setParcours(response)
|
||||
})
|
||||
}, [])
|
||||
|
||||
const navigate = useNavigate()
|
||||
// Paiement
|
||||
const [paiementType, setPaiementType] = useState('')
|
||||
const [numBordereau, setNumBordereau] = useState('')
|
||||
const [montantPaye, setMontantPaye] = useState('')
|
||||
const [montantConfig, setMontantConfig] = useState(null)
|
||||
|
||||
/**
|
||||
* hook for storing data in the input
|
||||
@ -102,6 +96,105 @@ const AddStudent = () => {
|
||||
parcours: ''
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
window.niveaus.getNiveau().then((response) => setNiveau(response))
|
||||
window.statuss.getStatus().then((response) => setStatus(response))
|
||||
window.anneescolaire.getAnneeScolaire().then((response) => setScolaire(response))
|
||||
window.mention.getMention().then((response) => setMention(response))
|
||||
window.notesysteme.getParcours().then((response) => setParcours(response))
|
||||
window.etudiants.getEtudiants().then((response) => setAllStudents(response || [])).catch(() => setAllStudents([]))
|
||||
}, [])
|
||||
|
||||
// Charger le montant quand mention_id et niveau changent
|
||||
useEffect(() => {
|
||||
if (formData.mention_id && formData.niveau && window.configecolage) {
|
||||
window.configecolage.getByMentionNiveau({
|
||||
mention_id: formData.mention_id,
|
||||
niveau_nom: formData.niveau
|
||||
}).then((res) => {
|
||||
setMontantConfig(res)
|
||||
}).catch(() => setMontantConfig(null))
|
||||
} else {
|
||||
setMontantConfig(null)
|
||||
}
|
||||
}, [formData.mention_id, formData.niveau])
|
||||
|
||||
// Recherche etudiant
|
||||
const handleSearch = (e) => {
|
||||
const query = e.target.value.toLowerCase().trim()
|
||||
setSearchQuery(e.target.value)
|
||||
if (query.length < 2) { setSearchResults([]); return }
|
||||
|
||||
// Vérifier si la recherche est un numéro
|
||||
const isNumeric = /^\d+$/.test(query)
|
||||
|
||||
const filtered = allStudents.filter((s) => {
|
||||
// Recherche par nom/prénom (toujours par inclusion)
|
||||
if (s.nom && s.nom.toLowerCase().includes(query)) return true
|
||||
if (s.prenom && s.prenom.toLowerCase().includes(query)) return true
|
||||
|
||||
if (isNumeric) {
|
||||
// Pour les numéros : correspondance exacte parmi les num_inscription
|
||||
const nums = []
|
||||
if (s.num_inscription) nums.push(s.num_inscription.trim())
|
||||
if (s.all_num_inscriptions) {
|
||||
s.all_num_inscriptions.split(',').forEach(n => nums.push(n.trim()))
|
||||
}
|
||||
return nums.some(n => n === query)
|
||||
} else {
|
||||
// Pour le texte : recherche par inclusion dans num_inscription
|
||||
if (s.num_inscription && s.num_inscription.toLowerCase().includes(query)) return true
|
||||
if (s.all_num_inscriptions && s.all_num_inscriptions.toLowerCase().includes(query)) return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
setSearchResults(filtered.slice(0, 8))
|
||||
}
|
||||
|
||||
// Helper pour formater les dates en toute securite
|
||||
const formatDate = (val) => {
|
||||
if (!val) return ''
|
||||
try {
|
||||
const str = String(val)
|
||||
if (str.includes('T')) return str.split('T')[0]
|
||||
if (str.includes('-') && str.length >= 10) return str.substring(0, 10)
|
||||
return str
|
||||
} catch { return '' }
|
||||
}
|
||||
|
||||
const selectStudent = (student) => {
|
||||
setIsExistingStudent(true)
|
||||
setSearchQuery('')
|
||||
setSearchResults([])
|
||||
console.log('Student sélectionné:', JSON.stringify(student, null, 2))
|
||||
setFormData({
|
||||
nom: student.nom || '',
|
||||
prenom: student.prenom || '',
|
||||
photos: student.photos || null,
|
||||
date_de_naissances: formatDate(student.date_de_naissances),
|
||||
niveau: student.niveau || '',
|
||||
annee_scolaire: (scolaire.find((s) => s.is_current === 1) || {}).code || '',
|
||||
status: student.status || '',
|
||||
num_inscription: student.num_inscription || '',
|
||||
mention_id: student.mention_id || '',
|
||||
sexe: student.sexe || 'Garçon',
|
||||
nationaliter: student.nationalite || '',
|
||||
cin: student.cin || '',
|
||||
date_delivrence: formatDate(student.date_delivrance),
|
||||
annee_bacc: formatDate(student.annee_bacc),
|
||||
serie: student.serie || '',
|
||||
boursier: student.boursier || 'oui',
|
||||
domaine: student.domaine || '',
|
||||
contact: student.contact || '',
|
||||
parcours: student.parcours || '',
|
||||
existingId: student.id
|
||||
})
|
||||
if (imageVisual.current && student.photos) {
|
||||
imageVisual.current.style.display = 'block'
|
||||
imageVisual.current.src = student.photos
|
||||
}
|
||||
}
|
||||
|
||||
const [dataToSend, setDataToSend] = useState({})
|
||||
|
||||
useEffect(() => {
|
||||
@ -181,21 +274,63 @@ const AddStudent = () => {
|
||||
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault()
|
||||
// Handle form submission logic
|
||||
const response = await window.etudiants.insertEtudiant(formData)
|
||||
let etudiantId = null
|
||||
|
||||
console.log(response)
|
||||
if (!response.success) {
|
||||
setCode(422)
|
||||
setOpen(true)
|
||||
if (isExistingStudent && formData.existingId) {
|
||||
// Etudiant existant : réinscription pour la nouvelle année scolaire
|
||||
const reinscriptionData = {
|
||||
etudiant_id: formData.existingId,
|
||||
niveau: formData.niveau,
|
||||
annee_scolaire: formData.annee_scolaire,
|
||||
status: formData.status,
|
||||
num_inscription: formData.num_inscription,
|
||||
mention_id: formData.mention_id,
|
||||
parcours: formData.parcours
|
||||
}
|
||||
console.log('Reinscription envoyée:', reinscriptionData)
|
||||
const response = await window.etudiants.reinscribeEtudiant(reinscriptionData)
|
||||
console.log('Reinscription réponse:', response)
|
||||
if (!response || !response.success) {
|
||||
console.error('Erreur reinscription:', response)
|
||||
setCode(422)
|
||||
setOpen(true)
|
||||
return
|
||||
}
|
||||
etudiantId = formData.existingId
|
||||
} else {
|
||||
// Nouvel etudiant : insertion
|
||||
const response = await window.etudiants.insertEtudiant(formData)
|
||||
console.log('Insert:', response)
|
||||
if (!response.success) {
|
||||
setCode(422)
|
||||
setOpen(true)
|
||||
return
|
||||
}
|
||||
etudiantId = response.id
|
||||
}
|
||||
|
||||
if (response.success) {
|
||||
imageVisual.current.style.display = 'none'
|
||||
imageVisual.current.src = ''
|
||||
setCode(200)
|
||||
setOpen(true)
|
||||
// Enregistrer le paiement si un type est selectionne
|
||||
if (paiementType && numBordereau && montantPaye && etudiantId) {
|
||||
const annee = scolaire.find((s) => s.code === formData.annee_scolaire)
|
||||
if (annee) {
|
||||
await window.etudiants.payerTranche({
|
||||
etudiant_id: etudiantId,
|
||||
annee_scolaire_id: annee.id,
|
||||
type: paiementType === 'complet' ? 'Tranche Complète' : 'Tranche 1',
|
||||
montant: Number(montantPaye),
|
||||
num_bordereau: numBordereau
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
imageVisual.current.style.display = 'none'
|
||||
imageVisual.current.src = ''
|
||||
setCode(200)
|
||||
setOpen(true)
|
||||
setIsExistingStudent(false)
|
||||
setPaiementType('')
|
||||
setNumBordereau('')
|
||||
setMontantPaye('')
|
||||
}
|
||||
|
||||
const VisuallyHiddenInput = styled('input')({
|
||||
@ -344,11 +479,46 @@ const AddStudent = () => {
|
||||
p: 4
|
||||
}}
|
||||
>
|
||||
<div style={{ textAlign: 'right', fontWeight: 'bold' }}>
|
||||
<div style={{ textAlign: 'right', fontWeight: 'bold', marginBottom: '10px' }}>
|
||||
<Link to={'/exportetudiant'} style={{ textDecoration: 'none', color: 'orange' }}>
|
||||
Importer un fichier excel <FaFileExcel />
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{/* Barre de recherche etudiant existant */}
|
||||
<div style={{ position: 'relative', marginBottom: '15px' }}>
|
||||
<TextField
|
||||
fullWidth
|
||||
size="small"
|
||||
color="warning"
|
||||
placeholder="Rechercher un etudiant existant (nom, prenom, N° inscription)..."
|
||||
value={searchQuery}
|
||||
onChange={handleSearch}
|
||||
sx={{ '& .MuiOutlinedInput-root': { '&:hover fieldset': { borderColor: '#ff9800' } } }}
|
||||
/>
|
||||
{searchResults.length > 0 && (
|
||||
<Paper sx={{
|
||||
position: 'absolute', top: '100%', left: 0, right: 0, zIndex: 10,
|
||||
maxHeight: 250, overflow: 'auto', boxShadow: 3
|
||||
}}>
|
||||
{searchResults.map((s) => (
|
||||
<div
|
||||
key={s.id}
|
||||
onClick={() => selectStudent(s)}
|
||||
style={{
|
||||
padding: '8px 12px', cursor: 'pointer', borderBottom: '1px solid #eee',
|
||||
display: 'flex', justifyContent: 'space-between', alignItems: 'center'
|
||||
}}
|
||||
onMouseEnter={(e) => e.target.style.background = '#fff3e0'}
|
||||
onMouseLeave={(e) => e.target.style.background = 'white'}
|
||||
>
|
||||
<span><b>{s.nom}</b> {s.prenom}</span>
|
||||
<span style={{ color: 'gray', fontSize: '12px' }}>{s.num_inscription} - {s.niveau}</span>
|
||||
</div>
|
||||
))}
|
||||
</Paper>
|
||||
)}
|
||||
</div>
|
||||
<Box
|
||||
sx={{
|
||||
marginTop: '2%',
|
||||
@ -1034,6 +1204,95 @@ const AddStudent = () => {
|
||||
</Grid>
|
||||
</Grid>
|
||||
)}
|
||||
{/* Section Paiement - toujours visible sur page 2 */}
|
||||
{page2 && (
|
||||
<Grid item xs={12}>
|
||||
<Paper sx={{ p: 2, mt: 1, background: '#f9f9f9', border: '1px solid #ff9800' }}>
|
||||
<Typography variant="subtitle1" sx={{ mb: 1, fontWeight: 'bold' }}>
|
||||
<FaMoneyBillWave style={{ marginRight: 5 }} />
|
||||
Paiement Ecolage
|
||||
{montantConfig
|
||||
? ` - Droit total : ${Number(montantConfig.montant_total).toLocaleString('fr-FR')} Ar`
|
||||
: ' - (Configurer les droits dans Admin > Config Ecolage)'}
|
||||
</Typography>
|
||||
{montantConfig ? (
|
||||
<Grid container spacing={2} alignItems="center">
|
||||
<Grid item xs={12}>
|
||||
<RadioGroup
|
||||
row
|
||||
value={paiementType}
|
||||
onChange={(e) => {
|
||||
const val = e.target.value
|
||||
setPaiementType(val)
|
||||
if (val === 'complet' && montantConfig) {
|
||||
setMontantPaye(String(montantConfig.montant_total))
|
||||
} else {
|
||||
setMontantPaye('')
|
||||
}
|
||||
}}
|
||||
>
|
||||
<FormControlLabel value="" control={<Radio color="default" />}
|
||||
label="Pas de paiement"
|
||||
/>
|
||||
<FormControlLabel value="tranche1" control={<Radio color="warning" />}
|
||||
label="Tranche 1"
|
||||
/>
|
||||
<FormControlLabel value="complet" control={<Radio color="warning" />}
|
||||
label="Tranche Complete"
|
||||
/>
|
||||
</RadioGroup>
|
||||
</Grid>
|
||||
{paiementType && (
|
||||
<>
|
||||
<Grid item xs={12} sm={4}>
|
||||
<TextField
|
||||
required
|
||||
name="num_bordereau"
|
||||
label="N° Bordereau"
|
||||
type="text"
|
||||
fullWidth
|
||||
size="small"
|
||||
color="warning"
|
||||
placeholder="Ex: BRD-2026-001"
|
||||
value={numBordereau}
|
||||
onChange={(e) => setNumBordereau(e.target.value)}
|
||||
sx={{ '& .MuiOutlinedInput-root': { '&:hover fieldset': { borderColor: '#ff9800' } } }}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={4}>
|
||||
<TextField
|
||||
required
|
||||
label="Montant paye"
|
||||
type="number"
|
||||
fullWidth
|
||||
size="small"
|
||||
color="warning"
|
||||
value={montantPaye}
|
||||
onChange={(e) => setMontantPaye(e.target.value)}
|
||||
InputProps={{ startAdornment: <InputAdornment position="start">Ar</InputAdornment> }}
|
||||
sx={{ '& .MuiOutlinedInput-root': { '&:hover fieldset': { borderColor: '#ff9800' } } }}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={4}>
|
||||
<Typography sx={{ fontSize: '13px', color: '#555', lineHeight: 1.4 }}>
|
||||
Droit total : <b>{Number(montantConfig.montant_total).toLocaleString('fr-FR')} Ar</b><br />
|
||||
Reste a payer : <b style={{ color: Number(montantPaye || 0) >= montantConfig.montant_total ? 'green' : 'red' }}>
|
||||
{Number(Math.max(0, montantConfig.montant_total - Number(montantPaye || 0))).toLocaleString('fr-FR')} Ar
|
||||
</b>
|
||||
</Typography>
|
||||
</Grid>
|
||||
</>
|
||||
)}
|
||||
</Grid>
|
||||
) : (
|
||||
<Typography sx={{ color: 'gray', fontSize: '14px' }}>
|
||||
Selectionnez d'abord une mention et un niveau sur la page 1, puis configurez les droits dans Admin > Config Ecolage.
|
||||
</Typography>
|
||||
)}
|
||||
</Paper>
|
||||
</Grid>
|
||||
)}
|
||||
|
||||
{/* Submit Button */}
|
||||
<Grid
|
||||
item
|
||||
@ -1045,7 +1304,7 @@ const AddStudent = () => {
|
||||
<FaLeftLong className="pageprecedent" style={{ outline: 'none' }} />
|
||||
</IconButton>
|
||||
<Tooltip anchorSelect=".pageprecedent" className="custom-tooltip" place="top">
|
||||
Page précédents
|
||||
Page precedents
|
||||
</Tooltip>
|
||||
<IconButton color="warning" onClick={seePage2}>
|
||||
<FaRightLong className="pagesuivant" style={{ outline: 'none' }} />
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import React, { useState } from 'react'
|
||||
import React, { useState, useEffect } from 'react'
|
||||
import {
|
||||
Dialog,
|
||||
DialogActions,
|
||||
@ -6,20 +6,39 @@ import {
|
||||
DialogTitle,
|
||||
TextField,
|
||||
Button,
|
||||
Autocomplete,
|
||||
InputAdornment,
|
||||
Box,
|
||||
Grid
|
||||
Grid,
|
||||
FormControl,
|
||||
InputLabel,
|
||||
Select,
|
||||
MenuItem
|
||||
} from '@mui/material'
|
||||
import { MdLabelImportantOutline } from 'react-icons/md'
|
||||
|
||||
const AjoutTranche = ({ open, onClose, onSubmitSuccess, id }) => {
|
||||
const [anneesList, setAnneesList] = useState([])
|
||||
const [formData, setFormData] = useState({
|
||||
etudiant_id: id,
|
||||
tranchename: '',
|
||||
montant: ''
|
||||
annee_scolaire_id: '',
|
||||
type: '',
|
||||
montant: '',
|
||||
num_bordereau: ''
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
window.anneescolaire.getAnneeScolaire().then((response) => {
|
||||
setAnneesList(response || [])
|
||||
const currentYear = (response || []).find((a) => a.is_current === 1 || a.is_current === true)
|
||||
if (currentYear) {
|
||||
setFormData((prev) => ({ ...prev, annee_scolaire_id: currentYear.id }))
|
||||
}
|
||||
})
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
setFormData((prev) => ({ ...prev, etudiant_id: id }))
|
||||
}, [id])
|
||||
|
||||
const handleChange = (e) => {
|
||||
const { name, value } = e.target
|
||||
setFormData({ ...formData, [name]: value })
|
||||
@ -27,69 +46,87 @@ const AjoutTranche = ({ open, onClose, onSubmitSuccess, id }) => {
|
||||
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault()
|
||||
let response = await window.etudiants.createTranche(formData)
|
||||
const response = await window.etudiants.payerTranche(formData)
|
||||
|
||||
if (response.success) {
|
||||
onClose()
|
||||
onSubmitSuccess(true)
|
||||
setFormData({
|
||||
etudiant_id: id,
|
||||
tranchename: '',
|
||||
montant: ''
|
||||
annee_scolaire_id: formData.annee_scolaire_id,
|
||||
type: '',
|
||||
montant: '',
|
||||
num_bordereau: ''
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog open={open} onClose={onClose}>
|
||||
<form action="" onSubmit={handleSubmit}>
|
||||
<DialogTitle>Ajout tranche</DialogTitle>
|
||||
<Dialog open={open} onClose={onClose} maxWidth="sm" fullWidth>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<DialogTitle>Enregistrer un paiement</DialogTitle>
|
||||
<DialogContent>
|
||||
<Box sx={{ flexGrow: 1 }}>
|
||||
<Box sx={{ flexGrow: 1, mt: 1 }}>
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={12}>
|
||||
<FormControl fullWidth color="warning" required size="small">
|
||||
<InputLabel>Annee scolaire</InputLabel>
|
||||
<Select
|
||||
name="annee_scolaire_id"
|
||||
value={formData.annee_scolaire_id}
|
||||
onChange={handleChange}
|
||||
label="Annee scolaire"
|
||||
>
|
||||
{anneesList.map((a) => (
|
||||
<MenuItem value={a.id} key={a.id}>{a.code}</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<FormControl fullWidth color="warning" required size="small">
|
||||
<InputLabel>Type de paiement</InputLabel>
|
||||
<Select
|
||||
name="type"
|
||||
value={formData.type}
|
||||
onChange={handleChange}
|
||||
label="Type de paiement"
|
||||
>
|
||||
<MenuItem value="Tranche 1">Tranche 1</MenuItem>
|
||||
<MenuItem value="Tranche 2">Tranche 2</MenuItem>
|
||||
<MenuItem value="Tranche Complète">Tranche Complete (1 + 2)</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<TextField
|
||||
autoFocus
|
||||
margin="normal"
|
||||
required
|
||||
name="tranchename"
|
||||
label="Désignation"
|
||||
name="num_bordereau"
|
||||
label="N° Bordereau"
|
||||
type="text"
|
||||
fullWidth
|
||||
placeholder="Tranche 1"
|
||||
size="small"
|
||||
placeholder="Ex: BRD-2026-001"
|
||||
variant="outlined"
|
||||
value={formData.tranchename}
|
||||
value={formData.num_bordereau}
|
||||
color="warning"
|
||||
onChange={handleChange}
|
||||
InputProps={{
|
||||
startAdornment: (
|
||||
<InputAdornment position="start">
|
||||
<MdLabelImportantOutline />
|
||||
</InputAdornment>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<TextField
|
||||
autoFocus
|
||||
margin="normal"
|
||||
required
|
||||
name="montant"
|
||||
label="Montant"
|
||||
type="number"
|
||||
fullWidth
|
||||
placeholder="Montant"
|
||||
size="small"
|
||||
variant="outlined"
|
||||
value={formData.montant}
|
||||
color="warning"
|
||||
onChange={handleChange}
|
||||
InputProps={{
|
||||
startAdornment: (
|
||||
<InputAdornment position="start">
|
||||
<MdLabelImportantOutline />
|
||||
</InputAdornment>
|
||||
)
|
||||
startAdornment: <InputAdornment position="start">Ar</InputAdornment>
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
@ -97,12 +134,8 @@ const AjoutTranche = ({ open, onClose, onSubmitSuccess, id }) => {
|
||||
</Box>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={onClose} color="error">
|
||||
Annuler
|
||||
</Button>
|
||||
<Button type="submit" color="warning">
|
||||
Soumettre
|
||||
</Button>
|
||||
<Button onClick={onClose} color="error">Annuler</Button>
|
||||
<Button type="submit" color="warning" variant="contained">Enregistrer</Button>
|
||||
</DialogActions>
|
||||
</form>
|
||||
</Dialog>
|
||||
|
||||
@ -142,13 +142,12 @@ const AnneeScolaire = () => {
|
||||
}))
|
||||
|
||||
const setCurrent = async (id) => {
|
||||
// let response = await window.anneescolaire.setCurrent({id});
|
||||
// console.log(response);
|
||||
// if (response.changes) {
|
||||
// window.anneescolaire.getAnneeScolaire().then((response) => {
|
||||
// setAnneeScolaire(response);
|
||||
// });
|
||||
// }
|
||||
const response = await window.anneescolaire.setCurrent({ id })
|
||||
if (response.success) {
|
||||
window.anneescolaire.getAnneeScolaire().then((response) => {
|
||||
setAnneeScolaire(response)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const deleteButton = async (id) => {
|
||||
|
||||
152
src/renderer/src/components/ConfigEcolage.jsx
Normal file
152
src/renderer/src/components/ConfigEcolage.jsx
Normal file
@ -0,0 +1,152 @@
|
||||
import React, { useState, useEffect } from 'react'
|
||||
import {
|
||||
Box, Button, InputAdornment, TextField, Grid, Typography,
|
||||
FormControl, InputLabel, Select, MenuItem, Paper
|
||||
} from '@mui/material'
|
||||
import { FaTrash, FaMoneyBillWave } from 'react-icons/fa'
|
||||
import { FaPenToSquare } from 'react-icons/fa6'
|
||||
import classe from '../assets/AllStyleComponents.module.css'
|
||||
import classeHome from '../assets/Home.module.css'
|
||||
|
||||
const ConfigEcolage = () => {
|
||||
const [configList, setConfigList] = useState([])
|
||||
const [mentions, setMentions] = useState([])
|
||||
const [niveaux, setNiveaux] = useState([])
|
||||
const [ecolageForm, setEcolageForm] = useState({ mention_id: '', niveau_id: '', montant_total: '' })
|
||||
|
||||
const loadConfig = () => {
|
||||
window.configecolage.getAll().then((res) => setConfigList(res || []))
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
loadConfig()
|
||||
window.mention.getMention().then((res) => setMentions(res || []))
|
||||
window.niveaus.getNiveau().then((res) => setNiveaux(res || []))
|
||||
}, [])
|
||||
|
||||
const handleChange = (e) => {
|
||||
setEcolageForm({ ...ecolageForm, [e.target.name]: e.target.value })
|
||||
}
|
||||
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault()
|
||||
const res = await window.configecolage.upsert(ecolageForm)
|
||||
if (res.success) {
|
||||
loadConfig()
|
||||
setEcolageForm({ mention_id: '', niveau_id: '', montant_total: '' })
|
||||
}
|
||||
}
|
||||
|
||||
const handleDelete = async (id) => {
|
||||
const res = await window.configecolage.delete({ id })
|
||||
if (res.success) loadConfig()
|
||||
}
|
||||
|
||||
const handleEdit = (c) => {
|
||||
setEcolageForm({ mention_id: c.mention_id, niveau_id: c.niveau_id, montant_total: c.montant_total })
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={classe.mainHome}>
|
||||
<div className={classeHome.header}>
|
||||
<div className={classe.h1style}>
|
||||
<div className={classeHome.blockTitle}>
|
||||
<h1>
|
||||
<FaMoneyBillWave style={{ marginRight: 10 }} />
|
||||
Configuration Ecolage
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ padding: '0 2%' }}>
|
||||
<Paper sx={{ p: 3 }}>
|
||||
<Typography variant="subtitle1" sx={{ mb: 2, fontWeight: 'bold' }}>
|
||||
Definir le montant des droits par mention et niveau
|
||||
</Typography>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<Grid container spacing={2} alignItems="center">
|
||||
<Grid item xs={12} sm={3}>
|
||||
<FormControl fullWidth size="small" color="warning" required>
|
||||
<InputLabel>Mention</InputLabel>
|
||||
<Select name="mention_id" value={ecolageForm.mention_id} onChange={handleChange} label="Mention">
|
||||
{mentions.map((m) => (
|
||||
<MenuItem value={m.id} key={m.id}>{m.nom}</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={3}>
|
||||
<FormControl fullWidth size="small" color="warning" required>
|
||||
<InputLabel>Niveau</InputLabel>
|
||||
<Select name="niveau_id" value={ecolageForm.niveau_id} onChange={handleChange} label="Niveau">
|
||||
{niveaux.map((n) => (
|
||||
<MenuItem value={n.id} key={n.id}>{n.nom}</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={3}>
|
||||
<TextField
|
||||
required
|
||||
name="montant_total"
|
||||
label="Montant total"
|
||||
type="number"
|
||||
fullWidth
|
||||
size="small"
|
||||
color="warning"
|
||||
value={ecolageForm.montant_total}
|
||||
onChange={handleChange}
|
||||
InputProps={{ startAdornment: <InputAdornment position="start">Ar</InputAdornment> }}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={3}>
|
||||
<Button type="submit" color="warning" variant="contained" fullWidth>
|
||||
Enregistrer
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</form>
|
||||
|
||||
<table className="table table-bordered table-striped text-center shadow-sm" style={{ marginTop: '20px' }}>
|
||||
<thead className="table-secondary">
|
||||
<tr>
|
||||
<th>Mention</th>
|
||||
<th>Niveau</th>
|
||||
<th>Montant total</th>
|
||||
<th>Tranche 1</th>
|
||||
<th>Tranche 2</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{configList.length === 0 ? (
|
||||
<tr><td colSpan={6} style={{ color: 'gray' }}>Aucune configuration</td></tr>
|
||||
) : (
|
||||
configList.map((c) => (
|
||||
<tr key={c.id}>
|
||||
<td>{c.mention_nom}</td>
|
||||
<td>{c.niveau_nom}</td>
|
||||
<td><b>{Number(c.montant_total).toLocaleString('fr-FR')} Ar</b></td>
|
||||
<td>{Number(c.montant_total / 2).toLocaleString('fr-FR')} Ar</td>
|
||||
<td>{Number(c.montant_total / 2).toLocaleString('fr-FR')} Ar</td>
|
||||
<td style={{ display: 'flex', gap: '8px', justifyContent: 'center' }}>
|
||||
<Button color="warning" size="small" variant="contained" onClick={() => handleEdit(c)}>
|
||||
<FaPenToSquare />
|
||||
</Button>
|
||||
<Button color="error" size="small" variant="contained" onClick={() => handleDelete(c.id)}>
|
||||
<FaTrash />
|
||||
</Button>
|
||||
</td>
|
||||
</tr>
|
||||
))
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</Paper>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ConfigEcolage
|
||||
374
src/renderer/src/components/Ecolage.jsx
Normal file
374
src/renderer/src/components/Ecolage.jsx
Normal file
@ -0,0 +1,374 @@
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { Link } from 'react-router-dom'
|
||||
import classe from '../assets/AllStyleComponents.module.css'
|
||||
import classeHome from '../assets/Home.module.css'
|
||||
import Paper from '@mui/material/Paper'
|
||||
import { Button, InputAdornment } from '@mui/material'
|
||||
import { DataGrid, GridToolbar } from '@mui/x-data-grid'
|
||||
import { frFR } from '@mui/x-data-grid/locales'
|
||||
import dayjs from 'dayjs'
|
||||
import { createTheme, ThemeProvider } from '@mui/material/styles'
|
||||
import InputLabel from '@mui/material/InputLabel'
|
||||
import MenuItem from '@mui/material/MenuItem'
|
||||
import FormControl from '@mui/material/FormControl'
|
||||
import Select from '@mui/material/Select'
|
||||
import { Tooltip } from 'react-tooltip'
|
||||
import { FaGraduationCap, FaMoneyBillWave } from 'react-icons/fa'
|
||||
import { MdPayment } from 'react-icons/md'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
|
||||
const Ecolage = () => {
|
||||
const theme = createTheme({
|
||||
components: {
|
||||
MuiIconButton: {
|
||||
styleOverrides: { root: { color: 'gray' } }
|
||||
},
|
||||
MuiButton: {
|
||||
styleOverrides: { root: { color: '#121212' } }
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const [status, setStatus] = useState([])
|
||||
const [filterModel, setFilterModel] = useState({ items: [] })
|
||||
const [sortModel, setSortModel] = useState([])
|
||||
const location = useLocation()
|
||||
|
||||
const savedFilter = localStorage.getItem('ecolageNiveau') || ''
|
||||
const savedAnnee = localStorage.getItem('ecolageAnnee') || ''
|
||||
const initialFilter = location.state?.selectedNiveau || savedFilter
|
||||
const initialAnnee = location.state?.selectedAnnee || savedAnnee
|
||||
|
||||
const [selectedNiveau, setSelectedNiveau] = useState(initialFilter)
|
||||
const [selectedAnnee, setSelectedAnnee] = useState(initialAnnee)
|
||||
const [anneesList, setAnneesList] = useState([])
|
||||
const [allEtudiants, setAllEtudiants] = useState([])
|
||||
const [etudiants, setEtudiants] = useState([])
|
||||
const [niveaus, setNiveau] = useState([])
|
||||
|
||||
const [paginationModel, setPaginationModel] = useState({ page: 0, pageSize: 20 })
|
||||
const [pageSizeOptions, setPageSizeOptions] = useState([20, 40, 60])
|
||||
|
||||
useEffect(() => {
|
||||
if (initialFilter) setSelectedNiveau(initialFilter)
|
||||
if (initialAnnee) setSelectedAnnee(initialAnnee)
|
||||
}, [])
|
||||
|
||||
// Load academic years
|
||||
useEffect(() => {
|
||||
window.anneescolaire.getAnneeScolaire().then((response) => {
|
||||
setAnneesList(response || [])
|
||||
const currentYear = (response || []).find((a) => a.is_current === 1 || a.is_current === true)
|
||||
if (currentYear && !savedAnnee) {
|
||||
setSelectedAnnee(currentYear.code)
|
||||
localStorage.setItem('ecolageAnnee', currentYear.code)
|
||||
}
|
||||
})
|
||||
}, [])
|
||||
|
||||
// Load students when year changes
|
||||
useEffect(() => {
|
||||
const loadEtudiants = async () => {
|
||||
if (selectedAnnee && selectedAnnee !== '') {
|
||||
const response = await window.etudiants.getAllEtudiantsForEcolage({
|
||||
annee_scolaire: selectedAnnee
|
||||
})
|
||||
setAllEtudiants(response || [])
|
||||
} else {
|
||||
setAllEtudiants([])
|
||||
}
|
||||
}
|
||||
loadEtudiants()
|
||||
}, [selectedAnnee])
|
||||
|
||||
// Filter by niveau
|
||||
useEffect(() => {
|
||||
if (selectedNiveau && selectedNiveau !== '') {
|
||||
setEtudiants(allEtudiants.filter((e) => e.niveau === selectedNiveau))
|
||||
} else {
|
||||
setEtudiants(allEtudiants)
|
||||
}
|
||||
}, [allEtudiants, selectedNiveau])
|
||||
|
||||
// Load saved datagrid state
|
||||
useEffect(() => {
|
||||
const savedFilters = localStorage.getItem('ecolageGridFilters')
|
||||
const savedSort = localStorage.getItem('ecolageGridSort')
|
||||
const savedPagination = localStorage.getItem('ecolageGridPagination')
|
||||
|
||||
if (savedFilters) setFilterModel(JSON.parse(savedFilters))
|
||||
if (savedSort) setSortModel(JSON.parse(savedSort))
|
||||
if (savedPagination) setPaginationModel(JSON.parse(savedPagination))
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
window.niveaus.getNiveau().then((response) => setNiveau(response))
|
||||
window.statuss.getStatus().then((response) => setStatus(response))
|
||||
}, [])
|
||||
|
||||
function comparestatut(statutID) {
|
||||
let statusText
|
||||
status.map((statu) => {
|
||||
if (statutID == statu.id) statusText = statu.nom
|
||||
})
|
||||
return statusText
|
||||
}
|
||||
|
||||
const handlePaginationModelChange = (newModel) => {
|
||||
setPaginationModel(newModel)
|
||||
const maxOption = Math.max(...pageSizeOptions)
|
||||
if (newModel.pageSize === maxOption) {
|
||||
setPageSizeOptions((prev) => [...prev, maxOption + 20])
|
||||
}
|
||||
}
|
||||
|
||||
const columns = [
|
||||
{ field: 'nom', headerName: 'Nom', width: 180 },
|
||||
{ field: 'prenom', headerName: 'Prenom', width: 180 },
|
||||
{ field: 'num_inscription', headerName: "N° d'inscription", width: 160 },
|
||||
{ field: 'niveau', headerName: 'Niveau', width: 80 },
|
||||
{ field: 'nomMention', headerName: 'Mention', width: 180 },
|
||||
{ field: 'annee_scolaire', headerName: 'Annee universitaire', width: 130 },
|
||||
{ field: 'status', headerName: 'Status', width: 140 },
|
||||
{ field: 'contact', headerName: 'Contact', width: 150 },
|
||||
{
|
||||
field: 'tranches_payees',
|
||||
headerName: 'Tranches payees',
|
||||
width: 130,
|
||||
renderCell: (params) => (
|
||||
<span
|
||||
style={{
|
||||
color: params.value > 0 ? 'green' : 'red',
|
||||
fontWeight: 'bold'
|
||||
}}
|
||||
>
|
||||
{params.value > 0 ? `${params.value} payee(s)` : 'Aucune'}
|
||||
</span>
|
||||
)
|
||||
},
|
||||
{
|
||||
field: 'photos',
|
||||
headerName: 'Image',
|
||||
width: 80,
|
||||
renderCell: (params) => (
|
||||
<img
|
||||
src={params.value}
|
||||
alt="pdp"
|
||||
style={{ width: 40, height: 40, borderRadius: '50%', objectFit: 'cover' }}
|
||||
/>
|
||||
)
|
||||
},
|
||||
{
|
||||
field: 'action',
|
||||
headerName: 'Action',
|
||||
width: 180,
|
||||
renderCell: (params) => (
|
||||
<div style={{ display: 'flex', gap: '10px' }}>
|
||||
<Link to={`/tranche/${params.value}`}>
|
||||
<Button
|
||||
color="warning"
|
||||
variant="contained"
|
||||
className={`payer${params.value}`}
|
||||
>
|
||||
<MdPayment style={{ fontSize: '20px', color: 'white' }} />
|
||||
</Button>
|
||||
<Tooltip
|
||||
anchorSelect={`.payer${params.value}`}
|
||||
className="custom-tooltip"
|
||||
place="top"
|
||||
>
|
||||
Gerer le paiement
|
||||
</Tooltip>
|
||||
</Link>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
]
|
||||
|
||||
const dataRow = etudiants.map((etudiant) => ({
|
||||
id: etudiant.id,
|
||||
nom: etudiant.nom,
|
||||
prenom: etudiant.prenom,
|
||||
niveau: etudiant.niveau,
|
||||
annee_scolaire: etudiant.annee_scolaire,
|
||||
status: comparestatut(etudiant.status),
|
||||
num_inscription: etudiant.num_inscription,
|
||||
nomMention: etudiant.nomMention,
|
||||
contact: etudiant.contact,
|
||||
photos: etudiant.photos,
|
||||
tranches_payees: etudiant.tranches_payees || 0,
|
||||
action: etudiant.id
|
||||
}))
|
||||
|
||||
const FilterData = (e) => {
|
||||
const niveau = e.target.value
|
||||
setSelectedNiveau(niveau)
|
||||
localStorage.setItem('ecolageNiveau', niveau)
|
||||
setPaginationModel((prev) => ({ ...prev, page: 0 }))
|
||||
}
|
||||
|
||||
const FilterByAnnee = (e) => {
|
||||
const annee = e.target.value
|
||||
setSelectedAnnee(annee)
|
||||
localStorage.setItem('ecolageAnnee', annee)
|
||||
setSelectedNiveau('')
|
||||
localStorage.setItem('ecolageNiveau', '')
|
||||
setPaginationModel((prev) => ({ ...prev, page: 0 }))
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={classe.mainHome}>
|
||||
<style>
|
||||
{`
|
||||
.custom-tooltip {
|
||||
font-size: 15px;
|
||||
border: solid 1px white !important;
|
||||
z-index: 999;
|
||||
}
|
||||
`}
|
||||
</style>
|
||||
<div className={classeHome.header}>
|
||||
<div className={classe.h1style}>
|
||||
<div className={classeHome.blockTitle}>
|
||||
<h1>
|
||||
<FaMoneyBillWave style={{ marginRight: '10px' }} />
|
||||
Gestion Ecolage
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
{/* Filters */}
|
||||
<div className={classeHome.container}>
|
||||
<div
|
||||
style={{
|
||||
width: '100%',
|
||||
textAlign: 'right',
|
||||
display: 'flex',
|
||||
justifyContent: 'flex-end',
|
||||
alignItems: 'center'
|
||||
}}
|
||||
>
|
||||
<FormControl
|
||||
sx={{
|
||||
m: 1,
|
||||
width: '25%',
|
||||
'& .MuiOutlinedInput-root': {
|
||||
'&:hover fieldset': { borderColor: '#ff9800' }
|
||||
}
|
||||
}}
|
||||
size="small"
|
||||
variant="outlined"
|
||||
>
|
||||
<InputLabel sx={{ color: 'black', fontSize: '18px' }} color="warning">
|
||||
Annee scolaire
|
||||
</InputLabel>
|
||||
<Select
|
||||
label="Annee scolaire"
|
||||
color="warning"
|
||||
value={selectedAnnee}
|
||||
onChange={FilterByAnnee}
|
||||
sx={{ background: 'white' }}
|
||||
>
|
||||
<MenuItem value="">
|
||||
<em>Toutes les annees</em>
|
||||
</MenuItem>
|
||||
{anneesList.map((a) => (
|
||||
<MenuItem value={a.code} key={a.id}>
|
||||
{a.code}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
|
||||
<FormControl
|
||||
sx={{
|
||||
m: 1,
|
||||
width: '25%',
|
||||
'& .MuiOutlinedInput-root': {
|
||||
'&:hover fieldset': { borderColor: '#ff9800' }
|
||||
}
|
||||
}}
|
||||
size="small"
|
||||
variant="outlined"
|
||||
>
|
||||
<InputLabel sx={{ color: 'black', fontSize: '18px' }} color="warning">
|
||||
Niveau
|
||||
</InputLabel>
|
||||
<Select
|
||||
label="Niveau"
|
||||
color="warning"
|
||||
name="niveau"
|
||||
value={selectedNiveau}
|
||||
onChange={FilterData}
|
||||
startAdornment={
|
||||
<InputAdornment position="start">
|
||||
<FaGraduationCap />
|
||||
</InputAdornment>
|
||||
}
|
||||
sx={{ background: 'white' }}
|
||||
>
|
||||
<MenuItem value="">
|
||||
<em>Tous les etudiants</em>
|
||||
</MenuItem>
|
||||
{niveaus.map((niveau) => (
|
||||
<MenuItem value={niveau.nom} key={niveau.id}>
|
||||
{niveau.nom}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={classeHome.boxEtudiantsCard}>
|
||||
<div style={{ display: 'flex', justifyContent: 'center' }}>
|
||||
<Paper
|
||||
sx={{
|
||||
width: '100%',
|
||||
height: 'auto',
|
||||
minHeight: 500,
|
||||
display: 'flex'
|
||||
}}
|
||||
>
|
||||
<ThemeProvider theme={theme}>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', width: '100%' }}>
|
||||
<DataGrid
|
||||
rows={dataRow}
|
||||
columns={columns}
|
||||
filterModel={filterModel}
|
||||
onFilterModelChange={(newModel) => {
|
||||
setFilterModel(newModel)
|
||||
localStorage.setItem('ecolageGridFilters', JSON.stringify(newModel))
|
||||
}}
|
||||
sortModel={sortModel}
|
||||
onSortModelChange={(newModel) => {
|
||||
setSortModel(newModel)
|
||||
localStorage.setItem('ecolageGridSort', JSON.stringify(newModel))
|
||||
}}
|
||||
paginationModel={paginationModel}
|
||||
onPaginationModelChange={(newModel) => {
|
||||
handlePaginationModelChange(newModel)
|
||||
localStorage.setItem('ecolageGridPagination', JSON.stringify(newModel))
|
||||
}}
|
||||
pageSizeOptions={pageSizeOptions}
|
||||
sx={{
|
||||
border: 0,
|
||||
width: 'auto',
|
||||
height: '50%',
|
||||
minHeight: 400,
|
||||
display: 'flex',
|
||||
justifyContent: 'center'
|
||||
}}
|
||||
slots={{ toolbar: GridToolbar }}
|
||||
localeText={frFR.components.MuiDataGrid.defaultProps.localeText}
|
||||
/>
|
||||
</div>
|
||||
</ThemeProvider>
|
||||
</Paper>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Ecolage
|
||||
@ -29,12 +29,15 @@ const Home = () => {
|
||||
const currentYear = dayjs().year()
|
||||
|
||||
useEffect(() => {
|
||||
// Fetch data and update state
|
||||
window.etudiants.getDataToDashboards().then((response) => {
|
||||
setEtudiants(response.etudiants)
|
||||
setOriginalEtudiants(response.etudiants)
|
||||
setNiveau(response.niveau)
|
||||
setAnnee_scolaire(response.anne_scolaire)
|
||||
window.niveaus.getNiveau().then((response) => {
|
||||
setNiveau(response || [])
|
||||
})
|
||||
window.anneescolaire.getAnneeScolaire().then((response) => {
|
||||
setAnnee_scolaire(response || [])
|
||||
})
|
||||
window.etudiants.getEtudiants().then((response) => {
|
||||
setEtudiants(response || [])
|
||||
setOriginalEtudiants(response || [])
|
||||
})
|
||||
}, [])
|
||||
|
||||
@ -60,14 +63,13 @@ const Home = () => {
|
||||
// Find the maximum value using Math.max
|
||||
const maxStudentCount = Math.max(...studentCounts)
|
||||
|
||||
const FilterAnneeScolaire = (e) => {
|
||||
let annee_scolaire = e.target.value
|
||||
const filteredEtudiants = originalEtudiants.filter(
|
||||
(etudiant) => etudiant.annee_scolaire === annee_scolaire
|
||||
)
|
||||
setEtudiants(filteredEtudiants)
|
||||
if (annee_scolaire == 'general') {
|
||||
const FilterAnneeScolaire = async (e) => {
|
||||
const annee = e.target.value
|
||||
if (annee === 'general') {
|
||||
setEtudiants(originalEtudiants)
|
||||
} else {
|
||||
const filtered = await window.etudiants.getEtudiantsByAnnee(annee)
|
||||
setEtudiants(filtered || [])
|
||||
}
|
||||
}
|
||||
// end filter all data
|
||||
@ -173,9 +175,9 @@ const Home = () => {
|
||||
<MenuItem value="general" selected>
|
||||
<em>Géneral</em>
|
||||
</MenuItem>
|
||||
{annee_scolaire.map((niveau) => (
|
||||
<MenuItem value={niveau.annee_scolaire} key={niveau.annee_scolaire}>
|
||||
{niveau.annee_scolaire}
|
||||
{annee_scolaire.map((a) => (
|
||||
<MenuItem value={a.code} key={a.id}>
|
||||
{a.code}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
|
||||
@ -12,153 +12,168 @@ import { Button, Modal, Box, Menu, MenuItem } from '@mui/material'
|
||||
import { Tooltip } from 'react-tooltip'
|
||||
import ReleverNotes from './ReleverNotes'
|
||||
import { FaDownload } from 'react-icons/fa'
|
||||
import getSemestre from './function/GetSemestre'
|
||||
|
||||
const Noteclasse = () => {
|
||||
const { niveau, scolaire } = useParams()
|
||||
|
||||
const [etudiants, setEtudiants] = useState([])
|
||||
const [mention, setMention] = useState([])
|
||||
const [session, setSession] = useState([])
|
||||
const [moyennesCalculees, setMoyennesCalculees] = useState([])
|
||||
|
||||
const formData = {
|
||||
niveau,
|
||||
scolaire
|
||||
}
|
||||
const formData = { niveau, scolaire }
|
||||
|
||||
useEffect(() => {
|
||||
window.notes.getMoyenne(formData).then((response) => {
|
||||
setEtudiants(response)
|
||||
})
|
||||
window.noteRepech.getMoyenneRepech(formData).then((response) => {
|
||||
setSession(response)
|
||||
})
|
||||
window.mention.getMention().then((response) => {
|
||||
setMention(response)
|
||||
})
|
||||
}, [])
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
// Récupérer la liste des étudiants
|
||||
const etudiantsData = await window.notes.getMoyenne(formData)
|
||||
setEtudiants(etudiantsData)
|
||||
|
||||
let dataToMap = []
|
||||
// Récupérer les mentions
|
||||
const mentionData = await window.mention.getMention()
|
||||
setMention(mentionData)
|
||||
|
||||
// Pour chaque étudiant, récupérer TOUTES ses notes avec noteRelerer
|
||||
const moyennes = []
|
||||
|
||||
for (let i = 0; i < etudiantsData.length; i++) {
|
||||
if (etudiantsData[i] && etudiantsData[i][0]) {
|
||||
const etudiantId = etudiantsData[i][0].etudiant_id
|
||||
const etudiantInfo = etudiantsData[i][0]
|
||||
|
||||
try {
|
||||
// Utiliser noteRelerer pour avoir TOUTES les matières
|
||||
const notesData = await window.notes.noteRelerer({
|
||||
id: etudiantId,
|
||||
anneescolaire: scolaire,
|
||||
niveau: niveau
|
||||
})
|
||||
|
||||
if (notesData && notesData.noteNormal && notesData.noteRepech && notesData.semestre) {
|
||||
// Fusionner les notes normales avec les semestres
|
||||
const updatedMatieres = notesData.noteNormal.map((matiere) => {
|
||||
const semesters = getSemestre(matiere.etudiant_niveau)
|
||||
const matchedSemestre = notesData.semestre.find(
|
||||
(sem) =>
|
||||
sem.matiere_id === matiere.matiere_id &&
|
||||
sem.mention_id === matiere.mention_id &&
|
||||
(sem.nom === semesters[0] || sem.nom === semesters[1])
|
||||
)
|
||||
|
||||
return {
|
||||
...matiere,
|
||||
semestre: matchedSemestre ? matchedSemestre.nom : null
|
||||
}
|
||||
})
|
||||
|
||||
const updatedMatieresRepech = notesData.noteRepech.map((matiere) => {
|
||||
const semesters = getSemestre(matiere.etudiant_niveau)
|
||||
const matchedSemestre = notesData.semestre.find(
|
||||
(sem) =>
|
||||
sem.matiere_id === matiere.matiere_id &&
|
||||
sem.mention_id === matiere.mention_id &&
|
||||
(sem.nom === semesters[0] || sem.nom === semesters[1])
|
||||
)
|
||||
|
||||
return {
|
||||
...matiere,
|
||||
semestre: matchedSemestre ? matchedSemestre.nom : null
|
||||
}
|
||||
})
|
||||
|
||||
// Fusionner notes normales et rattrapage
|
||||
updatedMatieres.forEach((item1) => {
|
||||
let matchingItem = updatedMatieresRepech.find(
|
||||
(item2) => item2.matiere_id === item1.matiere_id
|
||||
)
|
||||
item1.noterepech = matchingItem ? matchingItem.note : null
|
||||
})
|
||||
|
||||
// Calculer la moyenne pondérée
|
||||
const moyenne = calculerMoyennePonderee(updatedMatieres)
|
||||
|
||||
moyennes.push({
|
||||
id: etudiantId,
|
||||
nom: etudiantInfo.nom,
|
||||
prenom: etudiantInfo.prenom,
|
||||
photos: etudiantInfo.photos,
|
||||
mention: etudiantInfo.mention_id,
|
||||
anneescolaire: etudiantInfo.annee_scolaire,
|
||||
moyenne: moyenne.toFixed(2),
|
||||
aRattrapage: updatedMatieres.some(m => m.noterepech !== null && Number(m.noterepech) > 0)
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Erreur pour l'étudiant ${etudiantId}:`, error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setMoyennesCalculees(moyennes)
|
||||
console.log('=== MOYENNES CALCULÉES ===')
|
||||
console.log('Nombre d\'étudiants:', moyennes.length)
|
||||
if (moyennes.length > 0) {
|
||||
console.log('Premier étudiant:', moyennes[0])
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Erreur lors du chargement des données:', error)
|
||||
}
|
||||
}
|
||||
|
||||
fetchData()
|
||||
}, [niveau, scolaire])
|
||||
|
||||
function returnmention(id) {
|
||||
let mentions
|
||||
for (let index = 0; index < mention.length; index++) {
|
||||
if (mention[index].id == id) {
|
||||
mentions = mention[index].nom
|
||||
}
|
||||
}
|
||||
return mentions
|
||||
const found = mention.find((m) => m.id === id)
|
||||
return found ? found.nom : ''
|
||||
}
|
||||
|
||||
function checkNull(params) {
|
||||
console.log(params);
|
||||
if (params == null || params == undefined) {
|
||||
return null
|
||||
}
|
||||
return params
|
||||
}
|
||||
// ========================================
|
||||
// FONCTION CENTRALISÉE POUR CALCUL DE MOYENNE PONDÉRÉE
|
||||
// ========================================
|
||||
const calculerMoyennePonderee = (matieres) => {
|
||||
let totalNotesPonderees = 0
|
||||
let totalCredits = 0
|
||||
|
||||
// MODIFICATION: Nouvelle fonction pour calculer la moyenne avec rattrapage
|
||||
function compareSessionNotesForAverage(session1, session2) {
|
||||
// Si il y a une session de rattrapage, utiliser la meilleure note
|
||||
if (session2) {
|
||||
return Math.max(session1, session2.note)
|
||||
}
|
||||
// Sinon utiliser la note normale
|
||||
return session1
|
||||
}
|
||||
matieres.forEach((matiere) => {
|
||||
let noteFinale
|
||||
|
||||
function compareSessionNotes(session1, session2) {
|
||||
let notes
|
||||
if (session2) {
|
||||
if (session1 < session2.note) {
|
||||
notes = session2.note
|
||||
// IMPORTANT: Traiter 0 comme null (pas de rattrapage)
|
||||
const noteRepechValide = matiere.noterepech !== null
|
||||
&& matiere.noterepech !== undefined
|
||||
&& Number(matiere.noterepech) > 0
|
||||
|
||||
// TOUJOURS prendre la meilleure note si rattrapage VALIDE existe
|
||||
if (noteRepechValide) {
|
||||
noteFinale = Math.max(Number(matiere.note), Number(matiere.noterepech))
|
||||
} else {
|
||||
notes = session1
|
||||
noteFinale = Number(matiere.note)
|
||||
}
|
||||
} else {
|
||||
notes = session1
|
||||
}
|
||||
return notes
|
||||
}
|
||||
|
||||
for (let index = 0; index < etudiants.length; index++) {
|
||||
let total = 0
|
||||
let note = 0
|
||||
let totalCredit = 0
|
||||
|
||||
// Create a new object for each student
|
||||
let modelJson = {
|
||||
id: '',
|
||||
nom: '',
|
||||
prenom: '',
|
||||
photos: '',
|
||||
moyenne: '',
|
||||
mention: '',
|
||||
anneescolaire: ''
|
||||
}
|
||||
|
||||
for (let j = 0; j < etudiants[index].length; j++) {
|
||||
modelJson.id = etudiants[index][j].etudiant_id
|
||||
modelJson.nom = etudiants[index][j].nom
|
||||
modelJson.prenom = etudiants[index][j].prenom
|
||||
modelJson.photos = etudiants[index][j].photos
|
||||
modelJson.mention = etudiants[index][j].mention_id
|
||||
modelJson.anneescolaire = etudiants[index][j].annee_scolaire
|
||||
|
||||
// MODIFICATION: Utiliser la meilleure note (rattrapage si existe) pour la moyenne générale
|
||||
if (session[index]) {
|
||||
note +=
|
||||
compareSessionNotesForAverage(etudiants[index][j].note, checkNull(session[index][j])) *
|
||||
etudiants[index][j].credit
|
||||
} else {
|
||||
note += etudiants[index][j].note * etudiants[index][j].credit
|
||||
if (noteFinale != null && !isNaN(noteFinale)) {
|
||||
totalNotesPonderees += noteFinale * Number(matiere.credit)
|
||||
totalCredits += Number(matiere.credit)
|
||||
}
|
||||
totalCredit += etudiants[index][j].credit
|
||||
}
|
||||
})
|
||||
|
||||
total = note / totalCredit
|
||||
modelJson.moyenne = total.toFixed(2)
|
||||
|
||||
// Add the new object to the array
|
||||
dataToMap.push(modelJson)
|
||||
return totalCredits > 0 ? totalNotesPonderees / totalCredits : 0
|
||||
}
|
||||
|
||||
function checkNumberSession(id) {
|
||||
let sessionNumber = 1
|
||||
for (let index = 0; index < session.length; index++) {
|
||||
for (let j = 0; j < session[index].length; j++) {
|
||||
if (session[index][j].etudiant_id == id) {
|
||||
sessionNumber = 2
|
||||
break
|
||||
}
|
||||
}
|
||||
if (sessionNumber === 2) break
|
||||
}
|
||||
return sessionNumber
|
||||
const etudiant = moyennesCalculees.find(e => e.id === id)
|
||||
return etudiant && etudiant.aRattrapage ? 2 : 1
|
||||
}
|
||||
|
||||
const theme = createTheme({
|
||||
components: {
|
||||
MuiIconButton: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
color: 'gray' // Change the color of toolbar icons
|
||||
}
|
||||
}
|
||||
},
|
||||
MuiButton: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
color: '#121212' // Change the color of toolbar icons
|
||||
}
|
||||
}
|
||||
}
|
||||
MuiIconButton: { styleOverrides: { root: { color: 'gray' } } },
|
||||
MuiButton: { styleOverrides: { root: { color: '#121212' } } }
|
||||
}
|
||||
})
|
||||
|
||||
const paginationModel = { page: 0, pageSize: 5 }
|
||||
|
||||
// États pour le menu déroulant
|
||||
const [anchorEl, setAnchorEl] = useState(null)
|
||||
const [selectedStudentId, setSelectedStudentId] = useState(null)
|
||||
const open = Boolean(anchorEl)
|
||||
@ -167,17 +182,92 @@ const Noteclasse = () => {
|
||||
setAnchorEl(event.currentTarget)
|
||||
setSelectedStudentId(studentId)
|
||||
}
|
||||
|
||||
const handleMenuClose = () => {
|
||||
setAnchorEl(null)
|
||||
setSelectedStudentId(null)
|
||||
}
|
||||
|
||||
const handleSessionTypeSelect = (sessionType) => {
|
||||
sendData(selectedStudentId, sessionType)
|
||||
handleMenuClose()
|
||||
}
|
||||
|
||||
const [openCard, setOpenCart] = useState(false)
|
||||
const [downloadTrigger, setDownloadTrigger] = useState(false)
|
||||
const [form, setForm] = useState({ id: '', niveau: '', anneescolaire: '', sessionType: 'ensemble' })
|
||||
const [selectedId, setSelectedId] = useState(null)
|
||||
|
||||
const sendData = (id, sessionType = 'ensemble') => {
|
||||
setSelectedId(id)
|
||||
setForm((prev) => ({ ...prev, sessionType }))
|
||||
setOpenCart(true)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedId !== null) {
|
||||
const foundData = moyennesCalculees.find((item) => item.id === selectedId)
|
||||
if (foundData) {
|
||||
setForm((prev) => ({
|
||||
...prev,
|
||||
id: foundData.id,
|
||||
anneescolaire: foundData.anneescolaire,
|
||||
niveau
|
||||
}))
|
||||
}
|
||||
}
|
||||
}, [openCard, selectedId])
|
||||
|
||||
const handleCloseCart = () => {
|
||||
setDownloadTrigger(false)
|
||||
setOpenCart(false)
|
||||
}
|
||||
|
||||
const modalReleverNotes = () => (
|
||||
<Modal open={openCard} onClose={handleCloseCart} aria-labelledby="modal-title">
|
||||
<Box
|
||||
sx={{
|
||||
boxShadow: 24,
|
||||
p: 4,
|
||||
pl: 0,
|
||||
maxHeight: '100vh',
|
||||
overflowY: 'auto',
|
||||
position: 'relative'
|
||||
}}
|
||||
>
|
||||
<ReleverNotes
|
||||
id={form.id}
|
||||
anneescolaire={scolaire}
|
||||
niveau={form.niveau}
|
||||
sessionType={form.sessionType}
|
||||
refs={downloadTrigger}
|
||||
/>
|
||||
<Button
|
||||
color="warning"
|
||||
variant="contained"
|
||||
onClick={() => setDownloadTrigger(true)}
|
||||
sx={{ position: 'absolute', top: '2%', right: '10%' }}
|
||||
>
|
||||
<FaDownload />
|
||||
Télécharger
|
||||
</Button>
|
||||
<button
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: '2.5%',
|
||||
right: '23%',
|
||||
border: 'none',
|
||||
background: 'orange',
|
||||
borderRadius: '100px',
|
||||
padding: '5px 10px',
|
||||
cursor: 'pointer'
|
||||
}}
|
||||
onClick={handleCloseCart}
|
||||
>
|
||||
X
|
||||
</button>
|
||||
</Box>
|
||||
</Modal>
|
||||
)
|
||||
|
||||
const columns = [
|
||||
{ field: 'nom', headerName: 'Nom', width: 170 },
|
||||
{ field: 'prenom', headerName: 'Prénom', width: 160 },
|
||||
@ -190,7 +280,7 @@ const Noteclasse = () => {
|
||||
width: 100,
|
||||
renderCell: (params) => (
|
||||
<img
|
||||
src={params.value} // Correct the access to the image source
|
||||
src={params.value}
|
||||
alt={'image pdp'}
|
||||
style={{ width: 50, height: 50, borderRadius: '50%', objectFit: 'cover' }}
|
||||
/>
|
||||
@ -222,7 +312,7 @@ const Noteclasse = () => {
|
||||
}
|
||||
]
|
||||
|
||||
const dataTable = dataToMap.map((data) => ({
|
||||
const dataTable = moyennesCalculees.map((data) => ({
|
||||
id: data.id,
|
||||
nom: data.nom,
|
||||
prenom: data.prenom,
|
||||
@ -233,139 +323,22 @@ const Noteclasse = () => {
|
||||
action: data.id
|
||||
}))
|
||||
|
||||
const [openCard, setOpenCart] = useState(false)
|
||||
const [bolll, setBolll] = useState(false)
|
||||
const [form, setForm] = useState({
|
||||
id: '',
|
||||
niveau: '',
|
||||
anneescolaire: '',
|
||||
sessionType: 'ensemble' // Par défaut
|
||||
})
|
||||
const [selectedId, setSelectedId] = useState(null) // Store id dynamically
|
||||
|
||||
const sendData = (id, sessionType = 'ensemble') => {
|
||||
setSelectedId(id)
|
||||
setForm(prevForm => ({
|
||||
...prevForm,
|
||||
sessionType: sessionType
|
||||
}))
|
||||
setOpenCart(true)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedId !== null) {
|
||||
const foundData = dataToMap.find((item) => item.id === selectedId)
|
||||
if (foundData) {
|
||||
setForm((prevForm) => ({
|
||||
...prevForm,
|
||||
id: foundData.id,
|
||||
anneescolaire: foundData.anneescolaire,
|
||||
niveau: niveau
|
||||
})) // Update form with the found object
|
||||
}
|
||||
}
|
||||
}, [openCard, selectedId])
|
||||
|
||||
console.log(form)
|
||||
|
||||
const downloadButton = () => {
|
||||
setBolll(true)
|
||||
}
|
||||
|
||||
/**
|
||||
* function to close modal
|
||||
*/
|
||||
const handleCloseCart = () => {
|
||||
setBolll(false)
|
||||
setOpenCart(false)
|
||||
}
|
||||
|
||||
const modalReleverNotes = () => {
|
||||
return (
|
||||
<Modal
|
||||
open={openCard}
|
||||
onClose={handleCloseCart}
|
||||
aria-labelledby="modal-title"
|
||||
aria-describedby="modal-description"
|
||||
sx={{
|
||||
overflow: 'auto'
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
boxShadow: 24,
|
||||
p: 4,
|
||||
overflow: 'auto',
|
||||
position: 'relative'
|
||||
}}
|
||||
>
|
||||
<ReleverNotes
|
||||
id={form.id}
|
||||
anneescolaire={scolaire}
|
||||
niveau={form.niveau}
|
||||
sessionType={form.sessionType}
|
||||
refs={bolll}
|
||||
/>
|
||||
<Button
|
||||
color="warning"
|
||||
variant="contained"
|
||||
onClick={downloadButton}
|
||||
sx={{ position: 'absolute', top: '2%', right: '10%' }}
|
||||
>
|
||||
<FaDownload />
|
||||
Télécharger
|
||||
</Button>
|
||||
<button
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: '2.5%',
|
||||
right: '23%',
|
||||
border: 'none',
|
||||
background: 'orange',
|
||||
outline: 'none',
|
||||
borderRadius: '100px'
|
||||
}}
|
||||
onClick={handleCloseCart}
|
||||
>
|
||||
X
|
||||
</button>
|
||||
</Box>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={classe.mainHome}>
|
||||
{modalReleverNotes()}
|
||||
|
||||
{/* Menu pour sélectionner le type de session */}
|
||||
<Menu
|
||||
anchorEl={anchorEl}
|
||||
open={open}
|
||||
onClose={handleMenuClose}
|
||||
MenuListProps={{
|
||||
'aria-labelledby': 'session-button',
|
||||
}}
|
||||
>
|
||||
<MenuItem onClick={() => handleSessionTypeSelect('normale')}>
|
||||
Session Normale
|
||||
</MenuItem>
|
||||
<MenuItem onClick={() => handleSessionTypeSelect('ensemble')}>
|
||||
Session Rattrapage
|
||||
</MenuItem>
|
||||
<Menu anchorEl={anchorEl} open={open} onClose={handleMenuClose}>
|
||||
<MenuItem onClick={() => handleSessionTypeSelect('normale')}>Session Normale</MenuItem>
|
||||
<MenuItem onClick={() => handleSessionTypeSelect('ensemble')}>Session Rattrapage</MenuItem>
|
||||
</Menu>
|
||||
|
||||
<div className={classeHome.header}>
|
||||
<div className={classe.h1style}>
|
||||
<div className={classeHome.blockTitle}>
|
||||
<h1>
|
||||
Notes des {niveau} en {scolaire}
|
||||
</h1>
|
||||
<h1>Notes des {niveau} en {scolaire}</h1>
|
||||
<div style={{ display: 'flex', gap: '10px' }}>
|
||||
<Link to={`/resultat/${niveau}/${scolaire}`}>
|
||||
<Button color="warning" variant="contained">
|
||||
Resultat
|
||||
</Button>
|
||||
<Button color="warning" variant="contained">Resultat</Button>
|
||||
</Link>
|
||||
<Link to={'#'} onClick={() => window.history.back()}>
|
||||
<Button color="warning" variant="contained">
|
||||
@ -377,50 +350,22 @@ const Noteclasse = () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={classeHome.boxEtudiantsCard}>
|
||||
<div style={{ display: 'flex', justifyContent: 'center' }}>
|
||||
<Paper
|
||||
sx={{
|
||||
height: 'auto', // Auto height to make the grid responsive
|
||||
width: '100%',
|
||||
minHeight: 500, // Ensures a minimum height
|
||||
display: 'flex'
|
||||
}}
|
||||
>
|
||||
<ThemeProvider theme={theme}>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
width: '100%', // Make it responsive to full width
|
||||
flexDirection: 'column' // Stacks content vertically on smaller screens
|
||||
}}
|
||||
>
|
||||
<DataGrid
|
||||
rows={dataTable}
|
||||
columns={columns}
|
||||
initialState={{ pagination: { paginationModel } }}
|
||||
pageSizeOptions={[5, 10]}
|
||||
sx={{
|
||||
border: 0,
|
||||
width: '100%', // Ensures the DataGrid takes full width
|
||||
height: '50%', // Ensures it grows to fit content
|
||||
minHeight: 400, // Minimum height for the DataGrid
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
'@media (max-width: 600px)': {
|
||||
width: '100%', // 100% width on small screens
|
||||
height: 'auto' // Allow height to grow with content
|
||||
}
|
||||
}}
|
||||
slots={{ toolbar: GridToolbar }}
|
||||
localeText={frFR.components.MuiDataGrid.defaultProps.localeText}
|
||||
/>
|
||||
</div>
|
||||
</ThemeProvider>
|
||||
</Paper>
|
||||
</div>
|
||||
<div className={classeHome.boxEtudiantsCard} style={{ maxHeight: '70vh', overflowY: 'auto' }}>
|
||||
<Paper sx={{ height: 'auto', width: '100%', minHeight: 500 }}>
|
||||
<ThemeProvider theme={theme}>
|
||||
<DataGrid
|
||||
rows={dataTable}
|
||||
columns={columns}
|
||||
initialState={{ pagination: { paginationModel } }}
|
||||
pageSizeOptions={[5, 10, 20]}
|
||||
slots={{ toolbar: GridToolbar }}
|
||||
localeText={frFR.components.MuiDataGrid.defaultProps.localeText}
|
||||
autoHeight={false}
|
||||
sx={{ minHeight: 500 }}
|
||||
loading={moyennesCalculees.length === 0}
|
||||
/>
|
||||
</ThemeProvider>
|
||||
</Paper>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@ -14,55 +14,46 @@ const ReleverNotes = ({ id, anneescolaire, niveau, sessionType = 'ensemble', ref
|
||||
const [etudiant, setEtudiant] = useState([])
|
||||
const [matieres, setMatieres] = useState([])
|
||||
const [notes, setNotes] = useState([])
|
||||
|
||||
// Fonction pour vérifier si les crédits doivent être affichés
|
||||
const shouldShowCredits = () => {
|
||||
return niveau !== 'L1' && niveau !== 'L2'
|
||||
}
|
||||
const [noteSysteme, setNoteSysteme] = useState(null)
|
||||
|
||||
const handleDownloadPDF = async () => {
|
||||
const input = Telever.current
|
||||
|
||||
// Set a high scale for better quality
|
||||
const scale = 3
|
||||
|
||||
html2Canvas(input, {
|
||||
scale, // Increase resolution
|
||||
useCORS: true, // Handle cross-origin images
|
||||
scale,
|
||||
useCORS: true,
|
||||
allowTaint: true
|
||||
}).then((canvas) => {
|
||||
const imgData = canvas.toDataURL('image/png')
|
||||
|
||||
// Create a PDF with dimensions matching the captured content
|
||||
const pdf = new jsPDF({
|
||||
orientation: 'portrait',
|
||||
unit: 'mm',
|
||||
format: 'a4'
|
||||
})
|
||||
|
||||
const imgWidth = 210 // A4 width in mm
|
||||
const pageHeight = 297 // A4 height in mm
|
||||
const imgHeight = (canvas.height * imgWidth) / canvas.width
|
||||
const pageWidth = 210
|
||||
const pageHeight = 297
|
||||
const margin = 5
|
||||
const printWidth = pageWidth - margin * 2
|
||||
const imgHeight = (canvas.height * printWidth) / canvas.width
|
||||
|
||||
let position = 0
|
||||
|
||||
pdf.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight, '', 'FAST')
|
||||
|
||||
// Handle multi-page case
|
||||
while (position + imgHeight >= pageHeight) {
|
||||
position -= pageHeight
|
||||
pdf.addPage()
|
||||
pdf.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight, '', 'FAST')
|
||||
if (imgHeight > pageHeight - margin * 2) {
|
||||
const ratio = (pageHeight - margin * 2) / imgHeight
|
||||
const adjustedWidth = printWidth * ratio
|
||||
const adjustedHeight = pageHeight - margin * 2
|
||||
const xOffset = (pageWidth - adjustedWidth) / 2
|
||||
pdf.addImage(imgData, 'PNG', xOffset, margin, adjustedWidth, adjustedHeight, '', 'FAST')
|
||||
} else {
|
||||
pdf.addImage(imgData, 'PNG', margin, margin, printWidth, imgHeight, '', 'FAST')
|
||||
}
|
||||
|
||||
pdf.save('document.pdf')
|
||||
pdf.save('releve_de_notes.pdf')
|
||||
})
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!id) {
|
||||
// id doesn't exist, you might want to retry, or do nothing
|
||||
// For example, refetch later or show an error
|
||||
return
|
||||
}
|
||||
window.etudiants.getSingle({ id }).then((response) => {
|
||||
@ -76,6 +67,10 @@ const ReleverNotes = ({ id, anneescolaire, niveau, sessionType = 'ensemble', ref
|
||||
window.notes.noteRelerer({ id, anneescolaire, niveau }).then((response) => {
|
||||
setNotes(response)
|
||||
})
|
||||
|
||||
window.notesysteme.getSysteme().then((response) => {
|
||||
if (response) setNoteSysteme(response)
|
||||
})
|
||||
}, [id])
|
||||
|
||||
const Telever = useRef()
|
||||
@ -90,42 +85,35 @@ const ReleverNotes = ({ id, anneescolaire, niveau, sessionType = 'ensemble', ref
|
||||
const [matiereWithSemestreRepech, setMatiereWithSemestreRepech] = useState([])
|
||||
|
||||
useEffect(() => {
|
||||
if (!notes.noteNormal || !notes.semestre || !notes.noteRepech) return // Ensure data exists
|
||||
if (!notes.noteNormal || !notes.semestre || !notes.noteRepech) return
|
||||
|
||||
const updatedMatieres = notes.noteNormal.map((matiere) => {
|
||||
// Get the semesters based on the student's niveau
|
||||
const semesters = getSemestre(matiere.etudiant_niveau)
|
||||
|
||||
// Find the matched semestre based on the conditions
|
||||
const matchedSemestre = notes.semestre.find(
|
||||
(sem) =>
|
||||
sem.matiere_id === matiere.matiere_id &&
|
||||
sem.mention_id === matiere.mention_id &&
|
||||
(sem.nom === semesters[0] || sem.nom === semesters[1]) // Check if the semester matches
|
||||
(sem.nom === semesters[0] || sem.nom === semesters[1])
|
||||
)
|
||||
|
||||
return {
|
||||
...matiere,
|
||||
semestre: matchedSemestre ? matchedSemestre.nom : null // Add 'semestre' or set null if no match
|
||||
semestre: matchedSemestre ? matchedSemestre.nom : null
|
||||
}
|
||||
})
|
||||
|
||||
const updatedMatieresRepech = notes.noteRepech.map((matiere) => {
|
||||
// Get the semesters based on the student's niveau
|
||||
const semesters = getSemestre(matiere.etudiant_niveau)
|
||||
|
||||
// Find the matched semestre based on the conditions
|
||||
const matchedSemestre = notes.semestre.find(
|
||||
(sem) =>
|
||||
sem.matiere_id === matiere.matiere_id &&
|
||||
sem.mention_id === matiere.mention_id &&
|
||||
(sem.nom === semesters[0] || sem.nom === semesters[1]) // Check if the semester matches
|
||||
(sem.nom === semesters[0] || sem.nom === semesters[1])
|
||||
)
|
||||
|
||||
// Return the updated matiere with the matched semestre or null if no match
|
||||
return {
|
||||
...matiere,
|
||||
semestre: matchedSemestre ? matchedSemestre.nom : null // Add 'semestre' or set null if no match
|
||||
semestre: matchedSemestre ? matchedSemestre.nom : null
|
||||
}
|
||||
})
|
||||
|
||||
@ -135,62 +123,85 @@ const ReleverNotes = ({ id, anneescolaire, niveau, sessionType = 'ensemble', ref
|
||||
|
||||
function compareMention(mentionID) {
|
||||
let statusText
|
||||
|
||||
matieres.map((statu) => {
|
||||
if (mentionID == statu.id) {
|
||||
statusText = statu.nom
|
||||
}
|
||||
})
|
||||
|
||||
return statusText ? statusText.charAt(0).toUpperCase() + statusText.slice(1) : statusText
|
||||
}
|
||||
|
||||
// data are finaly get and ready for the traitement below
|
||||
|
||||
// Merging the arrays based on matiere_id
|
||||
// Fusion des notes normales et de rattrapage
|
||||
matiereWithSemestre.forEach((item1) => {
|
||||
// Find the corresponding item in array2 based on matiere_id
|
||||
let matchingItem = matiereWithSemestreRepech.find(
|
||||
(item2) => item2.matiere_id === item1.matiere_id
|
||||
)
|
||||
|
||||
// If there's a match, add noterepech from array2, otherwise use the note from array1
|
||||
item1.noterepech = matchingItem ? matchingItem.note : item1.note
|
||||
item1.noterepech = matchingItem ? matchingItem.note : null
|
||||
})
|
||||
|
||||
// step 1 group all by semestre
|
||||
const groupedDataBySemestre = matiereWithSemestre.reduce((acc, matiere) => {
|
||||
const { semestre } = matiere
|
||||
|
||||
if (!acc[semestre]) {
|
||||
acc[semestre] = []
|
||||
}
|
||||
|
||||
acc[semestre].push(matiere)
|
||||
|
||||
return acc
|
||||
}, {})
|
||||
|
||||
// MODIFICATION: Fonction compareMoyenne mise à jour
|
||||
const compareMoyenne = (normal, rattrapage, sessionType) => {
|
||||
if (sessionType === 'normale') {
|
||||
// Pour session normale: toujours évaluer selon la note normale uniquement
|
||||
return Number(normal) >= 10 ? 'Admis' : 'Ajourné'
|
||||
} else if (sessionType === 'rattrapage') {
|
||||
// Pour session rattrapage: évaluer selon la note de rattrapage
|
||||
return Number(rattrapage) >= 10 ? 'Admis' : 'Ajourné'
|
||||
} else {
|
||||
// Pour session ensemble: prendre la meilleure des deux notes
|
||||
const bestNote = Math.max(Number(normal), Number(rattrapage))
|
||||
return bestNote >= 10 ? 'Admis' : 'Ajourné'
|
||||
}
|
||||
// ========================================
|
||||
// FONCTION CENTRALISÉE POUR CALCUL DE MOYENNE PONDÉRÉE
|
||||
// ========================================
|
||||
const calculerMoyennePonderee = (matieres, utiliserRattrapage = true) => {
|
||||
let totalNotesPonderees = 0
|
||||
let totalCredits = 0
|
||||
|
||||
matieres.forEach((matiere) => {
|
||||
let noteFinale
|
||||
|
||||
// IMPORTANT: Traiter 0 comme null (pas de rattrapage)
|
||||
const noteRepechValide = matiere.noterepech !== null
|
||||
&& matiere.noterepech !== undefined
|
||||
&& Number(matiere.noterepech) > 0
|
||||
|
||||
// Si on doit utiliser le rattrapage ET qu'il existe, prendre la meilleure note
|
||||
if (utiliserRattrapage && noteRepechValide) {
|
||||
noteFinale = Math.max(Number(matiere.note), Number(matiere.noterepech))
|
||||
} else {
|
||||
// Sinon, prendre uniquement la note normale
|
||||
noteFinale = Number(matiere.note)
|
||||
}
|
||||
|
||||
if (noteFinale != null && !isNaN(noteFinale)) {
|
||||
totalNotesPonderees += noteFinale * Number(matiere.credit)
|
||||
totalCredits += Number(matiere.credit)
|
||||
}
|
||||
})
|
||||
|
||||
return totalCredits > 0 ? totalNotesPonderees / totalCredits : 0
|
||||
}
|
||||
|
||||
// Fonction pour obtenir la meilleure note entre normale et rattrapage
|
||||
const getBestNote = (noteNormale, noteRattrapage) => {
|
||||
// Traiter 0 comme null (pas de rattrapage valide)
|
||||
if (noteRattrapage === null || noteRattrapage === undefined || Number(noteRattrapage) <= 0) {
|
||||
return noteNormale
|
||||
}
|
||||
return Math.max(Number(noteNormale), Number(noteRattrapage))
|
||||
}
|
||||
|
||||
// Fonction pour déterminer Admis/Ajourné
|
||||
// Fonction pour déterminer Admis/Ajourné
|
||||
const compareMoyenne = (matieres, sessionType) => {
|
||||
const utiliserRattrapage = sessionType === 'rattrapage' || sessionType === 'ensemble'
|
||||
const moyenne = calculerMoyennePonderee(matieres, utiliserRattrapage)
|
||||
if (!noteSysteme) return ''
|
||||
return moyenne >= noteSysteme.admis ? 'Admis' : 'Ajourné'
|
||||
}
|
||||
|
||||
const TbodyContent = () => {
|
||||
return (
|
||||
<>
|
||||
{Object.entries(groupedDataBySemestre).map(([semestre, matieres]) => {
|
||||
// Group by unite_enseignement inside each semestre
|
||||
const groupedByUnite = matieres.reduce((acc, matiere) => {
|
||||
if (!acc[matiere.ue]) {
|
||||
acc[matiere.ue] = []
|
||||
@ -205,7 +216,6 @@ const ReleverNotes = ({ id, anneescolaire, niveau, sessionType = 'ensemble', ref
|
||||
<>
|
||||
{matieres.map((matiere, matiereIndex) => (
|
||||
<tr key={matiere.id} style={{ border: 'none' }}>
|
||||
{/* Display 'semestre' only for the first row of the first unite_enseignement */}
|
||||
{uniteIndex === 0 && matiereIndex === 0 && (
|
||||
<td
|
||||
rowSpan={
|
||||
@ -225,7 +235,6 @@ const ReleverNotes = ({ id, anneescolaire, niveau, sessionType = 'ensemble', ref
|
||||
</td>
|
||||
)}
|
||||
|
||||
{/* Display 'unite_enseignement' only for the first row of each group */}
|
||||
{matiereIndex === 0 && (
|
||||
<td
|
||||
rowSpan={matieres.length}
|
||||
@ -240,12 +249,11 @@ const ReleverNotes = ({ id, anneescolaire, niveau, sessionType = 'ensemble', ref
|
||||
</td>
|
||||
)}
|
||||
|
||||
{/* Matiere Data */}
|
||||
<td style={{ borderRight: 'solid 1px black', borderTop: 'solid 1px black' }}>
|
||||
<td style={{ borderRight: 'solid 1px black', borderTop: 'solid 1px black', textAlign: 'left', paddingLeft: '4px' }}>
|
||||
{matiere.nom}
|
||||
</td>
|
||||
|
||||
{/* Affichage conditionnel des colonnes selon le type de session */}
|
||||
{/* SECTION NORMALE */}
|
||||
{sessionType !== 'rattrapage' && (
|
||||
<>
|
||||
<td style={{ borderRight: 'solid 1px black', borderTop: 'solid 1px black' }}>
|
||||
@ -254,7 +262,6 @@ 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}
|
||||
@ -266,24 +273,22 @@ const ReleverNotes = ({ id, anneescolaire, niveau, sessionType = 'ensemble', ref
|
||||
}}
|
||||
className="moyenneUENormale"
|
||||
>
|
||||
{(
|
||||
matieres.reduce((total, matiere) => total + matiere.note, 0) /
|
||||
matieres.length
|
||||
).toFixed(2)}
|
||||
{calculerMoyennePonderee(matieres, false).toFixed(2)}
|
||||
</td>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* SECTION RATTRAPAGE */}
|
||||
{sessionType !== 'normale' && (
|
||||
<>
|
||||
<td style={{ borderRight: 'solid 1px black', borderTop: 'solid 1px black' }}>
|
||||
{matiere.credit}
|
||||
{matiere.noterepech !== null &&
|
||||
matiere.noterepech !== undefined &&
|
||||
Number(matiere.noterepech) > 0
|
||||
? matiere.noterepech
|
||||
: matiere.note}
|
||||
</td>
|
||||
<td style={{ borderRight: 'solid 1px black', borderTop: 'solid 1px black' }}>
|
||||
{matiere.noterepech}
|
||||
</td>
|
||||
{/* Moyenne UE pour session rattrapage */}
|
||||
{matiereIndex === 0 && (
|
||||
<td
|
||||
rowSpan={matieres.length}
|
||||
@ -295,16 +300,13 @@ const ReleverNotes = ({ id, anneescolaire, niveau, sessionType = 'ensemble', ref
|
||||
}}
|
||||
className="moyenneUERattrapage"
|
||||
>
|
||||
{(
|
||||
matieres.reduce((total, matiere) => total + matiere.noterepech, 0) /
|
||||
matieres.length
|
||||
).toFixed(2)}
|
||||
{calculerMoyennePonderee(matieres, true).toFixed(2)}
|
||||
</td>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Display the comparison value only once */}
|
||||
{/* OBSERVATION */}
|
||||
{matiereIndex === 0 && (
|
||||
<td
|
||||
rowSpan={matieres.length}
|
||||
@ -315,26 +317,14 @@ const ReleverNotes = ({ id, anneescolaire, niveau, sessionType = 'ensemble', ref
|
||||
borderTop: 'solid 1px black'
|
||||
}}
|
||||
>
|
||||
{compareMoyenne(
|
||||
(
|
||||
matieres.reduce((total, matiere) => total + matiere.note, 0) /
|
||||
matieres.length
|
||||
).toFixed(2),
|
||||
(
|
||||
matieres.reduce((total, matiere) => total + matiere.noterepech, 0) /
|
||||
matieres.length
|
||||
).toFixed(2),
|
||||
sessionType
|
||||
)}
|
||||
{compareMoyenne(matieres, sessionType)}
|
||||
</td>
|
||||
)}
|
||||
</tr>
|
||||
))}
|
||||
|
||||
{/* Add Total Row for 'unite_enseignement' */}
|
||||
<tr
|
||||
style={{ border: 'none', borderLeft: 'solid 1px black' }}
|
||||
>
|
||||
{/* Total Row */}
|
||||
<tr style={{ border: 'none', borderLeft: 'solid 1px black' }}>
|
||||
<td
|
||||
colSpan={2}
|
||||
style={{
|
||||
@ -359,7 +349,7 @@ const ReleverNotes = ({ id, anneescolaire, niveau, sessionType = 'ensemble', ref
|
||||
borderTop: 'solid 1px black'
|
||||
}}
|
||||
>
|
||||
{matieres.reduce((total, matiere) => total + matiere.credit, 0)}
|
||||
{matieres.reduce((total, matiere) => total + Number(matiere.credit), 0)}
|
||||
</td>
|
||||
<td
|
||||
colSpan={2}
|
||||
@ -383,7 +373,6 @@ const ReleverNotes = ({ id, anneescolaire, niveau, sessionType = 'ensemble', ref
|
||||
borderRight: 'solid 1px black'
|
||||
}}
|
||||
>
|
||||
{matieres.reduce((total, matiere) => total + matiere.credit, 0)}
|
||||
</td>
|
||||
<td
|
||||
colSpan={2}
|
||||
@ -415,52 +404,80 @@ const ReleverNotes = ({ id, anneescolaire, niveau, sessionType = 'ensemble', ref
|
||||
)
|
||||
}
|
||||
|
||||
// MODIFICATION: Fonction totalNotes mise à jour pour utiliser les nouvelles classes
|
||||
// Remplacer la fonction totalNotes() dans ReleverNotes.jsx par celle-ci :
|
||||
// ========================================
|
||||
// CALCUL DE LA MOYENNE GÉNÉRALE
|
||||
// TOUJOURS avec les meilleures notes (rattrapage si existe)
|
||||
// ========================================
|
||||
const totalNotes = () => {
|
||||
// Session NORMALE → uniquement note normale
|
||||
if (sessionType === 'normale') {
|
||||
return calculerMoyennePonderee(matiereWithSemestre, false)
|
||||
}
|
||||
|
||||
const totalNotes = () => {
|
||||
// Calculer la moyenne pondérée par les crédits selon le type de session
|
||||
let totalNotesPonderees = 0
|
||||
let totalCredits = 0
|
||||
|
||||
if (sessionType === 'normale') {
|
||||
// Utiliser uniquement les notes normales
|
||||
matiereWithSemestre.forEach((matiere) => {
|
||||
if (matiere.note != null && !isNaN(matiere.note)) {
|
||||
totalNotesPonderees += matiere.note * matiere.credit
|
||||
totalCredits += matiere.credit
|
||||
}
|
||||
})
|
||||
} else if (sessionType === 'rattrapage') {
|
||||
// Utiliser uniquement les notes de rattrapage
|
||||
matiereWithSemestre.forEach((matiere) => {
|
||||
if (matiere.noterepech != null && !isNaN(matiere.noterepech)) {
|
||||
totalNotesPonderees += matiere.noterepech * matiere.credit
|
||||
totalCredits += matiere.credit
|
||||
}
|
||||
})
|
||||
} else {
|
||||
// Pour 'ensemble', prendre la meilleure note entre normale et rattrapage pour chaque matière
|
||||
matiereWithSemestre.forEach((matiere) => {
|
||||
const noteNormale = matiere.note != null && !isNaN(matiere.note) ? matiere.note : 0
|
||||
const noteRattrapage = matiere.noterepech != null && !isNaN(matiere.noterepech) ? matiere.noterepech : 0
|
||||
const bestNote = Math.max(noteNormale, noteRattrapage)
|
||||
|
||||
if (bestNote > 0) {
|
||||
totalNotesPonderees += bestNote * matiere.credit
|
||||
totalCredits += matiere.credit
|
||||
}
|
||||
})
|
||||
// Session RATTRAPAGE ou ENSEMBLE → meilleure note
|
||||
return calculerMoyennePonderee(matiereWithSemestre, true)
|
||||
}
|
||||
|
||||
return totalCredits > 0 ? totalNotesPonderees / totalCredits : 0
|
||||
}
|
||||
|
||||
const [note, setNote] = useState(0)
|
||||
|
||||
useEffect(() => {
|
||||
setNote(totalNotes())
|
||||
}, [TbodyContent, sessionType])
|
||||
const moyenne = totalNotes()
|
||||
console.log('=== DEBUG RELEVER NOTES ===')
|
||||
console.log('SessionType:', sessionType)
|
||||
console.log('Nombre de matières:', matiereWithSemestre.length)
|
||||
|
||||
// Log détaillé de toutes les matières
|
||||
if (matiereWithSemestre.length > 0) {
|
||||
console.log('Première matière:', matiereWithSemestre[0])
|
||||
console.log('Détail des matières:')
|
||||
|
||||
// Calcul pour session NORMALE (sans rattrapage)
|
||||
let totalPondereNormal = 0
|
||||
let totalCreditNormal = 0
|
||||
|
||||
// Calcul pour session RATTRAPAGE (avec meilleures notes)
|
||||
let totalPondereRattrapage = 0
|
||||
let totalCreditRattrapage = 0
|
||||
|
||||
matiereWithSemestre.forEach((mat, idx) => {
|
||||
const noteRepechValide = mat.noterepech !== null
|
||||
&& mat.noterepech !== undefined
|
||||
&& Number(mat.noterepech) > 0
|
||||
const meilleureNote = noteRepechValide
|
||||
? Math.max(Number(mat.note), Number(mat.noterepech))
|
||||
: Number(mat.note)
|
||||
|
||||
console.log(`Matière ${idx + 1}:`, {
|
||||
nom: mat.nom,
|
||||
note: mat.note,
|
||||
noterepech: mat.noterepech,
|
||||
credit: mat.credit,
|
||||
meilleure: meilleureNote
|
||||
})
|
||||
|
||||
// Pour session normale : uniquement note normale
|
||||
totalPondereNormal += Number(mat.note) * Number(mat.credit)
|
||||
totalCreditNormal += Number(mat.credit)
|
||||
|
||||
// Pour session rattrapage : meilleure note
|
||||
totalPondereRattrapage += meilleureNote * Number(mat.credit)
|
||||
totalCreditRattrapage += Number(mat.credit)
|
||||
})
|
||||
|
||||
console.log('--- SESSION NORMALE ---')
|
||||
console.log('Total notes pondérées:', totalPondereNormal)
|
||||
console.log('Total crédits:', totalCreditNormal)
|
||||
console.log('Moyenne session normale:', (totalPondereNormal / totalCreditNormal).toFixed(2))
|
||||
|
||||
console.log('--- SESSION RATTRAPAGE ---')
|
||||
console.log('Total notes pondérées:', totalPondereRattrapage)
|
||||
console.log('Total crédits:', totalCreditRattrapage)
|
||||
console.log('Moyenne session rattrapage:', (totalPondereRattrapage / totalCreditRattrapage).toFixed(2))
|
||||
}
|
||||
|
||||
console.log('Moyenne générale (toujours avec rattrapage):', moyenne)
|
||||
setNote(moyenne)
|
||||
}, [matiereWithSemestre, sessionType])
|
||||
|
||||
return (
|
||||
<div className={classe.mainHome}>
|
||||
@ -468,53 +485,69 @@ const totalNotes = () => {
|
||||
<div style={{ display: 'flex', justifyContent: 'center' }}>
|
||||
<Paper
|
||||
sx={{
|
||||
height: 'auto',
|
||||
minHeight: 500,
|
||||
width: '210mm',
|
||||
minHeight: '297mm',
|
||||
display: 'flex',
|
||||
padding: '1%',
|
||||
width: '70%',
|
||||
padding: '10mm 12mm',
|
||||
marginTop: '2%',
|
||||
justifyContent: 'center'
|
||||
justifyContent: 'center',
|
||||
'@media print': {
|
||||
boxShadow: 'none',
|
||||
margin: 0,
|
||||
padding: '8mm 10mm'
|
||||
}
|
||||
}}
|
||||
ref={Telever}
|
||||
>
|
||||
<div style={{ width: '80%' }}>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
textAlign: 'center'
|
||||
}}
|
||||
>
|
||||
<img src={logoRelerev2} alt="logo gauche" width={90} />
|
||||
<div style={{ width: '100%' }}>
|
||||
<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 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>
|
||||
|
||||
<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
|
||||
</h4>
|
||||
<hr style={{ margin: 0, border: 'solid 1px black' }} />
|
||||
|
||||
{/* block info */}
|
||||
<div style={{ marginTop: '2px', display: 'flex' }}>
|
||||
{/* gauche */}
|
||||
<div style={{ display: 'flex', width: '60%' }}>
|
||||
{/* gauche gauche */}
|
||||
<div style={{ width: '40%' }}>
|
||||
<span>
|
||||
<b>Nom</b>
|
||||
@ -532,7 +565,6 @@ const totalNotes = () => {
|
||||
<b>Codage</b>
|
||||
</span>
|
||||
</div>
|
||||
{/* gauche droite */}
|
||||
<div style={{ width: '60%' }}>
|
||||
<span>: {etudiant.nom}</span>
|
||||
<br />
|
||||
@ -543,9 +575,7 @@ const totalNotes = () => {
|
||||
<span>: {etudiant.num_inscription}</span>
|
||||
</div>
|
||||
</div>
|
||||
{/* droite */}
|
||||
<div style={{ display: 'flex', width: '40%' }}>
|
||||
{/* droite gauche */}
|
||||
<div style={{ width: '30%' }}>
|
||||
<span>
|
||||
<b>Annee Sco</b>
|
||||
@ -559,7 +589,6 @@ const totalNotes = () => {
|
||||
<b>Parcours</b>
|
||||
</span>
|
||||
</div>
|
||||
{/* droite droite */}
|
||||
<div style={{ width: '70%' }}>
|
||||
<span>: {etudiant.annee_scolaire}</span>
|
||||
<br />
|
||||
@ -571,7 +600,7 @@ const totalNotes = () => {
|
||||
</div>
|
||||
|
||||
{/* table */}
|
||||
<table style={{ marginTop: '5px', borderCollapse: 'collapse' }}>
|
||||
<table style={{ marginTop: '5px', borderCollapse: 'collapse', width: '100%', fontSize: '14px' }}>
|
||||
<thead>
|
||||
<tr style={{ borderTop: 'solid 1px black', textAlign: 'center' }}>
|
||||
<th colSpan={3}></th>
|
||||
@ -592,19 +621,13 @@ const totalNotes = () => {
|
||||
<th style={{ borderLeft: 'solid 1px black' }}></th>
|
||||
|
||||
{sessionType !== 'rattrapage' && (
|
||||
<th
|
||||
colSpan={3}
|
||||
style={{ borderLeft: 'solid 1px black' }}
|
||||
>
|
||||
<th colSpan={3} style={{ borderLeft: 'solid 1px black' }}>
|
||||
Normale
|
||||
</th>
|
||||
)}
|
||||
|
||||
{sessionType !== 'normale' && (
|
||||
<th
|
||||
colSpan={3}
|
||||
style={{ borderLeft: 'solid 1px black' }}
|
||||
>
|
||||
<th colSpan={3} style={{ borderLeft: 'solid 1px black' }}>
|
||||
Rattrapage
|
||||
</th>
|
||||
)}
|
||||
@ -617,14 +640,26 @@ const totalNotes = () => {
|
||||
style={{
|
||||
borderTop: 'solid 1px black',
|
||||
textAlign: 'center',
|
||||
padding:'20px',
|
||||
padding: '20px'
|
||||
}}
|
||||
>
|
||||
<th style={{padding: '1%', 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' }}>Unités <br /> d'Enseignement <br />(UE) </th>
|
||||
<th style={{ borderLeft: 'solid 1px black' }}>Éléments <br /> constitutifs <br />(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' && (
|
||||
<>
|
||||
@ -636,7 +671,6 @@ const totalNotes = () => {
|
||||
|
||||
{sessionType !== 'normale' && (
|
||||
<>
|
||||
<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>
|
||||
</>
|
||||
@ -673,11 +707,13 @@ const totalNotes = () => {
|
||||
paddingLeft: '2%'
|
||||
}}
|
||||
>
|
||||
Mention:{' '}
|
||||
<span style={{ marginLeft: '3%' }}>{getmentionAfterNotes(note)}</span>
|
||||
Mention: <span style={{ marginLeft: '3%' }}>{getmentionAfterNotes(note)}</span>
|
||||
</td>
|
||||
<td colSpan={sessionType === 'ensemble' ? 6 : 4} style={{ textAlign: 'left', paddingLeft: '1%' }}>
|
||||
Décision du Jury:{' '}
|
||||
<td
|
||||
colSpan={sessionType === 'ensemble' ? 6 : 4}
|
||||
style={{ textAlign: 'left', paddingLeft: '1%' }}
|
||||
>
|
||||
Décision du Jury: <span style={{ marginLeft: '5px' }}>{descisionJury(note, etudiant.niveau, noteSysteme)}</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
@ -686,7 +722,6 @@ const totalNotes = () => {
|
||||
<p>
|
||||
<b>Toamasine le</b>
|
||||
</p>
|
||||
{/* texte hidden for place in signature */}
|
||||
<p style={{ visibility: 'hidden' }}>
|
||||
Lorem ipsum dolor sit amet consectetur adipisicing elit. Blanditiis delectus
|
||||
perspiciatis nisi aliquid eos adipisci cumque amet ratione error voluptatum.
|
||||
|
||||
@ -3,79 +3,154 @@ import { useParams, Link } from 'react-router-dom'
|
||||
import classe from '../assets/AllStyleComponents.module.css'
|
||||
import classeHome from '../assets/Home.module.css'
|
||||
import Paper from '@mui/material/Paper'
|
||||
import { Button, Modal, Box, Tabs, Tab, Select, MenuItem, FormControl, InputLabel } from '@mui/material'
|
||||
import { Button, Tabs, Tab, Select, MenuItem, FormControl, InputLabel } from '@mui/material'
|
||||
import { IoMdReturnRight } from 'react-icons/io'
|
||||
import jsPDF from 'jspdf'
|
||||
import autoTable from 'jspdf-autotable'
|
||||
import { FaDownload } from 'react-icons/fa'
|
||||
import logoRelerev1 from '../assets/logorelever.png'
|
||||
import logoRelerev2 from '../assets/logorelever2.png'
|
||||
import getSemestre from './function/GetSemestre'
|
||||
|
||||
const Resultat = () => {
|
||||
const { niveau, scolaire } = useParams()
|
||||
const formData = {
|
||||
niveau,
|
||||
scolaire
|
||||
}
|
||||
const formData = { niveau, scolaire }
|
||||
const [etudiants, setEtudiants] = useState([])
|
||||
const [mention, setMention] = useState([])
|
||||
const [session, setSession] = useState([])
|
||||
const [tabValue, setTabValue] = useState(0)
|
||||
|
||||
// États pour les sélections
|
||||
const [selectedMatiere, setSelectedMatiere] = useState('')
|
||||
const [selectedUE, setSelectedUE] = useState('')
|
||||
const [selectedMentionId, setSelectedMentionId] = useState('')
|
||||
const [availableMatieres, setAvailableMatieres] = useState([])
|
||||
const [availableUEs, setAvailableUEs] = useState([])
|
||||
const [moyennesRattrapage, setMoyennesRattrapage] = useState([])
|
||||
|
||||
useEffect(() => {
|
||||
window.notes.getMoyenne(formData).then((response) => {
|
||||
setEtudiants(response)
|
||||
extractMatieresAndUEs(response)
|
||||
})
|
||||
window.noteRepech.getMoyenneRepech(formData).then((response) => {
|
||||
setSession(response)
|
||||
})
|
||||
window.mention.getMention().then((response) => {
|
||||
setMention(response)
|
||||
})
|
||||
}, [])
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
const etudiantsData = await window.notes.getMoyenne(formData)
|
||||
setEtudiants(etudiantsData)
|
||||
extractMatieresAndUEs(etudiantsData)
|
||||
const sessionData = await window.noteRepech.getMoyenneRepech(formData)
|
||||
setSession(sessionData)
|
||||
const mentionData = await window.mention.getMention()
|
||||
setMention(mentionData)
|
||||
if (mentionData.length > 0) {
|
||||
setSelectedMentionId(mentionData[0].id)
|
||||
}
|
||||
await calculerMoyennesRattrapage(etudiantsData)
|
||||
} catch (error) {
|
||||
console.error('Erreur lors du chargement des données:', error)
|
||||
}
|
||||
}
|
||||
fetchData()
|
||||
}, [niveau, scolaire])
|
||||
|
||||
const calculerMoyennesRattrapage = async (etudiantsData) => {
|
||||
const moyennes = []
|
||||
for (let i = 0; i < etudiantsData.length; i++) {
|
||||
if (etudiantsData[i] && etudiantsData[i][0]) {
|
||||
const etudiantId = etudiantsData[i][0].etudiant_id
|
||||
const etudiantInfo = etudiantsData[i][0]
|
||||
try {
|
||||
const notesData = await window.notes.noteRelerer({
|
||||
id: etudiantId,
|
||||
anneescolaire: scolaire,
|
||||
niveau: niveau
|
||||
})
|
||||
if (notesData && notesData.noteNormal && notesData.noteRepech && notesData.semestre) {
|
||||
const updatedMatieres = notesData.noteNormal.map((matiere) => {
|
||||
const semesters = getSemestre(matiere.etudiant_niveau)
|
||||
const matchedSemestre = notesData.semestre.find(
|
||||
(sem) =>
|
||||
sem.matiere_id === matiere.matiere_id &&
|
||||
sem.mention_id === matiere.mention_id &&
|
||||
(sem.nom === semesters[0] || sem.nom === semesters[1])
|
||||
)
|
||||
return { ...matiere, semestre: matchedSemestre ? matchedSemestre.nom : null }
|
||||
})
|
||||
const updatedMatieresRepech = notesData.noteRepech.map((matiere) => {
|
||||
const semesters = getSemestre(matiere.etudiant_niveau)
|
||||
const matchedSemestre = notesData.semestre.find(
|
||||
(sem) =>
|
||||
sem.matiere_id === matiere.matiere_id &&
|
||||
sem.mention_id === matiere.mention_id &&
|
||||
(sem.nom === semesters[0] || sem.nom === semesters[1])
|
||||
)
|
||||
return { ...matiere, semestre: matchedSemestre ? matchedSemestre.nom : null }
|
||||
})
|
||||
let aRattrapage = false
|
||||
updatedMatieres.forEach((item1) => {
|
||||
let matchingItem = updatedMatieresRepech.find(
|
||||
(item2) => item2.matiere_id === item1.matiere_id
|
||||
)
|
||||
item1.noterepech = matchingItem ? matchingItem.note : null
|
||||
if (item1.noterepech !== null && item1.noterepech !== undefined && Number(item1.noterepech) > 0) {
|
||||
aRattrapage = true
|
||||
}
|
||||
})
|
||||
if (aRattrapage) {
|
||||
const moyenne = calculerMoyennePonderee(updatedMatieres)
|
||||
moyennes.push({
|
||||
id: etudiantId,
|
||||
nom: etudiantInfo.nom,
|
||||
prenom: etudiantInfo.prenom,
|
||||
mention: etudiantInfo.mention_id,
|
||||
moyenne: moyenne.toFixed(2),
|
||||
admis: moyenne >= 10
|
||||
})
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Erreur pour l'étudiant ${etudiantId}:`, error)
|
||||
}
|
||||
}
|
||||
}
|
||||
setMoyennesRattrapage(moyennes)
|
||||
}
|
||||
|
||||
const calculerMoyennePonderee = (matieres) => {
|
||||
let totalNotesPonderees = 0
|
||||
let totalCredits = 0
|
||||
matieres.forEach((matiere) => {
|
||||
let noteFinale
|
||||
const noteRepechValide = matiere.noterepech !== null && matiere.noterepech !== undefined && Number(matiere.noterepech) > 0
|
||||
if (noteRepechValide) {
|
||||
noteFinale = Math.max(Number(matiere.note), Number(matiere.noterepech))
|
||||
} else {
|
||||
noteFinale = Number(matiere.note)
|
||||
}
|
||||
if (noteFinale != null && !isNaN(noteFinale)) {
|
||||
totalNotesPonderees += noteFinale * Number(matiere.credit)
|
||||
totalCredits += Number(matiere.credit)
|
||||
}
|
||||
})
|
||||
return totalCredits > 0 ? totalNotesPonderees / totalCredits : 0
|
||||
}
|
||||
|
||||
// Fonction pour extraire les matières et UEs disponibles
|
||||
const extractMatieresAndUEs = (data) => {
|
||||
const matieres = new Set()
|
||||
const matieres = new Map() // key=nomMat, value=mention_id
|
||||
const ues = new Set()
|
||||
|
||||
for (let index = 0; index < data.length; index++) {
|
||||
for (let j = 0; j < data[index].length; j++) {
|
||||
const matiere = data[index][j].matiere || `Matière ${j + 1}`
|
||||
const nomMat = data[index][j].nomMat || `Matière ${j + 1}`
|
||||
const ue = data[index][j].ue || `UE${Math.floor(j / 2) + 1}`
|
||||
matieres.add(matiere)
|
||||
if (!matieres.has(nomMat)) matieres.set(nomMat, data[index][j].mention_id)
|
||||
ues.add(ue)
|
||||
}
|
||||
}
|
||||
|
||||
setAvailableMatieres(Array.from(matieres))
|
||||
setAvailableMatieres(Array.from(matieres.keys()))
|
||||
setAvailableUEs(Array.from(ues))
|
||||
|
||||
// Sélectionner la première matière et UE par défaut
|
||||
if (matieres.size > 0) setSelectedMatiere(Array.from(matieres)[0])
|
||||
if (matieres.size > 0) setSelectedMatiere(Array.from(matieres.keys())[0])
|
||||
if (ues.size > 0) setSelectedUE(Array.from(ues)[0])
|
||||
}
|
||||
|
||||
let dataToMap = []
|
||||
|
||||
function returnmention(id) {
|
||||
let mentions
|
||||
for (let index = 0; index < mention.length; index++) {
|
||||
if (mention[index].id == id) {
|
||||
mentions = mention[index].nom
|
||||
}
|
||||
}
|
||||
return mentions
|
||||
const found = mention.find((m) => m.id === id)
|
||||
return found ? found.nom : ''
|
||||
}
|
||||
|
||||
// Fonction pour déterminer la mention selon la moyenne
|
||||
function getMentionFromMoyenne(moyenne) {
|
||||
const moy = parseFloat(moyenne)
|
||||
if (moy >= 18) return 'Excellent'
|
||||
@ -87,102 +162,86 @@ const Resultat = () => {
|
||||
return 'Remise à la famille'
|
||||
}
|
||||
|
||||
function checkNull(params) {
|
||||
if (params == null || params == undefined) {
|
||||
return null
|
||||
}
|
||||
return params
|
||||
}
|
||||
|
||||
function compareSessionNotes(session1, session2) {
|
||||
let notes
|
||||
if (session2) {
|
||||
if (session1 < session2.note) {
|
||||
notes = session2.note
|
||||
} else {
|
||||
notes = session1
|
||||
}
|
||||
} else {
|
||||
notes = session1
|
||||
return session1 < session2.note ? session2.note : session1
|
||||
}
|
||||
return notes
|
||||
return session1
|
||||
}
|
||||
|
||||
// Traitement des données pour résultat définitif - INCLUANT TOUS LES ÉTUDIANTS
|
||||
for (let index = 0; index < etudiants.length; index++) {
|
||||
let total = 0
|
||||
let note = 0
|
||||
let totalCredit = 0
|
||||
let hasValidNotes = false
|
||||
|
||||
let modelJson = {
|
||||
id: '',
|
||||
nom: '',
|
||||
prenom: '',
|
||||
photos: '',
|
||||
moyenne: '',
|
||||
mention: '',
|
||||
anneescolaire: ''
|
||||
}
|
||||
|
||||
for (let j = 0; j < etudiants[index].length; j++) {
|
||||
modelJson.id = etudiants[index][j].etudiant_id
|
||||
modelJson.nom = etudiants[index][j].nom
|
||||
modelJson.prenom = etudiants[index][j].prenom
|
||||
modelJson.photos = etudiants[index][j].photos
|
||||
modelJson.mention = etudiants[index][j].mention_id
|
||||
modelJson.anneescolaire = etudiants[index][j].annee_scolaire
|
||||
|
||||
let currentNote = etudiants[index][j].note
|
||||
if (session[index]) {
|
||||
currentNote = compareSessionNotes(etudiants[index][j].note, checkNull(session[index][j]))
|
||||
}
|
||||
|
||||
// Vérifier si l'étudiant a des notes valides
|
||||
if (currentNote != null && currentNote != undefined && !isNaN(currentNote)) {
|
||||
note += currentNote * etudiants[index][j].credit
|
||||
totalCredit += etudiants[index][j].credit
|
||||
hasValidNotes = true
|
||||
const calculerMoyennesSessionNormale = () => {
|
||||
const moyennes = []
|
||||
for (let index = 0; index < etudiants.length; index++) {
|
||||
if (etudiants[index] && etudiants[index][0]) {
|
||||
let totalNotesPonderees = 0
|
||||
let totalCredits = 0
|
||||
let hasValidNotes = false
|
||||
let modelJson = {
|
||||
id: etudiants[index][0].etudiant_id,
|
||||
nom: etudiants[index][0].nom,
|
||||
prenom: etudiants[index][0].prenom,
|
||||
photos: etudiants[index][0].photos,
|
||||
mention: etudiants[index][0].mention_id,
|
||||
anneescolaire: etudiants[index][0].annee_scolaire,
|
||||
moyenne: 'N/A'
|
||||
}
|
||||
for (let j = 0; j < etudiants[index].length; j++) {
|
||||
const noteNormale = Number(etudiants[index][j].note)
|
||||
const credit = Number(etudiants[index][j].credit)
|
||||
if (noteNormale != null && !isNaN(noteNormale)) {
|
||||
totalNotesPonderees += noteNormale * credit
|
||||
totalCredits += credit
|
||||
hasValidNotes = true
|
||||
}
|
||||
}
|
||||
if (hasValidNotes && totalCredits > 0) {
|
||||
modelJson.moyenne = (totalNotesPonderees / totalCredits).toFixed(2)
|
||||
}
|
||||
moyennes.push(modelJson)
|
||||
}
|
||||
}
|
||||
|
||||
// Calculer la moyenne même si certaines notes manquent
|
||||
if (hasValidNotes && totalCredit > 0) {
|
||||
total = note / totalCredit
|
||||
modelJson.moyenne = total.toFixed(2)
|
||||
} else {
|
||||
modelJson.moyenne = 'N/A'
|
||||
}
|
||||
|
||||
dataToMap.push(modelJson)
|
||||
return moyennes.sort((a, b) => {
|
||||
const moyA = a.moyenne === 'N/A' ? -1 : parseFloat(a.moyenne)
|
||||
const moyB = b.moyenne === 'N/A' ? -1 : parseFloat(b.moyenne)
|
||||
return moyB - moyA
|
||||
})
|
||||
}
|
||||
|
||||
const sortedStudents = calculerMoyennesSessionNormale()
|
||||
|
||||
const getResultsRattrapageAdmis = () => {
|
||||
return moyennesRattrapage.filter(e => e.mention == selectedMentionId && e.admis).sort((a, b) => parseFloat(b.moyenne) - parseFloat(a.moyenne))
|
||||
}
|
||||
|
||||
const getResultsRattrapageNonAdmis = () => {
|
||||
return moyennesRattrapage.filter(e => e.mention == selectedMentionId && !e.admis).sort((a, b) => parseFloat(b.moyenne) - parseFloat(a.moyenne))
|
||||
}
|
||||
|
||||
const getResultsByMention = () => {
|
||||
return sortedStudents.filter(s => s.mention == selectedMentionId)
|
||||
}
|
||||
|
||||
// Fonction pour obtenir les résultats par matière sélectionnée
|
||||
const getResultsByMatiere = () => {
|
||||
const results = []
|
||||
|
||||
for (let index = 0; index < etudiants.length; index++) {
|
||||
for (let j = 0; j < etudiants[index].length; j++) {
|
||||
const matiere = etudiants[index][j].matiere || `Matière ${j + 1}`
|
||||
|
||||
if (matiere === selectedMatiere) {
|
||||
const nomMat = etudiants[index][j].nomMat || `Matière ${j + 1}`
|
||||
const mentionId = etudiants[index][j].mention_id
|
||||
if (nomMat === selectedMatiere && mentionId == selectedMentionId) {
|
||||
let finalNote = etudiants[index][j].note
|
||||
if (session[index] && session[index][j]) {
|
||||
finalNote = compareSessionNotes(etudiants[index][j].note, session[index][j])
|
||||
}
|
||||
|
||||
results.push({
|
||||
id: etudiants[index][j].etudiant_id,
|
||||
nom: etudiants[index][j].nom,
|
||||
prenom: etudiants[index][j].prenom,
|
||||
note: finalNote != null ? finalNote.toFixed(2) : 'N/A',
|
||||
credit: etudiants[index][j].credit,
|
||||
mention: returnmention(etudiants[index][j].mention_id)
|
||||
credit: etudiants[index][j].credit
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return results.sort((a, b) => {
|
||||
const noteA = a.note === 'N/A' ? -1 : parseFloat(a.note)
|
||||
const noteB = b.note === 'N/A' ? -1 : parseFloat(b.note)
|
||||
@ -190,21 +249,17 @@ const Resultat = () => {
|
||||
})
|
||||
}
|
||||
|
||||
// Fonction pour obtenir les résultats par UE sélectionnée
|
||||
const getResultsByUE = () => {
|
||||
const groupedStudents = {}
|
||||
const matieresInUE = new Set()
|
||||
|
||||
// Grouper les étudiants et collecter les matières de l'UE
|
||||
for (let index = 0; index < etudiants.length; index++) {
|
||||
for (let j = 0; j < etudiants[index].length; j++) {
|
||||
const ue = etudiants[index][j].ue || `UE${Math.floor(j / 2) + 1}`
|
||||
const matiere = etudiants[index][j].matiere || `Matière ${j + 1}`
|
||||
|
||||
if (ue === selectedUE) {
|
||||
const matiere = etudiants[index][j].nomMat || `Matière ${j + 1}`
|
||||
const mentionId = etudiants[index][j].mention_id
|
||||
if (ue === selectedUE && mentionId == selectedMentionId) {
|
||||
matieresInUE.add(matiere)
|
||||
const etudiantId = etudiants[index][j].etudiant_id
|
||||
|
||||
if (!groupedStudents[etudiantId]) {
|
||||
groupedStudents[etudiantId] = {
|
||||
id: etudiantId,
|
||||
@ -216,12 +271,10 @@ const Resultat = () => {
|
||||
hasValidNotes: false
|
||||
}
|
||||
}
|
||||
|
||||
let finalNote = etudiants[index][j].note
|
||||
if (session[index] && session[index][j]) {
|
||||
finalNote = compareSessionNotes(etudiants[index][j].note, session[index][j])
|
||||
}
|
||||
|
||||
if (finalNote != null && finalNote != undefined && !isNaN(finalNote)) {
|
||||
groupedStudents[etudiantId].matieres[matiere] = finalNote.toFixed(2)
|
||||
groupedStudents[etudiantId].totalNote += finalNote * etudiants[index][j].credit
|
||||
@ -233,14 +286,10 @@ const Resultat = () => {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const results = Object.values(groupedStudents).map(student => ({
|
||||
...student,
|
||||
moyenneUE: student.hasValidNotes && student.totalCredit > 0
|
||||
? (student.totalNote / student.totalCredit).toFixed(2)
|
||||
: 'N/A'
|
||||
moyenneUE: student.hasValidNotes && student.totalCredit > 0 ? (student.totalNote / student.totalCredit).toFixed(2) : 'N/A'
|
||||
}))
|
||||
|
||||
return {
|
||||
students: results.sort((a, b) => {
|
||||
const moyA = a.moyenneUE === 'N/A' ? -1 : parseFloat(a.moyenneUE)
|
||||
@ -251,12 +300,6 @@ const Resultat = () => {
|
||||
}
|
||||
}
|
||||
|
||||
const sortedStudents = dataToMap.sort((a, b) => {
|
||||
const moyA = a.moyenne === 'N/A' ? -1 : parseFloat(a.moyenne)
|
||||
const moyB = b.moyenne === 'N/A' ? -1 : parseFloat(b.moyenne)
|
||||
return moyB - moyA
|
||||
})
|
||||
|
||||
const handleTabChange = (event, newValue) => {
|
||||
setTabValue(newValue)
|
||||
}
|
||||
@ -264,15 +307,9 @@ const Resultat = () => {
|
||||
const print = () => {
|
||||
const generatePDF = () => {
|
||||
try {
|
||||
const pdf = new jsPDF({
|
||||
orientation: 'portrait',
|
||||
unit: 'mm',
|
||||
format: 'a4'
|
||||
})
|
||||
|
||||
const pdf = new jsPDF({ orientation: 'portrait', unit: 'mm', format: 'a4' })
|
||||
pdf.addImage(logoRelerev1, 'PNG', 175, 5, 32, 30)
|
||||
pdf.addImage(logoRelerev2, 'PNG', 10, 5, 40, 30)
|
||||
|
||||
pdf.setFontSize(10)
|
||||
pdf.text('REPOBLIKAN\'I MADAGASIKARA', 105, 10, { align: 'center' })
|
||||
pdf.text('Fitiavana-Tanindrazana-Fandrosoana', 105, 14, { align: 'center' })
|
||||
@ -282,37 +319,24 @@ const Resultat = () => {
|
||||
pdf.text('********************', 105, 30, { align: 'center' })
|
||||
pdf.text('UNIVERSITÉ DE TOAMASINA', 105, 34, { align: 'center' })
|
||||
pdf.text('ÉCOLE SUPÉRIEURE POLYTECHNIQUE', 105, 38, { align: 'center' })
|
||||
|
||||
const tableId = tabValue === 0 ? '#resultTable' : tabValue === 1 ? '#subjectTable' : '#ueTable'
|
||||
|
||||
const tableId = tabValue === 0 ? '#mentionTable' : tabValue === 1 ? '#rattrapageAdmisTable' : tabValue === 2 ? '#rattrapageNonAdmisTable' : tabValue === 3 ? '#subjectTable' : '#ueTable'
|
||||
autoTable(pdf, {
|
||||
html: tableId,
|
||||
startY: 50,
|
||||
theme: 'grid',
|
||||
headStyles: {
|
||||
fillColor: [255, 255, 255], // Fond blanc
|
||||
halign: 'center',
|
||||
fontStyle: 'bold',
|
||||
textColor: [0, 0, 0], // Texte noir
|
||||
lineColor: [0, 0, 0], // Bordure noire
|
||||
lineWidth: 0.5
|
||||
},
|
||||
styles: {
|
||||
fontSize: 8,
|
||||
cellPadding: 2,
|
||||
halign: 'center',
|
||||
lineColor: [0, 0, 0], // Bordure noire pour toutes les cellules
|
||||
lineWidth: 0.5
|
||||
},
|
||||
bodyStyles: {
|
||||
lineColor: [0, 0, 0], // Bordure noire pour le corps du tableau
|
||||
lineWidth: 0.5
|
||||
}
|
||||
headStyles: { fillColor: [255, 255, 255], halign: 'center', fontStyle: 'bold', textColor: [0, 0, 0], lineColor: [0, 0, 0], lineWidth: 0.5 },
|
||||
styles: { fontSize: 8, cellPadding: 2, halign: 'center', lineColor: [0, 0, 0], lineWidth: 0.5 },
|
||||
bodyStyles: { lineColor: [0, 0, 0], lineWidth: 0.5 }
|
||||
})
|
||||
|
||||
const suffix = tabValue === 0 ? 'definitif' :
|
||||
tabValue === 1 ? `par-matiere-${selectedMatiere}` :
|
||||
`par-ue-${selectedUE}`
|
||||
if (tabValue === 1) {
|
||||
const admis = getResultsRattrapageAdmis()
|
||||
const finalY = pdf.lastAutoTable.finalY || 50
|
||||
pdf.setFontSize(11)
|
||||
pdf.setFont(undefined, 'bold')
|
||||
pdf.text(`Arrêt la liste des étudiants admis à ${admis.length}`, 105, finalY + 15, { align: 'center' })
|
||||
}
|
||||
const selectedMentionName = returnmention(selectedMentionId)
|
||||
const suffix = tabValue === 0 ? `session-normale-${selectedMentionName}` : tabValue === 1 ? `rattrapage-admis-${selectedMentionName}` : tabValue === 2 ? `rattrapage-non-admis-${selectedMentionName}` : tabValue === 3 ? `par-matiere-${selectedMatiere}` : `par-ue-${selectedUE}`
|
||||
pdf.save(`Resultat-${suffix}-${niveau}-${scolaire}.pdf`)
|
||||
} catch (error) {
|
||||
console.error('Error generating PDF:', error)
|
||||
@ -322,27 +346,9 @@ const Resultat = () => {
|
||||
}
|
||||
|
||||
const renderHeader = () => (
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'flex-start',
|
||||
justifyContent: 'space-between',
|
||||
marginBottom: '20px',
|
||||
position: 'relative'
|
||||
}}
|
||||
>
|
||||
<div style={{ display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', marginBottom: '20px', position: 'relative' }}>
|
||||
<img src={logoRelerev2} alt="Logo gauche" width={90} height={90} />
|
||||
|
||||
<div
|
||||
style={{
|
||||
position: 'absolute',
|
||||
left: '50%',
|
||||
transform: 'translateX(-50%)',
|
||||
textAlign: 'center',
|
||||
fontSize: '10px',
|
||||
lineHeight: '1.2'
|
||||
}}
|
||||
>
|
||||
<div style={{ position: 'absolute', left: '50%', transform: 'translateX(-50%)', textAlign: 'center', fontSize: '10px', lineHeight: '1.2' }}>
|
||||
<div style={{ fontWeight: 'bold' }}>REPOBLIKAN'I MADAGASIKARA</div>
|
||||
<div style={{ fontStyle: 'italic' }}>Fitiavana-Tanindrazana-Fandrosoana</div>
|
||||
<div>********************</div>
|
||||
@ -352,82 +358,167 @@ const Resultat = () => {
|
||||
<div style={{ fontWeight: 'bold' }}>UNIVERSITÉ DE TOAMASINA</div>
|
||||
<div style={{ fontWeight: 'bold' }}>ÉCOLE SUPÉRIEURE POLYTECHNIQUE</div>
|
||||
</div>
|
||||
|
||||
<img src={logoRelerev1} alt="Logo droite" width={110} height={90} />
|
||||
</div>
|
||||
)
|
||||
|
||||
const renderResultDefinitif = () => (
|
||||
<table
|
||||
className="table table-bordered table-striped text-center shadow-sm"
|
||||
id="resultTable"
|
||||
style={{ fontSize: '12px' }}
|
||||
>
|
||||
<thead className="table-secondary">
|
||||
<tr>
|
||||
<td colSpan={5} className="py-3" style={{ backgroundColor: '#f8f9fa' }}>
|
||||
<h6 style={{ margin: 0, fontWeight: 'bold' }}>
|
||||
Résultat Définitif : {niveau} admis en {niveau === 'L1' ? 'L2' : niveau === 'L2' ? 'L3' : 'Master'} par ordre de mérite
|
||||
</h6>
|
||||
</td>
|
||||
</tr>
|
||||
<tr style={{ backgroundColor: '#e9ecef' }}>
|
||||
<th style={{ width: '10%', fontWeight: 'bold' }}>RANG</th>
|
||||
<th style={{ width: '25%', fontWeight: 'bold' }}>NOMS</th>
|
||||
<th style={{ width: '30%', fontWeight: 'bold' }}>PRÉNOMS</th>
|
||||
<th style={{ width: '15%', fontWeight: 'bold' }}>Moyenne</th>
|
||||
<th style={{ width: '20%', fontWeight: 'bold' }}>Mention</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{sortedStudents.map((sorted, index) => (
|
||||
<tr key={sorted.id}>
|
||||
<td style={{ fontWeight: 'bold' }}>{index + 1}.</td>
|
||||
<td style={{ textAlign: 'left', paddingLeft: '10px', fontWeight: 'bold' }}>{sorted.nom}</td>
|
||||
<td style={{ textAlign: 'left', paddingLeft: '10px' }}>{sorted.prenom}</td>
|
||||
<td style={{ fontWeight: 'bold' }}>{sorted.moyenne}</td>
|
||||
<td style={{ fontWeight: 'bold' }}>
|
||||
{sorted.moyenne !== 'N/A' ? getMentionFromMoyenne(sorted.moyenne) : 'N/A'}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
)
|
||||
|
||||
const renderResultParMatiere = () => {
|
||||
const results = getResultsByMatiere()
|
||||
|
||||
const renderResultParMention = () => {
|
||||
const results = getResultsByMention()
|
||||
const selectedMentionName = returnmention(selectedMentionId)
|
||||
return (
|
||||
<>
|
||||
<div style={{ marginBottom: '20px' }}>
|
||||
<FormControl fullWidth>
|
||||
<InputLabel>Sélectionner une matière</InputLabel>
|
||||
<Select
|
||||
value={selectedMatiere}
|
||||
onChange={(e) => setSelectedMatiere(e.target.value)}
|
||||
label="Sélectionner une matière"
|
||||
>
|
||||
{availableMatieres.map((matiere) => (
|
||||
<MenuItem key={matiere} value={matiere}>
|
||||
{matiere}
|
||||
</MenuItem>
|
||||
))}
|
||||
<InputLabel>Sélectionner une mention</InputLabel>
|
||||
<Select value={selectedMentionId} onChange={(e) => setSelectedMentionId(e.target.value)} label="Sélectionner une mention">
|
||||
{mention.map((m) => (<MenuItem key={m.id} value={m.id}>{m.nom}</MenuItem>))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</div>
|
||||
<table className="table table-bordered table-striped text-center shadow-sm" id="mentionTable" style={{ fontSize: '12px' }}>
|
||||
<thead className="table-secondary">
|
||||
<tr>
|
||||
<td colSpan={5} className="py-3" style={{ backgroundColor: '#f8f9fa' }}>
|
||||
<h6 style={{ margin: 0, fontWeight: 'bold' }}>Session Normale - Résultat {niveau} pour la mention : {selectedMentionName} ({results.length} étudiant{results.length > 1 ? 's' : ''})</h6>
|
||||
</td>
|
||||
</tr>
|
||||
<tr style={{ backgroundColor: '#e9ecef' }}>
|
||||
<th style={{ width: '10%', fontWeight: 'bold' }}>RANG</th>
|
||||
<th style={{ width: '25%', fontWeight: 'bold' }}>NOMS</th>
|
||||
<th style={{ width: '30%', fontWeight: 'bold' }}>PRÉNOMS</th>
|
||||
<th style={{ width: '15%', fontWeight: 'bold' }}>Moyenne</th>
|
||||
<th style={{ width: '20%', fontWeight: 'bold' }}>Mention</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{results.length > 0 ? (results.map((sorted, index) => (
|
||||
<tr key={sorted.id}>
|
||||
<td style={{ fontWeight: 'bold' }}>{index + 1}.</td>
|
||||
<td style={{ textAlign: 'left', paddingLeft: '10px', fontWeight: 'bold' }}>{sorted.nom}</td>
|
||||
<td style={{ textAlign: 'left', paddingLeft: '10px' }}>{sorted.prenom}</td>
|
||||
<td style={{ fontWeight: 'bold' }}>{sorted.moyenne}</td>
|
||||
<td style={{ fontWeight: 'bold' }}>{sorted.moyenne !== 'N/A' ? getMentionFromMoyenne(sorted.moyenne) : 'N/A'}</td>
|
||||
</tr>
|
||||
))) : (<tr><td colSpan={5} style={{ textAlign: 'center', padding: '20px', fontStyle: 'italic' }}>Aucun étudiant avec la mention "{selectedMentionName}"</td></tr>)}
|
||||
</tbody>
|
||||
</table>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
<table
|
||||
className="table table-bordered table-striped text-center shadow-sm"
|
||||
id="subjectTable"
|
||||
style={{ fontSize: '12px' }}
|
||||
>
|
||||
const renderRattrapageAdmis = () => {
|
||||
const results = getResultsRattrapageAdmis()
|
||||
const selectedMentionName = returnmention(selectedMentionId)
|
||||
return (
|
||||
<>
|
||||
<div style={{ marginBottom: '20px' }}>
|
||||
<FormControl fullWidth>
|
||||
<InputLabel>Sélectionner une mention</InputLabel>
|
||||
<Select value={selectedMentionId} onChange={(e) => setSelectedMentionId(e.target.value)} label="Sélectionner une mention">
|
||||
{mention.map((m) => (<MenuItem key={m.id} value={m.id}>{m.nom}</MenuItem>))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</div>
|
||||
<table className="table table-bordered table-striped text-center shadow-sm" id="rattrapageAdmisTable" style={{ fontSize: '12px' }}>
|
||||
<thead className="table-secondary">
|
||||
<tr>
|
||||
<td colSpan={5} className="py-3" style={{ backgroundColor: '#f8f9fa' }}>
|
||||
<h6 style={{ margin: 0, fontWeight: 'bold' }}>Session de Rattrapage - Étudiants ADMIS {niveau} pour la mention : {selectedMentionName} ({results.length} étudiant{results.length > 1 ? 's' : ''})</h6>
|
||||
</td>
|
||||
</tr>
|
||||
<tr style={{ backgroundColor: '#e9ecef' }}>
|
||||
<th style={{ width: '10%', fontWeight: 'bold' }}>RANG</th>
|
||||
<th style={{ width: '25%', fontWeight: 'bold' }}>NOMS</th>
|
||||
<th style={{ width: '30%', fontWeight: 'bold' }}>PRÉNOMS</th>
|
||||
<th style={{ width: '15%', fontWeight: 'bold' }}>Moyenne</th>
|
||||
<th style={{ width: '20%', fontWeight: 'bold' }}>Mention</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{results.length > 0 ? (results.map((item, index) => (
|
||||
<tr key={item.id}>
|
||||
<td style={{ fontWeight: 'bold' }}>{index + 1}.</td>
|
||||
<td style={{ textAlign: 'left', paddingLeft: '10px', fontWeight: 'bold' }}>{item.nom}</td>
|
||||
<td style={{ textAlign: 'left', paddingLeft: '10px' }}>{item.prenom}</td>
|
||||
<td style={{ fontWeight: 'bold' }}>{item.moyenne}</td>
|
||||
<td style={{ fontWeight: 'bold' }}>{getMentionFromMoyenne(item.moyenne)}</td>
|
||||
</tr>
|
||||
))) : (<tr><td colSpan={5} style={{ textAlign: 'center', padding: '20px', fontStyle: 'italic' }}>Aucun étudiant admis pour cette mention</td></tr>)}
|
||||
</tbody>
|
||||
</table>
|
||||
{results.length > 0 && (<div style={{ textAlign: 'center', marginTop: '20px', fontSize: '14px', fontWeight: 'bold', padding: '10px', backgroundColor: '#d4edda', borderRadius: '5px' }}>Arrêt la liste des étudiants admis à {results.length}</div>)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const renderRattrapageNonAdmis = () => {
|
||||
const results = getResultsRattrapageNonAdmis()
|
||||
const selectedMentionName = returnmention(selectedMentionId)
|
||||
return (
|
||||
<>
|
||||
<div style={{ marginBottom: '20px' }}>
|
||||
<FormControl fullWidth>
|
||||
<InputLabel>Sélectionner une mention</InputLabel>
|
||||
<Select value={selectedMentionId} onChange={(e) => setSelectedMentionId(e.target.value)} label="Sélectionner une mention">
|
||||
{mention.map((m) => (<MenuItem key={m.id} value={m.id}>{m.nom}</MenuItem>))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</div>
|
||||
<table className="table table-bordered table-striped text-center shadow-sm" id="rattrapageNonAdmisTable" style={{ fontSize: '12px' }}>
|
||||
<thead className="table-secondary">
|
||||
<tr>
|
||||
<td colSpan={5} className="py-3" style={{ backgroundColor: '#f8f9fa' }}>
|
||||
<h6 style={{ margin: 0, fontWeight: 'bold' }}>Session de Rattrapage - Étudiants NON ADMIS {niveau} pour la mention : {selectedMentionName} ({results.length} étudiant{results.length > 1 ? 's' : ''})</h6>
|
||||
</td>
|
||||
</tr>
|
||||
<tr style={{ backgroundColor: '#e9ecef' }}>
|
||||
<th style={{ width: '10%', fontWeight: 'bold' }}>RANG</th>
|
||||
<th style={{ width: '25%', fontWeight: 'bold' }}>NOMS</th>
|
||||
<th style={{ width: '30%', fontWeight: 'bold' }}>PRÉNOMS</th>
|
||||
<th style={{ width: '15%', fontWeight: 'bold' }}>Moyenne</th>
|
||||
<th style={{ width: '20%', fontWeight: 'bold' }}>Mention</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{results.length > 0 ? (results.map((item, index) => (
|
||||
<tr key={item.id}>
|
||||
<td style={{ fontWeight: 'bold' }}>{index + 1}.</td>
|
||||
<td style={{ textAlign: 'left', paddingLeft: '10px', fontWeight: 'bold' }}>{item.nom}</td>
|
||||
<td style={{ textAlign: 'left', paddingLeft: '10px' }}>{item.prenom}</td>
|
||||
<td style={{ fontWeight: 'bold' }}>{item.moyenne}</td>
|
||||
<td style={{ fontWeight: 'bold' }}>{getMentionFromMoyenne(item.moyenne)}</td>
|
||||
</tr>
|
||||
))) : (<tr><td colSpan={5} style={{ textAlign: 'center', padding: '20px', fontStyle: 'italic' }}>Aucun étudiant non admis pour cette mention</td></tr>)}
|
||||
</tbody>
|
||||
</table>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const renderResultParMatiere = () => {
|
||||
const results = getResultsByMatiere()
|
||||
const selectedMentionName = returnmention(selectedMentionId)
|
||||
return (
|
||||
<>
|
||||
<div style={{ marginBottom: '20px', display: 'flex', gap: '16px' }}>
|
||||
<FormControl fullWidth>
|
||||
<InputLabel>Sélectionner une mention</InputLabel>
|
||||
<Select value={selectedMentionId} onChange={(e) => setSelectedMentionId(e.target.value)} label="Sélectionner une mention">
|
||||
{mention.map((m) => (<MenuItem key={m.id} value={m.id}>{m.nom}</MenuItem>))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<FormControl fullWidth>
|
||||
<InputLabel>Sélectionner une matière</InputLabel>
|
||||
<Select value={selectedMatiere} onChange={(e) => setSelectedMatiere(e.target.value)} label="Sélectionner une matière">
|
||||
{availableMatieres.map((matiere) => (<MenuItem key={matiere} value={matiere}>{matiere}</MenuItem>))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</div>
|
||||
<table className="table table-bordered table-striped text-center shadow-sm" id="subjectTable" style={{ fontSize: '12px' }}>
|
||||
<thead className="table-secondary">
|
||||
<tr>
|
||||
<td colSpan={4} className="py-3" style={{ backgroundColor: '#f8f9fa' }}>
|
||||
<h6 style={{ margin: 0, fontWeight: 'bold' }}>
|
||||
Résultat pour la matière : {selectedMatiere}
|
||||
</h6>
|
||||
<h6 style={{ margin: 0, fontWeight: 'bold' }}>Résultat {niveau} — Mention : {selectedMentionName} — Matière : {selectedMatiere}</h6>
|
||||
</td>
|
||||
</tr>
|
||||
<tr style={{ backgroundColor: '#e9ecef' }}>
|
||||
@ -454,46 +545,35 @@ const Resultat = () => {
|
||||
|
||||
const renderResultParUE = () => {
|
||||
const { students, matieres } = getResultsByUE()
|
||||
|
||||
const selectedMentionName = returnmention(selectedMentionId)
|
||||
return (
|
||||
<>
|
||||
<div style={{ marginBottom: '20px' }}>
|
||||
<div style={{ marginBottom: '20px', display: 'flex', gap: '16px' }}>
|
||||
<FormControl fullWidth>
|
||||
<InputLabel>Sélectionner une mention</InputLabel>
|
||||
<Select value={selectedMentionId} onChange={(e) => setSelectedMentionId(e.target.value)} label="Sélectionner une mention">
|
||||
{mention.map((m) => (<MenuItem key={m.id} value={m.id}>{m.nom}</MenuItem>))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<FormControl fullWidth>
|
||||
<InputLabel>Sélectionner une UE</InputLabel>
|
||||
<Select
|
||||
value={selectedUE}
|
||||
onChange={(e) => setSelectedUE(e.target.value)}
|
||||
label="Sélectionner une UE"
|
||||
>
|
||||
{availableUEs.map((ue) => (
|
||||
<MenuItem key={ue} value={ue}>
|
||||
{ue}
|
||||
</MenuItem>
|
||||
))}
|
||||
<Select value={selectedUE} onChange={(e) => setSelectedUE(e.target.value)} label="Sélectionner une UE">
|
||||
{availableUEs.map((ue) => (<MenuItem key={ue} value={ue}>{ue}</MenuItem>))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</div>
|
||||
|
||||
<table
|
||||
className="table table-bordered table-striped text-center shadow-sm"
|
||||
id="ueTable"
|
||||
style={{ fontSize: '12px' }}
|
||||
>
|
||||
<table className="table table-bordered table-striped text-center shadow-sm" id="ueTable" style={{ fontSize: '12px' }}>
|
||||
<thead className="table-secondary">
|
||||
<tr>
|
||||
<td colSpan={3 + matieres.length} className="py-3" style={{ backgroundColor: '#f8f9fa' }}>
|
||||
<h6 style={{ margin: 0, fontWeight: 'bold' }}>
|
||||
Résultat pour l'UE : {selectedUE}
|
||||
</h6>
|
||||
<h6 style={{ margin: 0, fontWeight: 'bold' }}>Résultat {niveau} — Mention : {selectedMentionName} — UE : {selectedUE}</h6>
|
||||
</td>
|
||||
</tr>
|
||||
<tr style={{ backgroundColor: '#e9ecef' }}>
|
||||
<th style={{ fontWeight: 'bold' }}>RANG</th>
|
||||
<th style={{ fontWeight: 'bold' }}>NOMS</th>
|
||||
<th style={{ fontWeight: 'bold' }}>PRÉNOMS</th>
|
||||
{matieres.map((matiere) => (
|
||||
<th key={matiere} style={{ fontWeight: 'bold' }}>{matiere}</th>
|
||||
))}
|
||||
{matieres.map((matiere) => (<th key={matiere} style={{ fontWeight: 'bold' }}>{matiere}</th>))}
|
||||
<th style={{ fontWeight: 'bold' }}>MOYENNE UE</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@ -503,11 +583,7 @@ const Resultat = () => {
|
||||
<td style={{ fontWeight: 'bold' }}>{index + 1}.</td>
|
||||
<td style={{ textAlign: 'left', paddingLeft: '10px', fontWeight: 'bold' }}>{student.nom}</td>
|
||||
<td style={{ textAlign: 'left', paddingLeft: '10px' }}>{student.prenom}</td>
|
||||
{matieres.map((matiere) => (
|
||||
<td key={matiere} style={{ fontWeight: 'bold' }}>
|
||||
{student.matieres[matiere] || 'N/A'}
|
||||
</td>
|
||||
))}
|
||||
{matieres.map((matiere) => (<td key={matiere} style={{ fontWeight: 'bold' }}>{student.matieres[matiere] || 'N/A'}</td>))}
|
||||
<td style={{ fontWeight: 'bold', backgroundColor: '#fff3cd' }}>{student.moyenneUE}</td>
|
||||
</tr>
|
||||
))}
|
||||
@ -522,9 +598,7 @@ const Resultat = () => {
|
||||
<div className={classeHome.header}>
|
||||
<div className={classe.h1style}>
|
||||
<div className={classeHome.blockTitle}>
|
||||
<h1>
|
||||
Resultat des {niveau} en {scolaire}
|
||||
</h1>
|
||||
<h1>Resultat des {niveau} en {scolaire}</h1>
|
||||
<div style={{ display: 'flex', gap: '10px' }}>
|
||||
<Link to={'#'} onClick={print}>
|
||||
<Button color="warning" variant="contained">
|
||||
@ -541,39 +615,26 @@ const Resultat = () => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={classeHome.boxEtudiantsCard}>
|
||||
<Paper
|
||||
sx={{
|
||||
height: 'auto',
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
padding: '2%'
|
||||
}}
|
||||
>
|
||||
<Paper sx={{ height: 'auto', width: '100%', display: 'flex', flexDirection: 'column', padding: '2%' }}>
|
||||
{renderHeader()}
|
||||
|
||||
<div style={{ marginBottom: '15px', fontSize: '12px' }}>
|
||||
<div><strong>Parcours :</strong> GC</div>
|
||||
<div><strong>Niveau :</strong> {niveau}</div>
|
||||
<div><strong>Année Universitaire :</strong> {scolaire}</div>
|
||||
</div>
|
||||
|
||||
<Tabs
|
||||
value={tabValue}
|
||||
onChange={handleTabChange}
|
||||
centered
|
||||
sx={{ marginBottom: '20px' }}
|
||||
>
|
||||
<Tab label="Résultat Définitif" />
|
||||
<Tabs value={tabValue} onChange={handleTabChange} centered sx={{ marginBottom: '20px' }}>
|
||||
<Tab label="Session Normale" />
|
||||
<Tab label="Rattrapage - Admis" />
|
||||
<Tab label="Rattrapage - Non Admis" />
|
||||
<Tab label="Par Matière" />
|
||||
<Tab label="Par UE" />
|
||||
</Tabs>
|
||||
|
||||
{tabValue === 0 && renderResultDefinitif()}
|
||||
{tabValue === 1 && renderResultParMatiere()}
|
||||
{tabValue === 2 && renderResultParUE()}
|
||||
{tabValue === 0 && renderResultParMention()}
|
||||
{tabValue === 1 && renderRattrapageAdmis()}
|
||||
{tabValue === 2 && renderRattrapageNonAdmis()}
|
||||
{tabValue === 3 && renderResultParMatiere()}
|
||||
{tabValue === 4 && renderResultParUE()}
|
||||
</Paper>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -18,6 +18,7 @@ import { BsCalendar2Date } from 'react-icons/bs'
|
||||
import { SiVitest } from 'react-icons/si'
|
||||
import { GrManual } from 'react-icons/gr'
|
||||
import { FaClipboardList } from 'react-icons/fa6'
|
||||
import { FaMoneyBillWave } from 'react-icons/fa'
|
||||
|
||||
const Sidenav = () => {
|
||||
const [anchorEl, setAnchorEl] = useState(null)
|
||||
@ -268,6 +269,15 @@ const isAdmin = () => {
|
||||
<MdAdminPanelSettings /> Admin
|
||||
</Link>
|
||||
</MenuItem>
|
||||
<MenuItem>
|
||||
<Link
|
||||
to="/configecolage"
|
||||
style={{ color: 'black', textDecoration: 'none' }}
|
||||
onClick={handleClose}
|
||||
>
|
||||
<FaMoneyBillWave /> Config Ecolage
|
||||
</Link>
|
||||
</MenuItem>
|
||||
<MenuItem>
|
||||
<Link
|
||||
to="/para"
|
||||
|
||||
@ -1,249 +1,232 @@
|
||||
import React, { useEffect, useRef, useState } from 'react'
|
||||
import { useParams, Link } from 'react-router-dom'
|
||||
import { Box, InputAdornment, Typography, Modal, TextField, Grid, Button } from '@mui/material'
|
||||
import Paper from '@mui/material/Paper'
|
||||
import { IoMdReturnRight } from 'react-icons/io'
|
||||
import { CgNotes } from 'react-icons/cg'
|
||||
import classe from '../assets/AllStyleComponents.module.css'
|
||||
import classeHome from '../assets/Home.module.css'
|
||||
import { IoMdReturnRight } from 'react-icons/io'
|
||||
import { Button } from '@mui/material'
|
||||
import { Box, InputAdornment, Typography, Modal, TextField, Grid } from '@mui/material'
|
||||
import { CgNotes } from 'react-icons/cg'
|
||||
import svgSuccess from '../assets/success.svg'
|
||||
import svgError from '../assets/error.svg'
|
||||
|
||||
const SingleNotes = () => {
|
||||
let { id, niveau, scolaire } = useParams()
|
||||
const { id, niveau, scolaire } = useParams()
|
||||
|
||||
const [notes, setNotes] = useState([])
|
||||
const [notesRepech, setNotesRepech] = useState([])
|
||||
const [formData, setFormData] = useState({})
|
||||
const [formData2, setFormData2] = useState({})
|
||||
const [etudiant, setEtudiant] = useState([])
|
||||
let annee_scolaire = scolaire
|
||||
const [etudiant, setEtudiant] = useState({})
|
||||
const [screenRattrapage, setScreenRattrapage] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
window.etudiants.getSingle({ id }).then((response) => {
|
||||
setEtudiant(response)
|
||||
})
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
let mention_id = etudiant.mention_id
|
||||
window.notes.getNotes({ id, niveau, mention_id }).then((response) => {
|
||||
setNotes(response)
|
||||
})
|
||||
|
||||
window.noteRepech.getNotesRepech({ id, niveau, mention_id }).then((response) => {
|
||||
setNotesRepech(response)
|
||||
})
|
||||
}, [etudiant])
|
||||
|
||||
console.log(notes)
|
||||
/**
|
||||
* Update formData whenever matieres change
|
||||
*/
|
||||
useEffect(() => {
|
||||
const initialFormData = notes.reduce((acc, mat) => {
|
||||
acc[mat.id] = mat.note // Initialize each key with an empty string
|
||||
return acc
|
||||
}, {})
|
||||
setFormData(initialFormData)
|
||||
}, [notes]) // Dependency array ensures this runs whenever `matieres` is updated
|
||||
|
||||
/**
|
||||
* Update formData2 whenever matieres change
|
||||
*/
|
||||
useEffect(() => {
|
||||
const initialFormData = notesRepech.reduce((acc, mat) => {
|
||||
acc[mat.id] = mat.note // Initialize each key with an empty string
|
||||
return acc
|
||||
}, {})
|
||||
setFormData2(initialFormData)
|
||||
}, [notesRepech]) // Dependency array ensures this runs whenever `matieres` is updated
|
||||
|
||||
const submitForm = async (e) => {
|
||||
e.preventDefault()
|
||||
let mention_id = etudiant.mention_id
|
||||
console.log('normal submited')
|
||||
let annee_scolaire = etudiant.annee_scolaire
|
||||
let response = await window.notes.updateNote({
|
||||
formData,
|
||||
niveau,
|
||||
id,
|
||||
mention_id,
|
||||
annee_scolaire
|
||||
})
|
||||
|
||||
if (response.changes) {
|
||||
setMessage('Modification des notes terminer avec succès')
|
||||
setStatus(200)
|
||||
setOpen(true)
|
||||
window.noteRepech.getNotesRepech({ id, niveau, mention_id }).then((response) => {
|
||||
setNotesRepech(response)
|
||||
})
|
||||
|
||||
window.notes.getNotes({ id, niveau, mention_id }).then((response) => {
|
||||
setNotes(response)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const submitForm2 = async (e) => {
|
||||
e.preventDefault()
|
||||
let mention_id = etudiant.mention_id
|
||||
console.log('rattrapage submited')
|
||||
let response = await window.noteRepech.updateNoteRepech({ formData2, niveau, id })
|
||||
|
||||
console.log(response)
|
||||
if (response.changes) {
|
||||
setMessage('Modification des notes terminer avec succès')
|
||||
setStatus(200)
|
||||
setOpen(true)
|
||||
window.noteRepech.getNotesRepech({ id, niveau, mention_id }).then((response) => {
|
||||
setNotesRepech(response)
|
||||
})
|
||||
|
||||
window.notes.getNotes({ id, niveau, mention_id }).then((response) => {
|
||||
setNotes(response)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/* ===================== MESSAGE MODAL ===================== */
|
||||
const [open, setOpen] = useState(false)
|
||||
const [status, setStatus] = useState(200)
|
||||
const [message, setMessage] = useState('')
|
||||
|
||||
/**
|
||||
* hook to open modal
|
||||
*/
|
||||
const [open, setOpen] = useState(false)
|
||||
|
||||
/**
|
||||
* function to close modal
|
||||
*/
|
||||
const handleClose = () => setOpen(false)
|
||||
|
||||
/**
|
||||
* function to return the view Modal
|
||||
*
|
||||
* @returns {JSX}
|
||||
*/
|
||||
const modals = () => (
|
||||
<Modal
|
||||
open={open}
|
||||
onClose={handleClose}
|
||||
aria-labelledby="modal-title"
|
||||
aria-describedby="modal-description"
|
||||
>
|
||||
/* ===================== DATA ===================== */
|
||||
|
||||
useEffect(() => {
|
||||
window.etudiants.getSingle({ id }).then(setEtudiant)
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (!etudiant?.mention_id) return
|
||||
|
||||
window.notes.getNotes({ id, niveau, mention_id: etudiant.mention_id, annee_scolaire: scolaire }).then(setNotes)
|
||||
window.noteRepech
|
||||
.getNotesRepech({ id, niveau, mention_id: etudiant.mention_id, annee_scolaire: scolaire })
|
||||
.then(setNotesRepech)
|
||||
}, [etudiant])
|
||||
|
||||
/* ===================== INIT FORM ===================== */
|
||||
|
||||
useEffect(() => {
|
||||
const init = {}
|
||||
notes.forEach((n) => (init[n.id] = n.note))
|
||||
setFormData(init)
|
||||
}, [notes])
|
||||
|
||||
useEffect(() => {
|
||||
const init = {}
|
||||
notesRepech.forEach((n) => (init[n.id] = n.note))
|
||||
setFormData2(init)
|
||||
}, [notesRepech])
|
||||
|
||||
/* ===================== SUBMIT NORMALE ===================== */
|
||||
|
||||
const submitForm = async (e) => {
|
||||
e.preventDefault()
|
||||
try {
|
||||
let mention_id = etudiant.mention_id
|
||||
let annee_scolaire = scolaire // utiliser l'année de l'URL, pas de l'étudiant
|
||||
|
||||
const response = await window.notes.updateNote({
|
||||
formData,
|
||||
niveau,
|
||||
id,
|
||||
mention_id,
|
||||
annee_scolaire
|
||||
})
|
||||
|
||||
setStatus(200)
|
||||
setMessage('Notes enregistrées avec succès')
|
||||
setOpen(true)
|
||||
|
||||
// rechargement avec l'année correcte
|
||||
const notesData = await window.notes.getNotes({ id, niveau, mention_id, annee_scolaire: scolaire })
|
||||
setNotes(notesData)
|
||||
|
||||
const repechData = await window.noteRepech.getNotesRepech({ id, niveau, mention_id, annee_scolaire: scolaire })
|
||||
setNotesRepech(repechData)
|
||||
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
setStatus(500)
|
||||
setMessage("Échec de l'enregistrement des notes")
|
||||
setOpen(true)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* ===================== SUBMIT RATTRAPAGE ===================== */
|
||||
|
||||
const submitForm2 = async (e) => {
|
||||
e.preventDefault()
|
||||
try {
|
||||
let mention_id = etudiant.mention_id
|
||||
|
||||
await window.noteRepech.updateNoteRepech({
|
||||
formData2,
|
||||
niveau,
|
||||
id,
|
||||
annee_scolaire: scolaire
|
||||
})
|
||||
|
||||
setStatus(200)
|
||||
setMessage('Notes de rattrapage enregistrées avec succès')
|
||||
setOpen(true)
|
||||
|
||||
const repechData = await window.noteRepech.getNotesRepech({ id, niveau, mention_id })
|
||||
setNotesRepech(repechData)
|
||||
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
setStatus(500)
|
||||
setMessage("Échec de l'enregistrement des notes de rattrapage")
|
||||
setOpen(true)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* ===================== MODAL ===================== */
|
||||
|
||||
const modalMessage = () => (
|
||||
<Modal open={open} onClose={handleClose}>
|
||||
<Box
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
top: '50%',
|
||||
left: '50%',
|
||||
transform: 'translate(-50%, -50%)',
|
||||
width: 450,
|
||||
width: 420,
|
||||
bgcolor: 'background.paper',
|
||||
boxShadow: 24,
|
||||
p: 4
|
||||
p: 4,
|
||||
textAlign: 'center'
|
||||
}}
|
||||
>
|
||||
{status === 200 ? (
|
||||
<Typography
|
||||
style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '10px' }}
|
||||
>
|
||||
<img src={svgSuccess} alt="" width={50} height={50} /> <span>{message}</span>
|
||||
</Typography>
|
||||
) : (
|
||||
<Typography
|
||||
style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '10px' }}
|
||||
>
|
||||
<img src={svgError} alt="" width={50} height={50} /> <span>{message}</span>
|
||||
</Typography>
|
||||
)}
|
||||
<Box
|
||||
sx={{
|
||||
marginTop: '2%',
|
||||
display: 'flex',
|
||||
gap: '20px',
|
||||
alignItems: 'end',
|
||||
justifyContent: 'flex-end'
|
||||
}}
|
||||
>
|
||||
<Button onClick={handleClose} color="warning" variant="contained">
|
||||
OK
|
||||
</Button>
|
||||
</Box>
|
||||
<img src={status === 200 ? svgSuccess : svgError} alt="" width={60} />
|
||||
<Typography sx={{ mt: 2 }}>{message}</Typography>
|
||||
|
||||
<Button sx={{ mt: 3 }} color="warning" variant="contained" onClick={handleClose}>
|
||||
OK
|
||||
</Button>
|
||||
</Box>
|
||||
</Modal>
|
||||
)
|
||||
|
||||
const nom = useRef()
|
||||
|
||||
const changeScreen = () => {
|
||||
setScreenRattrapage(!screenRattrapage)
|
||||
}
|
||||
/* ===================== UI ===================== */
|
||||
|
||||
return (
|
||||
<div className={classe.mainHome}>
|
||||
{modals()}
|
||||
{modalMessage()}
|
||||
|
||||
<div className={classeHome.header}>
|
||||
<div className={classe.h1style}>
|
||||
<div className={classeHome.blockTitle}>
|
||||
<h1>Mise a jour des notes</h1>
|
||||
<div style={{ display: 'flex', gap: '20px' }}>
|
||||
<Link onClick={() => window.history.back()}>
|
||||
<Button color="warning" variant="contained">
|
||||
<IoMdReturnRight style={{ fontSize: '20px' }} />
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
<h1>Mise à jour des notes</h1>
|
||||
<Link onClick={() => window.history.back()}>
|
||||
<Button color="warning" variant="contained">
|
||||
<IoMdReturnRight />
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* displaying the form */}
|
||||
<div className={classeHome.boxEtudiantsCard}>
|
||||
<Box
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
top: '55%',
|
||||
left: '50%',
|
||||
transform: 'translate(-50%, -50%)',
|
||||
width: 700,
|
||||
borderRadius: '2%',
|
||||
bgcolor: 'background.paper',
|
||||
boxShadow: 24,
|
||||
overflowY: 'auto',
|
||||
p: 4
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
marginTop: '2%',
|
||||
display: 'flex',
|
||||
width: '100%',
|
||||
height: '70vh',
|
||||
gap: '20px',
|
||||
alignItems: 'start',
|
||||
justifyContent: 'center'
|
||||
}}
|
||||
>
|
||||
{!screenRattrapage ? (
|
||||
<form action="" onSubmit={submitForm}>
|
||||
<h4 style={{ textAlign: 'center' }}>Mise a jour des notes</h4>
|
||||
{/* {/* map the all matiere and note to the form */}
|
||||
<Grid container spacing={2}>
|
||||
{notes.map((note) => (
|
||||
<Grid item xs={12} sm={3} key={note.nom}>
|
||||
<Paper sx={{ p: 4, width: 700, margin: 'auto', mt: 5 }}>
|
||||
{!screenRattrapage ? (
|
||||
<form onSubmit={submitForm}>
|
||||
<h4 style={{ textAlign: 'center' }}>Session normale</h4>
|
||||
|
||||
<Grid container spacing={2}>
|
||||
{notes.map((n) => (
|
||||
<Grid item xs={12} sm={3} key={n.id}>
|
||||
<TextField
|
||||
label={n.nom}
|
||||
value={formData[n.id] || ''}
|
||||
onChange={(e) =>
|
||||
setFormData({ ...formData, [n.id]: e.target.value })
|
||||
}
|
||||
fullWidth
|
||||
color="warning"
|
||||
InputProps={{
|
||||
startAdornment: (
|
||||
<InputAdornment position="start">
|
||||
<CgNotes />
|
||||
</InputAdornment>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
|
||||
<Box sx={{ display: 'flex', justifyContent: 'flex-end', mt: 3, gap: 2 }}>
|
||||
<Button onClick={() => setScreenRattrapage(true)} color="warning" variant="contained">
|
||||
Rattrapage
|
||||
</Button>
|
||||
<Button type="submit" color="warning" variant="contained">
|
||||
Enregistrer
|
||||
</Button>
|
||||
</Box>
|
||||
</form>
|
||||
) : (
|
||||
<form onSubmit={submitForm2}>
|
||||
<h4 style={{ textAlign: 'center' }}>Session rattrapage</h4>
|
||||
|
||||
<Grid container spacing={2}>
|
||||
{notesRepech.length === 0 ? (
|
||||
<Grid item xs={12}>
|
||||
<Typography color="green" textAlign="center">
|
||||
L'étudiant a validé tous les crédits.
|
||||
</Typography>
|
||||
</Grid>
|
||||
) : (
|
||||
notesRepech.map((n) => (
|
||||
<Grid item xs={12} sm={4} key={n.id}>
|
||||
<TextField
|
||||
label={note.nom}
|
||||
name={note.matiere_id}
|
||||
color="warning"
|
||||
fullWidth
|
||||
placeholder="point séparateur"
|
||||
className="inputToValidateExport"
|
||||
value={formData[note.matiere_id] || ''} // Access the correct value from formData
|
||||
onChange={
|
||||
(e) => setFormData({ ...formData, [note.id]: e.target.value }) // Update the specific key
|
||||
label={n.nom}
|
||||
value={formData2[n.id] || ''}
|
||||
onChange={(e) =>
|
||||
setFormData2({ ...formData2, [n.id]: e.target.value })
|
||||
}
|
||||
fullWidth
|
||||
color="warning"
|
||||
InputProps={{
|
||||
startAdornment: (
|
||||
<InputAdornment position="start">
|
||||
@ -251,111 +234,23 @@ const SingleNotes = () => {
|
||||
</InputAdornment>
|
||||
)
|
||||
}}
|
||||
inputRef={nom}
|
||||
sx={{
|
||||
'& .MuiOutlinedInput-root': {
|
||||
'&:hover fieldset': {
|
||||
borderColor: '#ff9800' // Set the border color on hover
|
||||
}
|
||||
},
|
||||
'& .MuiInputBase-input::placeholder': {
|
||||
fontSize: '11px' // Set the placeholder font size
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
<Grid
|
||||
item
|
||||
xs={12}
|
||||
style={{
|
||||
display: 'flex',
|
||||
gap: '30px',
|
||||
justifyContent: 'flex-end',
|
||||
marginTop: '2%'
|
||||
}}
|
||||
>
|
||||
<Button type="button" color="warning" variant="contained" onClick={changeScreen}>
|
||||
Voir les notes de rattrapage
|
||||
</Button>
|
||||
<Button type="submit" color="warning" variant="contained">
|
||||
Enregister
|
||||
</Button>
|
||||
</Grid>
|
||||
</form>
|
||||
) : (
|
||||
<form action="" onSubmit={submitForm2}>
|
||||
<h4 style={{ textAlign: 'center' }}>Mise a jour des notes de Rattrapage</h4>
|
||||
{/* {/* map the all matiere and note to the form */}
|
||||
<Grid container spacing={2}>
|
||||
{notesRepech.length === 0 ? (
|
||||
// Show this message if notesRepech is empty
|
||||
<Grid item xs={12}>
|
||||
<h4 style={{ textAlign: 'center', color: 'green' }}>
|
||||
L'étudiant a validé tous les crédits.
|
||||
</h4>
|
||||
</Grid>
|
||||
) : (
|
||||
// Render form fields if notesRepech contains data
|
||||
notesRepech.map((note) => (
|
||||
<Grid item xs={12} sm={4} key={note.nom}>
|
||||
<TextField
|
||||
label={note.nom}
|
||||
name={note.matiere_id}
|
||||
color="warning"
|
||||
fullWidth
|
||||
placeholder="point séparateur"
|
||||
className="inputToValidateExport"
|
||||
value={formData2[note.matiere_id] || ''} // Access the correct value from formData2
|
||||
onChange={
|
||||
(e) => setFormData2({ ...formData2, [note.id]: e.target.value }) // Update the specific key
|
||||
}
|
||||
InputProps={{
|
||||
startAdornment: (
|
||||
<InputAdornment position="start">
|
||||
<CgNotes />
|
||||
</InputAdornment>
|
||||
)
|
||||
}}
|
||||
inputRef={nom}
|
||||
sx={{
|
||||
'& .MuiOutlinedInput-root': {
|
||||
'&:hover fieldset': {
|
||||
borderColor: '#ff9800' // Set the border color on hover
|
||||
}
|
||||
},
|
||||
'& .MuiInputBase-input::placeholder': {
|
||||
fontSize: '11px' // Set the placeholder font size
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
))
|
||||
)}
|
||||
</Grid>
|
||||
))
|
||||
)}
|
||||
</Grid>
|
||||
|
||||
<Grid
|
||||
item
|
||||
xs={12}
|
||||
style={{
|
||||
display: 'flex',
|
||||
gap: '30px',
|
||||
justifyContent: 'flex-end',
|
||||
marginTop: '2%'
|
||||
}}
|
||||
>
|
||||
<Button type="button" color="warning" variant="contained" onClick={changeScreen}>
|
||||
Voir les notes session normale
|
||||
</Button>
|
||||
<Button type="submit" color="warning" variant="contained">
|
||||
Enregister
|
||||
</Button>
|
||||
</Grid>
|
||||
</form>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
<Box sx={{ display: 'flex', justifyContent: 'flex-end', mt: 3, gap: 2 }}>
|
||||
<Button onClick={() => setScreenRattrapage(false)} color="warning" variant="contained">
|
||||
Normale
|
||||
</Button>
|
||||
<Button type="submit" color="warning" variant="contained">
|
||||
Enregistrer
|
||||
</Button>
|
||||
</Box>
|
||||
</form>
|
||||
)}
|
||||
</Paper>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@ -64,16 +64,22 @@ const Student = () => {
|
||||
const [sortModel, setSortModel] = useState([])
|
||||
const location = useLocation()
|
||||
const savedFilter = localStorage.getItem('selectedNiveau') || ''
|
||||
const savedAnnee = localStorage.getItem('selectedAnnee') || ''
|
||||
const initialFilter = location.state?.selectedNiveau || savedFilter
|
||||
const initialAnnee = location.state?.selectedAnnee || savedAnnee
|
||||
|
||||
const [selectedNiveau, setSelectedNiveau] = useState(initialFilter)
|
||||
const [selectedAnnee, setSelectedAnnee] = useState(initialAnnee)
|
||||
const [anneesList, setAnneesList] = useState([])
|
||||
|
||||
useEffect(() => {
|
||||
if (initialFilter) {
|
||||
setSelectedNiveau(initialFilter)
|
||||
FilterData({ target: { value: initialFilter } }) // applique le filtre initial
|
||||
}
|
||||
}, [initialFilter])
|
||||
if (initialAnnee) {
|
||||
setSelectedAnnee(initialAnnee)
|
||||
}
|
||||
}, [])
|
||||
|
||||
|
||||
|
||||
@ -84,21 +90,58 @@ const Student = () => {
|
||||
const [etudiants, setEtudiants] = useState([])
|
||||
const [notes, setNotes] = useState([])
|
||||
|
||||
// Charger la liste des années scolaires disponibles
|
||||
useEffect(() => {
|
||||
window.etudiants.getEtudiants().then((response) => {
|
||||
setAllEtudiants(response)
|
||||
|
||||
if (selectedNiveau && selectedNiveau !== '') {
|
||||
setEtudiants(response.filter(e => e.niveau === selectedNiveau))
|
||||
} else {
|
||||
setEtudiants(response)
|
||||
window.anneescolaire.getAnneeScolaire().then((response) => {
|
||||
setAnneesList(response || [])
|
||||
const currentYear = (response || []).find(a => a.is_current === 1 || a.is_current === true)
|
||||
if (currentYear) {
|
||||
setSelectedAnnee(currentYear.code)
|
||||
localStorage.setItem('selectedAnnee', currentYear.code)
|
||||
}
|
||||
})
|
||||
}, [])
|
||||
|
||||
// Recharger les étudiants depuis la DB quand l'année sélectionnée change
|
||||
// Filtrer pour n'afficher que ceux qui ont payé au moins une tranche
|
||||
useEffect(() => {
|
||||
const loadEtudiants = async () => {
|
||||
let response
|
||||
if (selectedAnnee && selectedAnnee !== '') {
|
||||
response = await window.etudiants.getEtudiantsByAnnee(selectedAnnee)
|
||||
try {
|
||||
const paidIds = await window.etudiants.getEtudiantsWithPaidTranche({
|
||||
annee_scolaire: selectedAnnee
|
||||
})
|
||||
console.log('Etudiants total:', (response || []).length, '| Etudiants ayant payé:', paidIds)
|
||||
if (Array.isArray(paidIds) && paidIds.length > 0) {
|
||||
response = (response || []).filter((e) => paidIds.includes(e.id))
|
||||
} else {
|
||||
response = []
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Erreur filtre tranche:', err)
|
||||
}
|
||||
} else {
|
||||
response = await window.etudiants.getEtudiants()
|
||||
}
|
||||
setAllEtudiants(response || [])
|
||||
}
|
||||
loadEtudiants()
|
||||
|
||||
window.notes.getMoyenneVerify().then((response) => {
|
||||
setNotes(response)
|
||||
})
|
||||
}, [selectedNiveau])
|
||||
}, [selectedAnnee])
|
||||
|
||||
// Filtrer par niveau quand allEtudiants ou selectedNiveau change
|
||||
useEffect(() => {
|
||||
if (selectedNiveau && selectedNiveau !== '') {
|
||||
setEtudiants(allEtudiants.filter(e => e.niveau === selectedNiveau))
|
||||
} else {
|
||||
setEtudiants(allEtudiants)
|
||||
}
|
||||
}, [allEtudiants, selectedNiveau])
|
||||
|
||||
useEffect(() => {
|
||||
const savedFilters = localStorage.getItem('datagridFilters')
|
||||
@ -522,7 +565,7 @@ const Student = () => {
|
||||
|
||||
// Ensure that the array is flat (not wrapped in another array)
|
||||
const dataRow = etudiants.map((etudiant) => ({
|
||||
id: etudiant.id, // Ensure this exists and is unique for each etudiant
|
||||
id: etudiant.inscription_id || etudiant.id,
|
||||
nom: etudiant.nom,
|
||||
prenom: etudiant.prenom,
|
||||
niveau: etudiant.niveau,
|
||||
@ -544,7 +587,7 @@ const Student = () => {
|
||||
mention_id: etudiant.mention_id,
|
||||
mentionUnite: etudiant.mentionUnite,
|
||||
nomMention: etudiant.nomMention,
|
||||
action: etudiant.id // Ensure this is a valid URL for the image
|
||||
action: etudiant.id
|
||||
}))
|
||||
|
||||
function comparestatut(statutID) {
|
||||
@ -559,19 +602,24 @@ const Student = () => {
|
||||
}
|
||||
|
||||
/**
|
||||
* ✅ Fonction de filtrage avec reset de pagination
|
||||
* ✅ Fonction de filtrage par niveau avec reset de pagination
|
||||
*/
|
||||
const FilterData = (e) => {
|
||||
const niveau = e.target.value
|
||||
setSelectedNiveau(niveau)
|
||||
localStorage.setItem('selectedNiveau', niveau)
|
||||
setPaginationModel(prev => ({ ...prev, page: 0 }))
|
||||
}
|
||||
|
||||
if (niveau === '') {
|
||||
setEtudiants(allEtudiants)
|
||||
} else {
|
||||
const filtered = allEtudiants.filter(student => student.niveau === niveau)
|
||||
setEtudiants(filtered)
|
||||
}
|
||||
|
||||
/**
|
||||
* Fonction de filtrage par année scolaire
|
||||
*/
|
||||
const FilterByAnnee = (e) => {
|
||||
const annee = e.target.value
|
||||
setSelectedAnnee(annee)
|
||||
localStorage.setItem('selectedAnnee', annee)
|
||||
setSelectedNiveau('')
|
||||
localStorage.setItem('selectedNiveau', '')
|
||||
setPaginationModel(prev => ({ ...prev, page: 0 }))
|
||||
}
|
||||
|
||||
@ -664,12 +712,42 @@ const Student = () => {
|
||||
</div>
|
||||
{/* bare des filtre */}
|
||||
<div className={classeHome.container}>
|
||||
{/* filtre par niveau */}
|
||||
<div style={{ width: '100%', textAlign: 'right' }}>
|
||||
{/* filtre par année scolaire + niveau */}
|
||||
<div style={{ width: '100%', textAlign: 'right', display: 'flex', justifyContent: 'flex-end', alignItems: 'center' }}>
|
||||
{/* filtre par année scolaire */}
|
||||
<FormControl
|
||||
sx={{
|
||||
m: 1,
|
||||
width: '30%',
|
||||
width: '25%',
|
||||
'& .MuiOutlinedInput-root': {
|
||||
'&:hover fieldset': { borderColor: '#ff9800' }
|
||||
}
|
||||
}}
|
||||
size="small"
|
||||
variant="outlined"
|
||||
>
|
||||
<InputLabel sx={{ color: 'black', fontSize: '18px' }} color="warning">
|
||||
Année scolaire
|
||||
</InputLabel>
|
||||
<Select
|
||||
label="Année scolaire"
|
||||
color="warning"
|
||||
value={selectedAnnee}
|
||||
onChange={FilterByAnnee}
|
||||
sx={{ background: 'white' }}
|
||||
>
|
||||
<MenuItem value=""><em>Toutes les années</em></MenuItem>
|
||||
{anneesList.map((a) => (
|
||||
<MenuItem value={a.code} key={a.id}>{a.code}</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
|
||||
{/* filtre par niveau */}
|
||||
<FormControl
|
||||
sx={{
|
||||
m: 1,
|
||||
width: '25%',
|
||||
'& .MuiOutlinedInput-root': {
|
||||
'&:hover fieldset': {
|
||||
borderColor: '#ff9800' // Set the border color on hover
|
||||
|
||||
@ -3,96 +3,130 @@ import { useParams, Link } from 'react-router-dom'
|
||||
import classe from '../assets/AllStyleComponents.module.css'
|
||||
import classeHome from '../assets/Home.module.css'
|
||||
import Paper from '@mui/material/Paper'
|
||||
import { Button, Modal, Box } from '@mui/material'
|
||||
import { Button, Box, Typography, Modal } from '@mui/material'
|
||||
import { IoMdReturnRight } from 'react-icons/io'
|
||||
import AjoutTranche from './AjoutTranche'
|
||||
import { Tooltip } from 'react-tooltip'
|
||||
import { FaPenToSquare } from 'react-icons/fa6'
|
||||
import { FaTrash } from 'react-icons/fa'
|
||||
import { FaPenToSquare } from 'react-icons/fa6'
|
||||
import { MdPayment } from 'react-icons/md'
|
||||
import UpdateTranche from './UpdateTranche'
|
||||
import DeleteTranche from './DeleteTranche'
|
||||
import warning from '../assets/warning.svg'
|
||||
import success from '../assets/success.svg'
|
||||
|
||||
const TrancheEcolage = () => {
|
||||
const { id } = useParams()
|
||||
const [tranche, setTranche] = useState([])
|
||||
const [tranches, setTranches] = useState([])
|
||||
const [etudiant, setEtudiant] = useState({})
|
||||
const [montantConfig, setMontantConfig] = useState(null)
|
||||
|
||||
useEffect(() => {
|
||||
const loadData = () => {
|
||||
window.etudiants.getTranche({ id }).then((response) => {
|
||||
setTranche(response)
|
||||
setTranches(response || [])
|
||||
})
|
||||
|
||||
window.etudiants.getSingle({ id }).then((response) => {
|
||||
setEtudiant(response)
|
||||
// Charger la config ecolage pour cette mention + niveau
|
||||
if (response && response.mention_id && response.niveau) {
|
||||
window.configecolage.getByMentionNiveau({
|
||||
mention_id: response.mention_id,
|
||||
niveau_nom: response.niveau
|
||||
}).then((res) => setMontantConfig(res)).catch(() => setMontantConfig(null))
|
||||
}
|
||||
})
|
||||
}, [])
|
||||
|
||||
const [openAdd, setOpenAdd] = useState(false)
|
||||
const onCloseAdd = () => setOpenAdd(false)
|
||||
|
||||
const openAddFunction = () => {
|
||||
setOpenAdd(true)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
loadData()
|
||||
}, [])
|
||||
|
||||
// Modal ajout
|
||||
const [openAdd, setOpenAdd] = useState(false)
|
||||
const [isSubmited, setIsSubmited] = useState(false)
|
||||
|
||||
const handleFormSubmit = (status) => {
|
||||
setIsSubmited(status)
|
||||
}
|
||||
|
||||
const [openUpdate, setOpenUpdate] = useState(false)
|
||||
const onCloseUpdate = () => setOpenUpdate(false)
|
||||
const [idToSend, setIdToSend] = useState(null)
|
||||
const [idToSend2, setIdToSend2] = useState(null)
|
||||
|
||||
const openUpdateFunction = (id) => {
|
||||
setOpenUpdate(true)
|
||||
setIdToSend(id)
|
||||
}
|
||||
|
||||
const [openDelete, setOpenDelete] = useState(false)
|
||||
const onCloseDelete = () => setOpenDelete(false)
|
||||
const openDeleteFunction = (id) => {
|
||||
setOpenDelete(true)
|
||||
setIdToSend2(id)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (isSubmited) {
|
||||
window.etudiants.getTranche({ id }).then((response) => {
|
||||
setTranche(response)
|
||||
})
|
||||
loadData()
|
||||
setIsSubmited(false)
|
||||
}
|
||||
}, [isSubmited])
|
||||
|
||||
// Modal modification
|
||||
const [openUpdate, setOpenUpdate] = useState(false)
|
||||
const [trancheToEdit, setTrancheToEdit] = useState(null)
|
||||
|
||||
// Modal suppression
|
||||
const [openDeleteModal, setOpenDeleteModal] = useState(false)
|
||||
const [isDeleted, setIsDeleted] = useState(false)
|
||||
const [idToDelete, setIdToDelete] = useState(null)
|
||||
|
||||
const handleDelete = async () => {
|
||||
const response = await window.etudiants.deleteTranche({ id: idToDelete })
|
||||
if (response.success) {
|
||||
setIsDeleted(true)
|
||||
loadData()
|
||||
}
|
||||
}
|
||||
|
||||
const deleteModal = () => (
|
||||
<Modal open={openDeleteModal} onClose={() => { setOpenDeleteModal(false); setIsDeleted(false) }}>
|
||||
<Box sx={{
|
||||
position: 'absolute', top: '50%', left: '50%',
|
||||
transform: 'translate(-50%, -50%)', width: 400,
|
||||
bgcolor: 'background.paper', boxShadow: 24, p: 4
|
||||
}}>
|
||||
{isDeleted ? (
|
||||
<Typography style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '10px' }}>
|
||||
<img src={success} alt="" width={50} height={50} />
|
||||
<span>Supprime avec succes</span>
|
||||
</Typography>
|
||||
) : (
|
||||
<Typography style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '10px' }}>
|
||||
<img src={warning} alt="" width={50} height={50} />
|
||||
<span>Voulez-vous supprimer ce paiement ?</span>
|
||||
</Typography>
|
||||
)}
|
||||
<Box sx={{ mt: 2, display: 'flex', gap: '10px', justifyContent: 'flex-end' }}>
|
||||
{isDeleted ? (
|
||||
<Button onClick={() => { setOpenDeleteModal(false); setIsDeleted(false) }} color="warning" variant="contained">OK</Button>
|
||||
) : (
|
||||
<>
|
||||
<Button onClick={handleDelete} color="error" variant="contained">Oui</Button>
|
||||
<Button onClick={() => setOpenDeleteModal(false)} color="warning" variant="contained">Non</Button>
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
</Modal>
|
||||
)
|
||||
|
||||
return (
|
||||
<div className={classe.mainHome}>
|
||||
<AjoutTranche
|
||||
id={id}
|
||||
onClose={onCloseAdd}
|
||||
onClose={() => setOpenAdd(false)}
|
||||
onSubmitSuccess={handleFormSubmit}
|
||||
open={openAdd}
|
||||
/>
|
||||
{deleteModal()}
|
||||
<UpdateTranche
|
||||
onClose={onCloseUpdate}
|
||||
onSubmitSuccess={handleFormSubmit}
|
||||
open={openUpdate}
|
||||
id={idToSend}
|
||||
/>
|
||||
<DeleteTranche
|
||||
id={idToSend2}
|
||||
onClose={onCloseDelete}
|
||||
onClose={() => setOpenUpdate(false)}
|
||||
onSubmitSuccess={handleFormSubmit}
|
||||
open={openDelete}
|
||||
tranche={trancheToEdit}
|
||||
/>
|
||||
<div className={classeHome.header}>
|
||||
<div className={classe.h1style}>
|
||||
<div className={classeHome.blockTitle}>
|
||||
<h1>Tranche d'Ecolage</h1>
|
||||
<h1>Frais de Formation</h1>
|
||||
<div style={{ display: 'flex', gap: '10px' }}>
|
||||
<Link to={'#'} onClick={openAddFunction}>
|
||||
<Link to={'#'} onClick={() => setOpenAdd(true)}>
|
||||
<Button color="warning" variant="contained">
|
||||
Ajouter
|
||||
<MdPayment style={{ fontSize: '20px', marginRight: 5 }} /> Ajouter un paiement
|
||||
</Button>
|
||||
</Link>
|
||||
<Link to={'#'} onClick={() => window.history.back()}>
|
||||
@ -106,83 +140,111 @@ const TrancheEcolage = () => {
|
||||
</div>
|
||||
|
||||
<div className={classeHome.boxEtudiantsCard}>
|
||||
<Paper
|
||||
sx={{
|
||||
height: 'auto', // Auto height to make the grid responsive
|
||||
width: '100%',
|
||||
// minHeight: 500, // Ensures a minimum height
|
||||
display: 'flex',
|
||||
padding: '2%'
|
||||
}}
|
||||
>
|
||||
<table className="table table-bordered table-striped text-center shadow-sm" id="myTable2">
|
||||
<Paper sx={{ height: 'auto', width: '100%', display: 'flex', padding: '2%' }}>
|
||||
<table className="table table-bordered table-striped text-center shadow-sm">
|
||||
<thead className="table-secondary">
|
||||
<tr>
|
||||
<td colSpan={4} className="py-3">
|
||||
<td colSpan={8} className="py-3">
|
||||
<h6>
|
||||
Evolution d'écolage de {etudiant.nom} {etudiant.prenom}
|
||||
Evolution d'ecolage de <b>{etudiant.nom} {etudiant.prenom}</b> - N°: {etudiant.num_inscription}
|
||||
</h6>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Tranche N°</th>
|
||||
<th>Désignation</th>
|
||||
<th>Montant</th>
|
||||
<th>Annee scolaire</th>
|
||||
<th>Tranche 1 - N° Bordereau</th>
|
||||
<th>Tranche 1 - Montant</th>
|
||||
<th>Tranche 2 - N° Bordereau</th>
|
||||
<th>Tranche 2 - Montant</th>
|
||||
<th>Reste a payer</th>
|
||||
<th>Statut</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{tranche.map((tranch, index) => (
|
||||
<tr key={tranch.id}>
|
||||
<td>{index + 1}</td>
|
||||
<td>{tranch.tranchename}</td>
|
||||
<td>{Number(tranch.montant).toLocaleString(0, 3)}</td>
|
||||
<td
|
||||
className="fw-bold"
|
||||
style={{
|
||||
display: 'flex',
|
||||
gap: '10px',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center'
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="warning"
|
||||
onClick={() => openUpdateFunction(tranch.id)}
|
||||
>
|
||||
<FaPenToSquare
|
||||
style={{ fontSize: '20px', color: 'white' }}
|
||||
className={`update${tranch.id}`}
|
||||
/>
|
||||
<Tooltip
|
||||
anchorSelect={`.update${tranch.id}`}
|
||||
className="custom-tooltip"
|
||||
place="top"
|
||||
>
|
||||
Modifier
|
||||
</Tooltip>
|
||||
</Button>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="error"
|
||||
onClick={() => openDeleteFunction(tranch.id)}
|
||||
>
|
||||
<FaTrash
|
||||
style={{ fontSize: '20px', color: 'white' }}
|
||||
className={`delete${tranch.id}`}
|
||||
/>
|
||||
<Tooltip
|
||||
anchorSelect={`.delete${tranch.id}`}
|
||||
className="custom-tooltip"
|
||||
place="top"
|
||||
>
|
||||
Supprimer
|
||||
</Tooltip>
|
||||
</Button>
|
||||
</td>
|
||||
{tranches.length === 0 ? (
|
||||
<tr>
|
||||
<td colSpan={8} style={{ color: 'gray', padding: '20px' }}>Aucun paiement enregistre</td>
|
||||
</tr>
|
||||
))}
|
||||
) : (
|
||||
tranches.map((t) => {
|
||||
const total = (t.tranche1_montant || 0) + (t.tranche2_montant || 0)
|
||||
const hasTranche1 = t.tranche1_montant > 0
|
||||
const hasTranche2 = t.tranche2_montant > 0
|
||||
|
||||
return (
|
||||
<tr key={t.id}>
|
||||
<td><b>{t.annee_scolaire_code}</b></td>
|
||||
<td>{t.tranche1_bordereau || <span style={{ color: '#ccc' }}>-</span>}</td>
|
||||
<td>
|
||||
{hasTranche1 ? (
|
||||
<span style={{ color: 'green', fontWeight: 'bold' }}>
|
||||
{Number(t.tranche1_montant).toLocaleString('fr-FR')} Ar
|
||||
</span>
|
||||
) : (
|
||||
<span style={{ color: '#ccc' }}>Non paye</span>
|
||||
)}
|
||||
</td>
|
||||
<td>{t.tranche2_bordereau || <span style={{ color: '#ccc' }}>-</span>}</td>
|
||||
<td>
|
||||
{hasTranche2 ? (
|
||||
<span style={{ color: 'green', fontWeight: 'bold' }}>
|
||||
{Number(t.tranche2_montant).toLocaleString('fr-FR')} Ar
|
||||
</span>
|
||||
) : (
|
||||
<span style={{ color: '#ccc' }}>Non paye</span>
|
||||
)}
|
||||
</td>
|
||||
<td>
|
||||
{(() => {
|
||||
const droitTotal = montantConfig ? montantConfig.montant_total : 0
|
||||
const reste = Math.max(0, droitTotal - total)
|
||||
return (
|
||||
<b style={{ color: reste === 0 ? 'green' : 'red' }}>
|
||||
{Number(reste).toLocaleString('fr-FR')} Ar
|
||||
</b>
|
||||
)
|
||||
})()}
|
||||
</td>
|
||||
<td>
|
||||
{hasTranche1 && hasTranche2 ? (
|
||||
<span style={{ color: 'green', fontWeight: 'bold' }}>Complet</span>
|
||||
) : hasTranche1 || hasTranche2 ? (
|
||||
<span style={{ color: 'orange', fontWeight: 'bold' }}>Partiel</span>
|
||||
) : (
|
||||
<span style={{ color: 'red', fontWeight: 'bold' }}>Non paye</span>
|
||||
)}
|
||||
</td>
|
||||
<td style={{ display: 'flex', gap: '8px', justifyContent: 'center' }}>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="warning"
|
||||
size="small"
|
||||
onClick={() => { setTrancheToEdit(t); setOpenUpdate(true) }}
|
||||
className={`edit${t.id}`}
|
||||
>
|
||||
<FaPenToSquare style={{ fontSize: '16px', color: 'white' }} />
|
||||
</Button>
|
||||
<Tooltip anchorSelect={`.edit${t.id}`} className="custom-tooltip" place="top">
|
||||
Modifier
|
||||
</Tooltip>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="error"
|
||||
size="small"
|
||||
onClick={() => { setIdToDelete(t.id); setOpenDeleteModal(true) }}
|
||||
className={`del${t.id}`}
|
||||
>
|
||||
<FaTrash style={{ fontSize: '16px', color: 'white' }} />
|
||||
</Button>
|
||||
<Tooltip anchorSelect={`.del${t.id}`} className="custom-tooltip" place="top">
|
||||
Supprimer
|
||||
</Tooltip>
|
||||
</td>
|
||||
</tr>
|
||||
)
|
||||
})
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</Paper>
|
||||
|
||||
@ -6,38 +6,30 @@ import {
|
||||
DialogTitle,
|
||||
TextField,
|
||||
Button,
|
||||
Autocomplete,
|
||||
InputAdornment,
|
||||
Box,
|
||||
Grid
|
||||
} from '@mui/material'
|
||||
import { MdLabelImportantOutline } from 'react-icons/md'
|
||||
|
||||
const UpdateTranche = ({ open, onClose, onSubmitSuccess, id }) => {
|
||||
const UpdateTranche = ({ open, onClose, onSubmitSuccess, tranche }) => {
|
||||
const [formData, setFormData] = useState({
|
||||
id: id,
|
||||
tranchename: '',
|
||||
montant: ''
|
||||
id: '',
|
||||
tranche1_montant: '',
|
||||
tranche1_bordereau: '',
|
||||
tranche2_montant: '',
|
||||
tranche2_bordereau: ''
|
||||
})
|
||||
const [tranche, setTranche] = useState([])
|
||||
|
||||
useEffect(() => {
|
||||
if (id !== null) {
|
||||
window.etudiants.getSingleTranche({ id }).then((response) => {
|
||||
setTranche(response)
|
||||
})
|
||||
if (tranche) {
|
||||
setFormData({
|
||||
id: id
|
||||
id: tranche.id || '',
|
||||
tranche1_montant: tranche.tranche1_montant || '',
|
||||
tranche1_bordereau: tranche.tranche1_bordereau || '',
|
||||
tranche2_montant: tranche.tranche2_montant || '',
|
||||
tranche2_bordereau: tranche.tranche2_bordereau || ''
|
||||
})
|
||||
}
|
||||
}, [id])
|
||||
|
||||
useEffect(() => {
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
tranchename: tranche.tranchename || '',
|
||||
montant: tranche.montant || ''
|
||||
}))
|
||||
}, [tranche])
|
||||
|
||||
const handleChange = (e) => {
|
||||
@ -47,64 +39,81 @@ const UpdateTranche = ({ open, onClose, onSubmitSuccess, id }) => {
|
||||
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault()
|
||||
let response = await window.etudiants.updateTranche(formData)
|
||||
|
||||
if (response.changes) {
|
||||
const response = await window.etudiants.updateTranche(formData)
|
||||
if (response.success) {
|
||||
onClose()
|
||||
onSubmitSuccess(true)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog open={open} onClose={onClose}>
|
||||
<form action="" onSubmit={handleSubmit}>
|
||||
<DialogTitle>Ajout tranche</DialogTitle>
|
||||
<Dialog open={open} onClose={onClose} maxWidth="sm" fullWidth>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<DialogTitle>Modifier le paiement</DialogTitle>
|
||||
<DialogContent>
|
||||
<Box sx={{ flexGrow: 1 }}>
|
||||
<Box sx={{ flexGrow: 1, mt: 1 }}>
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={12}>
|
||||
<b>Tranche 1</b>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<TextField
|
||||
autoFocus
|
||||
margin="normal"
|
||||
required
|
||||
name="tranchename"
|
||||
label="Désignation"
|
||||
name="tranche1_bordereau"
|
||||
label="N° Bordereau"
|
||||
type="text"
|
||||
fullWidth
|
||||
placeholder="Tranche 1"
|
||||
size="small"
|
||||
variant="outlined"
|
||||
value={formData.tranchename}
|
||||
value={formData.tranche1_bordereau}
|
||||
color="warning"
|
||||
onChange={handleChange}
|
||||
InputProps={{
|
||||
startAdornment: (
|
||||
<InputAdornment position="start">
|
||||
<MdLabelImportantOutline />
|
||||
</InputAdornment>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<TextField
|
||||
autoFocus
|
||||
margin="normal"
|
||||
required
|
||||
name="montant"
|
||||
name="tranche1_montant"
|
||||
label="Montant"
|
||||
type="number"
|
||||
fullWidth
|
||||
placeholder="Montant"
|
||||
size="small"
|
||||
variant="outlined"
|
||||
value={formData.montant}
|
||||
value={formData.tranche1_montant}
|
||||
color="warning"
|
||||
onChange={handleChange}
|
||||
InputProps={{
|
||||
startAdornment: (
|
||||
<InputAdornment position="start">
|
||||
<MdLabelImportantOutline />
|
||||
</InputAdornment>
|
||||
)
|
||||
startAdornment: <InputAdornment position="start">Ar</InputAdornment>
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12} sx={{ mt: 1 }}>
|
||||
<b>Tranche 2</b>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<TextField
|
||||
name="tranche2_bordereau"
|
||||
label="N° Bordereau"
|
||||
type="text"
|
||||
fullWidth
|
||||
size="small"
|
||||
variant="outlined"
|
||||
value={formData.tranche2_bordereau}
|
||||
color="warning"
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<TextField
|
||||
name="tranche2_montant"
|
||||
label="Montant"
|
||||
type="number"
|
||||
fullWidth
|
||||
size="small"
|
||||
variant="outlined"
|
||||
value={formData.tranche2_montant}
|
||||
color="warning"
|
||||
onChange={handleChange}
|
||||
InputProps={{
|
||||
startAdornment: <InputAdornment position="start">Ar</InputAdornment>
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
@ -112,12 +121,8 @@ const UpdateTranche = ({ open, onClose, onSubmitSuccess, id }) => {
|
||||
</Box>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={onClose} color="error">
|
||||
Annuler
|
||||
</Button>
|
||||
<Button type="submit" color="warning">
|
||||
Soumettre
|
||||
</Button>
|
||||
<Button onClick={onClose} color="error">Annuler</Button>
|
||||
<Button type="submit" color="warning" variant="contained">Enregistrer</Button>
|
||||
</DialogActions>
|
||||
</form>
|
||||
</Dialog>
|
||||
|
||||
@ -32,10 +32,14 @@ function nextLevel(niveau) {
|
||||
}
|
||||
}
|
||||
|
||||
export const descisionJury = (notes, niveau) => {
|
||||
if (notes >= 10) {
|
||||
return `Admis en ${nextLevel(niveau)}`
|
||||
export const descisionJury = (notes, niveau, systeme) => {
|
||||
if (!systeme) return ''
|
||||
|
||||
if (notes >= systeme.admis) {
|
||||
return `Admis`
|
||||
} else if (notes > systeme.renvoyer) {
|
||||
return `Redoublant`
|
||||
} else {
|
||||
return 'Vous redoublez'
|
||||
return `Renvoyé`
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user