Stephane 4 months ago
parent
commit
9e9bc98548
  1. 27
      electron.vite.config.1754936034364.mjs
  2. 922
      src/main/backup.js
  3. 903
      src/main/index.js
  4. 212
      src/preload/index.backup.js
  5. 208
      src/preload/index.js
  6. 17
      src/renderer/src/assets/AllStyleComponents.module.css
  7. 283
      src/renderer/src/components/AddAnneeScolaire.jsx
  8. 317
      src/renderer/src/components/AddNotes.jsx
  9. 1070
      src/renderer/src/components/AddStudent.jsx
  10. 319
      src/renderer/src/components/AnneeScolaire.jsx
  11. 68
      src/renderer/src/components/Apropos.jsx
  12. 527
      src/renderer/src/components/ExportEtudiants.jsx
  13. 564
      src/renderer/src/components/Matieres.jsx
  14. 220
      src/renderer/src/components/ModalExportFichr.jsx
  15. 132
      src/renderer/src/components/NiveauMatiere.jsx
  16. 130
      src/renderer/src/components/ParcourMatiere.jsx
  17. 199
      src/renderer/src/components/Parcours.jsx
  18. 218
      src/renderer/src/components/Resultat.jsx
  19. 283
      src/renderer/src/components/Sidenav.jsx
  20. 268
      src/renderer/src/components/SingleAnneeScolaire.jsx
  21. 1156
      src/renderer/src/components/SingleEtudiant.jsx
  22. 584
      src/renderer/src/components/Student.jsx
  23. 458
      src/renderer/src/components/TesteDatagrid.jsx
  24. 127
      src/renderer/src/components/UpdateTranche.jsx
  25. 94
      src/renderer/src/components/function/GenerateFiche.js
  26. 134
      src/renderer/src/components/function/PDFEditor.js
  27. 151
      src/renderer/src/components/function/PDFEditorV2.js
  28. 54
      src/renderer/src/test/qr.html
  29. 74
      src/renderer/src/test/relever.html

27
electron.vite.config.1754936034364.mjs

@ -0,0 +1,27 @@
// electron.vite.config.mjs
import { resolve } from "path";
import { defineConfig, externalizeDepsPlugin } from "electron-vite";
import react from "@vitejs/plugin-react";
var electron_vite_config_default = defineConfig({
main: {
plugins: [externalizeDepsPlugin()]
},
preload: {
plugins: [externalizeDepsPlugin()]
},
renderer: {
resolve: {
alias: {
"@renderer": resolve("src/renderer/src")
}
},
plugins: [react()]
},
worker: {
format: "es"
// Use ES module for worker (you can also use 'iife')
}
});
export {
electron_vite_config_default as default
};

922
src/main/backup.js

@ -0,0 +1,922 @@
import { app, shell, BrowserWindow, ipcMain, Tray, Menu } from 'electron'
import { join } from 'path'
const path = require('path')
import { electronApp, optimizer, is } from '@electron-toolkit/utils'
import icon from '../../resources/logo.ico?asset' // Your tray icon file
const database = require('../../database/database2')
database
.createTables()
.then(() => database.insertDefaultAdmin())
.then(() => database.insertStatusesIfNotExist())
.catch(console.error)
const { createConfigIp, updateIPConfig } = require('../../database/Models/IpConfig')
const { importFileToDatabase } = require('../../database/import/Etudiants')
const { loginUser, forgotPassword, insertUser, updateUser } = require('../../database/Models/Users')
const {
insertEtudiant,
getSingleEtudiant,
FilterDataByNiveau,
updateEtudiant,
changePDP,
updateParcours,
createTranche,
getTranche,
updateTranche,
deleteTranche,
deleteEtudiant,
getSingleTranche
} = require('../../database/Models/Etudiants')
const {
insertNiveau,
updateNiveau,
getSingleNiveau,
deleteNiveau
} = require('../../database/Models/Niveau')
const {
insertNote,
getNote,
updateNote,
showMoyen,
getMatiereAndNote,
getNotesWithRepechToDisplay
} = require('../../database/Models/Notes')
const {
createMatiere,
getSingleMatiere,
updateMatiere,
updateMatiereNiveau,
displayMatiereFromForm,
deleteMatiere,
asygnationToMention,
getMentionMatiere,
getMentionMatiereChecked,
getSemestreMatiere,
insertUpdateMentionSemestre,
insertNewProf,
getSIngleProf,
updateProf
} = require('../../database/Models/Matieres')
const { importFileToDatabaseMatiere } = require('../../database/import/Matieres')
const { importNiveau } = require('../../database/import/Niveau')
const { updateSysteme } = require('../../database/Models/NoteSysrem')
const {
createAnneeScolaire,
deleteAnneeScolaire,
getSingleAnneScolaire,
updateAnneeScolaire,
setCurrent
} = require('../../database/Models/AnneeScolaire')
const {
createMention,
deleteMention,
getSingleMention,
updateMention
} = require('../../database/Models/Mentions')
const {
getNoteRepech,
updateNoteRepech,
showMoyenRepech
} = require('../../database/Models/NoteRepechage')
const {
updateCurrentYears,
updateStudents,
updateNessesaryTable
} = require('../../database/function/System')
const { autoUpdater } = require('electron-updater')
const { URL } = require('../../database/api/Config')
const {
insertParcour,
getSingleParcours,
deletes,
updateparcour,
parcourMatiere,
extractFiche,
getParcourMatiere
} = require('../../database/Models/Parcours')
// Declare mainWindow and tray in the global scope
let mainWindow
let tray = null
updateCurrentYears()
updateStudents()
autoUpdater.setFeedURL({
provider: 'generic',
url: `${URL}/latest` // Ensure this points to the folder containing latest.yml
})
function createWindow() {
// Create the browser window.
mainWindow = new BrowserWindow({
width: 1375,
minWidth: 1375,
height: 740,
minHeight: 740,
show: false,
autoHideMenuBar: true,
fullscreen: false,
icon: path.join(__dirname, 'resources', 'logo.ico'), // Path to your icon,
...(process.platform === 'linux' ? { icon } : {}),
webPreferences: {
preload: join(__dirname, '../preload/index.js'),
nodeIntegration: true,
contextIsolation: true,
sandbox: false
}
})
// Désactiver les raccourcis clavier
mainWindow.webContents.on('before-input-event', (event, input) => {
if (input.control || input.meta || input.alt || input.key === 'F11') {
event.preventDefault()
}
})
mainWindow.on('ready-to-show', () => {
mainWindow.maximize() // Maximiser la fenêtre
mainWindow.show()
})
mainWindow.webContents.setWindowOpenHandler((details) => {
shell.openExternal(details.url)
return { action: 'deny' }
})
// Load the appropriate URL based on environment
if (is.dev && process.env['ELECTRON_RENDERER_URL']) {
mainWindow.loadURL(process.env['ELECTRON_RENDERER_URL'])
} else {
mainWindow.loadFile(join(__dirname, '../renderer/index.html'))
}
// Handle window close (hide instead of closing)
mainWindow.on('close', (event) => {
if (!app.isQuiting) {
event.preventDefault()
mainWindow.hide() // Minimize to tray instead of closing
} else {
// Destroy the tray when quitting
if (tray) tray.destroy()
}
})
}
// Function to create the system tray
function createTray() {
const iconPath = icon // Use your icon path here
tray = new Tray(iconPath)
// Create a context menu for the tray
const contextMenu = Menu.buildFromTemplate([
{
label: 'Ouvrir',
click: () => {
mainWindow.show()
mainWindow.webContents.send('navigateToRoute', '#/') // Send the route as a string
}
},
{
label: 'A Propos',
click: () => {
mainWindow.show()
mainWindow.webContents.send('navigateToRoute', '#/apropos') // Send the route as a string
}
},
{
label: 'Quit',
click: () => {
// Clear localStorage in the renderer process
mainWindow.webContents
.executeJavaScript('localStorage.removeItem("ACCESS_TOKEN");')
.then(() => {
console.log('localStorage cleared.')
// Ensure the app quits entirely
if (tray) {
app.quit()
tray.destroy()
// if (app.quit()) {
// tray.destroy()
// }
} // Quit the app
})
.catch((err) => {
console.error('Error clearing localStorage:', err)
// Quit the app even if clearing fails
if (tray) {
app.quit()
tray.destroy()
// if (app.quit()) {
// tray.destroy()
// }
}
})
}
}
])
tray.setToolTip('My Electron App')
tray.setContextMenu(contextMenu)
// Show the app when the tray icon is clicked
tray.on('click', () => {
mainWindow.show()
})
}
app.whenReady().then(() => {
electronApp.setAppUserModelId('com.electron')
autoUpdater.checkForUpdatesAndNotify()
app.on('browser-window-created', (_, window) => {
optimizer.watchWindowShortcuts(window)
})
createWindow()
createTray() // Create the tray icon
app.on('activate', function () {
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})
})
// When an update is available
autoUpdater.on('update-available', () => {
dialog.showMessageBox({
type: 'info',
title: 'Mise à jour disponible',
message: 'Une nouvelle version est disponible. Téléchargement en cours...'
})
})
// When the update is downloaded
autoUpdater.on('update-downloaded', (info) => {
dialog
.showMessageBox({
type: 'info',
title: 'Mise à jour prête',
message: `La version ${info.version} a été téléchargée. Redémarrer maintenant ?`,
buttons: ['Redémarrer', 'Plus tard']
})
.then((result) => {
if (result.response === 0) {
autoUpdater.quitAndInstall()
}
})
})
// If an error occurs
autoUpdater.on('error', (error) => {
dialog.showErrorBox('Update Error', error == null ? 'Unknown' : error.message)
})
// Quit the app when all windows are closed, except on macOS
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit()
}
})
// In this file you can include the rest of your app"s specific main process
// code. You can also put them in separate files and require them here.
// Event for handling login
ipcMain.handle('login', async (event, credentials) => {
const { username, password } = credentials
const users = await loginUser(username, password)
if (users) {
return { success: true, user: users }
} else {
return { success: false }
}
})
// Event for handling insert other user
ipcMain.handle('insertUser', async (event, credentials) => {
const { username, email, password, roles } = credentials
const users = await insertUser(username, email, password, roles)
return users
})
// event for handlign forgot password
ipcMain.handle('forgotPassword', async (event, credentials) => {
const { email, password, passwordConfirmation } = credentials
const updated = await forgotPassword(email, password, passwordConfirmation)
if (updated) {
return updated
}
})
// event for updating users
ipcMain.handle('updateUsers', async (event, credentials) => {
const { username, newUsername, email, newEmail, passwordVerif, password, id } = credentials
const update = await updateUser(newUsername, newEmail, password, id)
return update
})
// event for quit app
ipcMain.handle('quit', async () => {
app.quit()
})
// event for minimizing the app
ipcMain.handle('minimize', async () => {
if (mainWindow) {
mainWindow.minimize()
}
})
// event for insert etudiants
ipcMain.handle('insertEtudiant', async (event, credentials) => {
const {
nom,
prenom,
photos,
date_de_naissances,
niveau,
annee_scolaire,
status,
num_inscription,
mention_id,
sexe,
nationaliter,
cin,
date_delivrence,
annee_bacc,
serie,
boursier,
domaine,
contact,
parcours
} = credentials
const insert = await insertEtudiant(
nom,
prenom,
photos,
date_de_naissances,
niveau,
annee_scolaire,
status,
num_inscription,
mention_id,
sexe,
nationaliter,
cin,
date_delivrence,
annee_bacc,
serie,
boursier,
domaine,
contact,
parcours
)
return insert
})
// event for fetching single
ipcMain.handle('getByNiveau', async (event, credentials) => {
const { niveau } = credentials
const getSingle = await FilterDataByNiveau(niveau)
return getSingle
})
// event for fetching single
ipcMain.handle('single', async (event, credentials) => {
const { id } = credentials
const getSingle = await getSingleEtudiant(id)
return getSingle
})
// event for inserting niveau
ipcMain.handle('insertNiveau', async (event, credentials) => {
const { nom } = credentials
const insert = await insertNiveau(nom)
return insert
})
// event for updating etudiants
ipcMain.handle('updateETudiants', async (event, credentials) => {
const {
nom,
prenom,
photos,
date_de_naissances,
niveau,
annee_scolaire,
status,
mention_id,
num_inscription,
id,
sexe,
nationalite,
cin,
date_delivrance,
annee_bacc,
serie,
boursier,
domaine,
contact,
parcours
} = credentials
const updating = await updateEtudiant(
nom,
prenom,
photos,
date_de_naissances,
niveau,
annee_scolaire,
status,
mention_id,
num_inscription,
id,
sexe,
nationalite,
cin,
date_delivrance,
annee_bacc,
serie,
boursier,
domaine,
contact,
parcours
)
return updating
})
// event for updating etudiants pdp
ipcMain.handle('updateETudiantsPDP', async (event, credentials) => {
const { pdp, id } = credentials
const updating = await changePDP(pdp, id)
return updating
})
// event for adding notes
ipcMain.handle('insertNote', async (event, credentials) => {
const { etudiant_id, etudiant_niveau, mention_id, formData, annee_scolaire } = credentials
const insert = await insertNote(
etudiant_id,
etudiant_niveau,
mention_id,
formData,
annee_scolaire
)
return insert
})
// event for get single note
ipcMain.handle('getSingleNote', async (event, credentials) => {
const { id, niveau, mention_id } = credentials
const get = await getNote(id, niveau, mention_id)
return get
})
// event for get single note repech
ipcMain.handle('getNotesRepech', async (event, credentials) => {
const { id, niveau, mention_id } = credentials
const get = await getNoteRepech(id, niveau, mention_id)
return get
})
// event for updating note
ipcMain.handle('updatetNote', async (event, credentials) => {
const { formData, niveau, id, mention_id, annee_scolaire } = credentials
const update = await updateNote(formData, niveau, id, mention_id, annee_scolaire)
return update
})
// event for updating note repech
ipcMain.handle('updatetNoteRepech', async (event, credentials) => {
const { formData2, niveau, id } = credentials
const update = await updateNoteRepech(formData2, niveau, id)
return update
})
// event to get single matiere
ipcMain.handle('getMatiereByID', async (event, credentials) => {
const { id } = credentials
const matiere = await getSingleMatiere(id)
return matiere
})
// event for updating matiere
ipcMain.handle('updateMatiere', async (event, credentials) => {
const { nom, credit, uniter, ue, id } = credentials
const update = await updateMatiere(nom, id, credit, uniter, ue)
return update
})
ipcMain.handle('updateMatiereNiveau', async (event, credentials) => {
// credentials = { niveau_id, id }
const update = await updateMatiereNiveau(credentials) // ✅ on passe id + niveau_id
return update
})
// event for importExcel
ipcMain.handle('importexcel', async (event, credentials) => {
const files = credentials
console.log(files)
const insert = await importFileToDatabase(files)
return insert
})
// event for udatign a single niveau
ipcMain.handle('updateSingleNiveau', async (event, credentials) => {
const { nom, id } = credentials
const update = updateNiveau(nom, id)
return update
})
// event to get single niveau
ipcMain.handle('singleNiveau', async (event, credentials) => {
const { id } = credentials
const update = getSingleNiveau(id)
return update
})
// event for creating matiere
ipcMain.handle('createMatiere', async (event, credentials) => {
const { nom, credit, uniter, ue } = credentials
const create = createMatiere(nom, credit, uniter, ue)
return create
})
// event for import excel matiere
ipcMain.handle('importExcelMatiere', async (event, credentials) => {
const files = credentials
console.log(files)
const insert = await importFileToDatabaseMatiere(files)
return insert
})
// event for import excel niveau
ipcMain.handle('importNiveau', async (event, credentials) => {
const files = credentials
console.log(files)
const insert = await importNiveau(files)
return insert
})
// event for updating note systeme
ipcMain.handle('updateNoteSysteme', async (event, credentials) => {
const { id, admis, redouble, renvoyer } = credentials
const update = updateSysteme(id, admis, redouble, renvoyer)
return update
})
// event for updating note systeme
ipcMain.handle('createAnneeScolaire', async (event, credentials) => {
const { code, debut, fin } = credentials
const create = createAnneeScolaire(code, debut, fin)
return create
})
ipcMain.handle('getMoyene', async (event, credentials) => {
const { niveau, scolaire } = credentials
console.log('index.js', niveau, scolaire)
const create = showMoyen(niveau, scolaire)
return create
})
ipcMain.handle('getMoyenneRepech', async (event, credentials) => {
const { niveau, scolaire } = credentials
console.log('index.js', niveau, scolaire)
const create = showMoyenRepech(niveau, scolaire)
return create
})
ipcMain.handle('noteMatiere', async (event, credentials) => {
const { id, niveau, annee_scolaire } = credentials
const get = getMatiereAndNote(id, niveau, annee_scolaire)
return get
})
ipcMain.handle('displayMatiereFromForm', async (event, credentials) => {
const { niveau, mention_id, parcours } = credentials
const get = displayMatiereFromForm(niveau, mention_id, parcours)
return get
})
ipcMain.handle('createMention', async (event, credentials) => {
const { nom, uniter } = credentials
const get = createMention(nom, uniter)
return get
})
ipcMain.handle('getSingleMention', async (event, credentials) => {
const { id } = credentials
const get = getSingleMention(id)
return get
})
ipcMain.handle('updateMention', async (event, credentials) => {
const { nom, uniter, id } = credentials
const get = updateMention(nom, uniter, id)
return get
})
ipcMain.handle('deleteMention', async (event, credentials) => {
const { id } = credentials
const get = deleteMention(id)
return get
})
ipcMain.handle('deleteNiveaus', async (event, credentials) => {
const { id } = credentials
const get = deleteNiveau(id)
return get
})
ipcMain.handle('deleteMatiere', async (event, id) => {
return await deleteMatiere(id);
});
ipcMain.handle('asign', async (event, credentials) => {
const { formData, id } = credentials
// console.log(formData, id);
const get = asygnationToMention(formData, id)
return get
})
ipcMain.handle('asignSemestre', async (event, credentials) => {
const { id } = credentials
const get = getMentionMatiereChecked(id)
return get
})
ipcMain.handle('getAsign', async (event, credentials) => {
const { id } = credentials
// console.log(formData, id);
const get = getMentionMatiere(id)
return get
})
ipcMain.handle('deleteAnneeScolaire', async (event, credentials) => {
const { id } = credentials
// console.log(formData, id);
const get = deleteAnneeScolaire(id)
return get
})
ipcMain.handle('getSemestreMatiere', async (event, credentials) => {
const { id } = credentials
// console.log(formData, id);
const get = getSemestreMatiere(id)
return get
})
ipcMain.handle('insertUpdateMentionSemestre', async (event, credentials) => {
const { id, selectedSemestres } = credentials
// console.log(formData, id);
const get = insertUpdateMentionSemestre(id, selectedSemestres)
return get
})
ipcMain.handle('getSingleAnneeScolaire', async (event, credentials) => {
const { id } = credentials
// console.log(formData, id);
const get = getSingleAnneScolaire(id)
return get
})
ipcMain.handle('updateAnneeScolaire', async (event, credentials) => {
const { code, debut, fin, id } = credentials
// console.log(formData, id);
const get = updateAnneeScolaire(id, code, debut, fin)
return get
})
ipcMain.handle('setCurrent', async (event, credentials) => {
const { id } = credentials
// console.log(formData, id);
const get = setCurrent(id)
return get
})
ipcMain.handle('noteRelerer', async (event, credentials) => {
const { id, anneescolaire, niveau } = credentials
// console.log(formData, id);
const get = getNotesWithRepechToDisplay(id, anneescolaire, niveau)
return get
})
ipcMain.handle('updateNessesary', async (event, credentials) => {
const { id, multiplicateur } = credentials
// console.log(formData, id);
const get = updateNessesaryTable(id, multiplicateur)
return get
})
ipcMain.handle('insertProf', async (event, credentials) => {
const { nom_enseignant, prenom_enseignant, contact, date, matiere_id } = credentials
// console.log(formData, id);
const get = insertNewProf(matiere_id, nom_enseignant, prenom_enseignant, contact, date)
return get
})
ipcMain.handle('insertParcours', async (event, credentials) => {
const { nom, uniter, mention_id } = credentials
// console.log(formData, id);
const get = insertParcour(nom, uniter, mention_id)
return get
})
ipcMain.handle('getSingleParcours', async (event, credentials) => {
const { id } = credentials
// console.log(formData, id);
const get = getSingleParcours(id)
return get
})
ipcMain.handle('deleteParcours', async (event, credentials) => {
const { id } = credentials
// console.log(formData, id);
const get = deletes(id)
return get
})
ipcMain.handle('updateParcours', async (event, credentials) => {
const { nom, uniter, mention_id, id } = credentials
// console.log(formData, id);
const get = updateparcour(id, nom, uniter, mention_id)
return get
})
ipcMain.handle('parcourMatiere', async (event, credentials) => {
const { matiere_id, parcour_id } = credentials
// console.log(formData, id);
const get = parcourMatiere(matiere_id, parcour_id)
return get
})
ipcMain.handle('getSingleProf', async (event, credentials) => {
const { id } = credentials
// console.log(formData, id);
const get = getSIngleProf(id)
return get
})
ipcMain.handle('updateProf', async (event, credentials) => {
const { nom_enseignant, prenom_enseignant, contact, date, matiere_id } = credentials
// console.log(formData, id);
const get = updateProf(matiere_id, nom_enseignant, prenom_enseignant, contact, date)
return get
})
ipcMain.handle('extractFiches', async (event, credentials) => {
const { matiere_id } = credentials
// console.log(formData, id);
const get = extractFiche(matiere_id)
return get
})
ipcMain.handle('getParcourMatiere', async (event, credentials) => {
const { matiere_id } = credentials
// console.log(formData, id);
const get = getParcourMatiere(matiere_id)
return get
})
ipcMain.handle('changeParcours', async (event, credentials) => {
const { parcours, user_id } = credentials
// console.log(formData, id);
const get = updateParcours(parcours, user_id)
return get
})
ipcMain.handle('createTranche', async (event, credentials) => {
const { etudiant_id, tranchename, montant } = credentials
// console.log(formData, id);
const get = createTranche(etudiant_id, tranchename, montant)
return get
})
ipcMain.handle('getTranche', async (event, credentials) => {
const { id } = credentials
// console.log(formData, id);
const get = getTranche(id)
return get
})
ipcMain.handle('updateTranche', async (event, credentials) => {
const { id, tranchename, montant } = credentials
// console.log(formData, id);
const get = updateTranche(id, tranchename, montant)
return get
})
ipcMain.handle('deleteTranche', async (event, credentials) => {
const { id } = credentials
console.log(id)
const get = deleteTranche(id)
return get
})
ipcMain.handle('deleteEtudiant', async (event, id) => {
return await deleteEtudiant(id);
});
ipcMain.handle('getSingleTranche', async (event, credentials) => {
const { id } = credentials
// console.log(formData, id);
const get = getSingleTranche(id)
return get
})
ipcMain.handle('createIPConfig', async (event, credentials) => {
const { ipname } = credentials
// console.log(formData, id);
const get = createConfigIp(ipname)
return get
})
ipcMain.handle('updateIPConfig', async (event, credentials) => {
const { id, ipname } = credentials
// console.log(formData, id);
const get = updateIPConfig(id, ipname)
return get
})

903
src/main/index.js

