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
|
```bash
|
||||||
$ npm install
|
git clone <url-du-repo>
|
||||||
|
cd c-university
|
||||||
```
|
```
|
||||||
|
|
||||||
### Development
|
### 2. Installer les dépendances
|
||||||
|
|
||||||
```bash
|
```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
|
```bash
|
||||||
# For windows
|
npm run dev
|
||||||
$ npm run build:win
|
|
||||||
|
|
||||||
# For macOS
|
|
||||||
$ npm run build:mac
|
|
||||||
|
|
||||||
# For Linux
|
|
||||||
$ npm run build:linux
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 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 sql = 'UPDATE anneescolaire SET is_current = 0 WHERE id > 0 AND is_current = 1'
|
||||||
const sql2 = 'UPDATE anneescolaire SET is_current = 1 WHERE id = ?'
|
const sql2 = 'UPDATE anneescolaire SET is_current = 1 WHERE id = ?'
|
||||||
|
|
||||||
pool.query(sql)
|
await pool.query(sql)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const [result] = pool.query(sql2, [id])
|
const [result] = await pool.query(sql2, [id])
|
||||||
console.log(result)
|
console.log(result)
|
||||||
|
|
||||||
return {
|
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')
|
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(
|
async function insertEtudiant(
|
||||||
nom,
|
nom,
|
||||||
@ -24,51 +38,65 @@ async function insertEtudiant(
|
|||||||
contact,
|
contact,
|
||||||
parcours
|
parcours
|
||||||
) {
|
) {
|
||||||
const sql =
|
const conn = await pool.getConnection()
|
||||||
'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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let [result] = await pool.query(sql, [
|
await conn.beginTransaction()
|
||||||
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
|
|
||||||
])
|
|
||||||
|
|
||||||
return {
|
// 1. Insert permanent info into etudiants
|
||||||
success: true,
|
const [etudiantResult] = await conn.query(
|
||||||
id: result.insertId
|
`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) {
|
} catch (error) {
|
||||||
|
await conn.rollback()
|
||||||
return error
|
return error
|
||||||
|
} finally {
|
||||||
|
conn.release()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* function to get all etudiants
|
* Get all students filtered by a specific annee_scolaire
|
||||||
*
|
|
||||||
* @returns JSON
|
|
||||||
*/
|
*/
|
||||||
async function getAllEtudiants() {
|
async function getAllEtudiantsByAnnee(annee_scolaire) {
|
||||||
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'
|
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 {
|
try {
|
||||||
let [rows] = await pool.query(sql)
|
const [rows] = await pool.query(sql, [annee_scolaire])
|
||||||
|
|
||||||
return rows
|
return rows
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return error
|
return error
|
||||||
@ -76,18 +104,51 @@ async function getAllEtudiants() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* function to return a single etudiant
|
* Get all students with ALL their inscriptions (une ligne par inscription)
|
||||||
* and display it on the screen
|
*/
|
||||||
*
|
async function getAllEtudiants() {
|
||||||
* @param {int} id
|
const sql = `
|
||||||
* @returns Promise
|
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) {
|
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 {
|
try {
|
||||||
const [rows] = await pool.query(sql, [id])
|
const [rows] = await pool.query(sql, [id])
|
||||||
|
|
||||||
return rows[0]
|
return rows[0]
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return error
|
return error
|
||||||
@ -95,15 +156,25 @@ async function getSingleEtudiant(id) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* function to get all etudiants M2
|
* Filter students by their latest inscription niveau
|
||||||
*
|
|
||||||
* @returns JSON
|
|
||||||
*/
|
*/
|
||||||
async function FilterDataByNiveau(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 {
|
try {
|
||||||
let [rows] = await pool.query(sql, [niveau])
|
const [rows] = await pool.query(sql, [niveau])
|
||||||
|
|
||||||
return rows
|
return rows
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return error
|
return error
|
||||||
@ -111,18 +182,7 @@ async function FilterDataByNiveau(niveau) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* function to update etudiants
|
* Update a student: permanent fields in etudiants, annual fields in latest inscription
|
||||||
*
|
|
||||||
* @param {*} nom
|
|
||||||
* @param {*} prenom
|
|
||||||
* @param {*} photos
|
|
||||||
* @param {*} date_de_naissances
|
|
||||||
* @param {*} niveau
|
|
||||||
* @param {*} annee_scolaire
|
|
||||||
* @param {*} status
|
|
||||||
* @param {*} num_inscription
|
|
||||||
* @param {*} id
|
|
||||||
* @returns promise
|
|
||||||
*/
|
*/
|
||||||
async function updateEtudiant(
|
async function updateEtudiant(
|
||||||
nom,
|
nom,
|
||||||
@ -146,67 +206,88 @@ async function updateEtudiant(
|
|||||||
contact,
|
contact,
|
||||||
parcours
|
parcours
|
||||||
) {
|
) {
|
||||||
const sql =
|
const conn = await pool.getConnection()
|
||||||
'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 = ?'
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let [result] = await pool.query(sql, [
|
await conn.beginTransaction()
|
||||||
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
|
|
||||||
])
|
|
||||||
|
|
||||||
if (result.affectedRows === 0) {
|
// Update permanent fields (sans num_inscription car il est propre à chaque année)
|
||||||
return {
|
await conn.query(
|
||||||
success: false,
|
`UPDATE etudiants SET nom=?, prenom=?, photos=?, date_de_naissances=?,
|
||||||
message: 'Année Univesitaire non trouvé.'
|
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 {
|
await conn.commit()
|
||||||
success: true,
|
return { success: true, message: 'Étudiant mis à jour avec succès.' }
|
||||||
message: 'Année Univesitaire supprimé avec succès.'
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
await conn.rollback()
|
||||||
return error
|
return error
|
||||||
|
} finally {
|
||||||
|
conn.release()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* function to return the needed data in dashboard
|
* Get dashboard data
|
||||||
*
|
|
||||||
* @returns promise
|
|
||||||
*/
|
*/
|
||||||
async function getDataToDashboard() {
|
async function getDataToDashboard() {
|
||||||
const query = 'SELECT * FROM niveaus'
|
const query = 'SELECT * FROM niveaus'
|
||||||
const query2 = 'SELECT * FROM etudiants'
|
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 {
|
try {
|
||||||
let [rows] = await pool.query(query)
|
const [niveau] = await pool.query(query)
|
||||||
let niveau = rows
|
const [etudiants] = await pool.query(query2)
|
||||||
;[rows] = await pool.query(query2)
|
const [anne_scolaire] = await pool.query(query3)
|
||||||
let etudiants = rows
|
|
||||||
;[rows] = await pool.query(query3)
|
|
||||||
let anne_scolaire = rows
|
|
||||||
|
|
||||||
return { niveau, etudiants, anne_scolaire }
|
return { niveau, etudiants, anne_scolaire }
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return error
|
return error
|
||||||
@ -215,170 +296,273 @@ async function getDataToDashboard() {
|
|||||||
|
|
||||||
async function changePDP(photos, id) {
|
async function changePDP(photos, id) {
|
||||||
const sql = 'UPDATE etudiants SET photos = ? WHERE id = ?'
|
const sql = 'UPDATE etudiants SET photos = ? WHERE id = ?'
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let [result] = await pool.query(sql, [photos, id])
|
const [result] = await pool.query(sql, [photos, id])
|
||||||
|
|
||||||
if (result.affectedRows === 0) {
|
if (result.affectedRows === 0) {
|
||||||
return {
|
return { success: false, message: 'Étudiant non trouvé.' }
|
||||||
success: false,
|
|
||||||
message: 'Année Univesitaire non trouvé.'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
message: 'Année Univesitaire supprimé avec succès.'
|
|
||||||
}
|
}
|
||||||
|
return { success: true, message: 'Photo mise à jour avec succès.' }
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return error
|
return error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function updateParcours(parcours, id) {
|
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 {
|
try {
|
||||||
let [result] = await pool.query(sql, [parcours, id])
|
const [result] = await pool.query(sql, [parcours, id, id])
|
||||||
|
|
||||||
if (result.affectedRows === 0) {
|
if (result.affectedRows === 0) {
|
||||||
return {
|
return { success: false, message: 'Inscription non trouvée.' }
|
||||||
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: true, message: 'Parcours mis à jour avec succès.' }
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return error
|
return error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function deleteEtudiant(id) {
|
async function deleteEtudiant(id) {
|
||||||
console.log("id: ", id);
|
const conn = await pool.getConnection()
|
||||||
const sql = 'DELETE FROM etudiants WHERE id = ?';
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let [result] = await pool.query(sql, [id]);
|
await conn.beginTransaction()
|
||||||
console.log("Résultat DELETE:", result);
|
// 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) {
|
if (result.affectedRows === 0) {
|
||||||
return {
|
return { success: false, message: 'Étudiant non trouvé.' }
|
||||||
success: false,
|
|
||||||
message: 'Etudiant non trouvée.'
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
return { success: true, message: 'Étudiant supprimé avec succès.' }
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
message: 'Matière supprimée avec succès.'
|
|
||||||
};
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("err: ",+ error)
|
await conn.rollback()
|
||||||
return { success: false, error: 'Erreur, veuillez réessayer: ' + error };
|
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 {
|
try {
|
||||||
const [rows] = await pool.query(sql, [id])
|
// Vérifier si une ligne existe déjà
|
||||||
return rows[0]
|
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) {
|
} 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 = {
|
module.exports = {
|
||||||
insertEtudiant,
|
insertEtudiant,
|
||||||
getAllEtudiants,
|
getAllEtudiants,
|
||||||
|
getAllEtudiantsByAnnee,
|
||||||
FilterDataByNiveau,
|
FilterDataByNiveau,
|
||||||
getSingleEtudiant,
|
getSingleEtudiant,
|
||||||
updateEtudiant,
|
updateEtudiant,
|
||||||
getDataToDashboard,
|
getDataToDashboard,
|
||||||
changePDP,
|
changePDP,
|
||||||
updateParcours,
|
updateParcours,
|
||||||
createTranche,
|
payerTranche,
|
||||||
getTranche,
|
getTranche,
|
||||||
updateTranche,
|
updateTranche,
|
||||||
deleteTranche,
|
deleteTranche,
|
||||||
deleteEtudiant,
|
deleteEtudiant,
|
||||||
getSingleTranche
|
getEtudiantsWithPaidTranche,
|
||||||
|
reinscribeEtudiant
|
||||||
}
|
}
|
||||||
|
|||||||
@ -64,17 +64,18 @@ async function getNoteOnline() {
|
|||||||
*
|
*
|
||||||
* @returns promise
|
* @returns promise
|
||||||
*/
|
*/
|
||||||
async function getNoteRepech(id, niveau) {
|
async function getNoteRepech(id, niveau, annee_scolaire) {
|
||||||
const query = `
|
const query = `
|
||||||
SELECT notesrepech.*, matieres.*
|
SELECT notesrepech.*, matieres.*
|
||||||
FROM notesrepech
|
FROM notesrepech
|
||||||
JOIN matieres ON notesrepech.matiere_id = matieres.id
|
JOIN matieres ON notesrepech.matiere_id = matieres.id
|
||||||
WHERE notesrepech.etudiant_id = ?
|
WHERE notesrepech.etudiant_id = ?
|
||||||
AND notesrepech.etudiant_niveau = ?
|
AND notesrepech.etudiant_niveau = ?
|
||||||
|
AND notesrepech.annee_scolaire = ?
|
||||||
`
|
`
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const [rows] = await pool.query(query, [id, niveau])
|
const [rows] = await pool.query(query, [id, niveau, annee_scolaire])
|
||||||
return rows
|
return rows
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error in getNoteRepech:', error)
|
console.error('Error in getNoteRepech:', error)
|
||||||
@ -140,14 +141,14 @@ async function showMoyenRepech(niveau, scolaire) {
|
|||||||
* @param {string} niveau - The student level
|
* @param {string} niveau - The student level
|
||||||
* @returns {Promise} - Promise resolving to the database response or an error
|
* @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 matiere_ids = Object.keys(formData)
|
||||||
const values = Object.values(formData)
|
const values = Object.values(formData)
|
||||||
|
|
||||||
const query = `
|
const query = `
|
||||||
UPDATE notesrepech
|
UPDATE notesrepech
|
||||||
SET note = ?
|
SET note = ?
|
||||||
WHERE etudiant_id = ? AND etudiant_niveau = ? AND matiere_id = ?
|
WHERE etudiant_id = ? AND etudiant_niveau = ? AND matiere_id = ? AND annee_scolaire = ?
|
||||||
`
|
`
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -156,17 +157,13 @@ async function updateNoteRepech(formData, niveau, id) {
|
|||||||
for (let index = 0; index < matiere_ids.length; index++) {
|
for (let index = 0; index < matiere_ids.length; index++) {
|
||||||
let data = values[index]
|
let data = values[index]
|
||||||
|
|
||||||
// Convert string number with comma to float, e.g. "12,5" => 12.5
|
|
||||||
if (typeof data === 'string') {
|
if (typeof data === 'string') {
|
||||||
data = parseFloat(data.replace(',', '.'))
|
data = parseFloat(data.replace(',', '.'))
|
||||||
} else {
|
} else {
|
||||||
data = parseFloat(String(data).replace(',', '.'))
|
data = parseFloat(String(data).replace(',', '.'))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Optional: console log to verify conversion
|
const [result] = await pool.query(query, [data, id, niveau, matiere_ids[index], annee_scolaire])
|
||||||
console.log(data)
|
|
||||||
|
|
||||||
const [result] = await pool.query(query, [data, id, niveau, matiere_ids[index]])
|
|
||||||
response = result
|
response = result
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -189,8 +186,8 @@ async function blockShowMoyeneRepech() {
|
|||||||
const query2 = `
|
const query2 = `
|
||||||
SELECT notesrepech.*,
|
SELECT notesrepech.*,
|
||||||
etudiants.id AS etudiantsId,
|
etudiants.id AS etudiantsId,
|
||||||
etudiants.mention_id AS mentionId,
|
notesrepech.mention_id AS mentionId,
|
||||||
etudiants.niveau,
|
notesrepech.etudiant_niveau AS niveau,
|
||||||
matieres.*
|
matieres.*
|
||||||
FROM notesrepech
|
FROM notesrepech
|
||||||
INNER JOIN etudiants ON notesrepech.etudiant_id = etudiants.id
|
INNER JOIN etudiants ON notesrepech.etudiant_id = etudiants.id
|
||||||
|
|||||||
@ -88,34 +88,19 @@ async function getNoteOnline() {
|
|||||||
*
|
*
|
||||||
* @returns promise
|
* @returns promise
|
||||||
*/
|
*/
|
||||||
async function getNote(id, niveau) {
|
async function getNote(id, niveau, annee_scolaire) {
|
||||||
let connection
|
let connection
|
||||||
try {
|
try {
|
||||||
connection = await pool.getConnection()
|
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(
|
const [response2] = await connection.execute(
|
||||||
`
|
`
|
||||||
SELECT notes.*, matieres.*
|
SELECT notes.*, matieres.*
|
||||||
FROM notes
|
FROM notes
|
||||||
JOIN matieres ON notes.matiere_id = matieres.id
|
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
|
return response2
|
||||||
@ -164,18 +149,18 @@ async function showMoyen(niveau, scolaire) {
|
|||||||
|
|
||||||
// 2. Prepare the second query
|
// 2. Prepare the second query
|
||||||
const query2 = `
|
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
|
FROM notes
|
||||||
INNER JOIN etudiants ON notes.etudiant_id = etudiants.id
|
INNER JOIN etudiants ON notes.etudiant_id = etudiants.id
|
||||||
INNER JOIN matieres ON notes.matiere_id = matieres.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
|
// 3. Loop over each student and fetch their notes
|
||||||
for (let index = 0; index < etudiantWithNotes.length; index++) {
|
for (let index = 0; index < etudiantWithNotes.length; index++) {
|
||||||
const etudiantId = etudiantWithNotes[index].etudiant_id
|
const etudiantId = etudiantWithNotes[index].etudiant_id
|
||||||
const [rows] = await pool.query(query2, [etudiantId])
|
const [rows] = await pool.query(query2, [etudiantId, scolaire])
|
||||||
allEtudiantWithNotes.push(rows) // push just the rows, not [rows, fields]
|
allEtudiantWithNotes.push(rows)
|
||||||
}
|
}
|
||||||
|
|
||||||
return allEtudiantWithNotes
|
return allEtudiantWithNotes
|
||||||
@ -207,10 +192,10 @@ async function updateNote(formData, niveau, id, mention_id, annee_scolaire) {
|
|||||||
data = parseFloat(String(data).replace(',', '.'))
|
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(
|
const [check] = await pool.query(
|
||||||
'SELECT * FROM notesrepech WHERE etudiant_id = ? AND matiere_id = ? AND etudiant_niveau = ?',
|
'SELECT * FROM notesrepech WHERE etudiant_id = ? AND matiere_id = ? AND etudiant_niveau = ? AND annee_scolaire = ?',
|
||||||
[id, matiere_id[index], niveau]
|
[id, matiere_id[index], niveau, annee_scolaire]
|
||||||
)
|
)
|
||||||
|
|
||||||
if (data < 10) {
|
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(
|
;[response] = await pool.query(
|
||||||
'UPDATE notes SET note = ? WHERE etudiant_id = ? AND etudiant_niveau = ? AND matiere_id = ?',
|
'UPDATE notes SET note = ? WHERE etudiant_id = ? AND etudiant_niveau = ? AND matiere_id = ? AND annee_scolaire = ?',
|
||||||
[data, id, niveau, matiere_id[index]]
|
[data, id, niveau, matiere_id[index], annee_scolaire]
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
// 4. Remove from notesrepech if note >= 10
|
// 4. Remove from notesrepech if note >= 10
|
||||||
await pool.query(
|
await pool.query(
|
||||||
'DELETE FROM notesrepech WHERE etudiant_id = ? AND etudiant_niveau = ? AND matiere_id = ?',
|
'DELETE FROM notesrepech WHERE etudiant_id = ? AND etudiant_niveau = ? AND matiere_id = ? AND annee_scolaire = ?',
|
||||||
[id, niveau, matiere_id[index]]
|
[id, niveau, matiere_id[index], annee_scolaire]
|
||||||
)
|
)
|
||||||
|
|
||||||
// 5. Update main note
|
// 5. Update main note for the correct year
|
||||||
;[response] = await pool.query(
|
;[response] = await pool.query(
|
||||||
'UPDATE notes SET note = ? WHERE etudiant_id = ? AND etudiant_niveau = ? AND matiere_id = ?',
|
'UPDATE notes SET note = ? WHERE etudiant_id = ? AND etudiant_niveau = ? AND matiere_id = ? AND annee_scolaire = ?',
|
||||||
[data, id, niveau, matiere_id[index]]
|
[data, id, niveau, matiere_id[index], annee_scolaire]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -258,8 +243,8 @@ async function blockShowMoyene() {
|
|||||||
SELECT
|
SELECT
|
||||||
notes.*,
|
notes.*,
|
||||||
etudiants.id AS etudiantsId,
|
etudiants.id AS etudiantsId,
|
||||||
etudiants.mention_id AS mentionId,
|
notes.mention_id AS mentionId,
|
||||||
etudiants.niveau,
|
notes.etudiant_niveau AS niveau,
|
||||||
matieres.*
|
matieres.*
|
||||||
FROM notes
|
FROM notes
|
||||||
INNER JOIN etudiants ON notes.etudiant_id = etudiants.id
|
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++) {
|
for (let index = 0; index < newResponse.length; index++) {
|
||||||
const [students] = await connection.query(
|
const [students] = await connection.query(
|
||||||
`
|
`
|
||||||
SELECT * FROM etudiants
|
SELECT e.*, i.niveau, i.mention_id, i.parcours, i.status, a.code AS annee_scolaire
|
||||||
WHERE niveau LIKE ? AND annee_scolaire LIKE ?
|
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}%`]
|
[`%${newResponse[index]}%`, `%${now}%`]
|
||||||
)
|
)
|
||||||
|
|||||||
@ -65,13 +65,9 @@ async function createTables() {
|
|||||||
prenom VARCHAR(250) DEFAULT NULL,
|
prenom VARCHAR(250) DEFAULT NULL,
|
||||||
photos TEXT DEFAULT NULL,
|
photos TEXT DEFAULT NULL,
|
||||||
date_de_naissances DATE DEFAULT NULL,
|
date_de_naissances DATE DEFAULT NULL,
|
||||||
niveau VARCHAR(250) NOT NULL,
|
num_inscription TEXT DEFAULT NULL,
|
||||||
annee_scolaire VARCHAR(20) NOT NULL,
|
|
||||||
status INT DEFAULT NULL,
|
|
||||||
mention_id INT NOT NULL,
|
|
||||||
num_inscription TEXT UNIQUE NOT NULL,
|
|
||||||
sexe VARCHAR(20) DEFAULT NULL,
|
sexe VARCHAR(20) DEFAULT NULL,
|
||||||
cin VARCHAR(250) DEFAULT NULL,
|
cin TEXT DEFAULT NULL,
|
||||||
date_delivrance TEXT DEFAULT NULL,
|
date_delivrance TEXT DEFAULT NULL,
|
||||||
nationalite VARCHAR(250) DEFAULT NULL,
|
nationalite VARCHAR(250) DEFAULT NULL,
|
||||||
annee_bacc TEXT DEFAULT NULL,
|
annee_bacc TEXT DEFAULT NULL,
|
||||||
@ -79,11 +75,8 @@ async function createTables() {
|
|||||||
boursier VARCHAR(20) DEFAULT NULL,
|
boursier VARCHAR(20) DEFAULT NULL,
|
||||||
domaine VARCHAR(250) DEFAULT NULL,
|
domaine VARCHAR(250) DEFAULT NULL,
|
||||||
contact VARCHAR(20) DEFAULT NULL,
|
contact VARCHAR(20) DEFAULT NULL,
|
||||||
parcours VARCHAR(250) DEFAULT NULL,
|
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE 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)
|
|
||||||
) ENGINE=InnoDB;
|
) ENGINE=InnoDB;
|
||||||
`)
|
`)
|
||||||
|
|
||||||
@ -188,6 +181,23 @@ async function createTables() {
|
|||||||
) ENGINE=InnoDB;
|
) 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(`
|
await connection.query(`
|
||||||
CREATE TABLE IF NOT EXISTS traitmentsystem (
|
CREATE TABLE IF NOT EXISTS traitmentsystem (
|
||||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
@ -248,8 +258,27 @@ async function createTables() {
|
|||||||
CREATE TABLE IF NOT EXISTS trancheecolage (
|
CREATE TABLE IF NOT EXISTS trancheecolage (
|
||||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
etudiant_id INT NOT NULL,
|
etudiant_id INT NOT NULL,
|
||||||
tranchename VARCHAR(255) NOT NULL,
|
annee_scolaire_id INT NOT NULL,
|
||||||
montant DOUBLE 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;
|
) ENGINE=InnoDB;
|
||||||
`)
|
`)
|
||||||
|
|
||||||
|
|||||||
@ -68,159 +68,161 @@ async function updateStudents() {
|
|||||||
const connection = await pool.getConnection()
|
const connection = await pool.getConnection()
|
||||||
try {
|
try {
|
||||||
await connection.beginTransaction()
|
await connection.beginTransaction()
|
||||||
|
const today = dayjs().format('YYYY-MM-DD')
|
||||||
|
|
||||||
// Get unfinished years (only one record assumed)
|
// 1. Récupérer toutes les années scolaires triées par debut
|
||||||
const [unfinishedYearsRows] = await connection.query(
|
const [allYears] = await connection.query(
|
||||||
'SELECT * FROM traitmentsystem WHERE is_finished = 0 ORDER BY id ASC LIMIT 1'
|
'SELECT * FROM anneescolaire ORDER BY debut ASC'
|
||||||
)
|
)
|
||||||
if (unfinishedYearsRows.length === 0) {
|
if (allYears.length < 2) {
|
||||||
await connection.release()
|
await connection.rollback()
|
||||||
return { message: 'No unfinished years found.' }
|
connection.release()
|
||||||
}
|
return { message: 'Pas assez d\'années scolaires configurées.' }
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get distinct student IDs with notesrepech
|
// 2. Seuils de notes
|
||||||
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
|
|
||||||
const [noteSystemRows] = await connection.query('SELECT * FROM notesystems LIMIT 1')
|
const [noteSystemRows] = await connection.query('SELECT * FROM notesystems LIMIT 1')
|
||||||
const noteSystem = noteSystemRows[0]
|
const noteSystem = noteSystemRows[0]
|
||||||
|
|
||||||
// Update etudiants based on moyenne
|
let totalProcessed = 0
|
||||||
for (const student of dataToMap) {
|
|
||||||
let status, newNiveau, newAnnee
|
|
||||||
newAnnee = updateSchoolYear(student.annee_scolaire)
|
|
||||||
|
|
||||||
if (student.moyenne >= noteSystem.admis) {
|
// 3. Pour chaque paire d'années consécutives (annéeN → annéeN+1)
|
||||||
newNiveau = nextLevel(student.niveau)
|
for (let i = 0; i < allYears.length - 1; i++) {
|
||||||
status = 2 // Passed
|
const prevYear = allYears[i]
|
||||||
} else if (student.moyenne >= noteSystem.redouble) {
|
const nextYear = allYears[i + 1]
|
||||||
newNiveau = student.niveau
|
|
||||||
status = 3 // Repeat
|
|
||||||
} else {
|
|
||||||
newNiveau = student.niveau
|
|
||||||
status = 4 // Fail
|
|
||||||
}
|
|
||||||
|
|
||||||
await connection.query(
|
// Traiter uniquement si l'année suivante a déjà commencé
|
||||||
'UPDATE etudiants SET niveau = ?, annee_scolaire = ?, status = ? WHERE id = ?',
|
if (nextYear.debut > today) continue
|
||||||
[newNiveau, newAnnee, status, student.id]
|
|
||||||
|
// 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
|
// 4. Mettre à jour traitmentsystem (nettoyage)
|
||||||
await connection.query('UPDATE traitmentsystem SET is_finished = 1 WHERE id = ?', [
|
await connection.query(
|
||||||
unfinishedYear.id
|
'UPDATE traitmentsystem SET is_finished = 1 WHERE is_finished = 0'
|
||||||
])
|
)
|
||||||
|
|
||||||
await connection.commit()
|
await connection.commit()
|
||||||
connection.release()
|
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) {
|
} catch (error) {
|
||||||
await connection.rollback()
|
await connection.rollback()
|
||||||
connection.release()
|
connection.release()
|
||||||
console.error(error)
|
console.error('❌ updateStudents error:', error)
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,6 +9,12 @@ const customParseFormat = require('dayjs/plugin/customParseFormat');
|
|||||||
dayjs.extend(customParseFormat);
|
dayjs.extend(customParseFormat);
|
||||||
|
|
||||||
// ---------- Fonctions utilitaires ----------
|
// ---------- 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) {
|
function fixEncoding(str) {
|
||||||
if (typeof str !== 'string') return str;
|
if (typeof str !== 'string') return str;
|
||||||
return str
|
return str
|
||||||
@ -62,13 +68,8 @@ function convertToISODate(input) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Vérifie année bissextile
|
// ---------- UPDATE étudiant existant ----------
|
||||||
function isLeapYear(year) {
|
async function updateEtudiant(row, conn) {
|
||||||
return (year % 4 === 0 && year % 100 !== 0) || (year % 400 === 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------- UPDATE étudiant ----------
|
|
||||||
async function updateEtudiant(row) {
|
|
||||||
const fields = [];
|
const fields = [];
|
||||||
const params = [];
|
const params = [];
|
||||||
|
|
||||||
@ -79,13 +80,10 @@ async function updateEtudiant(row) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Only permanent fields in etudiants
|
||||||
addFieldIfValue('nom', row.nom);
|
addFieldIfValue('nom', row.nom);
|
||||||
addFieldIfValue('prenom', row.prenom);
|
addFieldIfValue('prenom', row.prenom);
|
||||||
addFieldIfValue('date_de_naissances', convertToISODate(row.date_naissance));
|
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('num_inscription', row.num_inscription?.toString());
|
||||||
addFieldIfValue('sexe', row.sexe);
|
addFieldIfValue('sexe', row.sexe);
|
||||||
addFieldIfValue('date_delivrance', convertToISODate(row.date_de_delivrance));
|
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' };
|
if (fields.length === 0) return { success: false, error: 'Aucun champ valide à mettre à jour' };
|
||||||
|
|
||||||
let sql, whereParams;
|
let sql, whereParams;
|
||||||
|
|
||||||
if (row.cin && row.cin.toString().trim() !== '') {
|
if (row.cin && row.cin.toString().trim() !== '') {
|
||||||
sql = `UPDATE etudiants SET ${fields.join(', ')} WHERE cin = ?`;
|
sql = `UPDATE etudiants SET ${fields.join(', ')} WHERE cin = ?`;
|
||||||
whereParams = [row.cin];
|
whereParams = [row.cin];
|
||||||
@ -109,49 +106,44 @@ async function updateEtudiant(row) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const [result] = await pool.query(sql, [...params, ...whereParams]);
|
const [result] = await conn.query(sql, [...params, ...whereParams]);
|
||||||
return { success: true, affectedRows: result.affectedRows };
|
|
||||||
} catch (error) {
|
|
||||||
return { success: false, error: error.message };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------- INSERT multiple étudiants ----------
|
// Update or create inscription for this year
|
||||||
async function insertMultipleEtudiants(etudiants) {
|
if (result.affectedRows > 0) {
|
||||||
if (!etudiants || etudiants.length === 0) return { success: true, 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 = `
|
if (etudiantId && row.annee_scolaire_id) {
|
||||||
INSERT INTO etudiants (
|
// Check if inscription exists for this year
|
||||||
nom, prenom, photos, date_de_naissances, niveau, annee_scolaire, status,
|
const [existing] = await conn.query(
|
||||||
mention_id, num_inscription, sexe, cin, date_delivrance, nationalite,
|
'SELECT id FROM inscriptions WHERE etudiant_id = ? AND annee_scolaire_id = ?',
|
||||||
annee_bacc, serie, boursier, domaine, contact, parcours
|
[etudiantId, row.annee_scolaire_id]
|
||||||
) VALUES ?
|
);
|
||||||
`;
|
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 };
|
return { success: true, affectedRows: result.affectedRows };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return { success: false, error: error.message };
|
return { success: false, error: error.message };
|
||||||
@ -188,53 +180,115 @@ async function importFileToDatabase(filePath) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const [mentionRows] = await pool.query('SELECT * FROM mentions');
|
const conn = await pool.getConnection();
|
||||||
const [statusRows] = await pool.query('SELECT * FROM status');
|
try {
|
||||||
|
await conn.beginTransaction();
|
||||||
|
|
||||||
const etudiantsToInsert = [];
|
const [mentionRows] = await conn.query('SELECT * FROM mentions');
|
||||||
const doublons = [];
|
const [statusRows] = await conn.query('SELECT * FROM status');
|
||||||
|
|
||||||
for (const row of records) {
|
const etudiantsToInsert = [];
|
||||||
row.date_naissance = convertToISODate(row.date_naissance);
|
const doublons = [];
|
||||||
|
|
||||||
// Mapping mention
|
for (const row of records) {
|
||||||
const matchedMention = mentionRows.find(
|
row.date_naissance = convertToISODate(row.date_naissance);
|
||||||
m => m.nom.toUpperCase() === row.mention.toUpperCase() || m.uniter.toUpperCase() === row.mention.toUpperCase()
|
|
||||||
);
|
|
||||||
if (matchedMention) row.mention = matchedMention.id;
|
|
||||||
|
|
||||||
// Mapping status
|
// Mapping mention
|
||||||
row.code_redoublement = (row.code_redoublement ? row.code_redoublement.trim().substring(0, 1) : 'N');
|
const matchedMention = mentionRows.find(
|
||||||
const statusMatch = statusRows.find(s => s.nom.toLowerCase().startsWith(row.code_redoublement.toLowerCase()));
|
m => m.nom.toUpperCase() === row.mention.toUpperCase() || m.uniter.toUpperCase() === row.mention.toUpperCase()
|
||||||
if (statusMatch) row.code_redoublement = statusMatch.id;
|
);
|
||||||
|
if (matchedMention) row.mention = matchedMention.id;
|
||||||
|
|
||||||
// Détection doublons (ignorer CIN vide)
|
// Mapping status
|
||||||
let existing;
|
const codeLettre = (row.code_redoublement ? row.code_redoublement.trim().substring(0, 1) : 'N');
|
||||||
if (row.cin && row.cin.toString().trim() !== '') {
|
row.code_redoublement = codeLettre;
|
||||||
[existing] = await pool.query('SELECT * FROM etudiants WHERE cin = ?', [row.cin]);
|
const statusMatch = statusRows.find(s => s.nom.toLowerCase().startsWith(row.code_redoublement.toLowerCase()));
|
||||||
} else {
|
if (statusMatch) row.code_redoublement = statusMatch.id;
|
||||||
[existing] = await pool.query(
|
|
||||||
'SELECT * FROM etudiants WHERE LOWER(TRIM(nom)) = ? AND LOWER(TRIM(prenom)) = ?',
|
// Auto-progression du niveau selon le statut
|
||||||
[row.nom.toLowerCase().trim(), row.prenom.toLowerCase().trim()]
|
// 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) {
|
await conn.commit();
|
||||||
doublons.push({ nom: row.nom, prenom: row.prenom, cin: row.cin });
|
return {
|
||||||
const updateResult = await updateEtudiant(row);
|
error: false,
|
||||||
if (!updateResult.success) return { error: true, message: `Erreur update ${row.nom} ${row.prenom}: ${updateResult.error}` };
|
message: `Importation réussie. ${etudiantsToInsert.length} nouvel(le)(s) inséré(s), ${doublons.length} mis à jour.`
|
||||||
} else {
|
};
|
||||||
etudiantsToInsert.push(row);
|
} 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 };
|
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,
|
changePDP,
|
||||||
deleteEtudiant,
|
deleteEtudiant,
|
||||||
updateParcours,
|
updateParcours,
|
||||||
createTranche,
|
payerTranche,
|
||||||
getTranche,
|
getTranche,
|
||||||
updateTranche,
|
updateTranche,
|
||||||
deleteTranche,
|
deleteTranche,
|
||||||
getSingleTranche
|
getEtudiantsWithPaidTranche,
|
||||||
|
reinscribeEtudiant
|
||||||
} = require('../../database/Models/Etudiants')
|
} = require('../../database/Models/Etudiants')
|
||||||
const {
|
const {
|
||||||
insertNiveau,
|
insertNiveau,
|
||||||
@ -100,8 +101,14 @@ const {
|
|||||||
// Declare mainWindow and tray in the global scope
|
// Declare mainWindow and tray in the global scope
|
||||||
let mainWindow
|
let mainWindow
|
||||||
let tray = null
|
let tray = null
|
||||||
updateCurrentYears()
|
;(async () => {
|
||||||
updateStudents()
|
try {
|
||||||
|
await updateCurrentYears()
|
||||||
|
await updateStudents()
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Startup system error:', error)
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
|
||||||
autoUpdater.setFeedURL({
|
autoUpdater.setFeedURL({
|
||||||
provider: 'generic',
|
provider: 'generic',
|
||||||
@ -368,6 +375,31 @@ ipcMain.handle('insertEtudiant', async (event, credentials) => {
|
|||||||
return insert
|
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
|
// event for fetching single
|
||||||
ipcMain.handle('getByNiveau', async (event, credentials) => {
|
ipcMain.handle('getByNiveau', async (event, credentials) => {
|
||||||
const { niveau } = credentials
|
const { niveau } = credentials
|
||||||
@ -472,18 +504,18 @@ ipcMain.handle('insertNote', async (event, credentials) => {
|
|||||||
|
|
||||||
// event for get single note
|
// event for get single note
|
||||||
ipcMain.handle('getSingleNote', async (event, credentials) => {
|
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
|
return get
|
||||||
})
|
})
|
||||||
|
|
||||||
// event for get single note repech
|
// event for get single note repech
|
||||||
ipcMain.handle('getNotesRepech', async (event, credentials) => {
|
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
|
return get
|
||||||
})
|
})
|
||||||
@ -499,9 +531,9 @@ ipcMain.handle('updatetNote', async (event, credentials) => {
|
|||||||
|
|
||||||
// event for updating note repech
|
// event for updating note repech
|
||||||
ipcMain.handle('updatetNoteRepech', async (event, credentials) => {
|
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
|
return update
|
||||||
})
|
})
|
||||||
@ -846,58 +878,61 @@ ipcMain.handle('changeParcours', async (event, credentials) => {
|
|||||||
return get
|
return get
|
||||||
})
|
})
|
||||||
|
|
||||||
ipcMain.handle('createTranche', async (event, credentials) => {
|
ipcMain.handle('payerTranche', async (event, credentials) => {
|
||||||
const { etudiant_id, tranchename, montant } = credentials
|
const { etudiant_id, annee_scolaire_id, type, montant, num_bordereau } = credentials
|
||||||
// console.log(formData, id);
|
return await payerTranche(etudiant_id, annee_scolaire_id, type, montant, num_bordereau)
|
||||||
const get = createTranche(etudiant_id, tranchename, montant)
|
|
||||||
|
|
||||||
return get
|
|
||||||
})
|
})
|
||||||
|
|
||||||
ipcMain.handle('getTranche', async (event, credentials) => {
|
ipcMain.handle('getTranche', async (event, credentials) => {
|
||||||
const { id } = credentials
|
const { id } = credentials
|
||||||
// console.log(formData, id);
|
return await getTranche(id)
|
||||||
const get = getTranche(id)
|
|
||||||
|
|
||||||
return get
|
|
||||||
})
|
})
|
||||||
|
|
||||||
ipcMain.handle('updateTranche', async (event, credentials) => {
|
ipcMain.handle('updateTranche', async (event, credentials) => {
|
||||||
const { id, tranchename, montant } = credentials
|
const { id, tranche1_montant, tranche1_bordereau, tranche2_montant, tranche2_bordereau } = credentials
|
||||||
// console.log(formData, id);
|
return await updateTranche(id, tranche1_montant, tranche1_bordereau, tranche2_montant, tranche2_bordereau)
|
||||||
const get = updateTranche(id, tranchename, montant)
|
|
||||||
|
|
||||||
return get
|
|
||||||
})
|
})
|
||||||
|
|
||||||
ipcMain.handle('deleteTranche', async (event, credentials) => {
|
ipcMain.handle('deleteTranche', async (event, credentials) => {
|
||||||
const { id } = credentials
|
const { id } = credentials
|
||||||
console.log(id)
|
return await deleteTranche(id)
|
||||||
const get = deleteTranche(id)
|
|
||||||
|
|
||||||
return get
|
|
||||||
})
|
})
|
||||||
|
|
||||||
ipcMain.handle('getSingleTranche', async (event, credentials) => {
|
ipcMain.handle('getEtudiantsWithPaidTranche', async (event, credentials) => {
|
||||||
const { id } = credentials
|
const { annee_scolaire } = credentials
|
||||||
// console.log(formData, id);
|
return await getEtudiantsWithPaidTranche(annee_scolaire)
|
||||||
const get = getSingleTranche(id)
|
})
|
||||||
|
|
||||||
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) => {
|
ipcMain.handle('createIPConfig', async (event, credentials) => {
|
||||||
const { ipname } = credentials
|
const { ipname } = credentials
|
||||||
// console.log(formData, id);
|
|
||||||
const get = createConfigIp(ipname)
|
const get = createConfigIp(ipname)
|
||||||
|
|
||||||
return get
|
return get
|
||||||
})
|
})
|
||||||
|
|
||||||
ipcMain.handle('updateIPConfig', async (event, credentials) => {
|
ipcMain.handle('updateIPConfig', async (event, credentials) => {
|
||||||
const { id, ipname } = credentials
|
const { id, ipname } = credentials
|
||||||
// console.log(formData, id);
|
|
||||||
const get = updateIPConfig(id, ipname)
|
const get = updateIPConfig(id, ipname)
|
||||||
|
|
||||||
return get
|
return get
|
||||||
})
|
})
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import { contextBridge, ipcRenderer } from 'electron'
|
|||||||
import { electronAPI } from '@electron-toolkit/preload'
|
import { electronAPI } from '@electron-toolkit/preload'
|
||||||
const { getNessesarytable } = require('../../database/function/System')
|
const { getNessesarytable } = require('../../database/function/System')
|
||||||
const { getAllUsers } = require('../../database/Models/Users')
|
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 { verifyEtudiantIfHeHasNotes, blockShowMoyene } = require('../../database/Models/Notes')
|
||||||
const { getMatiere, getSemestre, getEnseignants } = require('../../database/Models/Matieres')
|
const { getMatiere, getSemestre, getEnseignants } = require('../../database/Models/Matieres')
|
||||||
const { getSysteme } = require('../../database/Models/NoteSysrem')
|
const { getSysteme } = require('../../database/Models/NoteSysrem')
|
||||||
@ -71,6 +71,7 @@ if (process.contextIsolated) {
|
|||||||
contextBridge.exposeInMainWorld('etudiants', {
|
contextBridge.exposeInMainWorld('etudiants', {
|
||||||
insertEtudiant: (credentials) => ipcRenderer.invoke('insertEtudiant', credentials),
|
insertEtudiant: (credentials) => ipcRenderer.invoke('insertEtudiant', credentials),
|
||||||
getEtudiants: () => getAllEtudiants(),
|
getEtudiants: () => getAllEtudiants(),
|
||||||
|
getEtudiantsByAnnee: (annee) => getAllEtudiantsByAnnee(annee),
|
||||||
FilterDataByNiveau: (credential) => ipcRenderer.invoke('getByNiveau', credential),
|
FilterDataByNiveau: (credential) => ipcRenderer.invoke('getByNiveau', credential),
|
||||||
getSingle: (credential) => ipcRenderer.invoke('single', credential),
|
getSingle: (credential) => ipcRenderer.invoke('single', credential),
|
||||||
updateEtudiants: (credentials) => ipcRenderer.invoke('updateETudiants', credentials),
|
updateEtudiants: (credentials) => ipcRenderer.invoke('updateETudiants', credentials),
|
||||||
@ -78,12 +79,13 @@ if (process.contextIsolated) {
|
|||||||
updateEtudiantsPDP: (credentials) => ipcRenderer.invoke('updateETudiantsPDP', credentials),
|
updateEtudiantsPDP: (credentials) => ipcRenderer.invoke('updateETudiantsPDP', credentials),
|
||||||
importExcel: (credentials) => ipcRenderer.invoke('importexcel', credentials),
|
importExcel: (credentials) => ipcRenderer.invoke('importexcel', credentials),
|
||||||
changeParcours: (credentials) => ipcRenderer.invoke('changeParcours', 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),
|
getTranche: (credentials) => ipcRenderer.invoke('getTranche', credentials),
|
||||||
updateTranche: (credentials) => ipcRenderer.invoke('updateTranche', credentials),
|
updateTranche: (credentials) => ipcRenderer.invoke('updateTranche', credentials),
|
||||||
deleteTranche: (credentials) => ipcRenderer.invoke('deleteTranche', credentials),
|
deleteTranche: (credentials) => ipcRenderer.invoke('deleteTranche', credentials),
|
||||||
deleteEtudiant: (id) => ipcRenderer.invoke('deleteEtudiant', id),
|
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)
|
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
|
* contextbridge for status
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -39,6 +39,7 @@ import Parcours from '../components/Parcours'
|
|||||||
import ModalExportFichr from '../components/ModalExportFichr'
|
import ModalExportFichr from '../components/ModalExportFichr'
|
||||||
import Resultat from '../components/Resultat'
|
import Resultat from '../components/Resultat'
|
||||||
import TrancheEcolage from '../components/TrancheEcolage'
|
import TrancheEcolage from '../components/TrancheEcolage'
|
||||||
|
import ConfigEcolage from '../components/ConfigEcolage'
|
||||||
|
|
||||||
// Use createHashRouter instead of createBrowserRouter because the desktop app is in local machine
|
// Use createHashRouter instead of createBrowserRouter because the desktop app is in local machine
|
||||||
const Router = createHashRouter([
|
const Router = createHashRouter([
|
||||||
@ -178,6 +179,10 @@ const Router = createHashRouter([
|
|||||||
path: '/resultat/:niveau/:scolaire',
|
path: '/resultat/:niveau/:scolaire',
|
||||||
element: <Resultat />
|
element: <Resultat />
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/configecolage',
|
||||||
|
element: <ConfigEcolage />
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/tranche/:id',
|
path: '/tranche/:id',
|
||||||
element: <TrancheEcolage />
|
element: <TrancheEcolage />
|
||||||
|
|||||||
@ -139,7 +139,7 @@ const previousFilter = location.state?.selectedNiveau
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleClose2 = () => {
|
const handleClose2 = () => {
|
||||||
navigate('/student', { state: { selectedNiveau: previousFilter } })
|
navigate('/student', { state: { selectedNiveau: previousFilter, selectedAnnee: localStorage.getItem('selectedAnnee') || '' } })
|
||||||
setOpen(false)
|
setOpen(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -45,37 +45,31 @@ import { MdChangeCircle, MdGrade, MdRule } from 'react-icons/md'
|
|||||||
import ModalRecepice from './ModalRecepice'
|
import ModalRecepice from './ModalRecepice'
|
||||||
import { FaLeftLong, FaRightLong } from 'react-icons/fa6'
|
import { FaLeftLong, FaRightLong } from 'react-icons/fa6'
|
||||||
import { Tooltip } from 'react-tooltip'
|
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 AddStudent = () => {
|
||||||
|
const navigate = useNavigate()
|
||||||
|
|
||||||
const [niveaus, setNiveau] = useState([])
|
const [niveaus, setNiveau] = useState([])
|
||||||
const [status, setStatus] = useState([])
|
const [status, setStatus] = useState([])
|
||||||
const [scolaire, setScolaire] = useState([])
|
const [scolaire, setScolaire] = useState([])
|
||||||
const [mention, setMention] = useState([])
|
const [mention, setMention] = useState([])
|
||||||
const [parcours, setParcours] = useState([])
|
const [parcours, setParcours] = useState([])
|
||||||
|
const [isExistingStudent, setIsExistingStudent] = useState(false)
|
||||||
|
|
||||||
useEffect(() => {
|
// Recherche etudiant existant
|
||||||
window.niveaus.getNiveau().then((response) => {
|
const [searchQuery, setSearchQuery] = useState('')
|
||||||
setNiveau(response)
|
const [searchResults, setSearchResults] = useState([])
|
||||||
})
|
const [allStudents, setAllStudents] = useState([])
|
||||||
|
|
||||||
window.statuss.getStatus().then((response) => {
|
// Paiement
|
||||||
setStatus(response)
|
const [paiementType, setPaiementType] = useState('')
|
||||||
})
|
const [numBordereau, setNumBordereau] = useState('')
|
||||||
|
const [montantPaye, setMontantPaye] = useState('')
|
||||||
window.anneescolaire.getAnneeScolaire().then((response) => {
|
const [montantConfig, setMontantConfig] = useState(null)
|
||||||
setScolaire(response)
|
|
||||||
})
|
|
||||||
|
|
||||||
window.mention.getMention().then((response) => {
|
|
||||||
setMention(response)
|
|
||||||
})
|
|
||||||
|
|
||||||
window.notesysteme.getParcours().then((response) => {
|
|
||||||
setParcours(response)
|
|
||||||
})
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const navigate = useNavigate()
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* hook for storing data in the input
|
* hook for storing data in the input
|
||||||
@ -102,6 +96,105 @@ const AddStudent = () => {
|
|||||||
parcours: ''
|
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({})
|
const [dataToSend, setDataToSend] = useState({})
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -181,21 +274,63 @@ const AddStudent = () => {
|
|||||||
|
|
||||||
const handleSubmit = async (e) => {
|
const handleSubmit = async (e) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
// Handle form submission logic
|
let etudiantId = null
|
||||||
const response = await window.etudiants.insertEtudiant(formData)
|
|
||||||
|
|
||||||
console.log(response)
|
if (isExistingStudent && formData.existingId) {
|
||||||
if (!response.success) {
|
// Etudiant existant : réinscription pour la nouvelle année scolaire
|
||||||
setCode(422)
|
const reinscriptionData = {
|
||||||
setOpen(true)
|
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) {
|
// Enregistrer le paiement si un type est selectionne
|
||||||
imageVisual.current.style.display = 'none'
|
if (paiementType && numBordereau && montantPaye && etudiantId) {
|
||||||
imageVisual.current.src = ''
|
const annee = scolaire.find((s) => s.code === formData.annee_scolaire)
|
||||||
setCode(200)
|
if (annee) {
|
||||||
setOpen(true)
|
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')({
|
const VisuallyHiddenInput = styled('input')({
|
||||||
@ -344,11 +479,46 @@ const AddStudent = () => {
|
|||||||
p: 4
|
p: 4
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div style={{ textAlign: 'right', fontWeight: 'bold' }}>
|
<div style={{ textAlign: 'right', fontWeight: 'bold', marginBottom: '10px' }}>
|
||||||
<Link to={'/exportetudiant'} style={{ textDecoration: 'none', color: 'orange' }}>
|
<Link to={'/exportetudiant'} style={{ textDecoration: 'none', color: 'orange' }}>
|
||||||
Importer un fichier excel <FaFileExcel />
|
Importer un fichier excel <FaFileExcel />
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</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
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
marginTop: '2%',
|
marginTop: '2%',
|
||||||
@ -1034,6 +1204,95 @@ const AddStudent = () => {
|
|||||||
</Grid>
|
</Grid>
|
||||||
</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 */}
|
{/* Submit Button */}
|
||||||
<Grid
|
<Grid
|
||||||
item
|
item
|
||||||
@ -1045,7 +1304,7 @@ const AddStudent = () => {
|
|||||||
<FaLeftLong className="pageprecedent" style={{ outline: 'none' }} />
|
<FaLeftLong className="pageprecedent" style={{ outline: 'none' }} />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<Tooltip anchorSelect=".pageprecedent" className="custom-tooltip" place="top">
|
<Tooltip anchorSelect=".pageprecedent" className="custom-tooltip" place="top">
|
||||||
Page précédents
|
Page precedents
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<IconButton color="warning" onClick={seePage2}>
|
<IconButton color="warning" onClick={seePage2}>
|
||||||
<FaRightLong className="pagesuivant" style={{ outline: 'none' }} />
|
<FaRightLong className="pagesuivant" style={{ outline: 'none' }} />
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import React, { useState } from 'react'
|
import React, { useState, useEffect } from 'react'
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogActions,
|
DialogActions,
|
||||||
@ -6,20 +6,39 @@ import {
|
|||||||
DialogTitle,
|
DialogTitle,
|
||||||
TextField,
|
TextField,
|
||||||
Button,
|
Button,
|
||||||
Autocomplete,
|
|
||||||
InputAdornment,
|
InputAdornment,
|
||||||
Box,
|
Box,
|
||||||
Grid
|
Grid,
|
||||||
|
FormControl,
|
||||||
|
InputLabel,
|
||||||
|
Select,
|
||||||
|
MenuItem
|
||||||
} from '@mui/material'
|
} from '@mui/material'
|
||||||
import { MdLabelImportantOutline } from 'react-icons/md'
|
|
||||||
|
|
||||||
const AjoutTranche = ({ open, onClose, onSubmitSuccess, id }) => {
|
const AjoutTranche = ({ open, onClose, onSubmitSuccess, id }) => {
|
||||||
|
const [anneesList, setAnneesList] = useState([])
|
||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
etudiant_id: id,
|
etudiant_id: id,
|
||||||
tranchename: '',
|
annee_scolaire_id: '',
|
||||||
montant: ''
|
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 handleChange = (e) => {
|
||||||
const { name, value } = e.target
|
const { name, value } = e.target
|
||||||
setFormData({ ...formData, [name]: value })
|
setFormData({ ...formData, [name]: value })
|
||||||
@ -27,69 +46,87 @@ const AjoutTranche = ({ open, onClose, onSubmitSuccess, id }) => {
|
|||||||
|
|
||||||
const handleSubmit = async (e) => {
|
const handleSubmit = async (e) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
let response = await window.etudiants.createTranche(formData)
|
const response = await window.etudiants.payerTranche(formData)
|
||||||
|
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
onClose()
|
onClose()
|
||||||
onSubmitSuccess(true)
|
onSubmitSuccess(true)
|
||||||
setFormData({
|
setFormData({
|
||||||
etudiant_id: id,
|
etudiant_id: id,
|
||||||
tranchename: '',
|
annee_scolaire_id: formData.annee_scolaire_id,
|
||||||
montant: ''
|
type: '',
|
||||||
|
montant: '',
|
||||||
|
num_bordereau: ''
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onClose={onClose}>
|
<Dialog open={open} onClose={onClose} maxWidth="sm" fullWidth>
|
||||||
<form action="" onSubmit={handleSubmit}>
|
<form onSubmit={handleSubmit}>
|
||||||
<DialogTitle>Ajout tranche</DialogTitle>
|
<DialogTitle>Enregistrer un paiement</DialogTitle>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<Box sx={{ flexGrow: 1 }}>
|
<Box sx={{ flexGrow: 1, mt: 1 }}>
|
||||||
<Grid container spacing={2}>
|
<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}>
|
<Grid item xs={12} sm={6}>
|
||||||
<TextField
|
<TextField
|
||||||
autoFocus
|
|
||||||
margin="normal"
|
|
||||||
required
|
required
|
||||||
name="tranchename"
|
name="num_bordereau"
|
||||||
label="Désignation"
|
label="N° Bordereau"
|
||||||
type="text"
|
type="text"
|
||||||
fullWidth
|
fullWidth
|
||||||
placeholder="Tranche 1"
|
size="small"
|
||||||
|
placeholder="Ex: BRD-2026-001"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
value={formData.tranchename}
|
value={formData.num_bordereau}
|
||||||
color="warning"
|
color="warning"
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
InputProps={{
|
|
||||||
startAdornment: (
|
|
||||||
<InputAdornment position="start">
|
|
||||||
<MdLabelImportantOutline />
|
|
||||||
</InputAdornment>
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={12} sm={6}>
|
<Grid item xs={12} sm={6}>
|
||||||
<TextField
|
<TextField
|
||||||
autoFocus
|
|
||||||
margin="normal"
|
|
||||||
required
|
required
|
||||||
name="montant"
|
name="montant"
|
||||||
label="Montant"
|
label="Montant"
|
||||||
type="number"
|
type="number"
|
||||||
fullWidth
|
fullWidth
|
||||||
placeholder="Montant"
|
size="small"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
value={formData.montant}
|
value={formData.montant}
|
||||||
color="warning"
|
color="warning"
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
InputProps={{
|
InputProps={{
|
||||||
startAdornment: (
|
startAdornment: <InputAdornment position="start">Ar</InputAdornment>
|
||||||
<InputAdornment position="start">
|
|
||||||
<MdLabelImportantOutline />
|
|
||||||
</InputAdornment>
|
|
||||||
)
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
@ -97,12 +134,8 @@ const AjoutTranche = ({ open, onClose, onSubmitSuccess, id }) => {
|
|||||||
</Box>
|
</Box>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={onClose} color="error">
|
<Button onClick={onClose} color="error">Annuler</Button>
|
||||||
Annuler
|
<Button type="submit" color="warning" variant="contained">Enregistrer</Button>
|
||||||
</Button>
|
|
||||||
<Button type="submit" color="warning">
|
|
||||||
Soumettre
|
|
||||||
</Button>
|
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</form>
|
</form>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|||||||
@ -142,13 +142,12 @@ const AnneeScolaire = () => {
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
const setCurrent = async (id) => {
|
const setCurrent = async (id) => {
|
||||||
// let response = await window.anneescolaire.setCurrent({id});
|
const response = await window.anneescolaire.setCurrent({ id })
|
||||||
// console.log(response);
|
if (response.success) {
|
||||||
// if (response.changes) {
|
window.anneescolaire.getAnneeScolaire().then((response) => {
|
||||||
// window.anneescolaire.getAnneeScolaire().then((response) => {
|
setAnneeScolaire(response)
|
||||||
// setAnneeScolaire(response);
|
})
|
||||||
// });
|
}
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const deleteButton = async (id) => {
|
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()
|
const currentYear = dayjs().year()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Fetch data and update state
|
window.niveaus.getNiveau().then((response) => {
|
||||||
window.etudiants.getDataToDashboards().then((response) => {
|
setNiveau(response || [])
|
||||||
setEtudiants(response.etudiants)
|
})
|
||||||
setOriginalEtudiants(response.etudiants)
|
window.anneescolaire.getAnneeScolaire().then((response) => {
|
||||||
setNiveau(response.niveau)
|
setAnnee_scolaire(response || [])
|
||||||
setAnnee_scolaire(response.anne_scolaire)
|
})
|
||||||
|
window.etudiants.getEtudiants().then((response) => {
|
||||||
|
setEtudiants(response || [])
|
||||||
|
setOriginalEtudiants(response || [])
|
||||||
})
|
})
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
@ -60,14 +63,13 @@ const Home = () => {
|
|||||||
// Find the maximum value using Math.max
|
// Find the maximum value using Math.max
|
||||||
const maxStudentCount = Math.max(...studentCounts)
|
const maxStudentCount = Math.max(...studentCounts)
|
||||||
|
|
||||||
const FilterAnneeScolaire = (e) => {
|
const FilterAnneeScolaire = async (e) => {
|
||||||
let annee_scolaire = e.target.value
|
const annee = e.target.value
|
||||||
const filteredEtudiants = originalEtudiants.filter(
|
if (annee === 'general') {
|
||||||
(etudiant) => etudiant.annee_scolaire === annee_scolaire
|
|
||||||
)
|
|
||||||
setEtudiants(filteredEtudiants)
|
|
||||||
if (annee_scolaire == 'general') {
|
|
||||||
setEtudiants(originalEtudiants)
|
setEtudiants(originalEtudiants)
|
||||||
|
} else {
|
||||||
|
const filtered = await window.etudiants.getEtudiantsByAnnee(annee)
|
||||||
|
setEtudiants(filtered || [])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// end filter all data
|
// end filter all data
|
||||||
@ -173,9 +175,9 @@ const Home = () => {
|
|||||||
<MenuItem value="general" selected>
|
<MenuItem value="general" selected>
|
||||||
<em>Géneral</em>
|
<em>Géneral</em>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
{annee_scolaire.map((niveau) => (
|
{annee_scolaire.map((a) => (
|
||||||
<MenuItem value={niveau.annee_scolaire} key={niveau.annee_scolaire}>
|
<MenuItem value={a.code} key={a.id}>
|
||||||
{niveau.annee_scolaire}
|
{a.code}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
))}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
|
|||||||
@ -12,153 +12,168 @@ import { Button, Modal, Box, Menu, MenuItem } from '@mui/material'
|
|||||||
import { Tooltip } from 'react-tooltip'
|
import { Tooltip } from 'react-tooltip'
|
||||||
import ReleverNotes from './ReleverNotes'
|
import ReleverNotes from './ReleverNotes'
|
||||||
import { FaDownload } from 'react-icons/fa'
|
import { FaDownload } from 'react-icons/fa'
|
||||||
|
import getSemestre from './function/GetSemestre'
|
||||||
|
|
||||||
const Noteclasse = () => {
|
const Noteclasse = () => {
|
||||||
const { niveau, scolaire } = useParams()
|
const { niveau, scolaire } = useParams()
|
||||||
|
|
||||||
const [etudiants, setEtudiants] = useState([])
|
const [etudiants, setEtudiants] = useState([])
|
||||||
const [mention, setMention] = useState([])
|
const [mention, setMention] = useState([])
|
||||||
const [session, setSession] = useState([])
|
const [moyennesCalculees, setMoyennesCalculees] = useState([])
|
||||||
|
|
||||||
const formData = {
|
const formData = { niveau, scolaire }
|
||||||
niveau,
|
|
||||||
scolaire
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
window.notes.getMoyenne(formData).then((response) => {
|
const fetchData = async () => {
|
||||||
setEtudiants(response)
|
try {
|
||||||
})
|
// Récupérer la liste des étudiants
|
||||||
window.noteRepech.getMoyenneRepech(formData).then((response) => {
|
const etudiantsData = await window.notes.getMoyenne(formData)
|
||||||
setSession(response)
|
setEtudiants(etudiantsData)
|
||||||
})
|
|
||||||
window.mention.getMention().then((response) => {
|
// Récupérer les mentions
|
||||||
setMention(response)
|
const mentionData = await window.mention.getMention()
|
||||||
})
|
setMention(mentionData)
|
||||||
}, [])
|
|
||||||
|
|
||||||
let dataToMap = []
|
// 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) {
|
function returnmention(id) {
|
||||||
let mentions
|
const found = mention.find((m) => m.id === id)
|
||||||
for (let index = 0; index < mention.length; index++) {
|
return found ? found.nom : ''
|
||||||
if (mention[index].id == id) {
|
|
||||||
mentions = mention[index].nom
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return mentions
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkNull(params) {
|
// ========================================
|
||||||
console.log(params);
|
// FONCTION CENTRALISÉE POUR CALCUL DE MOYENNE PONDÉRÉE
|
||||||
if (params == null || params == undefined) {
|
// ========================================
|
||||||
return null
|
const calculerMoyennePonderee = (matieres) => {
|
||||||
}
|
let totalNotesPonderees = 0
|
||||||
return params
|
let totalCredits = 0
|
||||||
}
|
|
||||||
|
|
||||||
// MODIFICATION: Nouvelle fonction pour calculer la moyenne avec rattrapage
|
matieres.forEach((matiere) => {
|
||||||
function compareSessionNotesForAverage(session1, session2) {
|
let noteFinale
|
||||||
// Si il y a une session de rattrapage, utiliser la meilleure note
|
|
||||||
if (session2) {
|
// IMPORTANT: Traiter 0 comme null (pas de rattrapage)
|
||||||
return Math.max(session1, session2.note)
|
const noteRepechValide = matiere.noterepech !== null
|
||||||
}
|
&& matiere.noterepech !== undefined
|
||||||
// Sinon utiliser la note normale
|
&& Number(matiere.noterepech) > 0
|
||||||
return session1
|
|
||||||
}
|
// TOUJOURS prendre la meilleure note si rattrapage VALIDE existe
|
||||||
|
if (noteRepechValide) {
|
||||||
function compareSessionNotes(session1, session2) {
|
noteFinale = Math.max(Number(matiere.note), Number(matiere.noterepech))
|
||||||
let notes
|
|
||||||
if (session2) {
|
|
||||||
if (session1 < session2.note) {
|
|
||||||
notes = session2.note
|
|
||||||
} else {
|
} else {
|
||||||
notes = session1
|
noteFinale = Number(matiere.note)
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
notes = session1
|
|
||||||
}
|
|
||||||
return notes
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let index = 0; index < etudiants.length; index++) {
|
if (noteFinale != null && !isNaN(noteFinale)) {
|
||||||
let total = 0
|
totalNotesPonderees += noteFinale * Number(matiere.credit)
|
||||||
let note = 0
|
totalCredits += Number(matiere.credit)
|
||||||
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
|
|
||||||
}
|
}
|
||||||
totalCredit += etudiants[index][j].credit
|
})
|
||||||
}
|
|
||||||
|
|
||||||
total = note / totalCredit
|
return totalCredits > 0 ? totalNotesPonderees / totalCredits : 0
|
||||||
modelJson.moyenne = total.toFixed(2)
|
|
||||||
|
|
||||||
// Add the new object to the array
|
|
||||||
dataToMap.push(modelJson)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkNumberSession(id) {
|
function checkNumberSession(id) {
|
||||||
let sessionNumber = 1
|
const etudiant = moyennesCalculees.find(e => e.id === id)
|
||||||
for (let index = 0; index < session.length; index++) {
|
return etudiant && etudiant.aRattrapage ? 2 : 1
|
||||||
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 theme = createTheme({
|
const theme = createTheme({
|
||||||
components: {
|
components: {
|
||||||
MuiIconButton: {
|
MuiIconButton: { styleOverrides: { root: { color: 'gray' } } },
|
||||||
styleOverrides: {
|
MuiButton: { styleOverrides: { root: { color: '#121212' } } }
|
||||||
root: {
|
|
||||||
color: 'gray' // Change the color of toolbar icons
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
MuiButton: {
|
|
||||||
styleOverrides: {
|
|
||||||
root: {
|
|
||||||
color: '#121212' // Change the color of toolbar icons
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const paginationModel = { page: 0, pageSize: 5 }
|
const paginationModel = { page: 0, pageSize: 5 }
|
||||||
|
|
||||||
// États pour le menu déroulant
|
|
||||||
const [anchorEl, setAnchorEl] = useState(null)
|
const [anchorEl, setAnchorEl] = useState(null)
|
||||||
const [selectedStudentId, setSelectedStudentId] = useState(null)
|
const [selectedStudentId, setSelectedStudentId] = useState(null)
|
||||||
const open = Boolean(anchorEl)
|
const open = Boolean(anchorEl)
|
||||||
@ -167,17 +182,92 @@ const Noteclasse = () => {
|
|||||||
setAnchorEl(event.currentTarget)
|
setAnchorEl(event.currentTarget)
|
||||||
setSelectedStudentId(studentId)
|
setSelectedStudentId(studentId)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleMenuClose = () => {
|
const handleMenuClose = () => {
|
||||||
setAnchorEl(null)
|
setAnchorEl(null)
|
||||||
setSelectedStudentId(null)
|
setSelectedStudentId(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleSessionTypeSelect = (sessionType) => {
|
const handleSessionTypeSelect = (sessionType) => {
|
||||||
sendData(selectedStudentId, sessionType)
|
sendData(selectedStudentId, sessionType)
|
||||||
handleMenuClose()
|
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 = [
|
const columns = [
|
||||||
{ field: 'nom', headerName: 'Nom', width: 170 },
|
{ field: 'nom', headerName: 'Nom', width: 170 },
|
||||||
{ field: 'prenom', headerName: 'Prénom', width: 160 },
|
{ field: 'prenom', headerName: 'Prénom', width: 160 },
|
||||||
@ -190,7 +280,7 @@ const Noteclasse = () => {
|
|||||||
width: 100,
|
width: 100,
|
||||||
renderCell: (params) => (
|
renderCell: (params) => (
|
||||||
<img
|
<img
|
||||||
src={params.value} // Correct the access to the image source
|
src={params.value}
|
||||||
alt={'image pdp'}
|
alt={'image pdp'}
|
||||||
style={{ width: 50, height: 50, borderRadius: '50%', objectFit: 'cover' }}
|
style={{ width: 50, height: 50, borderRadius: '50%', objectFit: 'cover' }}
|
||||||
/>
|
/>
|
||||||
@ -202,17 +292,17 @@ const Noteclasse = () => {
|
|||||||
flex: 1,
|
flex: 1,
|
||||||
renderCell: (params) => (
|
renderCell: (params) => (
|
||||||
<div style={{ display: 'flex', gap: '10px' }}>
|
<div style={{ display: 'flex', gap: '10px' }}>
|
||||||
<Button
|
<Button
|
||||||
color="warning"
|
color="warning"
|
||||||
variant="contained"
|
variant="contained"
|
||||||
className={`update${params.value}`}
|
className={`update${params.value}`}
|
||||||
onClick={(event) => handleMenuClick(event, params.value)}
|
onClick={(event) => handleMenuClick(event, params.value)}
|
||||||
>
|
>
|
||||||
<IoNewspaperOutline style={{ fontSize: '20px', color: 'white' }} />
|
<IoNewspaperOutline style={{ fontSize: '20px', color: 'white' }} />
|
||||||
</Button>
|
</Button>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
anchorSelect={`.update${params.value}`}
|
anchorSelect={`.update${params.value}`}
|
||||||
style={{ fontSize: '13px', zIndex: 22 }}
|
style={{ fontSize: '13px', zIndex: 22 }}
|
||||||
place="bottom-end"
|
place="bottom-end"
|
||||||
>
|
>
|
||||||
Imprimer un relevé de notes
|
Imprimer un relevé de notes
|
||||||
@ -222,7 +312,7 @@ const Noteclasse = () => {
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
const dataTable = dataToMap.map((data) => ({
|
const dataTable = moyennesCalculees.map((data) => ({
|
||||||
id: data.id,
|
id: data.id,
|
||||||
nom: data.nom,
|
nom: data.nom,
|
||||||
prenom: data.prenom,
|
prenom: data.prenom,
|
||||||
@ -233,139 +323,22 @@ const Noteclasse = () => {
|
|||||||
action: data.id
|
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 (
|
return (
|
||||||
<div className={classe.mainHome}>
|
<div className={classe.mainHome}>
|
||||||
{modalReleverNotes()}
|
{modalReleverNotes()}
|
||||||
|
|
||||||
{/* Menu pour sélectionner le type de session */}
|
<Menu anchorEl={anchorEl} open={open} onClose={handleMenuClose}>
|
||||||
<Menu
|
<MenuItem onClick={() => handleSessionTypeSelect('normale')}>Session Normale</MenuItem>
|
||||||
anchorEl={anchorEl}
|
<MenuItem onClick={() => handleSessionTypeSelect('ensemble')}>Session Rattrapage</MenuItem>
|
||||||
open={open}
|
|
||||||
onClose={handleMenuClose}
|
|
||||||
MenuListProps={{
|
|
||||||
'aria-labelledby': 'session-button',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<MenuItem onClick={() => handleSessionTypeSelect('normale')}>
|
|
||||||
Session Normale
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem onClick={() => handleSessionTypeSelect('ensemble')}>
|
|
||||||
Session Rattrapage
|
|
||||||
</MenuItem>
|
|
||||||
</Menu>
|
</Menu>
|
||||||
|
|
||||||
<div className={classeHome.header}>
|
<div className={classeHome.header}>
|
||||||
<div className={classe.h1style}>
|
<div className={classe.h1style}>
|
||||||
<div className={classeHome.blockTitle}>
|
<div className={classeHome.blockTitle}>
|
||||||
<h1>
|
<h1>Notes des {niveau} en {scolaire}</h1>
|
||||||
Notes des {niveau} en {scolaire}
|
|
||||||
</h1>
|
|
||||||
<div style={{ display: 'flex', gap: '10px' }}>
|
<div style={{ display: 'flex', gap: '10px' }}>
|
||||||
<Link to={`/resultat/${niveau}/${scolaire}`}>
|
<Link to={`/resultat/${niveau}/${scolaire}`}>
|
||||||
<Button color="warning" variant="contained">
|
<Button color="warning" variant="contained">Resultat</Button>
|
||||||
Resultat
|
|
||||||
</Button>
|
|
||||||
</Link>
|
</Link>
|
||||||
<Link to={'#'} onClick={() => window.history.back()}>
|
<Link to={'#'} onClick={() => window.history.back()}>
|
||||||
<Button color="warning" variant="contained">
|
<Button color="warning" variant="contained">
|
||||||
@ -377,50 +350,22 @@ const Noteclasse = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={classeHome.boxEtudiantsCard}>
|
<div className={classeHome.boxEtudiantsCard} style={{ maxHeight: '70vh', overflowY: 'auto' }}>
|
||||||
<div style={{ display: 'flex', justifyContent: 'center' }}>
|
<Paper sx={{ height: 'auto', width: '100%', minHeight: 500 }}>
|
||||||
<Paper
|
<ThemeProvider theme={theme}>
|
||||||
sx={{
|
<DataGrid
|
||||||
height: 'auto', // Auto height to make the grid responsive
|
rows={dataTable}
|
||||||
width: '100%',
|
columns={columns}
|
||||||
minHeight: 500, // Ensures a minimum height
|
initialState={{ pagination: { paginationModel } }}
|
||||||
display: 'flex'
|
pageSizeOptions={[5, 10, 20]}
|
||||||
}}
|
slots={{ toolbar: GridToolbar }}
|
||||||
>
|
localeText={frFR.components.MuiDataGrid.defaultProps.localeText}
|
||||||
<ThemeProvider theme={theme}>
|
autoHeight={false}
|
||||||
<div
|
sx={{ minHeight: 500 }}
|
||||||
style={{
|
loading={moyennesCalculees.length === 0}
|
||||||
display: 'flex',
|
/>
|
||||||
alignItems: 'center',
|
</ThemeProvider>
|
||||||
justifyContent: 'center',
|
</Paper>
|
||||||
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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -14,55 +14,46 @@ const ReleverNotes = ({ id, anneescolaire, niveau, sessionType = 'ensemble', ref
|
|||||||
const [etudiant, setEtudiant] = useState([])
|
const [etudiant, setEtudiant] = useState([])
|
||||||
const [matieres, setMatieres] = useState([])
|
const [matieres, setMatieres] = useState([])
|
||||||
const [notes, setNotes] = useState([])
|
const [notes, setNotes] = useState([])
|
||||||
|
const [noteSysteme, setNoteSysteme] = useState(null)
|
||||||
// Fonction pour vérifier si les crédits doivent être affichés
|
|
||||||
const shouldShowCredits = () => {
|
|
||||||
return niveau !== 'L1' && niveau !== 'L2'
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleDownloadPDF = async () => {
|
const handleDownloadPDF = async () => {
|
||||||
const input = Telever.current
|
const input = Telever.current
|
||||||
|
|
||||||
// Set a high scale for better quality
|
|
||||||
const scale = 3
|
const scale = 3
|
||||||
|
|
||||||
html2Canvas(input, {
|
html2Canvas(input, {
|
||||||
scale, // Increase resolution
|
scale,
|
||||||
useCORS: true, // Handle cross-origin images
|
useCORS: true,
|
||||||
allowTaint: true
|
allowTaint: true
|
||||||
}).then((canvas) => {
|
}).then((canvas) => {
|
||||||
const imgData = canvas.toDataURL('image/png')
|
const imgData = canvas.toDataURL('image/png')
|
||||||
|
|
||||||
// Create a PDF with dimensions matching the captured content
|
|
||||||
const pdf = new jsPDF({
|
const pdf = new jsPDF({
|
||||||
orientation: 'portrait',
|
orientation: 'portrait',
|
||||||
unit: 'mm',
|
unit: 'mm',
|
||||||
format: 'a4'
|
format: 'a4'
|
||||||
})
|
})
|
||||||
|
|
||||||
const imgWidth = 210 // A4 width in mm
|
const pageWidth = 210
|
||||||
const pageHeight = 297 // A4 height in mm
|
const pageHeight = 297
|
||||||
const imgHeight = (canvas.height * imgWidth) / canvas.width
|
const margin = 5
|
||||||
|
const printWidth = pageWidth - margin * 2
|
||||||
|
const imgHeight = (canvas.height * printWidth) / canvas.width
|
||||||
|
|
||||||
let position = 0
|
if (imgHeight > pageHeight - margin * 2) {
|
||||||
|
const ratio = (pageHeight - margin * 2) / imgHeight
|
||||||
pdf.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight, '', 'FAST')
|
const adjustedWidth = printWidth * ratio
|
||||||
|
const adjustedHeight = pageHeight - margin * 2
|
||||||
// Handle multi-page case
|
const xOffset = (pageWidth - adjustedWidth) / 2
|
||||||
while (position + imgHeight >= pageHeight) {
|
pdf.addImage(imgData, 'PNG', xOffset, margin, adjustedWidth, adjustedHeight, '', 'FAST')
|
||||||
position -= pageHeight
|
} else {
|
||||||
pdf.addPage()
|
pdf.addImage(imgData, 'PNG', margin, margin, printWidth, imgHeight, '', 'FAST')
|
||||||
pdf.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight, '', 'FAST')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pdf.save('document.pdf')
|
pdf.save('releve_de_notes.pdf')
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!id) {
|
if (!id) {
|
||||||
// id doesn't exist, you might want to retry, or do nothing
|
|
||||||
// For example, refetch later or show an error
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
window.etudiants.getSingle({ id }).then((response) => {
|
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) => {
|
window.notes.noteRelerer({ id, anneescolaire, niveau }).then((response) => {
|
||||||
setNotes(response)
|
setNotes(response)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
window.notesysteme.getSysteme().then((response) => {
|
||||||
|
if (response) setNoteSysteme(response)
|
||||||
|
})
|
||||||
}, [id])
|
}, [id])
|
||||||
|
|
||||||
const Telever = useRef()
|
const Telever = useRef()
|
||||||
@ -90,42 +85,35 @@ const ReleverNotes = ({ id, anneescolaire, niveau, sessionType = 'ensemble', ref
|
|||||||
const [matiereWithSemestreRepech, setMatiereWithSemestreRepech] = useState([])
|
const [matiereWithSemestreRepech, setMatiereWithSemestreRepech] = useState([])
|
||||||
|
|
||||||
useEffect(() => {
|
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) => {
|
const updatedMatieres = notes.noteNormal.map((matiere) => {
|
||||||
// Get the semesters based on the student's niveau
|
|
||||||
const semesters = getSemestre(matiere.etudiant_niveau)
|
const semesters = getSemestre(matiere.etudiant_niveau)
|
||||||
|
|
||||||
// Find the matched semestre based on the conditions
|
|
||||||
const matchedSemestre = notes.semestre.find(
|
const matchedSemestre = notes.semestre.find(
|
||||||
(sem) =>
|
(sem) =>
|
||||||
sem.matiere_id === matiere.matiere_id &&
|
sem.matiere_id === matiere.matiere_id &&
|
||||||
sem.mention_id === matiere.mention_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 {
|
return {
|
||||||
...matiere,
|
...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) => {
|
const updatedMatieresRepech = notes.noteRepech.map((matiere) => {
|
||||||
// Get the semesters based on the student's niveau
|
|
||||||
const semesters = getSemestre(matiere.etudiant_niveau)
|
const semesters = getSemestre(matiere.etudiant_niveau)
|
||||||
|
|
||||||
// Find the matched semestre based on the conditions
|
|
||||||
const matchedSemestre = notes.semestre.find(
|
const matchedSemestre = notes.semestre.find(
|
||||||
(sem) =>
|
(sem) =>
|
||||||
sem.matiere_id === matiere.matiere_id &&
|
sem.matiere_id === matiere.matiere_id &&
|
||||||
sem.mention_id === matiere.mention_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 {
|
return {
|
||||||
...matiere,
|
...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) {
|
function compareMention(mentionID) {
|
||||||
let statusText
|
let statusText
|
||||||
|
|
||||||
matieres.map((statu) => {
|
matieres.map((statu) => {
|
||||||
if (mentionID == statu.id) {
|
if (mentionID == statu.id) {
|
||||||
statusText = statu.nom
|
statusText = statu.nom
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
return statusText ? statusText.charAt(0).toUpperCase() + statusText.slice(1) : statusText
|
return statusText ? statusText.charAt(0).toUpperCase() + statusText.slice(1) : statusText
|
||||||
}
|
}
|
||||||
|
|
||||||
// data are finaly get and ready for the traitement below
|
// Fusion des notes normales et de rattrapage
|
||||||
|
|
||||||
// Merging the arrays based on matiere_id
|
|
||||||
matiereWithSemestre.forEach((item1) => {
|
matiereWithSemestre.forEach((item1) => {
|
||||||
// Find the corresponding item in array2 based on matiere_id
|
|
||||||
let matchingItem = matiereWithSemestreRepech.find(
|
let matchingItem = matiereWithSemestreRepech.find(
|
||||||
(item2) => item2.matiere_id === item1.matiere_id
|
(item2) => item2.matiere_id === item1.matiere_id
|
||||||
)
|
)
|
||||||
|
item1.noterepech = matchingItem ? matchingItem.note : null
|
||||||
// If there's a match, add noterepech from array2, otherwise use the note from array1
|
|
||||||
item1.noterepech = matchingItem ? matchingItem.note : item1.note
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// step 1 group all by semestre
|
|
||||||
const groupedDataBySemestre = matiereWithSemestre.reduce((acc, matiere) => {
|
const groupedDataBySemestre = matiereWithSemestre.reduce((acc, matiere) => {
|
||||||
const { semestre } = matiere
|
const { semestre } = matiere
|
||||||
|
|
||||||
if (!acc[semestre]) {
|
if (!acc[semestre]) {
|
||||||
acc[semestre] = []
|
acc[semestre] = []
|
||||||
}
|
}
|
||||||
|
|
||||||
acc[semestre].push(matiere)
|
acc[semestre].push(matiere)
|
||||||
|
|
||||||
return acc
|
return acc
|
||||||
}, {})
|
}, {})
|
||||||
|
|
||||||
// MODIFICATION: Fonction compareMoyenne mise à jour
|
// ========================================
|
||||||
const compareMoyenne = (normal, rattrapage, sessionType) => {
|
// FONCTION CENTRALISÉE POUR CALCUL DE MOYENNE PONDÉRÉE
|
||||||
if (sessionType === 'normale') {
|
// ========================================
|
||||||
// Pour session normale: toujours évaluer selon la note normale uniquement
|
const calculerMoyennePonderee = (matieres, utiliserRattrapage = true) => {
|
||||||
return Number(normal) >= 10 ? 'Admis' : 'Ajourné'
|
let totalNotesPonderees = 0
|
||||||
} else if (sessionType === 'rattrapage') {
|
let totalCredits = 0
|
||||||
// Pour session rattrapage: évaluer selon la note de rattrapage
|
|
||||||
return Number(rattrapage) >= 10 ? 'Admis' : 'Ajourné'
|
matieres.forEach((matiere) => {
|
||||||
} else {
|
let noteFinale
|
||||||
// Pour session ensemble: prendre la meilleure des deux notes
|
|
||||||
const bestNote = Math.max(Number(normal), Number(rattrapage))
|
// IMPORTANT: Traiter 0 comme null (pas de rattrapage)
|
||||||
return bestNote >= 10 ? 'Admis' : 'Ajourné'
|
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 = () => {
|
const TbodyContent = () => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{Object.entries(groupedDataBySemestre).map(([semestre, matieres]) => {
|
{Object.entries(groupedDataBySemestre).map(([semestre, matieres]) => {
|
||||||
// Group by unite_enseignement inside each semestre
|
|
||||||
const groupedByUnite = matieres.reduce((acc, matiere) => {
|
const groupedByUnite = matieres.reduce((acc, matiere) => {
|
||||||
if (!acc[matiere.ue]) {
|
if (!acc[matiere.ue]) {
|
||||||
acc[matiere.ue] = []
|
acc[matiere.ue] = []
|
||||||
@ -205,7 +216,6 @@ const ReleverNotes = ({ id, anneescolaire, niveau, sessionType = 'ensemble', ref
|
|||||||
<>
|
<>
|
||||||
{matieres.map((matiere, matiereIndex) => (
|
{matieres.map((matiere, matiereIndex) => (
|
||||||
<tr key={matiere.id} style={{ border: 'none' }}>
|
<tr key={matiere.id} style={{ border: 'none' }}>
|
||||||
{/* Display 'semestre' only for the first row of the first unite_enseignement */}
|
|
||||||
{uniteIndex === 0 && matiereIndex === 0 && (
|
{uniteIndex === 0 && matiereIndex === 0 && (
|
||||||
<td
|
<td
|
||||||
rowSpan={
|
rowSpan={
|
||||||
@ -225,7 +235,6 @@ const ReleverNotes = ({ id, anneescolaire, niveau, sessionType = 'ensemble', ref
|
|||||||
</td>
|
</td>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Display 'unite_enseignement' only for the first row of each group */}
|
|
||||||
{matiereIndex === 0 && (
|
{matiereIndex === 0 && (
|
||||||
<td
|
<td
|
||||||
rowSpan={matieres.length}
|
rowSpan={matieres.length}
|
||||||
@ -240,12 +249,11 @@ const ReleverNotes = ({ id, anneescolaire, niveau, sessionType = 'ensemble', ref
|
|||||||
</td>
|
</td>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Matiere Data */}
|
<td style={{ borderRight: 'solid 1px black', borderTop: 'solid 1px black', textAlign: 'left', paddingLeft: '4px' }}>
|
||||||
<td style={{ borderRight: 'solid 1px black', borderTop: 'solid 1px black' }}>
|
|
||||||
{matiere.nom}
|
{matiere.nom}
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
{/* Affichage conditionnel des colonnes selon le type de session */}
|
{/* SECTION NORMALE */}
|
||||||
{sessionType !== 'rattrapage' && (
|
{sessionType !== 'rattrapage' && (
|
||||||
<>
|
<>
|
||||||
<td style={{ borderRight: 'solid 1px black', borderTop: 'solid 1px black' }}>
|
<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' }}>
|
<td style={{ borderRight: 'solid 1px black', borderTop: 'solid 1px black' }}>
|
||||||
{matiere.note}
|
{matiere.note}
|
||||||
</td>
|
</td>
|
||||||
{/* Moyenne UE pour session normale */}
|
|
||||||
{matiereIndex === 0 && (
|
{matiereIndex === 0 && (
|
||||||
<td
|
<td
|
||||||
rowSpan={matieres.length}
|
rowSpan={matieres.length}
|
||||||
@ -266,24 +273,22 @@ const ReleverNotes = ({ id, anneescolaire, niveau, sessionType = 'ensemble', ref
|
|||||||
}}
|
}}
|
||||||
className="moyenneUENormale"
|
className="moyenneUENormale"
|
||||||
>
|
>
|
||||||
{(
|
{calculerMoyennePonderee(matieres, false).toFixed(2)}
|
||||||
matieres.reduce((total, matiere) => total + matiere.note, 0) /
|
|
||||||
matieres.length
|
|
||||||
).toFixed(2)}
|
|
||||||
</td>
|
</td>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* SECTION RATTRAPAGE */}
|
||||||
{sessionType !== 'normale' && (
|
{sessionType !== 'normale' && (
|
||||||
<>
|
<>
|
||||||
<td style={{ borderRight: 'solid 1px black', borderTop: 'solid 1px black' }}>
|
<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>
|
||||||
<td style={{ borderRight: 'solid 1px black', borderTop: 'solid 1px black' }}>
|
|
||||||
{matiere.noterepech}
|
|
||||||
</td>
|
|
||||||
{/* Moyenne UE pour session rattrapage */}
|
|
||||||
{matiereIndex === 0 && (
|
{matiereIndex === 0 && (
|
||||||
<td
|
<td
|
||||||
rowSpan={matieres.length}
|
rowSpan={matieres.length}
|
||||||
@ -295,16 +300,13 @@ const ReleverNotes = ({ id, anneescolaire, niveau, sessionType = 'ensemble', ref
|
|||||||
}}
|
}}
|
||||||
className="moyenneUERattrapage"
|
className="moyenneUERattrapage"
|
||||||
>
|
>
|
||||||
{(
|
{calculerMoyennePonderee(matieres, true).toFixed(2)}
|
||||||
matieres.reduce((total, matiere) => total + matiere.noterepech, 0) /
|
|
||||||
matieres.length
|
|
||||||
).toFixed(2)}
|
|
||||||
</td>
|
</td>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Display the comparison value only once */}
|
{/* OBSERVATION */}
|
||||||
{matiereIndex === 0 && (
|
{matiereIndex === 0 && (
|
||||||
<td
|
<td
|
||||||
rowSpan={matieres.length}
|
rowSpan={matieres.length}
|
||||||
@ -315,26 +317,14 @@ const ReleverNotes = ({ id, anneescolaire, niveau, sessionType = 'ensemble', ref
|
|||||||
borderTop: 'solid 1px black'
|
borderTop: 'solid 1px black'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{compareMoyenne(
|
{compareMoyenne(matieres, sessionType)}
|
||||||
(
|
|
||||||
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
|
|
||||||
)}
|
|
||||||
</td>
|
</td>
|
||||||
)}
|
)}
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
{/* Add Total Row for 'unite_enseignement' */}
|
{/* Total Row */}
|
||||||
<tr
|
<tr style={{ border: 'none', borderLeft: 'solid 1px black' }}>
|
||||||
style={{ border: 'none', borderLeft: 'solid 1px black' }}
|
|
||||||
>
|
|
||||||
<td
|
<td
|
||||||
colSpan={2}
|
colSpan={2}
|
||||||
style={{
|
style={{
|
||||||
@ -348,7 +338,7 @@ const ReleverNotes = ({ id, anneescolaire, niveau, sessionType = 'ensemble', ref
|
|||||||
>
|
>
|
||||||
Total de Credit
|
Total de Credit
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
{sessionType !== 'rattrapage' && (
|
{sessionType !== 'rattrapage' && (
|
||||||
<>
|
<>
|
||||||
<td
|
<td
|
||||||
@ -359,7 +349,7 @@ const ReleverNotes = ({ id, anneescolaire, niveau, sessionType = 'ensemble', ref
|
|||||||
borderTop: 'solid 1px black'
|
borderTop: 'solid 1px black'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{matieres.reduce((total, matiere) => total + matiere.credit, 0)}
|
{matieres.reduce((total, matiere) => total + Number(matiere.credit), 0)}
|
||||||
</td>
|
</td>
|
||||||
<td
|
<td
|
||||||
colSpan={2}
|
colSpan={2}
|
||||||
@ -372,7 +362,7 @@ const ReleverNotes = ({ id, anneescolaire, niveau, sessionType = 'ensemble', ref
|
|||||||
></td>
|
></td>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{sessionType !== 'normale' && (
|
{sessionType !== 'normale' && (
|
||||||
<>
|
<>
|
||||||
<td
|
<td
|
||||||
@ -383,7 +373,6 @@ const ReleverNotes = ({ id, anneescolaire, niveau, sessionType = 'ensemble', ref
|
|||||||
borderRight: 'solid 1px black'
|
borderRight: 'solid 1px black'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{matieres.reduce((total, matiere) => total + matiere.credit, 0)}
|
|
||||||
</td>
|
</td>
|
||||||
<td
|
<td
|
||||||
colSpan={2}
|
colSpan={2}
|
||||||
@ -396,7 +385,7 @@ const ReleverNotes = ({ id, anneescolaire, niveau, sessionType = 'ensemble', ref
|
|||||||
></td>
|
></td>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<td
|
<td
|
||||||
style={{
|
style={{
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
@ -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 = () => {
|
// ========================================
|
||||||
// Calculer la moyenne pondérée par les crédits selon le type de session
|
const totalNotes = () => {
|
||||||
let totalNotesPonderees = 0
|
// Session NORMALE → uniquement note normale
|
||||||
let totalCredits = 0
|
if (sessionType === 'normale') {
|
||||||
|
return calculerMoyennePonderee(matiereWithSemestre, false)
|
||||||
if (sessionType === 'normale') {
|
}
|
||||||
// Utiliser uniquement les notes normales
|
|
||||||
matiereWithSemestre.forEach((matiere) => {
|
// Session RATTRAPAGE ou ENSEMBLE → meilleure note
|
||||||
if (matiere.note != null && !isNaN(matiere.note)) {
|
return calculerMoyennePonderee(matiereWithSemestre, true)
|
||||||
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
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return totalCredits > 0 ? totalNotesPonderees / totalCredits : 0
|
|
||||||
}
|
|
||||||
|
|
||||||
const [note, setNote] = useState(0)
|
const [note, setNote] = useState(0)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setNote(totalNotes())
|
const moyenne = totalNotes()
|
||||||
}, [TbodyContent, sessionType])
|
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 (
|
return (
|
||||||
<div className={classe.mainHome}>
|
<div className={classe.mainHome}>
|
||||||
@ -468,53 +485,69 @@ const totalNotes = () => {
|
|||||||
<div style={{ display: 'flex', justifyContent: 'center' }}>
|
<div style={{ display: 'flex', justifyContent: 'center' }}>
|
||||||
<Paper
|
<Paper
|
||||||
sx={{
|
sx={{
|
||||||
height: 'auto',
|
width: '210mm',
|
||||||
minHeight: 500,
|
minHeight: '297mm',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
padding: '1%',
|
padding: '10mm 12mm',
|
||||||
width: '70%',
|
|
||||||
marginTop: '2%',
|
marginTop: '2%',
|
||||||
justifyContent: 'center'
|
justifyContent: 'center',
|
||||||
|
'@media print': {
|
||||||
|
boxShadow: 'none',
|
||||||
|
margin: 0,
|
||||||
|
padding: '8mm 10mm'
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
ref={Telever}
|
ref={Telever}
|
||||||
>
|
>
|
||||||
<div style={{ width: '80%' }}>
|
<div style={{ width: '100%' }}>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'space-between',
|
justifyContent: 'space-between',
|
||||||
textAlign: 'center'
|
textAlign: 'center'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<img src={logoRelerev2} alt="logo gauche" width={90} />
|
<img src={logoRelerev2} alt="logo gauche" width={90} />
|
||||||
|
|
||||||
<div style={{ flex: 1, margin: '0 20px' }}>
|
<div style={{ flex: 1, margin: '0 20px' }}>
|
||||||
<h5 style={{ margin: 0, fontWeight: 'bold', textTransform: 'uppercase',fontSize: '16px' }}>
|
<h5
|
||||||
REPOBLIKAN'I MADAGASIKARA
|
style={{
|
||||||
</h5>
|
margin: 0,
|
||||||
<p style={{ margin: 0, fontStyle: 'italic',fontSize: '11px' }}>Fitiavana – Tanindrazana – Fandrosoana</p>
|
fontWeight: 'bold',
|
||||||
<p style={{ margin: 0, fontWeight: 'bold',fontSize: '11px' }}>
|
textTransform: 'uppercase',
|
||||||
MINISTÈRE DE L'ENSEIGNEMENT SUPÉRIEUR <br />
|
fontSize: '16px'
|
||||||
ET DE LA RECHERCHE SCIENTIFIQUE
|
}}
|
||||||
</p>
|
>
|
||||||
<p style={{ margin: 0, fontWeight: 'bold',fontSize: '16px' }}>UNIVERSITÉ DE TOAMASINA</p>
|
REPOBLIKAN'I MADAGASIKARA
|
||||||
<p style={{ margin: 0, fontWeight: 'bold',fontSize: '16px' }}>ÉCOLE SUPÉRIEURE POLYTECHNIQUE</p>
|
</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>
|
</div>
|
||||||
|
|
||||||
<img src={logoRelerev1} alt="logo droite" width={90} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<hr style={{ margin: 0, border: 'solid 1px black' }} />
|
<hr style={{ margin: 0, border: 'solid 1px black' }} />
|
||||||
<h4 style={{ textTransform: 'uppercase', textAlign: 'center', marginBottom: 0 }}>
|
<h4 style={{ textTransform: 'uppercase', textAlign: 'center', marginBottom: 0 }}>
|
||||||
Releve de notes
|
Releve de notes
|
||||||
</h4>
|
</h4>
|
||||||
<hr style={{ margin: 0, border: 'solid 1px black' }} />
|
<hr style={{ margin: 0, border: 'solid 1px black' }} />
|
||||||
|
|
||||||
{/* block info */}
|
{/* block info */}
|
||||||
<div style={{ marginTop: '2px', display: 'flex' }}>
|
<div style={{ marginTop: '2px', display: 'flex' }}>
|
||||||
{/* gauche */}
|
|
||||||
<div style={{ display: 'flex', width: '60%' }}>
|
<div style={{ display: 'flex', width: '60%' }}>
|
||||||
{/* gauche gauche */}
|
|
||||||
<div style={{ width: '40%' }}>
|
<div style={{ width: '40%' }}>
|
||||||
<span>
|
<span>
|
||||||
<b>Nom</b>
|
<b>Nom</b>
|
||||||
@ -532,7 +565,6 @@ const totalNotes = () => {
|
|||||||
<b>Codage</b>
|
<b>Codage</b>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{/* gauche droite */}
|
|
||||||
<div style={{ width: '60%' }}>
|
<div style={{ width: '60%' }}>
|
||||||
<span>: {etudiant.nom}</span>
|
<span>: {etudiant.nom}</span>
|
||||||
<br />
|
<br />
|
||||||
@ -543,9 +575,7 @@ const totalNotes = () => {
|
|||||||
<span>: {etudiant.num_inscription}</span>
|
<span>: {etudiant.num_inscription}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/* droite */}
|
|
||||||
<div style={{ display: 'flex', width: '40%' }}>
|
<div style={{ display: 'flex', width: '40%' }}>
|
||||||
{/* droite gauche */}
|
|
||||||
<div style={{ width: '30%' }}>
|
<div style={{ width: '30%' }}>
|
||||||
<span>
|
<span>
|
||||||
<b>Annee Sco</b>
|
<b>Annee Sco</b>
|
||||||
@ -559,7 +589,6 @@ const totalNotes = () => {
|
|||||||
<b>Parcours</b>
|
<b>Parcours</b>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{/* droite droite */}
|
|
||||||
<div style={{ width: '70%' }}>
|
<div style={{ width: '70%' }}>
|
||||||
<span>: {etudiant.annee_scolaire}</span>
|
<span>: {etudiant.annee_scolaire}</span>
|
||||||
<br />
|
<br />
|
||||||
@ -571,7 +600,7 @@ const totalNotes = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* table */}
|
{/* table */}
|
||||||
<table style={{ marginTop: '5px', borderCollapse: 'collapse' }}>
|
<table style={{ marginTop: '5px', borderCollapse: 'collapse', width: '100%', fontSize: '14px' }}>
|
||||||
<thead>
|
<thead>
|
||||||
<tr style={{ borderTop: 'solid 1px black', textAlign: 'center' }}>
|
<tr style={{ borderTop: 'solid 1px black', textAlign: 'center' }}>
|
||||||
<th colSpan={3}></th>
|
<th colSpan={3}></th>
|
||||||
@ -590,25 +619,19 @@ const totalNotes = () => {
|
|||||||
<th style={{ borderLeft: 'solid 1px black' }}></th>
|
<th style={{ borderLeft: 'solid 1px black' }}></th>
|
||||||
<th style={{ borderLeft: 'solid 1px black' }}></th>
|
<th style={{ borderLeft: 'solid 1px black' }}></th>
|
||||||
<th style={{ borderLeft: 'solid 1px black' }}></th>
|
<th style={{ borderLeft: 'solid 1px black' }}></th>
|
||||||
|
|
||||||
{sessionType !== 'rattrapage' && (
|
{sessionType !== 'rattrapage' && (
|
||||||
<th
|
<th colSpan={3} style={{ borderLeft: 'solid 1px black' }}>
|
||||||
colSpan={3}
|
|
||||||
style={{ borderLeft: 'solid 1px black' }}
|
|
||||||
>
|
|
||||||
Normale
|
Normale
|
||||||
</th>
|
</th>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{sessionType !== 'normale' && (
|
{sessionType !== 'normale' && (
|
||||||
<th
|
<th colSpan={3} style={{ borderLeft: 'solid 1px black' }}>
|
||||||
colSpan={3}
|
|
||||||
style={{ borderLeft: 'solid 1px black' }}
|
|
||||||
>
|
|
||||||
Rattrapage
|
Rattrapage
|
||||||
</th>
|
</th>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<th
|
<th
|
||||||
style={{ borderLeft: 'solid 1px black', borderRight: 'solid 1px black' }}
|
style={{ borderLeft: 'solid 1px black', borderRight: 'solid 1px black' }}
|
||||||
></th>
|
></th>
|
||||||
@ -617,15 +640,27 @@ const totalNotes = () => {
|
|||||||
style={{
|
style={{
|
||||||
borderTop: 'solid 1px black',
|
borderTop: 'solid 1px black',
|
||||||
textAlign: 'center',
|
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
|
semestre
|
||||||
</th>
|
</th>
|
||||||
<th style={{ borderLeft: 'solid 1px black' }}>Unités <br /> d'Enseignement <br />(UE) </th>
|
<th style={{ borderLeft: 'solid 1px black' }}>
|
||||||
<th style={{ borderLeft: 'solid 1px black' }}>Éléments <br /> constitutifs <br />(EC)</th>
|
Unités <br /> d'Enseignement <br />
|
||||||
|
(UE){' '}
|
||||||
|
</th>
|
||||||
|
<th style={{ borderLeft: 'solid 1px black' }}>
|
||||||
|
Éléments <br /> constitutifs <br />
|
||||||
|
(EC)
|
||||||
|
</th>
|
||||||
|
|
||||||
{sessionType !== 'rattrapage' && (
|
{sessionType !== 'rattrapage' && (
|
||||||
<>
|
<>
|
||||||
<th style={{ borderLeft: 'solid 1px black', padding: '0 5px' }}>crédit</th>
|
<th style={{ borderLeft: 'solid 1px black', padding: '0 5px' }}>crédit</th>
|
||||||
@ -633,15 +668,14 @@ const totalNotes = () => {
|
|||||||
<th style={{ borderLeft: 'solid 1px black', padding: '0 5px' }}>Moyenne</th>
|
<th style={{ borderLeft: 'solid 1px black', padding: '0 5px' }}>Moyenne</th>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{sessionType !== 'normale' && (
|
{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' }}>Notes</th>
|
||||||
<th style={{ borderLeft: 'solid 1px black', padding: '0 5px' }}>Moyenne</th>
|
<th style={{ borderLeft: 'solid 1px black', padding: '0 5px' }}>Moyenne</th>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<th style={{ borderLeft: 'solid 1px black', borderRight: 'solid 1px black' }}>
|
<th style={{ borderLeft: 'solid 1px black', borderRight: 'solid 1px black' }}>
|
||||||
Observation
|
Observation
|
||||||
</th>
|
</th>
|
||||||
@ -673,11 +707,13 @@ const totalNotes = () => {
|
|||||||
paddingLeft: '2%'
|
paddingLeft: '2%'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Mention:{' '}
|
Mention: <span style={{ marginLeft: '3%' }}>{getmentionAfterNotes(note)}</span>
|
||||||
<span style={{ marginLeft: '3%' }}>{getmentionAfterNotes(note)}</span>
|
|
||||||
</td>
|
</td>
|
||||||
<td colSpan={sessionType === 'ensemble' ? 6 : 4} style={{ textAlign: 'left', paddingLeft: '1%' }}>
|
<td
|
||||||
Décision du Jury:{' '}
|
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>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
@ -686,7 +722,6 @@ const totalNotes = () => {
|
|||||||
<p>
|
<p>
|
||||||
<b>Toamasine le</b>
|
<b>Toamasine le</b>
|
||||||
</p>
|
</p>
|
||||||
{/* texte hidden for place in signature */}
|
|
||||||
<p style={{ visibility: 'hidden' }}>
|
<p style={{ visibility: 'hidden' }}>
|
||||||
Lorem ipsum dolor sit amet consectetur adipisicing elit. Blanditiis delectus
|
Lorem ipsum dolor sit amet consectetur adipisicing elit. Blanditiis delectus
|
||||||
perspiciatis nisi aliquid eos adipisci cumque amet ratione error voluptatum.
|
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 classe from '../assets/AllStyleComponents.module.css'
|
||||||
import classeHome from '../assets/Home.module.css'
|
import classeHome from '../assets/Home.module.css'
|
||||||
import Paper from '@mui/material/Paper'
|
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 { IoMdReturnRight } from 'react-icons/io'
|
||||||
import jsPDF from 'jspdf'
|
import jsPDF from 'jspdf'
|
||||||
import autoTable from 'jspdf-autotable'
|
import autoTable from 'jspdf-autotable'
|
||||||
import { FaDownload } from 'react-icons/fa'
|
import { FaDownload } from 'react-icons/fa'
|
||||||
import logoRelerev1 from '../assets/logorelever.png'
|
import logoRelerev1 from '../assets/logorelever.png'
|
||||||
import logoRelerev2 from '../assets/logorelever2.png'
|
import logoRelerev2 from '../assets/logorelever2.png'
|
||||||
|
import getSemestre from './function/GetSemestre'
|
||||||
|
|
||||||
const Resultat = () => {
|
const Resultat = () => {
|
||||||
const { niveau, scolaire } = useParams()
|
const { niveau, scolaire } = useParams()
|
||||||
const formData = {
|
const formData = { niveau, scolaire }
|
||||||
niveau,
|
|
||||||
scolaire
|
|
||||||
}
|
|
||||||
const [etudiants, setEtudiants] = useState([])
|
const [etudiants, setEtudiants] = useState([])
|
||||||
const [mention, setMention] = useState([])
|
const [mention, setMention] = useState([])
|
||||||
const [session, setSession] = useState([])
|
const [session, setSession] = useState([])
|
||||||
const [tabValue, setTabValue] = useState(0)
|
const [tabValue, setTabValue] = useState(0)
|
||||||
|
|
||||||
// États pour les sélections
|
|
||||||
const [selectedMatiere, setSelectedMatiere] = useState('')
|
const [selectedMatiere, setSelectedMatiere] = useState('')
|
||||||
const [selectedUE, setSelectedUE] = useState('')
|
const [selectedUE, setSelectedUE] = useState('')
|
||||||
|
const [selectedMentionId, setSelectedMentionId] = useState('')
|
||||||
const [availableMatieres, setAvailableMatieres] = useState([])
|
const [availableMatieres, setAvailableMatieres] = useState([])
|
||||||
const [availableUEs, setAvailableUEs] = useState([])
|
const [availableUEs, setAvailableUEs] = useState([])
|
||||||
|
const [moyennesRattrapage, setMoyennesRattrapage] = useState([])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
window.notes.getMoyenne(formData).then((response) => {
|
const fetchData = async () => {
|
||||||
setEtudiants(response)
|
try {
|
||||||
extractMatieresAndUEs(response)
|
const etudiantsData = await window.notes.getMoyenne(formData)
|
||||||
})
|
setEtudiants(etudiantsData)
|
||||||
window.noteRepech.getMoyenneRepech(formData).then((response) => {
|
extractMatieresAndUEs(etudiantsData)
|
||||||
setSession(response)
|
const sessionData = await window.noteRepech.getMoyenneRepech(formData)
|
||||||
})
|
setSession(sessionData)
|
||||||
window.mention.getMention().then((response) => {
|
const mentionData = await window.mention.getMention()
|
||||||
setMention(response)
|
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 extractMatieresAndUEs = (data) => {
|
||||||
const matieres = new Set()
|
const matieres = new Map() // key=nomMat, value=mention_id
|
||||||
const ues = new Set()
|
const ues = new Set()
|
||||||
|
|
||||||
for (let index = 0; index < data.length; index++) {
|
for (let index = 0; index < data.length; index++) {
|
||||||
for (let j = 0; j < data[index].length; j++) {
|
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}`
|
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)
|
ues.add(ue)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
setAvailableMatieres(Array.from(matieres.keys()))
|
||||||
setAvailableMatieres(Array.from(matieres))
|
|
||||||
setAvailableUEs(Array.from(ues))
|
setAvailableUEs(Array.from(ues))
|
||||||
|
if (matieres.size > 0) setSelectedMatiere(Array.from(matieres.keys())[0])
|
||||||
// Sélectionner la première matière et UE par défaut
|
|
||||||
if (matieres.size > 0) setSelectedMatiere(Array.from(matieres)[0])
|
|
||||||
if (ues.size > 0) setSelectedUE(Array.from(ues)[0])
|
if (ues.size > 0) setSelectedUE(Array.from(ues)[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
let dataToMap = []
|
|
||||||
|
|
||||||
function returnmention(id) {
|
function returnmention(id) {
|
||||||
let mentions
|
const found = mention.find((m) => m.id === id)
|
||||||
for (let index = 0; index < mention.length; index++) {
|
return found ? found.nom : ''
|
||||||
if (mention[index].id == id) {
|
|
||||||
mentions = mention[index].nom
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return mentions
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fonction pour déterminer la mention selon la moyenne
|
|
||||||
function getMentionFromMoyenne(moyenne) {
|
function getMentionFromMoyenne(moyenne) {
|
||||||
const moy = parseFloat(moyenne)
|
const moy = parseFloat(moyenne)
|
||||||
if (moy >= 18) return 'Excellent'
|
if (moy >= 18) return 'Excellent'
|
||||||
@ -87,102 +162,86 @@ const Resultat = () => {
|
|||||||
return 'Remise à la famille'
|
return 'Remise à la famille'
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkNull(params) {
|
|
||||||
if (params == null || params == undefined) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
return params
|
|
||||||
}
|
|
||||||
|
|
||||||
function compareSessionNotes(session1, session2) {
|
function compareSessionNotes(session1, session2) {
|
||||||
let notes
|
|
||||||
if (session2) {
|
if (session2) {
|
||||||
if (session1 < session2.note) {
|
return session1 < session2.note ? session2.note : session1
|
||||||
notes = session2.note
|
|
||||||
} else {
|
|
||||||
notes = session1
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
notes = session1
|
|
||||||
}
|
}
|
||||||
return notes
|
return session1
|
||||||
}
|
}
|
||||||
|
|
||||||
// Traitement des données pour résultat définitif - INCLUANT TOUS LES ÉTUDIANTS
|
const calculerMoyennesSessionNormale = () => {
|
||||||
for (let index = 0; index < etudiants.length; index++) {
|
const moyennes = []
|
||||||
let total = 0
|
for (let index = 0; index < etudiants.length; index++) {
|
||||||
let note = 0
|
if (etudiants[index] && etudiants[index][0]) {
|
||||||
let totalCredit = 0
|
let totalNotesPonderees = 0
|
||||||
let hasValidNotes = false
|
let totalCredits = 0
|
||||||
|
let hasValidNotes = false
|
||||||
let modelJson = {
|
let modelJson = {
|
||||||
id: '',
|
id: etudiants[index][0].etudiant_id,
|
||||||
nom: '',
|
nom: etudiants[index][0].nom,
|
||||||
prenom: '',
|
prenom: etudiants[index][0].prenom,
|
||||||
photos: '',
|
photos: etudiants[index][0].photos,
|
||||||
moyenne: '',
|
mention: etudiants[index][0].mention_id,
|
||||||
mention: '',
|
anneescolaire: etudiants[index][0].annee_scolaire,
|
||||||
anneescolaire: ''
|
moyenne: 'N/A'
|
||||||
}
|
}
|
||||||
|
for (let j = 0; j < etudiants[index].length; j++) {
|
||||||
for (let j = 0; j < etudiants[index].length; j++) {
|
const noteNormale = Number(etudiants[index][j].note)
|
||||||
modelJson.id = etudiants[index][j].etudiant_id
|
const credit = Number(etudiants[index][j].credit)
|
||||||
modelJson.nom = etudiants[index][j].nom
|
if (noteNormale != null && !isNaN(noteNormale)) {
|
||||||
modelJson.prenom = etudiants[index][j].prenom
|
totalNotesPonderees += noteNormale * credit
|
||||||
modelJson.photos = etudiants[index][j].photos
|
totalCredits += credit
|
||||||
modelJson.mention = etudiants[index][j].mention_id
|
hasValidNotes = true
|
||||||
modelJson.anneescolaire = etudiants[index][j].annee_scolaire
|
}
|
||||||
|
}
|
||||||
let currentNote = etudiants[index][j].note
|
if (hasValidNotes && totalCredits > 0) {
|
||||||
if (session[index]) {
|
modelJson.moyenne = (totalNotesPonderees / totalCredits).toFixed(2)
|
||||||
currentNote = compareSessionNotes(etudiants[index][j].note, checkNull(session[index][j]))
|
}
|
||||||
}
|
moyennes.push(modelJson)
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return moyennes.sort((a, b) => {
|
||||||
// Calculer la moyenne même si certaines notes manquent
|
const moyA = a.moyenne === 'N/A' ? -1 : parseFloat(a.moyenne)
|
||||||
if (hasValidNotes && totalCredit > 0) {
|
const moyB = b.moyenne === 'N/A' ? -1 : parseFloat(b.moyenne)
|
||||||
total = note / totalCredit
|
return moyB - moyA
|
||||||
modelJson.moyenne = total.toFixed(2)
|
})
|
||||||
} else {
|
}
|
||||||
modelJson.moyenne = 'N/A'
|
|
||||||
}
|
const sortedStudents = calculerMoyennesSessionNormale()
|
||||||
|
|
||||||
dataToMap.push(modelJson)
|
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 getResultsByMatiere = () => {
|
||||||
const results = []
|
const results = []
|
||||||
|
|
||||||
for (let index = 0; index < etudiants.length; index++) {
|
for (let index = 0; index < etudiants.length; index++) {
|
||||||
for (let j = 0; j < etudiants[index].length; j++) {
|
for (let j = 0; j < etudiants[index].length; j++) {
|
||||||
const matiere = etudiants[index][j].matiere || `Matière ${j + 1}`
|
const nomMat = etudiants[index][j].nomMat || `Matière ${j + 1}`
|
||||||
|
const mentionId = etudiants[index][j].mention_id
|
||||||
if (matiere === selectedMatiere) {
|
if (nomMat === selectedMatiere && mentionId == selectedMentionId) {
|
||||||
let finalNote = etudiants[index][j].note
|
let finalNote = etudiants[index][j].note
|
||||||
if (session[index] && session[index][j]) {
|
if (session[index] && session[index][j]) {
|
||||||
finalNote = compareSessionNotes(etudiants[index][j].note, session[index][j])
|
finalNote = compareSessionNotes(etudiants[index][j].note, session[index][j])
|
||||||
}
|
}
|
||||||
|
|
||||||
results.push({
|
results.push({
|
||||||
id: etudiants[index][j].etudiant_id,
|
id: etudiants[index][j].etudiant_id,
|
||||||
nom: etudiants[index][j].nom,
|
nom: etudiants[index][j].nom,
|
||||||
prenom: etudiants[index][j].prenom,
|
prenom: etudiants[index][j].prenom,
|
||||||
note: finalNote != null ? finalNote.toFixed(2) : 'N/A',
|
note: finalNote != null ? finalNote.toFixed(2) : 'N/A',
|
||||||
credit: etudiants[index][j].credit,
|
credit: etudiants[index][j].credit
|
||||||
mention: returnmention(etudiants[index][j].mention_id)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return results.sort((a, b) => {
|
return results.sort((a, b) => {
|
||||||
const noteA = a.note === 'N/A' ? -1 : parseFloat(a.note)
|
const noteA = a.note === 'N/A' ? -1 : parseFloat(a.note)
|
||||||
const noteB = b.note === 'N/A' ? -1 : parseFloat(b.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 getResultsByUE = () => {
|
||||||
const groupedStudents = {}
|
const groupedStudents = {}
|
||||||
const matieresInUE = new Set()
|
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 index = 0; index < etudiants.length; index++) {
|
||||||
for (let j = 0; j < etudiants[index].length; j++) {
|
for (let j = 0; j < etudiants[index].length; j++) {
|
||||||
const ue = etudiants[index][j].ue || `UE${Math.floor(j / 2) + 1}`
|
const ue = etudiants[index][j].ue || `UE${Math.floor(j / 2) + 1}`
|
||||||
const matiere = etudiants[index][j].matiere || `Matière ${j + 1}`
|
const matiere = etudiants[index][j].nomMat || `Matière ${j + 1}`
|
||||||
|
const mentionId = etudiants[index][j].mention_id
|
||||||
if (ue === selectedUE) {
|
if (ue === selectedUE && mentionId == selectedMentionId) {
|
||||||
matieresInUE.add(matiere)
|
matieresInUE.add(matiere)
|
||||||
const etudiantId = etudiants[index][j].etudiant_id
|
const etudiantId = etudiants[index][j].etudiant_id
|
||||||
|
|
||||||
if (!groupedStudents[etudiantId]) {
|
if (!groupedStudents[etudiantId]) {
|
||||||
groupedStudents[etudiantId] = {
|
groupedStudents[etudiantId] = {
|
||||||
id: etudiantId,
|
id: etudiantId,
|
||||||
@ -216,12 +271,10 @@ const Resultat = () => {
|
|||||||
hasValidNotes: false
|
hasValidNotes: false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let finalNote = etudiants[index][j].note
|
let finalNote = etudiants[index][j].note
|
||||||
if (session[index] && session[index][j]) {
|
if (session[index] && session[index][j]) {
|
||||||
finalNote = compareSessionNotes(etudiants[index][j].note, session[index][j])
|
finalNote = compareSessionNotes(etudiants[index][j].note, session[index][j])
|
||||||
}
|
}
|
||||||
|
|
||||||
if (finalNote != null && finalNote != undefined && !isNaN(finalNote)) {
|
if (finalNote != null && finalNote != undefined && !isNaN(finalNote)) {
|
||||||
groupedStudents[etudiantId].matieres[matiere] = finalNote.toFixed(2)
|
groupedStudents[etudiantId].matieres[matiere] = finalNote.toFixed(2)
|
||||||
groupedStudents[etudiantId].totalNote += finalNote * etudiants[index][j].credit
|
groupedStudents[etudiantId].totalNote += finalNote * etudiants[index][j].credit
|
||||||
@ -233,14 +286,10 @@ const Resultat = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const results = Object.values(groupedStudents).map(student => ({
|
const results = Object.values(groupedStudents).map(student => ({
|
||||||
...student,
|
...student,
|
||||||
moyenneUE: student.hasValidNotes && student.totalCredit > 0
|
moyenneUE: student.hasValidNotes && student.totalCredit > 0 ? (student.totalNote / student.totalCredit).toFixed(2) : 'N/A'
|
||||||
? (student.totalNote / student.totalCredit).toFixed(2)
|
|
||||||
: 'N/A'
|
|
||||||
}))
|
}))
|
||||||
|
|
||||||
return {
|
return {
|
||||||
students: results.sort((a, b) => {
|
students: results.sort((a, b) => {
|
||||||
const moyA = a.moyenneUE === 'N/A' ? -1 : parseFloat(a.moyenneUE)
|
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) => {
|
const handleTabChange = (event, newValue) => {
|
||||||
setTabValue(newValue)
|
setTabValue(newValue)
|
||||||
}
|
}
|
||||||
@ -264,15 +307,9 @@ const Resultat = () => {
|
|||||||
const print = () => {
|
const print = () => {
|
||||||
const generatePDF = () => {
|
const generatePDF = () => {
|
||||||
try {
|
try {
|
||||||
const pdf = new jsPDF({
|
const pdf = new jsPDF({ orientation: 'portrait', unit: 'mm', format: 'a4' })
|
||||||
orientation: 'portrait',
|
pdf.addImage(logoRelerev1, 'PNG', 175, 5, 32, 30)
|
||||||
unit: 'mm',
|
pdf.addImage(logoRelerev2, 'PNG', 10, 5, 40, 30)
|
||||||
format: 'a4'
|
|
||||||
})
|
|
||||||
|
|
||||||
pdf.addImage(logoRelerev1, 'PNG', 175, 5, 32, 30)
|
|
||||||
pdf.addImage(logoRelerev2, 'PNG', 10, 5, 40, 30)
|
|
||||||
|
|
||||||
pdf.setFontSize(10)
|
pdf.setFontSize(10)
|
||||||
pdf.text('REPOBLIKAN\'I MADAGASIKARA', 105, 10, { align: 'center' })
|
pdf.text('REPOBLIKAN\'I MADAGASIKARA', 105, 10, { align: 'center' })
|
||||||
pdf.text('Fitiavana-Tanindrazana-Fandrosoana', 105, 14, { 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('********************', 105, 30, { align: 'center' })
|
||||||
pdf.text('UNIVERSITÉ DE TOAMASINA', 105, 34, { align: 'center' })
|
pdf.text('UNIVERSITÉ DE TOAMASINA', 105, 34, { align: 'center' })
|
||||||
pdf.text('ÉCOLE SUPÉRIEURE POLYTECHNIQUE', 105, 38, { align: 'center' })
|
pdf.text('ÉCOLE SUPÉRIEURE POLYTECHNIQUE', 105, 38, { align: 'center' })
|
||||||
|
const tableId = tabValue === 0 ? '#mentionTable' : tabValue === 1 ? '#rattrapageAdmisTable' : tabValue === 2 ? '#rattrapageNonAdmisTable' : tabValue === 3 ? '#subjectTable' : '#ueTable'
|
||||||
const tableId = tabValue === 0 ? '#resultTable' : tabValue === 1 ? '#subjectTable' : '#ueTable'
|
|
||||||
|
|
||||||
autoTable(pdf, {
|
autoTable(pdf, {
|
||||||
html: tableId,
|
html: tableId,
|
||||||
startY: 50,
|
startY: 50,
|
||||||
theme: 'grid',
|
theme: 'grid',
|
||||||
headStyles: {
|
headStyles: { fillColor: [255, 255, 255], halign: 'center', fontStyle: 'bold', textColor: [0, 0, 0], lineColor: [0, 0, 0], lineWidth: 0.5 },
|
||||||
fillColor: [255, 255, 255], // Fond blanc
|
styles: { fontSize: 8, cellPadding: 2, halign: 'center', lineColor: [0, 0, 0], lineWidth: 0.5 },
|
||||||
halign: 'center',
|
bodyStyles: { lineColor: [0, 0, 0], lineWidth: 0.5 }
|
||||||
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
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
if (tabValue === 1) {
|
||||||
const suffix = tabValue === 0 ? 'definitif' :
|
const admis = getResultsRattrapageAdmis()
|
||||||
tabValue === 1 ? `par-matiere-${selectedMatiere}` :
|
const finalY = pdf.lastAutoTable.finalY || 50
|
||||||
`par-ue-${selectedUE}`
|
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`)
|
pdf.save(`Resultat-${suffix}-${niveau}-${scolaire}.pdf`)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error generating PDF:', error)
|
console.error('Error generating PDF:', error)
|
||||||
@ -322,27 +346,9 @@ const Resultat = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const renderHeader = () => (
|
const renderHeader = () => (
|
||||||
<div
|
<div style={{ display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', marginBottom: '20px', position: 'relative' }}>
|
||||||
style={{
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'flex-start',
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
marginBottom: '20px',
|
|
||||||
position: 'relative'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<img src={logoRelerev2} alt="Logo gauche" width={90} height={90} />
|
<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={{ fontWeight: 'bold' }}>REPOBLIKAN'I MADAGASIKARA</div>
|
||||||
<div style={{ fontStyle: 'italic' }}>Fitiavana-Tanindrazana-Fandrosoana</div>
|
<div style={{ fontStyle: 'italic' }}>Fitiavana-Tanindrazana-Fandrosoana</div>
|
||||||
<div>********************</div>
|
<div>********************</div>
|
||||||
@ -352,82 +358,167 @@ const Resultat = () => {
|
|||||||
<div style={{ fontWeight: 'bold' }}>UNIVERSITÉ DE TOAMASINA</div>
|
<div style={{ fontWeight: 'bold' }}>UNIVERSITÉ DE TOAMASINA</div>
|
||||||
<div style={{ fontWeight: 'bold' }}>ÉCOLE SUPÉRIEURE POLYTECHNIQUE</div>
|
<div style={{ fontWeight: 'bold' }}>ÉCOLE SUPÉRIEURE POLYTECHNIQUE</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<img src={logoRelerev1} alt="Logo droite" width={110} height={90} />
|
<img src={logoRelerev1} alt="Logo droite" width={110} height={90} />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
||||||
const renderResultDefinitif = () => (
|
const renderResultParMention = () => {
|
||||||
<table
|
const results = getResultsByMention()
|
||||||
className="table table-bordered table-striped text-center shadow-sm"
|
const selectedMentionName = returnmention(selectedMentionId)
|
||||||
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()
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div style={{ marginBottom: '20px' }}>
|
<div style={{ marginBottom: '20px' }}>
|
||||||
<FormControl fullWidth>
|
<FormControl fullWidth>
|
||||||
<InputLabel>Sélectionner une matière</InputLabel>
|
<InputLabel>Sélectionner une mention</InputLabel>
|
||||||
<Select
|
<Select value={selectedMentionId} onChange={(e) => setSelectedMentionId(e.target.value)} label="Sélectionner une mention">
|
||||||
value={selectedMatiere}
|
{mention.map((m) => (<MenuItem key={m.id} value={m.id}>{m.nom}</MenuItem>))}
|
||||||
onChange={(e) => setSelectedMatiere(e.target.value)}
|
|
||||||
label="Sélectionner une matière"
|
|
||||||
>
|
|
||||||
{availableMatieres.map((matiere) => (
|
|
||||||
<MenuItem key={matiere} value={matiere}>
|
|
||||||
{matiere}
|
|
||||||
</MenuItem>
|
|
||||||
))}
|
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</div>
|
</div>
|
||||||
|
<table className="table table-bordered table-striped text-center shadow-sm" id="mentionTable" style={{ fontSize: '12px' }}>
|
||||||
<table
|
<thead className="table-secondary">
|
||||||
className="table table-bordered table-striped text-center shadow-sm"
|
<tr>
|
||||||
id="subjectTable"
|
<td colSpan={5} className="py-3" style={{ backgroundColor: '#f8f9fa' }}>
|
||||||
style={{ fontSize: '12px' }}
|
<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>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
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">
|
<thead className="table-secondary">
|
||||||
<tr>
|
<tr>
|
||||||
<td colSpan={4} className="py-3" style={{ backgroundColor: '#f8f9fa' }}>
|
<td colSpan={4} className="py-3" style={{ backgroundColor: '#f8f9fa' }}>
|
||||||
<h6 style={{ margin: 0, fontWeight: 'bold' }}>
|
<h6 style={{ margin: 0, fontWeight: 'bold' }}>Résultat {niveau} — Mention : {selectedMentionName} — Matière : {selectedMatiere}</h6>
|
||||||
Résultat pour la matière : {selectedMatiere}
|
|
||||||
</h6>
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr style={{ backgroundColor: '#e9ecef' }}>
|
<tr style={{ backgroundColor: '#e9ecef' }}>
|
||||||
@ -454,46 +545,35 @@ const Resultat = () => {
|
|||||||
|
|
||||||
const renderResultParUE = () => {
|
const renderResultParUE = () => {
|
||||||
const { students, matieres } = getResultsByUE()
|
const { students, matieres } = getResultsByUE()
|
||||||
|
const selectedMentionName = returnmention(selectedMentionId)
|
||||||
return (
|
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>
|
<FormControl fullWidth>
|
||||||
<InputLabel>Sélectionner une UE</InputLabel>
|
<InputLabel>Sélectionner une UE</InputLabel>
|
||||||
<Select
|
<Select value={selectedUE} onChange={(e) => setSelectedUE(e.target.value)} label="Sélectionner une UE">
|
||||||
value={selectedUE}
|
{availableUEs.map((ue) => (<MenuItem key={ue} value={ue}>{ue}</MenuItem>))}
|
||||||
onChange={(e) => setSelectedUE(e.target.value)}
|
|
||||||
label="Sélectionner une UE"
|
|
||||||
>
|
|
||||||
{availableUEs.map((ue) => (
|
|
||||||
<MenuItem key={ue} value={ue}>
|
|
||||||
{ue}
|
|
||||||
</MenuItem>
|
|
||||||
))}
|
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</div>
|
</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">
|
<thead className="table-secondary">
|
||||||
<tr>
|
<tr>
|
||||||
<td colSpan={3 + matieres.length} className="py-3" style={{ backgroundColor: '#f8f9fa' }}>
|
<td colSpan={3 + matieres.length} className="py-3" style={{ backgroundColor: '#f8f9fa' }}>
|
||||||
<h6 style={{ margin: 0, fontWeight: 'bold' }}>
|
<h6 style={{ margin: 0, fontWeight: 'bold' }}>Résultat {niveau} — Mention : {selectedMentionName} — UE : {selectedUE}</h6>
|
||||||
Résultat pour l'UE : {selectedUE}
|
|
||||||
</h6>
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr style={{ backgroundColor: '#e9ecef' }}>
|
<tr style={{ backgroundColor: '#e9ecef' }}>
|
||||||
<th style={{ fontWeight: 'bold' }}>RANG</th>
|
<th style={{ fontWeight: 'bold' }}>RANG</th>
|
||||||
<th style={{ fontWeight: 'bold' }}>NOMS</th>
|
<th style={{ fontWeight: 'bold' }}>NOMS</th>
|
||||||
<th style={{ fontWeight: 'bold' }}>PRÉNOMS</th>
|
<th style={{ fontWeight: 'bold' }}>PRÉNOMS</th>
|
||||||
{matieres.map((matiere) => (
|
{matieres.map((matiere) => (<th key={matiere} style={{ fontWeight: 'bold' }}>{matiere}</th>))}
|
||||||
<th key={matiere} style={{ fontWeight: 'bold' }}>{matiere}</th>
|
|
||||||
))}
|
|
||||||
<th style={{ fontWeight: 'bold' }}>MOYENNE UE</th>
|
<th style={{ fontWeight: 'bold' }}>MOYENNE UE</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@ -503,11 +583,7 @@ const Resultat = () => {
|
|||||||
<td style={{ fontWeight: 'bold' }}>{index + 1}.</td>
|
<td style={{ fontWeight: 'bold' }}>{index + 1}.</td>
|
||||||
<td style={{ textAlign: 'left', paddingLeft: '10px', fontWeight: 'bold' }}>{student.nom}</td>
|
<td style={{ textAlign: 'left', paddingLeft: '10px', fontWeight: 'bold' }}>{student.nom}</td>
|
||||||
<td style={{ textAlign: 'left', paddingLeft: '10px' }}>{student.prenom}</td>
|
<td style={{ textAlign: 'left', paddingLeft: '10px' }}>{student.prenom}</td>
|
||||||
{matieres.map((matiere) => (
|
{matieres.map((matiere) => (<td key={matiere} style={{ fontWeight: 'bold' }}>{student.matieres[matiere] || 'N/A'}</td>))}
|
||||||
<td key={matiere} style={{ fontWeight: 'bold' }}>
|
|
||||||
{student.matieres[matiere] || 'N/A'}
|
|
||||||
</td>
|
|
||||||
))}
|
|
||||||
<td style={{ fontWeight: 'bold', backgroundColor: '#fff3cd' }}>{student.moyenneUE}</td>
|
<td style={{ fontWeight: 'bold', backgroundColor: '#fff3cd' }}>{student.moyenneUE}</td>
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
))}
|
||||||
@ -522,9 +598,7 @@ const Resultat = () => {
|
|||||||
<div className={classeHome.header}>
|
<div className={classeHome.header}>
|
||||||
<div className={classe.h1style}>
|
<div className={classe.h1style}>
|
||||||
<div className={classeHome.blockTitle}>
|
<div className={classeHome.blockTitle}>
|
||||||
<h1>
|
<h1>Resultat des {niveau} en {scolaire}</h1>
|
||||||
Resultat des {niveau} en {scolaire}
|
|
||||||
</h1>
|
|
||||||
<div style={{ display: 'flex', gap: '10px' }}>
|
<div style={{ display: 'flex', gap: '10px' }}>
|
||||||
<Link to={'#'} onClick={print}>
|
<Link to={'#'} onClick={print}>
|
||||||
<Button color="warning" variant="contained">
|
<Button color="warning" variant="contained">
|
||||||
@ -541,39 +615,26 @@ const Resultat = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={classeHome.boxEtudiantsCard}>
|
<div className={classeHome.boxEtudiantsCard}>
|
||||||
<Paper
|
<Paper sx={{ height: 'auto', width: '100%', display: 'flex', flexDirection: 'column', padding: '2%' }}>
|
||||||
sx={{
|
|
||||||
height: 'auto',
|
|
||||||
width: '100%',
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
padding: '2%'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{renderHeader()}
|
{renderHeader()}
|
||||||
|
|
||||||
<div style={{ marginBottom: '15px', fontSize: '12px' }}>
|
<div style={{ marginBottom: '15px', fontSize: '12px' }}>
|
||||||
<div><strong>Parcours :</strong> GC</div>
|
<div><strong>Parcours :</strong> GC</div>
|
||||||
<div><strong>Niveau :</strong> {niveau}</div>
|
<div><strong>Niveau :</strong> {niveau}</div>
|
||||||
<div><strong>Année Universitaire :</strong> {scolaire}</div>
|
<div><strong>Année Universitaire :</strong> {scolaire}</div>
|
||||||
</div>
|
</div>
|
||||||
|
<Tabs value={tabValue} onChange={handleTabChange} centered sx={{ marginBottom: '20px' }}>
|
||||||
<Tabs
|
<Tab label="Session Normale" />
|
||||||
value={tabValue}
|
<Tab label="Rattrapage - Admis" />
|
||||||
onChange={handleTabChange}
|
<Tab label="Rattrapage - Non Admis" />
|
||||||
centered
|
|
||||||
sx={{ marginBottom: '20px' }}
|
|
||||||
>
|
|
||||||
<Tab label="Résultat Définitif" />
|
|
||||||
<Tab label="Par Matière" />
|
<Tab label="Par Matière" />
|
||||||
<Tab label="Par UE" />
|
<Tab label="Par UE" />
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
{tabValue === 0 && renderResultParMention()}
|
||||||
{tabValue === 0 && renderResultDefinitif()}
|
{tabValue === 1 && renderRattrapageAdmis()}
|
||||||
{tabValue === 1 && renderResultParMatiere()}
|
{tabValue === 2 && renderRattrapageNonAdmis()}
|
||||||
{tabValue === 2 && renderResultParUE()}
|
{tabValue === 3 && renderResultParMatiere()}
|
||||||
|
{tabValue === 4 && renderResultParUE()}
|
||||||
</Paper>
|
</Paper>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -18,6 +18,7 @@ import { BsCalendar2Date } from 'react-icons/bs'
|
|||||||
import { SiVitest } from 'react-icons/si'
|
import { SiVitest } from 'react-icons/si'
|
||||||
import { GrManual } from 'react-icons/gr'
|
import { GrManual } from 'react-icons/gr'
|
||||||
import { FaClipboardList } from 'react-icons/fa6'
|
import { FaClipboardList } from 'react-icons/fa6'
|
||||||
|
import { FaMoneyBillWave } from 'react-icons/fa'
|
||||||
|
|
||||||
const Sidenav = () => {
|
const Sidenav = () => {
|
||||||
const [anchorEl, setAnchorEl] = useState(null)
|
const [anchorEl, setAnchorEl] = useState(null)
|
||||||
@ -268,6 +269,15 @@ const isAdmin = () => {
|
|||||||
<MdAdminPanelSettings /> Admin
|
<MdAdminPanelSettings /> Admin
|
||||||
</Link>
|
</Link>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
<MenuItem>
|
||||||
|
<Link
|
||||||
|
to="/configecolage"
|
||||||
|
style={{ color: 'black', textDecoration: 'none' }}
|
||||||
|
onClick={handleClose}
|
||||||
|
>
|
||||||
|
<FaMoneyBillWave /> Config Ecolage
|
||||||
|
</Link>
|
||||||
|
</MenuItem>
|
||||||
<MenuItem>
|
<MenuItem>
|
||||||
<Link
|
<Link
|
||||||
to="/para"
|
to="/para"
|
||||||
|
|||||||
@ -1,249 +1,232 @@
|
|||||||
import React, { useEffect, useRef, useState } from 'react'
|
import React, { useEffect, useRef, useState } from 'react'
|
||||||
import { useParams, Link } from 'react-router-dom'
|
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 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 classe from '../assets/AllStyleComponents.module.css'
|
||||||
import classeHome from '../assets/Home.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 svgSuccess from '../assets/success.svg'
|
||||||
import svgError from '../assets/error.svg'
|
import svgError from '../assets/error.svg'
|
||||||
|
|
||||||
const SingleNotes = () => {
|
const SingleNotes = () => {
|
||||||
let { id, niveau, scolaire } = useParams()
|
const { id, niveau, scolaire } = useParams()
|
||||||
|
|
||||||
const [notes, setNotes] = useState([])
|
const [notes, setNotes] = useState([])
|
||||||
const [notesRepech, setNotesRepech] = useState([])
|
const [notesRepech, setNotesRepech] = useState([])
|
||||||
const [formData, setFormData] = useState({})
|
const [formData, setFormData] = useState({})
|
||||||
const [formData2, setFormData2] = useState({})
|
const [formData2, setFormData2] = useState({})
|
||||||
const [etudiant, setEtudiant] = useState([])
|
const [etudiant, setEtudiant] = useState({})
|
||||||
let annee_scolaire = scolaire
|
|
||||||
const [screenRattrapage, setScreenRattrapage] = useState(false)
|
const [screenRattrapage, setScreenRattrapage] = useState(false)
|
||||||
|
|
||||||
useEffect(() => {
|
/* ===================== MESSAGE MODAL ===================== */
|
||||||
window.etudiants.getSingle({ id }).then((response) => {
|
const [open, setOpen] = useState(false)
|
||||||
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)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const [status, setStatus] = useState(200)
|
const [status, setStatus] = useState(200)
|
||||||
const [message, setMessage] = useState('')
|
const [message, setMessage] = useState('')
|
||||||
|
|
||||||
/**
|
|
||||||
* hook to open modal
|
|
||||||
*/
|
|
||||||
const [open, setOpen] = useState(false)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* function to close modal
|
|
||||||
*/
|
|
||||||
const handleClose = () => setOpen(false)
|
const handleClose = () => setOpen(false)
|
||||||
|
|
||||||
/**
|
/* ===================== DATA ===================== */
|
||||||
* function to return the view Modal
|
|
||||||
*
|
useEffect(() => {
|
||||||
* @returns {JSX}
|
window.etudiants.getSingle({ id }).then(setEtudiant)
|
||||||
*/
|
}, [])
|
||||||
const modals = () => (
|
|
||||||
<Modal
|
useEffect(() => {
|
||||||
open={open}
|
if (!etudiant?.mention_id) return
|
||||||
onClose={handleClose}
|
|
||||||
aria-labelledby="modal-title"
|
window.notes.getNotes({ id, niveau, mention_id: etudiant.mention_id, annee_scolaire: scolaire }).then(setNotes)
|
||||||
aria-describedby="modal-description"
|
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
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
top: '50%',
|
top: '50%',
|
||||||
left: '50%',
|
left: '50%',
|
||||||
transform: 'translate(-50%, -50%)',
|
transform: 'translate(-50%, -50%)',
|
||||||
width: 450,
|
width: 420,
|
||||||
bgcolor: 'background.paper',
|
bgcolor: 'background.paper',
|
||||||
boxShadow: 24,
|
boxShadow: 24,
|
||||||
p: 4
|
p: 4,
|
||||||
|
textAlign: 'center'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{status === 200 ? (
|
<img src={status === 200 ? svgSuccess : svgError} alt="" width={60} />
|
||||||
<Typography
|
<Typography sx={{ mt: 2 }}>{message}</Typography>
|
||||||
style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '10px' }}
|
|
||||||
>
|
<Button sx={{ mt: 3 }} color="warning" variant="contained" onClick={handleClose}>
|
||||||
<img src={svgSuccess} alt="" width={50} height={50} /> <span>{message}</span>
|
OK
|
||||||
</Typography>
|
</Button>
|
||||||
) : (
|
|
||||||
<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>
|
|
||||||
</Box>
|
</Box>
|
||||||
</Modal>
|
</Modal>
|
||||||
)
|
)
|
||||||
|
|
||||||
const nom = useRef()
|
/* ===================== UI ===================== */
|
||||||
|
|
||||||
const changeScreen = () => {
|
|
||||||
setScreenRattrapage(!screenRattrapage)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classe.mainHome}>
|
<div className={classe.mainHome}>
|
||||||
{modals()}
|
{modalMessage()}
|
||||||
|
|
||||||
<div className={classeHome.header}>
|
<div className={classeHome.header}>
|
||||||
<div className={classe.h1style}>
|
<div className={classe.h1style}>
|
||||||
<div className={classeHome.blockTitle}>
|
<div className={classeHome.blockTitle}>
|
||||||
<h1>Mise a jour des notes</h1>
|
<h1>Mise à jour des notes</h1>
|
||||||
<div style={{ display: 'flex', gap: '20px' }}>
|
<Link onClick={() => window.history.back()}>
|
||||||
<Link onClick={() => window.history.back()}>
|
<Button color="warning" variant="contained">
|
||||||
<Button color="warning" variant="contained">
|
<IoMdReturnRight />
|
||||||
<IoMdReturnRight style={{ fontSize: '20px' }} />
|
</Button>
|
||||||
</Button>
|
</Link>
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* displaying the form */}
|
|
||||||
<div className={classeHome.boxEtudiantsCard}>
|
<div className={classeHome.boxEtudiantsCard}>
|
||||||
<Box
|
<Paper sx={{ p: 4, width: 700, margin: 'auto', mt: 5 }}>
|
||||||
sx={{
|
{!screenRattrapage ? (
|
||||||
position: 'absolute',
|
<form onSubmit={submitForm}>
|
||||||
top: '55%',
|
<h4 style={{ textAlign: 'center' }}>Session normale</h4>
|
||||||
left: '50%',
|
|
||||||
transform: 'translate(-50%, -50%)',
|
<Grid container spacing={2}>
|
||||||
width: 700,
|
{notes.map((n) => (
|
||||||
borderRadius: '2%',
|
<Grid item xs={12} sm={3} key={n.id}>
|
||||||
bgcolor: 'background.paper',
|
<TextField
|
||||||
boxShadow: 24,
|
label={n.nom}
|
||||||
overflowY: 'auto',
|
value={formData[n.id] || ''}
|
||||||
p: 4
|
onChange={(e) =>
|
||||||
}}
|
setFormData({ ...formData, [n.id]: e.target.value })
|
||||||
>
|
}
|
||||||
<Box
|
fullWidth
|
||||||
sx={{
|
color="warning"
|
||||||
marginTop: '2%',
|
InputProps={{
|
||||||
display: 'flex',
|
startAdornment: (
|
||||||
width: '100%',
|
<InputAdornment position="start">
|
||||||
height: '70vh',
|
<CgNotes />
|
||||||
gap: '20px',
|
</InputAdornment>
|
||||||
alignItems: 'start',
|
)
|
||||||
justifyContent: 'center'
|
}}
|
||||||
}}
|
/>
|
||||||
>
|
</Grid>
|
||||||
{!screenRattrapage ? (
|
))}
|
||||||
<form action="" onSubmit={submitForm}>
|
</Grid>
|
||||||
<h4 style={{ textAlign: 'center' }}>Mise a jour des notes</h4>
|
|
||||||
{/* {/* map the all matiere and note to the form */}
|
<Box sx={{ display: 'flex', justifyContent: 'flex-end', mt: 3, gap: 2 }}>
|
||||||
<Grid container spacing={2}>
|
<Button onClick={() => setScreenRattrapage(true)} color="warning" variant="contained">
|
||||||
{notes.map((note) => (
|
Rattrapage
|
||||||
<Grid item xs={12} sm={3} key={note.nom}>
|
</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
|
<TextField
|
||||||
label={note.nom}
|
label={n.nom}
|
||||||
name={note.matiere_id}
|
value={formData2[n.id] || ''}
|
||||||
color="warning"
|
onChange={(e) =>
|
||||||
fullWidth
|
setFormData2({ ...formData2, [n.id]: e.target.value })
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
fullWidth
|
||||||
|
color="warning"
|
||||||
InputProps={{
|
InputProps={{
|
||||||
startAdornment: (
|
startAdornment: (
|
||||||
<InputAdornment position="start">
|
<InputAdornment position="start">
|
||||||
@ -251,111 +234,23 @@ const SingleNotes = () => {
|
|||||||
</InputAdornment>
|
</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
|
</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
|
<Box sx={{ display: 'flex', justifyContent: 'flex-end', mt: 3, gap: 2 }}>
|
||||||
item
|
<Button onClick={() => setScreenRattrapage(false)} color="warning" variant="contained">
|
||||||
xs={12}
|
Normale
|
||||||
style={{
|
</Button>
|
||||||
display: 'flex',
|
<Button type="submit" color="warning" variant="contained">
|
||||||
gap: '30px',
|
Enregistrer
|
||||||
justifyContent: 'flex-end',
|
</Button>
|
||||||
marginTop: '2%'
|
</Box>
|
||||||
}}
|
</form>
|
||||||
>
|
)}
|
||||||
<Button type="button" color="warning" variant="contained" onClick={changeScreen}>
|
</Paper>
|
||||||
Voir les notes session normale
|
|
||||||
</Button>
|
|
||||||
<Button type="submit" color="warning" variant="contained">
|
|
||||||
Enregister
|
|
||||||
</Button>
|
|
||||||
</Grid>
|
|
||||||
</form>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -64,41 +64,84 @@ const Student = () => {
|
|||||||
const [sortModel, setSortModel] = useState([])
|
const [sortModel, setSortModel] = useState([])
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
const savedFilter = localStorage.getItem('selectedNiveau') || ''
|
const savedFilter = localStorage.getItem('selectedNiveau') || ''
|
||||||
|
const savedAnnee = localStorage.getItem('selectedAnnee') || ''
|
||||||
const initialFilter = location.state?.selectedNiveau || savedFilter
|
const initialFilter = location.state?.selectedNiveau || savedFilter
|
||||||
|
const initialAnnee = location.state?.selectedAnnee || savedAnnee
|
||||||
|
|
||||||
const [selectedNiveau, setSelectedNiveau] = useState(initialFilter)
|
const [selectedNiveau, setSelectedNiveau] = useState(initialFilter)
|
||||||
|
const [selectedAnnee, setSelectedAnnee] = useState(initialAnnee)
|
||||||
|
const [anneesList, setAnneesList] = useState([])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (initialFilter) {
|
if (initialFilter) {
|
||||||
setSelectedNiveau(initialFilter)
|
setSelectedNiveau(initialFilter)
|
||||||
FilterData({ target: { value: initialFilter } }) // applique le filtre initial
|
|
||||||
}
|
}
|
||||||
}, [initialFilter])
|
if (initialAnnee) {
|
||||||
|
setSelectedAnnee(initialAnnee)
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* hook for displaying the students
|
* hook for displaying the students
|
||||||
*/
|
*/
|
||||||
const [allEtudiants, setAllEtudiants] = useState([])
|
const [allEtudiants, setAllEtudiants] = useState([])
|
||||||
const [etudiants, setEtudiants] = useState([])
|
const [etudiants, setEtudiants] = useState([])
|
||||||
const [notes, setNotes] = useState([])
|
const [notes, setNotes] = useState([])
|
||||||
|
|
||||||
|
// Charger la liste des années scolaires disponibles
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
window.etudiants.getEtudiants().then((response) => {
|
window.anneescolaire.getAnneeScolaire().then((response) => {
|
||||||
setAllEtudiants(response)
|
setAnneesList(response || [])
|
||||||
|
const currentYear = (response || []).find(a => a.is_current === 1 || a.is_current === true)
|
||||||
if (selectedNiveau && selectedNiveau !== '') {
|
if (currentYear) {
|
||||||
setEtudiants(response.filter(e => e.niveau === selectedNiveau))
|
setSelectedAnnee(currentYear.code)
|
||||||
} else {
|
localStorage.setItem('selectedAnnee', currentYear.code)
|
||||||
setEtudiants(response)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
// 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) => {
|
window.notes.getMoyenneVerify().then((response) => {
|
||||||
setNotes(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(() => {
|
useEffect(() => {
|
||||||
const savedFilters = localStorage.getItem('datagridFilters')
|
const savedFilters = localStorage.getItem('datagridFilters')
|
||||||
@ -522,7 +565,7 @@ const Student = () => {
|
|||||||
|
|
||||||
// Ensure that the array is flat (not wrapped in another array)
|
// Ensure that the array is flat (not wrapped in another array)
|
||||||
const dataRow = etudiants.map((etudiant) => ({
|
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,
|
nom: etudiant.nom,
|
||||||
prenom: etudiant.prenom,
|
prenom: etudiant.prenom,
|
||||||
niveau: etudiant.niveau,
|
niveau: etudiant.niveau,
|
||||||
@ -544,7 +587,7 @@ const Student = () => {
|
|||||||
mention_id: etudiant.mention_id,
|
mention_id: etudiant.mention_id,
|
||||||
mentionUnite: etudiant.mentionUnite,
|
mentionUnite: etudiant.mentionUnite,
|
||||||
nomMention: etudiant.nomMention,
|
nomMention: etudiant.nomMention,
|
||||||
action: etudiant.id // Ensure this is a valid URL for the image
|
action: etudiant.id
|
||||||
}))
|
}))
|
||||||
|
|
||||||
function comparestatut(statutID) {
|
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 FilterData = (e) => {
|
||||||
const niveau = e.target.value
|
const niveau = e.target.value
|
||||||
setSelectedNiveau(niveau)
|
setSelectedNiveau(niveau)
|
||||||
|
localStorage.setItem('selectedNiveau', niveau)
|
||||||
if (niveau === '') {
|
setPaginationModel(prev => ({ ...prev, page: 0 }))
|
||||||
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 }))
|
setPaginationModel(prev => ({ ...prev, page: 0 }))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -664,12 +712,42 @@ const Student = () => {
|
|||||||
</div>
|
</div>
|
||||||
{/* bare des filtre */}
|
{/* bare des filtre */}
|
||||||
<div className={classeHome.container}>
|
<div className={classeHome.container}>
|
||||||
{/* filtre par niveau */}
|
{/* filtre par année scolaire + niveau */}
|
||||||
<div style={{ width: '100%', textAlign: 'right' }}>
|
<div style={{ width: '100%', textAlign: 'right', display: 'flex', justifyContent: 'flex-end', alignItems: 'center' }}>
|
||||||
|
{/* filtre par année scolaire */}
|
||||||
<FormControl
|
<FormControl
|
||||||
sx={{
|
sx={{
|
||||||
m: 1,
|
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': {
|
'& .MuiOutlinedInput-root': {
|
||||||
'&:hover fieldset': {
|
'&:hover fieldset': {
|
||||||
borderColor: '#ff9800' // Set the border color on hover
|
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 classe from '../assets/AllStyleComponents.module.css'
|
||||||
import classeHome from '../assets/Home.module.css'
|
import classeHome from '../assets/Home.module.css'
|
||||||
import Paper from '@mui/material/Paper'
|
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 { IoMdReturnRight } from 'react-icons/io'
|
||||||
import AjoutTranche from './AjoutTranche'
|
import AjoutTranche from './AjoutTranche'
|
||||||
import { Tooltip } from 'react-tooltip'
|
import { Tooltip } from 'react-tooltip'
|
||||||
import { FaPenToSquare } from 'react-icons/fa6'
|
|
||||||
import { FaTrash } from 'react-icons/fa'
|
import { FaTrash } from 'react-icons/fa'
|
||||||
|
import { FaPenToSquare } from 'react-icons/fa6'
|
||||||
|
import { MdPayment } from 'react-icons/md'
|
||||||
import UpdateTranche from './UpdateTranche'
|
import UpdateTranche from './UpdateTranche'
|
||||||
import DeleteTranche from './DeleteTranche'
|
import warning from '../assets/warning.svg'
|
||||||
|
import success from '../assets/success.svg'
|
||||||
|
|
||||||
const TrancheEcolage = () => {
|
const TrancheEcolage = () => {
|
||||||
const { id } = useParams()
|
const { id } = useParams()
|
||||||
const [tranche, setTranche] = useState([])
|
const [tranches, setTranches] = useState([])
|
||||||
const [etudiant, setEtudiant] = useState({})
|
const [etudiant, setEtudiant] = useState({})
|
||||||
|
const [montantConfig, setMontantConfig] = useState(null)
|
||||||
|
|
||||||
useEffect(() => {
|
const loadData = () => {
|
||||||
window.etudiants.getTranche({ id }).then((response) => {
|
window.etudiants.getTranche({ id }).then((response) => {
|
||||||
setTranche(response)
|
setTranches(response || [])
|
||||||
})
|
})
|
||||||
|
|
||||||
window.etudiants.getSingle({ id }).then((response) => {
|
window.etudiants.getSingle({ id }).then((response) => {
|
||||||
setEtudiant(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 [isSubmited, setIsSubmited] = useState(false)
|
||||||
|
|
||||||
const handleFormSubmit = (status) => {
|
const handleFormSubmit = (status) => {
|
||||||
setIsSubmited(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(() => {
|
useEffect(() => {
|
||||||
if (isSubmited) {
|
if (isSubmited) {
|
||||||
window.etudiants.getTranche({ id }).then((response) => {
|
loadData()
|
||||||
setTranche(response)
|
|
||||||
})
|
|
||||||
setIsSubmited(false)
|
setIsSubmited(false)
|
||||||
}
|
}
|
||||||
}, [isSubmited])
|
}, [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 (
|
return (
|
||||||
<div className={classe.mainHome}>
|
<div className={classe.mainHome}>
|
||||||
<AjoutTranche
|
<AjoutTranche
|
||||||
id={id}
|
id={id}
|
||||||
onClose={onCloseAdd}
|
onClose={() => setOpenAdd(false)}
|
||||||
onSubmitSuccess={handleFormSubmit}
|
onSubmitSuccess={handleFormSubmit}
|
||||||
open={openAdd}
|
open={openAdd}
|
||||||
/>
|
/>
|
||||||
|
{deleteModal()}
|
||||||
<UpdateTranche
|
<UpdateTranche
|
||||||
onClose={onCloseUpdate}
|
|
||||||
onSubmitSuccess={handleFormSubmit}
|
|
||||||
open={openUpdate}
|
open={openUpdate}
|
||||||
id={idToSend}
|
onClose={() => setOpenUpdate(false)}
|
||||||
/>
|
|
||||||
<DeleteTranche
|
|
||||||
id={idToSend2}
|
|
||||||
onClose={onCloseDelete}
|
|
||||||
onSubmitSuccess={handleFormSubmit}
|
onSubmitSuccess={handleFormSubmit}
|
||||||
open={openDelete}
|
tranche={trancheToEdit}
|
||||||
/>
|
/>
|
||||||
<div className={classeHome.header}>
|
<div className={classeHome.header}>
|
||||||
<div className={classe.h1style}>
|
<div className={classe.h1style}>
|
||||||
<div className={classeHome.blockTitle}>
|
<div className={classeHome.blockTitle}>
|
||||||
<h1>Tranche d'Ecolage</h1>
|
<h1>Frais de Formation</h1>
|
||||||
<div style={{ display: 'flex', gap: '10px' }}>
|
<div style={{ display: 'flex', gap: '10px' }}>
|
||||||
<Link to={'#'} onClick={openAddFunction}>
|
<Link to={'#'} onClick={() => setOpenAdd(true)}>
|
||||||
<Button color="warning" variant="contained">
|
<Button color="warning" variant="contained">
|
||||||
Ajouter
|
<MdPayment style={{ fontSize: '20px', marginRight: 5 }} /> Ajouter un paiement
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
<Link to={'#'} onClick={() => window.history.back()}>
|
<Link to={'#'} onClick={() => window.history.back()}>
|
||||||
@ -106,83 +140,111 @@ const TrancheEcolage = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={classeHome.boxEtudiantsCard}>
|
<div className={classeHome.boxEtudiantsCard}>
|
||||||
<Paper
|
<Paper sx={{ height: 'auto', width: '100%', display: 'flex', padding: '2%' }}>
|
||||||
sx={{
|
<table className="table table-bordered table-striped text-center shadow-sm">
|
||||||
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">
|
|
||||||
<thead className="table-secondary">
|
<thead className="table-secondary">
|
||||||
<tr>
|
<tr>
|
||||||
<td colSpan={4} className="py-3">
|
<td colSpan={8} className="py-3">
|
||||||
<h6>
|
<h6>
|
||||||
Evolution d'écolage de {etudiant.nom} {etudiant.prenom}
|
Evolution d'ecolage de <b>{etudiant.nom} {etudiant.prenom}</b> - N°: {etudiant.num_inscription}
|
||||||
</h6>
|
</h6>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Tranche N°</th>
|
<th>Annee scolaire</th>
|
||||||
<th>Désignation</th>
|
<th>Tranche 1 - N° Bordereau</th>
|
||||||
<th>Montant</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>
|
<th>Action</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{tranche.map((tranch, index) => (
|
{tranches.length === 0 ? (
|
||||||
<tr key={tranch.id}>
|
<tr>
|
||||||
<td>{index + 1}</td>
|
<td colSpan={8} style={{ color: 'gray', padding: '20px' }}>Aucun paiement enregistre</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>
|
|
||||||
</tr>
|
</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>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|||||||
@ -6,38 +6,30 @@ import {
|
|||||||
DialogTitle,
|
DialogTitle,
|
||||||
TextField,
|
TextField,
|
||||||
Button,
|
Button,
|
||||||
Autocomplete,
|
|
||||||
InputAdornment,
|
InputAdornment,
|
||||||
Box,
|
Box,
|
||||||
Grid
|
Grid
|
||||||
} from '@mui/material'
|
} 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({
|
const [formData, setFormData] = useState({
|
||||||
id: id,
|
id: '',
|
||||||
tranchename: '',
|
tranche1_montant: '',
|
||||||
montant: ''
|
tranche1_bordereau: '',
|
||||||
|
tranche2_montant: '',
|
||||||
|
tranche2_bordereau: ''
|
||||||
})
|
})
|
||||||
const [tranche, setTranche] = useState([])
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (id !== null) {
|
if (tranche) {
|
||||||
window.etudiants.getSingleTranche({ id }).then((response) => {
|
|
||||||
setTranche(response)
|
|
||||||
})
|
|
||||||
setFormData({
|
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])
|
}, [tranche])
|
||||||
|
|
||||||
const handleChange = (e) => {
|
const handleChange = (e) => {
|
||||||
@ -47,64 +39,81 @@ const UpdateTranche = ({ open, onClose, onSubmitSuccess, id }) => {
|
|||||||
|
|
||||||
const handleSubmit = async (e) => {
|
const handleSubmit = async (e) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
let response = await window.etudiants.updateTranche(formData)
|
const response = await window.etudiants.updateTranche(formData)
|
||||||
|
if (response.success) {
|
||||||
if (response.changes) {
|
|
||||||
onClose()
|
onClose()
|
||||||
onSubmitSuccess(true)
|
onSubmitSuccess(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onClose={onClose}>
|
<Dialog open={open} onClose={onClose} maxWidth="sm" fullWidth>
|
||||||
<form action="" onSubmit={handleSubmit}>
|
<form onSubmit={handleSubmit}>
|
||||||
<DialogTitle>Ajout tranche</DialogTitle>
|
<DialogTitle>Modifier le paiement</DialogTitle>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<Box sx={{ flexGrow: 1 }}>
|
<Box sx={{ flexGrow: 1, mt: 1 }}>
|
||||||
<Grid container spacing={2}>
|
<Grid container spacing={2}>
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<b>Tranche 1</b>
|
||||||
|
</Grid>
|
||||||
<Grid item xs={12} sm={6}>
|
<Grid item xs={12} sm={6}>
|
||||||
<TextField
|
<TextField
|
||||||
autoFocus
|
name="tranche1_bordereau"
|
||||||
margin="normal"
|
label="N° Bordereau"
|
||||||
required
|
|
||||||
name="tranchename"
|
|
||||||
label="Désignation"
|
|
||||||
type="text"
|
type="text"
|
||||||
fullWidth
|
fullWidth
|
||||||
placeholder="Tranche 1"
|
size="small"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
value={formData.tranchename}
|
value={formData.tranche1_bordereau}
|
||||||
color="warning"
|
color="warning"
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
InputProps={{
|
|
||||||
startAdornment: (
|
|
||||||
<InputAdornment position="start">
|
|
||||||
<MdLabelImportantOutline />
|
|
||||||
</InputAdornment>
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={12} sm={6}>
|
<Grid item xs={12} sm={6}>
|
||||||
<TextField
|
<TextField
|
||||||
autoFocus
|
name="tranche1_montant"
|
||||||
margin="normal"
|
|
||||||
required
|
|
||||||
name="montant"
|
|
||||||
label="Montant"
|
label="Montant"
|
||||||
type="number"
|
type="number"
|
||||||
fullWidth
|
fullWidth
|
||||||
placeholder="Montant"
|
size="small"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
value={formData.montant}
|
value={formData.tranche1_montant}
|
||||||
color="warning"
|
color="warning"
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
InputProps={{
|
InputProps={{
|
||||||
startAdornment: (
|
startAdornment: <InputAdornment position="start">Ar</InputAdornment>
|
||||||
<InputAdornment position="start">
|
}}
|
||||||
<MdLabelImportantOutline />
|
/>
|
||||||
</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>
|
</Grid>
|
||||||
@ -112,12 +121,8 @@ const UpdateTranche = ({ open, onClose, onSubmitSuccess, id }) => {
|
|||||||
</Box>
|
</Box>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={onClose} color="error">
|
<Button onClick={onClose} color="error">Annuler</Button>
|
||||||
Annuler
|
<Button type="submit" color="warning" variant="contained">Enregistrer</Button>
|
||||||
</Button>
|
|
||||||
<Button type="submit" color="warning">
|
|
||||||
Soumettre
|
|
||||||
</Button>
|
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</form>
|
</form>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|||||||
@ -32,10 +32,14 @@ function nextLevel(niveau) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const descisionJury = (notes, niveau) => {
|
export const descisionJury = (notes, niveau, systeme) => {
|
||||||
if (notes >= 10) {
|
if (!systeme) return ''
|
||||||
return `Admis en ${nextLevel(niveau)}`
|
|
||||||
|
if (notes >= systeme.admis) {
|
||||||
|
return `Admis`
|
||||||
|
} else if (notes > systeme.renvoyer) {
|
||||||
|
return `Redoublant`
|
||||||
} else {
|
} else {
|
||||||
return 'Vous redoublez'
|
return `Renvoyé`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user