@ -0,0 +1,903 @@
import { app, shell, BrowserWindow, ipcMain, Tray, Menu } from 'electron'
import { join } from 'path'
const path = require('path')
import { electronApp, optimizer, is } from '@electron-toolkit/utils'
import icon from '../../resources/logo.ico?asset' // Your tray icon file
const database = require('../../database/database')
database
.createTables()
.then(() => database.insertDefaultAdmin())
.then(() => database.insertStatusesIfNotExist())
.catch(console.error)
const { loginUsers, insertUser, updateUser } = require('../../database/Models/Users')
const { createConfigIp, updateIPConfig } = require('../../database/Models/IpConfig')
const { importFileToDatabase } = require('../../database/import/Etudiants')
const {
insertEtudiant,
getSingleEtudiant,
FilterDataByNiveau,
updateEtudiant,
changePDP,
updateParcours,
createTranche,
getTranche,
updateTranche,
deleteTranche,
getSingleTranche
} = require('../../database/Models/Etudiants')
const {
insertNiveau,
updateNiveau,
getSingleNiveau,
deleteNiveau
} = require('../../database/Models/Niveau')
const {
insertNote,
getNote,
updateNote,
showMoyen,
getMatiereAndNote,
getNotesWithRepechToDisplay
} = require('../../database/Models/Notes')
const {
createMatiere,
getSingleMatiere,
updateMatiere,
updateMatiereNiveau,
displayMatiereFromForm,
deleteMatiere,
deleteEtudiant,
asygnationToMention,
getMentionMatiere,
getMentionMatiereChecked,
getSemestreMatiere,
insertUpdateMentionSemestre,
insertNewProf,
getSIngleProf,
updateProf
} = require('../../database/Models/Matieres')
const { importFileToDatabaseMatiere } = require('../../database/import/Matieres')
const { importNiveau } = require('../../database/import/Niveau')
const { updateSysteme } = require('../../database/Models/NoteSysrem')
const {
createAnneeScolaire,
deleteAnneeScolaire,
getSingleAnneScolaire,
updateAnneeScolaire,
setCurrent
} = require('../../database/Models/AnneeScolaire')
const {
createMention,
deleteMention,
getSingleMention,
updateMention
} = require('../../database/Models/Mentions')
const {
getNoteRepech,
updateNoteRepech,
showMoyenRepech
} = require('../../database/Models/NoteRepechage')
const {
updateCurrentYears,
updateStudents,
updateNessesaryTable
} = require('../../database/function/System')
const { autoUpdater } = require('electron-updater')
const { URL } = require('../../database/api/Config')
const {
insertParcour,
getSingleParcours,
deletes,
updateparcour,
parcourMatiere,
extractFiche,
getParcourMatiere
} = require('../../database/Models/Parcours')
// Declare mainWindow and tray in the global scope
let mainWindow
let tray = null
updateCurrentYears()
updateStudents()
autoUpdater.setFeedURL({
provider: 'generic',
url: `${URL}/latest` // Ensure this points to the folder containing latest.yml
})
function createWindow() {
// Create the browser window.
mainWindow = new BrowserWindow({
width: 1375,
minWidth: 1375,
height: 740,
minHeight: 740,
show: false,
autoHideMenuBar: true,
fullscreen: false,
icon: path.join(__dirname, 'resources', 'logo.ico'), // Path to your icon,
...(process.platform === 'linux' ? { icon } : {}),
webPreferences: {
preload: join(__dirname, '../preload/index.js'),
nodeIntegration: true,
contextIsolation: true,
sandbox: false
}
})
// Désactiver les raccourcis clavier
mainWindow.webContents.on('before-input-event', (event, input) => {
if (input.control || input.meta || input.alt || input.key === 'F11') {
event.preventDefault()
}
})
mainWindow.on('ready-to-show', () => {
mainWindow.maximize() // Maximiser la fenêtre
mainWindow.show()
})
mainWindow.webContents.setWindowOpenHandler((details) => {
shell.openExternal(details.url)
return { action: 'deny' }
})
// Load the appropriate URL based on environment
if (is.dev && process.env['ELECTRON_RENDERER_URL']) {
mainWindow.loadURL(process.env['ELECTRON_RENDERER_URL'])
} else {
mainWindow.loadFile(join(__dirname, '../renderer/index.html'))
}
// Handle window close (hide instead of closing)
mainWindow.on('close', (event) => {
if (!app.isQuiting) {
event.preventDefault()
mainWindow.hide() // Minimize to tray instead of closing
} else {
// Destroy the tray when quitting
if (tray) tray.destroy()
}
})
}
// Function to create the system tray
function createTray() {
const iconPath = icon // Use your icon path here
tray = new Tray(iconPath)
// Create a context menu for the tray
const contextMenu = Menu.buildFromTemplate([
{
label: 'Ouvrir',
click: () => {
mainWindow.show()
mainWindow.webContents.send('navigateToRoute', '#/') // Send the route as a string
}
},
{
label: 'A Propos',
click: () => {
mainWindow.show()
mainWindow.webContents.send('navigateToRoute', '#/apropos') // Send the route as a string
}
},
{
label: 'Quit',
click: () => {
// Clear localStorage in the renderer process
mainWindow.webContents
.executeJavaScript('localStorage.removeItem("ACCESS_TOKEN");')
.then(() => {
console.log('localStorage cleared.')
// Ensure the app quits entirely
if (tray) {
app.quit()
tray.destroy()
// if (app.quit()) {
// tray.destroy()
// }
} // Quit the app
})
.catch((err) => {
console.error('Error clearing localStorage:', err)
// Quit the app even if clearing fails
if (tray) {
app.quit()
tray.destroy()
// if (app.quit()) {
// tray.destroy()
// }
}
})
}
}
])
tray.setToolTip('My Electron App')
tray.setContextMenu(contextMenu)
// Show the app when the tray icon is clicked
tray.on('click', () => {
mainWindow.show()
})
}
app.whenReady().then(() => {
electronApp.setAppUserModelId('com.electron')
autoUpdater.checkForUpdatesAndNotify()
app.on('browser-window-created', (_, window) => {
optimizer.watchWindowShortcuts(window)
})
createWindow()
createTray() // Create the tray icon
app.on('activate', function () {
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})
})
// When an update is available
autoUpdater.on('update-available', () => {
dialog.showMessageBox({
type: 'info',
title: 'Mise à jour disponible',
message: 'Une nouvelle version est disponible. Téléchargement en cours...'
})
})
// When the update is downloaded
autoUpdater.on('update-downloaded', (info) => {
dialog
.showMessageBox({
type: 'info',
title: 'Mise à jour prête',
message: `La version ${info.version} a été téléchargée. Redémarrer maintenant ?`,
buttons: ['Redémarrer', 'Plus tard']
})
.then((result) => {
if (result.response === 0) {
autoUpdater.quitAndInstall()
}
})
})
// If an error occurs
autoUpdater.on('error', (error) => {
dialog.showErrorBox('Update Error', error == null ? 'Unknown' : error.message)
})
// Quit the app when all windows are closed, except on macOS
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit()
}
})
// In this file you can include the rest of your app"s specific main process
// code. You can also put them in separate files and require them here.
// event for quit app
ipcMain.handle('quit', async () => {
app.quit()
})
// event for minimizing the app
ipcMain.handle('minimize', async () => {
if (mainWindow) {
mainWindow.minimize()
}
})
ipcMain.handle('login', async (event, credentials) => {
const { username, password } = credentials
// Pass username and password to loginUsers
let response = await loginUsers(username, password)
return response
})
ipcMain.handle('insertUser', async (event, credentials) => {
const { username, email, password, roles } = credentials
const users = await insertUser(username, email, password, roles)
return users
})
ipcMain.handle('updateUsers', async (event, credentials) => {
const { username, email, password, id } = credentials
const update = await updateUser(username, email, password, id)
return update
})
// event for insert etudiants
ipcMain.handle('insertEtudiant', async (event, credentials) => {
const {
nom,
prenom,
photos,
date_de_naissances,
niveau,
annee_scolaire,
status,
num_inscription,
mention_id,
sexe,
nationaliter,
cin,
date_delivrence,
annee_bacc,
serie,
boursier,
domaine,
contact,
parcours
} = credentials
const insert = await insertEtudiant(
nom,
prenom,
photos,
date_de_naissances,
niveau,
annee_scolaire,
status,
num_inscription,
mention_id,
sexe,
nationaliter,
cin,
date_delivrence,
annee_bacc,
serie,
boursier,
domaine,
contact,
parcours
)
return insert
})
// event for fetching single
ipcMain.handle('getByNiveau', async (event, credentials) => {
const { niveau } = credentials
const getSingle = await FilterDataByNiveau(niveau)
return getSingle
})
// event for fetching single
ipcMain.handle('single', async (event, credentials) => {
const { id } = credentials
const getSingle = await getSingleEtudiant(id)
return getSingle
})
// event for inserting niveau
ipcMain.handle('insertNiveau', async (event, credentials) => {
const { nom } = credentials
const insert = await insertNiveau(nom)
return insert
})
// event for updating etudiants
ipcMain.handle('updateETudiants', async (event, credentials) => {
const {
nom,
prenom,
photos,
date_de_naissances,
niveau,
annee_scolaire,
status,
mention_id,
num_inscription,
id,
sexe,
nationalite,
cin,
date_delivrance,
annee_bacc,
serie,
boursier,
domaine,
contact,
parcours
} = credentials
const updating = await updateEtudiant(
nom,
prenom,
photos,
date_de_naissances,
niveau,
annee_scolaire,
status,
mention_id,
num_inscription,
id,
sexe,
nationalite,
cin,
date_delivrance,
annee_bacc,
serie,
boursier,
domaine,
contact,
parcours
)
return updating
})
// event for updating etudiants pdp
ipcMain.handle('updateETudiantsPDP', async (event, credentials) => {
const { pdp, id } = credentials
const updating = await changePDP(pdp, id)
return updating
})
// event for adding notes
ipcMain.handle('insertNote', async (event, credentials) => {
const { etudiant_id, etudiant_niveau, mention_id, formData, annee_scolaire } = credentials
const insert = await insertNote(
etudiant_id,
etudiant_niveau,
mention_id,
formData,
annee_scolaire
)
return insert
})
// event for get single note
ipcMain.handle('getSingleNote', async (event, credentials) => {
const { id, niveau, mention_id } = credentials
const get = await getNote(id, niveau, mention_id)
return get
})
// event for get single note repech
ipcMain.handle('getNotesRepech', async (event, credentials) => {
const { id, niveau, mention_id } = credentials
const get = await getNoteRepech(id, niveau, mention_id)
return get
})
// event for updating note
ipcMain.handle('updatetNote', async (event, credentials) => {
const { formData, niveau, id, mention_id, annee_scolaire } = credentials
const update = await updateNote(formData, niveau, id, mention_id, annee_scolaire)
return update
})
// event for updating note repech
ipcMain.handle('updatetNoteRepech', async (event, credentials) => {
const { formData2, niveau, id } = credentials
const update = await updateNoteRepech(formData2, niveau, id)
return update
})
// event to get single matiere
ipcMain.handle('getMatiereByID', async (event, credentials) => {
const { id } = credentials
const matiere = await getSingleMatiere(id)
return matiere
})
// // event for updating matiere
ipcMain.handle('updateMatiere', async (event, credentials) => {
const { nom, credit, uniter, ue, id } = credentials
const update = await updateMatiere(nom, id, credit, uniter, ue)
return update
})
ipcMain.handle('updateMatiereNiveau', async (event, credentials) => {
// credentials = { niveau_id, id }
const update = await updateMatiereNiveau(credentials) // ✅ on passe id + niveau_id
return update
})
// event for importExcel
ipcMain.handle('importexcel', async (event, credentials) => {
const files = credentials
console.log(files)
const insert = await importFileToDatabase(files)
return insert
})
// event for udatign a single niveau
ipcMain.handle('updateSingleNiveau', async (event, credentials) => {
const { nom, id } = credentials
const update = updateNiveau(nom, id)
return update
})
// event to get single niveau
ipcMain.handle('singleNiveau', async (event, credentials) => {
const { id } = credentials
const update = getSingleNiveau(id)
return update
})
// event for creating matiere
ipcMain.handle('createMatiere', async (event, credentials) => {
const { nom, credit, uniter, ue } = credentials
const create = createMatiere(nom, credit, uniter, ue)
return create
})
// // event for import excel matiere
ipcMain.handle('importExcelMatiere', async (event, credentials) => {
const files = credentials
console.log(files)
const insert = await importFileToDatabaseMatiere(files)
return insert
})
// event for import excel niveau
ipcMain.handle('importNiveau', async (event, credentials) => {
const files = credentials
console.log(files)
const insert = await importNiveau(files)
return insert
})
// event for updating note systeme
ipcMain.handle('updateNoteSysteme', async (event, credentials) => {
const { id, admis, redouble, renvoyer } = credentials
const update = updateSysteme(id, admis, redouble, renvoyer)
return update
})
// event for updating note systeme
ipcMain.handle('createAnneeScolaire', async (event, credentials) => {
const { code, debut, fin } = credentials
const create = createAnneeScolaire(code, debut, fin)
return create
})
ipcMain.handle('getMoyene', async (event, credentials) => {
const { niveau, scolaire } = credentials
console.log('index.js', niveau, scolaire)
const create = showMoyen(niveau, scolaire)
return create
})
ipcMain.handle('getMoyenneRepech', async (event, credentials) => {
const { niveau, scolaire } = credentials
console.log('index.js', niveau, scolaire)
const create = showMoyenRepech(niveau, scolaire)
return create
})
ipcMain.handle('noteMatiere', async (event, credentials) => {
const { id, niveau, annee_scolaire } = credentials
const get = getMatiereAndNote(id, niveau, annee_scolaire)
return get
})
ipcMain.handle('displayMatiereFromForm', async (event, credentials) => {
const { niveau, mention_id, parcours } = credentials
const get = displayMatiereFromForm(niveau, mention_id, parcours)
return get
})
ipcMain.handle('createMention', async (event, credentials) => {
const { nom, uniter } = credentials
const get = createMention(nom, uniter)
return get
})
ipcMain.handle('getSingleMention', async (event, credentials) => {
const { id } = credentials
const get = getSingleMention(id)
return get
})
ipcMain.handle('updateMention', async (event, credentials) => {
const { nom, uniter, id } = credentials
const get = updateMention(nom, uniter, id)
return get
})
ipcMain.handle('deleteMention', async (event, credentials) => {
const { id } = credentials
const get = deleteMention(id)
return get
})
ipcMain.handle('deleteNiveaus', async (event, credentials) => {
const { id } = credentials
const get = deleteNiveau(id)
return get
})
ipcMain.handle('deleteMatiere', async (event, id) => {
return await deleteMatiere(id);
});
ipcMain.handle('deleteEtudiant', async (event, id) => {
return await deleteEtudiant(id);
});
ipcMain.handle('asign', async (event, credentials) => {
const { formData, id } = credentials
// console.log(formData, id);
const get = asygnationToMention(formData, id)
return get
})
ipcMain.handle('asignSemestre', async (event, credentials) => {
const { id } = credentials
const get = getMentionMatiereChecked(id)
return get
})
ipcMain.handle('getAsign', async (event, credentials) => {
const { id } = credentials
// console.log(formData, id);
const get = getMentionMatiere(id)
return get
})
ipcMain.handle('deleteAnneeScolaire', async (event, credentials) => {
const { id } = credentials
// console.log(formData, id);
const get = deleteAnneeScolaire(id)
return get
})
ipcMain.handle('getSemestreMatiere', async (event, credentials) => {
const { id } = credentials
// console.log(formData, id);
const get = getSemestreMatiere(id)
return get
})
ipcMain.handle('insertUpdateMentionSemestre', async (event, credentials) => {
const { id, selectedSemestres } = credentials
// console.log(formData, id);
const get = insertUpdateMentionSemestre(id, selectedSemestres)
return get
})
ipcMain.handle('getSingleAnneeScolaire', async (event, credentials) => {
const { id } = credentials
// console.log(formData, id);
const get = getSingleAnneScolaire(id)
return get
})
ipcMain.handle('updateAnneeScolaire', async (event, credentials) => {
const { code, debut, fin, id } = credentials
// console.log(formData, id);
const get = updateAnneeScolaire(id, code, debut, fin)
return get
})
ipcMain.handle('setCurrent', async (event, credentials) => {
const { id } = credentials
// console.log(formData, id);
const get = setCurrent(id)
return get
})
ipcMain.handle('noteRelerer', async (event, credentials) => {
const { id, anneescolaire, niveau } = credentials
// console.log(formData, id);
const get = getNotesWithRepechToDisplay(id, anneescolaire, niveau)
return get
})
ipcMain.handle('updateNessesary', async (event, credentials) => {
const { id, multiplicateur } = credentials
// console.log(formData, id);
const get = updateNessesaryTable(id, multiplicateur)
return get
})
ipcMain.handle('insertProf', async (event, credentials) => {
const { nom_enseignant, prenom_enseignant, contact, date, matiere_id } = credentials
// console.log(formData, id);
const get = insertNewProf(matiere_id, nom_enseignant, prenom_enseignant, contact, date)
return get
})
ipcMain.handle('insertParcours', async (event, credentials) => {
const { nom, uniter, mention_id } = credentials
// console.log(formData, id);
const get = insertParcour(nom, uniter, mention_id)
return get
})
ipcMain.handle('getSingleParcours', async (event, credentials) => {
const { id } = credentials
// console.log(formData, id);
const get = getSingleParcours(id)
return get
})
ipcMain.handle('deleteParcours', async (event, credentials) => {
const { id } = credentials
// console.log(formData, id);
const get = deletes(id)
return get
})
ipcMain.handle('updateParcours', async (event, credentials) => {
const { nom, uniter, mention_id, id } = credentials
// console.log(formData, id);
const get = updateparcour(id, nom, uniter, mention_id)
return get
})
ipcMain.handle('parcourMatiere', async (event, credentials) => {
const { matiere_id, parcour_id } = credentials
// console.log(formData, id);
const get = parcourMatiere(matiere_id, parcour_id)
return get
})
ipcMain.handle('getSingleProf', async (event, credentials) => {
const { id } = credentials
// console.log(formData, id);
const get = getSIngleProf(id)
return get
})
ipcMain.handle('updateProf', async (event, credentials) => {
const { nom_enseignant, prenom_enseignant, contact, date, matiere_id } = credentials
// console.log(formData, id);
const get = updateProf(matiere_id, nom_enseignant, prenom_enseignant, contact, date)
return get
})
ipcMain.handle('extractFiches', async (event, credentials) => {
const { matiere_id } = credentials
// console.log(formData, id);
const get = extractFiche(matiere_id)
return get
})
ipcMain.handle('getParcourMatiere', async (event, credentials) => {
const { matiere_id } = credentials
// console.log(formData, id);
const get = getParcourMatiere(matiere_id)
return get
})
ipcMain.handle('changeParcours', async (event, credentials) => {
const { parcours, user_id } = credentials
// console.log(formData, id);
const get = updateParcours(parcours, user_id)
return get
})
ipcMain.handle('createTranche', async (event, credentials) => {
const { etudiant_id, tranchename, montant } = credentials
// console.log(formData, id);
const get = createTranche(etudiant_id, tranchename, montant)
return get
})
ipcMain.handle('getTranche', async (event, credentials) => {
const { id } = credentials
// console.log(formData, id);
const get = getTranche(id)
return get
})
ipcMain.handle('updateTranche', async (event, credentials) => {
const { id, tranchename, montant } = credentials
// console.log(formData, id);
const get = updateTranche(id, tranchename, montant)
return get
})
ipcMain.handle('deleteTranche', async (event, credentials) => {
const { id } = credentials
console.log(id)
const get = deleteTranche(id)
return get
})
ipcMain.handle('getSingleTranche', async (event, credentials) => {
const { id } = credentials
// console.log(formData, id);
const get = getSingleTranche(id)
return get
})
ipcMain.handle('createIPConfig', async (event, credentials) => {
const { ipname } = credentials
// console.log(formData, id);
const get = createConfigIp(ipname)
return get
})
ipcMain.handle('updateIPConfig', async (event, credentials) => {
const { id, ipname } = credentials
// console.log(formData, id);
const get = updateIPConfig(id, ipname)
return get
})

212
src/preload/index.backup.js

@ -0,0 +1,212 @@
import { contextBridge, ipcRenderer } from 'electron'
import { electronAPI } from '@electron-toolkit/preload'
const { getNessesarytable } = require('../../database/function/System')
const { getNiveau } = require('../../database/Models/Niveau')
const { getAllUsers } = require('../../database/Models/Users')
const { getAllEtudiants, getDataToDashboard } = require('../../database/Models/Etudiants')
const { verifyEtudiantIfHeHasNotes, blockShowMoyene } = require('../../database/Models/Notes')
const { synchronizeData } = require('../../database/api/SyncronisationDataUsers')
const { synchronizeDataEtudiants } = require('../../database/api/SyncronisationDataEtudiants')
const { synchronizeDataNotes } = require('../../database/api/CheckUpdateNote')
const { getMatiere, getSemestre, getEnseignants } = require('../../database/Models/Matieres')
const { getSysteme } = require('../../database/Models/NoteSysrem')
const { getStatus } = require('../../database/Models/Status')
const { getAnneeScolaire, getInterval } = require('../../database/Models/AnneeScolaire')
const { getMentions } = require('../../database/Models/Mentions')
const { getAll } = require('../../database/api/Get')
const { getParcours } = require('../../database/Models/Parcours')
const { getIPConfig } = require('../../database/Models/IpConfig')
// Custom APIs for renderer
const api = {}
// Use `contextBridge` APIs to expose Electron APIs to
// renderer only if context isolation is enabled, otherwise
// just add to the DOM global.
if (process.contextIsolated) {
try {
contextBridge.exposeInMainWorld('electron', electronAPI)
contextBridge.exposeInMainWorld('api', api)
/**
* contextBridge for Tray
*/
contextBridge.exposeInMainWorld('Tray', {
onNavigate: (callback) => {
ipcRenderer.on('navigateToRoute', (event, route) => {
callback(route) // Pass the route to the renderer callback
})
}
})
/**
* contextBridge for users
*/
contextBridge.exposeInMainWorld('allUser', {
users: () => getAllUsers(),
login: (credentials) => ipcRenderer.invoke('login', credentials),
insertUsers: (credentials) => ipcRenderer.invoke('insertUser', credentials),
forgotPassword: (credentials) => ipcRenderer.invoke('forgotPassword', credentials),
quit: () => ipcRenderer.invoke('quit'),
minimize: () => ipcRenderer.invoke('minimize'),
updateUsers: (credentials) => ipcRenderer.invoke('updateUsers', credentials)
})
contextBridge.exposeInMainWorld('syncro', {
getall: () => getAll()
})
// syncronisation des donner
window.addEventListener('online', async () => {
if (navigator.onLine) {
// synchronizeData()
// synchronizeDataEtudiants()
// synchronizeDataNotes()
await getAll()
}
})
// send data
getAll()
/**
* contextBridge for etudiants
*/
contextBridge.exposeInMainWorld('etudiants', {
insertEtudiant: (credentials) => ipcRenderer.invoke('insertEtudiant', credentials),
getEtudiants: () => getAllEtudiants(),
FilterDataByNiveau: (credential) => ipcRenderer.invoke('getByNiveau', credential),
getSingle: (credential) => ipcRenderer.invoke('single', credential),
updateEtudiants: (credentials) => ipcRenderer.invoke('updateETudiants', credentials),
getDataToDashboards: () => getDataToDashboard(),
updateEtudiantsPDP: (credentials) => ipcRenderer.invoke('updateETudiantsPDP', credentials),
importExcel: (credentials) => ipcRenderer.invoke('importexcel', credentials),
changeParcours: (credentials) => ipcRenderer.invoke('changeParcours', credentials),
createTranche: (credentials) => ipcRenderer.invoke('createTranche', credentials),
getTranche: (credentials) => ipcRenderer.invoke('getTranche', credentials),
updateTranche: (credentials) => ipcRenderer.invoke('updateTranche', credentials),
deleteTranche: (credentials) => ipcRenderer.invoke('deleteTranche', credentials),
getSingleTranche: (credentials) => ipcRenderer.invoke('getSingleTranche', credentials)
})
/**
* cobtextBridge for niveaus
*/
contextBridge.exposeInMainWorld('niveaus', {
getNiveau: () => getNiveau(),
getSingleNiveau: (credential) => ipcRenderer.invoke('singleNiveau', credential),
insertNiveau: (credentials) => ipcRenderer.invoke('insertNiveau', credentials),
updateSingleNiveau: (credentials) => ipcRenderer.invoke('updateSingleNiveau', credentials),
importNiveau: (credentials) => ipcRenderer.invoke('importNiveau', credentials),
deleteNiveaus: (credentials) => ipcRenderer.invoke('deleteNiveaus', credentials)
})
/**
* contextBridge for notes
*/
contextBridge.exposeInMainWorld('notes', {
getNotes: (credentials) => ipcRenderer.invoke('getSingleNote', credentials),
insertNote: (credentials) => ipcRenderer.invoke('insertNote', credentials),
updateNote: (credentials) => ipcRenderer.invoke('updatetNote', credentials),
getMoyenne: (credentials) => ipcRenderer.invoke('getMoyene', credentials),
noteMatiere: (credentials) => ipcRenderer.invoke('noteMatiere', credentials),
noteRelerer: (credentials) => ipcRenderer.invoke('noteRelerer', credentials),
getMoyenneVerify: () => verifyEtudiantIfHeHasNotes(),
getblockNote: () => blockShowMoyene()
})
/**
* contextbridge for note repechage
*/
contextBridge.exposeInMainWorld('noteRepech', {
getNotesRepech: (credentials) => ipcRenderer.invoke('getNotesRepech', credentials),
updateNoteRepech: (credentials) => ipcRenderer.invoke('updatetNoteRepech', credentials),
getMoyenneRepech: (credentials) => ipcRenderer.invoke('getMoyenneRepech', credentials)
})
/**
* contextBridge for matieres
*/
contextBridge.exposeInMainWorld('matieres', {
getMatiere: () => getMatiere(),
createMatiere: (credentials) => ipcRenderer.invoke('createMatiere', credentials),
getMatiereByID: (credentials) => ipcRenderer.invoke('getMatiereByID', credentials),
updateMatiere: (credentials) => ipcRenderer.invoke('updateMatiere', credentials),
updateMatiereNiveau: (credentials) => ipcRenderer.invoke('updateMatiereNiveau', credentials),
importExcel: (credentials) => ipcRenderer.invoke('importExcelMatiere', credentials),
displayMatiereFromForm: (credentials) =>
ipcRenderer.invoke('displayMatiereFromForm', credentials),
deleteMatiere: (credentials) => ipcRenderer.invoke('deleteMatiere', credentials),
deleteEtudiant: (credentials) => ipcRenderer.invoke('deleteEtudiant', credentials),
asign: (credentials) => ipcRenderer.invoke('asign', credentials),
getAsign: (credentials) => ipcRenderer.invoke('getAsign', credentials),
asignSemestre: (credentials) => ipcRenderer.invoke('asignSemestre', credentials),
getSemestreMatiere: (credentials) => ipcRenderer.invoke('getSemestreMatiere', credentials),
getSemestre: () => getSemestre(),
getNessesary: () => getNessesarytable(),
getENseignant: () => getEnseignants(),
insertUpdateMentionSemestre: (credentials) =>
ipcRenderer.invoke('insertUpdateMentionSemestre', credentials),
updateNessesary: (credentials) => ipcRenderer.invoke('updateNessesary', credentials),
insertProf: (credentials) => ipcRenderer.invoke('insertProf', credentials),
getSingleProf: (credentials) => ipcRenderer.invoke('getSingleProf', credentials),
updateProf: (credentials) => ipcRenderer.invoke('updateProf', credentials)
})
/**
* contextBridge for note systeme
*/
contextBridge.exposeInMainWorld('notesysteme', {
getSyteme: () => getSysteme(),
updateNoteSysteme: (credentials) => ipcRenderer.invoke('updateNoteSysteme', credentials),
insertParcours: (credentials) => ipcRenderer.invoke('insertParcours', credentials),
getSingleParcours: (credentials) => ipcRenderer.invoke('getSingleParcours', credentials),
deleteParcours: (credentials) => ipcRenderer.invoke('deleteParcours', credentials),
updateParcours: (credentials) => ipcRenderer.invoke('updateParcours', credentials),
parcourMatiere: (credentials) => ipcRenderer.invoke('parcourMatiere', credentials),
getParcours: () => getParcours(),
extractFiches: (credentials) => ipcRenderer.invoke('extractFiches', credentials),
getParcourMatiere: (credentials) => ipcRenderer.invoke('getParcourMatiere', credentials),
createIPConfig: (credentials) => ipcRenderer.invoke('createIPConfig', credentials),
getIPConfig: () => getIPConfig(),
updateIPConfig: (credentials) => ipcRenderer.invoke('updateIPConfig', credentials)
})
/**
* contextbridge for status
*/
contextBridge.exposeInMainWorld('statuss', {
getStatus: () => getStatus()
})
/**
* contextbridge for annee scolaire
*/
contextBridge.exposeInMainWorld('anneescolaire', {
getAnneeScolaire: () => getAnneeScolaire(),
getInterval: () => getInterval(),
createAnneeScolaire: (credentials) => ipcRenderer.invoke('createAnneeScolaire', credentials),
deleteAnneeScolaire: (credentials) => ipcRenderer.invoke('deleteAnneeScolaire', credentials),
getSingleAnneeScolaire: (credentials) =>
ipcRenderer.invoke('getSingleAnneeScolaire', credentials),
updateAnneeScolaire: (credentials) => ipcRenderer.invoke('updateAnneeScolaire', credentials),
setCurrent: (credentials) => ipcRenderer.invoke('setCurrent', credentials)
})
/**
* contextbridge for mention
*/
contextBridge.exposeInMainWorld('mention', {
createMention: (credentials) => ipcRenderer.invoke('createMention', credentials),
getMention: () => getMentions(),
getSingleMention: (credentials) => ipcRenderer.invoke('getSingleMention', credentials),
updateMention: (credentials) => ipcRenderer.invoke('updateMention', credentials),
deleteMention: (credentials) => ipcRenderer.invoke('deleteMention', credentials)
})
} catch (error) {
console.error(error)
}
} else {
window.electron = electronAPI
window.api = api
}

208
src/preload/index.js

@ -0,0 +1,208 @@
import { contextBridge, ipcRenderer } from 'electron'
import { electronAPI } from '@electron-toolkit/preload'
const { getNessesarytable } = require('../../database/function/System')
const { getAllUsers } = require('../../database/Models/Users')
const { getAllEtudiants, getDataToDashboard } = require('../../database/Models/Etudiants')
const { verifyEtudiantIfHeHasNotes, blockShowMoyene } = require('../../database/Models/Notes')
const { getMatiere, getSemestre, getEnseignants } = require('../../database/Models/Matieres')
const { getSysteme } = require('../../database/Models/NoteSysrem')
const { getStatus } = require('../../database/Models/Status')
const { getAnneeScolaire, getInterval } = require('../../database/Models/AnneeScolaire')
const { getMentions } = require('../../database/Models/Mentions')
const { getAll } = require('../../database/api/Get')
const { getParcours } = require('../../database/Models/Parcours')
const { getNiveau } = require('../../database/Models/Niveau')
const { getIPConfig } = require('../../database/Models/IpConfig')
// Custom APIs for renderer
const api = {}
// Use `contextBridge` APIs to expose Electron APIs to
// renderer only if context isolation is enabled, otherwise
// just add to the DOM global.
if (process.contextIsolated) {
try {
contextBridge.exposeInMainWorld('electron', electronAPI)
contextBridge.exposeInMainWorld('api', api)
/**
* contextBridge for Tray
*/
contextBridge.exposeInMainWorld('Tray', {
onNavigate: (callback) => {
ipcRenderer.on('navigateToRoute', (event, route) => {
callback(route) // Pass the route to the renderer callback
})
}
})
/**
* contextBridge for users
*/
contextBridge.exposeInMainWorld('allUser', {
users: () => getAllUsers(),
login: (credentials) => ipcRenderer.invoke('login', credentials),
insertUsers: (credentials) => ipcRenderer.invoke('insertUser', credentials),
forgotPassword: (credentials) => ipcRenderer.invoke('forgotPassword', credentials),
quit: () => ipcRenderer.invoke('quit'),
minimize: () => ipcRenderer.invoke('minimize'),
updateUsers: (credentials) => ipcRenderer.invoke('updateUsers', credentials)
})
contextBridge.exposeInMainWorld('syncro', {
getall: () => getAll()
})
// syncronisation des donner
window.addEventListener('online', async () => {
if (navigator.onLine) {
// synchronizeData()
// synchronizeDataEtudiants()
// synchronizeDataNotes()
await getAll()
}
})
// send data
getAll()
/**
* contextBridge for etudiants
*/
contextBridge.exposeInMainWorld('etudiants', {
insertEtudiant: (credentials) => ipcRenderer.invoke('insertEtudiant', credentials),
getEtudiants: () => getAllEtudiants(),
FilterDataByNiveau: (credential) => ipcRenderer.invoke('getByNiveau', credential),
getSingle: (credential) => ipcRenderer.invoke('single', credential),
updateEtudiants: (credentials) => ipcRenderer.invoke('updateETudiants', credentials),
getDataToDashboards: () => getDataToDashboard(),
updateEtudiantsPDP: (credentials) => ipcRenderer.invoke('updateETudiantsPDP', credentials),
importExcel: (credentials) => ipcRenderer.invoke('importexcel', credentials),
changeParcours: (credentials) => ipcRenderer.invoke('changeParcours', credentials),
createTranche: (credentials) => ipcRenderer.invoke('createTranche', credentials),
getTranche: (credentials) => ipcRenderer.invoke('getTranche', credentials),
updateTranche: (credentials) => ipcRenderer.invoke('updateTranche', credentials),
deleteTranche: (credentials) => ipcRenderer.invoke('deleteTranche', credentials),
getSingleTranche: (credentials) => ipcRenderer.invoke('getSingleTranche', credentials)
})
/**
* cobtextBridge for niveaus
*/
contextBridge.exposeInMainWorld('niveaus', {
getNiveau: () => getNiveau(),
getSingleNiveau: (credential) => ipcRenderer.invoke('singleNiveau', credential),
insertNiveau: (credentials) => ipcRenderer.invoke('insertNiveau', credentials),
updateSingleNiveau: (credentials) => ipcRenderer.invoke('updateSingleNiveau', credentials),
importNiveau: (credentials) => ipcRenderer.invoke('importNiveau', credentials),
deleteNiveaus: (credentials) => ipcRenderer.invoke('deleteNiveaus', credentials)
})
/**
* contextBridge for notes
*/
contextBridge.exposeInMainWorld('notes', {
getNotes: (credentials) => ipcRenderer.invoke('getSingleNote', credentials),
insertNote: (credentials) => ipcRenderer.invoke('insertNote', credentials),
updateNote: (credentials) => ipcRenderer.invoke('updatetNote', credentials),
getMoyenne: (credentials) => ipcRenderer.invoke('getMoyene', credentials),
noteMatiere: (credentials) => ipcRenderer.invoke('noteMatiere', credentials),
noteRelerer: (credentials) => ipcRenderer.invoke('noteRelerer', credentials),
getMoyenneVerify: () => verifyEtudiantIfHeHasNotes(),
getblockNote: () => blockShowMoyene()
})
/**
* contextbridge for note repechage
*/
contextBridge.exposeInMainWorld('noteRepech', {
getNotesRepech: (credentials) => ipcRenderer.invoke('getNotesRepech', credentials),
updateNoteRepech: (credentials) => ipcRenderer.invoke('updatetNoteRepech', credentials),
getMoyenneRepech: (credentials) => ipcRenderer.invoke('getMoyenneRepech', credentials)
})
/**
* contextBridge for matieres
*/
contextBridge.exposeInMainWorld('matieres', {
getMatiere: () => getMatiere(),
createMatiere: (credentials) => ipcRenderer.invoke('createMatiere', credentials),
getMatiereByID: (credentials) => ipcRenderer.invoke('getMatiereByID', credentials),
updateMatiere: (credentials) => ipcRenderer.invoke('updateMatiere', credentials),
updateMatiereNiveau: (credentials) => ipcRenderer.invoke('updateMatiereNiveau', credentials),
importExcel: (credentials) => ipcRenderer.invoke('importExcelMatiere', credentials),
displayMatiereFromForm: (credentials) =>
ipcRenderer.invoke('displayMatiereFromForm', credentials),
deleteMatiere: (id) => ipcRenderer.invoke('deleteMatiere', id),
deleteEtudiant: (id) => ipcRenderer.invoke('deleteEtudiant', id),
asign: (credentials) => ipcRenderer.invoke('asign', credentials),
getAsign: (credentials) => ipcRenderer.invoke('getAsign', credentials),
asignSemestre: (credentials) => ipcRenderer.invoke('asignSemestre', credentials),
getSemestreMatiere: (credentials) => ipcRenderer.invoke('getSemestreMatiere', credentials),
getSemestre: () => getSemestre(),
getNessesary: () => getNessesarytable(),
getENseignant: () => getEnseignants(),
insertUpdateMentionSemestre: (credentials) =>
ipcRenderer.invoke('insertUpdateMentionSemestre', credentials),
updateNessesary: (credentials) => ipcRenderer.invoke('updateNessesary', credentials),
insertProf: (credentials) => ipcRenderer.invoke('insertProf', credentials),
getSingleProf: (credentials) => ipcRenderer.invoke('getSingleProf', credentials),
updateProf: (credentials) => ipcRenderer.invoke('updateProf', credentials)
})
/**
* contextBridge for note systeme
*/
contextBridge.exposeInMainWorld('notesysteme', {
getSyteme: () => getSysteme(),
updateNoteSysteme: (credentials) => ipcRenderer.invoke('updateNoteSysteme', credentials),
insertParcours: (credentials) => ipcRenderer.invoke('insertParcours', credentials),
getSingleParcours: (credentials) => ipcRenderer.invoke('getSingleParcours', credentials),
deleteParcours: (credentials) => ipcRenderer.invoke('deleteParcours', credentials),
updateParcours: (credentials) => ipcRenderer.invoke('updateParcours', credentials),
parcourMatiere: (credentials) => ipcRenderer.invoke('parcourMatiere', credentials),
getParcours: () => getParcours(),
extractFiches: (credentials) => ipcRenderer.invoke('extractFiches', credentials),
getParcourMatiere: (credentials) => ipcRenderer.invoke('getParcourMatiere', credentials),
createIPConfig: (credentials) => ipcRenderer.invoke('createIPConfig', credentials),
getIPConfig: () => getIPConfig(),
updateIPConfig: (credentials) => ipcRenderer.invoke('updateIPConfig', credentials)
})
/**
* contextbridge for status
*/
contextBridge.exposeInMainWorld('statuss', {
getStatus: () => getStatus()
})
/**
* contextbridge for annee scolaire
*/
contextBridge.exposeInMainWorld('anneescolaire', {
getAnneeScolaire: () => getAnneeScolaire(),
getInterval: () => getInterval(),
createAnneeScolaire: (credentials) => ipcRenderer.invoke('createAnneeScolaire', credentials),
deleteAnneeScolaire: (credentials) => ipcRenderer.invoke('deleteAnneeScolaire', credentials),
getSingleAnneeScolaire: (credentials) =>
ipcRenderer.invoke('getSingleAnneeScolaire', credentials),
updateAnneeScolaire: (credentials) => ipcRenderer.invoke('updateAnneeScolaire', credentials),
setCurrent: (credentials) => ipcRenderer.invoke('setCurrent', credentials)
})
/**
* contextbridge for mention
*/
contextBridge.exposeInMainWorld('mention', {
createMention: (credentials) => ipcRenderer.invoke('createMention', credentials),
getMention: () => getMentions(),
getSingleMention: (credentials) => ipcRenderer.invoke('getSingleMention', credentials),
updateMention: (credentials) => ipcRenderer.invoke('updateMention', credentials),
deleteMention: (credentials) => ipcRenderer.invoke('deleteMention', credentials)
})
} catch (error) {
console.error(error)
}
} else {
window.electron = electronAPI
window.api = api
}

17
src/renderer/src/assets/AllStyleComponents.module.css

@ -0,0 +1,17 @@
.h1style {
/* text-transform: uppercase; */
font-weight: 900;
/* 6636af4a 6636af ffae01 */
border-left: 10px solid #ffff;
padding-left: 10px;
margin-bottom: 30px;
background: linear-gradient(to right, #ffaf01b4, transparent);
color: white;
width: 100%;
padding-left: 45px;
font-size: 25px;
}
.mainHome {
padding: 1% 0 0 0;
width: 100%;
}

283
src/renderer/src/components/AddAnneeScolaire.jsx

@ -0,0 +1,283 @@
import React, { useRef, useState } from 'react'
import classe from '../assets/AllStyleComponents.module.css'
import classeHome from '../assets/Home.module.css'
import Paper from '@mui/material/Paper'
import { Link, useNavigate } from 'react-router-dom'
import { IoMdReturnRight } from 'react-icons/io'
import { Modal, Box, Typography, Button, InputAdornment, TextField, Grid } from '@mui/material'
import { FaCalendarAlt, FaCalendarPlus } from 'react-icons/fa'
import validationAnneeScolaire from './validation/ValidationAddAnneeScolaire'
import svgError from '../assets/error.svg'
import svgSuccess from '../assets/success.svg'
const AddAnneeScolaire = () => {
const [formData, setFormData] = useState({
code: '',
debut: '',
fin: ''
})
const debutRef = useRef()
const codeRef = useRef()
const finRef = useRef()
/**
* function to set the data in state
* @param {*} e
*/
const handleInputChange = (e) => {
const { name, value } = e.target
setFormData((prevData) => ({
...prevData,
[name]: value
}))
}
const formSubmit = async (e) => {
e.preventDefault()
let isValid = validationAnneeScolaire(codeRef.current, debutRef.current, finRef.current)
if (isValid) {
let response = await window.anneescolaire.createAnneeScolaire(formData)
console.log(response)
if (response.success) {
setStatus(200)
setOpen(true)
} else {
setStatus(400)
setOpen(true)
}
} else {
setStatus(400)
setOpen(true)
}
}
const [status, setStatus] = useState(200)
/**
* hook to open modal
*/
const [open, setOpen] = useState(false)
/**
* function to close modal
*/
const handleClose = () => {
setOpen(false)
window.history.back()
}
const handleClose2 = () => {
setOpen(false)
}
/**
* function to return the view Modal
*
* @returns {JSX}
*/
const modals = () => (
<Modal
open={open}
onClose={status === 200 ? handleClose : handleClose}
aria-labelledby="modal-title"
aria-describedby="modal-description"
>
<Box
sx={{
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
width: 450,
bgcolor: 'background.paper',
boxShadow: 24,
p: 4
}}
>
{status === 200 ? (
<Typography style={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
<img src={svgSuccess} alt="" width={50} height={50} />{' '}
<span>Année Scolaire insérer avec succes</span>
</Typography>
) : (
<Typography style={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
<img src={svgError} alt="" width={50} height={50} />{' '}
<span>Erreur, veuillez réessayer</span>
</Typography>
)}
<Box
sx={{
marginTop: '2%',
display: 'flex',
gap: '20px',
alignItems: 'end',
justifyContent: 'flex-end'
}}
>
{status === 200 ? (
<Button onClick={handleClose} color="warning" variant="contained">
OK
</Button>
) : (
<Button onClick={handleClose2} color="warning" variant="contained">
OK
</Button>
)}
</Box>
</Box>
</Modal>
)
return (
<div className={classe.mainHome}>
{modals()}
<div className={classeHome.header}>
<div className={classe.h1style}>
<div className={classeHome.blockTitle}>
<h1 style={{ display: 'flex', alignItems: 'center', gap: '10px' }}>
<FaCalendarPlus /> Ajout Année Scolaire
</h1>
<Link to={'#'} onClick={() => window.history.back()}>
<Button color="warning" variant="contained">
<IoMdReturnRight style={{ fontSize: '20px' }} />
</Button>
</Link>
</div>
</div>
</div>
<div className={classeHome.boxEtudiantsCard}>
<Paper
sx={{
width: '100%',
height: '100%', // Auto height to make the grid responsive
minHeight: 500, // Ensures a minimum height
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center'
}}
>
<form
onSubmit={formSubmit}
style={{
width: '50%',
padding: '1%',
height: '20%',
border: 'solid 2px orange',
borderRadius: '10px'
}}
>
<h4 style={{ textAlign: 'center', padding: '0 0 3% 0' }}>
Creation de nouvelle Année Scolaire
</h4>
<Grid container spacing={2}>
<Grid item xs={12} sm={6}>
<TextField
label={'Année Scolaire'}
name={'code'}
placeholder="2024-2025"
color="warning"
fullWidth
value={formData.code}
InputProps={{
startAdornment: (
<InputAdornment position="start">
<FaCalendarAlt />
</InputAdornment>
)
}}
onChange={handleInputChange}
inputRef={codeRef}
className="inputAddNote"
sx={{
'& .MuiOutlinedInput-root': {
'&:hover fieldset': {
borderColor: '#ff9800' // Set the border color on hover
}
},
'& .MuiInputBase-input::placeholder': {
fontSize: '14px' // Set the placeholder font size
}
}}
/>
</Grid>
<Grid item xs={12} sm={6}>
<TextField
label={'Date de début'}
name={'debut'}
color="warning"
onChange={handleInputChange}
inputRef={debutRef}
value={formData.debut}
fullWidth
type="date"
InputProps={{
startAdornment: (
<InputAdornment position="start">
<FaCalendarAlt />
</InputAdornment>
)
}}
className="inputAddNote"
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 item xs={12} sm={6}>
<TextField
label={'Date de fin'}
name={'fin'}
color="warning"
fullWidth
type="date"
InputProps={{
startAdornment: (
<InputAdornment position="start">
<FaCalendarAlt />
</InputAdornment>
)
}}
onChange={handleInputChange}
inputRef={finRef}
value={formData.fin}
className="inputAddNote"
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
item
xs={12}
style={{ display: 'flex', gap: '30px', justifyContent: 'flex-end' }}
>
<Button type="submit" color="warning" variant="contained">
Enregister
</Button>
</Grid>
</Grid>
</form>
</Paper>
</div>
</div>
)
}
export default AddAnneeScolaire

317
src/renderer/src/components/AddNotes.jsx

@ -0,0 +1,317 @@
import React, { useEffect, useRef, useState } from 'react'
import classe from '../assets/AllStyleComponents.module.css'
import classeAdd from '../assets/AddNotes.module.css'
import classeHome from '../assets/Home.module.css'
import { Modal, Box, Typography, Button, InputAdornment, TextField, Grid } from '@mui/material'
import { CgNotes } from 'react-icons/cg'
import { IoMdReturnRight } from 'react-icons/io'
import { Link, useParams, useNavigate } from 'react-router-dom'
import svgSuccess from '../assets/success.svg'
import svgError from '../assets/error.svg'
import validateAddNote from './validation/AddNote'
import ModalUpdateParcoursEtudiant from './ModalUpdateParcoursEtudiant'
const AddNotes = () => {
const { id, niveau, mention_id, parcours } = useParams()
const [matieres, setMatieres] = useState([])
const [formData, setFormData] = useState({}) // Initialize with an empty object
const [etudiants, setEtudiants] = useState([])
const navigate = useNavigate()
const [openModal1, setOpenModal1] = useState(false)
const oncloseModal1 = () => {
setOpenModal1(false)
}
/**
* Fetching the matieres
*/
useEffect(() => {
window.matieres.displayMatiereFromForm({ niveau, mention_id, parcours }).then((response) => {
setMatieres(response)
})
window.etudiants.getSingle({ id }).then((response) => {
setEtudiants(response)
})
if (parcours == 'Pas de parcours' && niveau != 'L1') {
setOpenModal1(true)
}
}, [])
const [isSubmitted, setIsSubmitted] = useState(false)
const [parcoursChange, setParcourChanges] = useState('')
const handleFormSubmit = (status, parcours) => {
setIsSubmitted(status)
setParcourChanges(parcours)
}
useEffect(() => {
if (isSubmitted) {
let parcours = parcoursChange
window.matieres.displayMatiereFromForm({ niveau, mention_id, parcours }).then((response) => {
setMatieres(response)
console.log("resulat teste:1", response);
console.log("resulat teste:2", mention_id);
console.log("resulat teste:3", parcours);
})
setIsSubmitted(false)
}
}, [isSubmitted])
let niveauEtudiant = etudiants.niveau
let AnneeScolaireEtudiant = etudiants.annee_scolaire
/**
* Update formData whenever matieres change
*/
useEffect(() => {
const initialFormData = matieres.reduce((acc, mat) => {
acc[mat.id] = '' // Initialize each key with an empty string
return acc
}, {})
setFormData(initialFormData)
}, [matieres]) // Dependency array ensures this runs whenever `matieres` is updated
const [disabled, setDisabled] = useState(false)
/**
* Handle form submission
*/
const handleSubmit = async (e) => {
e.preventDefault()
const etudiant_id = id
const etudiant_niveau = niveauEtudiant
let valid = validateAddNote()
let annee_scolaire = AnneeScolaireEtudiant
let mention_id = etudiants.mention_id
if (valid) {
let response = await window.notes.insertNote({
etudiant_id,
etudiant_niveau,
mention_id,
formData,
annee_scolaire
})
console.log(response)
if (response.success) {
setOpen(true)
setStatut(200)
setDisabled(true)
const resetFormData = matieres.reduce((acc, mat) => {
acc[mat.id] = '' // Reset each field to an empty string
return acc
}, {})
setFormData(resetFormData)
}
} else {
setOpen(true)
setStatut(400)
}
}
console.log('matiere: ',matieres);
const [statut, setStatut] = useState(200)
/**
* hook to open modal
*/
const [open, setOpen] = useState(false)
/**
* function to close modal
*/
const handleClose = () => {
setOpen(false)
}
const handleClose2 = () => {
navigate('/notes')
setOpen(false)
}
/**
* function to return the view Modal
*
* @returns {JSX}
*/
const modals = () => (
<Modal
open={open}
onClose={handleClose}
aria-labelledby="modal-title"
aria-describedby="modal-description"
>
<Box
sx={{
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
width: 450,
bgcolor: 'background.paper',
boxShadow: 24,
p: 4
}}
>
{statut === 200 ? (
<Typography
style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '10px' }}
>
<img src={svgSuccess} alt="" width={70} height={70} />{' '}
<span>
Note de {etudiants.nom} {etudiants.prenom} en {etudiants.niveau} a été inserer avec
succès
</span>
</Typography>
) : (
<Typography
style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '10px' }}
>
<img src={svgError} alt="" width={70} height={70} />{' '}
<span>Inserer au moin un champ</span>
</Typography>
)}
<Box
sx={{
marginTop: '2%',
display: 'flex',
gap: '20px',
alignItems: 'end',
justifyContent: 'flex-end'
}}
>
{statut == 200 ? (
<Button onClick={handleClose2} color="warning" variant="contained">
OK
</Button>
) : (
<Button onClick={handleClose} color="warning" variant="contained">
OK
</Button>
)}
</Box>
</Box>
</Modal>
)
return (
<div className={classe.mainHome}>
{modals()}
<ModalUpdateParcoursEtudiant
onClose={oncloseModal1}
open={openModal1}
user_id={etudiants.id}
onSubmit={handleFormSubmit}
/>
<div className={classeAdd.header}>
<div className={classe.h1style}>
<div className={classeHome.blockTitle}>
<h1 style={{ display: 'flex', alignItems: 'center', gap: '10px' }}>
<CgNotes />
Ajout note
</h1>
<Link to={'/student'}>
<Button color="warning" variant="contained">
<IoMdReturnRight style={{ fontSize: '20px' }} />
</Button>
</Link>
</div>
</div>
{/* displaying */}
<div className={classeHome.boxEtudiantsCard}>
<Box
sx={{
position: 'absolute',
top: '55%',
left: '50%',
transform: 'translate(-50%, -50%)',
width: 700,
borderRadius: '2%',
bgcolor: 'background.paper',
boxShadow: 24,
p: 4
}}
>
<Box
sx={{
marginTop: '2%',
display: 'flex',
width: '100%',
gap: '20px',
alignItems: 'start',
justifyContent: 'center'
}}
>
<form onSubmit={handleSubmit} style={{ width: '100%' }}>
<h4 style={{ textAlign: 'center', marginBottom: '3%' }}>
Ajout des notes :{' '}
<span style={{ color: '#ff9800' }}>
{etudiants.nom} {etudiants.prenom} en {etudiants.niveau}
</span>
</h4>
{/* map the all matiere to the form */}
<Grid container spacing={2}>
{matieres.map((mat) => (
<Grid item xs={12} sm={3} key={mat.nom}>
<TextField
label={mat.nom}
name={mat.id}
placeholder="point séparateur"
color="warning"
fullWidth
value={formData[mat.id] || ''} // Access the correct value from formData
onChange={
(e) => setFormData({ ...formData, [mat.id]: e.target.value }) // Update the specific key
}
InputProps={{
startAdornment: (
<InputAdornment position="start">
<CgNotes />
</InputAdornment>
)
}}
className="inputAddNote"
sx={{
'& .MuiOutlinedInput-root': {
'&:hover fieldset': {
borderColor: '#ff9800' // Set the border color on hover
}
},
'& .MuiInputBase-input::placeholder': {
fontSize: '11px' // Set the placeholder font size
}
}}
/>
</Grid>
))}
</Grid>
<Grid
item
xs={12}
style={{
display: 'flex',
gap: '30px',
justifyContent: 'flex-end',
marginTop: '1%'
}}
>
<Button type="submit" color="warning" variant="contained" disabled={disabled}>
Enregister
</Button>
</Grid>
</form>
</Box>
</Box>
</div>
</div>
</div>
)
}
export default AddNotes

1070
src/renderer/src/components/AddStudent.jsx

File diff suppressed because it is too large

319
src/renderer/src/components/AnneeScolaire.jsx

@ -0,0 +1,319 @@
import React, { useEffect, useState } from 'react'
import classe from '../assets/AllStyleComponents.module.css'
import classeHome from '../assets/Home.module.css'
import Paper from '@mui/material/Paper'
import { Modal, Box, Typography, Button, InputAdornment, TextField, Grid } from '@mui/material'
import { FaCalendarAlt, FaCheck, FaCheckCircle, FaSquare } from 'react-icons/fa'
import { DataGrid, GridToolbar, GridToolbarFilterButton } from '@mui/x-data-grid'
import { createTheme, ThemeProvider } from '@mui/material/styles'
import { frFR } from '@mui/x-data-grid/locales'
import { FaCalendarPlus } from 'react-icons/fa'
import { Link } from 'react-router-dom'
import { FaPenToSquare, FaTrash } from 'react-icons/fa6'
import { Tooltip } from 'react-tooltip'
import dayjs from 'dayjs'
import warning from '../assets/warning.svg'
import success from '../assets/success.svg'
import { BsCalendar2Date } from 'react-icons/bs'
const AnneeScolaire = () => {
const theme = createTheme({
components: {
MuiIconButton: {
styleOverrides: {
root: {
color: 'gray' // Change the color of toolbar icons
}
}
},
MuiButton: {
styleOverrides: {
root: {
color: '#121212' // Change the color of toolbar icons
}
}
}
}
})
const [anneeScolaire, setAnneeScolaire] = useState([])
useEffect(() => {
window.anneescolaire.getAnneeScolaire().then((response) => {
setAnneeScolaire(response)
})
}, [])
const [isDeleted, setIsDeleted] = useState(false)
const [ids, setIds] = useState(0)
const column = [
{ field: 'code', headerName: 'Année Scolaire', width: 130 },
{ field: 'debut', headerName: 'Date de début', width: 130 },
{ field: 'fin', headerName: 'Date de fin', width: 130 },
{
field: 'action',
headerName: 'Action',
flex: 1,
renderCell: (params) => (
<div style={{ display: 'flex', gap: '10px' }}>
<Link to={`/anneescolaire/${params.value}`}>
<Button color="warning" variant="contained">
<FaPenToSquare
style={{ fontSize: '20px', color: 'white' }}
className={`update${params.value}`}
/>
<Tooltip
anchorSelect={`.update${params.value}`}
style={{ fontSize: '15px', zIndex: 22 }}
place="top"
>
Modifier
</Tooltip>
</Button>
</Link>
<Link
to={`#`}
onClick={() => {
setIds(params.row.id)
setOpen(true)
}}
>
<Button color="error" variant="contained">
<FaTrash
style={{ fontSize: '20px', color: 'white' }}
className={`delete${params.value}`}
/>
<Tooltip
anchorSelect={`.delete${params.value}`}
style={{ fontSize: '15px', zIndex: 22 }}
place="top"
>
Supprimer
</Tooltip>
</Button>
</Link>
</div>
)
},
{
field: 'current',
headerName: 'Année en cours',
flex: 1,
renderCell: (params) => (
<div style={{ display: 'flex', gap: '10px' }}>
<Link to={`#`}>
<Button color="success" variant="contained">
{params.value.current == 1 ? (
<FaCheck
style={{ fontSize: '20px', color: 'white' }}
className={`courrants${params.value.id}`}
/>
) : (
<FaSquare
style={{ fontSize: '20px', color: 'white' }}
className={`courrant${params.value.id}`}
onClick={() => setCurrent(params.value.id)}
/>
)}
</Button>
<Tooltip
anchorSelect={`.courrants${params.value.id}`}
style={{ fontSize: '15px', zIndex: 22 }}
place="top"
>
Année en cours
</Tooltip>
</Link>
</div>
)
}
]
const paginationModel = { page: 0, pageSize: 5 }
let data = anneeScolaire.map((annee) => ({
id: annee.id,
code: annee.code,
debut: dayjs(annee.debut).format('DD-MM-YYYY'),
fin: dayjs(annee.fin).format('DD-MM-YYYY'),
action: annee.id,
current: { current: annee.is_current, id: annee.id }
}))
const setCurrent = async (id) => {
// let response = await window.anneescolaire.setCurrent({id});
// console.log(response);
// if (response.changes) {
// window.anneescolaire.getAnneeScolaire().then((response) => {
// setAnneeScolaire(response);
// });
// }
}
const deleteButton = async (id) => {
let response = await window.anneescolaire.deleteAnneeScolaire({ id })
if (response.success) {
const updatedAnneeScolaire = anneeScolaire.filter((anneeScolaire) => anneeScolaire.id !== id)
setAnneeScolaire(updatedAnneeScolaire)
setIsDeleted(true)
}
}
const CustomToolbar = () => {
return (
<div>
<GridToolbarFilterButton />
</div>
)
}
/**
* hook to open modal
*/
const [open, setOpen] = useState(false)
/**
* function to close modal
*/
const handleClose = () => {
setOpen(false)
setIsDeleted(false)
}
/**
* function to return the view Modal
*
* @returns {JSX}
*/
const modals = () => (
<Modal
open={open}
onClose={handleClose}
aria-labelledby="modal-title"
aria-describedby="modal-description"
>
<Box
sx={{
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
width: 450,
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>Suprimer avec succèss</span>
</Typography>
) : (
<Typography
style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '10px' }}
>
<img src={warning} alt="" width={50} height={50} />{' '}
<span>Voulez vous supprimer cette année ?</span>
</Typography>
)}
<Box
sx={{
marginTop: '2%',
display: 'flex',
gap: '20px',
alignItems: 'end',
justifyContent: 'flex-end'
}}
>
{isDeleted ? (
<Button onClick={handleClose} color="warning" variant="contained">
OK
</Button>
) : (
<div>
<Button
onClick={() => deleteButton(ids)}
sx={{ mr: 1 }}
color="warning"
variant="contained"
>
Oui
</Button>
<Button onClick={handleClose} color="warning" variant="contained">
Non
</Button>
</div>
)}
</Box>
</Box>
</Modal>
)
return (
<div className={classe.mainHome}>
{modals()}
<div className={classeHome.header}>
<div className={classe.h1style}>
<div className={classeHome.blockTitle}>
<h1 style={{ display: 'flex', alignItems: 'center', gap: '10px' }}>
<BsCalendar2Date /> Année Scolaire
</h1>
<Link to={'/addanneescolaire'}>
<Button color="warning" variant="contained">
<FaCalendarPlus style={{ fontSize: '20px' }} /> Ajouter
</Button>
</Link>
</div>
</div>
</div>
<div className={classeHome.boxEtudiantsCard}>
<Paper
sx={{
width: '100%',
height: '100%', // Auto height to make the grid responsive
minHeight: 500, // Ensures a minimum height
display: 'flex',
flexDirection: 'column'
}}
>
<ThemeProvider theme={theme}>
<div
style={{
display: 'flex',
flexDirection: 'column', // Stacks content vertically on smaller screens,
width: '100%'
}}
>
<DataGrid
rows={data}
columns={column}
initialState={{ pagination: { paginationModel } }}
pageSizeOptions={[5, 10]}
sx={{
border: 0,
width: 'auto', // 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: CustomToolbar }}
localeText={frFR.components.MuiDataGrid.defaultProps.localeText}
/>
</div>
</ThemeProvider>
</Paper>
</div>
</div>
)
}
export default AnneeScolaire

68
src/renderer/src/components/Apropos.jsx

@ -0,0 +1,68 @@
import React from 'react'
import classe from '../assets/AllStyleComponents.module.css'
import classeHome from '../assets/Home.module.css'
import logo from '../assets/logo or.png'
import { Box } from '@mui/material'
import classeAdd from '../assets/AddStudent.module.css'
const Apropos = () => {
return (
<div className={classe.mainHome}>
<div className={classeHome.header}>
<div className={classe.h1style}>
<div className={classeHome.blockTitle}>
<h1>A propos</h1>
{/* contenu */}
<div className={classeHome.contenaire}>
<div className={classeAdd.boxEtudiantsCard}>
<Box
sx={{
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
width: 700,
borderRadius: '3%',
bgcolor: 'background.paper',
boxShadow: 24,
p: 4
}}
>
<Box
sx={{
marginTop: '5%',
display: 'flex',
gap: '10px',
alignItems: 'start',
flexDirection: 'column',
fontSize: 16,
fontFamily: 'sans-serif',
justifyContent: 'flex-end'
}}
>
<span style={{ display: 'flex', marginLeft: '30%' }}>
<img src={logo} alt="" height={250} width={250} />
</span>
<p style={{ color: 'black' }}>
Nom du Logiciel: CUniversity <br />
<br /> Description : logiciel de gestion d'espt <br /> <br />
Createur: CPAY COMPANY FOR MADAGASCAR <br />
<br /> Licence: A vie <br />
<br /> Contact: 0348415301
<br /> <br />
E-mail: director@c4m.mg
</p>
</Box>
</Box>
</div>
</div>
</div>
</div>
</div>
</div>
)
}
export default Apropos

527
src/renderer/src/components/ExportEtudiants.jsx

@ -0,0 +1,527 @@
import React, { useEffect, useState } from 'react'
import classe from '../assets/AllStyleComponents.module.css'
import classeAdd from '../assets/AddStudent.module.css'
import classeHome from '../assets/Home.module.css'
import { IoMdReturnRight } from 'react-icons/io'
import { Link } from 'react-router-dom'
import { FaFileExcel, FaCloudUploadAlt, FaCloudDownloadAlt } from 'react-icons/fa'
import { Box, Button, Typography, ThemeProvider, Modal } from '@mui/material'
import { styled, createTheme } from '@mui/material/styles'
import * as XLSX from 'xlsx'
import Papa from 'papaparse'
import { DataGrid, GridToolbarContainer, GridToolbarColumnsButton } from '@mui/x-data-grid'
import { frFR } from '@mui/x-data-grid/locales'
import dayjs from 'dayjs'
import CustomBar from './CustomBar'
import svgSuccess from '../assets/success.svg'
import svgError from '../assets/error.svg'
import { MenuItem, Select, FormControl, InputLabel } from '@mui/material'
const ExportEtudiants = () => {
const [tableData, setTableData] = useState([])
const [error, setError] = useState('')
const [isInserted, setIsinserted] = useState(true)
const [message, setMessage] = useState('')
const theme = createTheme({
components: {
MuiIconButton: {
styleOverrides: {
root: {
color: 'gray' // Toolbar icons color
}
}
},
MuiButton: {
styleOverrides: {
root: {
color: '#121212' // Button text color
}
}
}
}
})
const [header, setHeader] = useState([])
let field = []
const [dynamicColumns, setColumns] = useState([])
for (let index = 0; index < header.length; index++) {
field.push(header[index].toLowerCase().replace(/\s+/g, '_'))
}
useEffect(() => {
setColumns(
header.map((col, index) => ({
field: field[index], // Converts the header text to field names (e.g., "Nom" -> "nom")
headerName: col, // Display the header as is
width: 150 // Adjust the width as needed
}))
)
}, [header])
const paginationModel = { page: 0, pageSize: 5 }
// Assuming `tableData` is an array where each entry corresponds to a student's data
const dataRow = tableData.map((etudiant, index) => {
// Dynamically create an object with fields from the header and data from `etudiant`
let row = { id: index + 1 } // Unique ID for each row
field.forEach((fieldName, idx) => {
if (fieldName === 'date_de_naissance') {
row[fieldName] = dayjs(etudiant[idx]).format('DD-MM-YYYY') // Format date
} else {
row[fieldName] = etudiant[idx] // Assign value to field dynamically
}
})
return row
})
const VisuallyHiddenInput = styled('input')({
clip: 'rect(0 0 0 0)',
clipPath: 'inset(50%)',
height: 1,
overflow: 'hidden',
position: 'absolute',
bottom: 0,
left: 0,
whiteSpace: 'nowrap',
width: 1
})
const [files, setFiles] = useState()
const handleFileChange = (event) => {
const file = event.target.files[0]
setFiles(event.target.files[0])
if (!file) {
setError('No file selected')
return
}
const fileExtension = file.name.split('.').pop().toLowerCase()
if (fileExtension === 'xlsx') {
const reader = new FileReader()
reader.onload = (e) => {
const data = new Uint8Array(e.target.result)
const workbook = XLSX.read(data, { type: 'array' })
// Extract data from all sheets and combine
const allData = []
workbook.SheetNames.forEach((sheetName) => {
const worksheet = workbook.Sheets[sheetName]
const rows = XLSX.utils.sheet_to_json(worksheet, { header: 1 })
setHeader(rows[0]) // keep the header
allData.push(...rows.slice(1)) // Skip header row for each sheet
})
setTableData(allData)
}
reader.readAsArrayBuffer(file)
} else if (fileExtension === 'csv') {
const reader = new FileReader()
reader.onload = (e) => {
Papa.parse(e.target.result, {
complete: (results) => {
setHeader(results.data[0]) // keep the header
console.log(results.data)
setTableData(results.data.slice(1)) // Skip header row
},
header: false
})
}
reader.readAsText(file)
} else {
setError('Unsupported file format. Please upload .xlsx or .csv file.')
setTableData([])
}
}
/**
* fonction qui envoye dans le back
*/
const handleImport = async () => {
if (!files) {
setError("Veuillez choisir un fichier d'abord.")
return
}
try {
let response = await window.etudiants.importExcel(files.path)
console.log(response)
if (response.message) {
setMessage(response.message)
}
if (response.error) {
setIsinserted(false)
setOpen(true)
// Ne vide pas tableData, ça permet à l'utilisateur de corriger le fichier
} else {
setIsinserted(true)
setOpen(true)
setTableData([]) // vider seulement si insertion réussie
}
} catch (err) {
setMessage('Erreur inattendue lors de l’import')
setIsinserted(false)
setOpen(true)
}
}
/**
* hook to open modal
*/
const [open, setOpen] = useState(false)
/**
* function to close modal
*/
const handleClose = () => setOpen(false)
/**
* function to return the view Modal
*
* @returns {JSX}
*/
const modals = () => (
<Modal
open={open}
onClose={handleClose}
aria-labelledby="modal-title"
aria-describedby="modal-description"
>
<Box
sx={{
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
width: 450,
bgcolor: 'background.paper',
boxShadow: 24,
p: 4
}}
>
{isInserted ? (
<Typography
style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '10px' }}
>
<img src={svgSuccess} alt="" width={50} height={50} />{' '}
<span>Importation a été effectuée avec succès</span>
</Typography>
) : (
<Typography
style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '10px' }}
>
<img src={svgError} alt="" width={50} height={50} />{' '}
<span>
L'importation n'a pas été effectuée
<br />
{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>
</Modal>
)
const exemplaireFileExcel = [
{
nom: 'nom2',
prenom: 'prenom2',
niveau: 'L1',
date_naissance: 'jj/mm/AAAA',
annee_scolaire: '2024-2025',
mention: 'INFO',
num_inscription: 'azertyuiop',
sexe: 'F',
cin: '1234567890987',
date_de_delivrance: 'JJ/MM/AAAA',
nationaliter: 'Malagasy',
annee_baccalaureat: 'AAAA',
serie: 'D',
code_redoublement: 'N si nouveau, P si passant, R si redoublant RM si renvoyer, A si ancient',
boursier: 'Non',
domaine: "(S) Science de l'ingénieur",
contact: '0387205654'
},
{
nom: 'nom1',
prenom: 'prenom2',
niveau: 'L2',
date_naissance: 'jj/mm/AAAA',
annee_scolaire: '2024-2025',
mention: 'Informatique',
num_inscription: 'azertyuiop',
sexe: 'M',
cin: '1234567890987',
date_de_delivrance: 'JJ/MM/AAAA',
nationaliter: 'Malagasy',
annee_baccalaureat: 'AAAA',
serie: 'D',
code_redoublement: 'N si nouveau, P si passant, R si redoublant RM si renvoyer, A si ancient',
boursier: 'Non',
domaine: "(S) Science de l'ingénieur",
contact: '0387205654'
}
]
const convertToExcel = () => {
// convert json to sheet
const worksheet = XLSX.utils.json_to_sheet(exemplaireFileExcel)
// Create a new workbook and append the worksheet
const workbook = XLSX.utils.book_new()
XLSX.utils.book_append_sheet(workbook, worksheet, 'Sheet1')
// Write the workbook to a Blob and create a download link
const excelBuffer = XLSX.write(workbook, { bookType: 'xlsx', type: 'array' })
const data = new Blob([excelBuffer], { type: 'application/octet-stream' })
const url = URL.createObjectURL(data)
// Trigger a download
const link = document.createElement('a')
link.href = url
link.download = 'exemplaire_etudiant.xlsx'
link.click()
// Clean up
URL.revokeObjectURL(url)
}
const handleColumnVisibilityChange = (model) => {
// Get the currently visible columns
const visibleColumns = dynamicColumns.filter((column) => model[column.field] !== false)
// Create a new Excel file with visible columns
createExcelFile(visibleColumns)
}
const createExcelFile = (columns) => {
// Extract and set the header
const header = columns.reduce((acc, col) => {
acc[col.field] = col.headerName || col.field // Use headerName or field as default
return acc
}, {})
// Map the data rows to match the extracted headers
const worksheetData = dataRow.map((row) => {
const filteredRow = {}
columns.forEach((col) => {
const headerName = header[col.field]
filteredRow[headerName] = row[col.field]
})
return filteredRow
})
// Create a worksheet from the data
const ws = XLSX.utils.json_to_sheet(worksheetData)
// Create a workbook and add the worksheet to it
const wb = XLSX.utils.book_new()
XLSX.utils.book_append_sheet(wb, ws, 'Sheet1')
// Generate the Excel file as binary data
const excelFile = XLSX.write(wb, { bookType: 'xlsx', type: 'array' })
// Create a Blob for the Excel data
const blob = new Blob([excelFile], {
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
})
// Create a link element to trigger the download
const url = URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
link.download = 'original-file.xlsx' // Original file name or any desired name
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
URL.revokeObjectURL(url) // Clean up
}
// Handle header name change for a specific field and auto-export to Excel
const handleHeaderChange = (field, newHeaderName) => {
setColumns((prevColumns) =>
prevColumns.map((col) => (col.field === field ? { ...col, headerName: newHeaderName } : col))
)
}
return (
<div className={classe.mainHome}>
{modals()}
<div className={classeAdd.header}>
<div className={classe.h1style}>
<div className={classeHome.blockTitle}>
<h1 style={{ display: 'flex', alignItems: 'center', gap: '10px' }}>
<FaFileExcel /> Export
</h1>
<Link to={'/addstudent'}>
<Button color="warning" variant="contained">
<IoMdReturnRight style={{ fontSize: '20px' }} />
</Button>
</Link>
</div>
</div>
</div>
<Box
sx={{
// position: 'absolute',
// top: '52%',
// left: '52%',
// transform: 'translate(-50%, -50%)',
width: '99.5%',
borderRadius: '2%',
bgcolor: 'background.paper',
boxShadow: 24,
p: 4
}}
>
<Box sx={{ mt: 2, display: 'flex', alignItems: 'center', flexDirection: 'column' }}>
<div style={{ display: 'flex', gap: '20px', marginBottom: '20px' }}>
<Button
component="label"
variant="contained"
color="warning"
startIcon={<FaCloudUploadAlt />}
>
Charger un fichier excel
<VisuallyHiddenInput type="file" onChange={handleFileChange} accept=".xlsx, .csv" />
</Button>
<Button
component="label"
variant="contained"
color="error"
startIcon={<FaCloudDownloadAlt />}
onClick={convertToExcel}
>
Télécharger le modèle d'import
</Button>
</div>
{error && (
<Typography color="error" variant="body2">
{error}
</Typography>
)}
{tableData.length > 0 && (
<ThemeProvider theme={theme}>
<div
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
flexWrap: 'wrap'
}}
>
{/* Dropdowns for each column */}
{dynamicColumns.map((col) => (
<FormControl
key={col.field}
sx={{
m: 1,
width: '100%',
'& .MuiOutlinedInput-root': {
'&:hover fieldset': {
borderColor: '#ff9800' // Set the border color on hover
}
}
}}
size="small"
color="warning"
variant="outlined"
style={{ margin: 10, width: 200 }}
>
<InputLabel id={col.headerName}>{col.headerName}</InputLabel>
<Select
labelId={col.headerName}
value={col.headerName}
label={col.headerName}
color="warning"
name={col.headerName}
onChange={(e) => handleHeaderChange(col.field, e.target.value)}
>
<MenuItem value={col.headerName}>{col.headerName}</MenuItem>
<MenuItem value={'nom'}>nom</MenuItem>
<MenuItem value={'prenom'}>prenom</MenuItem>
<MenuItem value={`niveau`}>niveau</MenuItem>
<MenuItem value={`date_naissance`}>date de naissance</MenuItem>
<MenuItem value={`annee_scolaire`}>année scolaire</MenuItem>
<MenuItem value={`mention`}>mention</MenuItem>
<MenuItem value={`num_inscription`}>numéro d'inscription</MenuItem>
<MenuItem value={`nationalite`}>Nationaliter</MenuItem>
<MenuItem value={`sexe`}>Sexe</MenuItem>
<MenuItem value={`cin`}>CIN</MenuItem>
<MenuItem value={`date_de_livraison`}>Date de livraison</MenuItem>
<MenuItem value={`annee_baccalaureat`}>Année du baccalaureat</MenuItem>
<MenuItem value={`serie`}>Série</MenuItem>
<MenuItem value={`code_redoublement`}>Code redoublement</MenuItem>
<MenuItem value={`boursier`}>Boursier</MenuItem>
<MenuItem value={`domaine`}>Domaine</MenuItem>
<MenuItem value={`contact`}>Contact</MenuItem>
<MenuItem value={`parcours`}>Parcours</MenuItem>
</Select>
</FormControl>
))}
</div>
<div
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'column',
width: '100%'
}}
>
<DataGrid
rows={dataRow}
columns={dynamicColumns}
initialState={{ pagination: { paginationModel } }}
onColumnVisibilityModelChange={handleColumnVisibilityChange} // Listen for column visibility changes
pageSizeOptions={[5, 10]}
disableRowSelectionOnClick
sx={{
width: '100%',
height: '100%',
minHeight: 400,
maxWidth: '100%', // Prevents grid from exceeding the container width
'@media (max-width: 600px)': {
width: '100%',
height: 'auto', // Adjust height for smaller screens
fontSize: '0.8rem' // Smaller font size for better readability on small screens
},
'@media (max-width: 960px)': {
fontSize: '1rem' // Adjust font size for medium screens
}
}}
slots={{ toolbar: CustomBar }}
slotProps={{ toolbar: { onImport: handleImport } }}
localeText={frFR.components.MuiDataGrid.defaultProps.localeText}
/>
</div>
</ThemeProvider>
)}
</Box>
</Box>
</div>
)
}
export default ExportEtudiants

564
src/renderer/src/components/Matieres.jsx

@ -0,0 +1,564 @@
import React, { useEffect, useRef, useState } from 'react'
import classe from '../assets/AllStyleComponents.module.css'
import classeAdd from '../assets/AddStudent.module.css'
import classeHome from '../assets/Home.module.css'
import { Link } from 'react-router-dom'
import { BsBookmarkPlusFill } from 'react-icons/bs'
import {
FaUserCircle,
FaBook,
FaUserCog,
FaTrash,
FaPlus,
FaRegPlusSquare,
FaNewspaper
} from 'react-icons/fa'
import { Box, Button, InputAdornment, Typography, Modal, TextField, Grid } from '@mui/material'
import Paper from '@mui/material/Paper'
import { createTheme, ThemeProvider } from '@mui/material/styles'
import { DataGrid, GridToolbar } from '@mui/x-data-grid'
import { frFR } from '@mui/x-data-grid/locales'
import { Fa1, Fa2, Fa3, FaM, FaP, FaPenToSquare, FaS } from 'react-icons/fa6'
import { Tooltip } from 'react-tooltip'
import warning from '../assets/warning.svg'
import success from '../assets/success.svg'
import { GiTeacher } from 'react-icons/gi'
import ModalAddProf from './ModalAddProf'
import ParcourMatiere from './ParcourMatiere'
import UpdateModalProf from './UpdateModalProf'
import ModalProcessFichePresence from './ModalProcessFichePresence'
import ModalExportFichr from './ModalExportFichr'
import NiveauMatiere from './NiveauMatiere'
const Matieres = () => {
const [matiere, setMatiere] = useState([])
const [openAssignNiveau, setOpenAssignNiveau] = useState(false)
const [matiereIdAssign, setMatiereIdAssign] = useState(null)
const [Enseignants, setEnseignants] = useState([])
useEffect(() => {
window.matieres.getMatiere().then((response) => {
setMatiere(response)
})
window.matieres.getENseignant().then((response) => {
setEnseignants(response)
})
}, [])
const theme = createTheme({
components: {
MuiIconButton: {
styleOverrides: {
root: {
color: 'gray' // Change the color of toolbar icons
}
}
},
MuiButton: {
styleOverrides: {
root: {
color: '#121212' // Change the color of toolbar icons
}
}
}
}
})
const openAssignNiveauModal = (id) => {
setMatiereIdAssign(id)
setOpenAssignNiveau(true)
}
const closeAssignNiveauModal = () => {
setOpenAssignNiveau(false)
setMatiereIdAssign(null)
}
const handleNiveauAssignSuccess = (status) => {
if (status) {
// Rafraîchir les données des matières et enseignants
window.matieres.getMatiere().then((response) => {
setMatiere(response)
})
window.matieres.getENseignant().then((response) => {
setEnseignants(response)
})
}
}
const [isDeleted, setIsDeleted] = useState(false)
const [ids, setIds] = useState(0)
const columns = [
{ field: 'nom', headerName: 'Nom', width: 230 },
{ field: 'credit', headerName: 'Crédit', width: 80 },
{ field: 'heure', headerName: 'Heure', width: 80 },
{ field: 'uniter', headerName: "Unité d'enseignement", width: 180 },
{ field: 'ue', headerName: 'UE', width: 80 },
{ field: 'niveau_id', headerName: 'Niveau', width: 80 },
{ field: 'enseignant', headerName: 'Professeur actuele', width: 230 },
{
field: 'action',
headerName: 'Action',
width: 600,
renderCell: (params) => (
<div style={{ display: 'flex', gap: '10px' }}>
<Link to={`/singlematiere/${params.value}`}>
<Button color="warning" variant="contained">
<FaPenToSquare
style={{ fontSize: '20px', color: 'white' }}
className={`update${params.value}`}
/>
<Tooltip
anchorSelect={`.update${params.value}`}
style={{ fontSize: '15px', zIndex: 22 }}
place="top"
>
Modifier
</Tooltip>
</Button>
</Link>
<Link to={`/asignmatiere/${params.value}`}>
<Button color="warning" variant="contained">
<FaM
className={`asignMention${params.value}`}
style={{ fontSize: '20px', color: 'white' }}
/>
<Tooltip
anchorSelect={`.asignMention${params.value}`}
style={{ fontSize: '15px', zIndex: 9 }}
place="top"
>
Assigner a un mension
</Tooltip>
</Button>
</Link>
<Link
to={`/asignmatieresemestre/${params.value}`}
className={`sem${params.value}`}
style={{ textDecoration: 'none' }}
>
<Tooltip
anchorSelect={`.sem${params.value}`}
style={{ fontSize: '15px', zIndex: 9 }}
place="top"
>
Assigner à un semestre
</Tooltip>
<Button color="warning" variant="contained">
<FaS style={{ fontSize: '20px', color: 'white' }} />
</Button>
</Link>
{/* ----------- BOUTON ASSIGNER NIVEAU ----------- */}
<Link
to="#"
className={`niveau${params.value}`}
style={{ textDecoration: 'none' }}
onClick={(e) => {
e.preventDefault(); // empêche le # de provoquer un scroll
openAssignNiveauModal(params.value);
}}
>
<Tooltip
anchorSelect={`.niveau${params.value}`}
style={{ fontSize: '15px', zIndex: 9 }}
place="top"
>
Assigner à un niveau
</Tooltip>
<Button color="warning" variant="contained">
{/* Icône de ton choix, ici FaRegPlusSquare par exemple */}
<FaRegPlusSquare style={{ fontSize: '20px', color: 'white' }} />
</Button>
</Link>
<Link
to="#"
style={{ textDecoration: 'none' }}
className={`parcour${params.value}`}
onClick={() => openParcoursFunction(params.value)}
>
<Tooltip
anchorSelect={`.parcour${params.value}`}
style={{ fontSize: '15px', zIndex: 9 }}
place="top"
>
Assigner à des parcours
</Tooltip>
<Button color="warning" variant="contained">
<FaP style={{ fontSize: '20px', color: 'white' }} />
</Button>
</Link>
<Link to={`#`}>
<Button color="warning" variant="contained">
<GiTeacher
style={{ fontSize: '20px', color: 'white' }}
className={`asignProf${params.value}`}
/>
<Tooltip
anchorSelect={`.asignProf${params.value}`}
style={{ fontSize: '15px', zIndex: 22 }}
place="top"
clickable
>
<Link style={{ zIndex: 99, display: 'flex', alignItems: 'center', gap: '10px' }}>
<Button
color="warning"
variant="contained"
onClick={() => openFormModal(params.value)}
>
<FaPlus
style={{ fontSize: '20px', color: 'white' }}
className={`adprofModal${params.value}`}
/>
<Tooltip
anchorSelect={`.adprofModal${params.value}`}
style={{ fontSize: '15px', zIndex: 9 }}
place="top"
>
Ajouter un nouveau professeur
</Tooltip>
</Button>
<Button
color="warning"
variant="contained"
onClick={() => openUppdateProfFunction(params.value)}
>
<FaPenToSquare
style={{ fontSize: '20px', color: 'white' }}
className={`updateprofModal${params.value}`}
/>
<Tooltip
anchorSelect={`.updateprofModal${params.value}`}
style={{ fontSize: '15px', zIndex: 9 }}
place="top"
>
Modifier un professeur
</Tooltip>
</Button>
</Link>
</Tooltip>
</Button>
</Link>
<Link to={`/fiche/${params.value}/${params.row.nom}`}>
<Tooltip
anchorSelect={`.presence${params.value}`}
style={{ fontSize: '15px', zIndex: 22 }}
place="top"
>
Fiche de presence éxamen
</Tooltip>
<Button color="warning" variant="contained">
<FaNewspaper
style={{ fontSize: '20px', color: 'white' }}
className={`presence${params.value}`}
/>
</Button>
</Link>
<Link
to={`#`}
onClick={() => {
setIds(params.row.id)
setOpen(true)
}}
>
<Button color="error" variant="contained">
<FaTrash
style={{ fontSize: '20px', color: 'white' }}
className={`supprimer${params.value}`}
/>
<Tooltip
anchorSelect={`.supprimer${params.value}`}
style={{ fontSize: '15px', zIndex: 22 }}
place="top"
>
Supprimer
</Tooltip>
</Button>
</Link>
</div>
)
}
]
const compareMatieres = (matiere_id) => {
let NomPrenom = ''
for (let index = 0; index < Enseignants.length; index++) {
if (Enseignants[index].matiere_id == matiere_id) {
NomPrenom = `${Enseignants[index].nom_enseignant} ${Enseignants[index].prenom_enseignant}`
}
}
return NomPrenom
}
const paginationModel = { page: 0, pageSize: 5 }
let dataRow = matiere.map((mat) => ({
id: mat.id, // Ensure this exists and is unique for each etudiant
nom: mat.nom,
credit: mat.credit,
heure: mat.heure,
uniter: mat.unite_enseignement,
ue: mat.ue,
niveau_id: mat.niveau_nom ?? 'pas de niveau',
enseignant:
compareMatieres(mat.id) != '' ? compareMatieres(mat.id) : 'Veuillez assigner un professeur',
action: mat.id // Ensure this is a valid URL for the image
}))
const deleteButton = async (id) => {
let response = await window.matieres.deleteMatiere(id);
if (response.success) {
const updatedMatieres = matiere.filter((matiere) => matiere.id !== id)
setMatiere(updatedMatieres)
setIsDeleted(true)
}
}
/**
* hook to open modal
*/
const [open, setOpen] = useState(false)
/**
* function to close modal
*/
const handleClose = () => {
setOpen(false)
setIsDeleted(false)
}
/**
* function to return the view Modal
*
* @returns {JSX}
*/
const modals = () => (
<Modal
open={open}
onClose={handleClose}
aria-labelledby="modal-title"
aria-describedby="modal-description"
>
<Box
sx={{
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
width: 450,
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>Suprimer avec succèss</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 matiere ?</span>
</Typography>
)}
<Box
sx={{
marginTop: '2%',
display: 'flex',
gap: '20px',
alignItems: 'end',
justifyContent: 'flex-end'
}}
>
{isDeleted ? (
<Button onClick={handleClose} color="warning" variant="contained">
OK
</Button>
) : (
<div>
<Button
onClick={() => deleteButton(ids)}
sx={{ mr: 1 }}
color="warning"
variant="contained"
>
Oui
</Button>
<Button onClick={handleClose} color="warning" variant="contained">
Non
</Button>
</div>
)}
</Box>
</Box>
</Modal>
)
const [openForm, setOpenForm] = useState(false)
const [matiereId, setMatiereId] = useState('')
const [isSubmitted, setIsSubmitted] = useState(false)
// Callback function to receive the submission status
const handleFormSubmit = (status) => {
setIsSubmitted(status)
}
useEffect(() => {
if (isSubmitted) {
window.matieres
.getMatiere()
.then((response) => {
setMatiere(response)
setIsSubmitted(false) // Reset isSubmitted after fetching data
})
.catch((error) => {
console.error('Error fetching matiere:', error)
setIsSubmitted(false) // Ensure reset even on error
})
window.matieres.getENseignant().then((response) => {
setEnseignants(response)
})
}
}, [isSubmitted])
const closeForm = () => {
setOpenForm(false)
}
const openFormModal = (id) => {
setOpenForm(true)
setMatiereId(id)
}
const [openParcours, setOpenParcours] = useState(false)
const onCloseParcours = () => setOpenParcours(false)
const [idToSend, setIdToSend] = useState(null)
const openParcoursFunction = (id) => {
setIdToSend(id)
setOpenParcours(true)
}
const [openUppdateProf, setOpenUpdateProf] = useState(false)
const onCloseUpdateProf = () => setOpenUpdateProf(false)
const [idSends, setIdSend] = useState('')
const openUppdateProfFunction = (id) => {
setIdSend(id)
setOpenUpdateProf(true)
}
const [openFiche, setOpenFiche] = useState(false)
const onCloseFiche = () => setOpenFiche(false)
const [dataFiche, setDataFiche] = useState(null)
const paperRef = useRef()
// const extractFiche = async (matiere_id) => {
// let response = await
// setDataFiche(response)
// }
return (
<div className={classe.mainHome}>
{modals()}
<ModalAddProf
open={openForm}
onClose={closeForm}
matiere_id={matiereId}
onSubmitSuccess={handleFormSubmit}
/>
<ParcourMatiere matiere_id={idToSend} onClose={onCloseParcours} open={openParcours} />
<UpdateModalProf
matiere_id={idSends}
onClose={onCloseUpdateProf}
onSubmitSuccess={handleFormSubmit}
open={openUppdateProf}
/>
<ModalProcessFichePresence matiere_id={matiereId} onClose={onCloseFiche} open={openFiche} />
{/* Modal NiveauMatiere */}
<NiveauMatiere
open={openAssignNiveau}
onClose={closeAssignNiveauModal}
matiere_id={matiereIdAssign}
onSubmitSuccess={handleNiveauAssignSuccess}
/>
<div className={classeAdd.header}>
<div className={classe.h1style}>
<div className={classeHome.blockTitle}>
<h1 style={{ display: 'flex', alignItems: 'center', gap: '10px' }}>
<FaBook />
Matiere
</h1>
<Link to={'/addmatiere'}>
<Button color="warning" variant="contained">
<BsBookmarkPlusFill style={{ fontSize: '20px' }} /> AJouter
</Button>
</Link>
</div>
</div>
</div>
{/* displaying data */}
<div className={classeHome.boxEtudiantsCard}>
<div style={{ display: 'flex', justifyContent: 'center' }}>
<Paper
sx={{
height: 'auto', // Auto height to make the grid responsive
minHeight: 500, // Ensures a minimum height
display: 'flex',
// alignItems: "center",
// justifyContent: "center",
width: '100%'
}}
>
<ThemeProvider theme={theme}>
<div
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'column', // Stacks content vertically on smaller screens
width: '100%'
}}
>
<DataGrid
rows={dataRow}
columns={columns}
initialState={{ pagination: { paginationModel } }}
pageSizeOptions={[5, 10]}
sx={{
border: 0,
width: '100%', // Ensures the DataGrid takes full width
height: '100%', // 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>
)
}
export default Matieres

220
src/renderer/src/components/ModalExportFichr.jsx

@ -0,0 +1,220 @@
import React, { useEffect, useRef, useState } from 'react'
import { Link, useParams } from 'react-router-dom'
import classe from '../assets/AllStyleComponents.module.css'
import classeAdd from '../assets/AddStudent.module.css'
import classeHome from '../assets/Home.module.css'
import { FaBook, FaDownload } from 'react-icons/fa'
import { IoMdReturnRight } from 'react-icons/io'
import { Box, Button, Typography, ThemeProvider, Modal } from '@mui/material'
import Paper from '@mui/material/Paper'
import html2cancas from 'html2canvas'
import jsPDF from 'jspdf'
import autoTable from 'jspdf-autotable'
const ModalExportFichr = () => {
const [fiche, setFiche] = useState([])
const [mentions, setMentions] = useState([])
const PaperRef = useRef()
let { matiere_id } = useParams()
let { nom } = useParams()
useEffect(() => {
if (matiere_id !== null && matiere_id !== undefined) {
window.notesysteme.extractFiches({ matiere_id }).then((response) => {
setFiche(response)
})
}
window.mention.getMention().then((response) => {
setMentions(response)
})
}, [matiere_id])
// Sort by NomPrenom alphabetically
const sortedStudents = fiche.sort((a, b) => a.nom.localeCompare(b.nom))
function compareMention(id) {
let mentionText
mentions.map((ment) => {
if (id == ment.id) {
mentionText = ment.nom
}
})
return mentionText
}
// const download = () => {
// const generatePDF = async () => {
// try {
// await new Promise((resolve) => setTimeout(resolve, 500));
// const canvas = await html2cancas(PaperRef.current, {
// scale: 2,
// useCORS: true,
// logging: false,
// backgroundColor: "#ffffff",
// imageTimeout: 0, // Prevent timeout errors
// });
// const imgData = canvas.toDataURL("image/jpeg", 0.8); // Use JPEG for smaller size
// const pdf = new jsPDF({
// orientation: "portrait",
// unit: "mm",
// format: "a4",
// compress: true,
// });
// let imgWidth = 210;
// let imgHeight = (canvas.height * imgWidth) / canvas.width;
// let yPosition = 0;
// // If image height is greater than A4 page height, add multiple pages
// while (yPosition < imgHeight) {
// pdf.addImage(imgData, "JPEG", 0, yPosition * -1, imgWidth, imgHeight);
// if (yPosition + 297 < imgHeight) pdf.addPage(); // A4 height = 297mm
// yPosition += 297;
// }
// pdf.save("document.pdf");
// } catch (error) {
// console.error("Error generating PDF:", error);
// }
// };
// generatePDF();
// }
const download = () => {
const generatePDF = () => {
try {
const pdf = new jsPDF({
orientation: 'portrait',
unit: 'mm',
format: 'a4'
})
// Select the table
autoTable(pdf, {
html: '#myTable', // ID de la table
startY: 20,
theme: 'grid',
headStyles: {
fillColor: 'gray',
halign: 'center',
fontStyle: 'bold',
textColor: 'black'
}, // Supprimer la couleur et centrer
margin: { top: 10 },
styles: { fontSize: 8, cellPadding: 2, halign: 'center' }, // Centrer le texte des cellules
didDrawPage: (data) => {
pdf.text('', 14, 10)
}
})
pdf.save('document.pdf')
} catch (error) {
console.error('Error generating PDF:', error)
}
}
generatePDF()
}
return (
<div className={classe.mainHome}>
<div className={classeAdd.header}>
<div className={classe.h1style}>
<div className={classeHome.blockTitle}>
<h1 style={{ display: 'flex', alignItems: 'center', gap: '10px' }}>
<FaBook />
Matiere
</h1>
<div style={{ display: 'flex', gap: '10px' }}>
<Link to={'#'} onClick={download}>
<Button color="warning" variant="contained">
<FaDownload style={{ fontSize: '20px' }} />
</Button>
</Link>
<Link to={'/matiere'}>
<Button color="warning" variant="contained">
<IoMdReturnRight style={{ fontSize: '20px' }} />
</Button>
</Link>
</div>
</div>
</div>
</div>
<div className={classeHome.boxEtudiantsCard}>
<Paper
sx={{
// alignItems: "center",
// justifyContent: "center",
width: '100%'
}}
ref={PaperRef}
>
<div
style={{
padding: '10px',
textAlign: 'center',
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}}
>
<table style={{ border: 'solid 1px gray', fontSize: '20px' }} id="myTable">
<thead>
<tr style={{ textAlign: 'center', borderBottom: 'solid 1px gray' }}>
<th colSpan={4}> {nom} </th>
</tr>
<tr style={{ borderBottom: 'solid 1px gray' }}>
<th style={{ borderRight: 'solid 1px gray' }}>N°</th>
<th style={{ borderRight: 'solid 1px gray' }}>Nom et Prenom</th>
<th style={{ borderRight: 'solid 1px gray' }}>Mention</th>
<th>Emergement</th>
</tr>
</thead>
<tbody>
{sortedStudents.map((fi, index) => (
<tr key={index}>
<td
style={{
borderRight: 'solid 1px gray',
borderBottom: 'solid 1px gray',
padding: '5px'
}}
>
{index + 1}
</td>
<td
style={{
borderRight: 'solid 1px gray',
borderBottom: 'solid 1px gray',
padding: '5px'
}}
>
{fi.nom} {fi.prenom}
</td>
<td
style={{
borderRight: 'solid 1px gray',
borderBottom: 'solid 1px gray',
padding: '5px'
}}
>
{compareMention(fi.mention_id)}
</td>
<td style={{ borderBottom: 'solid 1px gray', padding: '5px' }}></td>
</tr>
))}
</tbody>
</table>
</div>
</Paper>
</div>
</div>
)
}
export default ModalExportFichr

132
src/renderer/src/components/NiveauMatiere.jsx

@ -0,0 +1,132 @@
import React, { useEffect, useState } from 'react';
import {
Dialog,
DialogActions,
DialogContent,
DialogTitle,
Button,
Box,
Grid,
FormControl,
InputLabel,
Select,
OutlinedInput,
MenuItem
} from '@mui/material'
const NiveauMatiere = ({ open, onClose, matiere_id }) => {
const [formData, setFormData] = useState({
niveau_id: '',
id: ''
})
const [niveaux, setNiveaux] = useState([])
const [niveauxMatiere, setNiveauxMatiere] = useState([])
console.log(niveaux);
useEffect(() => {
window.niveaus.getNiveau().then((response) => {
setNiveaux(response)
})
}, [])
useEffect(() => {
if (niveauxMatiere.length !== 0) {
const niveauIds = niveauxMatiere.map((item) => item.niveau_id)
setFormData((prevState) => ({
...prevState,
niveau_id: niveauIds
}))
}
}, [niveauxMatiere])
useEffect(() => {
if (matiere_id) {
setFormData(prev => ({
...prev,
id: matiere_id
}));
}
}, [matiere_id]);
const handleChange = (event) => {
const { name, value } = event.target
setFormData(prevState => ({
...prevState,
niveau_id: value // pas de tableau
}));
}
const formSubmit = async (e) => {
e.preventDefault();
console.log("Form envoyé côté front:", formData);
let response = await window.matieres.updateMatiereNiveau(formData);
console.log("Réponse backend:", response);
if (response.success) {
onClose();
}
};
return (
<Dialog open={open} onClose={onClose}>
<form action="" onSubmit={formSubmit}>
<DialogTitle>Assignation à des niveaux</DialogTitle>
<DialogContent>
<Box sx={{ flexGrow: 1 }}>
<Grid container spacing={2}>
<Grid item xs={12} sm={6}>
<FormControl sx={{ m: 1, width: 300 }}>
<InputLabel id="niveaux-select-label" color="warning">
Niveaux
</InputLabel>
<Select
labelId="niveaux-select-label"
id="niveaux-select"
name="niveau_id"
value={formData.niveau_id || ''}
onChange={handleChange}
color="warning"
size="small"
required
input={<OutlinedInput label="Niveaux" />}
MenuProps={{
PaperProps: {
style: {
maxHeight: 200,
width: 250
}
}
}}
>
{niveaux.map((niveau) => (
<MenuItem key={niveau.niveau_id} value={niveau.niveau_id}>
{niveau.nom}
</MenuItem>
))}
</Select>
</FormControl>
</Grid>
</Grid>
</Box>
</DialogContent>
<DialogActions>
<Button onClick={onClose} color="error">
Annuler
</Button>
<Button type="submit" color="warning">
Soumettre
</Button>
</DialogActions>
</form>
</Dialog>
)
}
export default NiveauMatiere

130
src/renderer/src/components/ParcourMatiere.jsx

@ -0,0 +1,130 @@
import React, { useEffect, useState } from 'react';
import {
Dialog,
DialogActions,
DialogContent,
DialogTitle,
TextField,
Button,
InputAdornment,
Box,
Grid,
FormControl,
InputLabel,
Select,
OutlinedInput,
MenuItem
} from '@mui/material'
const ParcourMatiere = ({ open, onClose, matiere_id }) => {
const [formData, setFormData] = useState({
parcour_id: [],
matiere_id: ''
})
const [parcours, setParcours] = useState([])
const [parcoursMatiere, setParcoursMatiere] = useState([])
useEffect(() => {
window.notesysteme.getParcours().then((response) => {
setParcours(response)
})
}, [])
useEffect(() => {
if (matiere_id) {
setFormData({
matiere_id: matiere_id
})
window.notesysteme.getParcourMatiere({ matiere_id }).then((response) => {
setParcoursMatiere(response)
})
}
}, [matiere_id])
useEffect(() => {
if (parcoursMatiere.length !== 0) {
const parcourIds = parcoursMatiere.map((item) => item.parcour_id)
setFormData((prevState) => ({
...prevState,
parcour_id: parcourIds // Merge & remove duplicates
}))
}
}, [parcoursMatiere])
const handleChange = (event) => {
const { name, value } = event.target
setFormData((prevState) => ({
...prevState,
[name]: value // Ensures multiple selection works correctly
}))
}
const formSubmit = async (e) => {
e.preventDefault()
let response = await window.notesysteme.parcourMatiere(formData)
if (response.success) {
onClose()
}
}
return (
<Dialog open={open} onClose={onClose}>
<form action="" onSubmit={formSubmit}>
<DialogTitle>Assignation à des parcours</DialogTitle>
<DialogContent>
<Box sx={{ flexGrow: 1 }}>
<Grid container spacing={2}>
<Grid item xs={12} sm={6}>
<FormControl sx={{ m: 1, width: 300 }}>
<InputLabel id="parcours-select-label" color="warning">
Parcours
</InputLabel>
<Select
labelId="parcours-select-label"
id="parcours-select"
multiple
name="parcour_id"
value={formData.parcour_id || []}
onChange={handleChange}
color="warning"
size="small"
required
input={<OutlinedInput label="Parcours" />} // Fixed label name
MenuProps={{
PaperProps: {
style: {
maxHeight: 200, // Limit dropdown height
width: 250 // Adjust dropdown width if needed
}
}
}}
>
{parcours.map((sem) => (
<MenuItem key={sem.id} value={sem.id}>
{sem.nom}
</MenuItem>
))}
</Select>
</FormControl>
</Grid>
</Grid>
</Box>
</DialogContent>
<DialogActions>
<Button onClick={onClose} color="error">
Annuler
</Button>
<Button type="submit" color="warning">
Soumettre
</Button>
</DialogActions>
</form>
</Dialog>
)
}
export default ParcourMatiere

199
src/renderer/src/components/Parcours.jsx

@ -0,0 +1,199 @@
import React, { useEffect, useState } from 'react'
import classe from '../assets/AllStyleComponents.module.css'
import classeAdd from '../assets/AddStudent.module.css'
import classeHome from '../assets/Home.module.css'
import { Link } from 'react-router-dom'
import { MdRule } from 'react-icons/md'
import { FaPlus } from 'react-icons/fa'
import { Box, Button, InputAdornment, Typography, Modal, TextField, Grid } from '@mui/material'
import Paper from '@mui/material/Paper'
import { createTheme, ThemeProvider } from '@mui/material/styles'
import { DataGrid, GridToolbar } from '@mui/x-data-grid'
import { frFR } from '@mui/x-data-grid/locales'
import { Tooltip } from 'react-tooltip'
import warning from '../assets/warning.svg'
import success from '../assets/success.svg'
import AddParcours from './AddParcours'
import UpdateParcour from './UpdateParcour'
import { FaPenToSquare } from 'react-icons/fa6'
const Parcours = () => {
const [parcours, setParcours] = useState([])
useEffect(() => {
window.notesysteme.getParcours().then((response) => {
setParcours(response)
})
}, [])
const theme = createTheme({
components: {
MuiIconButton: {
styleOverrides: {
root: {
color: 'gray' // Change the color of toolbar icons
}
}
},
MuiButton: {
styleOverrides: {
root: {
color: '#121212' // Change the color of toolbar icons
}
}
}
}
})
const columns = [
{ field: 'nom', headerName: 'Nom', width: 230 },
{ field: 'uniter', headerName: 'Uniter', width: 180 },
{ field: 'mention', headerName: 'Mention', width: 230 },
{
field: 'action',
headerName: 'Action',
flex: 1,
renderCell: (params) => (
<div style={{ display: 'flex', gap: '10px' }}>
<Link to={`#`} onClick={() => openUpdate(params.value)}>
<Button color="warning" variant="contained">
<FaPenToSquare
style={{ fontSize: '20px', color: 'white', outline: 'none' }}
className={`update${params.value}`}
/>
<Tooltip
anchorSelect={`.update${params.value}`}
style={{ fontSize: '15px', zIndex: 22 }}
place="top"
>
Modifier
</Tooltip>
</Button>
</Link>
</div>
)
}
]
const paginationModel = { page: 0, pageSize: 5 }
const dataRow = parcours.map((parc) => ({
id: parc.id,
nom: parc.nom,
uniter: parc.uniter,
mention: parc.mention_nom || parc.mention || 'Mention non trouvée',
action: parc.id
}))
const [isSubmitted, setIsSubmitted] = useState(false)
const handleFormSubmit = (status) => {
setIsSubmitted(status)
}
useEffect(() => {
if (isSubmitted) {
window.notesysteme.getParcours().then((response) => {
setParcours(response)
})
setIsSubmitted(false)
}
}, [isSubmitted])
const [openModalAdd, setOpenModalAdd] = useState(false)
const [openModalUpdate, setOpenModalUpdate] = useState(false)
const [idToSend, setIdToSend] = useState(null)
const closeUpdate = () => {
setOpenModalUpdate(false)
}
const openUpdate = (id) => {
setIdToSend(id)
setOpenModalUpdate(true)
}
const openModalAddFunction = () => {
setOpenModalAdd(true)
}
const closeModalAdd = () => {
setOpenModalAdd(false)
}
return (
<div className={classe.mainHome}>
<AddParcours open={openModalAdd} onClose={closeModalAdd} onSubmitSuccess={handleFormSubmit} />
<UpdateParcour
id={idToSend}
onClose={closeUpdate}
onSubmitSuccess={handleFormSubmit}
open={openModalUpdate}
/>
<div className={classeAdd.header}>
<div className={classe.h1style}>
<div className={classeHome.blockTitle}>
<h1 style={{ display: 'flex', alignItems: 'center', gap: '10px' }}>
<MdRule />
Parcours
</h1>
<Link to={'#'} onClick={openModalAddFunction}>
<Button color="warning" variant="contained">
<FaPlus style={{ fontSize: '20px' }} /> AJouter
</Button>
</Link>
</div>
</div>
</div>
<div className={classeHome.boxEtudiantsCard}>
<div style={{ display: 'flex', justifyContent: 'center' }}>
<Paper
sx={{
height: 'auto', // Auto height to make the grid responsive
minHeight: 500, // Ensures a minimum height
display: 'flex',
// alignItems: "center",
// justifyContent: "center",
width: '100%'
}}
>
<ThemeProvider theme={theme}>
<div
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'column', // Stacks content vertically on smaller screens
width: '100%'
}}
>
<DataGrid
rows={dataRow}
columns={columns}
initialState={{ pagination: { paginationModel } }}
pageSizeOptions={[5, 10]}
sx={{
border: 0,
width: '100%', // Ensures the DataGrid takes full width
height: '100%', // 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>
)
}
export default Parcours

218
src/renderer/src/components/Resultat.jsx

@ -0,0 +1,218 @@
import React, { useEffect, useState } from 'react'
import { useParams, Link } from 'react-router-dom'
import classe from '../assets/AllStyleComponents.module.css'
import classeHome from '../assets/Home.module.css'
import Paper from '@mui/material/Paper'
import { Button, Modal, Box } from '@mui/material'
import { IoMdReturnRight } from 'react-icons/io'
import jsPDF from 'jspdf'
import autoTable from 'jspdf-autotable'
import { FaDownload } from 'react-icons/fa'
const Resultat = () => {
const { niveau, scolaire } = useParams()
const formData = {
niveau,
scolaire
}
const [etudiants, setEtudiants] = useState([])
const [mention, setMention] = useState([])
const [session, setSession] = useState([])
useEffect(() => {
window.notes.getMoyenne(formData).then((response) => {
setEtudiants(response)
})
window.noteRepech.getMoyenneRepech(formData).then((response) => {
setSession(response)
})
window.mention.getMention().then((response) => {
setMention(response)
})
}, [])
let dataToMap = []
function returnmention(id) {
let mentions
for (let index = 0; index < mention.length; index++) {
if (mention[index].id == id) {
mentions = mention[index].nom
}
}
return mentions
}
function checkNull(params) {
if (params == null || params == undefined) {
return null
}
return params
}
const print = () => {
const generatePDF = () => {
try {
const pdf = new jsPDF({
orientation: 'portrait',
unit: 'mm',
format: 'a4'
})
// Select the table
autoTable(pdf, {
html: '#myTable2', // ID de la table
startY: 20,
theme: 'grid',
headStyles: {
fillColor: 'gray',
halign: 'center',
fontStyle: 'bold',
textColor: 'black'
}, // Supprimer la couleur et centrer
margin: { top: 10 },
styles: { fontSize: 8, cellPadding: 2, halign: 'center' }, // Centrer le texte des cellules
didDrawPage: (data) => {
pdf.text('', 14, 10)
}
})
pdf.save(`Resultat-${niveau}-${scolaire}.pdf`)
} catch (error) {
console.error('Error generating PDF:', error)
}
}
generatePDF()
}
function compareSessionNotes(session1, session2) {
let notes
if (session2) {
if (session1 < session2.note) {
notes = session2.note
} else {
notes = session1
}
} else {
notes = session1
}
return notes
}
for (let index = 0; index < etudiants.length; index++) {
let total = 0
let note = 0
let totalCredit = 0
// Create a new object for each student
let modelJson = {
id: '',
nom: '',
prenom: '',
photos: '',
moyenne: '',
mention: '',
anneescolaire: ''
}
for (let j = 0; j < etudiants[index].length; j++) {
modelJson.id = etudiants[index][j].etudiant_id
modelJson.nom = etudiants[index][j].nom
modelJson.prenom = etudiants[index][j].prenom
modelJson.photos = etudiants[index][j].photos
modelJson.mention = etudiants[index][j].mention_id
modelJson.anneescolaire = etudiants[index][j].annee_scolaire
// console.log(checkNull(session[index][j]));
if (session[index]) {
note +=
compareSessionNotes(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
modelJson.moyenne = total.toFixed(2)
// Add the new object to the array
dataToMap.push(modelJson)
}
const sortedStudents = dataToMap
.filter((student) => parseFloat(student.moyenne) >= 10)
.sort((a, b) => parseFloat(b.moyenne) - parseFloat(a.moyenne))
console.log(sortedStudents)
return (
<div className={classe.mainHome}>
<div className={classeHome.header}>
<div className={classe.h1style}>
<div className={classeHome.blockTitle}>
<h1>
Resultat des {niveau} en {scolaire}
</h1>
<div style={{ display: 'flex', gap: '10px' }}>
<Link to={'#'} onClick={print}>
<Button color="warning" variant="contained">
<FaDownload style={{ fontSize: '20px' }} />
Télécharger
</Button>
</Link>
<Link to={'#'} onClick={() => window.history.back()}>
<Button color="warning" variant="contained">
<IoMdReturnRight style={{ fontSize: '20px' }} />
</Button>
</Link>
</div>
</div>
</div>
</div>
<div className={classeHome.boxEtudiantsCard}>
<Paper
sx={{
height: 'auto', // Auto height to make the grid responsive
width: '100%',
// minHeight: 500, // Ensures a minimum height
display: 'flex',
padding: '2%'
}}
>
<table className="table table-bordered table-striped text-center shadow-sm" id="myTable2">
<thead className="table-secondary">
<tr>
<td colSpan={4} className="py-3">
<h6>
Niveau {niveau} | Année Scolaire {scolaire}
</h6>
</td>
</tr>
<tr>
<th>Nom</th>
<th>Prenom</th>
<th>Mention</th>
<th>Moyenne</th>
</tr>
</thead>
<tbody>
{sortedStudents.map((sorted) => (
<tr key={sorted.id}>
<td>{sorted.nom}</td>
<td>{sorted.prenom}</td>
<td>{returnmention(sorted.mention)}</td>
<td className="fw-bold">{sorted.moyenne}</td>
</tr>
))}
</tbody>
</table>
</Paper>
</div>
</div>
)
}
export default Resultat

283
src/renderer/src/components/Sidenav.jsx

@ -0,0 +1,283 @@
import React, { useState } from 'react'
import classe from '../assets/Sidenav.module.css'
import { RiDashboardHorizontalFill } from 'react-icons/ri'
import { PiStudentFill } from 'react-icons/pi'
import { IoMdHelpCircleOutline } from 'react-icons/io'
import { CgNotes } from 'react-icons/cg'
import { FaUserCircle, FaBook, FaUserCog } from 'react-icons/fa'
import { Link } from 'react-router-dom'
import { useLocation } from 'react-router-dom'
import { Tooltip } from 'react-tooltip'
import { LuLogOut } from 'react-icons/lu'
import { GiUpgrade } from 'react-icons/gi'
import Menu from '@mui/material/Menu'
import MenuItem from '@mui/material/MenuItem'
import { useAuthContext } from '../contexts/AuthContext'
import { MdAdminPanelSettings, MdRule } from 'react-icons/md'
import { BsCalendar2Date } from 'react-icons/bs'
import { SiVitest } from 'react-icons/si'
import { GrManual } from 'react-icons/gr'
import { FaClipboardList } from 'react-icons/fa6'
const Sidenav = () => {
const [anchorEl, setAnchorEl] = useState(null)
const open = Boolean(anchorEl)
const { setToken } = useAuthContext()
const handleClick = (event) => {
setAnchorEl(event.currentTarget)
}
const handleClose = () => {
setAnchorEl(null)
}
// don't touch it, i don't know why but the active button stop workin without this
const location = useLocation()
const logout = () => {
localStorage.removeItem('ACCESS_TOKEN')
setToken(null)
}
return (
<nav className={classe.navbar}>
<style>
{`
.custom-tooltip {
font-size: 15px;
border: solid 1px white !important;
border-radius: 4px;
}
`}
</style>
<ul className={classe.liste1}>
<li>
<Link
to={'/'}
className={classe.nav_link}
style={{
outline: 'none',
borderBottom: window.location.hash == '#/' ? '2px solid white' : 'none'
}}
>
<RiDashboardHorizontalFill className="dashboard" style={{ outline: 'none' }} />
<Tooltip anchorSelect=".dashboard" className="custom-tooltip" place="top">
Dashboard
</Tooltip>
</Link>
</li>
<li>
<Link
to={'/student'}
className={classe.nav_link}
style={{
outline: 'none',
borderBottom: window.location.hash == '#/student' ? '2px solid white' : 'none'
}}
>
<PiStudentFill className="student" style={{ outline: 'none' }} />
<Tooltip anchorSelect=".student" className="custom-tooltip" place="top">
Etudiants
</Tooltip>
</Link>
</li>
<li>
<Link
to={'/notes'}
className={classe.nav_link}
style={{
outline: 'none',
borderBottom: window.location.hash == '#/notes' ? '2px solid white' : 'none'
}}
>
<CgNotes className="notes" style={{ outline: 'none' }} />
<Tooltip anchorSelect=".notes" className="custom-tooltip" place="top">
Notes
</Tooltip>
</Link>
</li>
<li>
<Link
to={'/mention'}
className={classe.nav_link}
style={{
outline: 'none',
borderBottom: window.location.hash == '#/mention' ? '2px solid white' : 'none'
}}
>
<FaClipboardList className="mention" style={{ outline: 'none' }} />
<Tooltip anchorSelect=".mention" className="custom-tooltip" place="top">
Mentions
</Tooltip>
</Link>
</li>
<li>
<Link
to={'/matiere'}
className={classe.nav_link}
style={{
outline: 'none',
borderBottom: window.location.hash == '#/matiere' ? '2px solid white' : 'none'
}}
>
<FaBook className="matiere" style={{ outline: 'none' }} />
<Tooltip anchorSelect=".matiere" className="custom-tooltip" place="top">
Matières
</Tooltip>
</Link>
</li>
<li>
<Link
to={'/niveau'}
className={classe.nav_link}
style={{
outline: 'none',
borderBottom: window.location.hash == '#/niveau' ? '2px solid white' : 'none'
}}
>
<GiUpgrade className="niveau" style={{ outline: 'none' }} />
<Tooltip anchorSelect=".niveau" className="custom-tooltip" place="top">
Niveau
</Tooltip>
</Link>
</li>
<li>
<Link
to={'/anneescolaire'}
className={classe.nav_link}
style={{
outline: 'none',
borderBottom: window.location.hash == '#/niveau' ? '2px solid white' : 'none'
}}
>
<BsCalendar2Date className="anneescolaire" style={{ outline: 'none' }} />
<Tooltip anchorSelect=".anneescolaire" className="custom-tooltip" place="top">
Année Scolaire
</Tooltip>
</Link>
</li>
<li>
<Link
to={'/parcours'}
className={classe.nav_link}
style={{
outline: 'none',
borderBottom: window.location.hash == '#/parcours' ? '2px solid white' : 'none'
}}
>
<MdRule className="rules" style={{ outline: 'none' }} />
<Tooltip anchorSelect=".rules" className="custom-tooltip" place="top">
Parcours
</Tooltip>
</Link>
</li>
<li>
<Link
to={'/apropos'}
className={classe.nav_link}
style={{
outline: 'none',
borderBottom: window.location.hash == '#/apropos' ? '2px solid white' : 'none'
}}
>
<IoMdHelpCircleOutline className="Apropos" style={{ outline: 'none' }} />
<Tooltip anchorSelect=".Apropos" className="custom-tooltip" place="top">
A propos
</Tooltip>
</Link>
</li>
{/* <li>
<Link to={'/manual'} className={classe.nav_link} style={{
outline: 'none',
borderBottom: window.location.hash == '#/manual' ? '2px solid white' : 'none'
}}>
<GrManual className='manual' style={{ outline: 'none' }} />
<Tooltip anchorSelect=".manual" className='custom-tooltip' place="top">
Manuel d'utilisation
</Tooltip>
</Link>
</li> */}
</ul>
<ul className={classe.liste2}>
<li>
<Link
id="basic-button"
aria-controls={open ? 'basic-menu' : undefined}
aria-haspopup="true"
aria-expanded={open ? 'true' : undefined}
onClick={handleClick}
style={{
outline: 'none',
borderBottom:
window.location.hash == '#/admin' || window.location.hash == '#/para'
? '2px solid white'
: 'none'
}}
to={'#'}
className={classe.nav_link}
>
<FaUserCircle className="admin" style={{ outline: 'none' }} />
<Tooltip anchorSelect=".admin" place="top" className="custom-tooltip">
Admin
</Tooltip>
</Link>
<Menu
id="basic-menu"
anchorEl={anchorEl}
open={open}
onClose={handleClose}
MenuListProps={{
'aria-labelledby': 'basic-button'
}}
>
<MenuItem>
<Link
to="/systemenote"
style={{ color: 'black', textDecoration: 'none' }}
onClick={handleClose}
>
<CgNotes /> Système des notes
</Link>
</MenuItem>
<MenuItem>
<Link
to="/admin"
style={{ color: 'black', textDecoration: 'none' }}
onClick={handleClose}
>
<MdAdminPanelSettings /> Admin
</Link>
</MenuItem>
<MenuItem>
<Link
to="/para"
style={{ color: 'black', textDecoration: 'none' }}
onClick={handleClose}
>
<FaUserCog /> Paramètre
</Link>
</MenuItem>
</Menu>
</li>
{/* <li>
<Link to={'/teste'}>
<SiVitest className='test' style={{outline:'none'}} />
<Tooltip anchorSelect=".test" className='custom-tooltip' place="top">
Teste
</Tooltip>
</Link>
</li> */}
<li>
<LuLogOut className="logout" style={{ outline: 'none' }} onClick={logout} />
<Tooltip anchorSelect=".logout" className="custom-tooltip" place="top">
Déconnexion
</Tooltip>
</li>
</ul>
</nav>
)
}
export default Sidenav

268
src/renderer/src/components/SingleAnneeScolaire.jsx

@ -0,0 +1,268 @@
import { useEffect, useState } from 'react'
import classe from '../assets/AllStyleComponents.module.css'
import classeHome from '../assets/Home.module.css'
import Paper from '@mui/material/Paper'
import { Link, useParams } from 'react-router-dom'
import { IoMdReturnRight } from 'react-icons/io'
import { Modal, Box, Typography, Button, InputAdornment, TextField, Grid } from '@mui/material'
import { FaCalendarAlt } from 'react-icons/fa'
import svgError from '../assets/error.svg'
import svgSuccess from '../assets/success.svg'
import { BsCalendar2Date } from 'react-icons/bs'
import dayjs from 'dayjs'
const SingleAnneeScolaire = () => {
const { id } = useParams()
const [formData, setFormData] = useState({
code: '',
debut: '',
fin: '',
id: ''
})
const [scolaire, setScolaire] = useState([])
const [status, setStatus] = useState(200)
const [open, setOpen] = useState(false)
useEffect(() => {
window.anneescolaire.getSingleAnneeScolaire({ id }).then((response) => {
setScolaire(response)
})
}, [])
useEffect(() => {
setFormData((prev) => ({
...prev,
code: scolaire.code,
debut: dayjs(scolaire.debut).format('YYYY-MM-DD'),
fin: dayjs(scolaire.fin).format('YYYY-MM-DD'),
id: scolaire.id
}))
}, [scolaire])
/**
* function to set the data in state
* @param {*} e
*/
const handleInputChange = (e) => {
const { name, value } = e.target
setFormData((prevData) => ({
...prevData,
[name]: value
}))
}
const formSubmit = async (e) => {
e.preventDefault()
let response = await window.anneescolaire.updateAnneeScolaire(formData)
if (response.success) {
setOpen(true)
setStatus(200)
} else {
setStatus(400)
setOpen(true)
}
}
const handleClose = () => setOpen(false)
const modals = () => (
<Modal
open={open}
onClose={handleClose}
aria-labelledby="modal-title"
aria-describedby="modal-description"
>
<Box
sx={{
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
width: 450,
bgcolor: 'background.paper',
boxShadow: 24,
p: 4
}}
>
<Typography
sx={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
gap: 2
}}
>
<img src={status === 200 ? svgSuccess : svgError} alt="" width={70} height={70} />
<span> {status === 200 ? 'Mise à jour effectuée avec succès' : 'Erreur'}</span>
</Typography>
<Box
sx={{
marginTop: 2,
display: 'flex',
justifyContent: 'flex-end'
}}
>
<Button onClick={handleClose} color="warning" variant="contained">
OK
</Button>
</Box>
</Box>
</Modal>
)
return (
<div className={classe.mainHome}>
{modals()}
<div className={classeHome.header}>
<div className={classe.h1style}>
<div className={classeHome.blockTitle}>
<h1 style={{ display: 'flex', alignItems: 'center', gap: '10px' }}>
<BsCalendar2Date />
Mise a jour Année Scolaire
</h1>
<Link to={'#'} onClick={() => window.history.back()}>
<Button color="warning" variant="contained">
<IoMdReturnRight style={{ fontSize: '20px' }} />
</Button>
</Link>
</div>
</div>
</div>
<div className={classeHome.boxEtudiantsCard}>
<Paper
sx={{
width: '100%',
height: '100%', // Auto height to make the grid responsive
minHeight: 500, // Ensures a minimum height
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center'
}}
>
<form
style={{
width: '50%',
padding: '1%',
height: '20%',
border: 'solid 2px orange',
borderRadius: '10px'
}}
onSubmit={formSubmit}
>
<h4 style={{ textAlign: 'center', padding: '0 0 3% 0' }}>
mise a jour Année Scolaire
</h4>
<Grid container spacing={2}>
<Grid item xs={12} sm={6}>
<TextField
label={'Année Scolaire'}
name={'code'}
placeholder="2024-2025"
color="warning"
fullWidth
value={formData.code}
InputProps={{
startAdornment: (
<InputAdornment position="start">
<FaCalendarAlt />
</InputAdornment>
)
}}
onChange={handleInputChange}
required
className="inputAddNote"
sx={{
'& .MuiOutlinedInput-root': {
'&:hover fieldset': {
borderColor: '#ff9800' // Set the border color on hover
}
},
'& .MuiInputBase-input::placeholder': {
fontSize: '14px' // Set the placeholder font size
}
}}
/>
</Grid>
<Grid item xs={12} sm={6}>
<TextField
label={'Date de début'}
name={'debut'}
color="warning"
onChange={handleInputChange}
required
value={formData.debut}
fullWidth
type="date"
InputProps={{
startAdornment: (
<InputAdornment position="start">
<FaCalendarAlt />
</InputAdornment>
)
}}
className="inputAddNote"
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 item xs={12} sm={6}>
<TextField
label={'Date de fin'}
name={'fin'}
color="warning"
fullWidth
type="date"
InputProps={{
startAdornment: (
<InputAdornment position="start">
<FaCalendarAlt />
</InputAdornment>
)
}}
onChange={handleInputChange}
required
value={formData.fin}
className="inputAddNote"
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
item
xs={12}
style={{ display: 'flex', gap: '30px', justifyContent: 'flex-end' }}
>
<Button type="submit" color="warning" variant="contained">
Enregister
</Button>
</Grid>
</Grid>
</form>
</Paper>
</div>
</div>
)
}
export default SingleAnneeScolaire

1156
src/renderer/src/components/SingleEtudiant.jsx

File diff suppressed because it is too large

584
src/renderer/src/components/Student.jsx

@ -0,0 +1,584 @@
import React, { useEffect, useRef, useState } from 'react'
import { Link, Navigate } 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 { PiStudentFill } from 'react-icons/pi'
import { DataGrid, GridToolbar } from '@mui/x-data-grid'
import { frFR } from '@mui/x-data-grid/locales'
import dayjs from 'dayjs'
import { FaCertificate, FaGraduationCap, FaPlus, FaReceipt, FaToolbox } from 'react-icons/fa'
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 { FaPenToSquare, FaFilePdf } from 'react-icons/fa6'
import { CgNotes } from 'react-icons/cg'
import { IoEyeSharp } from 'react-icons/io5'
import PDFEditor from './function/PDFEditor'
import ModalCertificate from './ModalCertificate'
import ModalStage from './ModalStage'
import ModalRecepice from './ModalRecepice'
import { processPdf } from './function/PDFEditorV2'
import { MdVerified } from 'react-icons/md'
const Student = () => {
const theme = createTheme({
components: {
MuiIconButton: {
styleOverrides: {
root: {
color: 'gray' // Change the color of toolbar icons
}
}
},
MuiButton: {
styleOverrides: {
root: {
color: '#121212' // Change the color of toolbar icons
}
}
}
}
})
const [status, setStatus] = useState([])
const [mention, setMention] = useState([])
/**
* hook for displaying the students
*/
const [etudiants, setEtudiants] = useState([])
const [notes, setNotes] = useState([])
useEffect(() => {
window.etudiants.getEtudiants().then((response) => {
setEtudiants(response)
})
window.notes.getMoyenneVerify().then((response) => {
setNotes(response)
})
}, [])
const [niveaus, setNiveau] = useState([])
useEffect(() => {
window.niveaus.getNiveau().then((response) => {
setNiveau(response)
})
window.statuss.getStatus().then((response) => {
setStatus(response)
})
window.mention.getMention().then((response) => {
setMention(response)
})
}, [])
const SeeNote = ({ params }) => {
const matchingNote = notes.find(
(element) =>
element.etudiant_niveau === params.row.niveau && element.etudiant_id === params.value
)
return (
<div style={{ display: 'flex', gap: '10px' }}>
{matchingNote ? (
<Link
to={`/single/notes/${params.value}/${params.row.niveau}/${params.row.annee_scolaire}`}
>
<Button color="warning" variant="contained" className={`voirs${params.value}`}>
<IoEyeSharp style={{ fontSize: '20px', color: 'white' }} />
</Button>
<Tooltip anchorSelect={`.voirs${params.value}`} className="custom-tooltip" place="top">
Voir les notes
</Tooltip>
</Link>
) : (
<Link
to={`/addnotes/${params.value}/${params.row.niveau}/${params.row.mention_id}/${params.row.parcour}`}
>
<Button color="warning" variant="contained" className={`note${params.value}`}>
<CgNotes style={{ fontSize: '20px', color: 'white' }} />
</Button>
<Tooltip anchorSelect={`.note${params.value}`} className="custom-tooltip" place="top">
Ajouter un notes à cet étudiant
</Tooltip>
</Link>
)}
</div>
)
}
/**
* function to return the date to local date string
*
* @param {string} dateString
* @returns string
*/
function formatDate(dateString) {
// Convert the string to a Date object
const dateObject = new Date(dateString)
// Format the date using toLocaleDateString
const formattedDate = dateObject.toLocaleDateString('fr-FR', {
day: '2-digit',
month: 'long',
year: 'numeric'
})
return formattedDate
}
/**
* data column, header of the grid
*/
const columns = [
{ field: 'nom', headerName: 'Nom', width: 180 },
{ field: 'prenom', headerName: 'Prenom', width: 180 },
{ field: 'sexe', headerName: 'Sexe', width: 80 },
{ field: 'cin', headerName: 'CIN', width: 180 },
{ field: 'date_deli', headerName: 'Date de delivrance', width: 80 },
{ field: 'nation', headerName: 'Natoinalité', width: 120 },
{ field: 'annee_bacc', headerName: 'Année du Baccalauréat', width: 80 },
{ field: 'serie', headerName: 'Série', width: 80 },
{ field: 'bourse', headerName: 'Boursier', width: 80 },
{ field: 'domaine', headerName: 'Domaine', width: 180 },
{ field: 'contact', headerName: 'Contact', width: 180 },
{ field: 'niveau', headerName: 'Niveau', width: 80 },
{ field: 'date_naissance', headerName: 'Date de naissance', width: 130 },
{ field: 'annee_scolaire', headerName: 'Année univarsitaire', width: 130 },
{ field: 'status', headerName: 'Status', width: 140 },
{ field: 'num_inscription', headerName: "Numéro d'inscription", width: 160 },
{ field: 'parcour', headerName: 'Parcours', width: 150 },
{
field: 'photos',
headerName: 'Image',
width: 100,
renderCell: (params) => (
<img
src={params.value} // Correct the access to the image source
alt={'image pdp'}
style={{ width: 50, height: 50, borderRadius: '50%', objectFit: 'cover' }}
/>
)
},
{
field: 'action',
headerName: 'Action',
width: 300,
renderCell: (params) => (
<div style={{ display: 'flex', gap: '10px' }}>
<Link to={`/single/${params.value}`}>
<Button color="warning" variant="contained">
<FaPenToSquare
style={{ fontSize: '20px', color: 'white' }}
className={`update${params.value}`}
/>
<Tooltip
anchorSelect={`.update${params.value}`}
className="custom-tooltip"
place="top"
>
Modifier
</Tooltip>
</Button>
</Link>
<Link to={`/tranche/${params.value}`} className={`verif${params.value}`}>
<Button color="warning" variant="contained">
<MdVerified style={{ fontSize: '20px', color: 'white' }} />
</Button>
<Tooltip anchorSelect={`.verif${params.value}`} className="custom-tooltip" place="top">
Verification Frais de Formation
</Tooltip>
</Link>
<Link onClick={() => Print(params.value)}>
<Button color="warning" variant="contained" className={`export${params.value}`}>
<FaFilePdf style={{ fontSize: '20px', color: 'white' }} />
</Button>
<Tooltip anchorSelect={`.export${params.value}`} className="custom-tooltip" place="top">
Exporter carte d'etudiants
</Tooltip>
</Link>
<Link className={`voir${params.value}`}>
<Button color="warning" variant="contained">
<FaPlus style={{ fontSize: '20px', color: 'white' }} />
</Button>
<Tooltip
anchorSelect={`.voir${params.value}`}
style={{
fontSize: '12px',
overflow: 'visible',
zIndex: 999222,
display: 'flex',
flexDirection: 'column',
gap: '10px',
marginTop: "15px"
}}
place="left-end"
clickable
>
{/* Groupe 1 : Certificat */}
<Link style={{ display: 'flex', gap: '10px' }}>
<SeeNote params={params} />
<Link>
<Button
onClick={() => handleOpen(params.value)}
color="warning"
variant="contained"
className={`receip${params.value}`}
>
<FaCertificate style={{ fontSize: '20px', color: 'white' }} />
</Button>
<Tooltip
anchorSelect={`.receip${params.value}`}
className="custom-tooltip"
place="top"
>
Télecharger le Certificat de scolariter
</Tooltip>
</Link>
</Link>
{/* Groupe 2 : Stage (affiché seulement pour L2) */}
<Link style={{ display: 'flex', gap: '10px' }}>
{params.row.niveau !== 'L1' && (
<Link>
<Button
onClick={() => handleOpen2()}
color="warning"
variant="contained"
className={`stage${params.value}`}
>
<FaToolbox style={{ fontSize: '20px', color: 'white' }} />
</Button>
<Tooltip
anchorSelect={`.stage${params.value}`}
className="custom-tooltip"
place="top"
>
Télecharger l'autorisation de stage
</Tooltip>
</Link>
)}
<Link>
<Button
onClick={() => handleOpen3(params.value)}
color="warning"
variant="contained"
className={`recepice${params.value}`}
>
<FaReceipt style={{ fontSize: '20px', color: 'white' }} />
</Button>
<Tooltip
anchorSelect={`.recepice${params.value}`}
className="custom-tooltip"
place="top"
>
Télecharger le recepissé d'inscription
</Tooltip>
</Link>
</Link>
</Tooltip>
</Link>
</div>
)
}
]
const Print = async (id) => {
console.log(id)
let etudiant = await window.etudiants.getSingle({ id })
if (etudiant) {
let data = {
f1: `${etudiant.nom} ${etudiant.prenom}`,
f2: `Naissances: ${dayjs(etudiant.date_de_naissances).format('DD-MM-YYYY')}`,
f3: `Niveau: ${etudiant.niveau}`,
f4: `Année: ${etudiant.annee_scolaire}`,
f5: `Inscription: ${etudiant.num_inscription}`,
f8: etudiant.photos
}
processPdf(data)
// PDFEditor(data);
}
}
// PAGINATION CORRIGÉE - États pour la pagination complète
const [paginationModel, setPaginationModel] = useState({
page: 0,
pageSize: 20
})
const [pageSizeOptions, setPageSizeOptions] = useState([20, 40, 60])
// Gestionnaire complet pour les changements de pagination (page ET pageSize)
const handlePaginationModelChange = (newModel) => {
console.log('📊 Pagination changed:', newModel) // Pour debug
setPaginationModel(newModel)
// Si l'utilisateur choisit la plus grande option, ajouter +20
const maxOption = Math.max(...pageSizeOptions)
if (newModel.pageSize === maxOption) {
setPageSizeOptions((prev) => [...prev, maxOption + 20])
}
}
// Ensure that the array is flat (not wrapped in another array)
const dataRow = etudiants.map((etudiant) => ({
id: etudiant.id, // Ensure this exists and is unique for each etudiant
nom: etudiant.nom,
prenom: etudiant.prenom,
niveau: etudiant.niveau,
date_naissance: dayjs(etudiant.date_de_naissances).format('DD-MM-YYYY'),
annee_scolaire: etudiant.annee_scolaire,
status: comparestatut(etudiant.status),
num_inscription: etudiant.num_inscription,
parcour: etudiant.parcours == null ? 'Pas de parcours' : etudiant.parcours,
photos: etudiant.photos,
sexe: etudiant.sexe,
cin: etudiant.cin,
date_deli: dayjs(etudiant.date_delivrance).format('DD-MM-YYYY'),
nation: etudiant.nationalite,
annee_bacc: etudiant.annee_bacc,
serie: etudiant.serie,
bourse: etudiant.boursier,
domaine: etudiant.domaine,
contact: etudiant.contact,
mention_id: etudiant.mention_id,
action: etudiant.id // Ensure this is a valid URL for the image
}))
function comparestatut(statutID) {
let statusText
status.map((statu) => {
if (statutID == statu.id) {
statusText = statu.nom
}
})
return statusText
}
/**
* Fonction de filtrage avec reset de pagination
*/
const FilterData = async (e) => {
let niveau = e.target.value
if (niveau !== '') {
let data = await window.etudiants.FilterDataByNiveau({ niveau })
setEtudiants(data)
// Reset vers la première page après filtrage
setPaginationModel(prev => ({ ...prev, page: 0 }))
} else {
window.etudiants.getEtudiants().then((response) => {
setEtudiants(response)
// Reset vers la première page
setPaginationModel(prev => ({ ...prev, page: 0 }))
})
}
}
const [openModal, setOpenModal] = useState(false)
const [openModal2, setOpenModal2] = useState(false)
const [openModal3, setOpenModal3] = useState(false)
const [json, setJson] = useState()
const [json2, setJson2] = useState()
const [nom, setNom] = useState([])
const [nom2, setNom2] = useState([])
const handleOpen = (id) => {
window.etudiants.getSingle({ id }).then((response) => {
setNom(response)
})
setOpenModal(true)
}
const handleOpen3 = (id) => {
window.etudiants.getSingle({ id }).then((response) => {
setNom2(response)
})
setOpenModal3(true)
}
const handleOpen2 = () => {
setOpenModal2(true)
}
function compareMention(mentionID) {
let statusText
mention.map((statu) => {
if (mentionID == statu.id) {
statusText = statu.nom
}
})
return statusText ? statusText.charAt(0).toUpperCase() + statusText.slice(1) : statusText
}
useEffect(() => {
setJson({
nomPrenom: nom.nom + ' ' + nom.prenom,
naissances: nom.date_de_naissances,
mention: compareMention(nom.mention_id),
niveau: nom.niveau,
annee: nom.annee_scolaire,
inscri: nom.num_inscription
})
}, [nom])
useEffect(() => {
setJson2({
nomPrenom: nom.nom + ' ' + nom.prenom,
mention: compareMention(nom.mention_id),
niveau: nom.niveau,
annee: nom.annee_scolaire,
inscri: nom.num_inscription
})
}, [nom2])
const handleClose = () => setOpenModal(false)
const handleClose2 = () => setOpenModal2(false)
const handleClose3 = () => setOpenModal3(false)
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>Etudiants</h1>
<Link to={'/addstudent'}>
<Button color="warning" variant="contained">
<PiStudentFill style={{ fontSize: '20px' }} /> Ajouter
</Button>
</Link>
</div>
</div>
{/* bare des filtre */}
<div className={classeHome.container}>
{/* filtre par niveau */}
<div style={{ width: '100%', textAlign: 'right' }}>
<FormControl
sx={{
m: 1,
width: '30%',
'& .MuiOutlinedInput-root': {
'&:hover fieldset': {
borderColor: '#ff9800' // Set the border color on hover
}
}
}}
size="small"
variant="outlined"
>
<InputLabel
id="demo-select-small-label"
sx={{ color: 'black', fontSize: '18px' }}
color="warning"
>
Niveau
</InputLabel>
<Select
labelId="demo-select-small-label"
id="demo-select-small"
label="Niveau"
color="warning"
name="niveau"
defaultValue={''}
onChange={FilterData}
startAdornment={
<InputAdornment position="start">
<FaGraduationCap />
</InputAdornment>
}
sx={{
background: 'white',
display: 'flex',
alignItems: 'center', // Align icon and text vertically
'& .MuiSelect-icon': {
marginLeft: 'auto' // Keep the dropdown arrow to the right
}
}}
>
<MenuItem value="">
<em>Tous les étudiants</em>
</MenuItem>
{niveaus.map((niveau) => (
<MenuItem value={niveau.nom} key={niveau.id}>
{niveau.nom}
</MenuItem>
))}
</Select>
</FormControl>
</div>
</div>
</div>
{/* display the data-grid students */}
<div className={classeHome.boxEtudiantsCard}>
<div style={{ display: 'flex', justifyContent: 'center' }}>
<Paper
sx={{
width: '100%',
height: 'auto', // Auto height to make the grid responsive
minHeight: 500, // Ensures a minimum height
display: 'flex'
}}
>
<ThemeProvider theme={theme}>
<div
style={{
display: 'flex',
flexDirection: 'column', // Stacks content vertically on smaller screens,
width: '100%'
}}
>
<DataGrid
rows={dataRow}
columns={columns}
pageSizeOptions={pageSizeOptions}
paginationModel={paginationModel} // Utilise l'état complet
onPaginationModelChange={handlePaginationModelChange} // Gère page ET pageSize
sx={{
border: 0,
width: 'auto', // 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>
<ModalCertificate open={openModal} onClose={handleClose} json={json} />
<ModalStage open={openModal2} onClose={handleClose2} />
<ModalRecepice open={openModal3} onClose={handleClose3} json={json2} />
</div>
</div>
</div>
)
}
export default Student

458
src/renderer/src/components/TesteDatagrid.jsx

@ -0,0 +1,458 @@
import React, { useEffect, useRef, useState } from 'react'
import classe from '../assets/AllStyleComponents.module.css'
import classeHome from '../assets/Home.module.css'
import Paper from '@mui/material/Paper'
import entete from '../assets/enteterelever.png'
import jsPDF from 'jspdf'
import html2Canvas from 'html2canvas'
const TesteDatagrid = ({ id, niveau, annee_scolaire, nomPrenom, inscription, refs }) => {
const [print, setPrint] = useState(refs)
const paperRef = useRef()
const [teste, setTeste] = useState([])
useEffect(() => {
window.notes.noteMatiere({ id, niveau, annee_scolaire }).then((response) => {
setTeste(response)
})
}, [id, niveau, annee_scolaire])
console.log(teste)
useEffect(() => {
if (refs) {
let certificat = paperRef.current
html2Canvas(certificat, { scale: 3 }).then((canvas) => {
const URLimg = canvas.toDataURL()
const doc = new jsPDF('portrait', 'mm', 'a4')
const width = doc.internal.pageSize.getWidth()
const height = doc.internal.pageSize.getHeight()
doc.addImage(URLimg, 'PNG', 0, 0, width, height)
doc.save(`releve_${nomPrenom}.pdf`)
})
}
}, [refs])
// Step 1: Group by semestre
const groupedBySemestre = teste.reduce((acc, item) => {
const { semestre, unite_enseignement } = item
// Ensure a group exists for this `semestre`
if (!acc[semestre]) {
acc[semestre] = {}
}
// Step 2: Further group by unite_enseignement
if (!acc[semestre][unite_enseignement]) {
acc[semestre][unite_enseignement] = []
}
acc[semestre][unite_enseignement].push(item) // Add the item to the appropriate group
return acc
}, {})
console.log(groupedBySemestre)
// Initialize semestre1 and semestre2
let semestre1 = {}
let semestre2 = {}
// Separate groupedBySemestre into semestre1 and semestre2 dynamically
Object.keys(groupedBySemestre).forEach((semestre) => {
// Check if the semester is odd or even
if (parseInt(semestre.replace('S', '')) % 2 !== 0) {
// Odd semesters (S1, S3, S5, ...)
semestre1[semestre] = groupedBySemestre[semestre]
} else {
// Even semesters (S2, S4, S6, ...)
semestre2[semestre] = groupedBySemestre[semestre]
}
})
// Function to count the total elements in the groupedData
function countTotalElements(data) {
let totalCount = 0
// Iterate through each key in the object
Object.keys(data).forEach((key) => {
// Add the length of the array at the current key to totalCount
totalCount += data[key].length
})
return totalCount
}
function crontCredit() {
let total = 0
let credit = document.querySelectorAll('.classCredit')
for (let index = 0; index < credit.length; index++) {
total += Number(credit[index].textContent)
}
return total
}
function countMoyenneGeneral() {
let total = 0
let moyenne = document.querySelectorAll('.classMoyenne')
for (let index = 0; index < moyenne.length; index++) {
total += Number(moyenne[index].textContent)
}
return (total / moyenne.length).toFixed(2)
}
// Combine both semesters into one object to handle them together
const combinedSemesters = { ...semestre1, ...semestre2 }
// Function to generate the rows
const generateTableRows = (semesters) => {
const rows = []
// Iterate over each semester's keys (S1, S2, etc.)
Object.keys(semesters).forEach((semestreKey) => {
const units = semesters[semestreKey]
// Iterate over each unite_enseignement
Object.keys(units).forEach((unitKey, idx) => {
const unitArray = units[unitKey]
const isFirstRow = idx === 0 // Check if it's the first row for this unit
unitArray.forEach((item, itemIdx) => {
rows.push(
<tr key={`${semestreKey}-${unitKey}-${item.id}`}>
{isFirstRow && itemIdx === 0 ? (
<td
rowSpan={countTotalElements(units)}
key={`${semestreKey}-semestre`}
style={{ borderBottom: 'solid 1px black', borderRight: 'solid 1px black' }}
>
<b>{semestreKey}</b>
</td>
) : null}
{itemIdx === 0 ? (
<td
rowSpan={unitArray.length}
key={`${unitKey}-unit`}
style={{
borderBottom: 'solid 1px black',
margin: 0,
padding: 0,
fontSize: 12,
borderRight: 'solid 1px black'
}}
>
<b>{unitKey}</b>
</td>
) : null}
<td
style={{
borderBottom: 'solid 1px black',
margin: 0,
padding: 0,
fontSize: 12,
borderRight: 'solid 1px black'
}}
>
{item.nom}
</td>
<td
style={{
borderBottom: 'solid 1px black',
margin: 0,
padding: 0,
fontSize: 12,
borderRight: 'solid 1px black'
}}
>
{' '}
<span className="classCredit">{item.credit}</span>
</td>
<td
style={{
borderBottom: 'solid 1px black',
margin: 0,
padding: 0,
fontSize: 12,
borderRight: 'solid 1px black'
}}
>
{item.note}
</td>
{/* Ensure this <td> renders only once for the unitArray */}
{itemIdx === 0 ? (
<td
rowSpan={unitArray.length}
style={{ borderBottom: 'solid 1px black', margin: 0, padding: 0, fontSize: 12 }}
>
<span className="classMoyenne">
{(
unitArray.reduce((sum, item) => sum + item.credit * item.note, 0) /
unitArray.reduce((sum, item) => sum + item.credit, 0)
).toFixed(2)}
</span>
</td>
) : null}
</tr>
)
})
})
})
return rows
}
return (
<div className={classe.mainHome}>
<div className={classeHome.boxEtudiantsCard}>
<div style={{ display: 'flex', justifyContent: 'center' }}>
<Paper
sx={{
// width: "100%",
height: 'auto', // Auto height to make the grid responsive
minHeight: 500, // Ensures a minimum height
display: 'flex',
padding: '1%'
}}
ref={paperRef}
>
<div>
<img src={entete} alt="image en tete" style={{ border: 'solid 1px gray' }} />
<div
style={{
width: '100%',
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}}
>
<div
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'column',
fontWeight: 'bold',
textTransform: 'uppercase',
border: 'solid 1px gray',
margin: '1%',
width: '70%',
fontSize: '13px'
}}
>
<span>ECOLE SUPERIEURE POLYTECHNIQUE</span>
<span>REPOBLIKANI MADAGASIKARA</span>
<span>Fitiavana-Tanindrazana-Fandrosoana</span>
<span>*********************</span>
<h2>RELEVÉE DE NOTE</h2>
</div>
</div>
<div style={{ fontSize: '13px', margin: '1%' }}>
<span>Nom & Prenoms: </span>
<span>{nomPrenom}</span>
<br />
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<span>Année scolaire: </span>
<span>{annee_scolaire}</span>
<span>Niveau: </span>
<span>{niveau}</span>
<span>inscription: </span>
<span>{inscription}</span>
</div>
</div>
<table
style={{
border: 'solid 1px black',
margin: 0,
padding: 0,
fontSize: 12,
width: '100%'
}}
>
<thead>
<tr>
<th
style={{
borderBottom: 'solid 1px black',
margin: 0,
padding: 0,
fontSize: 12,
borderRight: 'solid 1px black'
}}
></th>
<th
style={{
borderBottom: 'solid 1px black',
margin: 0,
padding: 0,
fontSize: 12,
borderRight: 'solid 1px black',
textAlign: 'center'
}}
>
Unités <br /> dEnseignement <br /> (UE)
</th>
<th
style={{
borderBottom: 'solid 1px black',
margin: 0,
padding: 0,
fontSize: 12,
borderRight: 'solid 1px black',
textAlign: 'center'
}}
>
Eléments constitutifs
</th>
<th
style={{
borderBottom: 'solid 1px black',
margin: 0,
padding: 0,
fontSize: 12,
borderRight: 'solid 1px black',
textAlign: 'center'
}}
>
Crédits
</th>
<th
style={{
borderBottom: 'solid 1px black',
margin: 0,
padding: 0,
fontSize: 12,
borderRight: 'solid 1px black',
textAlign: 'center'
}}
>
Note
</th>
<th
style={{
borderBottom: 'solid 1px black',
margin: 0,
padding: 0,
fontSize: 12,
textAlign: 'center',
width: '250px'
}}
>
Moyenne
</th>
</tr>
</thead>
<tbody style={{ margin: 0, padding: 0 }}>
{generateTableRows(combinedSemesters)}
<tr>
<td
colSpan={3}
style={{
borderBottom: 'solid 1px black',
margin: 0,
padding: 0,
fontSize: 13,
borderRight: 'solid 1px black',
textAlign: 'right',
paddingRight: '3px'
}}
>
{' '}
<b>Total crédit:</b>
</td>
<td
style={{
borderBottom: 'solid 1px black',
margin: 0,
padding: 0,
fontSize: 13,
textAlignLast: 'left',
paddingLeft: '20px',
borderRight: 'solid 1px black'
}}
>
<b>{crontCredit()}</b>
</td>
<td
style={{
borderBottom: 'solid 1px black',
margin: 0,
padding: 0,
fontSize: 13,
borderRight: 'solid 1px black'
}}
></td>
<td
style={{
borderBottom: 'solid 1px black',
margin: 0,
padding: 0,
fontSize: 13
}}
></td>
</tr>
<tr>
<td
colSpan={5}
style={{
margin: 0,
padding: 0,
fontSize: 13,
borderRight: 'solid 1px black',
textAlign: 'right',
paddingRight: '3px'
}}
>
<b>Moyenne générale :</b>
</td>
<td>
<b>{countMoyenneGeneral()}</b>
</td>
</tr>
<tr>
<td
colSpan={5}
style={{
borderBottom: 'solid 1px black',
margin: 0,
padding: 0,
fontSize: 13,
borderRight: 'solid 1px black',
textAlign: 'right',
paddingRight: '3px'
}}
>
<b>Observation :</b>
</td>
<td>
<input
type="text"
style={{
border: 'none',
outline: 'none',
width: '100%',
textAlign: 'center'
}}
/>
</td>
</tr>
</tbody>
</table>
<div style={{ textAlign: 'center', marginTop: '1%' }}>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'end' }}>
<span>Décision de jury :</span>
<input type="text" style={{ border: 'none', width: '300px', outline: 'none' }} />
</div>
<p>Le Directeur </p>
</div>
<p style={{ textAlign: 'right', marginTop: '5%' }}>RATSIMBAZAFY Christian Pierre</p>
</div>
</Paper>
</div>
</div>
</div>
)
}
export default TesteDatagrid

127
src/renderer/src/components/UpdateTranche.jsx

@ -0,0 +1,127 @@
import React, { useEffect, useState } from 'react'
import {
Dialog,
DialogActions,
DialogContent,
DialogTitle,
TextField,
Button,
Autocomplete,
InputAdornment,
Box,
Grid
} from '@mui/material'
import { MdLabelImportantOutline } from 'react-icons/md'
const UpdateTranche = ({ open, onClose, onSubmitSuccess, id }) => {
const [formData, setFormData] = useState({
id: id,
tranchename: '',
montant: ''
})
const [tranche, setTranche] = useState([])
useEffect(() => {
if (id !== null) {
window.etudiants.getSingleTranche({ id }).then((response) => {
setTranche(response)
})
setFormData({
id: id
})
}
}, [id])
useEffect(() => {
setFormData((prev) => ({
...prev,
tranchename: tranche.tranchename || '',
montant: tranche.montant || ''
}))
}, [tranche])
const handleChange = (e) => {
const { name, value } = e.target
setFormData({ ...formData, [name]: value })
}
const handleSubmit = async (e) => {
e.preventDefault()
let response = await window.etudiants.updateTranche(formData)
if (response.changes) {
onClose()
onSubmitSuccess(true)
}
}
return (
<Dialog open={open} onClose={onClose}>
<form action="" onSubmit={handleSubmit}>
<DialogTitle>Ajout tranche</DialogTitle>
<DialogContent>
<Box sx={{ flexGrow: 1 }}>
<Grid container spacing={2}>
<Grid item xs={12} sm={6}>
<TextField
autoFocus
margin="normal"
required
name="tranchename"
label="Désignation"
type="text"
fullWidth
placeholder="Tranche 1"
variant="outlined"
value={formData.tranchename}
color="warning"
onChange={handleChange}
InputProps={{
startAdornment: (
<InputAdornment position="start">
<MdLabelImportantOutline />
</InputAdornment>
)
}}
/>
</Grid>
<Grid item xs={12} sm={6}>
<TextField
autoFocus
margin="normal"
required
name="montant"
label="Montant"
type="number"
fullWidth
placeholder="Montant"
variant="outlined"
value={formData.montant}
color="warning"
onChange={handleChange}
InputProps={{
startAdornment: (
<InputAdornment position="start">
<MdLabelImportantOutline />
</InputAdornment>
)
}}
/>
</Grid>
</Grid>
</Box>
</DialogContent>
<DialogActions>
<Button onClick={onClose} color="error">
Annuler
</Button>
<Button type="submit" color="warning">
Soumettre
</Button>
</DialogActions>
</form>
</Dialog>
)
}
export default UpdateTranche

94
src/renderer/src/components/function/GenerateFiche.js

@ -0,0 +1,94 @@
import { PDFDocument, rgb, StandardFonts } from 'pdf-lib'
export const generatePDF = async (students) => {
// Sort by NomPrenom alphabetically
const sortedStudents = students.sort((a, b) => a.nom.localeCompare(b.nom))
// function compareMention(id) {
// let mentionText;
// mentions.map((ment) => {
// if (id == ment.id) {
// mentionText = ment.nom
// }
// })
// return mentionText;
// }
const pdfDoc = await PDFDocument.create()
const pageWidth = 595 // A4 width in points
const pageHeight = 842 // A4 height in points
const margin = 40
const fontSize = 12
const rowHeight = 25
const tableStartY = pageHeight - margin - 50
const maxRowsPerPage = Math.floor((tableStartY - margin) / rowHeight)
const font = await pdfDoc.embedFont(StandardFonts.Helvetica)
const page = pdfDoc.addPage([pageWidth, pageHeight])
let y = tableStartY
let rowCount = 0
// Add Table Headers
page.drawText('Nom matieres', {
x: pageWidth / 2 - 50,
y,
size: 16,
font,
color: rgb(0, 0, 0)
})
y -= 30
const headers = ['N°', 'Nom et Prenom', 'Mention', 'Émergement']
const columnWidths = [50, 200, 100, 100]
const xPositions = [
margin,
margin + columnWidths[0],
margin + columnWidths[0] + columnWidths[1],
margin + columnWidths[0] + columnWidths[1] + columnWidths[2]
]
headers.forEach((header, index) => {
page.drawText(header, { x: xPositions[index], y, size: fontSize, font, color: rgb(0, 0, 0) })
})
y -= rowHeight
// Function to add a new page when needed
const addNewPage = () => {
const newPage = pdfDoc.addPage([pageWidth, pageHeight])
y = tableStartY
rowCount = 0
return newPage
}
// Loop through student data and populate the table
sortedStudents.forEach((student, index) => {
if (rowCount >= maxRowsPerPage) {
page = addNewPage()
}
const rowData = [
(index + 1).toString(),
`${student.nom} ${student.prenom}`,
student.mention,
''
]
rowData.forEach((text, i) => {
page.drawText(text, { x: xPositions[i], y, size: fontSize, font, color: rgb(0, 0, 0) })
})
y -= rowHeight
rowCount++
})
// Save and download PDF
const pdfBytes = await pdfDoc.save()
const blob = new Blob([pdfBytes], { type: 'application/pdf' })
const link = document.createElement('a')
link.href = URL.createObjectURL(blob)
link.download = 'student_list.pdf'
link.click()
}

134
src/renderer/src/components/function/PDFEditor.js

@ -0,0 +1,134 @@
import { PDFDocument } from 'pdf-lib'
import { saveAs } from 'file-saver'
import pdf from '../../assets/carte_etudiant.pdf'
import pdf2 from '../../assets/arriere.pdf'
import pdf3 from '../../assets/business_card_template_001_form.pdf'
import QRCode from 'qrcode'
const PDFEditor = async (data) => {
// Load the existing PDF files
const existingPdfBytes = await fetch(pdf).then((res) => res.arrayBuffer())
const existingPdfBytes2 = await fetch(pdf2).then((res) => res.arrayBuffer())
const existingPdfBytes3 = await fetch(pdf3).then((res) => res.arrayBuffer())
// Load the PDFs
const pdfDoc = await PDFDocument.load(existingPdfBytes)
const pdfDoc2 = await PDFDocument.load(existingPdfBytes2)
const pdfDoc3 = await PDFDocument.load(existingPdfBytes3)
// Get the form from the first PDF (front)
const form = pdfDoc.getForm()
form.getTextField('name').setText(data.f1)
form.getTextField('birthday').setText('Date de naissance: ' + data.f2)
form.getTextField('niveau').setText('Niveau: ' + data.f3)
form.getTextField('annee').setText('Année scolaire: ' + data.f4)
form.getTextField('num_inscription').setText("Numéro d'inscription: " + data.f5)
form.flatten()
// ----------------------------------------- carte frontale ----------------------------------------------------
const canvas = document.createElement('canvas')
const context = canvas.getContext('2d')
const diameter = 90
const resolutionScale = 4
canvas.width = diameter * resolutionScale
canvas.height = diameter * resolutionScale
const base64Data = data.f8.split(',')[1]
const img = new Image()
img.src = `data:image/png;base64,${base64Data}`
await new Promise((resolve) => {
img.onload = resolve
})
context.scale(resolutionScale, resolutionScale)
context.beginPath()
context.arc(diameter / 2, diameter / 2, diameter / 2, 0, Math.PI * 2)
context.closePath()
context.clip()
context.drawImage(img, 0, 0, diameter, diameter)
const canvasImageBase64 = canvas.toDataURL('image/png')
const canvasImageBytes = Uint8Array.from(atob(canvasImageBase64.split(',')[1]), (char) =>
char.charCodeAt(0)
)
const image = await pdfDoc.embedPng(canvasImageBytes)
const page = pdfDoc.getPage(0)
page.drawImage(image, {
x: 74 - diameter / 2,
y: 77 - diameter / 2,
width: diameter,
height: diameter
})
const form2 = pdfDoc2.getForm()
const form3 = pdfDoc3.getForm()
const field = form2.getField('f6')
if (field) {
form2.removeField(field)
form3.removeField(field)
}
// ----------------------------------------------- carte arriere -------------------------------------------
const paperContent = `
CUniversity
Nom et prenom: ${data.f1}
Date de naissance: ${data.f2}
Niveau: ${data.f3}
Année scolaire: ${data.f4}
Numéro d'inscription: ${data.f5}
`
const qrCanvas = document.createElement('canvas')
const qrWidth = 300
QRCode.toCanvas(
qrCanvas,
paperContent,
{ errorCorrectionLevel: 'H', width: qrWidth },
(error) => {
if (error) {
console.error(error)
return
}
const qrImageBase64 = qrCanvas.toDataURL('image/png')
const qrImageBytes = Uint8Array.from(atob(qrImageBase64.split(',')[1]), (char) =>
char.charCodeAt(0)
)
;(async () => {
const page2 = pdfDoc2.getPage(0)
const qrImage = await pdfDoc2.embedPng(qrImageBytes)
const x = 7
const y = 75
const qrSize = 95
page2.drawImage(qrImage, {
x,
y,
width: qrSize,
height: qrSize
})
// Merge the front and back (pages) into a new document
const newPdfDoc = await PDFDocument.create()
// const [frontPage] = await newPdfDoc.copyPages(pdfDoc, [0]); // Front page
const [frontPage] = await newPdfDoc.copyPages(pdfDoc3, [0]) // Front page
const [backPage] = await newPdfDoc.copyPages(pdfDoc2, [0]) // Back page
newPdfDoc.addPage(frontPage)
newPdfDoc.addPage(backPage)
const pdfBytes = await newPdfDoc.save()
// Trigger the download of the merged PDF
const blob = new Blob([pdfBytes], { type: 'application/pdf' })
saveAs(blob, `carte_etudiant_${data.f1}.pdf`)
})()
}
)
}
export default PDFEditor

151
src/renderer/src/components/function/PDFEditorV2.js

@ -0,0 +1,151 @@
import { PDFDocument, PDFTextField } from 'pdf-lib'
import PDF from '../../assets/business_card_template_001_form.pdf'
import PDF2 from '../../assets/business_card_template_002.pdf'
import QRCode from 'qrcode'
async function fillPdfFields(jsonData) {
const response = await fetch(PDF) // Load the PDF file
const response2 = await fetch(PDF2) // Load the second PDF file
const pdfBytes = await response.arrayBuffer()
const pdfBytes2 = await response2.arrayBuffer()
const pdfDoc = await PDFDocument.load(pdfBytes)
const pdfDoc2 = await PDFDocument.load(pdfBytes2)
const form = pdfDoc.getForm()
const fields = form
.getFields()
.filter((field) => field instanceof PDFTextField)
.map((field) => ({ name: field.getName(), field }))
const dataMapping = {
f1: jsonData.f1,
f2: jsonData.f2,
f3: jsonData.f3,
f4: jsonData.f4,
f5: jsonData.f5
}
// Fill text fields
Object.keys(dataMapping).forEach((key, index) => {
if (fields[index]) {
const { field } = fields[index]
field.setText(dataMapping[key] || '')
console.log(`Setting ${key}: ${dataMapping[key]} -> ${fields[index].name}`)
}
})
// ---------------------------------calculate and paste the image in pdf--------------------------------------
if (jsonData.f8) {
const diameter = 90
const resolutionScale = 4
const canvas = document.createElement('canvas')
const context = canvas.getContext('2d')
canvas.width = diameter * resolutionScale
canvas.height = diameter * resolutionScale
const base64Data = jsonData.f8.startsWith('data:image')
? jsonData.f8.split(',')[1]
: jsonData.f8 // Handle case where "data:image/png;base64," is missing
const img = new Image()
await new Promise((resolve, reject) => {
img.onload = resolve
img.onerror = reject
img.src = `data:image/png;base64,${base64Data}`
})
context.scale(resolutionScale, resolutionScale)
context.beginPath()
context.arc(diameter / 2, diameter / 2, diameter / 2, 0, Math.PI * 2)
context.closePath()
context.clip()
context.drawImage(img, 0, 0, diameter, diameter)
const canvasImageBase64 = canvas.toDataURL('image/png')
const image = await pdfDoc.embedPng(canvasImageBase64)
const page = pdfDoc.getPage(0)
page.drawImage(image, {
x: 186.4 - diameter / 2, // Keep the same X position
y: 90 - diameter / 2, // Keep the same Y position
width: diameter * 0.8, // Reduce size (e.g., 70% of original)
height: diameter * 0.8 // Reduce size (e.g., 70% of original)
})
}
// -------------------------------------------paste the qrCode in the pd--------------------------------------
const paperContent = `
C-University
Nom et prenom: ${jsonData.f1}
Date de naissance: ${jsonData.f2}
Niveau: ${jsonData.f3}
Année scolaire: ${jsonData.f4}
Numéro d'inscription: ${jsonData.f5}
`
const qrCanvas = document.createElement('canvas')
const qrWidth = 300
QRCode.toCanvas(
qrCanvas,
paperContent,
{ errorCorrectionLevel: 'H', width: qrWidth },
(error) => {
if (error) {
console.error(error)
return
}
const qrImageBase64 = qrCanvas.toDataURL('image/png')
const qrImageBytes = Uint8Array.from(atob(qrImageBase64.split(',')[1]), (char) =>
char.charCodeAt(0)
)
;(async () => {
const page2 = pdfDoc2.getPage(0)
const qrImage = await pdfDoc2.embedPng(qrImageBytes)
const x = 4
const y = 39
const qrSize = 92
page2.drawImage(qrImage, {
x,
y,
width: qrSize,
height: qrSize
})
// Merge the front and back (pages) into a new document
const newPdfDoc = await PDFDocument.create()
// const [frontPage] = await newPdfDoc.copyPages(pdfDoc, [0]); // Front page
const [frontPage] = await newPdfDoc.copyPages(pdfDoc, [0]) // Front page
const [backPage] = await newPdfDoc.copyPages(pdfDoc2, [0]) // Back page
newPdfDoc.addPage(frontPage)
newPdfDoc.addPage(backPage)
const pdfBytes = await newPdfDoc.save()
// Trigger the download of the merged PDF
const blob = new Blob([pdfBytes], { type: 'application/pdf' })
saveAs(blob, `carte_etudiant_${jsonData.f1}.pdf`)
})()
}
)
// Serialize the PDF and return the modified document
const modifiedPdfBytes = await pdfDoc.save()
return modifiedPdfBytes
}
/**
* function who export and filled PDF data
* @param {JSON} jsonData
*/
export const processPdf = async (jsonData) => {
await fillPdfFields(jsonData)
}

54
src/renderer/src/test/qr.html

@ -0,0 +1,54 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="style.css" />
<title>Document</title>
</head>
<body>
<div class="container">
<div class="cart">
<div class="title">
<h1>université de toamasina</h1>
<p>***********</p>
<p>
ecole superieure <br />
polytechnique
</p>
</div>
<div class="content">
<div class="cart-photos">
<img src="../images/fab.jpg" alt="" />
</div>
<div class="cart-info">
<p><b>Nom</b> : BE</p>
<p><b>Prenom</b> : Joseph Fabrice</p>
<p><b>Date de naissance</b> : 11-12-2001</p>
<p><b>Niveau</b> : L3</p>
<p><b>Année scolaire</b> : 2023-2024</p>
<p><b>Num inscription</b> : 12345678900</p>
</div>
</div>
</div>
<div class="cart">
<div class="qrContent">
<div class="gauche">
<h1>QR en ligne</h1>
<img
src="https://upload.wikimedia.org/wikipedia/commons/thumb/d/d0/QR_code_for_mobile_English_Wikipedia.svg/800px-QR_code_for_mobile_English_Wikipedia.svg.png"
alt=""
/>
</div>
<div class="droite">
<h1>QR locale</h1>
<img
src="https://upload.wikimedia.org/wikipedia/commons/thumb/d/d0/QR_code_for_mobile_English_Wikipedia.svg/800px-QR_code_for_mobile_English_Wikipedia.svg.png"
alt=""
/>
</div>
</div>
</div>
</div>
</body>
</html>

74
src/renderer/src/test/relever.html

@ -0,0 +1,74 @@
<!doctype html>
<html lang="fr">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Relevé de Note</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<div class="header">
<h1>REPOBLIKAN’I MADAGASIKARA</h1>
<h2>Fitiavana-Tanindrazana-Fandrosoana</h2>
<hr />
<h2>MINISTÈRE DE L’ENSEIGNEMENT SUPÉRIEUR ET DE LA RECHERCHE SCIENTIFIQUE</h2>
<h3>UNIVERSITÉ DE TOAMASINA</h3>
<h3>ECOLE SUPERIEURE POLYTECHNIQUE</h3>
<p>Fahaizaña sy Fañahy</p>
</div>
<div class="releve">
<h3>RELEVÉE DE NOTE</h3>
<p><strong>Nom & Prenoms :</strong> F3</p>
<p>
<strong>Niveau :</strong> L1 <span><strong>Année scolaire :</strong> 2022-2023</span>
</p>
<p><strong>N° inscription :</strong> F42</p>
<table>
<thead>
<tr>
<th>Unités d’Enseignement (UE)</th>
<th>Eléments constitutifs</th>
<th>Crédits</th>
<th>Note</th>
<th>Moyenne</th>
</tr>
</thead>
<tbody>
<tr>
<td>UEMI1</td>
<td>Algèbre</td>
<td>4</td>
<td>F6</td>
<td>F8</td>
</tr>
<tr>
<td>UEMI1</td>
<td>Analyse</td>
<td>5</td>
<td>F5</td>
<td></td>
</tr>
<tr>
<td>UEPI1</td>
<td>Mécanique Général I</td>
<td>4</td>
<td>F9</td>
<td>F13</td>
</tr>
<!-- Add more rows as needed following this structure -->
<tr>
<td colspan="2"><strong>Total crédit</strong></td>
<td colspan="3">30</td>
</tr>
</tbody>
</table>
<p><strong>Moyenne générale :</strong> F41</p>
<p><strong>Observation :</strong> F47</p>
<p><strong>Décision de jury :</strong> F46</p>
<p><strong>Le Directeur :</strong> RATSIMBAZAFY Christian Pierre</p>
</div>
</body>
</html>
Loading…
Cancel
Save