commit 1af4aebf273c91ec79e0bcdf9954272f91aeef2a Author: Fabrice BJ Date: Tue May 20 08:47:39 2025 +0200 initialisation c-university diff --git a/.devdbrc b/.devdbrc new file mode 100644 index 0000000..8d1f149 --- /dev/null +++ b/.devdbrc @@ -0,0 +1,14 @@ +[ + { + "type": "sqlite", + "path": "c:\\laragon\\www\\electron-react\\data.db" + }, + { + "type": "sqlite", + "path": "c:\\laragon\\www\\electron-react\\base\\data.db" + }, + { + "type": "sqlite", + "path": "d:\\dart\\project\\CUNIVERSITY\\base\\data.db" + } +] \ No newline at end of file diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..3dce414 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,9 @@ +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 2 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true \ No newline at end of file diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..a6f34fe --- /dev/null +++ b/.eslintignore @@ -0,0 +1,4 @@ +node_modules +dist +out +.gitignore diff --git a/.eslintrc.cjs b/.eslintrc.cjs new file mode 100644 index 0000000..1bb7310 --- /dev/null +++ b/.eslintrc.cjs @@ -0,0 +1,9 @@ +module.exports = { + extends: [ + 'eslint:recommended', + 'plugin:react/recommended', + 'plugin:react/jsx-runtime', + '@electron-toolkit', + '@electron-toolkit/eslint-config-prettier' + ] +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d86a8d6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +node_modules +dist +out +fashion-street-style-horizontal-business-card-template.zip +.DS_Store +*.log* +data.db +package-lock.json diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..9c6b791 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,6 @@ +out +dist +pnpm-lock.yaml +LICENSE.md +tsconfig.json +tsconfig.*.json diff --git a/.prettierrc.yaml b/.prettierrc.yaml new file mode 100644 index 0000000..35893b3 --- /dev/null +++ b/.prettierrc.yaml @@ -0,0 +1,4 @@ +singleQuote: true +semi: false +printWidth: 100 +trailingComma: none diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..940260d --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,3 @@ +{ + "recommendations": ["dbaeumer.vscode-eslint"] +} diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..0b6b9a6 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,39 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Debug Main Process", + "type": "node", + "request": "launch", + "cwd": "${workspaceRoot}", + "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron-vite", + "windows": { + "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron-vite.cmd" + }, + "runtimeArgs": ["--sourcemap"], + "env": { + "REMOTE_DEBUGGING_PORT": "9222" + } + }, + { + "name": "Debug Renderer Process", + "port": 9222, + "request": "attach", + "type": "chrome", + "webRoot": "${workspaceFolder}/src/renderer", + "timeout": 60000, + "presentation": { + "hidden": true + } + } + ], + "compounds": [ + { + "name": "Debug All", + "configurations": ["Debug Main Process", "Debug Renderer Process"], + "presentation": { + "order": 1 + } + } + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..245ee3b --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,12 @@ +{ + "[typescript]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[javascript]": { + "editor.defaultFormatter": "vscode.typescript-language-features" + }, + "[json]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "editor.wordBasedSuggestions": "matchingDocuments" +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..0534b92 --- /dev/null +++ b/README.md @@ -0,0 +1,34 @@ +# package.json + +An Electron application with React + +## Recommended IDE Setup + +- [VSCode](https://code.visualstudio.com/) + [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) + [Prettier](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) + +## Project Setup + +### Install + +```bash +$ npm install +``` + +### Development + +```bash +$ npm run dev +``` + +### Build + +```bash +# For windows +$ npm run build:win + +# For macOS +$ npm run build:mac + +# For Linux +$ npm run build:linux +``` diff --git a/backup.js b/backup.js new file mode 100644 index 0000000..340ebba --- /dev/null +++ b/backup.js @@ -0,0 +1,118 @@ +import { app, shell, BrowserWindow, ipcMain } from 'electron' +import { join } from 'path' +import { electronApp, optimizer, is } from '@electron-toolkit/utils' +import icon from '../../resources/icon.png?asset' +const { loginUser, forgotPassword } = require('../../database/Models/Users') + +let mainWindow +let splashWindow + +function createSplashWindow() { + // Create the splash screen window + splashWindow = new BrowserWindow({ + width: 400, // Width of the splash screen + height: 300, // Height of the splash screen + frame: false, // Remove window frame for a cleaner look + alwaysOnTop: true, // Keep the splash screen on top of the main window + transparent: true, // Make the window background transparent (optional) + resizable: false, + webPreferences: { + nodeIntegration: true, + contextIsolation: false + } + }) + + // Load your splash screen HTML or content + splashWindow.loadFile(join('src/renderer/splash.html')) // Adjust path to your splash HTML file +} + +function createMainWindow() { + // Create the main browser window + mainWindow = new BrowserWindow({ + width: 1000, + minWidth: 1000, + height: 670, + minHeight: 670, + show: false, + autoHideMenuBar: true, + fullscreen: true, // This will make the window fullscreen when opened + ...(process.platform === 'linux' ? { icon } : {}), + webPreferences: { + preload: join(__dirname, '../preload/index.js'), + nodeIntegration: true, + contextIsolation: true, + sandbox: false + } + }) + + mainWindow.on('ready-to-show', () => { + // Show the main window after it is ready + mainWindow.show() + splashWindow.close() // Close the splash screen + }) + + mainWindow.webContents.setWindowOpenHandler((details) => { + shell.openExternal(details.url) + return { action: 'deny' } + }) + + // Load the remote URL for development or the local HTML file for production + if (is.dev && process.env['ELECTRON_RENDERER_URL']) { + mainWindow.loadURL(process.env['ELECTRON_RENDERER_URL']) + } else { + mainWindow.loadFile(join(__dirname, '../renderer/index.html')) + } +} + +// This method will be called when Electron has finished +// initialization and is ready to create browser windows. +// Some APIs can only be used after this event occurs. +app.whenReady().then(() => { + // Set app user model ID for Windows + electronApp.setAppUserModelId('com.electron') + + // Create splash screen + createSplashWindow() + + // Create main window + createMainWindow() + + app.on('activate', function () { + // On macOS, recreate a window in the app when the dock icon is clicked + if (BrowserWindow.getAllWindows().length === 0) createMainWindow() + }) +}) + +// Quit 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 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 + } +}) diff --git a/build/entitlements.mac.plist b/build/entitlements.mac.plist new file mode 100644 index 0000000..38c887b --- /dev/null +++ b/build/entitlements.mac.plist @@ -0,0 +1,12 @@ + + + + + com.apple.security.cs.allow-jit + + com.apple.security.cs.allow-unsigned-executable-memory + + com.apple.security.cs.allow-dyld-environment-variables + + + diff --git a/build/icon.icns b/build/icon.icns new file mode 100644 index 0000000..28644aa Binary files /dev/null and b/build/icon.icns differ diff --git a/build/icon.ico b/build/icon.ico new file mode 100644 index 0000000..72c391e Binary files /dev/null and b/build/icon.ico differ diff --git a/build/icon.png b/build/icon.png new file mode 100644 index 0000000..cf9e8b2 Binary files /dev/null and b/build/icon.png differ diff --git a/database/Models/AnneeScolaire.js b/database/Models/AnneeScolaire.js new file mode 100644 index 0000000..1f5b042 --- /dev/null +++ b/database/Models/AnneeScolaire.js @@ -0,0 +1,113 @@ +const { database } = require('../database') + +/** + * function to create annee scolaire + * @param {*} code + * @param {*} debut + * @param {*} fin + * @returns promise + */ +async function createAnneeScolaire(code, debut, fin) { + const query = database.prepare('INSERT INTO anneescolaire (code, debut, fin) VALUES (?, ?, ?)') + + try { + let response = await query.run(code, debut, fin) + + return response + } catch (error) { + return error + } +} + +/** + * function to get all année scolaire + * @returns promise + */ +async function getAnneeScolaire() { + const query = database.prepare('SELECT * FROM anneescolaire ORDER BY code DESC') + + try { + let response = await query.all() + + return response + } catch (error) { + return error + } +} + +async function getInterval() { + const query = database.prepare('SELECT debut, fin FROM anneescolaire ORDER BY id DESC') + + try { + let response = await query.all() + + return response + } catch (error) { + return error + } +} + +async function getSingleAnneScolaire(id) { + const query = database.prepare('SELECT * FROM anneescolaire WHERE id = ?') + + try { + let response = await query.get(id) + + return response + } catch (error) { + return error + } +} + +async function deleteAnneeScolaire(id) { + const query = database.prepare('DELETE FROM anneescolaire WHERE id = ?') + + try { + let response = query.run(id) + + return response + } catch (error) { + return error + } +} + +async function updateAnneeScolaire(id, code, debut, fin) { + const query = database.prepare( + 'UPDATE anneescolaire SET code = ?, debut = ?, fin = ? WHERE id = ?' + ) + + try { + let response = query.run(code, debut, fin, id) + + return response + } catch (error) { + return error + } +} + +async function setCurrent(id) { + const query = database.prepare( + 'UPDATE anneescolaire SET is_current = 0 WHERE id > 0 AND is_current = 1' + ) + const query2 = database.prepare('UPDATE anneescolaire SET is_current = 1 WHERE id = ?') + + let clear = query.run() + console.log(clear) + try { + let response = query2.run(id) + + return response + } catch (error) { + return error + } +} + +module.exports = { + createAnneeScolaire, + getAnneeScolaire, + getInterval, + deleteAnneeScolaire, + getSingleAnneScolaire, + updateAnneeScolaire, + setCurrent +} diff --git a/database/Models/Etudiants.js b/database/Models/Etudiants.js new file mode 100644 index 0000000..af8e672 --- /dev/null +++ b/database/Models/Etudiants.js @@ -0,0 +1,305 @@ +const { database } = require('../database') + +/** + * function to insert etudiant into databases + */ +async function 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 +) { + const query = database.prepare( + 'INSERT INTO etudiants (nom, prenom, photos, date_de_naissances, niveau, annee_scolaire, status, mention_id, num_inscription, sexe, cin, date_delivrence, nationalite, annee_bacc, serie, boursier, domaine, contact, parcours) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)' + ) + + try { + let response = await query.run( + nom, + prenom, + photos, + date_de_naissances, + niveau, + annee_scolaire, + status, + mention_id, + num_inscription, + sexe, + cin, + date_delivrence, + nationaliter, + annee_bacc, + serie, + boursier, + domaine, + contact, + parcours + ) + return response + } catch (error) { + return error + } +} + +/** + * function to get all etudiants + * + * @returns JSON + */ +async function getAllEtudiants() { + const query = database.prepare('SELECT * FROM etudiants ORDER BY annee_scolaire DESC') + try { + let response = await query.all() + + return response + } catch (error) { + return error + } +} + +/** + * function to return a single etudiant + * and display it on the screen + * + * @param {int} id + * @returns Promise + */ +async function getSingleEtudiant(id) { + const query = database.prepare('SELECT * FROM etudiants WHERE id = ?') + + try { + const etudiants = await query.get(id) + + if (etudiants) { + return etudiants + } else { + return { message: 'etudiants pas trouver' } + } + } catch (error) { + return error + } +} + +/** + * function to get all etudiants M2 + * + * @returns JSON + */ +async function FilterDataByNiveau(niveau) { + const query = database.prepare( + 'SELECT * FROM etudiants WHERE niveau = ? ORDER BY annee_scolaire DESC' + ) + try { + let response = await query.all(niveau) + + return response + } catch (error) { + return error + } +} + +/** + * function to update etudiants + * + * @param {*} nom + * @param {*} prenom + * @param {*} photos + * @param {*} date_de_naissances + * @param {*} niveau + * @param {*} annee_scolaire + * @param {*} status + * @param {*} num_inscription + * @param {*} id + * @returns promise + */ +async function updateEtudiant( + nom, + prenom, + photos, + date_de_naissances, + niveau, + annee_scolaire, + status, + mention_id, + num_inscription, + id, + sexe, + nationalite, + cin, + date_delivrence, + annee_bacc, + serie, + boursier, + domaine, + contact, + parcours +) { + const query = database.prepare( + 'UPDATE etudiants SET nom = ?, prenom = ?, photos = ?, date_de_naissances = ?, niveau = ?, annee_scolaire = ?, status = ?, mention_id = ?, num_inscription = ?, sexe = ?, cin = ?, date_delivrence = ?, nationalite = ?, annee_bacc = ?, serie = ?, boursier = ?, domaine = ?, contact = ?, parcours = ? WHERE id = ?' + ) + + try { + let response = await query.run( + nom, + prenom, + photos, + date_de_naissances, + niveau, + annee_scolaire, + status, + mention_id, + num_inscription, + sexe, + cin, + date_delivrence, + nationalite, + annee_bacc, + serie, + boursier, + domaine, + contact, + parcours, + id + ) + + return response + } catch (error) { + return error + } +} + +/** + * function to return the needed data in dashboard + * + * @returns promise + */ +async function getDataToDashboard() { + const query = database.prepare('SELECT * FROM niveaus') + const query2 = database.prepare('SELECT * FROM etudiants') + const query3 = database.prepare('SELECT DISTINCT annee_scolaire FROM etudiants') // get all année scolaire sans doublan + + try { + let niveau = query.all() + let etudiants = query2.all() + let anne_scolaire = query3.all() + + return { niveau, etudiants, anne_scolaire } + } catch (error) { + return error + } +} + +async function changePDP(photos, id) { + const query = database.prepare('UPDATE etudiants SET photos = ? WHERE id = ?') + + try { + let response = await query.run(photos, id) + + return response + } catch (error) { + return error + } +} + +async function updateParcours(parcours, id) { + const query = database.prepare('UPDATE etudiants SET parcours = ? WHERE id = ?') + + try { + let response = await query.run(parcours, id) + + return response + } catch (error) { + return error + } +} + +async function createTranche(etudiant_id, tranchename, montant) { + const query = database.prepare( + 'INSERT INTO trancheecolage (etudiant_id, tranchename, montant) VALUES (?, ?, ?)' + ) + + try { + let response = query.run(etudiant_id, tranchename, montant) + + return response + } catch (error) { + return error + } +} + +async function getTranche(id) { + const query = database.prepare('SELECT * FROM trancheecolage WHERE etudiant_id = ?') + + try { + let response = query.all(id) + + return response + } catch (error) { + return error + } +} + +async function updateTranche(id, tranchename, montant) { + const query = database.prepare( + 'UPDATE trancheecolage SET tranchename = ?, montant = ? WHERE id = ?' + ) + + try { + let response = query.run(tranchename, montant, id) + + return response + } catch (error) { + return error + } +} + +async function deleteTranche(id) { + const query = database.prepare('DELETE FROM trancheecolage WHERE id = ?') + + try { + let response = query.run(id) + + return response + } catch (error) { + return error + } +} + +async function getSingleTranche(id) { + try { + return await database.prepare('SELECT * FROM trancheecolage WHERE id = ?').get(id) + } catch (error) { + return error + } +} + +module.exports = { + insertEtudiant, + getAllEtudiants, + FilterDataByNiveau, + getSingleEtudiant, + updateEtudiant, + getDataToDashboard, + changePDP, + updateParcours, + createTranche, + getTranche, + updateTranche, + deleteTranche, + getSingleTranche +} diff --git a/database/Models/IpConfig.js b/database/Models/IpConfig.js new file mode 100644 index 0000000..e018c71 --- /dev/null +++ b/database/Models/IpConfig.js @@ -0,0 +1,41 @@ +const { database } = require('../database'); + +const createConfigIp = (ipname) => { + const query = database.prepare('INSERT INTO ipconfig (ipname) VALUES (?)'); + + try { + let response = query.run(ipname) + + return response; + } catch (error) { + return error; + } +} + +const getIPConfig = async () => { + try { + let response = await database.prepare('SELECT * FROM ipconfig WHERE id = ?').get(1); + + return response; + } catch (error) { + return error + } +} + +const updateIPConfig = async (id, name) => { + let query = database.prepare('UPDATE ipconfig SET ipname = ? WHERE id = ?'); + + try { + let response = await query.run(name, id); + + return response; + } catch (error) { + return error; + } +} + +module.exports = { + createConfigIp, + getIPConfig, + updateIPConfig +} diff --git a/database/Models/Matieres.js b/database/Models/Matieres.js new file mode 100644 index 0000000..164363d --- /dev/null +++ b/database/Models/Matieres.js @@ -0,0 +1,354 @@ +const { database } = require('../database') +const { matiereSysteme } = require('../function/System') +const { convertArrayAndString } = require('../function/StringArrayConvertion') + +/** + * function uset to create matiere + * @param {*} nom + * @returns Promise + */ +async function createMatiere(nom, credit, uniter, ue) { + const query = database.prepare( + 'INSERT INTO matieres (nom, unite_enseignement, credit, heure, ue) VALUES (?, ?, ?, ?, ?)' + ) + + const uniterHeure = database.prepare('SELECT uniter_heure FROM nessesaryTable').get() + const heure = credit * uniterHeure.uniter_heure + + try { + response = await query.run(nom, uniter, credit, heure, ue) + + return response + } catch (error) { + return error + } +} + +/** + * function to get all matieres + * @returns Promise + */ +async function getMatiere() { + const query = database.prepare('SELECT * FROM matieres ORDER BY id DESC') + + try { + let response = await query.all() + + return response + } catch (error) { + return error + } +} + +async function displayMatiereFromForm(niveau, mention_id, parcours) { + // Fetch the semestre array + let semestre = await matiereSysteme(niveau) // Ensure this returns an array with at least 2 items + + if (niveau !== 'L1') { + if (semestre.length < 2) { + console.error('Error: Semestre array does not contain enough elements.') + return + } + + // Prepare the query + let matiereQuery = database.prepare(` + SELECT DISTINCT m.* + FROM matieres m + JOIN matiere_semestre ms ON m.id = ms.matiere_id + JOIN semestres s ON ms.semestre_id = s.id + JOIN parcoursmatiere pm ON m.id = pm.matiere_id + JOIN parcours p ON pm.parcour_id = p.id + WHERE (s.nom LIKE ? OR s.nom LIKE ?) + AND ms.mention_id = ? + AND p.nom = ? + `) + + try { + // Execute the query with parameters + let response = matiereQuery.all(`%${semestre[0]}%`, `%${semestre[1]}%`, mention_id, parcours) + + console.log(response) + // Log the response + return response + } catch (error) { + return error + } + } else { + if (semestre.length < 2) { + console.error('Error: Semestre array does not contain enough elements.') + return + } + + // Prepare the query + let matiereQuery = database.prepare(` + SELECT DISTINCT m.* + FROM matieres m + JOIN matiere_semestre ms ON m.id = ms.matiere_id + JOIN semestres s ON ms.semestre_id = s.id + WHERE (s.nom LIKE ? OR s.nom LIKE ?) + AND ms.mention_id = ? + `) + + try { + // Execute the query with parameters + let response = matiereQuery.all(`%${semestre[0]}%`, `%${semestre[1]}%`, mention_id) + console.log(response) + // Log the response + return response + } catch (error) { + return error + } + } +} + +/** + * function to get single matiere + * @param {*} id + * @returns promise + */ +async function getSingleMatiere(id) { + const query = await database.prepare('SELECT * FROM matieres WHERE id = ?') + + try { + let response = query.get(id) + + return response + } catch (error) { + return error + } +} + +/** + * function used when updating matiere + * @param {*} nom + * @param {*} id + * @returns promise + */ +async function updateMatiere(nom, id, credit, uniter, ue) { + const query = database.prepare( + 'UPDATE matieres SET nom = ?, credit = ?, unite_enseignement = ?, heure = ?, ue = ? WHERE id = ?' + ) + + const uniterHeure = await database.prepare('SELECT uniter_heure FROM nessesaryTable').get() + const heure = credit * uniterHeure.uniter_heure + + try { + response = await query.run(nom, credit, uniter, heure, ue, id) + + return response + } catch (error) { + return error + } +} + +async function deleteMatiere(id) { + const query = database.prepare('DELETE FROM matieres WHERE id = ?') + + try { + let response = query.run(id) + + return response + } catch (error) { + return error + } +} + +async function asygnationToMention(formData, id) { + const clearQuery = database.prepare('DELETE FROM matiere_mention WHERE matiere_id = ?') + const query = database.prepare( + 'INSERT INTO matiere_mention (matiere_id, mention_id) VALUES (?,?)' + ) + const selectedKeys = Object.keys(formData).filter((key) => formData[key]) + const placeholders = selectedKeys.map(() => '?').join(',') + // Prepare the query with placeholders + const clearSemestreMentionQuery = database.prepare( + `DELETE FROM matiere_semestre WHERE matiere_id = ? AND mention_id NOT IN (${placeholders})` + ) + + const clearNoreQuery = database.prepare( + `DELETE FROM notes WHERE matiere_id = ? AND mention_id NOT IN (${placeholders})` + ) + + try { + let response + await clearQuery.run(id) + await clearNoreQuery.run(id, ...selectedKeys) + clearSemestreMentionQuery.run(id, ...selectedKeys) + // use transaction for speed execution + database.transaction(() => { + for (let index = 0; index < selectedKeys.length; index++) { + response = query.run(id, selectedKeys[index]) + } + })() + + return response + } catch (error) { + return error + } +} + +async function getMentionMatiere(id) { + const query = database.prepare('SELECT * FROM matiere_mention WHERE matiere_id = ?') + + try { + let response = await query.all(id) + + return response + } catch (error) { + return error + } +} + +async function getMentionMatiereChecked(matiere_id) { + const getMentionMatiere = database.prepare('SELECT * FROM matiere_mention WHERE matiere_id = ?') + let MentionArray = await getMentionMatiere.all(matiere_id) + let arrayID = [] + + for (let index = 0; index < MentionArray.length; index++) { + arrayID.push(MentionArray[index].mention_id) + } + + const mentionQuery = database.prepare( + `SELECT * FROM mentions WHERE id IN (${arrayID.map(() => '?').join(', ')})` + ) + + try { + const results = await mentionQuery.all(...arrayID) + + return results + } catch (error) { + return error + } +} + +async function getSemestreMatiere(id) { + const query = database.prepare('SELECT * FROM matiere_semestre WHERE matiere_id = ?') + + try { + let response = await query.all(id) + + return response + } catch (error) { + return error + } +} + +async function getSemestre() { + const query = database.prepare('SELECT * FROM semestres') + + try { + let response = await query.all() + + return response + } catch (error) { + return error + } +} + +async function insertUpdateMentionSemestre(matiere_id, formData) { + const clearQuery = database.prepare('DELETE FROM matiere_semestre WHERE matiere_id = ?') + clearQuery.run(matiere_id) + + const query = database.prepare( + 'INSERT INTO matiere_semestre (matiere_id, semestre_id, mention_id) VALUES (?, ?, ?)' + ) + let response + database.transaction(() => { + for (const key in formData) { + if (formData.hasOwnProperty(key)) { + for (let jindex = 0; jindex < formData[key].length; jindex++) { + response = query.run(matiere_id, formData[key][jindex], key) + } + } + } + })() + return response +} + +async function getEnseignants() { + const getIdQuery = database.prepare( + 'SELECT id FROM matiereEnseignants GROUP BY matiere_id ORDER BY MAX(date) DESC' + ) + + const query = database.prepare( + 'SELECT * FROM matiereEnseignants WHERE id IN (' + + new Array(getIdQuery.all().length).fill('?').join(',') + + ')' + ) + + try { + // Get the latest `id` for each `matiere_id` + const latestIds = getIdQuery.all().map((row) => row.id) + + // If no ids exist, return an empty array + if (latestIds.length === 0) { + return [] + } + + // Fetch the full details using the filtered IDs + let response = query.all(...latestIds) + + return response + } catch (error) { + return error + } +} + +async function insertNewProf(matiere_id, nom, prenom, contact, date) { + const query = database.prepare( + 'INSERT INTO matiereEnseignants (matiere_id, nom_enseignant, prenom_enseignant, contact, date) VALUES (?, ?, ?, ?, ?)' + ) + + try { + let response = query.run(matiere_id, nom, prenom, contact, date) + + return response + } catch (error) { + return error + } +} + +async function getSIngleProf(id) { + try { + const prof = await database + .prepare('SELECT * FROM matiereEnseignants WHERE matiere_id = ?') + .get(id) + + return prof + } catch (error) { + return error + } +} + +async function updateProf(matiere_id, nom, prenom, contact, date) { + const query = database.prepare( + 'UPDATE matiereEnseignants SET nom_enseignant = ?, prenom_enseignant = ?, contact = ?, date = ? WHERE matiere_id = ?' + ) + + try { + let response = query.run(nom, prenom, contact, date, matiere_id) + + return response + } catch (error) { + return error + } +} + +module.exports = { + createMatiere, + getSIngleProf, + getMatiere, + getSingleMatiere, + updateMatiere, + displayMatiereFromForm, + deleteMatiere, + asygnationToMention, + getMentionMatiere, + getMentionMatiereChecked, + getSemestreMatiere, + getSemestre, + insertUpdateMentionSemestre, + getEnseignants, + insertNewProf, + updateProf +} diff --git a/database/Models/Mentions.js b/database/Models/Mentions.js new file mode 100644 index 0000000..5644c19 --- /dev/null +++ b/database/Models/Mentions.js @@ -0,0 +1,69 @@ +const { database } = require('../database') + +async function createMention(nom, uniter) { + const query = database.prepare('INSERT INTO mentions (nom, uniter) VALUES (?, ?)') + + try { + let response = await query.run(nom, uniter) + + return response + } catch (error) { + return error + } +} + +async function deleteMention(id) { + const query = database.prepare('DELETE FROM mentions WHERE id = ?') + + try { + let response = await query.run(id) + + return response + } catch (error) { + return error + } +} + +async function getMentions() { + const query = database.prepare('SELECT * FROM mentions') + + try { + let response = await query.all() + + return response + } catch (error) { + return error + } +} + +async function getSingleMention(id) { + const query = database.prepare('SELECT * FROM mentions WHERE id = ?') + + try { + let response = query.get(id) + + return response + } catch (error) { + return error + } +} + +async function updateMention(nom, uniter, id) { + const query = database.prepare('UPDATE mentions SET nom = ?, uniter = ? WHERE id = ?') + + try { + let response = query.run(nom, uniter, id) + + return response + } catch (error) { + return error + } +} + +module.exports = { + createMention, + deleteMention, + getMentions, + getSingleMention, + updateMention +} diff --git a/database/Models/Niveau.js b/database/Models/Niveau.js new file mode 100644 index 0000000..f140067 --- /dev/null +++ b/database/Models/Niveau.js @@ -0,0 +1,106 @@ +const { database } = require('../database') + +/** + * function to insert niveau to database + */ +async function insertNiveau(nom) { + // Initialize an array to hold the individual names + let nom_multiple = [] + + // Check if 'nom' contains a comma and split it into multiple names + if (nom.includes(',')) { + nom_multiple = nom.split(',' || ', ').map((name) => name.trim()) // Trim to remove extra spaces + } else { + nom_multiple = [nom] // Treat it as a single name + } + + // Prepare the query for insertion + const query = database.prepare('INSERT INTO niveaus (nom) VALUES (?)') + + try { + let responses = [] + for (const name of nom_multiple) { + // Insert each name and collect the response + let response = await query.run(name) + responses.push(response) + } + if (nom_multiple.length === responses.length) { + return JSON.stringify({ + last_row: nom_multiple.length, + changes: 1 + }) + } else return false + } catch (error) { + return error // Return the error if any occurs + } +} + +/** + * function to get single niveau for updating + * @param {*} id + * @returns Promise + */ +async function getSingleNiveau(id) { + const query = database.prepare('SELECT * FROM niveaus WHERE id = ?') + + try { + let response = await query.get(id) + + return response + } catch (error) { + return error + } +} + +/** + * function used to update the niveau + * @param {*} nom + * @param {*} id + * @returns Promise + */ +async function updateNiveau(nom, id) { + const query = database.prepare('UPDATE niveaus SET nom = ? WHERE id = ?') + + try { + let response = query.run(nom, id) + + return response + } catch (error) { + return error + } +} + +/** + * function to get all niveau + */ +async function getNiveau() { + const query = database.prepare('SELECT * FROM niveaus') + + try { + let response = query.all() + + return response + } catch (error) { + return error + } +} + +async function deleteNiveau(id) { + const query = database.prepare('DELETE FROM niveaus WHERE id = ?') + + try { + let response = await query.run(id) + + return response + } catch (error) { + return error + } +} + +module.exports = { + getNiveau, + insertNiveau, + getSingleNiveau, + updateNiveau, + deleteNiveau +} diff --git a/database/Models/NoteRepechage.js b/database/Models/NoteRepechage.js new file mode 100644 index 0000000..e4ddd74 --- /dev/null +++ b/database/Models/NoteRepechage.js @@ -0,0 +1,222 @@ +const { database } = require('../database') +const { getNiveau } = require('./Niveau') +const { matiereSysteme } = require('../function/System') + +/** + * Function to insert notes into the database + * @param {Object} formData - The form data containing subject names and values + * @param {number} etudiant_id - The student ID + * @param {string} etudiant_niveau - The student level + * @returns {Promise} - Promise resolving to the database response or an error + */ +async function insertNoteRepech( + etudiant_id, + etudiant_niveau, + mention_id, + formData, + annee_scolaire +) { + // Extract keys and values dynamically + const matiere_id = Object.keys(formData) + const values = Object.values(formData) + + const query = database.prepare( + `INSERT INTO notesrepech (etudiant_id, matiere_id, etudiant_niveau, mention_id, note, annee_scolaire) VALUES (?, ?, ?, ?, ?, ?)` + ) + console.log(annee_scolaire) + try { + let response + for (let j = 0; j < matiere_id.length; j++) { + response = await query.run( + etudiant_id, + matiere_id[j], + etudiant_niveau, + mention_id, + parseFloat(values[j].replace(',', '.')) || 0, + annee_scolaire + ) + } + return response + } catch (error) { + return error + } +} + +/** + * + * @returns promise + */ +async function getNoteOnline() { + const query = database.prepare('SELECT notes.* FROM notes ') + + try { + let response = await query.all() + + return response + } catch (error) { + return error + } +} + +/** + * + * @returns promise + */ +async function getNoteRepech(id, niveau) { + let semestre = await matiereSysteme(niveau) + + const query2 = database.prepare( + 'SELECT notesrepech.*, matieres.* FROM notesrepech JOIN matieres ON (notesrepech.matiere_id = matieres.id) WHERE notesrepech.etudiant_id = ? AND notesrepech.etudiant_niveau = ?' + ) + + try { + let response2 = query2.all(id, niveau) + return response2 + } catch (error) { + console.error('Error in query2:', error) + return error + } +} + +/** + * Verify if a student has notes + * @returns {Promise} - Promise resolving to an array of notes or an empty array + */ +async function verifyEtudiantIfHeHasNotesRepech() { + try { + // Prepare the query to filter by etudiant_id and etudiant_niveau + const query = database.prepare('SELECT DISTINCT etudiant_id, etudiant_niveau FROM notesrepech') + + // Execute the query with the provided parameters + const response = query.all() + + // Return the response + return response + } catch (error) { + console.error('Error verifying student notes:', error) + throw error + } +} + +/** + * function to show moyenne in screen + * + * @returns promise + */ +async function showMoyenRepech(niveau, scolaire) { + const query = database.prepare( + `SELECT DISTINCT etudiant_id FROM notesrepech WHERE etudiant_niveau = ? AND annee_scolaire = ?` + ) + + let etudiantWithNotes = await query.all(niveau, scolaire) + + let allEtudiantWithNotes = [] + + const query2 = database.prepare( + 'SELECT notesrepech.*, etudiants.*, matieres.id, matieres.nom AS nomMat, matieres.credit FROM notesrepech INNER JOIN etudiants ON (notesrepech.etudiant_id = etudiants.id) INNER JOIN matieres ON (notesrepech.matiere_id = matieres.id) WHERE notesrepech.etudiant_id = ?' + ) + + try { + for (let index = 0; index < etudiantWithNotes.length; index++) { + allEtudiantWithNotes.push(query2.all(etudiantWithNotes[index].etudiant_id)) + } + + return allEtudiantWithNotes + } catch (error) { + return error + } +} + +/** + * function used when updating note + * @param {Object} formData - The form data containing subject names and values + * @param {string} niveau - The student level + * @returns {Promise} - Promise resolving to the database response or an error + */ +async function updateNoteRepech(formData, niveau, id) { + // Extract keys and values dynamically + const matiere_id = Object.keys(formData) + const values = Object.values(formData) + + const query = database.prepare( + 'UPDATE notesrepech SET note= ? WHERE etudiant_id = ? AND etudiant_niveau = ? AND matiere_id = ?' + ) + + try { + let response + + for (let index = 0; index < matiere_id.length; index++) { + let data = values[index] + if (typeof data === 'string') { + console.log(parseFloat(data.replace(',', '.'))) + } else { + console.log(parseFloat(String(data).replace(',', '.'))) + } + response = await query.run(data, id, niveau, matiere_id[index]) + } + + return response + } catch (error) { + return error + } +} + +async function blockShowMoyeneRepech() { + const query = database.prepare( + 'SELECT DISTINCT etudiant_niveau, annee_scolaire FROM notesrepech ORDER BY annee_scolaire DESC' + ) + + const queryMention = database.prepare('SELECT * FROM mentions') + + try { + let response = await query.all() + let mention = await queryMention.all() + let niveau = response.map((item) => item.etudiant_niveau) + let annee_scolaire = response.map((item) => item.annee_scolaire) + const query2 = database.prepare( + `SELECT notesrepech.*, etudiants.id AS etudiantsId, etudiants.mention_id AS mentionId, etudiants.niveau, matieres.* FROM notesrepech INNER JOIN etudiants ON (notesrepech.etudiant_id = etudiants.id) INNER JOIN matieres ON (notesrepech.matiere_id = matieres.id) WHERE notesrepech.etudiant_niveau = ? AND notesrepech.annee_scolaire = ?` + ) + + let allData = [] + + for (let index = 0; index < niveau.length; index++) { + allData.push(await query2.all(niveau[index], annee_scolaire[index])) + } + + return { response, allData, mention } + } catch (error) { + return error + } +} + +/** + * get all note with matiere for single student + * @param {*} id + * @param {*} niveau + * @param {*} annee_scolaire + * @returns promise + */ +async function getMatiereAndNoteRepech(id, niveau, annee_scolaire) { + const query = database.prepare( + 'SELECT * FROM notesrepech INNER JOIN matieres ON (notesrepech.matiere_id = matieres.id) WHERE notesrepech.etudiant_id = ? AND notesrepech.etudiant_niveau = ? AND notesrepech.annee_scolaire = ?' + ) + + try { + let response = await query.all(id, niveau, annee_scolaire) + + return response + } catch (error) { + return error + } +} + +module.exports = { + insertNoteRepech, + getNoteRepech, + showMoyenRepech, + getNoteOnline, + verifyEtudiantIfHeHasNotesRepech, + updateNoteRepech, + blockShowMoyeneRepech, + getMatiereAndNoteRepech +} diff --git a/database/Models/NoteSysrem.js b/database/Models/NoteSysrem.js new file mode 100644 index 0000000..7ebf516 --- /dev/null +++ b/database/Models/NoteSysrem.js @@ -0,0 +1,44 @@ +const { database } = require('../database') + +/** + * function to get all Systeme note + * @returns promise + */ +async function getSysteme() { + const query = database.prepare('SELECT * FROM notesystems') + + try { + let response = await query.get() + + return response + } catch (error) { + return error + } +} + +/** + * function to update systeme note + * @param {*} id + * @param {*} admis + * @param {*} redouble + * @param {*} renvoyer + * @returns promise + */ +async function updateSysteme(id, admis, redouble, renvoyer) { + const query = database.prepare( + 'UPDATE notesystems SET admis = ?, redouble = ?, renvoyer = ? WHERE id = ?' + ) + + try { + let response = await query.run(admis, redouble, renvoyer, id) + + return response + } catch (error) { + return error + } +} + +module.exports = { + getSysteme, + updateSysteme +} diff --git a/database/Models/Notes.js b/database/Models/Notes.js new file mode 100644 index 0000000..98657bf --- /dev/null +++ b/database/Models/Notes.js @@ -0,0 +1,317 @@ +const { database } = require('../database') +const { getNiveau } = require('./Niveau') +const { matiereSysteme } = require('../function/System') + +/** + * Function to insert notes into the database + * @param {Object} formData - The form data containing subject names and values + * @param {number} etudiant_id - The student ID + * @param {string} etudiant_niveau - The student level + * @returns {Promise} - Promise resolving to the database response or an error + */ +async function insertNote(etudiant_id, etudiant_niveau, mention_id, formData, annee_scolaire) { + // Extract keys and values dynamically + const matiere_id = Object.keys(formData) + const values = Object.values(formData) + + const query = database.prepare( + `INSERT INTO notes (etudiant_id, matiere_id, etudiant_niveau, mention_id, note, annee_scolaire) VALUES (?, ?, ?, ?, ?, ?)` + ) + + const insertRepechQuery = database.prepare( + `INSERT INTO notesrepech (etudiant_id, matiere_id, etudiant_niveau, mention_id, note, annee_scolaire) VALUES (?, ?, ?, ?, ?, ?)` + ) + + console.log(annee_scolaire) + try { + let response + let newMatiereId = [] + + // run the session normale + database.transaction(() => { + for (let j = 0; j < matiere_id.length; j++) { + if (values[j] < 10) { + newMatiereId.push(matiere_id[j]) + } + + response = query.run( + etudiant_id, + matiere_id[j], + etudiant_niveau, + mention_id, + parseFloat(values[j].replace(',', '.')) || 0, + annee_scolaire + ) + } + })() + + // run the second session and set it to be 0 to display it from screen + database.transaction(() => { + for (let j = 0; j < newMatiereId.length; j++) { + response = insertRepechQuery.run( + etudiant_id, + newMatiereId[j], + etudiant_niveau, + mention_id, + 0, + annee_scolaire + ) + } + })() + + return response + } catch (error) { + return error + } +} + +/** + * + * @returns promise + */ +async function getNoteOnline() { + const query = database.prepare('SELECT notes.* FROM notes ') + + try { + let response = await query.all() + + return response + } catch (error) { + return error + } +} + +/** + * + * @returns promise + */ +async function getNote(id, niveau, mention_id) { + let semestre = await matiereSysteme(niveau) + + const query = database.prepare( + 'SELECT notes.*, matieres.* FROM notes JOIN matieres ON (notes.matiere_id = matieres.id) WHERE notes.etudiant_id = ? AND notes.etudiant_niveau = ?' + ) + const matiereQuery = database.prepare(` + SELECT DISTINCT m.* + FROM matieres m + JOIN matiere_semestre ms ON m.id = ms.matiere_id + JOIN semestres s ON ms.semestre_id = s.id + WHERE (s.nom LIKE ? OR s.nom LIKE ?) + AND ms.mention_id = ? + `) + + let res = await matiereQuery.all(`%${semestre[0]}%`, `%${semestre[1]}%`, mention_id) + + let response = await query.all(id, niveau) + const infoEtudiants = database.prepare('SELECT * FROM etudiants WHERE id = ?') + let etudiant = await infoEtudiants.get(id) + + let arrayResponseIdMatiere = [] + for (let index = 0; index < response.length; index++) { + arrayResponseIdMatiere.push(response[index].matiere_id) + } + + const filteredIds = res + .filter((matiere) => !arrayResponseIdMatiere.includes(matiere.id)) + .map((matiere) => matiere.id) + + const json = filteredIds.reduce((acc, id) => { + acc[id] = '0' + return acc + }, {}) + + const query2 = database.prepare( + 'SELECT notes.*, matieres.* FROM notes JOIN matieres ON (notes.matiere_id = matieres.id) WHERE notes.etudiant_id = ? AND notes.etudiant_niveau = ?' + ) + + try { + let response2 = query2.all(id, niveau) + return response2 + } catch (error) { + console.error('Error in query2:', error) + return error + } +} + +/** + * Verify if a student has notes + * @returns {Promise} - Promise resolving to an array of notes or an empty array + */ +async function verifyEtudiantIfHeHasNotes() { + try { + // Prepare the query to filter by etudiant_id and etudiant_niveau + const query = database.prepare('SELECT DISTINCT etudiant_id, etudiant_niveau FROM notes') + + // Execute the query with the provided parameters + const response = query.all() + + // Return the response + return response + } catch (error) { + console.error('Error verifying student notes:', error) + throw error + } +} + +/** + * function to show moyenne in screen + * + * @returns promise + */ +async function showMoyen(niveau, scolaire) { + const query = database.prepare( + `SELECT DISTINCT etudiant_id FROM notes WHERE etudiant_niveau = ? AND annee_scolaire = ?` + ) + + let etudiantWithNotes = await query.all(niveau, scolaire) + + let allEtudiantWithNotes = [] + + const query2 = database.prepare( + 'SELECT notes.*, etudiants.*, matieres.id, matieres.nom AS nomMat, matieres.credit FROM notes INNER JOIN etudiants ON (notes.etudiant_id = etudiants.id) INNER JOIN matieres ON (notes.matiere_id = matieres.id) WHERE notes.etudiant_id = ?' + ) + + try { + for (let index = 0; index < etudiantWithNotes.length; index++) { + allEtudiantWithNotes.push(query2.all(etudiantWithNotes[index].etudiant_id)) + } + + return allEtudiantWithNotes + } catch (error) { + return error + } +} + +/** + * function used when updating note + * @param {Object} formData - The form data containing subject names and values + * @param {string} niveau - The student level + * @returns {Promise} - Promise resolving to the database response or an error + */ +async function updateNote(formData, niveau, id, mention_id, annee_scolaire) { + // Extract keys and values dynamically + const matiere_id = Object.keys(formData) + const values = Object.values(formData) + + const query = database.prepare( + 'UPDATE notes SET note= ? WHERE etudiant_id = ? AND etudiant_niveau = ? AND matiere_id = ?' + ) + + const clearFromRepech = database.prepare( + 'DELETE FROM notesrepech WHERE etudiant_id = ? AND etudiant_niveau = ? AND matiere_id = ?' + ) + const insertRepechQuery = database.prepare( + `INSERT INTO notesrepech (etudiant_id, matiere_id, etudiant_niveau, mention_id, note, annee_scolaire) VALUES (?, ?, ?, ?, ?, ?)` + ) + + const checkRepechQuery = database.prepare( + 'SELECT * FROM notesrepech WHERE etudiant_id = ? AND matiere_id = ? AND etudiant_niveau = ?' + ) + + try { + let response + + for (let index = 0; index < matiere_id.length; index++) { + let data = values[index] + if (typeof data === 'string') { + data = parseFloat(data.replace(',', '.')) + } else { + data = parseFloat(String(data).replace(',', '.')) + } + let check = await checkRepechQuery.get(id, matiere_id[index], niveau) + if (data < 10) { + if (!check) { + insertRepechQuery.run(id, matiere_id[index], niveau, mention_id, 0, annee_scolaire) + } + response = await query.run(data, id, niveau, matiere_id[index]) + } else { + clearFromRepech.run(id, niveau, matiere_id[index]) + response = await query.run(data, id, niveau, matiere_id[index]) + } + } + + return response + } catch (error) { + return error + } +} + +async function blockShowMoyene() { + const query = database.prepare( + 'SELECT DISTINCT etudiant_niveau, annee_scolaire FROM notes ORDER BY annee_scolaire DESC' + ) + + const queryMention = database.prepare('SELECT * FROM mentions') + + try { + let response = await query.all() + let mention = await queryMention.all() + let niveau = response.map((item) => item.etudiant_niveau) + let annee_scolaire = response.map((item) => item.annee_scolaire) + const query2 = database.prepare( + `SELECT notes.*, etudiants.id AS etudiantsId, etudiants.mention_id AS mentionId, etudiants.niveau, matieres.* FROM notes INNER JOIN etudiants ON (notes.etudiant_id = etudiants.id) INNER JOIN matieres ON (notes.matiere_id = matieres.id) WHERE notes.etudiant_niveau = ? AND notes.annee_scolaire = ?` + ) + + let allData = [] + + for (let index = 0; index < niveau.length; index++) { + allData.push(await query2.all(niveau[index], annee_scolaire[index])) + } + + return { response, allData, mention } + } catch (error) { + return error + } +} + +/** + * get all note with matiere for single student + * @param {*} id + * @param {*} niveau + * @param {*} annee_scolaire + * @returns promise + */ +async function getMatiereAndNote(id, niveau, annee_scolaire) { + const query = database.prepare( + 'SELECT * FROM notes INNER JOIN matieres ON (notes.matiere_id = matieres.id) WHERE notes.etudiant_id = ? AND notes.etudiant_niveau = ? AND notes.annee_scolaire = ?' + ) + + try { + let response = await query.all(id, niveau, annee_scolaire) + + return response + } catch (error) { + return error + } +} + +async function getNotesWithRepechToDisplay(id, anneescolaire, niveau) { + const queryNoteNormal = database.prepare( + 'SELECT * FROM notes INNER JOIN matieres ON (notes.matiere_id = matieres.id) WHERE notes.etudiant_id = ? AND notes.annee_scolaire = ? AND notes.etudiant_niveau = ?' + ) + let noteNormal = await queryNoteNormal.all(id, anneescolaire, niveau) + + const queryNoteRepech = database.prepare( + 'SELECT * FROM notesrepech INNER JOIN matieres ON (notesrepech.matiere_id = matieres.id) WHERE notesrepech.etudiant_id = ? AND notesrepech.annee_scolaire = ? AND notesrepech.etudiant_niveau = ?' + ) + let noteRepech = await queryNoteRepech.all(id, anneescolaire, niveau) + + const semestreQuery = database.prepare( + 'SELECT * FROM semestres INNER JOIN matiere_semestre ON(semestres.id = matiere_semestre.semestre_id)' + ) + let semestre = await semestreQuery.all() + + return { noteNormal, noteRepech, semestre } +} + +module.exports = { + insertNote, + getNote, + showMoyen, + getNoteOnline, + verifyEtudiantIfHeHasNotes, + updateNote, + blockShowMoyene, + getMatiereAndNote, + getNotesWithRepechToDisplay +} diff --git a/database/Models/Parcours.js b/database/Models/Parcours.js new file mode 100644 index 0000000..32337e6 --- /dev/null +++ b/database/Models/Parcours.js @@ -0,0 +1,179 @@ +const { database } = require('../database') +const { matiereSystemReverse } = require('../function/System') +const dayjs = require('dayjs') + +async function insertParcour(nom, uniter, mention_id) { + const query = database.prepare('INSERT INTO parcours (nom, uniter, mention_id) VALUES (?, ?, ?)') + + try { + let response = query.run(nom, uniter, mention_id == null ? 0 : mention_id) + + return response + } catch (error) { + return error + } +} + +async function getParcourMatiere(id) { + const parcourMatiereQuery = database.prepare('SELECT * FROM parcoursmatiere WHERE matiere_id = ?') + + try { + let response + return (response = await parcourMatiereQuery.all(id)) + } catch (error) { + return error + } +} + +async function getParcours() { + const query = database.prepare('SELECT * FROM parcours ORDER BY id DESC') + + try { + let response = query.all() + + return response + } catch (error) { + return error + } +} + +async function getSingleParcours(id) { + const query = database.prepare('SELECT * FROM parcours WHERE id = ?') + + try { + let response = await query.get(id) + + return response + } catch (error) { + return error + } +} + +async function deletes(id) { + const query = database.prepare('DELETE FROM parcours WHERE id = ?') + + try { + let response = await query.run(id) + + return response + } catch (error) { + return error + } +} + +async function updateparcour(id, nom, uniter, mention_id) { + const query = database.prepare( + 'UPDATE parcours SET nom = ?, uniter = ?, mention_id = ? WHERE id = ?' + ) + + try { + let response = await query.run(nom, uniter, mention_id, id) + + return response + } catch (error) { + return error + } +} + +async function parcourMatiere(matiere_id, parcour_id) { + const query = database.prepare( + 'INSERT INTO parcoursmatiere (matiere_id, parcour_id) VALUES (?, ?)' + ) + + database.prepare('DELETE FROM parcoursmatiere WHERE matiere_id = ?').run(matiere_id) + + try { + let response + database.transaction(() => { + for (let index = 0; index < parcour_id.length; index++) { + response = query.run(matiere_id, parcour_id[index]) + } + })() + + return response + } catch (error) { + return error + } +} + +async function extractFiche(matiere_id) { + const query = database.prepare( + 'SELECT matiere_semestre.semestre_id, matiere_semestre.mention_id, semestres.* FROM matiere_semestre INNER JOIN semestres ON (matiere_semestre.semestre_id = semestres.id) WHERE matiere_semestre.matiere_id = ?' + ) + const allStudentQuery = database.prepare( + 'SELECT * FROM etudiants WHERE niveau LIKE ? AND annee_scolaire LIKE ?' + ) + const parcourMatiereQuery = database.prepare( + 'SELECT * FROM parcoursmatiere INNER JOIN parcours ON (parcoursmatiere.parcour_id = parcours.id) WHERE parcoursmatiere.matiere_id = ?' + ) + + try { + let matiereSemestre = query.all(matiere_id) + let response = [] + let allSTudent = [] + let allMention_id = [] + let realSponse = [] + let now = dayjs().format('YYYY') + + realSponse = await database.transaction(async () => { + let parcours = parcourMatiereQuery.all(Number(matiere_id)) + for (let index = 0; index < matiereSemestre.length; index++) { + response.push(await matiereSystemReverse(matiereSemestre[index].nom)) + allMention_id.push(matiereSemestre[index].mention_id) + } + + let newResponse = [] + for (let index = 0; index < response.length; index++) { + if (response[index] != response[index + 1]) { + newResponse.push(response[index]) + } + } + + for (let index = 0; index < newResponse.length; index++) { + allSTudent = allStudentQuery.all(`%${newResponse[index]}%`, `%${now}%`) + } + + let studentFiltredMention = [] + for (let index = 0; index < allSTudent.length; index++) { + if (allMention_id.includes(allSTudent[index].mention_id)) { + studentFiltredMention.push(allSTudent[index]) + } + } + let allData = [] + + for (let j = 0; j < parcours.length; j++) { + for (let index = 0; index < studentFiltredMention.length; index++) { + if (parcours[j].parcour_id == 1) { + if ( + studentFiltredMention[index].parcours == null || + studentFiltredMention[index].parcours == parcours[j].nom + ) { + allData.push(studentFiltredMention[index]) + } + } else { + if (studentFiltredMention[index].parcours == parcours[j].nom) { + allData.push(studentFiltredMention[index]) + } + } + } + } + + return allData + })() + + return realSponse + } catch (error) { + return error + } +} + +module.exports = { + insertParcour, + getParcours, + getSingleParcours, + deletes, + updateparcour, + parcourMatiere, + extractFiche, + getParcourMatiere +} diff --git a/database/Models/Status.js b/database/Models/Status.js new file mode 100644 index 0000000..665db5a --- /dev/null +++ b/database/Models/Status.js @@ -0,0 +1,21 @@ +const { database } = require('../database') + +/** + * function to return all status + * @returns promise + */ +async function getStatus() { + const query = database.prepare('SELECT * FROM status') + + try { + let response = await query.all() + + return response + } catch (error) { + return error + } +} + +module.exports = { + getStatus +} diff --git a/database/Models/Users.js b/database/Models/Users.js new file mode 100644 index 0000000..269cbea --- /dev/null +++ b/database/Models/Users.js @@ -0,0 +1,149 @@ +const { database } = require('../database') +const bcrypt = require('bcryptjs') + +// Function to insert a user into the database +async function insertUser(username, email, password, roles) { + const saltRounds = 10 + + try { + // Await the bcrypt hashing to complete before proceeding + const hashedPassword = await bcrypt.hash(password, saltRounds) + + // Prepare and run the insert query using the hashed password + const insertUserQuery = database.prepare( + 'INSERT INTO users (username, email, password, roles) VALUES (?, ?, ?, ?)' + ) + const insertedUser = await insertUserQuery.run(username, email, hashedPassword, roles) + + return insertedUser + } catch (err) { + return err + } +} + +// Function to fetch all users from the database +async function getAllUsers() { + const getUsersQuery = database.prepare('SELECT * FROM users') + let response = await getUsersQuery.all() + + return response +} + +// Function to login a user +async function loginUser(username, password) { + // Prepare the query to get the user by username + const loginUserQuery = database.prepare('SELECT * FROM users WHERE LOWER(username) = ?') + + try { + // Execute the query and get the user from the database + const user = await loginUserQuery.get(username.toLowerCase()) + + if (user) { + // Use bcrypt to compare the provided password with the stored hashed password + const isPasswordValid = await bcrypt.compare(password, user.password) + + if (isPasswordValid) { + // If password matches, return the user + return user + } else { + // If password does not match + console.log('Invalid password') + } + } else { + // If no user is found with the provided username + console.log('User not found') + } + } catch (err) { + console.error('Error during login:', err) + } +} + +/** + * function to use in forgit password + * + * @param {*} email + * @param {*} password + * @param {*} passwordConfirmation + * @returns + */ +async function forgotPassword(email, password, passwordConfirmation) { + const saltRounds = 10 + const forgotPasswordQuery = database.prepare('SELECT * FROM users WHERE email = ?') + + if (password == passwordConfirmation) { + const user = await forgotPasswordQuery.get(email) + + if (user) { + const updateQuery = database.prepare('UPDATE users SET password = ? WHERE email = ?') + const hashedPassword = await bcrypt.hash(password, saltRounds) + + try { + await updateQuery.run(hashedPassword, email) + + return { message: 'Mot de passe modifier avec succes', status: 200 } + } catch (error) { + console.error('Error updating password:', error) + } + } else { + return { message: 'Email non trouver', status: 404 } + } + } else { + return { message: 'Mot de passe ne correspond pas', status: 401 } + } +} + +/** + * function to use when updatign the users + * + * @param {*} username + * @param {*} email + * @param {*} password + * @param {*} id + * @returns promise + */ +async function updateUser(username, email, password, id) { + const saltRounds = 10 + + try { + let query + let response + + if (password === '') { + // Update without changing the password + if (username === '' && email !== '') { + query = database.prepare('UPDATE users SET email = ? WHERE id = ?') + response = await query.run(email, id) + } else if (email === '' && username !== '') { + query = database.prepare('UPDATE users SET username = ? WHERE id = ?') + response = await query.run(username, id) + } else if (username !== '' && email !== '') { + query = database.prepare('UPDATE users SET username = ?, email = ? WHERE id = ?') + response = await query.run(username, email, id) + } + } else { + // Update with a new hashed password + const hashedPassword = await bcrypt.hash(password, saltRounds) + query = database.prepare( + 'UPDATE users SET username = ?, email = ?, password = ? WHERE id = ?' + ) + response = await query.run(username, email, hashedPassword, id) + } + + // Fetch the updated user after the update + const getUserQuery = database.prepare('SELECT * FROM users WHERE id = ?') + const updatedUser = await getUserQuery.get(id) + + return updatedUser // Return the updated user + } catch (error) { + console.error('Error updating user:', error) + throw error // Throw error to handle it in calling function if needed + } +} + +module.exports = { + getAllUsers, + insertUser, + loginUser, + forgotPassword, + updateUser +} diff --git a/database/TableData.sql b/database/TableData.sql new file mode 100644 index 0000000..5908dc7 --- /dev/null +++ b/database/TableData.sql @@ -0,0 +1,142 @@ +CREATE TABLE IF NOT EXISTS users ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + username VARCHAR(200) NOT NULL, + email VARCHAR(250) NOT NULL UNIQUE, + password TEXT NOT NULL, + roles VARCHAR(250) NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +CREATE TABLE IF NOT EXISTS status ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + nom VARCHAR(200) NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +CREATE TABLE IF NOT EXISTS mentions ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + nom VARCHAR(250) NOT NULL, + uniter VARCHAR(50) NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +CREATE TABLE IF NOT EXISTS niveaus ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + nom VARCHAR(50) NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +CREATE TABLE IF NOT EXISTS etudiants ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + nom VARCHAR(250) NOT NULL, + prenom VARCHAR(250) NOT NULL, + photos TEXT NOT NULL, + date_de_naissances DATE NOT NULL, + niveau VARCHAR(250) NOT NULL, + annee_scolaire VARCHAR(20) NOT NULL, + status INTEGER NOT NULL, + mention_id INTEGER NOT NULL, + num_inscription TEXT NOT NULL UNIQUE, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (status) REFERENCES status(id), + FOREIGN KEY (mention_id) REFERENCES mentions(id) +); + +CREATE TABLE IF NOT EXISTS matieres ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + nom VARCHAR(250) UNIQUE NOT NULL, + unite_enseignement VARCHAR(250) NOT NULL, + credit INTEGER NOT NULL, + heure INTEGER NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +CREATE TABLE IF NOT EXISTS semestres ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + nom VARCHAR(30) UNIQUE NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +CREATE TABLE IF NOT EXISTS matiere_mention ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + matiere_id INTEGER NOT NULL, + mention_id INTEGER NOT NULL, + FOREIGN KEY (matiere_id) REFERENCES matieres(id), + FOREIGN KEY (mention_id) REFERENCES mentions(id) +); + +CREATE TABLE IF NOT EXISTS matiere_semestre ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + matiere_id INTEGER NOT NULL, + semestre_id INTEGER NOT NULL, + mention_id INTEGER NOT NULL, + FOREIGN KEY (matiere_id) REFERENCES matieres(id), + FOREIGN KEY (semestre_id) REFERENCES semestres(id), + FOREIGN KEY (mention_id) REFERENCES mentions(id) +); + +CREATE TABLE IF NOT EXISTS notes ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + etudiant_id INTEGER NOT NULL, + matiere_id INTEGER NOT NULL, + etudiant_niveau VARCHAR(50) NOT NULL, + mention_id INTEGER NOT NULL, + note FLOAT DEFAULT NULL, + annee_scolaire VARCHAR(50) NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (etudiant_id) REFERENCES etudiants(id), + FOREIGN KEY (matiere_id) REFERENCES matieres(id), + FOREIGN KEY (mention_id) REFERENCES mentions(id) +); + +CREATE TABLE IF NOT EXISTS notesrepech ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + etudiant_id INTEGER NOT NULL, + matiere_id INTEGER NOT NULL, + etudiant_niveau VARCHAR(50) NOT NULL, + mention_id INTEGER NOT NULL, + note FLOAT DEFAULT NULL, + annee_scolaire VARCHAR(50) NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (etudiant_id) REFERENCES etudiants(id), + FOREIGN KEY (matiere_id) REFERENCES matieres(id), + FOREIGN KEY (mention_id) REFERENCES mentions(id) +); + +CREATE TABLE IF NOT EXISTS notesystems ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + admis FLOAT NOT NULL DEFAULT 10, + redouble FLOAT NOT NULL DEFAULT 9.99, + renvoyer FLOAT NOT NULL DEFAULT 7.99, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +CREATE TABLE IF NOT EXISTS anneescolaire ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + code VARCHAR(30) NOT NULL, + debut DATE NOT NULL, + fin DATE NOT NULL, + is_current INTEGER DEFAULT 0, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +CREATE TABLE IF NOT EXISTS traitmentsystem ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + code VARCHAR(30) NOT NULL, + debut DATE NOT NULL, + fin DATE NOT NULL, + is_finished INTEGER DEFAULT 0, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); diff --git a/database/api/CheckUpdateNote.js b/database/api/CheckUpdateNote.js new file mode 100644 index 0000000..db9809b --- /dev/null +++ b/database/api/CheckUpdateNote.js @@ -0,0 +1,151 @@ +/** + * URL fro the server web, you can modify it like your Domain name + */ +const { URL } = require('./Config') + +const { getNoteOnline, insertNote } = require('../Models/Notes') + +async function getAllNotesFeched() { + const notes = await getNoteOnline() + + return notes +} + +/** + * send data to the database in the web + */ +async function verifyNoteToWeb() { + /** + * class AJAX, don't touch it + */ + const XHR = new XMLHttpRequest() + + let notes = await getAllNotesFeched() + + XHR.onreadystatechange = () => { + if (XHR.readyState === 4) { + if (XHR.status === 200) { + // nothing here because the statement is in the server web + } else { + console.log('impossible de contacter le server pour la syncronisation') + } + } + } + + const data = new FormData() + + data.append('Verification', JSON.stringify(notes)) + + XHR.open('POST', `${URL}/verifywebNote`, true) + XHR.setRequestHeader('x-requested-with', 'xmlhttprequest') + XHR.send(data) +} + +async function verifyNoteToLocal() { + /** + * class AJAX, don't touch it + */ + const XHR = new XMLHttpRequest() + let notes = await getAllNotesFeched() + + XHR.onreadystatechange = () => { + if (XHR.readyState === 4) { + if (XHR.status === 200) { + const noteOnline = JSON.parse(XHR.responseText) + + let data_note = [] + // let bool = false; + + if (notes.length === 0) { + // Case when local data is empty, insert all online data + for (let index = 0; index < noteOnline.length; index++) { + console.log(noteOnline[index]['etudiant_id']) + insertNote( + noteOnline[index].etudiant_id, + noteOnline[index].etudiant_niveau, + noteOnline[index].Algebre, + noteOnline[index].Analyse, + noteOnline[index].Mecanique_Generale_I, + noteOnline[index].Resistance_Materiaux, + noteOnline[index].Electricite, + noteOnline[index].Chimie_Generale_1, + noteOnline[index].Algorithmique, + noteOnline[index].Thermodynamique_Physique, + noteOnline[index].Mecanique_Fluide, + noteOnline[index].Optique_Geometrique, + noteOnline[index].Calcul_Numerique, + noteOnline[index].Calcul_Vectoriel_Integral, + noteOnline[index].Francais, + noteOnline[index].Anglais, + noteOnline[index].Dessin_Technique, + noteOnline[index].Programmation + ) + } + } else { + // Case when online data has more entries than local data + // Loop through the notes array + for (let index = 0; index < notes.length; index++) { + // Push an object with the desired properties + data_note.push({ + etudiant_id: notes[index].etudiant_id, + etudiant_niveau: notes[index].etudiant_niveau + }) + } + for (let index = 0; index < noteOnline.length; index++) { + let bool = true + + for (let jindex = 0; jindex < data_note.length; jindex++) { + if ( + noteOnline[index].etudiant_id === data_note[jindex].etudiant_id && + noteOnline[index].etudiant_niveau === data_note[jindex].etudiant_niveau + ) { + bool = false + break + } + } + + if (bool) { + insertNote( + noteOnline[index].etudiant_id, + noteOnline[index].etudiant_niveau, + noteOnline[index].Algebre, + noteOnline[index].Analyse, + noteOnline[index].Mecanique_General_I, + noteOnline[index].Resistance_Materiaux, + noteOnline[index].Electricite, + noteOnline[index].Chimie_Generale_1, + noteOnline[index].Algorithmique, + noteOnline[index].Thermodynamique_Physique, + noteOnline[index].Mecanique_Fluide, + noteOnline[index].Optique_Geometrique, + noteOnline[index].Calcul_Numerique, + noteOnline[index].Calcul_Vectoriel_Integral, + noteOnline[index].Francais, + noteOnline[index].Anglais, + noteOnline[index].Dessin_Technique, + noteOnline[index].Programmation + ) + } + } + } + } + } + } + + XHR.open('GET', `${URL}/verifylocalNote`, true) + XHR.setRequestHeader('x-requested-with', 'xmlhttprequest') + XHR.send() +} + +async function synchronizeDataNotes() { + try { + await verifyNoteToWeb() + await verifyNoteToLocal() + } catch (error) { + console.error('Error during synchronization:', error) + } +} + +module.exports = { + synchronizeDataNotes +} diff --git a/database/api/Config.js b/database/api/Config.js new file mode 100644 index 0000000..1fea98d --- /dev/null +++ b/database/api/Config.js @@ -0,0 +1,5 @@ +const URL = 'https://api.polytechnique.c4m.mg' + +module.exports = { + URL +} diff --git a/database/api/Get.js b/database/api/Get.js new file mode 100644 index 0000000..a64ec64 --- /dev/null +++ b/database/api/Get.js @@ -0,0 +1,75 @@ +const { insertEtudiant } = require('../Models/Etudiants') +const { database } = require('../database') +const { URL } = require('./Config') +const dayjs = require('dayjs') + +const getEtudiants = async () => { + const XHR = new XMLHttpRequest() + const allEtudiants = await database.prepare('SELECT * FROM etudiants').all() + + if (XHR.readyState === 4) { + if (XHR.status === 200) { + const etudiantFromWeb = JSON.parse(XHR.responseText) + + let numInscArray = [] + for (let index = 0; index < allEtudiants.length; index++) { + numInscArray.push(allEtudiants[index].num_inscription) + } + + database.transaction(() => { + for (let index = 0; index < etudiantFromWeb.length; index++) { + if (numInscArray.includes(etudiantFromWeb[index]['num_inscription']) === false) { + insertEtudiant( + etudiantFromWeb[index]['nom'], + etudiantFromWeb[index]['prenom'], + etudiantFromWeb[index]['photos'], + etudiantFromWeb[index]['date_de_naissances'], + etudiantFromWeb[index]['niveau'], + etudiantFromWeb[index]['annee_scolaire'], + etudiantFromWeb[index]['status'], + etudiantFromWeb[index]['num_inscription'], + etudiantFromWeb[index]['mention_id'] + ) + } else { + const getTheEtudiant = database + .prepare('SELECT * FROM etudiants WHERE id = ?') + .get(etudiantFromWeb[index]['id']) + + if (getTheEtudiant.updated_at < etudiantFromWeb[index]['updated_at']) { + database + .prepare( + 'UPDATE etudiants SET nom = ?, prenom = ?, photos = ?, date_de_naissances = ?, niveau = ?, annee_scolaire = ?, status = ?, mention_id = ?, num_inscription = ? WHERE id = ?' + ) + .run( + etudiantFromWeb[index]['nom'], + etudiantFromWeb[index]['prenom'], + etudiantFromWeb[index]['photos'], + etudiantFromWeb[index]['date_de_naissances'], + etudiantFromWeb[index]['niveau'], + etudiantFromWeb[index]['annee_scolaire'], + etudiantFromWeb[index]['status'], + etudiantFromWeb[index]['num_inscription'], + etudiantFromWeb[index]['mention_id'], + etudiantFromWeb[index]['id'] + ) + } + } + } + })() + } else { + console.log('impossible de contacter le server') + } + } + + XHR.open('GET', `${URL}/getEtudiants`, true) + XHR.setRequestHeader('x-requested-with', 'xmlhttprequest') + XHR.send() +} + +async function getAll() { + await getEtudiants() +} + +module.exports = { + getAll +} diff --git a/database/api/Send.js b/database/api/Send.js new file mode 100644 index 0000000..2df4fe8 --- /dev/null +++ b/database/api/Send.js @@ -0,0 +1,422 @@ +const { database } = require('../database') +const { URL } = require('./Config') + +/** + * send data etudiants in server + */ +const sendEtudiants = async () => { + const XHR = new XMLHttpRequest() + const queryEtudiants = await database.prepare('SELECT * FROM etudiants').all() + + XHR.onreadystatechange = () => { + if (XHR.readyState === 4) { + if (XHR.status === 200) { + let response = XHR.responseText + + console.log(response) + } else { + console.log('impossible de contacter le serveur') + } + } + } + + const data = new FormData() + + data.append('Verification', JSON.stringify(queryEtudiants)) + + XHR.open('POST', `${URL}/etudiants`, true) + XHR.setRequestHeader('x-requested-with', 'xmlhttprequest') + XHR.send(data) +} + +/** + * send data matieres to server + */ +const sendMatieres = async () => { + const XHR = new XMLHttpRequest() + const queryMatieres = await database.prepare('SELECT * FROM matieres').all() + + XHR.onreadystatechange = () => { + if (XHR.readyState === 4) { + if (XHR.status === 200) { + let response = XHR.responseText + + console.log(response) + } else { + console.log('impossible de contacter le serveur') + } + } + } + + const data = new FormData() + + data.append('Verification', JSON.stringify(queryMatieres)) + + XHR.open('POST', `${URL}/matieres`, true) + XHR.setRequestHeader('x-requested-with', 'xmlhttprequest') + XHR.send(data) +} + +/** + * send data users to server + */ +const sendUsers = async () => { + const XHR = new XMLHttpRequest() + const queryUsers = database.prepare('SELECT * FROM users').all() + + XHR.onreadystatechange = () => { + if (XHR.readyState === 4) { + if (XHR.status === 200) { + let response = XHR.responseText + + console.log(response) + } else { + console.log('impossible de contacter le serveur') + } + } + } + + const data = new FormData() + + data.append('Verification', JSON.stringify(queryUsers)) + + XHR.open('POST', `${URL}/users`, true) + XHR.setRequestHeader('x-requested-with', 'xmlhttprequest') + XHR.send(data) +} + +/** + * send status to server + */ +const sendStatus = async () => { + const XHR = new XMLHttpRequest() + const queryStatus = database.prepare('SELECT * FROM status').all() + + XHR.onreadystatechange = () => { + if (XHR.readyState === 4) { + if (XHR.status === 200) { + let response = XHR.responseText + + console.log(response) + } else { + console.log('impossible de contacter le serveur') + } + } + } + + const data = new FormData() + + data.append('Verification', JSON.stringify(queryStatus)) + + XHR.open('POST', `${URL}/status`, true) + XHR.setRequestHeader('x-requested-with', 'xmlhttprequest') + XHR.send(data) +} + +/** + * send mention to server + */ +const sendMentions = async () => { + const XHR = new XMLHttpRequest() + const queryMentions = database.prepare('SELECT * FROM mentions').all() + + XHR.onreadystatechange = () => { + if (XHR.readyState === 4) { + if (XHR.status === 200) { + let response = XHR.responseText + + console.log(response) + } else { + console.log('impossible de contacter le serveur') + } + } + } + + const data = new FormData() + + data.append('Verification', JSON.stringify(queryMentions)) + + XHR.open('POST', `${URL}/mentions`, true) + XHR.setRequestHeader('x-requested-with', 'xmlhttprequest') + XHR.send(data) +} + +/** + * send niveaus to server + */ +const sendNiveaus = async () => { + const XHR = new XMLHttpRequest() + const queryNiveaus = database.prepare('SELECT * FROM niveaus').all() + + XHR.onreadystatechange = () => { + if (XHR.readyState === 4) { + if (XHR.status === 200) { + let response = XHR.responseText + + console.log(response) + } else { + console.log('impossible de contacter le serveur') + } + } + } + + const data = new FormData() + + data.append('Verification', JSON.stringify(queryNiveaus)) + + XHR.open('POST', `${URL}/niveaus`, true) + XHR.setRequestHeader('x-requested-with', 'xmlhttprequest') + XHR.send(data) +} + +/** + * send semestre to server + */ +const sendSemestres = async () => { + const XHR = new XMLHttpRequest() + const querySemestres = database.prepare('SELECT * FROM semestres').all() + + XHR.onreadystatechange = () => { + if (XHR.readyState === 4) { + if (XHR.status === 200) { + let response = XHR.responseText + + console.log(response) + } else { + console.log('impossible de contacter le serveur') + } + } + } + + const data = new FormData() + + data.append('Verification', JSON.stringify(querySemestres)) + + XHR.open('POST', `${URL}/semestres`, true) + XHR.setRequestHeader('x-requested-with', 'xmlhttprequest') + XHR.send(data) +} + +/** + * send matiereMention to server + */ +const sendMatiereMention = async () => { + const XHR = new XMLHttpRequest() + const queryMatieremantion = database.prepare('SELECT * FROM matiere_mention').all() + + XHR.onreadystatechange = () => { + if (XHR.readyState === 4) { + if (XHR.status === 200) { + let response = XHR.responseText + + console.log(response) + } else { + console.log('impossible de contacter le serveur') + } + } + } + + const data = new FormData() + + data.append('Verification', JSON.stringify(queryMatieremantion)) + + XHR.open('POST', `${URL}/matiere_mention`, true) + XHR.setRequestHeader('x-requested-with', 'xmlhttprequest') + XHR.send(data) +} + +/** + * send matiereSemestre to server + */ +const sendMatiereSemestre = async () => { + const XHR = new XMLHttpRequest() + const queryMatieresemestre = database.prepare('SELECT * FROM matiere_semestre').all() + + XHR.onreadystatechange = () => { + if (XHR.readyState === 4) { + if (XHR.status === 200) { + let response = XHR.responseText + + console.log(response) + } else { + console.log('impossible de contacter le serveur') + } + } + } + + const data = new FormData() + + data.append('Verification', JSON.stringify(queryMatieresemestre)) + + XHR.open('POST', `${URL}/matiere_semestre`, true) + XHR.setRequestHeader('x-requested-with', 'xmlhttprequest') + XHR.send(data) +} + +/** + * send notes to server + */ +const sendNotes = async () => { + const XHR = new XMLHttpRequest() + const queryNotes = database.prepare('SELECT * FROM notes').all() + + XHR.onreadystatechange = () => { + if (XHR.readyState === 4) { + if (XHR.status === 200) { + let response = XHR.responseText + + console.log(response) + } else { + console.log('impossible de contacter le serveur') + } + } + } + + const data = new FormData() + + data.append('Verification', JSON.stringify(queryNotes)) + + XHR.open('POST', `${URL}/notes`, true) + XHR.setRequestHeader('x-requested-with', 'xmlhttprequest') + XHR.send(data) +} + +/** + * send note repechage to server + */ +const sendNotesRepech = async () => { + const XHR = new XMLHttpRequest() + const queryNotesRepech = database.prepare('SELECT * FROM notesrepech').all() + + XHR.onreadystatechange = () => { + if (XHR.readyState === 4) { + if (XHR.status === 200) { + let response = XHR.responseText + + console.log(response) + } else { + console.log('impossible de contacter le serveur') + } + } + } + + const data = new FormData() + + data.append('Verification', JSON.stringify(queryNotesRepech)) + + XHR.open('POST', `${URL}/notesrepech`, true) + XHR.setRequestHeader('x-requested-with', 'xmlhttprequest') + XHR.send(data) +} + +/** + * send noteSystem to server + */ +const sendNoteSystem = async () => { + const XHR = new XMLHttpRequest() + const queryNoteSystem = database.prepare('SELECT * FROM notesystems').all() + + XHR.onreadystatechange = () => { + if (XHR.readyState === 4) { + if (XHR.status === 200) { + let response = XHR.responseText + + console.log(response) + } else { + console.log('impossible de contacter le serveur') + } + } + } + + const data = new FormData() + + data.append('Verification', JSON.stringify(queryNoteSystem)) + + XHR.open('POST', `${URL}/notesystems`, true) + XHR.setRequestHeader('x-requested-with', 'xmlhttprequest') + XHR.send(data) +} + +/** + * send annee scolaire to server + */ +const sendAnnee = async () => { + const XHR = new XMLHttpRequest() + const queryAnnee = database.prepare('SELECT * FROM anneescolaire').all() + + XHR.onreadystatechange = () => { + if (XHR.readyState === 4) { + if (XHR.status === 200) { + let response = XHR.responseText + + console.log(response) + } else { + console.log('impossible de contacter le serveur') + } + } + } + + const data = new FormData() + + data.append('Verification', JSON.stringify(queryAnnee)) + + XHR.open('POST', `${URL}/anneescolaire`, true) + XHR.setRequestHeader('x-requested-with', 'xmlhttprequest') + XHR.send(data) +} + +/** + * send traitement system to server + */ +const sendTraitmentSystem = async () => { + const XHR = new XMLHttpRequest() + const queryAnnee = database.prepare('SELECT * FROM traitmentsystem').all() + + XHR.onreadystatechange = () => { + if (XHR.readyState === 4) { + if (XHR.status === 200) { + let response = XHR.responseText + + console.log(response) + } else { + console.log('impossible de contacter le serveur') + } + } + } + + const data = new FormData() + + data.append('Verification', JSON.stringify(queryAnnee)) + + XHR.open('POST', `${URL}/traitmentsystem`, true) + XHR.setRequestHeader('x-requested-with', 'xmlhttprequest') + XHR.send(data) +} + +/** + * send all request to server + */ +async function sendAll() { + try { + await sendEtudiants() + await sendMatieres() + await sendUsers() + await sendStatus() + await sendMentions() + await sendNiveaus() + await sendSemestres() + await sendMatiereMention() + await sendMatiereSemestre() + await sendNotes() + await sendNotesRepech() + await sendNoteSystem() + await sendAnnee() + await sendTraitmentSystem() + } catch (error) { + return error + } +} + +module.exports = { + sendAll +} diff --git a/database/api/SyncronisationDataEtudiants.js b/database/api/SyncronisationDataEtudiants.js new file mode 100644 index 0000000..198b444 --- /dev/null +++ b/database/api/SyncronisationDataEtudiants.js @@ -0,0 +1,107 @@ +// here the data from the local and web server become one for etudiants table + +/** + * our get and insert etudiants function, don't touch it + */ +const { getAllEtudiants, insertEtudiant } = require('../Models/Etudiants') + +/** + * URL fro the server web, you can modify it like your Domain name + */ +const { URL } = require('./Config') + +/** + * function to get the all etudiants + * + * @returns promise + */ +async function getAllEtudiantsFetch() { + const etudiants = await getAllEtudiants() + + return etudiants +} + +async function verifyEtudiantTableWeb() { + /** + * class AJAX, don't touch it + */ + const XHR = new XMLHttpRequest() + + let etudiants = await getAllEtudiantsFetch() + + XHR.onreadystatechange = () => { + if (XHR.readyState === 4) { + if (XHR.status === 200) { + // nothing here because the statement is in the server web + } else { + console.log('impossible de contacter le server pour la syncronisation') + } + } + } + + const data = new FormData() + + data.append('Verification', JSON.stringify(etudiants)) + + XHR.open('POST', `${URL}/verifywebEtudiants`, true) + XHR.setRequestHeader('x-requested-with', 'xmlhttprequest') + XHR.send(data) +} + +async function verifyTableLocalEtudiants() { + /** + * class AJAX, don't touch it + */ + const XHR = new XMLHttpRequest() + let etudiants = await getAllEtudiantsFetch() + + XHR.onreadystatechange = () => { + if (XHR.readyState === 4) { + if (XHR.status === 200) { + const etudiantFromWeb = JSON.parse(XHR.responseText) + + // set all email from base local in Array numInscArray + let numInscArray = [] + for (let index = 0; index < etudiants.length; index++) { + numInscArray.push(etudiants[index]['num_inscription']) + } + + // search if the email from user from server is in local or not + // if not, then insert it + for (let index = 0; index < etudiantFromWeb.length; index++) { + if (numInscArray.includes(etudiantFromWeb[index]['num_inscription']) === false) { + insertEtudiant( + etudiantFromWeb[index]['nom'], + etudiantFromWeb[index]['prenom'], + etudiantFromWeb[index]['photos'], + etudiantFromWeb[index]['date_de_naissances'], + etudiantFromWeb[index]['niveau'], + etudiantFromWeb[index]['annee_scolaire'], + etudiantFromWeb[index]['num_inscription'] + ) + } + } + } else { + console.log('impossible de contacter le server pour la syncronisation') + } + } + } + + XHR.open('GET', `${URL}/verifylocalEtudiants`, true) + XHR.setRequestHeader('x-requested-with', 'xmlhttprequest') + XHR.send() +} + +// Call both functions sequentially or in parallel as needed +async function synchronizeDataEtudiants() { + try { + await verifyEtudiantTableWeb() + await verifyTableLocalEtudiants() + } catch (error) { + console.error('Error during synchronization:', error) + } +} + +module.exports = { + synchronizeDataEtudiants +} diff --git a/database/api/SyncronisationDataUsers.js b/database/api/SyncronisationDataUsers.js new file mode 100644 index 0000000..131bfc1 --- /dev/null +++ b/database/api/SyncronisationDataUsers.js @@ -0,0 +1,108 @@ +// here the data from the local and web server become one for users table + +/** + * our get and insert users function, don't touch it + */ +const { getAllUsers, insertUser } = require('../Models/Users') + +/** + * URL fro the server web, you can modify it like your Domain name + */ +const { URL } = require('./Config') + +/** + * function to get the all users + * + * @returns promise + */ +async function getAllUsersFetch() { + const users = await getAllUsers() + + return users +} + +/** + * function to syncronise data from local to web server, + * it will be an async function to get the result of promise in getAllUsersFetch() function + */ +async function verifyUserTableWeb() { + /** + * class AJAX, don't touch it + */ + const XHR = new XMLHttpRequest() + let users = await getAllUsersFetch() + + XHR.onreadystatechange = () => { + if (XHR.readyState === 4) { + if (XHR.status === 200) { + // nothing here because the statement is in the server web + } else { + console.log('impossible de contacter le server pour la syncronisation') + } + } + } + + const data = new FormData() + + data.append('Verification', JSON.stringify(users)) + + XHR.open('POST', `${URL}/verifyweb`, true) + XHR.setRequestHeader('x-requested-with', 'xmlhttprequest') + XHR.send(data) +} + +async function verifyUserTableLocal() { + /** + * class AJAX, don't touch it + */ + const XHR = new XMLHttpRequest() + let users = await getAllUsersFetch() + + XHR.onreadystatechange = () => { + if (XHR.readyState === 4) { + if (XHR.status === 200) { + const usersFromWeb = JSON.parse(XHR.responseText) + + // set all email from base local in Array emailArray + let emailArray = [] + for (let index = 0; index < users.length; index++) { + emailArray.push(users[index]['email']) + } + + // search if the email from user from server is in local or not + // if not, then insert it + for (let index = 0; index < usersFromWeb.length; index++) { + if (emailArray.includes(usersFromWeb[index]['email']) === false) { + insertUser( + usersFromWeb[index]['username'], + usersFromWeb[index]['email'], + usersFromWeb[index]['password'], + usersFromWeb[index]['photos'], + usersFromWeb[index]['roles'] + ) + } + } + } else { + console.log('impossible de contacter le server pour la syncronisation') + } + } + } + + XHR.open('GET', `${URL}/verifylocal`, true) + XHR.setRequestHeader('x-requested-with', 'xmlhttprequest') + XHR.send() +} + +// Call both functions sequentially or in parallel as needed +async function synchronizeData() { + try { + setTimeout(await verifyUserTableWeb(), 2000) + setTimeout(await verifyUserTableLocal(), 1000) + } catch (error) { + console.error('Error during synchronization:', error) + } +} + +module.exports = { + synchronizeData +} diff --git a/database/api/Update.js b/database/api/Update.js new file mode 100644 index 0000000..2e25009 --- /dev/null +++ b/database/api/Update.js @@ -0,0 +1,3 @@ +const { app, dialog } = require('electron') + +const checkUpdate = () => {} diff --git a/database/database.js b/database/database.js new file mode 100644 index 0000000..348df9c --- /dev/null +++ b/database/database.js @@ -0,0 +1,441 @@ +const sqlite = require('better-sqlite3') +const bcrypt = require('bcryptjs') + + +// Construct the database path using the detected IP +let dbPath = `./base/data.db`; + +// Connect to SQLite database with the initial path +let database = new sqlite(dbPath); + + + + + +// Create the users table if it doesn't exist +const createUserTableQuery = ` + CREATE TABLE IF NOT EXISTS users ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + username VARCHAR(200) NOT NULL, + email VARCHAR(250) NOT NULL UNIQUE, + password TEXT NOT NULL, + roles VARCHAR(250) NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ); +` +database.prepare(createUserTableQuery).run() + +// Insert a default admin user if not exists +const insertDefaultUserQuery = ` + INSERT INTO users (username, email, password, roles) + SELECT 'admin', 'admin@example.com', ?, 'admin' + WHERE NOT EXISTS (SELECT 1 FROM users WHERE username = 'admin'); +` + +// Hash the password '1234' before storing +const hashedPassword = bcrypt.hashSync('123456789', 10) +database.prepare(insertDefaultUserQuery).run(hashedPassword) + +// create table for note status +const createStatusTableQuery = ` + CREATE TABLE IF NOT EXISTS status ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + nom VARCHAR(200) NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ); +` +database.prepare(createStatusTableQuery).run() + +// create table for mention +const createMentionTableQuery = ` + CREATE TABLE IF NOT EXISTS mentions ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + nom VARCHAR(250) NOT NULL, + uniter VARCHAR(50) NOT NULL, -- Abréviation du nom + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ); +` +database.prepare(createMentionTableQuery).run() + +// Create the niveau table if it doesn't exist +const createNiveauTableQuery = ` + CREATE TABLE IF NOT EXISTS niveaus ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + nom VARCHAR(50) NOT NULL, -- Exemple: L1, L2, L3, etc. + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ); +` +database.prepare(createNiveauTableQuery).run() + +// Create the etudiants table if it doesn't exist +const createEtudiantsTableQuery = ` + CREATE TABLE IF NOT EXISTS etudiants ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + nom VARCHAR(250) DEFAULT NULL, + prenom VARCHAR(250) DEFAULT NULL, + photos TEXT NOT NULL, + date_de_naissances DATE NOT NULL, + niveau VARCHAR(250) NOT NULL, -- Clé étrangère vers niveaus + annee_scolaire VARCHAR(20) NOT NULL, + status INTEGER NOT NULL, + mention_id INTEGER NOT NULL, -- Clé étrangère vers mentions + num_inscription TEXT NOT NULL UNIQUE, + sexe VARCHAR(20) NOT NULL, + cin VARCHAR(250) DEFAULT NULL, + date_delivrence DEFAULT NULL, + nationalite DATE NOT NULL, + annee_bacc DATE NOT NULL, + serie VARCHAR(20) NOT NULL, + boursier BOOLEAN DEFAULT FALSE, + domaine VARCHAR(250) NOT NULL, + contact VARCHAR(20) NOT NULL, + parcours VARCHAR(250) DEFAULT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (status) REFERENCES status(id), + FOREIGN KEY (mention_id) REFERENCES mentions(id) + ); +` +database.prepare(createEtudiantsTableQuery).run() + +// Create the notes table if it doesn't exist +const createMatiereTableQuery = ` + CREATE TABLE IF NOT EXISTS matieres ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + nom VARCHAR(250) UNIQUE NOT NULL, + unite_enseignement VARCHAR(250) NOT NULL, + credit INTEGER NOT NULL, + heure INTEGER NOT NULL, + ue VARCHAR(10) NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ); +` +database.prepare(createMatiereTableQuery).run() + +// Create the semestre table if it doesn't exist +const createSemestreTableQuery = ` + CREATE TABLE IF NOT EXISTS semestres ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + nom VARCHAR(30) NOT NULL, -- Exemple: S1, S2, S3, etc. + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ); +` +database.prepare(createSemestreTableQuery).run() + +// Create the semestre table if it doesn't exist +const createMatiere_mentionTableQuery = ` + CREATE TABLE IF NOT EXISTS matiere_mention ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + matiere_id INTEGER NOT NULL, -- Clé étrangère vers matieres + mention_id INTEGER NOT NULL, -- Clé étrangère vers mentions + FOREIGN KEY (matiere_id) REFERENCES matieres(id), + FOREIGN KEY (mention_id) REFERENCES mentions(id) + ); +` +database.prepare(createMatiere_mentionTableQuery).run() + +const createMatiere_semestreTableQuery = ` + CREATE TABLE IF NOT EXISTS matiere_semestre ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + matiere_id INTEGER NOT NULL, -- Clé étrangère vers matieres + semestre_id INTEGER NOT NULL, -- Clé étrangère vers semestres + mention_id INTEGER NOT NULL, -- Clé étrangère vers niveaus + FOREIGN KEY (matiere_id) REFERENCES matieres(id), + FOREIGN KEY (semestre_id) REFERENCES semestres(id), + FOREIGN KEY (mention_id) REFERENCES mentions(id) + ); +` +database.prepare(createMatiere_semestreTableQuery).run() + +// Create the notes table if it doesn't exist +const createNoteTableQuery = ` + CREATE TABLE IF NOT EXISTS notes ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + etudiant_id INTEGER NOT NULL, -- Clé étrangère vers etudiants + matiere_id INTEGER NOT NULL, -- Clé étrangère vers matieres + etudiant_niveau VARCHAR(50) NOT NULL, + mention_id INTEGER NOT NULL, + note FLOAT DEFAULT NULL, + annee_scolaire VARCHAR(50) NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (etudiant_id) REFERENCES etudiants(id), + FOREIGN KEY (matiere_id) REFERENCES matieres(id) + FOREIGN KEY (mention_id) REFERENCES mentions(id) + ); +` +database.prepare(createNoteTableQuery).run() + +// Create the notes second session table if it doesn't exist +const createNoteRepechTableQuery = ` + CREATE TABLE IF NOT EXISTS notesrepech ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + etudiant_id INTEGER NOT NULL, -- Clé étrangère vers etudiants + matiere_id INTEGER NOT NULL, -- Clé étrangère vers matieres + etudiant_niveau VARCHAR(50) NOT NULL, + mention_id INTEGER NOT NULL, + note FLOAT DEFAULT NULL, + annee_scolaire VARCHAR(50) NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (etudiant_id) REFERENCES etudiants(id), + FOREIGN KEY (matiere_id) REFERENCES matieres(id) + FOREIGN KEY (mention_id) REFERENCES mentions(id) + ); +` +database.prepare(createNoteRepechTableQuery).run() + +// create table for note système +const createNoteSystemeTableQuery = ` + CREATE TABLE IF NOT EXISTS notesystems ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + admis FLOAT NOT NULL DEFAULT 10, + redouble FLOAT NOT NULL DEFAULT 9.99, + renvoyer FLOAT NOT NULL DEFAULT 7.99, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ); +` +database.prepare(createNoteSystemeTableQuery).run() + +// create table année scolaire +const createAnneeScolaireTableQuery = ` + CREATE TABLE IF NOT EXISTS anneescolaire ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + code VARCHAR(30) NOT NULL, + debut DATE NOT NULL, + fin DATE NOT NULL, + is_current INTEGER DEFAULT 0, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ); +` +database.prepare(createAnneeScolaireTableQuery).run() + +// create traitement systeme +const createTraitementSystemQuery = ` + CREATE TABLE IF NOT EXISTS traitmentsystem ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + code VARCHAR(30) NOT NULL, + debut DATE NOT NULL, + fin DATE NOT NULL, + is_finished INTEGER DEFAULT 0, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ); +` +database.prepare(createTraitementSystemQuery).run() + +const createNecessaryParameterTableQuery = ` + CREATE TABLE IF NOT EXISTS nessesaryTable ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + uniter_heure INTEGER NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ); +` +database.prepare(createNecessaryParameterTableQuery).run() + +const createMatiereEnseignantTableQuery = ` + CREATE TABLE IF NOT EXISTS matiereEnseignants ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + matiere_id INTEGER NOT NULL, + nom_enseignant VARCHAR(250) NOT NULL, + prenom_enseignant VARCHAR(250) NOT NULL, + contact VARCHAR(11) NOT NULL, + date DATE NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (matiere_id) REFERENCES matieres(id) + ); +` +database.prepare(createMatiereEnseignantTableQuery).run() + +const createParcourTableQuery = ` + CREATE TABLE IF NOT EXISTS parcours ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + nom VARCHAR(250) NOT NULL, + uniter VARCHAR(250) NOT NULL + ); +` +database.prepare(createParcourTableQuery).run() + +const createParcourSemestreTableQuery = ` + CREATE TABLE IF NOT EXISTS parcoursmatiere ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + matiere_id INTEGER NOT NULL, + parcour_id INTEGER NOT NULL, + FOREIGN KEY (matiere_id) REFERENCES matieres(id), + FOREIGN KEY (parcour_id) REFERENCES parcours(id) + ); +` +database.prepare(createParcourSemestreTableQuery).run() + +const createTableEcolageQuery = ` + CREATE TABLE IF NOT EXISTS trancheecolage ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + etudiant_id INTEGER NOT NULL, + tranchename VARCHAR(255) NOT NULL, + montant DOUBLE NOT NULL + ); +` +database.prepare(createTableEcolageQuery).run() + +const createTableStoreIP = ` + CREATE TABLE IF NOT EXISTS ipconfig ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + ipname VARCHAR(255) NOT NULL + ); +`; +database.prepare(createTableStoreIP).run() + +// -------------------------------------- function pre-excuter -------------------------------------------- + +async function insertStatusesIfNotExist() { + // Préparation des requêtes + const checkStatusQuery = database.prepare(` + SELECT COUNT(*) AS count FROM status WHERE nom = ?; + `) + const insertStatusQuery = database.prepare(` + INSERT INTO status (nom) VALUES (?); + `) + + // Tableau des statuts à vérifier/insérer + const arrayStatus = ['Nouveau', 'Passant', 'Redoublant', 'Renvoyé', 'Ancien'] + + for (let index = 0; index < arrayStatus.length; index++) { + const statusName = arrayStatus[index] + + // Vérification si le statut existe déjà + const result = checkStatusQuery.get(statusName) + + // Si le statut n'existe pas, on l'insère + if (result.count === 0) { + insertStatusQuery.run(statusName) + } + } +} +// execute the function +insertStatusesIfNotExist() + +async function insertDefaultNoteSystemIfNotExist() { + // Préparation de la requête pour vérifier si une entrée existe déjà + const checkNoteSystemQuery = database.prepare(` + SELECT COUNT(*) AS count FROM notesystems; + `) + + // Préparation de la requête pour insérer une entrée par défaut + const insertNoteSystemQuery = database.prepare(` + INSERT INTO notesystems (admis, redouble, renvoyer) + VALUES (?, ?, ?); + `) + + // Valeurs par défaut à insérer + const defaultValues = { + admis: 10.0, + redouble: 9.99, + renvoyer: 7.99 + } + + // Vérification si une entrée existe déjà + const result = checkNoteSystemQuery.get() + + if (result.count === 0) { + // Insérer les valeurs par défaut si aucune entrée n'existe + insertNoteSystemQuery.run(defaultValues.admis, defaultValues.redouble, defaultValues.renvoyer) + } +} + +insertDefaultNoteSystemIfNotExist() + +async function semestreCreate() { + const query = database.prepare('INSERT INTO semestres (nom) VALUES (?)') + // Préparation de la requête pour vérifier si une entrée existe déjà + const checkSemestreQuery = database.prepare(` + SELECT COUNT(*) AS count FROM semestres; + `) + + try { + let arraySemestre = [ + 'S1', + 'S2', + 'S3', + 'S4', + 'S5', + 'S6', + 'S7', + 'S8', + 'S9', + 'S10', + 'S11', + 'S12', + 'S13', + 'S14', + 'S14', + 'S16' + ] + // Vérification si une entrée existe déjà + const result = checkSemestreQuery.get() + + if (result.count === 0) { + database.transaction(() => { + for (let index = 0; index < arraySemestre.length; index++) { + query.run(arraySemestre[index]) + } + })() + } + } catch (error) { + console.log(error) + } +} + +const createNecessaryParameterTable = () => { + // Check if the table is empty + const rowCount = database.prepare(`SELECT COUNT(*) AS count FROM nessesaryTable`).get().count + + // If the table is empty, insert the default value + if (rowCount === 0) { + const insertDefaultQuery = ` + INSERT INTO nessesaryTable (uniter_heure) VALUES (15); + ` + database.prepare(insertDefaultQuery).run() + } +} + +// Call the function when the app runs +createNecessaryParameterTable() + +semestreCreate() + +// Function to get the IP from the database +function getIP() { + const data = database.prepare("SELECT * FROM ipconfig WHERE id = 1").get(); + + if (data) { + return data.ipname; + } else { + return null; // Explicitly return `null` if no data is found + } +} + +// Get the new IP from the database +let newIP = getIP(); + +if (newIP) { + // Construct the database path using the new IP from the database + dbPath = `\\\\${newIP}\\base\\data.db`; + + // Reconnect to SQLite database with the updated path + database = new sqlite(dbPath); // Re-initialize database connection with new path + console.log("now COnnect to the ", dbPath); +} + +module.exports = { + database +} diff --git a/database/function/DownloadReleverNote.js b/database/function/DownloadReleverNote.js new file mode 100644 index 0000000..4521564 --- /dev/null +++ b/database/function/DownloadReleverNote.js @@ -0,0 +1,33 @@ +const fs = require('fs') +const { PDFDocument } = require('pdf-lib') +const path = require('path') + +async function modifyPDF(filepath) { + // Load the existing PDF + const existingPdfBytes = fs.readFileSync(path.join(__dirname, '/../../src/renderer/', filepath)) + + // Load the PDF document + const pdfDoc = await PDFDocument.load(existingPdfBytes) + + // Embed the font for text replacement + const pages = pdfDoc.getPages() + const firstPage = pages[0] + + // Replace text based on your data object + const data = { f1: 'Nom', f2: 'Prénom', f3: 23 } + + // Example of replacing placeholders + firstPage.drawText(data.f1, { x: 120, y: 700, size: 12 }) + firstPage.drawText(data.f2, { x: 120, y: 680, size: 12 }) + firstPage.drawText(String(data.f3), { x: 120, y: 660, size: 12 }) + + // Save the modified PDF + const pdfBytes = await pdfDoc.save() + fs.writeFileSync('releves_modified.pdf', pdfBytes) + + console.log('PDF modified successfully!') +} + +module.exports = { + modifyPDF +} diff --git a/database/function/GetImageDefaault.js b/database/function/GetImageDefaault.js new file mode 100644 index 0000000..069dd9d --- /dev/null +++ b/database/function/GetImageDefaault.js @@ -0,0 +1,9 @@ +function getCompressedDefaultImage() { + return ` + data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/4gHYSUNDX1BST0ZJTEUAAQEAAAHIAAAAAAQwAABtbnRyUkdCIFhZWiAH4AABAAEAAAAAAABhY3NwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAA9tYAAQAAAADTLQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAlkZXNjAAAA8AAAACRyWFlaAAABFAAAABRnWFlaAAABKAAAABRiWFlaAAABPAAAABR3dHB0AAABUAAAABRyVFJDAAABZAAAAChnVFJDAAABZAAAAChiVFJDAAABZAAAAChjcHJ0AAABjAAAADxtbHVjAAAAAAAAAAEAAAAMZW5VUwAAAAgAAAAcAHMAUgBHAEJYWVogAAAAAAAAb6IAADj1AAADkFhZWiAAAAAAAABimQAAt4UAABjaWFlaIAAAAAAAACSgAAAPhAAAts9YWVogAAAAAAAA9tYAAQAAAADTLXBhcmEAAAAAAAQAAAACZmYAAPKnAAANWQAAE9AAAApbAAAAAAAAAABtbHVjAAAAAAAAAAEAAAAMZW5VUwAAACAAAAAcAEcAbwBvAGcAbABlACAASQBuAGMALgAgADIAMAAxADb/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/2wBDAQMDAwQDBAgEBAgQCwkLEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBD/wAARCAD6APoDASIAAhEBAxEB/8QAHAABAAIDAQEBAAAAAAAAAAAAAAYHAwQFAggB/8QAQRAAAgEDAgIHBQUFBQkAAAAAAAECAwQFBhESMQcTIUFRcYEUImGhwUJSkbHRFRYXMnIjM0NTsjRUYnSCkqLS4f/EABQBAQAAAAAAAAAAAAAAAAAAAAD/xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwD7LAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATPo1wFplbu5vb+hGtStoqMISW6c33+iXzJpldC6eylJx9jjbVNvdqUVwtenJgUwDs6j0rktOV+G5j1lCT2p14r3Zefg/gcYAAAAAAA2sZi77L3UbPH0JVakvDlFeLfcizsB0cYnHU41cpFXly+1p/3cX4Jd/mwKnBaOv9L4yOCnkLGypUK1rKMm6cduKDezT/AB39CrgAAAAAAAAAAAAAAAAAAAAAAAAAAAtPorpKGDuKu3bUuH8kkTUiXRittNb+Nef0JaBgvbK1yFtUs7yjGrRqLaUZFRap0be4O/jC0pVLi2rv+xlGO8t/uvbvLkAFMWWgdU3sVNY7qYvvrTUPlz+R0P4Wai4d/abHfw6yX/qWuAKavOj7VNmnP2BV4rvozUvlz+Rzcfp/KZHJQxdO0qU60n73WRceBd7e5e5+cMeLj4Vxbbb7duwHL09p2x07ZK2tI8VSXbVqte9N/p8DqgAczUtJV8BkKTW/Fbz/ACKKL9y64sVeJ99Cf+llBAAAAAAAAAAAAAAAAAAAAAAAAAAABbfRi99NJeFef0JaQ/ov4v3dnvFpdfLZ7c+xEwAAAAAAAAAAADUy72xd43/kT/0soIvnPS4MJfT8Lef5FDAAAAAAAAAAAAAAAAAAAAAAAAADJb0Kt1cU7ahHiqVZqEV4tvZGMknR9Zq81PbOS3jQUqvql2fmBbGJxtHEY23x1D+WjBRb+8+9+rNwAAAAAAAAAAAAMF7a0760rWdX+StBwl5NFC39nUx97Xsqy2nQqSpv0Z9AlR9JljG11I7iC2V1RjUf9S91/kgImAAAAAAAAAAAAAAAAAAAAAAAATXoqinm7mT5xt3t/wByIUTHotqqGfq02+2pbyS9GmBa4AAAAAAAAAAAAAVr0tRSvMdLvdOon+KLKKx6WKqlkrGjv2woyl+Mv/gEFAAAAAAAAAAAAAAAAAAAAAAAAO3ou+WP1LZVpy2hOfVSfwl2fnscQJuLUotprtTQH0QDj6UzcM9hqF25J1orq6y8Jrm/XmdgAAAAAAAAAAABTOv8hHIanueCW8LdRoRf9PP/AMmy1dQZelhMTXyFWSThHamvvTfJFFVas61Wdao95zk5Sfi2B5AAAAAAAAAAAAAAAAAAAAAAAAAAHd0jqetpvIdY952tbaNamvDukvii5LO8tchbU7uzrRq0qi3jKLPn46+n9UZTTtfjs6inSk/foz7YS/R/EC8QRjDdIWByijC4rexVnzjWfu7/AAly/IksJwqwVSnOM4yW6lF7pgegAAAPyUowi5TkopdrbeyQH6Yrm5oWlCdzdVY0qVNcUpyeySOBmNfafxUZRp3Ku6y5QoviW/xlyK21Fq3K6jqbXE+qt094UIP3V5+LAz6z1VPUd6oUHKNlQbVKL7OJ/eZHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAbNnlMljnvY31eh3tU6jSfmjWNuyxGUyUuGwx9xX8XCm2l5vkgOtQ19qqikv2k6m334Rf0Nj+JWqdtuvoefUo82/Rxqmuk52lKjv/AJlVfTc2l0W6g27bi0T8ON/oBoVukDVVZbftHq/6KcV9DkXuXymR/wBuyFxXXhOo2vw5EgrdGepqS3hC3q/CNXt+exx7/TOfxicrzE3EIrnNQ4or/qW6A5gDTXYwAAAAAAAAAAAAAAAAAAAAAAAAAAAAA3sPhMjnbtWmPoOcn/NJ9kYLxbA0Um3slu33EpwXR5mctw1rpexW77eKpHebXwj+uxOtNaGxeBUbirGN1eL/ABZrsi/+Fd3nzJKBHcToTTuKUZeye01V9uv73b5ciQQhCnFQpxUYrsSS2SPQAAAAAAOTk9LYHLp+2Y6lxv8AxILhl+KIPnei+8tuKvhLj2mnz6mp2TXk+T+RZwA+e7i2uLStK3uqM6VSD2lGa2aMZemc05itQUOqv7dOaXuVY9k4eT+hVWptHZHTlTrJLr7ST92tFcvhJdzA4AAAAAAAAAAAAAAAAAAAAAAAdTTuAutQ5GFlbpxgveq1NuyEfH9AM2mNL3upLvq6P9nb02utrNdkV4LxZcOJw9hhLSNnYUVCC5v7Un4t97PWLxdnh7KnYWNJQpU16yfe34s2wAAAAAAAAAAAAAAeK1GlcUpUK9ONSnNbSjJbpo9gCqdaaGqYhzyeLi52Te84c3S/WJDT6GnCFSEqdSKlGS2aa3TRUuutIPBXHt9jBuxrS5c+ql4eXgBEwAAAAAAAAAAAAAAAAAB7oUKtzXp29CDnUqyUIRXNt8kXZpXT1HTuLhaxSdefv15r7UvDyRDui/AKtWqZ64hvGk3Tobr7Xe/Tl6llAAAAAAAAAAAAAAAAAAAAMF7Z2+QtatndU1OlWi4yi/AzgCitR4Ovp/KVbCsm4r3qU/vwfJ/T0OYXBr/ALMYaVzRhvc2adSGy7XH7S/Dt9CnwAAAAAAAAAAAAAAZLehUua9O3pLedWShFfFsxkn6Osb7fqSlVnHenaQdd+fKPze/oBauIx1LE4y2x1FJRoU1Hzfe/V7s3AAAAAAAAAAAAAAAAAAAAAAAAUjrDDrC564tacOGjN9bSXcoy7dvTkXcQDpXxvHa2eVhHtpzdCb+DW6+af4gVsAAAAAAAAAAAAAFk9E1qo2t/etds6kaSfwS3+pWxa/Rcl+79T/mJfkgJiAAAAAAAAAAAAAAAAAAAAAAAAcLW9qrvS9/T23cKaqrzi0/od00M8k8JfJ/7vP8AIChgAAAAAAAf/9k= + ` +} + +module.exports = { + getCompressedDefaultImage +} diff --git a/database/function/Helper.js b/database/function/Helper.js new file mode 100644 index 0000000..57a1460 --- /dev/null +++ b/database/function/Helper.js @@ -0,0 +1,26 @@ +const { database } = require('../database') + +const getStatusMention = (menstionText) => { + const query = database.prepare('SELECT * FROM status') + + let response = query.all() + let statutCode + for (let index = 0; index < response.length; index++) { + let nom = response[index].nom + let nomLower = nom.toLowerCase() // Correct method + let find1 = menstionText.slice(0, 1) + let find2 = menstionText.slice(0, 3) + + if (nomLower.slice(0, 1) == find1.toLowerCase()) { + statutCode = response[index].id + } else if (nomLower.slice(0, 3) == find2.toLowerCase()) { + statutCode = response[index].id + } + } + + return statutCode +} + +module.exports = { + getStatusMention +} diff --git a/database/function/StringArrayConvertion.js b/database/function/StringArrayConvertion.js new file mode 100644 index 0000000..617d3db --- /dev/null +++ b/database/function/StringArrayConvertion.js @@ -0,0 +1,26 @@ +/** + * function to convert array to string and string to array + * @param {*} input + * @param {String} separator + * @returns {*} + */ +function convertArrayAndString(input, separator = ', ') { + // If the separator is just a comma without a space, change it to ", " + if (separator === ',') { + separator = ', ' + } + + if (Array.isArray(input)) { + // Convert array to string with the correct separator + return input.join(separator) + } else if (typeof input === 'string') { + // Convert string to array using the separator + return input.split(separator) + } else { + throw new Error('Input must be either an array or a string.') + } +} + +module.exports = { + convertArrayAndString +} diff --git a/database/function/System.js b/database/function/System.js new file mode 100644 index 0000000..5d6d98a --- /dev/null +++ b/database/function/System.js @@ -0,0 +1,332 @@ +const { database } = require('../database') +const dayjs = require('dayjs') + +async function updateCurrentYears() { + const fullDate = dayjs().format('YYYY-MM-DD') + + // Clear current year flag + const clearCurrent = database.prepare('UPDATE anneescolaire SET is_Current = 0 WHERE id > 0') + clearCurrent.run() + + // Set the new current year + const updateCurrent = database.prepare(` + UPDATE anneescolaire + SET is_Current = 1 + WHERE ? >= debut AND ? <= fin + `) + // console.log(); + updateCurrent.run(fullDate, fullDate) + + // Check if the update was successful + const check = database + .prepare( + ` + SELECT * FROM anneescolaire + WHERE ? >= debut AND ? <= fin + ` + ) + .get(fullDate, fullDate) + + // Insert into traitmentsystem if a current year exists + if (check) { + let search = database.prepare('SELECT * FROM traitmentsystem WHERE code = ?').get(check.code) + console.log(search) + if (!search) { + const insertQuery = database.prepare(` + INSERT INTO traitmentsystem (code, debut, fin) + VALUES (?, ?, ?) + `) + insertQuery.run(check.code, check.debut, check.fin) + } + } else { + console.log('No active school year found for the current date.') + } +} + +async function updateStudents() { + const getInfinishedYears = database + .prepare('SELECT * FROM traitmentsystem WHERE is_finished = 0 ORDER BY id ASC') + .get() + + const allEtudiants = database + .prepare('SELECT * FROM etudiants WHERE annee_scolaire = ?') + .all(getInfinishedYears.code) + + function checkNull(params) { + if (params == null || params == undefined) { + return null + } + return params + } + + function compareSessionNotes(session1, session2) { + let notes + if (session2) { + if (session1 < session2.note) { + notes = session2.note + } else { + notes = session1 + } + } else { + notes = session1 + } + return notes + } + + database.transaction(() => { + // get all note of student + const queryNotes = database.prepare( + `SELECT DISTINCT etudiant_id FROM notes WHERE etudiant_niveau = ? AND annee_scolaire = ?` + ) + let allEtudiantWithNotes = [] + let etudiantWithNotes = [] + let dataToMap = [] + let allEtudiantWithNotesRepech = [] + let etudiantWithNotesRepech = [] + + for (const etudiant of allEtudiants) { + const results = queryNotes.all(etudiant.niveau, etudiant.annee_scolaire) + etudiantWithNotes.push(...results) // Avoid nested arrays + } + + const uniqueId = etudiantWithNotes.filter( + (item, index, self) => index === self.findIndex((t) => t.etudiant_id === item.etudiant_id) + ) + + const query2 = database.prepare( + 'SELECT notes.*, etudiants.*, matieres.id, matieres.nom AS nomMat, matieres.credit FROM notes LEFT JOIN etudiants ON (notes.etudiant_id = etudiants.id) LEFT JOIN matieres ON (notes.matiere_id = matieres.id) WHERE notes.etudiant_id = ?' + ) + + for (let j = 0; j < uniqueId.length; j++) { + allEtudiantWithNotes.push(query2.all(uniqueId[j].etudiant_id)) + } + + const query = database.prepare( + `SELECT DISTINCT etudiant_id FROM notesrepech WHERE etudiant_niveau = ? AND annee_scolaire = ?` + ) + + for (const etudiant of allEtudiants) { + const results = query.all(etudiant.niveau, etudiant.annee_scolaire) + etudiantWithNotesRepech.push(...results) // Avoid nested arrays + } + + const uniqueIdRepech = etudiantWithNotes.filter( + (item, index, self) => index === self.findIndex((t) => t.etudiant_id === item.etudiant_id) + ) + + const query2Repech = database.prepare( + 'SELECT notesrepech.*, etudiants.*, matieres.id, matieres.nom AS nomMat, matieres.credit FROM notesrepech INNER JOIN etudiants ON (notesrepech.etudiant_id = etudiants.id) INNER JOIN matieres ON (notesrepech.matiere_id = matieres.id) WHERE notesrepech.etudiant_id = ?' + ) + + for (let j = 0; j < uniqueIdRepech.length; j++) { + allEtudiantWithNotesRepech.push(query2Repech.all(uniqueIdRepech[j].etudiant_id)) + } + + for (let index = 0; index < allEtudiantWithNotes.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: '', + niveau: '', + annee_scolaire: '' + } + + for (let j = 0; j < allEtudiantWithNotes[index].length; j++) { + modelJson.id = allEtudiantWithNotes[index][j].etudiant_id + modelJson.nom = allEtudiantWithNotes[index][j].nom + modelJson.prenom = allEtudiantWithNotes[index][j].prenom + modelJson.photos = allEtudiantWithNotes[index][j].photos + modelJson.mention = allEtudiantWithNotes[index][j].mention_id + modelJson.niveau = allEtudiantWithNotes[index][j].niveau + modelJson.annee_scolaire = allEtudiantWithNotes[index][j].annee_scolaire + + // console.log(checkNull(session[index][j])); + if (allEtudiantWithNotesRepech[index]) { + note += + compareSessionNotes( + allEtudiantWithNotes[index][j].note, + checkNull(allEtudiantWithNotesRepech[index][j]) + ) * allEtudiantWithNotes[index][j].credit + } else { + note += allEtudiantWithNotes[index][j].note * allEtudiantWithNotes[index][j].credit + } + totalCredit += allEtudiantWithNotes[index][j].credit + } + + total = note / totalCredit + modelJson.moyenne = total.toFixed(2) + + // Add the new object to the array + dataToMap.push(modelJson) + } + + // update all etudiant + let updated = false + if (dataToMap.length != 0) { + let noteSystem = database.prepare('SELECT * FROM notesystems').get() + for (let index = 0; index < dataToMap.length; index++) { + if (dataToMap[index].moyenne >= noteSystem.admis) { + let updateQuery = database.prepare( + 'UPDATE etudiants SET niveau = ?, annee_scolaire = ?, status = ? WHERE id = ?' + ) + updateQuery.run( + nextLevel(dataToMap[index].niveau), + updateSchoolYear(dataToMap[index].annee_scolaire), + 2, + dataToMap[index].id + ) + updated = true + } else if ( + dataToMap[index].moyenne < noteSystem.admis && + dataToMap[index].moyenne >= noteSystem.redouble + ) { + let updateQuery = database.prepare( + 'UPDATE etudiants SET niveau = ?, annee_scolaire = ? status = ? WHERE id = ?' + ) + updateQuery.run( + dataToMap[index].niveau, + updateSchoolYear(dataToMap[index].annee_scolaire), + 3, + dataToMap[index].id + ) + updated = true + } else { + let updateQuery = database.prepare( + 'UPDATE etudiants SET niveau = ?, annee_scolaire = ? status = ? WHERE id = ?' + ) + updateQuery.run( + dataToMap[index].niveau, + dataToMap[index].annee_scolaire, + 4, + dataToMap[index].id + ) + updated = true + } + } + } + + if (updated) { + const updateInfinishedYears = database.prepare( + 'UPDATE traitmentsystem SET is_finished = 1 WHERE id = ?' + ) + + updateInfinishedYears.run(getInfinishedYears.id) + } + })() +} + +function nextLevel(niveau) { + if (niveau == 'L1') { + return 'L2' + } else if (niveau == 'L2') { + return 'L3' + } else if (niveau == 'L3') { + return 'M1' + } else if (niveau == 'M1') { + return 'M2' + } else if (niveau == 'M2') { + return 'D1' + } else if (niveau == 'D1') { + return 'D2' + } else if (niveau == 'D2') { + return 'D3' + } else if (niveau == 'D3') { + return 'PHD' + } +} + +function updateSchoolYear(year) { + // Split the year into two parts + const [startYear, endYear] = year.split('-').map(Number) + + // Increment both the start and end year by 1 + const newStartYear = startYear + 1 + const newEndYear = endYear + 1 + + // Join the new years with a hyphen + const newYear = `${newStartYear}-${newEndYear}` + + return newYear +} + +async function matiereSysteme(etudiant_niveau) { + let systeme + if (etudiant_niveau == 'L1') { + systeme = ['S1', 'S2'] + } else if (etudiant_niveau == 'L2') { + systeme = ['S3', 'S4'] + } else if (etudiant_niveau == 'L3') { + systeme = ['S5', 'S6'] + } else if (etudiant_niveau == 'M1') { + systeme = ['S7', 'S8'] + } else if (etudiant_niveau == 'M2') { + systeme = ['S9', 'S10'] + } else if (etudiant_niveau == 'D1') { + systeme = ['S11', 'S12'] + } else if (etudiant_niveau == 'D2') { + systeme = ['S13', 'S14'] + } else if (etudiant_niveau == 'D3') { + systeme = ['S15', 'S16'] + } + + return systeme +} + +async function matiereSystemReverse(semestre) { + if (semestre == 'S1' || semestre == 'S2') { + return 'L1' + } else if (semestre == 'S3' || semestre == 'S4') { + return 'L2' + } else if (semestre == 'S5' || semestre == 'S6') { + return 'L3' + } else if (semestre == 'S7' || semestre == 'S8') { + return 'M1' + } else if (semestre == 'S9' || semestre == 'S10') { + return 'M2' + } else if (semestre == 'S11' || semestre == 'S12') { + return 'D1' + } else if (semestre == 'S13' || semestre == 'S14') { + return 'D2' + } else if (semestre == 'S15' || semestre == 'S16') { + return 'D3' + } +} + +async function getNessesarytable() { + try { + const query = await database.prepare('SELECT * FROM nessesaryTable').get() + + return query + } catch (error) { + return error + } +} + +async function updateNessesaryTable(id, multiplicateur) { + const query = database.prepare('UPDATE nessesaryTable SET uniter_heure = ? WHERE id = ?') + + try { + let update = query.run(multiplicateur, id) + + return update + } catch (error) { + return error + } +} + +module.exports = { + matiereSysteme, + updateCurrentYears, + updateStudents, + getNessesarytable, + updateNessesaryTable, + matiereSystemReverse +} diff --git a/database/import/Etudiants.js b/database/import/Etudiants.js new file mode 100644 index 0000000..c278478 --- /dev/null +++ b/database/import/Etudiants.js @@ -0,0 +1,210 @@ +const fs = require('fs') +const path = require('path') +const XLSX = require('xlsx') +const { getCompressedDefaultImage } = require('../function/GetImageDefaault') +const { parse } = require('csv-parse/sync') +const { insertEtudiant } = require('../Models/Etudiants') +const { database } = require('../database') +const { getMentions } = require('../Models/Mentions') +const dayjs = require('dayjs') +const { getStatusMention } = require('../function/Helper') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) + +// Function to convert any date format to 'YYYY-MM-DD' +function convertToISODate(input) { + // Try parsing the date with different formats + const formats = [ + 'DD/MM/YYYY', + 'MM/DD/YYYY', + 'YYYY-MM-DD', + 'DD-MM-YYYY', + 'MM-DD-YYYY', + 'DD/MM/YY', + 'MM/DD/YY' + ] + const parsedDate = dayjs(input, formats, true) // Strict parsing to ensure formats are matched correctly + + // If the date is valid, return it in the YYYY-MM-DD format + if (parsedDate.isValid()) { + return parsedDate.format('YYYY-MM-DD') + } + + function excelDateToJSDate(serial) { + const utc_days = Math.floor(serial - 25569); // days from Jan 1, 1970 + const utc_value = utc_days * 86400; // seconds in a day + return new Date(utc_value * 1000); // JS Date uses milliseconds + } + + let jsDate = excelDateToJSDate(input); + + // If the input is not a valid date, return 'Invalid Date' + return jsDate +} + +async function MentionList() { + let response = await getMentions() + return response +} + +let ListMention + +/** + * Function to import data from XLSX or CSV file into SQLite database + * @param {string} filePath - Path to the file (either .xlsx or .csv) + */ +async function importFileToDatabase(filePath) { + const fileExtension = path.extname(filePath).toLowerCase() + + // Determine the file type and parse accordingly + let records + if (fileExtension === '.xlsx') { + // Read and parse XLSX file + const workbook = XLSX.readFile(filePath) + const worksheet = workbook.Sheets[workbook.SheetNames[0]] // Assuming data is in the first sheet + records = XLSX.utils.sheet_to_json(worksheet, { defval: '', raw: false }) + } else if (fileExtension === '.csv') { + // Read and parse CSV file + const fileContent = fs.readFileSync(filePath, 'utf8') + records = parse(fileContent, { + columns: true, + skip_empty_lines: true + }) + } else { + console.error('Unsupported file format. Only .xlsx and .csv are allowed.') + return + } + + try { + let error = true + let message = '' + + // Vérifier les données en une seule boucle + for (const row of records) { + console.log(convertToISODate(row.date_naissance)) + if ( + !row.nom || + // !row.prenom || + !row.date_naissance || + !row.niveau || + !row.annee_scolaire || + !row.mention || + !row.num_inscription || + !row.nationaliter || + !row.sexe || + // !row.cin || + // !row.date_de_delivrance || + !row.annee_baccalaureat || + !row.serie || + !row.code_redoublement || + !row.boursier || + !row.domaine || + !row.contact + ) { + if (!row.nom) { + message = "Le champ 'nom' est inconnu" + } + // else if (!row.prenom) { + // message = "Le champ 'prenom' est inconnu" + // } + else if (!row.date_naissance) { + message = "Le champ 'date_naissance' est inconnu" + } else if (!row.niveau) { + message = "Le champ 'niveau' est inconnu" + } else if (!row.annee_scolaire) { + message = "Le champ 'annee_scolaire' est inconnu" + } else if (!row.mention) { + message = "Le champ 'mention' est inconnu" + } else if (!row.num_inscription) { + message = "Le champ 'num_inscription' est inconnu" + } else if (!row.nationaliter) { + message = "Le champ 'nationaliter' est inconnu" + } else if (!row.sexe) { + message = "Le champ 'sexe' est inconnu" + } + // else if (!row.cin) { + // message = "Le champ 'cin' est inconnu" + // } else if (!row.date_de_delivrance) { + // message = "Le champ 'date_de_delivrance' est inconnu" + // } + else if (!row.annee_baccalaureat) { + message = "Le champ 'annee_baccalaureat' est inconnu" + } else if (!row.serie) { + message = "Le champ 'serie' est inconnu" + } else if (!row.code_redoublement) { + message = "Le champ 'code_redoublement' est inconnu" + } else if (!row.boursier) { + message = "Le champ 'boursier' est inconnu" + } else if (!row.domaine) { + message = "Le champ 'domaine' est inconnu" + } else if (!row.contact) { + message = "Le champ 'contact' est inconnu" + } + error = false + break + } + } + + async function fetchMentions() { + try { + // Fetch the mentions + ListMention = await MentionList() + + // Assuming 'ListMention' is an array of objects like the ones you mentioned + // Si aucune erreur, insérer les données en batch + if (error !== false) { + // Utiliser transaction pour éviter une latence si l'insertion dépasse 100 + database.transaction(() => { + for (const row of records) { + // Convert row.mention to uppercase and compare with ListMention.nom and ListMention.uniter (also converted to uppercase) + const matchedMention = ListMention.find( + (mention) => + mention.nom.toUpperCase() === row.mention.toUpperCase() || + mention.uniter.toUpperCase() === row.mention.toUpperCase() + ) + + // If a match is found, update row.mention with ListMention.id + if (matchedMention) { + row.mention = matchedMention.id + } + // Insert the student data with the updated mention ID + insertEtudiant( + row.nom, + row.prenom, + getCompressedDefaultImage(), + convertToISODate(row.date_naissance), + row.niveau, + row.annee_scolaire, + getStatusMention(row.code_redoublement), + row.num_inscription, + row.mention, + row.sexe, + row.nationaliter, + row.cin, + row.date_de_livraison, + row.annee_baccalaureat, + row.serie, + row.boursier, + row.domaine, + row.contact + ) + } + })() + } + } catch (error) { + console.error('Error:', error) // Handle any errors + } + } + + fetchMentions() + + return { error, message } + } catch (error) { + console.error('Error inserting record:', error) + return { error: 'error' } + } +} + +module.exports = { + importFileToDatabase +} diff --git a/database/import/Matieres.js b/database/import/Matieres.js new file mode 100644 index 0000000..e85793d --- /dev/null +++ b/database/import/Matieres.js @@ -0,0 +1,69 @@ +const fs = require('fs') +const path = require('path') +const XLSX = require('xlsx') +const { parse } = require('csv-parse/sync') +const { createMatiere } = require('../Models/Matieres') +const { database } = require('../database') + +/** + * Function to import data from the first column of an XLSX or CSV file into SQLite database + * @param {string} filePath - Path to the file (either .xlsx or .csv) + */ +async function importFileToDatabaseMatiere(filePath) { + const fileExtension = path.extname(filePath).toLowerCase() + + // Determine the file type and parse accordingly + let records + if (fileExtension === '.xlsx') { + // Read and parse XLSX file + const workbook = XLSX.readFile(filePath) + const worksheet = workbook.Sheets[workbook.SheetNames[0]] // Assuming data is in the first sheet + records = XLSX.utils.sheet_to_json(worksheet, { defval: '' }) + } else if (fileExtension === '.csv') { + // Read and parse CSV file + const fileContent = fs.readFileSync(filePath, 'utf8') + records = parse(fileContent, { + columns: true, + skip_empty_lines: true + }) + } else { + console.error('Unsupported file format. Only .xlsx and .csv are allowed.') + return + } + + try { + let message = '' + let error = true + for (const row of records) { + if (!row.nom || !row.credit || !row.uniter || !row.ue) { + if (!row.nom) { + message = "Le champ 'nom' est inconnu" + } else if (!row.credit) { + message = "Le champ 'credit' est inconnu" + } else if (!row.uniter) { + message = "Le champ 'uniter' est inconnu" + } else if (!row.ue) { + message = "Le champ 'UE' est inconnu" + } + error = false + break + } + } + + if (error !== false) { + database.transaction(() => { + for (const row of records) { + createMatiere(row.nom, row.credit, row.uniter, row.ue) + } + })() + } + + return { error, message } + } catch (error) { + console.error('Error inserting record:', error) + } +} + +module.exports = { + importFileToDatabaseMatiere +} diff --git a/database/import/Niveau.js b/database/import/Niveau.js new file mode 100644 index 0000000..6b3832b --- /dev/null +++ b/database/import/Niveau.js @@ -0,0 +1,52 @@ +const fs = require('fs') +const path = require('path') +const XLSX = require('xlsx') +const { parse } = require('csv-parse/sync') +const { insertNiveau } = require('../Models/Niveau') + +async function importNiveau(filePath) { + const fileExtension = path.extname(filePath).toLowerCase() + + // Determine the file type and parse accordingly + let records + if (fileExtension === '.xlsx') { + // Read and parse XLSX file + const workbook = XLSX.readFile(filePath) + const worksheet = workbook.Sheets[workbook.SheetNames[0]] // Assuming data is in the first sheet + records = XLSX.utils.sheet_to_json(worksheet, { defval: '' }) + } else if (fileExtension === '.csv') { + // Read and parse CSV file + const fileContent = fs.readFileSync(filePath, 'utf8') + records = parse(fileContent, { + columns: true, + skip_empty_lines: true + }) + } else { + console.error('Unsupported file format. Only .xlsx and .csv are allowed.') + return + } + + try { + // Get the first column key dynamically + const firstColumnKey = Object.keys(records[0])[0] + console.log(`Detected first column key: ${firstColumnKey}`) + + for (const row of records) { + const firstColumnValue = row[firstColumnKey] || 'null pour le moment' + + // Insert into the database + await insertNiveau(firstColumnValue) + console.log(`Inserted value from first column: '${firstColumnValue}'`) + } + console.log( + `First column values successfully imported from ${fileExtension.toUpperCase()} file` + ) + return { success: 'success' } + } catch (error) { + console.error('Error inserting record:', error) + } +} + +module.exports = { + importNiveau +} diff --git a/electron-builder.yml b/electron-builder.yml new file mode 100644 index 0000000..53b0e66 --- /dev/null +++ b/electron-builder.yml @@ -0,0 +1,45 @@ +appId: com.electron.app +productName: CUniversity +directories: + buildResources: build +files: + - '!**/.vscode/*' + - '!src/*' + - '!electron.vite.config.{js,ts,mjs,cjs}' + - '!{.eslintignore,.eslintrc.cjs,.prettierignore,.prettierrc.yaml,dev-app-update.yml,CHANGELOG.md,README.md}' + - '!{.env,.env.*,.npmrc,pnpm-lock.yaml}' +asarUnpack: + - resources/** +win: + executableName: CUniversity +nsis: + artifactName: ${name}-${version}-setup.${ext} + shortcutName: ${productName} + uninstallDisplayName: ${productName} + createDesktopShortcut: always +mac: + entitlementsInherit: build/entitlements.mac.plist + extendInfo: + - NSCameraUsageDescription: Application requests access to the device's camera. + - NSMicrophoneUsageDescription: Application requests access to the device's microphone. + - NSDocumentsFolderUsageDescription: Application requests access to the user's Documents folder. + - NSDownloadsFolderUsageDescription: Application requests access to the user's Downloads folder. + notarize: false +dmg: + artifactName: ${name}-${version}.${ext} +linux: + target: + - AppImage + - snap + - deb + maintainer: electronjs.org + category: Utility +appImage: + artifactName: ${name}-${version}.${ext} +npmRebuild: false +publish: + provider: generic + url: https://api.polytechnique.c4m.mg/latest + channel: latest + useMultipleRangeRequest: false + diff --git a/electron.vite.config.1737019603496.mjs b/electron.vite.config.1737019603496.mjs new file mode 100644 index 0000000..d2f9e58 --- /dev/null +++ b/electron.vite.config.1737019603496.mjs @@ -0,0 +1,25 @@ +// 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 } diff --git a/electron.vite.config.1737797462311.mjs b/electron.vite.config.1737797462311.mjs new file mode 100644 index 0000000..d2f9e58 --- /dev/null +++ b/electron.vite.config.1737797462311.mjs @@ -0,0 +1,25 @@ +// 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 } diff --git a/electron.vite.config.mjs b/electron.vite.config.mjs new file mode 100644 index 0000000..9c084f4 --- /dev/null +++ b/electron.vite.config.mjs @@ -0,0 +1,23 @@ +import { resolve } from 'path' +import { defineConfig, externalizeDepsPlugin } from 'electron-vite' +import react from '@vitejs/plugin-react' + +export 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') + } +}) diff --git a/package.json b/package.json new file mode 100644 index 0000000..8ff4062 --- /dev/null +++ b/package.json @@ -0,0 +1,85 @@ +{ + "name": "c-university", + "version": "4.1.0", + "description": "An Electron application with React", + "main": "./out/main/index.js", + "author": "CPAY COMPANY FOR MADACASCAR", + "homepage": "https://electron-vite.org", + "scripts": { + "format": "prettier --write .", + "lint": "eslint . --ext .js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix", + "start": "electron-vite preview", + "dev": "electron-vite dev", + "build": "electron-vite build", + "postinstall": "electron-builder install-app-deps", + "build:unpack": "npm run build && electron-builder --dir", + "build:win": "npm run build && electron-builder --win", + "build:mac": "npm run build && electron-builder --mac", + "build:linux": "npm run build && electron-builder --linux" + }, + "build": { + "appId": "com.myapp", + "win": { + "target": "nsis", + "publish": [ + { + "provider": "generic", + "url": "https://api.polytechnique.c4m.mg/latest" + } + ] + } + }, + "dependencies": { + "@electron-toolkit/preload": "^3.0.1", + "@electron-toolkit/utils": "^3.0.0", + "@emotion/react": "^11.13.3", + "@emotion/styled": "^11.13.0", + "@mui/material": "^6.1.1", + "@mui/x-data-grid": "^7.18.0", + "ag-psd": "^22.0.2", + "axios": "^1.9.0", + "bcryptjs": "^2.4.3", + "better-sqlite3": "^11.3.0", + "bootstrap": "^5.3.3", + "chart.js": "^4.4.4", + "cors": "^2.8.5", + "csv-parse": "^5.5.6", + "dayjs": "^1.11.13", + "electron-log": "^5.2.0", + "electron-updater": "^6.3.9", + "express": "^4.21.2", + "file-saver": "^2.0.5", + "html2canvas": "^1.4.1", + "jspdf": "^2.5.2", + "jspdf-autotable": "^5.0.2", + "papaparse": "^5.4.1", + "pdf-lib": "^1.17.1", + "qrcode": "^1.5.4", + "react-bootstrap": "^2.10.4", + "react-chartjs-2": "^5.2.0", + "react-icons": "^5.3.0", + "react-pdf": "^6.2.2", + "react-router-dom": "^6.26.2", + "react-spinners": "^0.14.1", + "react-tooltip": "^5.28.0", + "reselect": "^5.1.1", + "update-electron-app": "^3.0.0", + "xlsx": "^0.18.5", + "xlsx-populate": "^1.21.0" + }, + "devDependencies": { + "@electron-toolkit/eslint-config": "^1.0.2", + "@electron-toolkit/eslint-config-prettier": "^2.0.0", + "@vitejs/plugin-react": "^4.3.1", + "electron": "^31.0.2", + "electron-builder": "^24.13.3", + "electron-vite": "^2.3.0", + "eslint": "^8.57.0", + "eslint-plugin-react": "^7.34.3", + "npm": "^10.9.2", + "prettier": "^3.3.2", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "vite": "^5.3.1" + } +} diff --git a/resources/icon.png b/resources/icon.png new file mode 100644 index 0000000..cf9e8b2 Binary files /dev/null and b/resources/icon.png differ diff --git a/resources/logo.ico b/resources/logo.ico new file mode 100644 index 0000000..f592b3c Binary files /dev/null and b/resources/logo.ico differ diff --git a/reste.txt b/reste.txt new file mode 100644 index 0000000..71a56d6 --- /dev/null +++ b/reste.txt @@ -0,0 +1,103 @@ +validation addmatiere +update annee scolaire +when delete matiere, delete also matiere_mention and matiere_semestre + +SELECT * +FROM table_name +WHERE NOW() BETWEEN debut AND fin; + +// End date (example) +const endDate = new Date("2025-03-20"); // Replace with your actual end date + +// Calculate the date 3 months before the end date +const threeMonthsBefore = new Date(endDate); +threeMonthsBefore.setMonth(threeMonthsBefore.getMonth() - 3); + +// Get the current date +const currentDate = new Date(); + +// Check if the current date is between threeMonthsBefore and endDate +if (currentDate >= threeMonthsBefore && currentDate <= endDate) { + console.log("The current date is within 3 months before the end date."); +} else { + console.log("The current date is outside the range."); +} + +Since you have an **Ethernet network** (but no Wi-Fi or router), you can share the SQLite database (`data.db`) over the network by sharing a folder. Here's how you can do it: + +--- + +### **Step 1: Share the Database Folder Over the Network** +#### **On PC 1 (Hosting the Database)** +1. **Locate the folder** + - Your database is at: + ``` + C:\electron\database\data.db + ``` + - Open **File Explorer** and go to `C:\electron\database\`. + +2. **Right-click on the `database` folder → Click 'Properties'** + - Go to the **'Sharing'** tab. + - Click **'Advanced Sharing'**. + - Check **'Share this folder'**. + +3. **Set Permissions** + - Click **'Permissions'**. + - Select **'Everyone'** and give **Full Control** (or at least Read/Write if both PCs need to modify the database). + - Click **OK** and **Apply**. + +4. **Note the Network Path** + - Open **Command Prompt** (`Win + R` → `cmd` → Enter). + - Type: + ```sh + ipconfig + ``` + - Look for your **Ethernet Adapter IPv4 Address** (e.g., `192.168.1.100`). + + The shared path will be: + ``` + \\192.168.1.100\database + ``` + +--- + +### **Step 2: Access the Database from PC 2** +#### **On PC 2 (Client PC)** +1. **Map the Network Drive** + - Open **File Explorer**. + - Click **'This PC' → 'Map Network Drive'**. + - Choose a drive letter (e.g., `Z:`). + - Enter the **Network Path** from Step 1 (e.g., `\\192.168.1.100\database`). + - Click **Finish**. + +2. **Modify SQLite Connection in Electron** + In your Electron app on PC 2, change: + ```javascript + const sqlite = require('better-sqlite3'); + const database = new sqlite('//192.168.1.100/database/data.db'); + ``` + OR, if you mapped it to `Z:\`: + ```javascript + const database = new sqlite('Z:/data.db'); + ``` + +--- + +### **Step 3: Test the Connection** +1. **Run the Electron app on PC 2** and check if it can read/write to `data.db`. +2. If there's an error, check: + - **Folder permissions** (PC 1 should allow read/write access). + - **Windows Firewall** (Allow File Sharing for `Private Networks`). + - **Network Discovery** (Enable it in **Control Panel → Network & Sharing Center**). + +--- + +### **Important Notes** +- **Concurrency Issues:** SQLite locks the file when writing, so only one PC should write at a time. If you need multiple write operations, consider using a **server-based approach** (like Node.js with Express). +- **Performance:** Network latency may cause slow database operations. +- **Auto-Reconnection:** If PC 1 reboots, PC 2 might lose connection. You can remap the drive automatically on startup. + +--- + +**✅ Done!** Now both PCs can access `data.db` over Ethernet. 🚀 +Let me know if you need further help! diff --git a/src/main/backup.js b/src/main/backup.js new file mode 100644 index 0000000..f74fbba --- /dev/null +++ b/src/main/backup.js @@ -0,0 +1,255 @@ +import { app, shell, BrowserWindow, ipcMain, Tray } from 'electron' +import { join } from 'path' +import { electronApp, optimizer, is } from '@electron-toolkit/utils' +import icon from '../../resources/icon.png?asset' +const { loginUser, forgotPassword, insertUser, updateUser } = require('../../database/Models/Users') +const { + insertEtudiant, + getSingleEtudiant, + FilterDataByNiveau, + updateEtudiant +} = require('../../database/Models/Etudiants') +const { insertNiveau } = require('../../database/Models/Niveau') +const { insertNote } = require('../../database/Models/Notes') + +// declare mainWindow in the global scope +let mainWindow +let tray = null + +function createWindow() { + // Create the browser window. + mainWindow = new BrowserWindow({ + width: 1000, + minWidth: 1000, + height: 670, + minHeight: 670, + show: false, + autoHideMenuBar: true, + fullscreen: true, // This will make the window fullscreen when opened + ...(process.platform === 'linux' ? { icon } : {}), + webPreferences: { + preload: join(__dirname, '../preload/index.js'), + nodeIntegration: true, + contextIsolation: true, + sandbox: false + } + }) + + mainWindow.on('ready-to-show', () => { + mainWindow.show() + }) + + mainWindow.webContents.setWindowOpenHandler((details) => { + shell.openExternal(details.url) + return { action: 'deny' } + }) + + // HMR for renderer base on electron-vite cli. + // Load the remote URL for development or the local html file for production. + if (is.dev && process.env['ELECTRON_RENDERER_URL']) { + mainWindow.loadURL(process.env['ELECTRON_RENDERER_URL']) + } else { + mainWindow.loadFile(join(__dirname, '../renderer/index.html')) + } +} + +// This method will be called when Electron has finished +// initialization and is ready to create browser windows. +// Some APIs can only be used after this event occurs. +app.whenReady().then(() => { + // Set app user model id for windows + electronApp.setAppUserModelId('com.electron') + + // Default open or close DevTools by F12 in development + // and ignore CommandOrControl + R in production. + // see https://github.com/alex8088/electron-toolkit/tree/master/packages/utils + app.on('browser-window-created', (_, window) => { + optimizer.watchWindowShortcuts(window) + }) + + // IPC test + ipcMain.on('pingpong', () => console.log('pongsss')) + + createWindow() + + app.on('activate', function () { + // On macOS it's common to re-create a window in the app when the + // dock icon is clicked and there are no other windows open. + if (BrowserWindow.getAllWindows().length === 0) createWindow() + }) +}) + +// Quit when all windows are closed, except on macOS. There, it's common +// for applications and their menu bar to stay active until the user quits +// explicitly with Cmd + Q. +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, email, passwordVerif, password, id } = credentials + + const update = await updateUser(username, email, 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, num_inscription } = + credentials + + const insert = await insertEtudiant( + nom, + prenom, + photos, + date_de_naissances, + niveau, + annee_scolaire, + num_inscription + ) + + 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, num_inscription, id } = + credentials + + const updating = await updateEtudiant( + nom, + prenom, + photos, + date_de_naissances, + niveau, + annee_scolaire, + num_inscription, + id + ) + + return updating +}) + +// event for adding notes +ipcMain.handle('insertNote', async (event, credentials) => { + const { + etudiant_id, + Algebre, + Analyse, + Mecanique_Generale_I, + Resistance_Materiaux, + Electricite, + Chimie_Generale_1, + Algorithmique, + Thermodynamique_Physique, + Mecanique_Fluide, + Optique_Geometrique, + Calcul_Numerique, + Calcul_Vectoriel_Integral, + Francais, + Anglais, + Dessin_Technique, + Programmation + } = credentials + + const insert = await insertNote( + etudiant_id, + Algebre, + Analyse, + Mecanique_Generale_I, + Resistance_Materiaux, + Electricite, + Chimie_Generale_1, + Algorithmique, + Thermodynamique_Physique, + Mecanique_Fluide, + Optique_Geometrique, + Calcul_Numerique, + Calcul_Vectoriel_Integral, + Francais, + Anglais, + Dessin_Technique, + Programmation + ) + + return insert +}) diff --git a/src/main/index.js b/src/main/index.js new file mode 100644 index 0000000..a64c16c --- /dev/null +++ b/src/main/index.js @@ -0,0 +1,908 @@ +import { app, shell, BrowserWindow, ipcMain, Tray, Menu, screen } 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 { 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, + 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, + 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 { modifyPDF } = require('../../database/function/DownloadReleverNote') +const log = require('electron-log') +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') +const express = require('express'); + +// 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_delivrence, + 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_delivrence, + 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 +}) +// 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, credentials) => { + const { id } = credentials + + const get = deleteMatiere(id) + return get +}) + +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 +}) diff --git a/src/main/test.js b/src/main/test.js new file mode 100644 index 0000000..53ce37c --- /dev/null +++ b/src/main/test.js @@ -0,0 +1,135 @@ +import { app, shell, BrowserWindow, ipcMain } from 'electron' +import { join } from 'path' +import { electronApp, optimizer, is } from '@electron-toolkit/utils' +import icon from '../../resources/icon.png?asset' +const { loginUser, forgotPassword } = require('../../database/Models/Users') + +let splashWindow +let mainWindow + +// Create splash window +function createSplashScreen() { + splashWindow = new BrowserWindow({ + width: 400, + height: 300, + frame: false, + alwaysOnTop: true, + transparent: true, + resizable: false, + webPreferences: { + nodeIntegration: true, + contextIsolation: false + } + }) + + splashWindow.loadFile(join(__dirname, '../renderer/splash.html')) + + splashWindow.on('closed', () => { + splashWindow = null + }) +} + +// Create main application window +function createWindow() { + mainWindow = new BrowserWindow({ + width: 1000, + minWidth: 1000, + height: 670, + minHeight: 670, + show: false, + autoHideMenuBar: true, + fullscreen: true, + ...(process.platform === 'linux' ? { icon } : {}), + webPreferences: { + preload: join(__dirname, '../preload/index.js'), + nodeIntegration: true, + contextIsolation: true, + sandbox: false + } + }) + + mainWindow.on('ready-to-show', () => { + if (splashWindow) { + splashWindow.close() // Close splash screen when main window is ready + } + mainWindow.show() + }) + + mainWindow.webContents.setWindowOpenHandler((details) => { + shell.openExternal(details.url) + return { action: 'deny' } + }) + + // Load the initial content + if (is.dev && process.env['ELECTRON_RENDERER_URL']) { + mainWindow.loadURL(process.env['ELECTRON_RENDERER_URL']) + } else { + mainWindow.loadFile(join(__dirname, '../renderer/index.html')) + } +} + +// Function to load new content and show the splash screen +function loadNewContent(contentPath) { + createSplashScreen() // Show splash screen before loading new content + + mainWindow.loadFile(contentPath).then(() => { + if (splashWindow) { + splashWindow.close() // Close splash screen after loading content + } + mainWindow.show() // Show main window + }) +} + +// This method will be called when Electron has finished initialization +app.whenReady().then(() => { + // Set app user model id for Windows + electronApp.setAppUserModelId('com.electron') + + // Default open or close DevTools by F12 in development + app.on('browser-window-created', (_, window) => { + optimizer.watchWindowShortcuts(window) + }) + + // Create initial main window + createWindow() + + // IPC for loading new content + ipcMain.on('load-content', (event, contentPath) => { + loadNewContent(contentPath) + }) + + app.on('activate', function () { + if (BrowserWindow.getAllWindows().length === 0) createWindow() + }) +}) + +// Quit when all windows are closed, except on macOS +app.on('window-all-closed', () => { + if (process.platform !== 'darwin') { + app.quit() + } +}) + +// 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 forgot password +ipcMain.handle('forgotPassword', async (event, credentials) => { + const { email, password, passwordConfirmation } = credentials + + const updated = await forgotPassword(email, password, passwordConfirmation) + + if (updated) { + return updated + } +}) diff --git a/src/preload/index.js b/src/preload/index.js new file mode 100644 index 0000000..baa360e --- /dev/null +++ b/src/preload/index.js @@ -0,0 +1,210 @@ +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), + importExcel: (credentials) => ipcRenderer.invoke('importExcelMatiere', credentials), + displayMatiereFromForm: (credentials) => + ipcRenderer.invoke('displayMatiereFromForm', credentials), + deleteMatiere: (credentials) => ipcRenderer.invoke('deleteMatiere', 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 +} diff --git a/src/renderer/index.html b/src/renderer/index.html new file mode 100644 index 0000000..ae63b91 --- /dev/null +++ b/src/renderer/index.html @@ -0,0 +1,18 @@ + + + + + Université de Toamasina + + + + + + +
+ + + diff --git a/src/renderer/src/App.jsx b/src/renderer/src/App.jsx new file mode 100644 index 0000000..cb1b663 --- /dev/null +++ b/src/renderer/src/App.jsx @@ -0,0 +1,63 @@ +import React, { useEffect, useState } from 'react' +import { RouterProvider } from 'react-router-dom' +import Router from './Routes/Routes' +import { AuthContextProvider, useAuthContext } from './contexts/AuthContext' +import { ClipLoader } from 'react-spinners' // Import the loader +import preloader from './assets/preloader.jpg' +import { DataProvider } from './contexts/MoyenneDeClasseContext' + +const App = () => { + const [loading, setLoading] = useState(true) + const { setToken } = useAuthContext() + + // Simulate loading (e.g., fetching some initial data or assets) + useEffect(() => { + const timer = setTimeout(() => { + setLoading(false) // Set loading to false after the simulated loading time + }, 3000) // 3 seconds delay to simulate loading + + return () => clearTimeout(timer) // Cleanup the timer + }, []) + + // Show Preloader while loading, else show your content + if (loading) { + return ( +
+ +
+ ) + } + + /** + * condition usign with the Tray icon on the bottom + */ + if (window.Tray && typeof window.Tray.onNavigate === 'function') { + window.Tray.onNavigate((route) => { + if (route) { + window.location.hash = route // Navigate by updating the hash + } + }) + } + + return ( + + + + + + ) +} + +export default App diff --git a/src/renderer/src/Routes/Routes.jsx b/src/renderer/src/Routes/Routes.jsx new file mode 100644 index 0000000..cafd33b --- /dev/null +++ b/src/renderer/src/Routes/Routes.jsx @@ -0,0 +1,207 @@ +import { createHashRouter } from 'react-router-dom' +import DefaultLayout from '../layouts/DefaultLayout' +import Login from '../components/Login' +import LoginLayout from '../layouts/LoginLayout' +import NotFound from '../components/NotFound' +import ForgotPassword from '../components/ForgotPassword' +import Home from '../components/Home' +import Student from '../components/Student' +import Notes from '../components/Notes' +import AddStudent from '../components/AddStudent' +import SingleEtudiant from '../components/SingleEtudiant' +import AddNotes from '../components/AddNotes' +import Apropos from '../components/Apropos' +import Niveau from '../components/Niveau' +import AddNiveau from '../components/AddNiveau' +import Admin from '../components/Addadmin' +import Setting from '../components/Param' +import SingleNotes from '../components/SingleNotes' +import ExportEtudiants from '../components/ExportEtudiants' +import SingleNiveau from '../components/singleNiveau' +import Matieres from '../components/Matieres' +import AddMatiere from '../components/AddMatiere' +import ImportMatiere from '../components/ImportMatiere' +import SingleMatiere from '../components/SingleMatiere' +import ImportNiveau from '../components/ImportNiveau' +import SystemeNote from '../components/SystemeNote' +import AnneeScolaire from '../components/AnneeScolaire' +import AddAnneeScolaire from '../components/AddAnneeScolaire' +import Noteclasse from '../components/Noteclasse' +import TesteDatagrid from '../components/TesteDatagrid' +import Manuel from '../components/Manuel' +import Mentions from '../components/Mentions' +import AddMention from '../components/AddMention' +import SinleMention from '../components/SinleMention' +import AssignMatiereToMention from '../components/AssignMatiereToMention' +import AssingMatiereToSemestre from '../components/AssingMatiereToSemestre' +import SingleAnneeScolaire from '../components/SingleAnneeScolaire' +import Parcours from '../components/Parcours' +import ModalExportFichr from '../components/ModalExportFichr' +import Resultat from '../components/Resultat' +import TrancheEcolage from '../components/TrancheEcolage' + +// Use createHashRouter instead of createBrowserRouter because the desktop app is in local machine +const Router = createHashRouter([ + { + path: '/', + element: , + children: [ + { + path: '/', // This will now be accessed as #/ + element: + }, + { + path: '/student', + element: + }, + { + path: '/notes', + element: + }, + { + path: '/addstudent', + element: + }, + { + path: '/single/:id', + element: + }, + { + path: '/addnotes/:id/:niveau/:mention_id/:parcours', + element: + }, + { + path: '/anneescolaire/:id', + element: + }, + { + path: '/asignmatiere/:id', + element: + }, + { + path: '/asignmatieresemestre/:id', + element: + }, + { + path: '/manual', + element: + }, + { + path: '/mention', + element: + }, + { + path: '/addmention', + element: + }, + { + path: '/singlemention/:id', + element: + }, + { + path: '/apropos', + element: + }, + { + path: '/niveau', + element: + }, + { + path: '/addniveau', + element: + }, + { + path: '/para', + element: + }, + { + path: '/admin', + element: + }, + { + path: '/single/notes/:id/:niveau/:scolaire', + element: + }, + { + path: '/exportetudiant', + element: + }, + { + path: '/single/niveau/:id', + element: + }, + { + path: '/matiere', + element: + }, + { + path: '/addmatiere', + element: + }, + { + path: '/addmatiere/import', + element: + }, + { + path: '/singlematiere/:id', + element: + }, + { + path: '/importniveau', + element: + }, + { + path: '/systemenote', + element: + }, + { + path: '/anneescolaire', + element: + }, + { + path: '/addanneescolaire', + element: + }, + { + path: '/noteclass/:niveau/:scolaire', + element: + }, + { + path: '/parcours', + element: + }, + { + path: '/fiche/:matiere_id/:nom', + element: + }, + { + path: '/resultat/:niveau/:scolaire', + element: + }, + { + path: '/tranche/:id', + element: + } + ] + }, + { + path: '/', + element: , + children: [ + { + path: '/login', + element: + }, + { + path: '/forgotpassword', + element: + } + ] + }, + { + path: '/*', + element: + } +]) + +export default Router diff --git a/src/renderer/src/assets/AddNotes.module.css b/src/renderer/src/assets/AddNotes.module.css new file mode 100644 index 0000000..b936f7e --- /dev/null +++ b/src/renderer/src/assets/AddNotes.module.css @@ -0,0 +1,12 @@ +.blockTitle h1 { + font-size: 10px; + font-weight: bold; + color: white; + display: flex; + align-items: center; + gap: 10px; +} +.boxEtudiantsCard { + width: 10%; + padding: 1%; +} diff --git a/src/renderer/src/assets/AddStudent.module.css b/src/renderer/src/assets/AddStudent.module.css new file mode 100644 index 0000000..ded8325 --- /dev/null +++ b/src/renderer/src/assets/AddStudent.module.css @@ -0,0 +1,15 @@ +.blockTitle h1 { + font-size: 20px; + font-weight: bold; + color: white; + display: flex; + align-items: center; + gap: 10px; +} +.boxEtudiantsCard { + width: 100%; + padding: 1%; + display: flex; + align-items: center; + justify-content: center; +} diff --git a/src/renderer/src/assets/AllStyleComponents.module.css b/src/renderer/src/assets/AllStyleComponents.module.css new file mode 100644 index 0000000..c847c73 --- /dev/null +++ b/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%; +} diff --git a/src/renderer/src/assets/Capture.PNG b/src/renderer/src/assets/Capture.PNG new file mode 100644 index 0000000..289dc59 Binary files /dev/null and b/src/renderer/src/assets/Capture.PNG differ diff --git a/src/renderer/src/assets/CarteStudent.module.css b/src/renderer/src/assets/CarteStudent.module.css new file mode 100644 index 0000000..1b641b2 --- /dev/null +++ b/src/renderer/src/assets/CarteStudent.module.css @@ -0,0 +1,86 @@ +body { + margin: 0; + padding: 0; +} +.container { + display: flex; + align-items: center; + justify-content: center; + height: 100vh; + gap: 20px; +} +.cart { + width: 33%; + /* height: 35%; */ + border: solid 1px rgba(0, 0, 0, 0.315); + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.8); /* Adds a soft shadow */ + /* border-radius: 10px; */ + padding: 1px; + background-color: #ffded4; + position: relative; +} +.cart-footer { + position: absolute; + height: 40px; + width: 100%; + bottom: 0; + left: 0; + border-bottom-left-radius: 10px; + border-bottom-right-radius: 10px; + background-color: #ff5a27; +} +.title { + margin: 0; + padding: 0; + text-align: center; + text-transform: uppercase; + z-index: 1; +} +.title h1, +p { + margin: 0; + font-size: 15px; +} +.content { + z-index: 1; + height: 70%; + display: flex; + align-items: center; + justify-content: space-between; +} +.cart_photos { + width: 30%; + text-align: center; +} +.cart_photos img { + border-radius: 50px; + border: solid 2px #ff5a27; + width: 95px; + height: 100px; + object-fit: cover; +} +.cart_info { + width: 60%; + padding-left: 1%; +} +.cart_info p { + font-size: 16px; +} +.qrContent { + display: flex; + align-items: center; + justify-content: space-around; + padding: 2%; +} +.gauche h1, +.droite h1 { + font-size: 17px; + text-align: center; +} +.gauche img, +.droite img { + width: 150px; +} +.droite .qrcodeDroite { + width: 150px; +} diff --git a/src/renderer/src/assets/Dashboard.module.css b/src/renderer/src/assets/Dashboard.module.css new file mode 100644 index 0000000..ba9e6c4 --- /dev/null +++ b/src/renderer/src/assets/Dashboard.module.css @@ -0,0 +1,4 @@ +.display { + background-color: rgba(255, 255, 255, 0.9); + /* margin-bottom: 2%; */ +} diff --git a/src/renderer/src/assets/DefaultLayout.module.css b/src/renderer/src/assets/DefaultLayout.module.css new file mode 100644 index 0000000..a7a712f --- /dev/null +++ b/src/renderer/src/assets/DefaultLayout.module.css @@ -0,0 +1,20 @@ +html, +body { + height: 100%; + margin: 0; + padding: 0; +} + +/* DefaultLayout.css */ +.default_layout { + /* height: 100%; Use full viewport height */ + min-height: 100vh; + overflow-y: auto; + background: url('../assets/background2.jpg') no-repeat center/cover; + /* background-color: #aad4e571; */ +} +.outlet { + padding-left: 55px; + margin-top: 0; + height: 100%; +} diff --git a/src/renderer/src/assets/ExportReleverNote.module.css b/src/renderer/src/assets/ExportReleverNote.module.css new file mode 100644 index 0000000..029e629 --- /dev/null +++ b/src/renderer/src/assets/ExportReleverNote.module.css @@ -0,0 +1,78 @@ +.header { + width: 100%; + display: flex; + align-items: start; + justify-content: space-between; +} +.headerCenter { + text-align: center; + color: black; + line-height: 15px; + width: 60%; +} +.headerCenter h5 { + text-transform: uppercase; + line-height: 12px; + font-size: 14px; +} +.headerCenter p { + font-style: italic; + font-weight: 400; + font-size: 14px; +} +.transition { + border: solid 1px gray; + width: 100%; + padding: 1% 2%; + font-weight: bold; +} +.transition h6 { + font-size: 12px; +} + +/* content */ +.content { + text-align: center; +} +.contentHeader { + color: black; + flex-direction: column; + display: flex; + align-items: center; + justify-content: center; + font-size: 13px; +} +.contentHeader h1 { + font-size: 28px; +} +.contentHeaderList { + font-weight: bold; + font-size: 13px; +} +.contentHeaderList p { + font-size: 13px; + display: flex; + gap: 30px; +} +.ContentTable { + display: flex; + align-items: center; + justify-content: center; +} +.ContentTable table { + border-color: black !important; + font-size: 13px; + margin: 0; +} +.contentFooter { + display: flex; + align-items: end; + flex-direction: column; + margin-top: 5px; +} +.signature { + width: 50%; + display: flex; + flex-direction: column; + gap: 30px; +} diff --git a/src/renderer/src/assets/Home.module.css b/src/renderer/src/assets/Home.module.css new file mode 100644 index 0000000..81a55c8 --- /dev/null +++ b/src/renderer/src/assets/Home.module.css @@ -0,0 +1,78 @@ +.boxEtudiantsCard { + width: 100%; + padding: 1%; + /* margin-top: 12%; */ +} +.cards { + /* width: 30%; */ + justify-content: space-between; + align-items: center; + padding: 0 1%; +} +.nomEtudiants { + font-weight: bold; +} +.header { + color: white; + /* backdrop-filter: blur(5px); */ + /* position: fixed; */ + width: 100%; + z-index: 9; +} +.container { + display: flex; + align-items: center; + justify-content: center; + padding: 1% 2%; + background: linear-gradient(to left, #ffaf01b4, transparent); +} +.blockTitle { + display: flex; + align-items: center; + justify-content: space-between; + padding: 0 3%; +} +.blockTitle h1 { + font-size: 20px; + font-weight: bold; + /* text-transform: uppercase; */ + color: white; +} +.blockTitle button { + font-size: 12px; + display: inline-flex; + gap: 10px; +} +.boxImg { + padding: 2%; + display: flex; + align-items: center; + justify-content: center; +} +.imagePDP { + object-fit: cover; + border: solid 2px rgb(156, 39, 176); +} +.mainHome { + border: solid 1px red; +} +.select:hover { + border-color: rgb(156, 39, 176) !important; +} +.details { + background-color: rgba(128, 128, 128, 0.4); + border-radius: 8px; + padding: 3% 1% 3% 4%; + text-align: left; +} +.details span { + display: block; + line-height: 25px; +} +.cardOverflow { + height: 450px; + transition: 1s ease; +} +.cardOverflow:hover { + transform: scale(1.03); +} diff --git a/src/renderer/src/assets/Login.module.css b/src/renderer/src/assets/Login.module.css new file mode 100644 index 0000000..cafe5c3 --- /dev/null +++ b/src/renderer/src/assets/Login.module.css @@ -0,0 +1,20 @@ +/* .container{ + background: url(bg2.jpg) no-repeat fixed center/cover; + background-color: #08000e; +} */ +.cards { + background-color: #06000aa4 !important; + color: white !important; + box-shadow: 0px 4px 10px rgba(255, 255, 255, 0.2) !important; /* Light white shadow */ + border: solid 1px rgba(245, 245, 245, 0.692); +} +.formulaireLogin { + color: white !important; +} +.formulaireLogin label { + color: white; + font-size: 17px; +} +.formulaireLogin input { + color: white; +} diff --git a/src/renderer/src/assets/Navbar.module.css b/src/renderer/src/assets/Navbar.module.css new file mode 100644 index 0000000..9ae24bd --- /dev/null +++ b/src/renderer/src/assets/Navbar.module.css @@ -0,0 +1,25 @@ +.navnar { + color: rgba(0, 0, 0, 0.7); + background-color: #2d2d2d; + display: flex; + align-items: center; + justify-content: space-between; + padding: 2px 15px; + position: fixed; + width: 100%; + top: 0; + border-bottom: solid 1px white; + color: white; + z-index: 99999; +} +.gauche { + width: 30%; + display: flex; + align-items: center; +} +.droite { + width: 4%; + display: flex; + align-items: center; + justify-content: space-between; +} diff --git a/src/renderer/src/assets/Sidenav.module.css b/src/renderer/src/assets/Sidenav.module.css new file mode 100644 index 0000000..407fca7 --- /dev/null +++ b/src/renderer/src/assets/Sidenav.module.css @@ -0,0 +1,51 @@ +.navbar { + background-color: #2d2d2d; + border-top: solid 1px white; + color: white; + width: 50px; + /* top: 28px; */ + position: fixed; + height: 100%; + /* bottom: 0; */ + display: flex; + align-items: center; + justify-content: space-between; + flex-direction: column; + padding: 0 0 5% 0; + margin: 0; + border-right: solid 1px white; + z-index: 99; +} +.liste1, +.liste2 { + list-style: none; + padding: 0; + margin: 0; + display: flex; + flex-direction: column; + gap: 20px; +} +.liste1 li, +.liste2 li { + font-size: 25px; + cursor: pointer; +} +.nav_link { + color: white; + position: relative; + transition: 0.8s ease; +} +.icon_label { + position: absolute; + top: 0; /* Below the icon */ + left: 60px; + transform: translateX(-50%); + background-color: black; + color: white; + padding: 5px; + border-radius: 4px; + white-space: nowrap; /* Keep the label on one line */ + font-size: 12px; + margin-top: 5px; /* Slight gap between icon and label */ + z-index: 1; +} diff --git a/src/renderer/src/assets/Template carte arriere.pdf b/src/renderer/src/assets/Template carte arriere.pdf new file mode 100644 index 0000000..53c244d Binary files /dev/null and b/src/renderer/src/assets/Template carte arriere.pdf differ diff --git a/src/renderer/src/assets/Template carte frontal.pdf b/src/renderer/src/assets/Template carte frontal.pdf new file mode 100644 index 0000000..c909b4b Binary files /dev/null and b/src/renderer/src/assets/Template carte frontal.pdf differ diff --git a/src/renderer/src/assets/admin.png b/src/renderer/src/assets/admin.png new file mode 100644 index 0000000..5d58ebe Binary files /dev/null and b/src/renderer/src/assets/admin.png differ diff --git a/src/renderer/src/assets/arriere.pdf b/src/renderer/src/assets/arriere.pdf new file mode 100644 index 0000000..2a04b58 Binary files /dev/null and b/src/renderer/src/assets/arriere.pdf differ diff --git a/src/renderer/src/assets/autorisationstage.pdf b/src/renderer/src/assets/autorisationstage.pdf new file mode 100644 index 0000000..b86ba10 Binary files /dev/null and b/src/renderer/src/assets/autorisationstage.pdf differ diff --git a/src/renderer/src/assets/background.jpg b/src/renderer/src/assets/background.jpg new file mode 100644 index 0000000..442ce5a Binary files /dev/null and b/src/renderer/src/assets/background.jpg differ diff --git a/src/renderer/src/assets/background2.jpg b/src/renderer/src/assets/background2.jpg new file mode 100644 index 0000000..d6d518f Binary files /dev/null and b/src/renderer/src/assets/background2.jpg differ diff --git a/src/renderer/src/assets/base.css b/src/renderer/src/assets/base.css new file mode 100644 index 0000000..e497b00 --- /dev/null +++ b/src/renderer/src/assets/base.css @@ -0,0 +1,97 @@ +/* :root { + --ev-c-white: #ffffff; + --ev-c-white-soft: #f8f8f8; + --ev-c-white-mute: #f2f2f2; + + --ev-c-black: #1b1b1f; + --ev-c-black-soft: #222222; + --ev-c-black-mute: #282828; + + --ev-c-gray-1: #515c67; + --ev-c-gray-2: #414853; + --ev-c-gray-3: #32363f; + + --ev-c-text-1: rgba(255, 255, 245, 0.86); + --ev-c-text-2: rgba(235, 235, 245, 0.6); + --ev-c-text-3: rgba(235, 235, 245, 0.38); + + --ev-button-alt-border: transparent; + --ev-button-alt-text: var(--ev-c-text-1); + --ev-button-alt-bg: var(--ev-c-gray-3); + --ev-button-alt-hover-border: transparent; + --ev-button-alt-hover-text: var(--ev-c-text-1); + --ev-button-alt-hover-bg: var(--ev-c-gray-2); +} + +:root { + --color-background: var(--ev-c-black); + --color-background-soft: var(--ev-c-black-soft); + --color-background-mute: var(--ev-c-black-mute); + + --color-text: var(--ev-c-text-1); +} + +*, +*::before, +*::after { + box-sizing: border-box; + margin: 0; + font-weight: normal; +} + +ul { + list-style: none; +} + +body { + min-height: 100vh; + color: var(--color-text); + background: var(--color-background); + line-height: 1.6; + font-family: + Inter, + -apple-system, + BlinkMacSystemFont, + 'Segoe UI', + Roboto, + Oxygen, + Ubuntu, + Cantarell, + 'Fira Sans', + 'Droid Sans', + 'Helvetica Neue', + sans-serif; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} */ +.form-control:focus { + outline: none; + box-shadow: none; + border-color: initial; /* Optional: restore the default border color */ +} +table { + border-collapse: collapse; /* Ensure there is no space between table cells */ + width: 100%; /* Adjust width as needed */ + margin: 0; + padding: 0; +} + +td, +th { + padding: 0; /* Remove padding from table cells */ + margin: 0; /* Ensure no margin inside cells */ + border: 1px solid black; /* Optional: Add border if needed */ + text-align: center; /* Center text horizontally */ + vertical-align: middle; /* Center text vertically */ +} + +tr { + margin: 0; + padding: 0; +} + +tbody { + margin: 0; + padding: 0; +} diff --git a/src/renderer/src/assets/bg1-min.jpg b/src/renderer/src/assets/bg1-min.jpg new file mode 100644 index 0000000..19699ea Binary files /dev/null and b/src/renderer/src/assets/bg1-min.jpg differ diff --git a/src/renderer/src/assets/bg1.jpg b/src/renderer/src/assets/bg1.jpg new file mode 100644 index 0000000..72c8c7d Binary files /dev/null and b/src/renderer/src/assets/bg1.jpg differ diff --git a/src/renderer/src/assets/bg2-min.jpg b/src/renderer/src/assets/bg2-min.jpg new file mode 100644 index 0000000..f5c0690 Binary files /dev/null and b/src/renderer/src/assets/bg2-min.jpg differ diff --git a/src/renderer/src/assets/bg2.jpg b/src/renderer/src/assets/bg2.jpg new file mode 100644 index 0000000..f393c60 Binary files /dev/null and b/src/renderer/src/assets/bg2.jpg differ diff --git a/src/renderer/src/assets/business_card_template_001.pdf b/src/renderer/src/assets/business_card_template_001.pdf new file mode 100644 index 0000000..a83845f Binary files /dev/null and b/src/renderer/src/assets/business_card_template_001.pdf differ diff --git a/src/renderer/src/assets/business_card_template_001_form.pdf b/src/renderer/src/assets/business_card_template_001_form.pdf new file mode 100644 index 0000000..a83845f Binary files /dev/null and b/src/renderer/src/assets/business_card_template_001_form.pdf differ diff --git a/src/renderer/src/assets/business_card_template_002.pdf b/src/renderer/src/assets/business_card_template_002.pdf new file mode 100644 index 0000000..7581bf1 Binary files /dev/null and b/src/renderer/src/assets/business_card_template_002.pdf differ diff --git a/src/renderer/src/assets/carte_etudiant.odg b/src/renderer/src/assets/carte_etudiant.odg new file mode 100644 index 0000000..15bdd1a Binary files /dev/null and b/src/renderer/src/assets/carte_etudiant.odg differ diff --git a/src/renderer/src/assets/carte_etudiant.pdf b/src/renderer/src/assets/carte_etudiant.pdf new file mode 100644 index 0000000..ab1983b Binary files /dev/null and b/src/renderer/src/assets/carte_etudiant.pdf differ diff --git a/src/renderer/src/assets/certificat scolariter.pdf b/src/renderer/src/assets/certificat scolariter.pdf new file mode 100644 index 0000000..754bcbe Binary files /dev/null and b/src/renderer/src/assets/certificat scolariter.pdf differ diff --git a/src/renderer/src/assets/electron.svg b/src/renderer/src/assets/electron.svg new file mode 100644 index 0000000..45ef09c --- /dev/null +++ b/src/renderer/src/assets/electron.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/renderer/src/assets/enteterelever.png b/src/renderer/src/assets/enteterelever.png new file mode 100644 index 0000000..6876bef Binary files /dev/null and b/src/renderer/src/assets/enteterelever.png differ diff --git a/src/renderer/src/assets/error.svg b/src/renderer/src/assets/error.svg new file mode 100644 index 0000000..7888679 --- /dev/null +++ b/src/renderer/src/assets/error.svg @@ -0,0 +1,52 @@ + + + + + diff --git a/src/renderer/src/assets/exemplecarte.png b/src/renderer/src/assets/exemplecarte.png new file mode 100644 index 0000000..517d61c Binary files /dev/null and b/src/renderer/src/assets/exemplecarte.png differ diff --git a/src/renderer/src/assets/logo or.png b/src/renderer/src/assets/logo or.png new file mode 100644 index 0000000..1602696 Binary files /dev/null and b/src/renderer/src/assets/logo or.png differ diff --git a/src/renderer/src/assets/logo.PNG b/src/renderer/src/assets/logo.PNG new file mode 100644 index 0000000..203f31c Binary files /dev/null and b/src/renderer/src/assets/logo.PNG differ diff --git a/src/renderer/src/assets/logo.ico b/src/renderer/src/assets/logo.ico new file mode 100644 index 0000000..f592b3c Binary files /dev/null and b/src/renderer/src/assets/logo.ico differ diff --git a/src/renderer/src/assets/logo.webp b/src/renderer/src/assets/logo.webp new file mode 100644 index 0000000..2fe582e Binary files /dev/null and b/src/renderer/src/assets/logo.webp differ diff --git a/src/renderer/src/assets/logo_uni-removebg-preview.webp b/src/renderer/src/assets/logo_uni-removebg-preview.webp new file mode 100644 index 0000000..87cadb5 Binary files /dev/null and b/src/renderer/src/assets/logo_uni-removebg-preview.webp differ diff --git a/src/renderer/src/assets/logorelever.png b/src/renderer/src/assets/logorelever.png new file mode 100644 index 0000000..952d3a2 Binary files /dev/null and b/src/renderer/src/assets/logorelever.png differ diff --git a/src/renderer/src/assets/logorelever2.png b/src/renderer/src/assets/logorelever2.png new file mode 100644 index 0000000..8c5cc64 Binary files /dev/null and b/src/renderer/src/assets/logorelever2.png differ diff --git a/src/renderer/src/assets/main.css b/src/renderer/src/assets/main.css new file mode 100644 index 0000000..445130c --- /dev/null +++ b/src/renderer/src/assets/main.css @@ -0,0 +1,163 @@ +@import './base.css'; + +body { + display: flex; + align-items: center; + justify-content: center; + overflow: hidden; + background-image: url('./wavy-lines.svg'); + background-size: cover; + user-select: none; +} + +code { + font-weight: 600; + padding: 3px 5px; + border-radius: 2px; + background-color: var(--color-background-mute); + font-family: + ui-monospace, + SFMono-Regular, + SF Mono, + Menlo, + Consolas, + Liberation Mono, + monospace; + font-size: 85%; +} + +#root { + display: flex; + align-items: center; + justify-content: center; + flex-direction: column; + margin-bottom: 80px; +} + +.logo { + margin-bottom: 20px; + -webkit-user-drag: none; + height: 128px; + width: 128px; + will-change: filter; + transition: filter 300ms; +} + +.logo:hover { + filter: drop-shadow(0 0 1.2em #6988e6aa); +} + +.creator { + font-size: 14px; + line-height: 16px; + color: var(--ev-c-text-2); + font-weight: 600; + margin-bottom: 10px; +} + +.text { + font-size: 28px; + color: var(--ev-c-text-1); + font-weight: 700; + line-height: 32px; + text-align: center; + margin: 0 10px; + padding: 16px 0; +} + +.tip { + font-size: 16px; + line-height: 24px; + color: var(--ev-c-text-2); + font-weight: 600; +} + +.react { + background: -webkit-linear-gradient(315deg, #087ea4 55%, #7c93ee); + background-clip: text; + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + font-weight: 700; +} + +.actions { + display: flex; + padding-top: 32px; + margin: -6px; + flex-wrap: wrap; + justify-content: flex-start; +} + +.action { + flex-shrink: 0; + padding: 6px; +} + +.action a { + cursor: pointer; + text-decoration: none; + display: inline-block; + border: 1px solid transparent; + text-align: center; + font-weight: 600; + white-space: nowrap; + border-radius: 20px; + padding: 0 20px; + line-height: 38px; + font-size: 14px; + border-color: var(--ev-button-alt-border); + color: var(--ev-button-alt-text); + background-color: var(--ev-button-alt-bg); +} + +.action a:hover { + border-color: var(--ev-button-alt-hover-border); + color: var(--ev-button-alt-hover-text); + background-color: var(--ev-button-alt-hover-bg); +} + +.versions { + position: absolute; + bottom: 30px; + margin: 0 auto; + padding: 15px 0; + font-family: 'Menlo', 'Lucida Console', monospace; + display: inline-flex; + overflow: hidden; + align-items: center; + border-radius: 22px; + background-color: #202127; + backdrop-filter: blur(24px); +} + +.versions li { + display: block; + float: left; + border-right: 1px solid var(--ev-c-gray-1); + padding: 0 20px; + font-size: 14px; + line-height: 14px; + opacity: 0.8; + &:last-child { + border: none; + } +} + +@media (max-width: 720px) { + .text { + font-size: 20px; + } +} + +@media (max-width: 620px) { + .versions { + display: none; + } +} + +@media (max-width: 350px) { + .tip, + .actions { + display: none; + } +} diff --git a/src/renderer/src/assets/manuel.pdf b/src/renderer/src/assets/manuel.pdf new file mode 100644 index 0000000..b624e57 Binary files /dev/null and b/src/renderer/src/assets/manuel.pdf differ diff --git a/src/renderer/src/assets/para.png b/src/renderer/src/assets/para.png new file mode 100644 index 0000000..94c9bd0 Binary files /dev/null and b/src/renderer/src/assets/para.png differ diff --git a/src/renderer/src/assets/preloader.jpg b/src/renderer/src/assets/preloader.jpg new file mode 100644 index 0000000..03edfaa Binary files /dev/null and b/src/renderer/src/assets/preloader.jpg differ diff --git a/src/renderer/src/assets/recepice.pdf b/src/renderer/src/assets/recepice.pdf new file mode 100644 index 0000000..c2e1cd4 Binary files /dev/null and b/src/renderer/src/assets/recepice.pdf differ diff --git a/src/renderer/src/assets/releves.pdf b/src/renderer/src/assets/releves.pdf new file mode 100644 index 0000000..46a9332 Binary files /dev/null and b/src/renderer/src/assets/releves.pdf differ diff --git a/src/renderer/src/assets/success.svg b/src/renderer/src/assets/success.svg new file mode 100644 index 0000000..5f4e178 --- /dev/null +++ b/src/renderer/src/assets/success.svg @@ -0,0 +1,52 @@ + + + + + diff --git a/src/renderer/src/assets/template.png b/src/renderer/src/assets/template.png new file mode 100644 index 0000000..4729a9a Binary files /dev/null and b/src/renderer/src/assets/template.png differ diff --git a/src/renderer/src/assets/template2.png b/src/renderer/src/assets/template2.png new file mode 100644 index 0000000..bd39be7 Binary files /dev/null and b/src/renderer/src/assets/template2.png differ diff --git a/src/renderer/src/assets/teste.pdf b/src/renderer/src/assets/teste.pdf new file mode 100644 index 0000000..d407be6 Binary files /dev/null and b/src/renderer/src/assets/teste.pdf differ diff --git a/src/renderer/src/assets/warning.svg b/src/renderer/src/assets/warning.svg new file mode 100644 index 0000000..74afebf --- /dev/null +++ b/src/renderer/src/assets/warning.svg @@ -0,0 +1,65 @@ + + + + + + + + diff --git a/src/renderer/src/assets/wavy-lines.svg b/src/renderer/src/assets/wavy-lines.svg new file mode 100644 index 0000000..f625247 --- /dev/null +++ b/src/renderer/src/assets/wavy-lines.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/renderer/src/components/AddAnneeScolaire.jsx b/src/renderer/src/components/AddAnneeScolaire.jsx new file mode 100644 index 0000000..d473830 --- /dev/null +++ b/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.changes) { + 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 = () => ( + + + {status === 200 ? ( + + {' '} + Année universitaire insérer avec succes + + ) : ( + + {' '} + Erreur, veuillez réessayer + + )} + + {status === 200 ? ( + + ) : ( + + )} + + + + ) + + return ( +
+ {modals()} +
+
+
+

+ Ajout Année universitaire +

+ window.history.back()}> + + +
+
+
+ +
+ +
+

+ Creation de nouvelle année universitaire +

+ + + + + + ) + }} + 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 + } + }} + /> + + + + + + ) + }} + 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 + } + }} + /> + + + + + + ) + }} + 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 + } + }} + /> + + + + + +
+
+
+
+ ) +} + +export default AddAnneeScolaire diff --git a/src/renderer/src/components/AddMatiere.jsx b/src/renderer/src/components/AddMatiere.jsx new file mode 100644 index 0000000..d0e8ce2 --- /dev/null +++ b/src/renderer/src/components/AddMatiere.jsx @@ -0,0 +1,413 @@ +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, useNavigate } from 'react-router-dom' +import { IoMdReturnRight } from 'react-icons/io' +import { + Box, + Button, + InputAdornment, + Typography, + Modal, + TextField, + Grid, + Autocomplete +} from '@mui/material' +import { BsBookmarkPlusFill } from 'react-icons/bs' +import { FaBook, FaClipboardList, FaClock, FaFileExcel } from 'react-icons/fa' +import validationMatiereAdd from './validation/ValidationMatiereAdd' +import svgSuccess from '../assets/success.svg' +import ChangeCapitalize from './function/ChangeCapitalizeLetter' +import { MdOutlineNumbers } from 'react-icons/md' +import { IoBookmark } from 'react-icons/io5' +import ChangeCapital from './function/ChangeCapitalLetter' +import { CgPathUnite } from 'react-icons/cg' + +const AddMatiere = () => { + const [formData, setFormData] = useState({ + nom: '', + credit: '', + uniter: '', + ue: '' + }) + + const navigate = useNavigate() + + const [matieres, setMatieres] = useState([]) + const [mention, setMention] = useState([]) + + useEffect(() => { + window.matieres.getMatiere().then((response) => { + setMatieres(response) + }) + + window.mention.getMention().then((response) => { + setMention(Array.isArray(response) ? response : []) + }) + }, []) + + /** + * hook to open modal + */ + const [open, setOpen] = useState(false) + + /** + * function to close modal + */ + const handleClose = () => { + setOpen(false) + navigate('/matiere') + } + + /** + * function to set the data in state + * @param {*} e + */ + const handleInputChange = (e) => { + const { name, value } = e.target + setFormData((prevData) => ({ + ...prevData, + [name]: value + })) + } + + const nomRef = useRef() + const errorRef = useRef() + const creditRef = useRef() + const ueRef = useRef() + const uniterRef = useRef() + + const formSubmit = async (e) => { + e.preventDefault() + + let arrayMatiere = [] + + matieres.map((matiere) => { + arrayMatiere.push(matiere.nom) + }) + + let valid = validationMatiereAdd( + nomRef.current, + errorRef.current, + arrayMatiere, + creditRef.current + ) + console.log(formData, valid) + if (valid) { + let response = await window.matieres.createMatiere(formData) + + if (response.changes == 1) { + setOpen(true) + } + + if (response.code) { + errorRef.current.textContent = `${formData.nom} existe déjà` + } + } + } + + /** + * function to return the view Modal + * + * @returns {JSX} + */ + const modals = () => ( + + + + {' '} + Matière insérer avec succes + + + + + + + ) + + return ( +
+ {modals()} +
+
+
+

+ + Ajout Matière +

+ + + +
+
+
+ + {/* displaying the formulaire */} +
+ +
+ + Importer un fichier excel + +
+

+ Ajout d'un Matière +

+ +
+ + + ChangeCapitalize(nomRef)} + InputProps={{ + startAdornment: ( + + + + ) + }} + inputRef={nomRef} + sx={{ + marginBottom: '5px', + '& .MuiOutlinedInput-root': { + '&:hover fieldset': { + borderColor: '#ff9800' // Set the border color on hover + } + } + }} + /> + + + + + + ) + }} + inputProps={{ min: 1 }} + inputRef={creditRef} + sx={{ + marginBottom: '5px', + '& .MuiOutlinedInput-root': { + '&:hover fieldset': { + borderColor: '#ff9800' // Set the border color on hover + } + } + }} + /> + + {/* + + + + ), + }} + inputProps={{ min: 1 }} + inputRef={semestreRef} + sx={{ + marginBottom:"5px", + '& .MuiOutlinedInput-root': { + '&:hover fieldset': { + borderColor: '#ff9800', // Set the border color on hover + }, + }, + }} + /> + */} + + ChangeCapital(uniterRef)} + InputProps={{ + startAdornment: ( + + + + ) + }} + inputProps={{ min: 1 }} + inputRef={uniterRef} + sx={{ + marginBottom: '5px', + '& .MuiOutlinedInput-root': { + '&:hover fieldset': { + borderColor: '#ff9800' // Set the border color on hover + } + } + }} + /> + + + ChangeCapital(ueRef)} + onChange={handleInputChange} + type="text" + InputProps={{ + startAdornment: ( + + + + ) + }} + inputProps={{ min: 1 }} + inputRef={ueRef} + sx={{ + marginBottom: '5px', + '& .MuiOutlinedInput-root': { + '&:hover fieldset': { + borderColor: '#ff9800' // Set the border color on hover + } + } + }} + /> + + {/* + option.nom || ""} // Safely access `nom` + value={formData.mention_id.map(id => mention.find(item => item.id === id)) || []} // Bind selected values to form data + onChange={(event, newValue) => { + setFormData((prevData) => ({ + ...prevData, + mention_id: newValue.map((item) => item.id), // Store only the IDs of selected mentions + })); + }} + isOptionEqualToValue={(option, value) => option.id === value.id} // Ensure correct matching of options + renderInput={(params) => ( + + + + + {params.InputProps.startAdornment} + + ), + }} + sx={{ + marginBottom: "5px", + '& .MuiOutlinedInput-root': { + '&:hover fieldset': { + borderColor: '#ff9800', // Set border color on hover + }, + }, + }} + /> + )} + /> + + */} + + +
+ + + +
+
+
+
+
+ ) +} + +export default AddMatiere diff --git a/src/renderer/src/components/AddMention.jsx b/src/renderer/src/components/AddMention.jsx new file mode 100644 index 0000000..34dcc34 --- /dev/null +++ b/src/renderer/src/components/AddMention.jsx @@ -0,0 +1,257 @@ +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, useNavigate } from 'react-router-dom' +import { IoMdReturnRight } from 'react-icons/io' +import { Box, Button, InputAdornment, Typography, Modal, TextField, Grid } from '@mui/material' +import { BsBookmarkPlusFill } from 'react-icons/bs' +import { FaClipboardList } from 'react-icons/fa6' +import ChangeCapital from './function/ChangeCapitalLetter' +import { IoBookmark } from 'react-icons/io5' +import svgSuccess from '../assets/success.svg' + +const AddMention = () => { + const [formData, setFormData] = useState({ + nom: '', + uniter: '' + }) + + const [errors, setErrors] = useState({ + nom: false, + uniter: false + }) + + const navigate = useNavigate() + + /** + * function to set the data in state + * @param {*} e + */ + const handleInputChange = (e) => { + const { name, value } = e.target + setFormData({ + ...formData, + [name]: value + }) + setErrors({ + ...errors, + [name]: false // Reset the error when user starts typing + }) + } + + const formSubmit = async (e) => { + e.preventDefault() + const newErrors = {} + let hasError = false + + // Check for empty fields + Object.keys(formData).forEach((key) => { + if (!formData[key].trim()) { + newErrors[key] = true // Set error for empty fields + hasError = true + } + }) + + setErrors(newErrors) + + if (!hasError) { + try { + let response = await window.mention.createMention(formData) + + if (response.changes) { + setOpen(true) + } + } catch (error) { + console.log(error) + } + } + } + + /** + * hook to open modal + */ + const [open, setOpen] = useState(false) + + /** + * function to close modal + */ + const handleClose = () => { + setOpen(false) + navigate('/mention') + } + + /** + * function to return the view Modal + * + * @returns {JSX} + */ + const modals = () => ( + + + + {' '} + Mention insérer avec succes + + + + + + + ) + + // Helper function to get helperText dynamically + const getHelperText = (field) => (errors[field] ? 'Ce champ est requis.' : '') + + const nomRef = useRef() + const uniterRef = useRef() + return ( +
+ {modals()} +
+
+
+

+ + Ajout Mention +

+ + + +
+
+
+ + {/* displaying the formulaire */} +
+ +

+ Ajout d'un Mention +

+ +
+ + + ChangeCapital(nomRef)} + InputProps={{ + startAdornment: ( + + + + ) + }} + helperText={getHelperText('nom')} + inputRef={nomRef} + sx={{ + marginBottom: '5px', + '& .MuiOutlinedInput-root': { + '&:hover fieldset': { + borderColor: '#ff9800' // Set the border color on hover + } + } + }} + /> + + + ChangeCapital(uniterRef)} + InputProps={{ + startAdornment: ( + + + + ) + }} + inputRef={uniterRef} + sx={{ + marginBottom: '5px', + '& .MuiOutlinedInput-root': { + '&:hover fieldset': { + borderColor: '#ff9800' // Set the border color on hover + } + } + }} + /> + + + + + +
+
+
+
+
+ ) +} + +export default AddMention diff --git a/src/renderer/src/components/AddNiveau.jsx b/src/renderer/src/components/AddNiveau.jsx new file mode 100644 index 0000000..0282680 --- /dev/null +++ b/src/renderer/src/components/AddNiveau.jsx @@ -0,0 +1,217 @@ +import React, { useEffect, useRef, useState } from 'react' +import classe from '../assets/AllStyleComponents.module.css' +import classeAdd from '../assets/AddStudent.module.css' +import { Link, useNavigate } from 'react-router-dom' +import { IoMdPersonAdd, IoMdReturnRight } from 'react-icons/io' +import { FaFileExcel, FaUser } from 'react-icons/fa' +import { Box, Button, InputAdornment, TextField, Grid, Modal, Typography } from '@mui/material' +import classeHome from '../assets/Home.module.css' +import validationNote from './validation/AddNiveau' +import svgSuccess from '../assets/success.svg' + +const AddNiveau = () => { + const navigate = useNavigate() + /** + * hook for storing data in the input + */ + const [formData, setFormData] = useState({ + nom: '' + }) + const [niveau, setNiveau] = useState([]) + /** + * hook to open modal + */ + const [open, setOpen] = useState(false) + + /** + * function to close modal + */ + const handleClose = () => { + setOpen(false) + navigate('/niveau') + } + + useEffect(() => { + window.niveaus.getNiveau().then((response) => { + setNiveau(response) + }) + }, []) + + /** + * function to set the data in state + * @param {*} e + */ + const handleInputChange = (e) => { + const { name, value } = e.target + setFormData((prevData) => ({ + ...prevData, + [name]: value + })) + } + + const nomRef = useRef() + const nomError = useRef() + + /** + * function to return the view Modal + * + * @returns {JSX} + */ + const modals = () => ( + + + + {' '} + Insertion a été effectuée avec succès + + + + + + + ) + + const formSubmit = async (e) => { + e.preventDefault() + let niveauNom = [] + niveau.map((niv) => { + niveauNom.push(niv.nom) + }) + let validation = validationNote(nomRef.current, nomError.current, niveauNom) + + if (validation) { + let response = await window.niveaus.insertNiveau(formData) + let responses = JSON.parse(response) + if (responses.changes == 1) { + setOpen(true) + setFormData({ + nom: '' + }) + } + + if (response.code) { + nomError.current.textContent = `${formData.nom} existe déjà` + } + } + } + + return ( +
+ {modals()} +
+
+
+

+ + Ajout niveau +

+ + + +
+
+
+ +
+ +

+ Ajout d'un niveau +

+
+ + Importer un fichier excel + +
+ +
+ + + + ) + }} + inputRef={nomRef} + sx={{ + marginBottom: '5px', + '& .MuiOutlinedInput-root': { + '&:hover fieldset': { + borderColor: '#ff9800' // Set the border color on hover + } + } + }} + /> + +
+ {/* Submit Button */} + + + + +
+
+
+
+ ) +} + +export default AddNiveau diff --git a/src/renderer/src/components/AddNotes.jsx b/src/renderer/src/components/AddNotes.jsx new file mode 100644 index 0000000..9424dd0 --- /dev/null +++ b/src/renderer/src/components/AddNotes.jsx @@ -0,0 +1,312 @@ +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) + }) + 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.changes) { + 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) + } + } + + 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 = () => ( + + + {statut === 200 ? ( + + {' '} + + Note de {etudiants.nom} {etudiants.prenom} en {etudiants.niveau} a été inserer avec + succès + + + ) : ( + + {' '} + Inserer au moin un champ + + )} + + {statut == 200 ? ( + + ) : ( + + )} + + + + ) + + return ( +
+ {modals()} + +
+
+
+

+ + Ajout note +

+ + + +
+
+ + {/* displaying */} +
+ + +
+

+ Ajout des notes :{' '} + + {etudiants.nom} {etudiants.prenom} en {etudiants.niveau} + +

+ + {/* map the all matiere to the form */} + + {matieres.map((mat) => ( + + setFormData({ ...formData, [mat.id]: e.target.value }) // Update the specific key + } + InputProps={{ + startAdornment: ( + + + + ) + }} + 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 + } + }} + /> + + ))} + + + + +
+
+
+
+
+
+ ) +} + +export default AddNotes diff --git a/src/renderer/src/components/AddParcours.jsx b/src/renderer/src/components/AddParcours.jsx new file mode 100644 index 0000000..6e3879e --- /dev/null +++ b/src/renderer/src/components/AddParcours.jsx @@ -0,0 +1,162 @@ +import React, { useEffect, useRef, useState } from 'react' +import { + Dialog, + DialogActions, + DialogContent, + DialogTitle, + TextField, + Button, + Autocomplete, + InputAdornment, + Box, + Grid +} from '@mui/material' +import { MdRule } from 'react-icons/md' +import { FaClipboardList } from 'react-icons/fa' + +const AddParcours = ({ open, onClose, onSubmitSuccess }) => { + const [formData, setFormData] = useState({ + nom: '', + uniter: '', + mention_id: '' + }) + + const [mention, setMention] = useState([]) + + useEffect(() => { + window.mention.getMention().then((response) => { + setMention(response) + }) + }, []) + + const handleChange = (e) => { + const { name, value } = e.target + setFormData({ ...formData, [name]: value }) + } + + const handleSubmit = async (e) => { + e.preventDefault() + let response = await window.notesysteme.insertParcours(formData) + + if (response.changes) { + onSubmitSuccess(true) + onClose() // Close the modal after submission + setFormData({ + nom: '', + uniter: '', + mention_id: '' + }) + } + } + + return ( + +
+ Information sur le parcour + + + + + + + + ) + }} + /> + + + + + + ) + }} + /> + + + option.nom || ''} // Safely access `nom` + value={mention.find((item) => item.id === formData.mention_id) || null} // Bind selected value to form data + onChange={(event, newValue) => { + setFormData((prevData) => ({ + ...prevData, + mention_id: newValue ? newValue.id : null // Store the ID of the selected mention + })) + }} + isOptionEqualToValue={(option, value) => option.id === value.id} // Ensure correct matching of options + renderInput={(params) => ( + + + + + {params.InputProps.startAdornment} + + ) + }} + sx={{ + marginBottom: '5px', + '& .MuiOutlinedInput-root': { + '&:hover fieldset': { + borderColor: '#ff9800' // Set border color on hover + } + } + }} + /> + )} + /> + + + + + + + + +
+
+ ) +} + +export default AddParcours diff --git a/src/renderer/src/components/AddStudent.jsx b/src/renderer/src/components/AddStudent.jsx new file mode 100644 index 0000000..c6b30f0 --- /dev/null +++ b/src/renderer/src/components/AddStudent.jsx @@ -0,0 +1,1070 @@ +import React, { useEffect, useRef, useState } from 'react' +import classe from '../assets/AllStyleComponents.module.css' +import classeAdd from '../assets/AddStudent.module.css' +import { IoMdMale, IoMdPersonAdd } from 'react-icons/io' +import { IoMdReturnRight } from 'react-icons/io' +import classeHome from '../assets/Home.module.css' +import { Link, useNavigate } from 'react-router-dom' +import { + Box, + Button, + InputAdornment, + Typography, + Modal, + TextField, + Grid, + Autocomplete, + IconButton +} from '@mui/material' +import { + FaUser, + FaIdBadge, + FaBirthdayCake, + FaGraduationCap, + FaCalendarAlt, + FaFileUpload, + FaFileExcel, + FaClipboardList, + FaPassport, + FaGlobeAfrica, + FaMoneyBillWave, + FaHome, + FaPhone +} from 'react-icons/fa' +import { styled } 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 { OutlinedInput } from '@mui/material' +import svgSuccess from '../assets/success.svg' +import svgError from '../assets/error.svg' +import ChangeCapital from './function/ChangeCapitalLetter' +import ChangeCapitalize from './function/ChangeCapitalizeLetter' +import { MdChangeCircle, MdGrade, MdRule } from 'react-icons/md' +import ModalRecepice from './ModalRecepice' +import { FaLeftLong, FaRightLong } from 'react-icons/fa6' +import { Tooltip } from 'react-tooltip' + +const AddStudent = () => { + const [niveaus, setNiveau] = useState([]) + const [status, setStatus] = useState([]) + const [scolaire, setScolaire] = useState([]) + const [mention, setMention] = useState([]) + const [parcours, setParcours] = useState([]) + + useEffect(() => { + window.niveaus.getNiveau().then((response) => { + setNiveau(response) + }) + + window.statuss.getStatus().then((response) => { + setStatus(response) + }) + + window.anneescolaire.getAnneeScolaire().then((response) => { + setScolaire(response) + }) + + window.mention.getMention().then((response) => { + setMention(response) + }) + + window.notesysteme.getParcours().then((response) => { + setParcours(response) + }) + }, []) + + const navigate = useNavigate() + + /** + * hook for storing data in the input + */ + const [formData, setFormData] = useState({ + nom: '', + prenom: '', + photos: null, + date_de_naissances: '', + niveau: '', + annee_scolaire: '', + status: '', + num_inscription: '', + mention_id: '', + sexe: 'Garçon', + nationaliter: '', + cin: '', + date_delivrence: '', + annee_bacc: '', + serie: '', + boursier: 'oui', + domaine: '', + contact: '', + parcours: '' + }) + + const [dataToSend, setDataToSend] = useState({}) + + useEffect(() => { + setDataToSend({ + mention: compareMention(formData.mention_id), + niveau: formData.niveau, + nomPrenom: formData.nom + ' ' + formData.prenom, + inscri: formData.num_inscription, + annee: formData.annee_scolaire + }) + }, [formData]) + + 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 + } + + /** + * ref for each input + */ + const nomRef = useRef() + const prenomRef = useRef() + const date_de_naissancesRef = useRef() + + /** + * function to set the data in state + * @param {*} e + */ + const handleInputChange = (e) => { + const { name, value } = e.target + setFormData((prevData) => ({ + ...prevData, + [name]: value + })) + } + + const imageVisual = useRef() + + const handleFileChange = (e) => { + let img_file = e.target.files[0] + const reader = new FileReader() + reader.readAsDataURL(img_file) + reader.onload = (ev) => { + const url = ev.target.result + // initialisation de nouvelle imageURL + const image = document.createElement('img') + image.src = url + + // create a new image + image.onload = (event) => { + let canvas = document.createElement('canvas') + let ratio = 250 / event.target.width + canvas.width = 250 + canvas.height = event.target.height * ratio + const context = canvas.getContext('2d') + context.drawImage(image, 0, 0, canvas.width, canvas.height) + + // new url + const new_URL = canvas.toDataURL('image/jpeg', 90) + imageVisual.current.style.display = 'block' + imageVisual.current.src = new_URL + // rendement de l'url a notre variable global + setFormData((prevData) => ({ + ...prevData, + photos: new_URL + })) + } + } + } + + const handleSubmit = async (e) => { + e.preventDefault() + // Handle form submission logic + const response = await window.etudiants.insertEtudiant(formData) + + console.log(response) + if (response.code) { + setCode(422) + setOpen(true) + } + + if (response.changes) { + imageVisual.current.style.display = 'none' + imageVisual.current.src = '' + setCode(200) + setOpen(true) + } + } + + 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 + }) + + /** + * hook to open modal + */ + const [open, setOpen] = useState(false) + const [open2, setOpen2] = useState(false) + const [code, setCode] = useState(200) + + /** + * function to close modal + */ + const handleClose = () => { + setOpen2(false) + navigate('/student') + } + const handleOpen3 = () => { + setOpenModal3(true) + } + + const handleClose2 = () => { + setOpen(false) + } + + /** + * function to return the view Modal + * + * @returns {JSX} + */ + const modals = () => ( + + + {code == 422 ? ( + + {' '} + Vérifier les champs + + ) : ( + + {' '} + Insertion a été effectuée avec succès + + )} + + {code === 200 ? ( + + ) : ( + + )} + + + + ) + + const [openModal3, setOpenModal3] = useState(false) + + const handleClose3 = () => { + navigate('/student') + setOpenModal3(false) + } + + const [page1, setPage1] = useState(true) + const [page2, setPage2] = useState(false) + + const seePage1 = () => { + setPage2(false) + setPage1(true) + } + + const seePage2 = () => { + setPage1(false) + setPage2(true) + } + + return ( +
+ {modals()} + +
+
+
+

+ + Ajout étudiant +

+ + + +
+
+
+
+ +
+ + Importer un fichier excel + +
+ +
+
+ +
+ + {page1 == true && ( + + {/* Nom and Prenom Fields */} + + ChangeCapital(nomRef)} + InputProps={{ + startAdornment: ( + + + + ) + }} + inputRef={nomRef} + sx={{ + '& .MuiOutlinedInput-root': { + '&:hover fieldset': { + borderColor: '#ff9800' // Set the border color on hover + } + } + }} + /> + + + ChangeCapitalize(prenomRef)} + InputProps={{ + startAdornment: ( + + + + ) + }} + inputRef={prenomRef} + sx={{ + '& .MuiOutlinedInput-root': { + '&:hover fieldset': { + borderColor: '#ff9800' // Set the border color on hover + } + } + }} + /> + + + {/* Date de Naissance and Niveau Fields */} + + + + + ) + }} + inputRef={date_de_naissancesRef} + sx={{ + '& .MuiOutlinedInput-root': { + '&:hover fieldset': { + borderColor: '#ff9800' // Set the border color on hover + } + } + }} + /> + + + + + + ) + }} + sx={{ + '& .MuiOutlinedInput-root': { + '&:hover fieldset': { + borderColor: '#ff9800' // Set the border color on hover + } + } + }} + /> + + + + + Année Universitaire + + + + + + + + Niveau + + + + + + + + Status + + + + + + option.nom || ''} // Safely access `nom` + value={mention.find((item) => item.id === formData.mention_id) || null} // Bind selected value to form data + onChange={(event, newValue) => { + setFormData((prevData) => ({ + ...prevData, + mention_id: newValue ? newValue.id : null // Store the ID of the selected mention + })) + }} + size="small" // Make the Autocomplete small + isOptionEqualToValue={(option, value) => option.id === value.id} // Ensure correct matching of options + renderInput={(params) => ( + + + + + {params.InputProps.startAdornment} + + ) + }} + sx={{ + marginBottom: '5px', + '& .MuiOutlinedInput-root': { + '&:hover fieldset': { + borderColor: '#ff9800' // Set border color on hover + } + } + }} + /> + )} + /> + + + {/* Photos Field */} + + + + + )} + {page2 && ( + + + + + + ) + }} + sx={{ + '& .MuiOutlinedInput-root': { + '&:hover fieldset': { + borderColor: '#ff9800' // Set the border color on hover + } + } + }} + /> + + + + + + ) + }} + sx={{ + '& .MuiOutlinedInput-root': { + '&:hover fieldset': { + borderColor: '#ff9800' // Set the border color on hover + } + } + }} + /> + + + + + Sexe + + + + + + + + + ) + }} + sx={{ + '& .MuiOutlinedInput-root': { + '&:hover fieldset': { + borderColor: '#ff9800' // Set the border color on hover + } + } + }} + /> + + + + + + ) + }} + sx={{ + '& .MuiOutlinedInput-root': { + '&:hover fieldset': { + borderColor: '#ff9800' // Set the border color on hover + } + } + }} + /> + + + + + + ) + }} + sx={{ + '& .MuiOutlinedInput-root': { + '&:hover fieldset': { + borderColor: '#ff9800' // Set the border color on hover + } + } + }} + /> + + + + + Boursier + + + + + + + + + ) + }} + sx={{ + '& .MuiOutlinedInput-root': { + '&:hover fieldset': { + borderColor: '#ff9800' // Set the border color on hover + } + } + }} + /> + + + + + + ) + }} + sx={{ + '& .MuiOutlinedInput-root': { + '&:hover fieldset': { + borderColor: '#ff9800' // Set the border color on hover + } + } + }} + /> + + + option.nom || ''} // Display `nom` + value={parcours.find((item) => item.nom === formData.parcours) || null} // Find selected option based on `nom` + onChange={(event, newValue) => { + setFormData((prevData) => ({ + ...prevData, + parcours: newValue ? newValue.nom : '' // Store only `nom` + })) + }} + size="small" + isOptionEqualToValue={(option, value) => option.nom === value?.nom} // Ensure correct matching + renderInput={(params) => ( + + + + + {params.InputProps.startAdornment} + + ) + }} + sx={{ + marginBottom: '5px', + '& .MuiOutlinedInput-root': { + '&:hover fieldset': { + borderColor: '#ff9800' // Set border color on hover + } + } + }} + /> + )} + /> + + + )} + {/* Submit Button */} + +
+ + + + + Page précédents + + + + + + Page suivants + +
+ +
+
+
+
+
+
+
+ ) +} + +export default AddStudent diff --git a/src/renderer/src/components/Addadmin.jsx b/src/renderer/src/components/Addadmin.jsx new file mode 100644 index 0000000..bc48d42 --- /dev/null +++ b/src/renderer/src/components/Addadmin.jsx @@ -0,0 +1,335 @@ +import React, { useRef, useState } from 'react' +import classe from '../assets/AllStyleComponents.module.css' +import img from '../assets/admin.png' +import classeHome from '../assets/Home.module.css' +import { Box, Button, InputAdornment, TextField, Grid, Typography, Modal } from '@mui/material' +import classeAdd from '../assets/AddStudent.module.css' +import validationAddAdmin from './validation/AddAdmin' +import svgSuccess from '../assets/success.svg' +import svgError from '../assets/error.svg' +import { FaEnvelope, FaLock, FaUser } from 'react-icons/fa' + +const Admin = () => { + const [formData, setFormData] = useState({ + username: '', + email: '', + password: '', + roles: 'Enseignant' + }) + + const usernameRef = useRef() + const emailRef = useRef() + const passwordRef = useRef() + const rolesRef = useRef() + const errorUsername = useRef() + const errorEmail = useRef() + const errorPassword = useRef() + + /** + * function to set the data in state + * @param {*} e + */ + const handleInputChange = (e) => { + const { name, value } = e.target + setFormData((prevData) => ({ + ...prevData, + [name]: value + })) + } + + const handleSubmit = async (e) => { + e.preventDefault() + // Handle form submission logic + const valid = validationAddAdmin( + usernameRef.current, + emailRef.current, + passwordRef.current, + errorUsername.current, + errorEmail.current, + errorPassword.current + ) + console.log(formData) + console.log(valid) + + if (valid) { + const response = await window.allUser.insertUsers(formData) + console.log(response) + if (response.changes) { + setOpen(true) + setFormData({ + username: '', + email: '', + password: '' + }) + } + + if (response.code) { + setCode(422) + setOpen(true) + } + } + } + + /** + * hook to open modal + */ + const [open, setOpen] = useState(false) + const [code, setCode] = useState(200) + + /** + * function to close modal + */ + const handleClose = () => setOpen(false) + + /** + * function to return the view Modal + * + * @returns {JSX} + */ + const modals = () => ( + + + {code == 422 ? ( + + {' '} + Email déjà pris + + ) : ( + + {' '} + Insertion a été effectuée avec succès + + )} + + + + + + ) + + const sendData = async () => { + await window.syncro.getall() + } + + return ( +
+ {modals()} +
+
+
+

Ajout d'admin

+
+ + +
+
+
+
+ {/* contenu */} +
+
+ + + + + + +
+ + {/* matieres Algebre */} + + + + + ) + }} + inputRef={usernameRef} + sx={{ + '& .MuiOutlinedInput-root': { + '&:hover fieldset': { + borderColor: '#ff9800' // Set the border color on hover + } + } + }} + /> + {/* error username */} + + + {/* matieres Analyse */} + + + + + ) + }} + inputRef={emailRef} + sx={{ + '& .MuiOutlinedInput-root': { + '&:hover fieldset': { + borderColor: '#ff9800' // Set the border color on hover + } + } + }} + /> + {/* error email */} + + + {/* matieres Mecanique general */} + + + + + ) + }} + inputRef={passwordRef} + sx={{ + '& .MuiOutlinedInput-root': { + '&:hover fieldset': { + borderColor: '#ff9800' // Set the border color on hover + } + } + }} + /> + {/* error password */} + + + {/* Matieres Resistance Materiaux */} + + + }} + inputRef={rolesRef} + sx={{ + '& .MuiOutlinedInput-root': { + '&:hover fieldset': { + borderColor: '#ff9800' // Set the border color on hover + } + } + }} + /> + + {/* Submit Button */} + + + + +
+
+
+
+
+
+ ) +} + +export default Admin diff --git a/src/renderer/src/components/AjoutTranche.jsx b/src/renderer/src/components/AjoutTranche.jsx new file mode 100644 index 0000000..91519b2 --- /dev/null +++ b/src/renderer/src/components/AjoutTranche.jsx @@ -0,0 +1,112 @@ +import React, { useState } from 'react' +import { + Dialog, + DialogActions, + DialogContent, + DialogTitle, + TextField, + Button, + Autocomplete, + InputAdornment, + Box, + Grid +} from '@mui/material' +import { MdLabelImportantOutline } from 'react-icons/md' + +const AjoutTranche = ({ open, onClose, onSubmitSuccess, id }) => { + const [formData, setFormData] = useState({ + etudiant_id: id, + tranchename: '', + montant: '' + }) + + const handleChange = (e) => { + const { name, value } = e.target + setFormData({ ...formData, [name]: value }) + } + + const handleSubmit = async (e) => { + e.preventDefault() + let response = await window.etudiants.createTranche(formData) + + if (response.changes) { + onClose() + onSubmitSuccess(true) + setFormData({ + etudiant_id: id, + tranchename: '', + montant: '' + }) + } + } + + return ( + +
+ Ajout tranche + + + + + + + + ) + }} + /> + + + + + + ) + }} + /> + + + + + + + + +
+
+ ) +} + +export default AjoutTranche diff --git a/src/renderer/src/components/AnneeScolaire.jsx b/src/renderer/src/components/AnneeScolaire.jsx new file mode 100644 index 0000000..257ba09 --- /dev/null +++ b/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 universitaire', 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) => ( +
+ + + + { + setIds(params.row.id) + setOpen(true) + }} + > + + +
+ ) + }, + { + field: 'current', + headerName: 'Année en cours', + flex: 1, + renderCell: (params) => ( +
+ + + + Année en cours + + +
+ ) + } + ] + + 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.changes) { + const updatedAnneeScolaire = anneeScolaire.filter((anneeScolaire) => anneeScolaire.id !== id) + setAnneeScolaire(updatedAnneeScolaire) + setIsDeleted(true) + } + } + + const CustomToolbar = () => { + return ( +
+ +
+ ) + } + + /** + * 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 = () => ( + + + {isDeleted ? ( + + Suprimer avec succèss + + ) : ( + + {' '} + Voulez vous supprimer cette année ? + + )} + + {isDeleted ? ( + + ) : ( +
+ + +
+ )} +
+
+
+ ) + + return ( +
+ {modals()} +
+
+
+

+ Année Universitaire +

+ + + +
+
+
+ +
+ + +
+ +
+
+
+
+
+ ) +} + +export default AnneeScolaire diff --git a/src/renderer/src/components/Apropos.jsx b/src/renderer/src/components/Apropos.jsx new file mode 100644 index 0000000..e8e80c6 --- /dev/null +++ b/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 ( +
+
+
+
+

A propos

+ + {/* contenu */} +
+
+ + + + + + +

+ Nom du Logiciel: CUniversity
+
Description : logiciel de gestion d'universiter

+ Createur: CPAY COMPANY FOR MADAGASCAR
+
Licence: A vie
+
Contact: 0348415301 +

+ E-mail: director@c4m.mg +

+
+
+
+
+
+
+
+
+ ) +} + +export default Apropos diff --git a/src/renderer/src/components/AssignMatiereToMention.jsx b/src/renderer/src/components/AssignMatiereToMention.jsx new file mode 100644 index 0000000..1559194 --- /dev/null +++ b/src/renderer/src/components/AssignMatiereToMention.jsx @@ -0,0 +1,203 @@ +import React, { useEffect, useState } from 'react' +import { Link, useParams } from 'react-router-dom' +import { FaPlus } from 'react-icons/fa' +import { Button, Grid, Paper, Checkbox, Modal, Typography, Box } from '@mui/material' +import { IoMdReturnRight } from 'react-icons/io' +import classe from '../assets/AllStyleComponents.module.css' +import classeAdd from '../assets/AddStudent.module.css' +import classeHome from '../assets/Home.module.css' +import svgSuccess from '../assets/success.svg' + +const AssignMatiereToMention = () => { + let { id } = useParams() + + const [mention, setMention] = useState([]) + const [matiere, setMatiere] = useState({}) + const [matiereMention, setMatiereMention] = useState([]) + const [formData, setFormData] = useState({}) + + // Fetch data on component mount + useEffect(() => { + window.matieres.getAsign({ id }).then((response) => { + setMatiereMention(response) // Set matiereMention + }) + window.mention.getMention().then((response) => { + setMention(response) // Set mention + }) + window.matieres.getMatiereByID({ id }).then((response) => { + setMatiere(response) // Set matiere + }) + }, [id]) + + // Initialize formData based on mentions and matiereMention + useEffect(() => { + if (mention.length && matiereMention.length) { + const initialFormData = mention.reduce((acc, mens) => { + // Check if the current mention ID exists in matiereMention + const isChecked = matiereMention.some((item) => item.mention_id === mens.id) + acc[mens.id] = isChecked // Set true if matched, false otherwise + return acc + }, {}) + setFormData(initialFormData) // Update formData + } + }, [mention, matiereMention]) + + /** + * Handle form submission + */ + const formSubmit = async (e) => { + e.preventDefault() + + let response = await window.matieres.asign({ formData, id }) + console.log(response) + if (response.changes) { + setOpen(true) + } + } + + /** + * Handle checkbox change + */ + const handleCheckboxChange = (id) => { + setFormData((prevData) => ({ + ...prevData, + [id]: !prevData[id] // Toggle the checkbox value + })) + } + + /** + * 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 = () => ( + + + + {' '} + Changemet effectuée avec succès + + + + + + + ) + + return ( +
+ {modals()} +
+
+
+

+ Asignation +

+ window.history.back()}> + + +
+
+
+ + +
+
+ Choisissez les mentions qui utilisent {matiere?.nom || 'la matière'} +
+ + {mention.map((mens) => ( + + + + {mens.nom} + + + + handleCheckboxChange(mens.id)} // Handle change + color="success" + /> + + + ))} + + + + +
+
+
+ ) +} + +export default AssignMatiereToMention diff --git a/src/renderer/src/components/AssingMatiereToSemestre.jsx b/src/renderer/src/components/AssingMatiereToSemestre.jsx new file mode 100644 index 0000000..386fda3 --- /dev/null +++ b/src/renderer/src/components/AssingMatiereToSemestre.jsx @@ -0,0 +1,259 @@ +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, useParams } from 'react-router-dom' +import { Button, Grid, Paper, Modal, Typography, Box } from '@mui/material' +import { IoMdReturnRight } from 'react-icons/io' +import { FaPlus } from 'react-icons/fa' +import OutlinedInput from '@mui/material/OutlinedInput' +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 svgSuccess from '../assets/success.svg' + +const AssingMatiereToSemestre = () => { + let { id } = useParams() + + const [mentions, setMentions] = useState([]) + const [matiereSemestre, setMatiereSemestre] = useState([]) + const [matiere, setMatiere] = useState({}) + const [semestre, setSemestre] = useState([]) + + useEffect(() => { + window.matieres.asignSemestre({ id }).then((response) => { + setMentions(response) + }) + + window.matieres.getMatiereByID({ id }).then((response) => { + setMatiere(response) // Set matiere + }) + + window.matieres.getSemestreMatiere({ id }).then((response) => { + setMatiereSemestre(response) // Set matiere + }) + + window.matieres.getSemestre().then((response) => { + setSemestre(response) // Set matiere + }) + }, [id]) + + // State to manage selected semestres for each mention + const [selectedSemestres, setSelectedSemestres] = useState({}) + + // Populate the initial state for selectedSemestres + useEffect(() => { + const initialSelectedSemestres = {} + matiereSemestre.forEach((item) => { + if (!initialSelectedSemestres[item.mention_id]) { + initialSelectedSemestres[item.mention_id] = [] + } + initialSelectedSemestres[item.mention_id].push(item.semestre_id) + }) + setSelectedSemestres(initialSelectedSemestres) + }, [matiereSemestre]) + + // Handle change in Select + const handleChange = (id) => (event) => { + const { + target: { value } + } = event + + // Update state for the specific mention ID + setSelectedSemestres((prevState) => ({ + ...prevState, + [id]: typeof value === 'string' ? value.split(',') : value + })) + } + + const formSubmit = async (e) => { + e.preventDefault() + + let response = await window.matieres.insertUpdateMentionSemestre({ id, selectedSemestres }) + + console.log(response) + if (response.changes) { + 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 = () => ( + + + + {' '} + Changemet effectuée avec succès + + + + + + + ) + + console.log(matiereSemestre) + + // Step 1: Preprocess matiereSemestre to create a mapping + const preprocessSemestres = () => { + const mapping = {} + matiereSemestre.forEach((item) => { + if (item.matiere_id === id) { + // Filter by matiere_id if necessary + if (!mapping[item.mention_id]) { + mapping[item.mention_id] = [] + } + mapping[item.mention_id].push(item.semestre_id) + } + }) + return mapping + } + + return ( +
+ {modals()} +
+
+
+

+ Asignation Semestre +

+ + + +
+
+
+ +
+
+ Choisissez les semestre dans la quelle on enseigne {matiere?.nom || 'la matière'} +
+ + {mentions.map((mens) => ( + +
+
+ + {mens.nom} + +
+
+ + + Semestre + + + +
+
+
+ ))} +
+ + + +
+
+
+ ) +} + +export default AssingMatiereToSemestre diff --git a/src/renderer/src/components/CustomBar.jsx b/src/renderer/src/components/CustomBar.jsx new file mode 100644 index 0000000..7d91015 --- /dev/null +++ b/src/renderer/src/components/CustomBar.jsx @@ -0,0 +1,30 @@ +import React from 'react' +import { + GridToolbarContainer, + GridToolbarFilterButton, + GridToolbarColumnsButton, + GridToolbarExport +} from '@mui/x-data-grid' +import { Button } from '@mui/material' +import { FaCloudUploadAlt } from 'react-icons/fa' + +const CustomBar = ({ onImport }) => { + return ( + + + + {/* Custom import button */} + + + + ) +} + +export default CustomBar diff --git a/src/renderer/src/components/DeleteTranche.jsx b/src/renderer/src/components/DeleteTranche.jsx new file mode 100644 index 0000000..62cc80c --- /dev/null +++ b/src/renderer/src/components/DeleteTranche.jsx @@ -0,0 +1,52 @@ +import React, { useEffect, useState } from 'react' +import { + Dialog, + DialogActions, + DialogContent, + DialogTitle, + Button, + Box, + Typography +} from '@mui/material' + +const DeleteTranche = ({ open, onClose, id, onSubmitSuccess }) => { + const [idDelete, setIdDelete] = useState(null) + + useEffect(() => { + setIdDelete(id) + }, [id]) + + console.log(idDelete) + + const deleteOption = async () => { + if (idDelete !== null) { + const id = idDelete + let response = await window.etudiants.deleteTranche({ id }) + if (response.changes) { + onSubmitSuccess(true) + onClose() + } + } + } + + return ( + + Suppression + + + Voulez vous Supprimer ? + + + + + + + + ) +} + +export default DeleteTranche diff --git a/src/renderer/src/components/ExportEtudiants.jsx b/src/renderer/src/components/ExportEtudiants.jsx new file mode 100644 index 0000000..0f450ac --- /dev/null +++ b/src/renderer/src/components/ExportEtudiants.jsx @@ -0,0 +1,517 @@ +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 () => { + // Code to handle file import (can open a file dialog or call handleFileChange) + + let response = await window.etudiants.importExcel(files.path) + + console.log(response) + + if (response.message) { + setMessage(response.message) + } + + if (response.error) { + setIsinserted(true) + setOpen(true) + setTableData([]) + } else { + 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 = () => ( + + + {isInserted ? ( + + {' '} + Importation a été effectuée avec succès + + ) : ( + + {' '} + + L'importation n'a pas été effectuée +
+ {message} +
+
+ )} + + + +
+
+ ) + + 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 ( +
+ {modals()} +
+
+
+

+ Export +

+ + + +
+
+
+ + + +
+ + +
+ {error && ( + + {error} + + )} + {tableData.length > 0 && ( + +
+ {/* Dropdowns for each column */} + {dynamicColumns.map((col) => ( + + {col.headerName} + + + ))} +
+
+ +
+
+ )} +
+
+
+ ) +} + +export default ExportEtudiants diff --git a/src/renderer/src/components/ForgotPassword.jsx b/src/renderer/src/components/ForgotPassword.jsx new file mode 100644 index 0000000..16f22e7 --- /dev/null +++ b/src/renderer/src/components/ForgotPassword.jsx @@ -0,0 +1,247 @@ +import React, { useRef, useState } from 'react' +import classe from '../assets/Login.module.css' +import { FaEnvelope, FaLock, FaLockOpen } from 'react-icons/fa' +import { Link } from 'react-router-dom' +import { + Modal, + Box, + Container, + Grid, + Card, + Typography, + TextField, + Button, + InputAdornment +} from '@mui/material' +import { validationForgotPassword } from './validation/ForgotPassword' + +const ForgotPassword = () => { + /** + * hook to store data from the input field + */ + const [email, setEmail] = useState() + const [password, setPassword] = useState() + const [passwordConfirmation, setPasswordConfirmation] = useState() + + /** + * hook to open modal and set the sattus and message + */ + const [open, setOpen] = useState(false) + const [message, setMessage] = useState('') + const [status, setStatus] = useState(null) + + const handleClose = () => setOpen(false) + + /** + * ref for our email and password, confirm password input and the error span + */ + const emailRef = useRef() + const passwordRef = useRef() + const passwordConfirmationRef = useRef() + const emailError = useRef() + const passwordError = useRef() + const passwordConfirmationError = useRef() + + /** + * function to return the view Modal + * + * @returns {JSX} + */ + const modals = () => ( + + + + {status === 200 ? 'Success' : 'Error'} + + + {message} + + + + + ) + + /** + * function to send the email verification + * to change the password and redirect the user to login + * + * @param {any} e + */ + const verification = async (e) => { + e.preventDefault() + + let validation = validationForgotPassword( + emailRef.current, + passwordRef.current, + passwordConfirmationRef.current, + emailError.current, + passwordError.current, + passwordConfirmationError.current + ) + console.log(validation) + if (validation === true) { + const response = await window.allUser.forgotPassword({ + email, + password, + passwordConfirmation + }) + + if (response.status === 200) { + setMessage(response.message) + setStatus(200) + } else { + setMessage(response.message) + setStatus(response.status) + } + setOpen(true) + console.log(response) + } + } + + return ( + + {modals()} + + + + + Changer de mot de passe + +
+ + + + ) + }} + sx={{ + '& .MuiOutlinedInput-root': { + '& fieldset': { + borderColor: 'white' // Set the border color when not focused + }, + '&:hover fieldset': { + borderColor: 'rgb(156, 39, 176)' // Set the border color on hover + } + } + }} + placeholder="Entrer email" + onChange={(e) => setEmail(e.target.value)} + inputRef={emailRef} + /> + + + + + + ) + }} + sx={{ + '& .MuiOutlinedInput-root': { + '& fieldset': { + borderColor: 'white' // Set the border color when not focused + }, + '&:hover fieldset': { + borderColor: 'rgb(156, 39, 176)' // Set the border color on hover + } + } + }} + placeholder="Mot de passe" + onChange={(e) => setPassword(e.target.value)} + inputRef={passwordRef} + /> + + + + + + ) + }} + sx={{ + '& .MuiOutlinedInput-root': { + '& fieldset': { + borderColor: 'white' // Set the border color when not focused + }, + '&:hover fieldset': { + borderColor: 'rgb(156, 39, 176)' // Set the border color on hover + } + } + }} + className={classe.input} + placeholder="Confirmation Mot de passe" + onChange={(e) => setPasswordConfirmation(e.target.value)} + inputRef={passwordConfirmationRef} + /> + + + + +
+ + Se connecter + +
+ +
+
+
+
+ ) +} + +export default ForgotPassword diff --git a/src/renderer/src/components/Home.jsx b/src/renderer/src/components/Home.jsx new file mode 100644 index 0000000..5d42279 --- /dev/null +++ b/src/renderer/src/components/Home.jsx @@ -0,0 +1,194 @@ +import React, { useEffect, useState } from 'react' +import classe from '../assets/AllStyleComponents.module.css' +import { Bar } from 'react-chartjs-2' +import { + Chart as ChartJS, + CategoryScale, + LinearScale, + BarElement, + Title, + Tooltip, + Legend +} from 'chart.js' +import classeHome from '../assets/Home.module.css' +import dclass from '../assets/Dashboard.module.css' +import dayjs from 'dayjs' +import MenuItem from '@mui/material/MenuItem' +import FormControl from '@mui/material/FormControl' +import Select from '@mui/material/Select' +import InputLabel from '@mui/material/InputLabel' + +ChartJS.register(CategoryScale, LinearScale, BarElement, Title, Tooltip, Legend) + +const Home = () => { + const [etudiants, setEtudiants] = useState([]) + const [niveau, setNiveau] = useState([]) + const [annee_scolaire, setAnnee_scolaire] = useState([]) + const [originalEtudiants, setOriginalEtudiants] = useState([]) + // Get the current year + const currentYear = dayjs().year() + + useEffect(() => { + // Fetch data and update state + window.etudiants.getDataToDashboards().then((response) => { + setEtudiants(response.etudiants) + setOriginalEtudiants(response.etudiants) + setNiveau(response.niveau) + setAnnee_scolaire(response.anne_scolaire) + }) + }, []) + + // filter all data + + // ordre des colones de filtre + const desiredOrder = ['L1', 'L2', 'L3', 'M1', 'M2', 'D1', 'D2', 'D3'] + let allNiveau = niveau.map((item) => item.nom) + allNiveau.sort((a, b) => desiredOrder.indexOf(a) - desiredOrder.indexOf(b)) + + const studentGrades = {} + + // Loop through allNiveau and set the number of students for each key + allNiveau.forEach((niveauNom) => { + // Filter etudiants based on the matching niveau + const studentCount = etudiants.filter((etudiant) => etudiant.niveau === niveauNom).length + + // Assign the student count as the value for the corresponding key in studentGrades + studentGrades[niveauNom] = studentCount + }) + const studentCounts = Object.values(studentGrades) + + // Find the maximum value using Math.max + const maxStudentCount = Math.max(...studentCounts) + + const FilterAnneeScolaire = (e) => { + let annee_scolaire = e.target.value + const filteredEtudiants = originalEtudiants.filter( + (etudiant) => etudiant.annee_scolaire === annee_scolaire + ) + setEtudiants(filteredEtudiants) + if (annee_scolaire == 'general') { + setEtudiants(originalEtudiants) + } + } + // end filter all data + + // Calculate the number of classes + const numberOfClasses = Object.keys(studentGrades).length + + // Data for the Bar chart + const data = { + labels: Object.keys(studentGrades), // Class levels + datasets: [ + { + label: 'Nombre des étudiants', + data: Object.values(studentGrades), // Student counts + backgroundColor: [ + '#FF6384', + '#36A2EB', + '#FFCE56', + '#4BC0C0', + '#9966FF', + '#FF9F40', + '#C9CBCF', + '#00A36C' + ], // Colors for each bar + borderColor: [ + '#FF6384', + '#36A2EB', + '#FFCE56', + '#4BC0C0', + '#9966FF', + '#FF9F40', + '#C9CBCF', + '#00A36C' + ], + borderWidth: 1 + } + ] + } + + // Chart options + const options = { + responsive: true, + plugins: { + legend: { + position: 'top' + }, + title: { + display: true, + text: `Nombre des niveau (Total : ${numberOfClasses})` + } + }, + scales: { + y: { + beginAtZero: true, + max: maxStudentCount // Set max value for the Y axis + } + } + } + + return ( +
+
+
+
+

Dashboard

+ + + Année Scolaire + + + +
+
+
+ {/* display each bars */} +
+ +
+
+ ) +} + +export default Home diff --git a/src/renderer/src/components/ImportMatiere.jsx b/src/renderer/src/components/ImportMatiere.jsx new file mode 100644 index 0000000..3e956c6 --- /dev/null +++ b/src/renderer/src/components/ImportMatiere.jsx @@ -0,0 +1,489 @@ +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 { Box, Button, Typography, ThemeProvider, Modal } from '@mui/material' +import { Link } from 'react-router-dom' +import { FaCloudDownloadAlt, FaCloudUploadAlt, FaFileExcel } from 'react-icons/fa' +import { IoMdReturnRight } from 'react-icons/io' +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 CustomBar from './CustomBar' +import dayjs from 'dayjs' +import svgSuccess from '../assets/success.svg' +import svgError from '../assets/error.svg' +import { MenuItem, Select, FormControl, InputLabel } from '@mui/material' + +const ImportMatiere = () => { + 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 [error, setError] = useState('') + const [isInserted, setIsinserted] = useState(true) + const [message, setMessage] = useState('') + const [tableData, setTableData] = useState([]) + const [files, setFiles] = useState() + 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 theme = createTheme({ + components: { + MuiIconButton: { + styleOverrides: { + root: { + color: 'gray' // Toolbar icons color + } + } + }, + MuiButton: { + styleOverrides: { + root: { + color: '#121212' // Button text color + } + } + } + } + }) + + 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 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 + 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([]) + } + } + + const exemplaireFileExcel = [ + { + nom: 'matiere 1', + credit: 5, + uniter: 'MUI', + heure: 39 + }, + { + nom: 'matiere 2', + credit: 5, + uniter: 'MUI', + heure: 39 + }, + { + nom: 'matiere 3', + credit: 5, + uniter: 'MUI', + heure: 39 + }, + { + nom: 'matiere 4', + credit: 5, + uniter: 'MUI', + heure: 39 + }, + { + nom: 'matiere 5', + credit: 5, + uniter: 'MUI', + heure: 39 + } + ] + + 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 = 'exemplaier_matiere.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 + } + + /** + * fonction qui envoye dans le back + */ + const handleImport = async () => { + // Code to handle file import (can open a file dialog or call handleFileChange) + + let response = await window.matieres.importExcel(files.path) + + console.log(response) + + if (response.message) { + setMessage(response.message) + } + + if (response.error) { + setIsinserted(true) + setOpen(true) + setTableData([]) + } else { + 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 = () => ( + + + {isInserted ? ( + + {' '} + Importation a été effectuée avec succès + + ) : ( + + {' '} + + L'importation n'a pas été effectuée +
+ {message} +
+
+ )} + + + +
+
+ ) + + // 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 ( +
+ {modals()} +
+
+
+

+ + Importation de données +

+ + + +
+
+
+ + {/* displaying */} + + +
+ + +
+ {error && ( + + {error} + + )} + {tableData.length > 0 && ( + +
+ {/* Dropdowns for each column */} + {dynamicColumns.map((col) => ( + + {col.headerName} + + + ))} +
+
+ +
+
+ )} +
+
+
+ ) +} + +export default ImportMatiere diff --git a/src/renderer/src/components/ImportNiveau.jsx b/src/renderer/src/components/ImportNiveau.jsx new file mode 100644 index 0000000..75221c1 --- /dev/null +++ b/src/renderer/src/components/ImportNiveau.jsx @@ -0,0 +1,397 @@ +import React, { 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 { Box, Button, Typography, ThemeProvider, Modal } from '@mui/material' +import { Link } from 'react-router-dom' +import { FaCloudDownloadAlt, FaCloudUploadAlt, FaFileExcel } from 'react-icons/fa' +import { IoMdReturnRight } from 'react-icons/io' +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 CustomBar from './CustomBar' +import dayjs from 'dayjs' +import svgSuccess from '../assets/success.svg' + +const ImportNiveau = () => { + 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 [error, setError] = useState('') + const [tableData, setTableData] = useState([]) + const [files, setFiles] = useState() + const [header, setHeader] = useState([]) + let field = [] + + for (let index = 0; index < header.length; index++) { + field.push(header[index].toLowerCase().replace(/\s+/g, '_')) + } + + const dynamicColumns = header.map((col, index) => ({ + field: col.toLowerCase().replace(/\s+/g, '_'), // 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 + })) + + const theme = createTheme({ + components: { + MuiIconButton: { + styleOverrides: { + root: { + color: 'gray' // Toolbar icons color + } + } + }, + MuiButton: { + styleOverrides: { + root: { + color: '#121212' // Button text color + } + } + } + } + }) + + 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 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 + 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([]) + } + } + + const exemplaireFileExcel = [ + { + Nom: 'L1' + }, + { + Nom: 'L2' + }, + { + Nom: 'L3' + }, + { + Nom: 'M1' + }, + { + Nom: 'M2' + } + ] + + 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_niveau.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 + } + + /** + * fonction qui envoye dans le back + */ + const handleImport = async () => { + // Code to handle file import (can open a file dialog or call handleFileChange) + + let response = await window.niveaus.importNiveau(files.path) + + console.log(response) + if (response.success) { + setOpen(true) + setTableData([]) + } + } + + /** + * 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 = () => ( + + + + {' '} + Importation a été effectuée avec succès + + + + + + + ) + + return ( +
+ {modals()} +
+
+
+

+ + Importation de données +

+ + + +
+
+
+ + {/* displaying */} + + +
+ + +
+ {error && ( + + {error} + + )} + {tableData.length > 0 && ( + +
+ +
+
+ )} +
+
+
+ ) +} + +export default ImportNiveau diff --git a/src/renderer/src/components/IpConfig.jsx b/src/renderer/src/components/IpConfig.jsx new file mode 100644 index 0000000..a584651 --- /dev/null +++ b/src/renderer/src/components/IpConfig.jsx @@ -0,0 +1,133 @@ +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 IpConfig = ({ open, onClose }) => { + + const [localIp, setLocalIp] = useState(""); + const [formData, setFormData] = useState({ + ipname: "", + id: '', + }) + const [ip, setIp] = useState({}); + + useEffect(() => { + + window.notesysteme.getIPConfig().then((response) => { + setIp(response) + }) + + const getLocalIP = async () => { + const pc = new RTCPeerConnection(); + pc.createDataChannel(""); // Create a data channel + pc.createOffer().then((offer) => pc.setLocalDescription(offer)); + + pc.onicecandidate = (event) => { + if (event && event.candidate) { + const ipRegex = /([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})/; + const match = ipRegex.exec(event.candidate.candidate); + if (match) setLocalIp(match[1]); + pc.close(); + } + }; + }; + + getLocalIP(); + }, []); + + useEffect(() => { + if (ip) { + setFormData((prevData) => ({ + ...prevData, + ipname: ip.ipname, + id: ip.id + })) + } + + }, [ip]); + + const handleChange = (e) => { + const { name, value } = e.target + setFormData({ ...formData, [name]: value }) + } + + const formSubmit = async (e) => { + e.preventDefault() + + if (ip == undefined) { + let response = await window.notesysteme.createIPConfig(formData) + console.log('running the creation: ', response); + if (response.changes) { + window.notesysteme.getIPConfig().then((response) => { + setIp(response) + }) + onClose() + } + } else { + let response = await window.notesysteme.updateIPConfig(formData) + console.log('running the update: ', response); + if (response.changes) { + window.notesysteme.getIPConfig().then((response) => { + setIp(response) + }) + onClose() + } + } + + } + + return ( + +
+ Configuration de l'adresse IP +
Votre Local IP: {localIp || "Detecting..."}
+ + + + + + + + + + + + + +
+
+ ) +} + +export default IpConfig diff --git a/src/renderer/src/components/Login.jsx b/src/renderer/src/components/Login.jsx new file mode 100644 index 0000000..aeded15 --- /dev/null +++ b/src/renderer/src/components/Login.jsx @@ -0,0 +1,157 @@ +import React, { useRef, useState } from 'react' +// import { Container, Row, Col, Form, Button, Card, InputGroup } from 'react-bootstrap'; +import { Container, Grid, Card, Typography, TextField, Button, InputAdornment } from '@mui/material' +import { FaUserCircle, FaLock, FaUser } from 'react-icons/fa' +import classe from '../assets/Login.module.css' +import { useAuthContext } from '../contexts/AuthContext' +import { Link } from 'react-router-dom' +import { ValidationLogin, invalidCredential } from './validation/Login' + +const Login = () => { + /** + * token from the AuthContext in the context folder + */ + const { setToken } = useAuthContext() + + /** + * hook to store our data from the input element + */ + const [username, setUsername] = useState() + const [password, setPassword] = useState() + + /** + * ref for our username and password input and the error span + */ + const userNameRef = useRef() + const passwordRef = useRef() + const userNameError = useRef() + const passwordError = useRef() + + /** + * function to send the data to the IPCRender + * and make login + * + * @param {any} e + */ + const formLogin = async (e) => { + e.preventDefault() + + let validate = ValidationLogin( + userNameRef.current, + passwordRef.current, + userNameError.current, + passwordError.current + ) + + if (validate) { + const response = await window.allUser.login({ username, password }) + + if (response.success) { + // Redirect to main window + setToken(JSON.stringify(response.user)) + } else { + invalidCredential(userNameError.current) + } + } + } + + return ( + + + + +
+ +
+ + Université de Toamasina + +
+ + + + ) + }} + className={classe.input} + onChange={(e) => setUsername(e.target.value)} + inputRef={userNameRef} + sx={{ + '& .MuiOutlinedInput-root': { + '& fieldset': { + borderColor: 'white' // Set the border color when not focused + }, + '&:hover fieldset': { + borderColor: 'rgb(156, 39, 176)' // Set the border color on hover + } + } + }} + /> + + + + + ) + }} + onChange={(e) => setPassword(e.target.value)} + inputRef={passwordRef} + sx={{ + '& .MuiOutlinedInput-root': { + '& fieldset': { + borderColor: 'white' // Set the border color when not focused + }, + '&:hover fieldset': { + borderColor: 'rgb(156, 39, 176)' // Set the border color on hover + } + } + }} + /> + + + + +
+ + Mot de passe oublié ? + +
+ +
+
+
+
+ ) +} + +export default Login diff --git a/src/renderer/src/components/Manuel.jsx b/src/renderer/src/components/Manuel.jsx new file mode 100644 index 0000000..3dff2f2 --- /dev/null +++ b/src/renderer/src/components/Manuel.jsx @@ -0,0 +1,98 @@ +import React, { useState } from 'react' +import { Box, Button, Typography, Grid, Paper } from '@mui/material' +import { GrManual } from 'react-icons/gr' +import { Document, Page } from 'react-pdf/dist/esm/entry.webpack5' +import { FaAngleDoubleLeft, FaAngleDoubleRight } from 'react-icons/fa' +import pdfUrl from '../assets/manuel.pdf' +import classe from '../assets/AllStyleComponents.module.css' +import classeAdd from '../assets/AddStudent.module.css' +import classeHome from '../assets/Home.module.css' + +const Manuel = () => { + const [numPages, setNumPages] = useState(null) // Use correct name + const [pageNumber, setPageNumber] = useState(1) + + // Fix the onDocumentSuccess function + function onDocumentSuccess({ numPages }) { + setNumPages(numPages) // Use the correct property + } + + const prevPage = () => { + if (pageNumber > 1) { + setPageNumber(pageNumber - 1) + } + } + + const nextPage = () => { + if (pageNumber < numPages) { + setPageNumber(pageNumber + 1) + } + } + + return ( +
+
+
+
+

+ Manuel d'utilisation +

+
+
+
+ + + + + + + + Page {pageNumber} sur {numPages} + + +
+ Chargement du Manuel...
} + error={
Une erreur s'est produite lors du chargement du Manuel.
} + > + + +
+ + + + + + + + ) +} + +export default Manuel diff --git a/src/renderer/src/components/Matieres.jsx b/src/renderer/src/components/Matieres.jsx new file mode 100644 index 0000000..2c2586d --- /dev/null +++ b/src/renderer/src/components/Matieres.jsx @@ -0,0 +1,502 @@ +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' + +const Matieres = () => { + const [matiere, setMatiere] = useState([]) + 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 [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: 'enseignant', headerName: 'Professeur actuele', width: 230 }, + { + field: 'action', + headerName: 'Action', + width: 600, + renderCell: (params) => ( +
+ + + + + + + + + Assigner à un semestre + + + + openParcoursFunction(params.value)} + > + + Assigner à des parcours + + + + + + + + + + + + + Fiche de presence éxamen + + + + { + setIds(params.row.id) + setOpen(true) + }} + > + + +
+ ) + } + ] + + 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, + 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.changes) { + 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 = () => ( + + + {isDeleted ? ( + + Suprimer avec succèss + + ) : ( + + {' '} + Voulez vous supprimer ce matiere ? + + )} + + {isDeleted ? ( + + ) : ( +
+ + +
+ )} +
+
+
+ ) + + 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 ( +
+ {modals()} + + + + +
+
+
+

+ + Matiere +

+ + + +
+
+
+ + {/* displaying data */} +
+
+ + +
+ +
+
+
+
+
+
+ ) +} + +export default Matieres diff --git a/src/renderer/src/components/Mentions.jsx b/src/renderer/src/components/Mentions.jsx new file mode 100644 index 0000000..2eb342d --- /dev/null +++ b/src/renderer/src/components/Mentions.jsx @@ -0,0 +1,261 @@ +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 { Box, Button, InputAdornment, Typography, Modal, TextField, Grid } from '@mui/material' +import { BsBookmarkPlusFill } from 'react-icons/bs' +import { FaClipboardList, FaPenToSquare, FaTrash } from 'react-icons/fa6' +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' + +const Mentions = () => { + 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 [isDeleted, setIsDeleted] = useState(false) + const [ids, setIds] = useState(0) + + const [mentions, setMentions] = useState([]) + + useEffect(() => { + window.mention.getMention().then((response) => { + setMentions(response) + }) + }, []) + + const columns = [ + { field: 'nom', headerName: 'Nom', width: 350 }, + { field: 'uniter', headerName: 'Unité ', width: 180 }, + { + field: 'action', + headerName: 'Action', + flex: 1, + renderCell: (params) => ( +
+ + + + { + setIds(params.row.id) + setOpen(true) + }} + > + + +
+ ) + } + ] + + const paginationModel = { page: 0, pageSize: 5 } + + const dataRow = mentions.map((men) => ({ + id: men.id, // Ensure this exists and is unique for each etudiant + nom: men.nom, + uniter: men.uniter, + action: men.id // Ensure this is a valid URL for the image + })) + + const deleteButton = async (id) => { + let response = await window.mention.deleteMention({ id }) + if (response.changes) { + const updatedMentions = mentions.filter((mention) => mention.id !== id) + setMentions(updatedMentions) + 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 = () => ( + + + {isDeleted ? ( + + Suprimer avec succèss + + ) : ( + + {' '} + Voulez vous supprimer ce mention ? + + )} + + {isDeleted ? ( + + ) : ( +
+ + +
+ )} +
+
+
+ ) + + return ( +
+ {modals()} +
+
+
+

+ + Mentions +

+ + + +
+
+
+ + {/* displaying data */} +
+
+ + + + + +
+
+
+ ) +} + +export default Mentions diff --git a/src/renderer/src/components/ModalAddEtudiants.jsx b/src/renderer/src/components/ModalAddEtudiants.jsx new file mode 100644 index 0000000..c4ce7bf --- /dev/null +++ b/src/renderer/src/components/ModalAddEtudiants.jsx @@ -0,0 +1,343 @@ +import React, { useRef, useState } from 'react' +import { + Modal, + Box, + Typography, + Button, + InputAdornment, + TextField, + Card, + Grid, + Container +} from '@mui/material' +import { + FaUser, + FaIdBadge, + FaBirthdayCake, + FaGraduationCap, + FaCalendarAlt, + FaFileUpload +} from 'react-icons/fa' + +const ModalAddEtudiants = ({ open, handleClose }) => { + /** + * hook for storing data in the input + */ + const [formData, setFormData] = useState({ + nom: '', + prenom: '', + photos: null, + date_de_naissances: '', + niveau: '', + annee_scolaire: '', + num_inscription: '' + }) + + /** + * ref for each input + */ + const nomRef = useRef() + const prenomRef = useRef() + const date_de_naissancesRef = useRef() + const niveauRef = useRef() + const annee_scolaireRef = useRef() + const numero_inscriptionRef = useRef() + const photosRef = useRef() + + /** + * function to set the data in state + * @param {*} e + */ + const handleInputChange = (e) => { + const { name, value } = e.target + setFormData((prevData) => ({ + ...prevData, + [name]: value + })) + } + + const handleFileChange = (e) => { + let img_file = e.target.files[0] + const reader = new FileReader() + reader.readAsDataURL(img_file) + reader.onload = (ev) => { + const url = ev.target.result + // initialisation de nouvelle imageURL + const image = document.createElement('img') + image.src = url + + // create a new image + image.onload = (event) => { + let canvas = document.createElement('canvas') + let ratio = 250 / event.target.width + canvas.width = 250 + canvas.height = event.target.height * ratio + const context = canvas.getContext('2d') + context.drawImage(image, 0, 0, canvas.width, canvas.height) + + // new url + const new_URL = canvas.toDataURL('image/jpeg', 90) + // rendement de l'url a notre variable global + setFormData((prevData) => ({ + ...prevData, + photos: new_URL + })) + } + } + } + + const handleSubmit = async (e) => { + e.preventDefault() + // Handle form submission logic + const response = await window.etudiants.insertEtudiant(formData) + console.log(response) + } + + return ( + + + + Ajoutez un etudiants + + +
+ + {/* Nom and Prenom Fields */} + + + + + ) + }} + inputRef={nomRef} + sx={{ + '& .MuiOutlinedInput-root': { + '&:hover fieldset': { + borderColor: 'rgb(156, 39, 176)' // Set the border color on hover + } + } + }} + /> + + + + + + ) + }} + inputRef={prenomRef} + sx={{ + '& .MuiOutlinedInput-root': { + '&:hover fieldset': { + borderColor: 'rgb(156, 39, 176)' // Set the border color on hover + } + } + }} + /> + + + {/* Date de Naissance and Niveau Fields */} + + + + + ) + }} + inputRef={date_de_naissancesRef} + sx={{ + '& .MuiOutlinedInput-root': { + '&:hover fieldset': { + borderColor: 'rgb(156, 39, 176)' // Set the border color on hover + } + } + }} + /> + + + + + + ) + }} + inputRef={niveauRef} + sx={{ + '& .MuiOutlinedInput-root': { + '&:hover fieldset': { + borderColor: 'rgb(156, 39, 176)' // Set the border color on hover + } + } + }} + /> + + + {/* Année Scolaire and Numéro d'Inscription Fields */} + + + + + ) + }} + inputRef={annee_scolaireRef} + sx={{ + '& .MuiOutlinedInput-root': { + '&:hover fieldset': { + borderColor: 'rgb(156, 39, 176)' // Set the border color on hover + } + } + }} + /> + + + + + + ) + }} + inputRef={numero_inscriptionRef} + sx={{ + '& .MuiOutlinedInput-root': { + '&:hover fieldset': { + borderColor: 'rgb(156, 39, 176)' // Set the border color on hover + } + } + }} + /> + + + {/* Photos Field */} + + + + + ) + }} + InputLabelProps={{ + shrink: true + }} + inputRef={photosRef} + sx={{ + '& .MuiOutlinedInput-root': { + '&:hover fieldset': { + borderColor: 'rgb(156, 39, 176)' // Set the border color on hover + } + } + }} + /> + + + {/* Submit Button */} + + + + + +
+
+
+
+ ) +} + +export default ModalAddEtudiants diff --git a/src/renderer/src/components/ModalAddProf.jsx b/src/renderer/src/components/ModalAddProf.jsx new file mode 100644 index 0000000..bc84d71 --- /dev/null +++ b/src/renderer/src/components/ModalAddProf.jsx @@ -0,0 +1,177 @@ +import React, { useEffect, useRef, useState } from 'react' +import { + Dialog, + DialogActions, + DialogContent, + DialogTitle, + TextField, + Button, + InputAdornment, + Box, + Grid +} from '@mui/material' +import ChangeCapital from './function/ChangeCapitalLetter' +import ChangeCapitalize from './function/ChangeCapitalizeLetter' +import { PiChalkboardTeacher } from 'react-icons/pi' +import { MdContactPhone, MdDateRange } from 'react-icons/md' + +const ModalAddProf = ({ open, onClose, matiere_id, onSubmitSuccess }) => { + const [formData, setFormData] = useState({ + nom_enseignant: '', + prenom_enseignant: '', + contact: '', + date: '', + matiere_id: '' + }) + + const nomRefs = useRef() + const prenomRefs = useRef() + + const handleChange = (e) => { + const { name, value } = e.target + setFormData({ ...formData, [name]: value }) + } + + useEffect(() => { + setFormData((prev) => ({ + ...prev, + matiere_id: matiere_id + })) + }, [matiere_id]) + + const handleSubmit = async (e) => { + e.preventDefault() + let response = await window.matieres.insertProf(formData) + + if (response.changes) { + onSubmitSuccess(true) + onClose() // Close the modal after submission + setFormData({ + nom_enseignant: '', + prenom_enseignant: '', + contact: '', + date: '', + matiere_id: '' + }) + } + } + + return ( + +
+ Information sur enseignant + + + + + ChangeCapital(nomRefs)} + onChange={handleChange} + InputProps={{ + startAdornment: ( + + + + ) + }} + /> + + + ChangeCapitalize(prenomRefs)} + onChange={handleChange} + InputProps={{ + startAdornment: ( + + + + ) + }} + /> + + + + + + ) + }} + /> + + + + + + ) + }} + /> + + + + + + + + +
+
+ ) +} + +export default ModalAddProf diff --git a/src/renderer/src/components/ModalCertificate.jsx b/src/renderer/src/components/ModalCertificate.jsx new file mode 100644 index 0000000..d513be3 --- /dev/null +++ b/src/renderer/src/components/ModalCertificate.jsx @@ -0,0 +1,97 @@ +import React, { useState } from 'react' +import { Dialog, DialogActions, DialogContent, DialogTitle, TextField, Button } from '@mui/material' +import PDFEditorCertificat from './function/PDFEditorCertificate' + +const ModalCertificate = ({ open, onClose, json }) => { + const [formData, setFormData] = useState({ + pere: '', + mere: '', + chefService: '' + }) + + const handleChange = (e) => { + const { name, value } = e.target + setFormData({ ...formData, [name]: value }) + } + + const handleSubmit = (e) => { + e.preventDefault() + console.log('Form submitted:', formData, json) + let data = { + f1: json.nomPrenom, + f2: json.naissances, + f3: formData.pere, + f4: formData.mere, + f5: json.niveau, + f6: json.mention, + f7: json.inscri, + f8: json.annee, + f9: formData.chefService + } + // Handle the form submission (e.g., send to a server or process data) + PDFEditorCertificat(data) + setFormData({ + mere: '', + pere: '', + chefService: '' + }) + onClose() // Close the modal after submission + } + + return ( + +
+ Informations sur l'élève + + + + + + + + + +
+
+ ) +} + +export default ModalCertificate diff --git a/src/renderer/src/components/ModalExportFichr.jsx b/src/renderer/src/components/ModalExportFichr.jsx new file mode 100644 index 0000000..f7f33aa --- /dev/null +++ b/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 ( +
+
+
+
+

+ + Matiere +

+
+ + + + + + +
+
+
+
+ +
+ +
+ + + + + + + + + + + + + + {sortedStudents.map((fi, index) => ( + + + + + + + ))} + +
{nom}
Nom et PrénomMentionEmergement
+ {index + 1} + + {fi.nom} {fi.prenom} + + {compareMention(fi.mention_id)} +
+
+
+
+
+ ) +} + +export default ModalExportFichr diff --git a/src/renderer/src/components/ModalFormMultiplicateur.jsx b/src/renderer/src/components/ModalFormMultiplicateur.jsx new file mode 100644 index 0000000..40b26ba --- /dev/null +++ b/src/renderer/src/components/ModalFormMultiplicateur.jsx @@ -0,0 +1,89 @@ +import React, { useEffect, useState } from 'react' +import { + Dialog, + DialogActions, + DialogContent, + DialogTitle, + TextField, + Button, + InputAdornment +} from '@mui/material' +import { FaTimes } from 'react-icons/fa' + +const ModalFormMultiplicateur = ({ open, onClose }) => { + const [formData, setFormData] = useState({ + id: '', + multiplicateur: '' + }) + + const [Multiplicateur, setMultiplicateur] = useState(0) + + useEffect(() => { + window.matieres.getNessesary().then((response) => { + setMultiplicateur(response) + }) + }, []) + + useEffect(() => { + setFormData((prev) => ({ + ...prev, + id: Multiplicateur.id, + multiplicateur: Multiplicateur.uniter_heure + })) + }, [Multiplicateur]) + + const handleChange = (e) => { + const { name, value } = e.target + setFormData({ ...formData, [name]: value }) + } + + const handleSubmit = async (e) => { + e.preventDefault() + console.log(formData) + let response = await window.matieres.updateNessesary(formData) + + if (response.changes) { + onClose() // Close the modal after submission + } + } + + return ( + +
+ Changer le multiplicateur + + + + + ) + }} + /> + + + + + +
+
+ ) +} + +export default ModalFormMultiplicateur diff --git a/src/renderer/src/components/ModalProcessFichePresence.jsx b/src/renderer/src/components/ModalProcessFichePresence.jsx new file mode 100644 index 0000000..5dd8285 --- /dev/null +++ b/src/renderer/src/components/ModalProcessFichePresence.jsx @@ -0,0 +1,54 @@ +import React, { useState } from 'react' +import { + Dialog, + DialogActions, + DialogContent, + DialogTitle, + TextField, + Button, + InputAdornment, + Box, + Grid +} from '@mui/material' +import { PiChalkboardTeacher } from 'react-icons/pi' + +const ModalProcessFichePresence = ({ open, onClose, matiere_id }) => { + const [formData, setFormData] = useState({ + matiere: '' + }) + return ( + + Option sur la la fiches + + + + + + + + ) + }} + /> + + + + + + ) +} + +export default ModalProcessFichePresence diff --git a/src/renderer/src/components/ModalRecepice.jsx b/src/renderer/src/components/ModalRecepice.jsx new file mode 100644 index 0000000..2f27cc0 --- /dev/null +++ b/src/renderer/src/components/ModalRecepice.jsx @@ -0,0 +1,66 @@ +import React, { useState } from 'react' +import { Dialog, DialogActions, DialogContent, DialogTitle, TextField, Button } from '@mui/material' +import PDFEditorRecepice from './function/PDFEditorRecepisse' + +const ModalRecepice = ({ open, onClose, json }) => { + const [formData, setFormData] = useState({ + nom: '' + }) + + const handleChange = (e) => { + const { name, value } = e.target + setFormData({ ...formData, [name]: value }) + } + + const handleSubmit = (e) => { + e.preventDefault() + console.log('Form submitted:', formData) + let data = { + f1: json.mention, + f2: json.niveau, + f3: json.nomPrenom, + f4: json.inscri, + f6: json.annee, + f7: formData.nom + } + // Handle the form submission (e.g., send to a server or process data) + PDFEditorRecepice(data) + setFormData({ + nom: '' + }) + onClose() // Close the modal after submission + } + + return ( + +
+ Informations suplementaire + + + + + + + +
+
+ ) +} + +export default ModalRecepice diff --git a/src/renderer/src/components/ModalStage.jsx b/src/renderer/src/components/ModalStage.jsx new file mode 100644 index 0000000..105fdc5 --- /dev/null +++ b/src/renderer/src/components/ModalStage.jsx @@ -0,0 +1,84 @@ +import React, { useRef, useState } from 'react' +import { Dialog, DialogActions, DialogContent, DialogTitle, TextField, Button } from '@mui/material' +import PDFEditorStage from './function/PDFEditorStage' +import ChangeCapital from './function/ChangeCapitalLetter' +import ChangeCapitalize from './function/ChangeCapitalizeLetter' + +const ModalStage = ({ open, onClose }) => { + const [formData, setFormData] = useState({ + nom: '', + prenom: '' + }) + + const handleChange = (e) => { + const { name, value } = e.target + setFormData({ ...formData, [name]: value }) + } + + const handleSubmit = (e) => { + e.preventDefault() + console.log('Form submitted:', formData) + let data = { + f1: formData.nom + ' ' + formData.prenom + } + // Handle the form submission (e.g., send to a server or process data) + PDFEditorStage(data) + setFormData({ + nom: '', + prenom: '' + }) + onClose() // Close the modal after submission + } + + const nomRef = useRef() + const prenomRef = useRef() + + return ( + +
+ Informations suplementaire + + ChangeCapital(nomRef)} + color="warning" + onChange={handleChange} + /> + ChangeCapitalize(prenomRef)} + color="warning" + onChange={handleChange} + /> + + + + + +
+
+ ) +} + +export default ModalStage diff --git a/src/renderer/src/components/ModalUpdateParcoursEtudiant.jsx b/src/renderer/src/components/ModalUpdateParcoursEtudiant.jsx new file mode 100644 index 0000000..642033a --- /dev/null +++ b/src/renderer/src/components/ModalUpdateParcoursEtudiant.jsx @@ -0,0 +1,107 @@ +import React, { useEffect, useState } from 'react' +import { + Dialog, + DialogActions, + DialogContent, + DialogTitle, + TextField, + Button, + Autocomplete, + InputAdornment +} from '@mui/material' +import { MdRule } from 'react-icons/md' + +const ModalUpdateParcoursEtudiant = ({ open, onClose, user_id, onSubmit }) => { + const [formData, setFormData] = useState({ + parcours: '', + user_id: '' + }) + const [parcours, setParcours] = useState([]) + + useEffect(() => { + window.notesysteme.getParcours().then((response) => { + setParcours(response) + }) + }, []) + + useEffect(() => { + if (user_id) { + setFormData((prevData) => ({ + ...prevData, + user_id: user_id + })) + } + }, [user_id]) + + const handleSubmit = async (e) => { + e.preventDefault() + + let response = await window.etudiants.changeParcours(formData) + + if (response.changes) { + onSubmit(true, formData.parcours) + onClose() + } + } + + return ( + +
+ Ajouter un parcours + + option.nom || ''} // Display `nom` + value={parcours.find((item) => item.nom === formData.parcours) || null} // Find selected option based on `nom` + onChange={(event, newValue) => { + setFormData((prevData) => ({ + ...prevData, + parcours: newValue ? newValue.nom : '' // Store only `nom` + })) + }} + size="small" + isOptionEqualToValue={(option, value) => option.nom === value?.nom} // Ensure correct matching + renderInput={(params) => ( + + + + + {params.InputProps.startAdornment} + + ) + }} + sx={{ + marginBottom: '5px', + '& .MuiOutlinedInput-root': { + '&:hover fieldset': { + borderColor: '#ff9800' // Set border color on hover + } + } + }} + /> + )} + /> + + + + + +
+
+ ) +} + +export default ModalUpdateParcoursEtudiant diff --git a/src/renderer/src/components/Navbar.jsx b/src/renderer/src/components/Navbar.jsx new file mode 100644 index 0000000..f79aaea --- /dev/null +++ b/src/renderer/src/components/Navbar.jsx @@ -0,0 +1,140 @@ +import React, { useState } from 'react' +import classe from '../assets/Navbar.module.css' +import icon from '../assets/logo.ico' +import { FaTimes, FaRegWindowMinimize } from 'react-icons/fa' +import { useAuthContext } from '../contexts/AuthContext' +import { Modal, Box, Typography, Button } from '@mui/material' +import { useNavigate } from 'react-router-dom' + +const Navbar = () => { + const { token, setToken } = useAuthContext() + const navigate = useNavigate() + + /** + * function to quit app + */ + const quitApp = () => { + if (token !== null) { + setOpen(true) + } else { + quit() + } + } + + /** + * function to minimize the app + * if the user try to open something + * in the desktop + */ + const minimize = async () => { + await window.allUser.minimize() + } + + /** + * hook to open modal + */ + const [open, setOpen] = useState(false) + + /** + * function to close modal + */ + const handleClose = () => setOpen(false) + + /** + * function to quit + */ + const quit = async () => { + await window.allUser.quit() + } + + /** + * function after user click yes button in the modal + */ + const logoutAndQuit = () => { + localStorage.removeItem('ACCESS_TOKEN') + setToken(null) + navigate('/login') + quit() + } + + /** + * function to return the view Modal + * + * @returns {JSX} + */ + const modals = () => ( + + + + Notification + + + Déconnecter et quitter ? + + + + + + + + ) + + return ( + <> +
+ {modals()} +
+ + Université de Toamasina +
+
+ + +
+
+ + ) +} + +export default Navbar diff --git a/src/renderer/src/components/Niveau.jsx b/src/renderer/src/components/Niveau.jsx new file mode 100644 index 0000000..abc839a --- /dev/null +++ b/src/renderer/src/components/Niveau.jsx @@ -0,0 +1,262 @@ +import React, { useEffect, useState } from 'react' +import { Link } from 'react-router-dom' +import { createTheme, ThemeProvider } from '@mui/material/styles' +import { DataGrid } from '@mui/x-data-grid' +import { frFR } from '@mui/x-data-grid/locales' +import Paper from '@mui/material/Paper' +import { Box, Button, InputAdornment, Typography, Modal, TextField, Grid } from '@mui/material' +import classe from '../assets/AllStyleComponents.module.css' +import classeHome from '../assets/Home.module.css' +import { GiUpgrade } from 'react-icons/gi' +import { FaPenToSquare, FaTrash } from 'react-icons/fa6' +import { Tooltip } from 'react-tooltip' +import warning from '../assets/warning.svg' +import success from '../assets/success.svg' + +const Niveau = () => { + const [niveaus, setNiveau] = useState([]) + + useEffect(() => { + window.niveaus.getNiveau().then((response) => { + setNiveau(response) + }) + }, []) + + const [isDeleted, setIsDeleted] = useState(false) + const [ids, setIds] = useState(0) + + const columns = [ + { field: 'nom', headerName: 'Nom', width: 200 }, + { + field: 'action', + headerName: 'Action', + flex: 1, + renderCell: (params) => ( +
+ + + + { + setIds(params.row.id) + setOpen(true) + }} + > + + +
+ ) + } + ] + + const paginationModel = { page: 0, pageSize: 5 } + + const dataRow = niveaus.map((niveau) => ({ + id: niveau.id, + nom: niveau.nom, + action: niveau.id + })) + + 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 + } + } + } + } + }) + + /** + * hook to open modal + */ + const [open, setOpen] = useState(false) + + /** + * function to close modal + */ + const handleClose = () => { + setOpen(false) + setIsDeleted(false) + } + + const deleteButton = async (id) => { + let response = await window.niveaus.deleteNiveaus({ id }) + if (response.changes) { + const updatedNiveaus = niveaus.filter((niveau) => niveau.id !== id) + setNiveau(updatedNiveaus) + setIsDeleted(true) + } + } + + /** + * function to return the view Modal + * + * @returns {JSX} + */ + const modals = () => ( + + + {isDeleted ? ( + + Suprimer avec succèss + + ) : ( + + {' '} + Voulez vous supprimer ce niveau ? + + )} + + {isDeleted ? ( + + ) : ( +
+ + +
+ )} +
+
+
+ ) + + return ( +
+ {modals()} +
+
+
+

Niveau

+ + + +
+
+
+ + {/* display the data-grid niveau */} +
+
+ + +
+ +
+
+
+
+
+
+ ) +} + +export default Niveau diff --git a/src/renderer/src/components/NotFound.jsx b/src/renderer/src/components/NotFound.jsx new file mode 100644 index 0000000..7d84918 --- /dev/null +++ b/src/renderer/src/components/NotFound.jsx @@ -0,0 +1,11 @@ +import React from 'react' + +const NotFound = () => { + return ( +
+

404 Not Found

+
+ ) +} + +export default NotFound diff --git a/src/renderer/src/components/Noteclasse.jsx b/src/renderer/src/components/Noteclasse.jsx new file mode 100644 index 0000000..f250ec7 --- /dev/null +++ b/src/renderer/src/components/Noteclasse.jsx @@ -0,0 +1,370 @@ +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 { DataGrid, GridToolbar } from '@mui/x-data-grid' +import { frFR } from '@mui/x-data-grid/locales' +import { createTheme, ThemeProvider } from '@mui/material/styles' +import { IoNewspaperOutline } from 'react-icons/io5' +import { IoMdReturnRight } from 'react-icons/io' +import { Button, Modal, Box } from '@mui/material' +import { Tooltip } from 'react-tooltip' +import ReleverNotes from './ReleverNotes' +import { FaDownload } from 'react-icons/fa' + +const Noteclasse = () => { + const { niveau, scolaire } = useParams() + + const [etudiants, setEtudiants] = useState([]) + const [mention, setMention] = useState([]) + const [session, setSession] = useState([]) + + const formData = { + niveau, + scolaire + } + + useEffect(() => { + window.notes.getMoyenne(formData).then((response) => { + setEtudiants(response) + }) + window.noteRepech.getMoyenneRepech(formData).then((response) => { + setSession(response) + }) + window.mention.getMention().then((response) => { + setMention(response) + }) + }, []) + + 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 + } + + 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) + } + + function checkNumberSession(id) { + let sessionNumber + for (let index = 0; index < session.length; index++) { + for (let j = 0; j < session[index].length; j++) { + if (session[index][j].etudiant_id == id) { + sessionNumber = 2 + } else { + sessionNumber = 1 + } + } + } + return sessionNumber + } + + 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 paginationModel = { page: 0, pageSize: 5 } + + const columns = [ + { field: 'nom', headerName: 'Nom', width: 170 }, + { field: 'prenom', headerName: 'Prenom', width: 160 }, + { field: 'session', headerName: 'Nombre de Session', width: 180 }, + { field: 'mention', headerName: 'Mention', width: 180 }, + { field: 'moyenne', headerName: 'Moyenne Général', width: 160 }, + { + field: 'photos', + headerName: 'Photos', + width: 100, + renderCell: (params) => ( + {'image + ) + }, + { + field: 'action', + headerName: 'Action', + flex: 1, + renderCell: (params) => ( +
+ sendData(params.value)}> + {/* */} + + + Imprimer un relevé de notes + + +
+ ) + } + ] + + const dataTable = dataToMap.map((data) => ({ + id: data.id, + nom: data.nom, + prenom: data.prenom, + photos: data.photos, + mention: returnmention(data.mention), + session: checkNumberSession(data.id), + moyenne: data.moyenne, + action: data.id + })) + + const [openCard, setOpenCart] = useState(false) + const [bolll, setBolll] = useState(false) + const [form, setForm] = useState({ + id: '', + niveau: '', + anneescolaire: '' + }) + const [selectedId, setSelectedId] = useState(null) // Store id dynamically + + const sendData = (id) => { + setOpenCart(true) + setSelectedId(id) + } + + useEffect(() => { + if (selectedId !== null) { + const foundData = dataToMap.find((item) => item.id === selectedId) + if (foundData) { + setForm((prevForm) => ({ + ...prevForm, + id: foundData.id, + anneescolaire: foundData.anneescolaire, + niveau: niveau + })) // Update form with the found object + } + } + }, [openCard, selectedId]) + console.log(form) + const downloadButton = () => { + setBolll(true) + } + + /** + * function to close modal + */ + const handleCloseCart = () => { + setBolll(false) + setOpenCart(false) + } + + const modalReleverNotes = () => { + return ( + + + + + + + + ) + } + + return ( +
+ {modalReleverNotes()} +
+
+
+

+ Notes des {niveau} en {scolaire} +

+
+ + + + window.history.back()}> + + +
+
+
+
+ +
+
+ + +
+ +
+
+
+
+
+
+ ) +} + +export default Noteclasse diff --git a/src/renderer/src/components/Notes.jsx b/src/renderer/src/components/Notes.jsx new file mode 100644 index 0000000..173a2c9 --- /dev/null +++ b/src/renderer/src/components/Notes.jsx @@ -0,0 +1,176 @@ +import React, { useEffect, useState } from 'react' +import { Button } from '@mui/material' +import { createTheme, ThemeProvider } from '@mui/material/styles' +import { IoEyeSharp } from 'react-icons/io5' +import { frFR } from '@mui/x-data-grid/locales' +import { Tooltip } from 'react-tooltip' +import Paper from '@mui/material/Paper' +import { DataGrid, GridToolbar } from '@mui/x-data-grid' +import classe from '../assets/AllStyleComponents.module.css' +import classeHome from '../assets/Home.module.css' +import { Link } from 'react-router-dom' + +const Notes = () => { + 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 paginationModel = { page: 0, pageSize: 5 } + + const columns = [ + { field: 'niveau', headerName: 'Niveau', width: 170 }, + { field: 'annee_scolaire', headerName: 'Année scolaire', width: 160 }, + // { field:'moyenne', headerName:'Moyenne de classe', width:160}, + { + field: 'action', + headerName: 'Action', + flex: 1, + renderCell: (params) => ( +
+ + + + Voir tous les etudiants dans ce niveau + + +
+ ) + } + ] + + /** + * hook for dispplaying note + */ + const [blockNotes, SetBlockNotes] = useState([]) + + useEffect(() => { + window.notes.getblockNote().then((response) => { + SetBlockNotes(response) + }) + }, []) + + const calculMoyenneDeClasse = (L1, annee) => { + let data = blockNotes.allData + let note = 0 + let someId = [] + let total = 0 + let totalEtudiant = [] + let id = [] + let totalCredit = 0 + + if (data != undefined) { + for (let index = 0; index < data.length; index++) { + for (let j = 0; j < data[index].length; j++) { + if (data[index][j].niveau == L1 && data[index][j].annee_scolaire == annee) { + totalEtudiant.push(data[index][j]) + } + } + } + } + + for (let index = 0; index < totalEtudiant.length; index++) { + someId.push(totalEtudiant[index].etudiant_id) + id = [...new Set(someId)] + note += totalEtudiant[index].note * totalEtudiant[index].credit + totalCredit += totalEtudiant[index].credit + } + total = note / totalCredit + + return total.toFixed(2) + } + + let dataRow + if (blockNotes.response) { + dataRow = blockNotes.response.map((note, index) => ({ + id: index, + niveau: note.etudiant_niveau, + moyenne: calculMoyenneDeClasse(note.etudiant_niveau, note.annee_scolaire), + annee_scolaire: note.annee_scolaire, + action: note.niveau + })) + } else { + dataRow = [] + } + + return ( +
+
+
+
+

Notes

+
+
+
+ + {/* display the data-grid students */} +
+
+ + +
+ +
+
+
+
+
+
+ ) +} + +export default Notes diff --git a/src/renderer/src/components/Param.jsx b/src/renderer/src/components/Param.jsx new file mode 100644 index 0000000..474e8eb --- /dev/null +++ b/src/renderer/src/components/Param.jsx @@ -0,0 +1,505 @@ +import React, { useRef, useState } from 'react' +import classe from '../assets/AllStyleComponents.module.css' +import img from '../assets/para.png' +import classeHome from '../assets/Home.module.css' +import { Box, Button, InputAdornment, TextField, Grid, Modal, Typography } from '@mui/material' +import classeAdd from '../assets/AddStudent.module.css' +import { useAuthContext } from '../contexts/AuthContext' +import bcrypt from 'bcryptjs' +import { FaEnvelope, FaLock, FaUser } from 'react-icons/fa' +import validationSetting from './validation/Setting' +import svgSuccess from '../assets/success.svg' +import svgError from '../assets/error.svg' +import { Link } from 'react-router-dom' +import { GrConfigure } from "react-icons/gr"; +import IpConfig from './IpConfig' + +const Setting = () => { + const { token, setToken } = useAuthContext() + + const userInfo = JSON.parse(token) + + const [isUsername, setIsUsername] = useState(true) + + const changeUsernameRef = useRef() + const changeEmailRef = useRef() + + const changeEmail = (e) => { + if (changeEmailRef.current.checked) { + setIsUsername(false) + } + } + + const changeUsername = (e) => { + if (changeUsernameRef.current.checked) { + setIsUsername(true) + } + } + + const [formData, setFormData] = useState({ + username: userInfo.username, + newUsername: '', + email: userInfo.email, + newEmail: '', + passwordVerif: '', + password: '', + id: userInfo.id + }) + + const usernameRef = useRef() + const emailRef = useRef() + const passwordRef = useRef() + const passwordChangeRef = useRef() + const usernameError = useRef() + const emailError = useRef() + const passwordError = useRef() + const passwordChangeError = useRef() + + /** + * function to set the data in state + * @param {*} e + */ + const handleInputChange = (e) => { + const { name, value } = e.target + setFormData((prevData) => ({ + ...prevData, + [name]: value + })) + } + + const handleSubmit = async (e) => { + e.preventDefault() + // Handle form submission logic + let validation = validationSetting( + usernameRef.current, + emailRef.current, + passwordRef.current, + passwordChangeRef.current, + usernameError.current, + emailError.current, + passwordError.current, + passwordChangeError.current + ) + + if (validation) { + if (formData.username === formData.newUsername) { + if (usernameError.current) { + usernameError.current.textContent = + "Le nom d'utilisateur doit être différent de l'original" + } + } else { + if (usernameError.current) { + usernameError.current.textContent = '' // Clear the username error if condition is not met + } + } + + if (formData.email === formData.newEmail) { + if (emailError.current) { + emailError.current.textContent = "Email doit être différent de l'original" + } + } else { + if (emailError.current) { + emailError.current.textContent = '' // Clear the email error if condition is not met + } + } + if (formData.email !== formData.newEmail && formData.username !== formData.newUsername) { + // Password verification + bcrypt.compare(formData.passwordVerif, userInfo.password, async function (err, result) { + if (result) { + // Clear password error if password matches + if (passwordError.current) { + passwordError.current.textContent = '' + } + + // Update user information if password is verified + const response = await window.allUser.updateUsers(formData) + if (response.id) { + setOpen(true) + setToken(JSON.stringify(response)) + setFormData({ + passwordVerif: '', + username: response.username, + newUsername: '', + email: response.email, + newEmail: '', + password: '', + id: userInfo.id + }) + } + + // Handle specific response code for errors + if (response.code) { + setCode(422) + setOpen(true) + } + } else { + // Set error message if password verification fails + if (passwordError.current) { + passwordError.current.textContent = 'mot de passe ne correspond pas' + } + } + }) + } + } + } + + /** + * hook to open modal + */ + const [open, setOpen] = useState(false) + const [code, setCode] = useState(200) + + /** + * function to close modal + */ + const handleClose = () => setOpen(false) + + /** + * function to return the view Modal + * + * @returns {JSX} + */ + const modals = () => ( + + + {code == 422 ? ( + + {' '} + Email déjà pris + + ) : ( + + {' '} + Modification a été effectuée avec succès + + )} + + + + + + ) + + const [openConfig, setOpenCOnfig] = useState(false) + const onCloseConfig = () => setOpenCOnfig(false) + + const openCOnfigFunction = () => setOpenCOnfig(true) + + return ( +
+ {modals()} + +
+
+
+

setting

+ + + +
+
+
+ {/* contenu */} +
+
+ + + + + + +
+ + + + + + + + {isUsername === true ? ( + + {/* username*/} + + + + + ) + }} + sx={{ + '& .MuiOutlinedInput-root': { + '&:hover fieldset': { + borderColor: '#ff9800' // Set the border color on hover + } + } + }} + /> + + + + + + ) + }} + inputRef={usernameRef} + sx={{ + '& .MuiOutlinedInput-root': { + '&:hover fieldset': { + borderColor: '#ff9800' // Set the border color on hover + } + } + }} + /> + + + + ) : ( + + {/* email */} + + + + + ) + }} + sx={{ + '& .MuiOutlinedInput-root': { + '&:hover fieldset': { + borderColor: '#ff9800' // Set the border color on hover + } + } + }} + /> + + + + + + ) + }} + inputRef={emailRef} + sx={{ + '& .MuiOutlinedInput-root': { + '&:hover fieldset': { + borderColor: '#ff9800' // Set the border color on hover + } + } + }} + /> + + + + )} + +

+ Insérer le mot de passe pour confirmer le changement +

+
+ {/* matieres Mecanique general */} + + + + + ) + }} + inputRef={passwordRef} + sx={{ + '& .MuiOutlinedInput-root': { + '&:hover fieldset': { + borderColor: '#ff9800' // Set the border color on hover + } + } + }} + /> + + + {/* Matieres Resistance Materiaux */} + + + }} + inputRef={passwordChangeRef} + sx={{ + '& .MuiOutlinedInput-root': { + '&:hover fieldset': { + borderColor: '#ff9800' // Set the border color on hover + } + } + }} + /> + + + {/* Submit Button */} + + + +
+
+
+
+
+
+
+ ) +} + +export default Setting diff --git a/src/renderer/src/components/ParcourMatiere.jsx b/src/renderer/src/components/ParcourMatiere.jsx new file mode 100644 index 0000000..467faa4 --- /dev/null +++ b/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.changes) { + onClose() + } + } + + return ( + +
+ Assignation à des parcours + + + + + + + Parcours + + + + + + + + + + + +
+
+ ) +} + +export default ParcourMatiere diff --git a/src/renderer/src/components/Parcours.jsx b/src/renderer/src/components/Parcours.jsx new file mode 100644 index 0000000..6a160e4 --- /dev/null +++ b/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) => ( +
+ openUpdate(params.value)}> + + +
+ ) + } + ] + + const paginationModel = { page: 0, pageSize: 5 } + + const dataRow = parcours.map((parc) => ({ + id: parc.id, + nom: parc.nom, + uniter: parc.uniter, + mention: parc.mention_id, + 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 ( +
+ + +
+
+
+

+ + Parcours +

+ + + +
+
+
+ +
+
+ + +
+ +
+
+
+
+
+
+ ) +} + +export default Parcours diff --git a/src/renderer/src/components/ReleverNotes.jsx b/src/renderer/src/components/ReleverNotes.jsx new file mode 100644 index 0000000..c41c357 --- /dev/null +++ b/src/renderer/src/components/ReleverNotes.jsx @@ -0,0 +1,589 @@ +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 jsPDF from 'jspdf' +import html2Canvas from 'html2canvas' +import logoRelerev1 from '../assets/logorelever.png' +import logoRelerev2 from '../assets/logorelever2.png' +import dayjs from 'dayjs' +import getSemestre from './function/GetSemestre' +import { descisionJury, getmentionAfterNotes } from './function/FonctionRelever' + +const ReleverNotes = ({ id, anneescolaire, niveau, refs }) => { + const [etudiant, setEtudiant] = useState([]) + const [matieres, setMatieres] = useState([]) + const [notes, setNotes] = useState([]) + + const handleDownloadPDF = async () => { + const input = Telever.current + + // Set a high scale for better quality + const scale = 3 + + html2Canvas(input, { + scale, // Increase resolution + useCORS: true, // Handle cross-origin images + allowTaint: true + }).then((canvas) => { + const imgData = canvas.toDataURL('image/png') + + // Create a PDF with dimensions matching the captured content + const pdf = new jsPDF({ + orientation: 'portrait', + unit: 'mm', + format: 'a4' + }) + + const imgWidth = 210 // A4 width in mm + const pageHeight = 297 // A4 height in mm + const imgHeight = (canvas.height * imgWidth) / canvas.width + + let position = 0 + + pdf.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight, '', 'FAST') + + // Handle multi-page case + while (position + imgHeight >= pageHeight) { + position -= pageHeight + pdf.addPage() + pdf.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight, '', 'FAST') + } + + pdf.save('document.pdf') + }) + } + + useEffect(() => { + window.etudiants.getSingle({ id }).then((response) => { + setEtudiant(response) + }) + + window.mention.getMention().then((response) => { + setMatieres(response) + }) + + window.notes.noteRelerer({ id, anneescolaire, niveau }).then((response) => { + setNotes(response) + }) + }, [id]) + + const Telever = useRef() + + useEffect(() => { + if (refs) { + handleDownloadPDF() + } + }, [refs]) + + const [matiereWithSemestre, setMatiereWithSemestre] = useState([]) + const [matiereWithSemestreRepech, setMatiereWithSemestreRepech] = useState([]) + + useEffect(() => { + if (!notes.noteNormal || !notes.semestre || !notes.noteRepech) return // Ensure data exists + + const updatedMatieres = notes.noteNormal.map((matiere) => { + // Get the semesters based on the student's niveau + const semesters = getSemestre(matiere.etudiant_niveau) + + // Find the matched semestre based on the conditions + const matchedSemestre = notes.semestre.find( + (sem) => + sem.matiere_id === matiere.matiere_id && + sem.mention_id === matiere.mention_id && + (sem.nom === semesters[0] || sem.nom === semesters[1]) // Check if the semester matches + ) + + return { + ...matiere, + semestre: matchedSemestre ? matchedSemestre.nom : null // Add 'semestre' or set null if no match + } + }) + + const updatedMatieresRepech = notes.noteRepech.map((matiere) => { + // Get the semesters based on the student's niveau + const semesters = getSemestre(matiere.etudiant_niveau) + + // Find the matched semestre based on the conditions + const matchedSemestre = notes.semestre.find( + (sem) => + sem.matiere_id === matiere.matiere_id && + sem.mention_id === matiere.mention_id && + (sem.nom === semesters[0] || sem.nom === semesters[1]) // Check if the semester matches + ) + + // Return the updated matiere with the matched semestre or null if no match + return { + ...matiere, + semestre: matchedSemestre ? matchedSemestre.nom : null // Add 'semestre' or set null if no match + } + }) + + setMatiereWithSemestre(updatedMatieres) + setMatiereWithSemestreRepech(updatedMatieresRepech) + }, [notes]) + + function compareMention(mentionID) { + let statusText + + matieres.map((statu) => { + if (mentionID == statu.id) { + statusText = statu.nom + } + }) + + return statusText ? statusText.charAt(0).toUpperCase() + statusText.slice(1) : statusText + } + + // data are finaly get and ready for the traitement below + + // Merging the arrays based on matiere_id + matiereWithSemestre.forEach((item1) => { + // Find the corresponding item in array2 based on matiere_id + let matchingItem = matiereWithSemestreRepech.find( + (item2) => item2.matiere_id === item1.matiere_id + ) + + // If there's a match, add noterepech from array2, otherwise use the note from array1 + item1.noterepech = matchingItem ? matchingItem.note : item1.note + }) + + // step 1 group all by semestre + const groupedDataBySemestre = matiereWithSemestre.reduce((acc, matiere) => { + const { semestre } = matiere + + if (!acc[semestre]) { + acc[semestre] = [] + } + + acc[semestre].push(matiere) + + return acc + }, {}) + + const compareMoyenne = (normal, rattrapage) => { + const note = Math.max(Number(normal), Number(rattrapage)) + return note >= 10 ? 'Admis' : 'Ajourné' + } + + const TbodyContent = () => { + return ( + <> + {Object.entries(groupedDataBySemestre).map(([semestre, matieres]) => { + // Group by unite_enseignement inside each semestre + const groupedByUnite = matieres.reduce((acc, matiere) => { + if (!acc[matiere.unite_enseignement]) { + acc[matiere.unite_enseignement] = [] + } + acc[matiere.unite_enseignement].push(matiere) + return acc + }, {}) + + return ( + + {Object.entries(groupedByUnite).map(([unite, matieres], uniteIndex) => ( + <> + {matieres.map((matiere, matiereIndex) => ( + + {/* Display 'semestre' only for the first row of the first unite_enseignement */} + {uniteIndex === 0 && matiereIndex === 0 && ( + + {semestre} + + )} + + {/* Display 'unite_enseignement' only for the first row of each group */} + {matiereIndex === 0 && ( + + {unite} + + )} + + {/* Matiere Data */} + + {matiere.nom} + + + {matiere.credit} + + + {matiere.note} + + + {matiere.credit} + + + {matiere.noterepech} + + + {/* Display the comparison value only once */} + {matiereIndex === 0 && ( + + {/* Replace 'hgh' with your logic for displaying the comparison */} + {compareMoyenne( + ( + matieres.reduce((total, matiere) => total + matiere.note, 0) / + matieres.length + ).toFixed(2), + ( + matieres.reduce((total, matiere) => total + matiere.noterepech, 0) / + matieres.length + ).toFixed(2) + )} + + )} + + ))} + + {/* Add Total Row for 'unite_enseignement' */} + + + Total de Credit et Moyenne des Notes + + + {/* Calculate Total de Credit */} + {matieres.reduce((total, matiere) => total + matiere.credit, 0)} + + + {/* Calculate Moyenne des Notes */} + {( + matieres.reduce((total, matiere) => total + matiere.note, 0) / + matieres.length + ).toFixed(2)}{' '} + {/* Format to 2 decimal places */} + + + {matieres.reduce((total, matiere) => total + matiere.credit, 0)} + + + {( + matieres.reduce((total, matiere) => total + matiere.noterepech, 0) / + matieres.length + ).toFixed(2)} + + + + + ))} + + ) + })} + + ) + } + + const totalNotes = () => { + let totalNotes = document.querySelectorAll('.moyenneNotes') + let totalNotesRepech = document.querySelectorAll('.moyenneNotesRattrapage') + + let TotalNoteNumber = 0 + let TotalNoteNumberRepech = 0 + + totalNotes.forEach((notes) => { + TotalNoteNumber += Number(notes.textContent / totalNotes.length) + // console.log(notes.textContent); + }) + + totalNotesRepech.forEach((notes) => { + TotalNoteNumberRepech += Number(notes.textContent / totalNotes.length) + // console.log(notes.textContent); + }) + + let note = Math.max(TotalNoteNumber, TotalNoteNumberRepech) + + return note + } + + const [note, setNote] = useState(0) + + useEffect(() => { + setNote(totalNotes()) + }, [TbodyContent]) + + return ( +
+
+
+ +
+
+ image en tete + image en tete +
+
+

+ Releve de notes +

+
+ {/* block info */} +
+ {/* gauche */} +
+ {/* gauche gauche */} +
+ + Nom + +
+ + Prenom + +
+ + Date de naissance + +
+ + Codage + +
+ {/* gauche droite */} +
+ : {etudiant.nom} +
+ : {etudiant.prenom} +
+ : {dayjs(etudiant.date_de_naissances).format('DD/MM/YYYY')} +
+ : {etudiant.num_inscription} +
+
+ {/* droite */} +
+ {/* droite gauche */} +
+ + Annee U + +
+ + Niveau + +
+ + Parcours + +
+ {/* droite droite */} +
+ : {etudiant.annee_scolaire} +
+ : {etudiant.niveau} +
+ : {compareMention(etudiant.mention_id)} +
+
+
+ + {/* table */} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Session +
+ Normale + + Rattrapage +
+ semestre + UEECcréditNotescréditNotes + Observation +
+ Moyenne + {note.toFixed(2)}/20
+ Mention:{' '} + {getmentionAfterNotes(note)} + + Décision du Jury:{' '} + + {descisionJury(note, etudiant.niveau)} + +
+
+

+ Toamasine le +

+ {/* texte hidden for place in signature */} +

+ Lorem ipsum dolor sit amet consectetur adipisicing elit. Blanditiis delectus + perspiciatis nisi aliquid eos adipisci cumque amet ratione error voluptatum. + Expedita velit enim nulla nam? Vitae fuga enim et temporibus. Lorem ipsum dolor + sit amet, consectetur adipisicing elit. Mollitia, assumenda? +

+
+
+
+
+
+
+ ) +} + +export default ReleverNotes diff --git a/src/renderer/src/components/Resultat.jsx b/src/renderer/src/components/Resultat.jsx new file mode 100644 index 0000000..af9e808 --- /dev/null +++ b/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 ( +
+
+
+
+

+ Resultat des {niveau} en {scolaire} +

+
+ + + + window.history.back()}> + + +
+
+
+
+ +
+ + + + + + + + + + + + + + + {sortedStudents.map((sorted) => ( + + + + + + + ))} + +
+
+ Niveau {niveau} | Année universitaire {scolaire} +
+
NomPrénomMentionMoyenne
{sorted.nom}{sorted.prenom}{returnmention(sorted.mention)}{sorted.moyenne}
+
+
+
+ ) +} + +export default Resultat diff --git a/src/renderer/src/components/Sidenav.jsx b/src/renderer/src/components/Sidenav.jsx new file mode 100644 index 0000000..28934ff --- /dev/null +++ b/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 ( + + ) +} + +export default Sidenav diff --git a/src/renderer/src/components/SingleAnneeScolaire.jsx b/src/renderer/src/components/SingleAnneeScolaire.jsx new file mode 100644 index 0000000..91d2521 --- /dev/null +++ b/src/renderer/src/components/SingleAnneeScolaire.jsx @@ -0,0 +1,267 @@ +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 { 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' + +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: scolaire.debut, + fin: scolaire.fin, + 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.changes) { + setOpen(true) + setStatus(200) + } else { + setStatus(400) + setOpen(true) + } + } + + const handleClose = () => setOpen(false) + + const modals = () => ( + + + + + {status === 200 ? 'Mise à jour effectuée avec succès' : 'Erreur'} + + + + + + + ) + + return ( +
+ {modals()} +
+
+
+

+ + Mise a jour Année universitaire +

+ window.history.back()}> + + +
+
+
+ +
+ +
+

+ mise a jour année universitaire +

+ + + + + + ) + }} + 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 + } + }} + /> + + + + + + ) + }} + 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 + } + }} + /> + + + + + + ) + }} + 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 + } + }} + /> + + + + + +
+
+
+
+ ) +} + +export default SingleAnneeScolaire diff --git a/src/renderer/src/components/SingleEtudiant.jsx b/src/renderer/src/components/SingleEtudiant.jsx new file mode 100644 index 0000000..3308087 --- /dev/null +++ b/src/renderer/src/components/SingleEtudiant.jsx @@ -0,0 +1,1152 @@ +import React, { useEffect, useState } from 'react' +import { + TextField, + Button, + Grid, + Card, + CardContent, + Avatar, + Typography, + InputAdornment, + Modal, + Box, + Autocomplete, + IconButton +} from '@mui/material' +import { Tooltip } from 'react-tooltip' +import { FaClipboardList, FaEdit } from 'react-icons/fa' +import { AiOutlineUser, AiOutlineCalendar, AiOutlineNumber } from 'react-icons/ai' +import { Link, useParams } from 'react-router-dom' +import classe from '../assets/AllStyleComponents.module.css' +import { FaCamera } from 'react-icons/fa' +import { styled } from '@mui/material/styles' +import warning from '../assets/warning.svg' +import { FaRegTimesCircle } from 'react-icons/fa' +import MenuItem from '@mui/material/MenuItem' +import { MdGrade, MdRule } from 'react-icons/md' +import { FaLeftLong, FaRightLong } from 'react-icons/fa6' + +const SingleEtudiant = () => { + const { id } = useParams() + const [etudiant, setEtudiant] = useState({}) + const [niveaus, setNiveaus] = useState([]) + const [mention, setMention] = useState([]) + const [parcours, setParcours] = useState([]) + + const [editMode, setEditMode] = useState(false) + const [formData, setFormData] = useState({ + nom: '', + prenom: '', + photos: null, + date_de_naissances: '', + niveau: '', + annee_scolaire: '', + status: '', + mention_id: '', + num_inscription: '', + sexe: 'Garçon', + nationalite: '', + cin: '', + date_delivrence: '', + annee_bacc: '', + serie: '', + boursier: 'oui', + domaine: '', + contact: '', + parcours: '' + }) + const [status, setStatus] = useState([]) + const [scolaire, setScolaire] = useState([]) + + // Fetch the student data + useEffect(() => { + window.etudiants.getSingle({ id }).then((response) => { + setEtudiant(response) + }) + + window.niveaus.getNiveau().then((response) => { + setNiveaus(response) + }) + + window.statuss.getStatus().then((response) => { + setStatus(response) + }) + + window.anneescolaire.getAnneeScolaire().then((response) => { + setScolaire(response) + }) + + window.mention.getMention().then((response) => { + setMention(response) + }) + + window.notesysteme.getParcours().then((response) => { + setParcours(response) + }) + }, [id]) + + console.log(scolaire) + function comparestatut(statutID) { + let statusText + + status.map((statu) => { + if (statutID == statu.id) { + statusText = statu.nom + } + }) + return statusText + } + + function compareMention(mentionId) { + let mentionText + + mention.map((ment) => { + if (mentionId == ment.id) { + mentionText = ment.nom + } + }) + return mentionText + } + + // Populate formData with etudiant values when etudiant changes + useEffect(() => { + if (etudiant) { + setFormData({ + nom: etudiant.nom || '', + prenom: etudiant.prenom || '', + photos: etudiant.photos || null, + date_de_naissances: etudiant.date_de_naissances || '', + niveau: etudiant.niveau || '', + annee_scolaire: etudiant.annee_scolaire || '', + status: etudiant.status || '', + mention_id: etudiant.mention_id || '', + num_inscription: etudiant.num_inscription || '', + id: etudiant.id || '', + sexe: etudiant.sexe, + nationalite: etudiant.nationalite, + cin: etudiant.cin, + date_delivrence: etudiant.date_delivrence, + annee_bacc: etudiant.annee_bacc, + serie: etudiant.serie, + boursier: etudiant.boursier, + domaine: etudiant.domaine, + contact: etudiant.contact, + parcours: etudiant.parcours + }) + } + }, [etudiant]) + + const handleChange = (e) => { + const { name, value } = e.target + setFormData({ + ...formData, + [name]: value + }) + } + + const handleSubmit = async () => { + const { + nom, + prenom, + date_de_naissances, + niveau, + annee_scolaire, + num_inscription, + sexe, + nationalite, + cin, + date_delivrence, + annee_bacc, + serie, + boursier, + domaine, + contact, + parcours + } = formData + + // Validation check: Ensure all required fields are filled + if ( + !nom || + !prenom || + !date_de_naissances || + !niveau || + !annee_scolaire || + !num_inscription || + !sexe || + !nationalite || + !cin || + !date_delivrence || + !annee_bacc || + !serie || + !boursier || + !domaine || + !contact || + !parcours + ) { + setOpen(true) + return // Prevent submission if validation fails + } + + // If validation passes, proceed with the submission + try { + let response = await window.etudiants.updateEtudiants(formData) + if (response.changes) { + setEtudiant(formData) + } + + setEditMode(false) + } catch (error) { + console.error('Error updating student:', error) + } + } + + /** + * 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 = () => ( + + + + {' '} + Voulez vous modifier la photos ? + + + + + + + + ) + + const [pdp, setPdp] = useState(null) + + const updatePDP = () => { + let avatar = document.getElementById('imagepdpdynamic') + let imgAvatar = avatar.querySelector('img') + + window.etudiants.updateEtudiantsPDP({ pdp, id }).then((responses) => { + if (responses.changes) { + if (imgAvatar === null) { + let image = document.createElement('img') + image.setAttribute('src', pdp) + image.style.width = '100%' + avatar.appendChild(image) + } else { + imgAvatar.removeAttribute('src') + imgAvatar.setAttribute('src', pdp) + } + setOpen(false) + } + }) + } + + const changePDP = () => { + document.getElementById('inputhiden').click() + } + + const handleFileChange = (e) => { + let img_file = e.target.files[0] + const reader = new FileReader() + reader.readAsDataURL(img_file) + reader.onload = (ev) => { + const url = ev.target.result + // initialisation de nouvelle imageURL + const image = document.createElement('img') + image.src = url + + // create a new image + image.onload = (event) => { + let canvas = document.createElement('canvas') + let ratio = 250 / event.target.width + canvas.width = 250 + canvas.height = event.target.height * ratio + const context = canvas.getContext('2d') + context.drawImage(image, 0, 0, canvas.width, canvas.height) + + // new url + const new_URL = canvas.toDataURL('image/jpeg', 90) + setPdp(new_URL) + setOpen(true) + } + } + } + + 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 [page1, setPage1] = useState(true) + const [page2, setPage2] = useState(false) + const [page3, setPage3] = useState(false) + + const seePage1 = () => { + setPage2(false) + setPage3(false) + setPage1(true) + } + + const seePage2 = () => { + setPage1(false) + setPage3(false) + setPage2(true) + } + + const seePage3 = () => { + setPage1(false) + setPage2(false) + setPage3(true) + } + + return ( +
+ {modals()} +
+ + + + + + + + +
+ + +
+
+ {editMode ? ( + <> + {page1 && ( + + {/* Group Nom and Prenom */} + + + + + + ) + }} + sx={{ + marginBottom: 2, + padding: 1, + marginLeft: '4%', + '& .MuiOutlinedInput-root': { + '&:hover fieldset': { + borderColor: '#ff9800' // Set the border color on hover + } + } + }} + /> + + + + + + ) + }} + sx={{ + marginBottom: 2, + padding: 1, + marginLeft: '4%', + '& .MuiOutlinedInput-root': { + '&:hover fieldset': { + borderColor: '#ff9800' // Set the border color on hover + } + } + }} + /> + + + + {/* Group Niveau and Date de Naissance */} + + + + + + ) + }} + sx={{ + marginBottom: 2, + padding: 1, + marginLeft: '4%', + '& .MuiOutlinedInput-root': { + '&:hover fieldset': { + borderColor: '#ff9800' // Couleur de la bordure au survol + } + } + }} + > + {/* Liste des options */} + {niveaus.map((niveau) => ( + + {niveau.nom} + + ))} + + + + + + + ) + }} + sx={{ + marginBottom: 2, + padding: 1, + marginLeft: '4%', + '& .MuiOutlinedInput-root': { + '&:hover fieldset': { + borderColor: '#ff9800' // Set the border color on hover + } + } + }} + /> + + + + {/* Group Année Scolaire and Numero d'Inscription */} + + + + + + ) + }} + sx={{ + marginBottom: 2, + padding: 1, + marginLeft: '4%', + '& .MuiOutlinedInput-root': { + '&:hover fieldset': { + borderColor: '#ff9800' // Set the border color on hover + } + } + }} + > + {scolaire.map((statu) => ( + + {statu.code} + + ))} + + + + + + + ) + }} + sx={{ + marginBottom: 2, + padding: 1, + marginLeft: '4%', + '& .MuiOutlinedInput-root': { + '&:hover fieldset': { + borderColor: '#ff9800' // Set the border color on hover + } + } + }} + /> + + + + {/* Group status*/} + + + + + + ) + }} + sx={{ + marginBottom: 2, + padding: 1, + marginLeft: '4%', + '& .MuiOutlinedInput-root': { + '&:hover fieldset': { + borderColor: '#ff9800' // Couleur de la bordure au survol + } + } + }} + > + {status.map((statu) => ( + + {statu.nom} + + ))} + + + + option.nom || ''} // Safely access `nom` + value={mention.find((item) => item.id === formData.mention_id) || null} // Bind selected value to form data + onChange={(event, newValue) => { + setFormData((prevData) => ({ + ...prevData, + mention_id: newValue ? newValue.id : null // Store the ID of the selected mention + })) + }} + // size="small" // Make the Autocomplete small + isOptionEqualToValue={(option, value) => option.id === value.id} // Ensure correct matching of options + renderInput={(params) => ( + + + + + {params.InputProps.startAdornment} + + ) + }} + sx={{ + marginBottom: 2, + padding: 1, + marginLeft: '4%', + // marginBottom: '5px', + '& .MuiOutlinedInput-root': { + '&:hover fieldset': { + borderColor: '#ff9800' // Set border color on hover + } + } + }} + /> + )} + /> + + + + )} + + {page2 && ( + + {/* Group Nom and Prenom */} + + + + + + ) + }} + sx={{ + marginBottom: 2, + padding: 1, + marginLeft: '4%', + '& .MuiOutlinedInput-root': { + '&:hover fieldset': { + borderColor: '#ff9800' // Set the border color on hover + } + } + }} + /> + + + + + + ) + }} + sx={{ + marginBottom: 2, + padding: 1, + marginLeft: '4%', + '& .MuiOutlinedInput-root': { + '&:hover fieldset': { + borderColor: '#ff9800' // Set the border color on hover + } + } + }} + /> + + + + {/* Group Niveau and Date de Naissance */} + + + + + + ) + }} + sx={{ + marginBottom: 2, + padding: 1, + marginLeft: '4%', + '& .MuiOutlinedInput-root': { + '&:hover fieldset': { + borderColor: '#ff9800' // Couleur de la bordure au survol + } + } + }} + > + {/* Liste des options */} + Garçon + Fille + + + + + + + ) + }} + sx={{ + marginBottom: 2, + padding: 1, + marginLeft: '4%', + '& .MuiOutlinedInput-root': { + '&:hover fieldset': { + borderColor: '#ff9800' // Set the border color on hover + } + } + }} + /> + + + + {/* Group Année Scolaire and Numero d'Inscription */} + + + + + + ) + }} + sx={{ + marginBottom: 2, + padding: 1, + marginLeft: '4%', + '& .MuiOutlinedInput-root': { + '&:hover fieldset': { + borderColor: '#ff9800' // Set the border color on hover + } + } + }} + > + + + + + + ) + }} + sx={{ + marginBottom: 2, + padding: 1, + marginLeft: '4%', + '& .MuiOutlinedInput-root': { + '&:hover fieldset': { + borderColor: '#ff9800' // Set the border color on hover + } + } + }} + /> + + + + {/* Group status*/} + + + + + + ) + }} + sx={{ + marginBottom: 2, + padding: 1, + marginLeft: '4%', + '& .MuiOutlinedInput-root': { + '&:hover fieldset': { + borderColor: '#ff9800' // Couleur de la bordure au survol + } + } + }} + > + Oui + Non + + + + option.nom || ''} // Display `nom` + value={parcours.find((item) => item.nom === etudiant.parcours) || null} // Find selected option based on `nom` + onChange={(event, newValue) => { + setFormData((prevData) => ({ + ...prevData, + parcours: newValue ? newValue.nom : '' // Store only `nom` + })) + }} + isOptionEqualToValue={(option, value) => option.nom === value?.nom} // Ensure correct matching + renderInput={(params) => ( + + + + + {params.InputProps.startAdornment} + + ) + }} + sx={{ + marginBottom: 2, + padding: 1, + marginLeft: '4%', + // marginBottom: '5px', + '& .MuiOutlinedInput-root': { + '&:hover fieldset': { + borderColor: '#ff9800' // Set border color on hover + } + } + }} + /> + )} + /> + + + + )} + + {page3 && ( + + + + + + + ) + }} + sx={{ + marginBottom: 2, + padding: 1, + marginLeft: '4%', + '& .MuiOutlinedInput-root': { + '&:hover fieldset': { + borderColor: '#ff9800' // Set the border color on hover + } + } + }} + /> + + + + + + ) + }} + sx={{ + marginBottom: 2, + padding: 1, + marginLeft: '4%', + '& .MuiOutlinedInput-root': { + '&:hover fieldset': { + borderColor: '#ff9800' // Set the border color on hover + } + } + }} + /> + + + + )} + + {/* Submit Button */} + +
+ + + + {/* + Page précédents + */} + + + + {/* + Page suivants + */} +
+ +
+ + ) : ( + <> +
+
+ + + Nom: {etudiant.nom} + + + + + Prenom: {etudiant.prenom} + + + + + Niveau: {etudiant.niveau} + + + + + Status: {comparestatut(etudiant.status)} + + + + + Date de Naissance: {etudiant.date_de_naissances} + + + + + Année Scolaire: {etudiant.annee_scolaire} + + + + + Numero d'Inscription: {etudiant.num_inscription} + + + + + Mention: {compareMention(etudiant.mention_id)} + + +
+
+ + )} + + + + +
+
+
+
+
+ ) +} + +export default SingleEtudiant diff --git a/src/renderer/src/components/SingleMatiere.jsx b/src/renderer/src/components/SingleMatiere.jsx new file mode 100644 index 0000000..66fa550 --- /dev/null +++ b/src/renderer/src/components/SingleMatiere.jsx @@ -0,0 +1,383 @@ +import React, { useEffect, useRef, useState } from 'react' +import { Link, useParams } from 'react-router-dom' +import { IoMdReturnRight } from 'react-icons/io' +import { + Box, + InputAdornment, + Typography, + Modal, + TextField, + Grid, + Button, + Autocomplete +} from '@mui/material' +import { BsBookmarkPlusFill } from 'react-icons/bs' +import svgSuccess from '../assets/success.svg' +import svgError from '../assets/error.svg' +import { MdOutlineNumbers } from 'react-icons/md' +import ChangeCapital from './function/ChangeCapitalLetter' +import { CiCalendar } from 'react-icons/ci' +import { IoBookmark } from 'react-icons/io5' +import { FaClipboardList, FaClock } from 'react-icons/fa' +import classe from '../assets/AllStyleComponents.module.css' +import classeHome from '../assets/Home.module.css' + +const SingleMatiere = () => { + const { id } = useParams() + const [matiere, setMatiere] = useState(null) + const [mentions, setMentions] = useState([]) + const [formData, setFormData] = useState({ + nom: '', + credit: '', + uniter: '', + ue: '', + id: id + }) + + const [message, setMessage] = useState('') + const [status, setStatus] = useState(200) + const [open, setOpen] = useState(false) + const uniterRef = useRef() + const ueRef = useRef() + + // Helper function to convert a string of IDs to an array + const stringToArray = (data) => (data ? data.split(',').map(Number) : []) + + useEffect(() => { + // Fetch single matiere data by ID + window.matieres.getMatiereByID({ id }).then((response) => { + setMatiere(response) + }) + + // Fetch mentions data + window.mention.getMention().then((response) => { + setMentions(Array.isArray(response) ? response : []) + }) + }, [id]) + + useEffect(() => { + if (matiere) { + setFormData((prev) => ({ + ...prev, + nom: matiere.nom || '', + credit: matiere.credit || '', + uniter: matiere.unite_enseignement || '', + ue: matiere.ue || '', + id: matiere.id || id + })) + } + }, [matiere, id]) + + const handleChange = (e) => { + const { name, value } = e.target + setFormData((prevData) => ({ + ...prevData, + [name]: value + })) + } + + const handleSubmit = async (e) => { + e.preventDefault() + const response = await window.matieres.updateMatiere(formData) + console.log(response) + if (response.changes) { + setStatus(200) + setMessage('Modification a été effectuée avec succès') + } else if (response.code) { + setStatus(400) + setMessage('La matière existe déjà dans la base !') + } + setOpen(true) + } + + const handleClose = () => setOpen(false) + + const modals = () => ( + + + + + {message} + + + + + + + ) + + return ( +
+ {modals()} +
+
+
+

Mise à jour des matières

+
+ + + +
+
+
+
+ +
+ +
+

Mise à jour

+ + + + + + ) + }} + sx={{ + marginBottom: '5px', + '& .MuiOutlinedInput-root': { + '&:hover fieldset': { + borderColor: '#ff9800' // Set the border color on hover + } + } + }} + /> + + + + + + ) + }} + inputProps={{ min: 1 }} + // inputRef={creditRef} + sx={{ + marginBottom: '5px', + '& .MuiOutlinedInput-root': { + '&:hover fieldset': { + borderColor: '#ff9800' // Set the border color on hover + } + } + }} + /> + + {/* + + + + ), + }} + inputProps={{ min: 1 }} + // inputRef={semestreRef} + sx={{ + marginBottom:"5px", + '& .MuiOutlinedInput-root': { + '&:hover fieldset': { + borderColor: '#ff9800', // Set the border color on hover + }, + }, + }} + /> + */} + + ChangeCapital(uniterRef)} + required + inputRef={uniterRef} + InputProps={{ + startAdornment: ( + + + + ) + }} + inputProps={{ min: 1 }} + // inputRef={uniterRef} + sx={{ + marginBottom: '5px', + '& .MuiOutlinedInput-root': { + '&:hover fieldset': { + borderColor: '#ff9800' // Set the border color on hover + } + } + }} + /> + + + ChangeCapital(ueRef)} + inputRef={ueRef} + type="text" + required + InputProps={{ + startAdornment: ( + + + + ) + }} + inputProps={{ min: 1 }} + // inputRef={uniterRef} + sx={{ + marginBottom: '5px', + '& .MuiOutlinedInput-root': { + '&:hover fieldset': { + borderColor: '#ff9800' // Set the border color on hover + } + } + }} + /> + + {/* + option.nom || ''} + value={mentions.filter((mention) => + formData.mention_id.includes(mention.id) + )} + onChange={(event, newValue) => { + setFormData((prevData) => ({ + ...prevData, + mention_id: newValue.map((item) => item.id), + })); + }} + isOptionEqualToValue={(option, value) => + option.id === value.id + } + renderInput={(params) => ( + + + + + {params.InputProps.startAdornment} + + ), + }} + sx={{ + marginBottom: "5px", + '& .MuiOutlinedInput-root': { + '&:hover fieldset': { + borderColor: '#ff9800', // Set border color on hover + }, + }, + }} + /> + )} + /> + */} + + + + +
+
+
+
+ ) +} + +export default SingleMatiere diff --git a/src/renderer/src/components/SingleNiveau.jsx b/src/renderer/src/components/SingleNiveau.jsx new file mode 100644 index 0000000..c8b35e3 --- /dev/null +++ b/src/renderer/src/components/SingleNiveau.jsx @@ -0,0 +1,222 @@ +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 { FaPenToSquare } from 'react-icons/fa6' +import { IoMdReturnRight } from 'react-icons/io' +import { Link, useParams } from 'react-router-dom' +import { Box, Button, InputAdornment, Typography, Modal, TextField, Grid } from '@mui/material' +import validationSingleNiveau from './validation/SingleNiveau' +import svgSuccess from '../assets/success.svg' + +const SingleNiveau = () => { + const { id } = useParams() + + const [niveau, setNiveau] = useState([]) + const [allNiveau, setAllNiveau] = useState([]) + + useEffect(() => { + window.niveaus.getSingleNiveau({ id }).then((response) => { + setNiveau(response) + }) + + window.niveaus.getNiveau().then((response) => { + setAllNiveau(response) + }) + }, []) + + useEffect(() => { + if (niveau) { + setFormData({ + nom: niveau.nom || '', + id: niveau.id || id + }) + } + }, [niveau]) + + const [formData, setFormData] = useState({ + nom: '', + id: id + }) + + const handleChange = (e) => { + const { name, value } = e.target + setFormData({ + ...formData, + [name]: value + }) + } + + const handleSubmit = async (e) => { + e.preventDefault() + let niveauNom = [] + allNiveau.map((niv) => { + niveauNom.push(niv.nom) + }) + let validation = validationSingleNiveau(nomRef.current, errorRef.current, niveauNom) + + if (validation) { + let response = await window.niveaus.updateSingleNiveau(formData) + + if (response.changes == 1) { + setOpen(true) + } + + if (response.code) { + nomError.current.textContent = `${formData.nom} existe déjà` + } + } + } + + /** + * 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 = () => ( + + + + {' '} + Modification a été effectuée avec succès + + + + + + + ) + + const nomRef = useRef() + const errorRef = useRef() + + return ( +
+ {modals()} +
+
+
+

+ + Mise à jour niveau +

+ + + +
+
+
+ +
+ + +
+

modification niveau

+ + {/* Nom Fields */} + + {/* */} + ) + }} + inputRef={nomRef} + sx={{ + '& .MuiOutlinedInput-root': { + '&:hover fieldset': { + borderColor: '#ff9800' // Set the border color on hover + } + } + }} + /> + + + + {/* Submit Button */} + + + + +
+
+
+
+
+ ) +} + +export default SingleNiveau diff --git a/src/renderer/src/components/SingleNotes.jsx b/src/renderer/src/components/SingleNotes.jsx new file mode 100644 index 0000000..07d0ce3 --- /dev/null +++ b/src/renderer/src/components/SingleNotes.jsx @@ -0,0 +1,364 @@ +import React, { useEffect, useRef, useState } from 'react' +import { useParams, Link } from 'react-router-dom' +import Paper from '@mui/material/Paper' +import classe from '../assets/AllStyleComponents.module.css' +import classeHome from '../assets/Home.module.css' +import { IoMdReturnRight } from 'react-icons/io' +import { Button } from '@mui/material' +import { Box, InputAdornment, Typography, Modal, TextField, Grid } from '@mui/material' +import { CgNotes } from 'react-icons/cg' +import svgSuccess from '../assets/success.svg' +import svgError from '../assets/error.svg' + +const SingleNotes = () => { + let { id, niveau, scolaire } = useParams() + const [notes, setNotes] = useState([]) + const [notesRepech, setNotesRepech] = useState([]) + const [formData, setFormData] = useState({}) + const [formData2, setFormData2] = useState({}) + const [etudiant, setEtudiant] = useState([]) + let annee_scolaire = scolaire + const [screenRattrapage, setScreenRattrapage] = useState(false) + + useEffect(() => { + window.etudiants.getSingle({ id }).then((response) => { + setEtudiant(response) + }) + }, []) + + useEffect(() => { + let mention_id = etudiant.mention_id + window.notes.getNotes({ id, niveau, mention_id }).then((response) => { + setNotes(response) + }) + + window.noteRepech.getNotesRepech({ id, niveau, mention_id }).then((response) => { + setNotesRepech(response) + }) + }, [etudiant]) + + console.log(notes) + /** + * Update formData whenever matieres change + */ + useEffect(() => { + const initialFormData = notes.reduce((acc, mat) => { + acc[mat.id] = mat.note // Initialize each key with an empty string + return acc + }, {}) + setFormData(initialFormData) + }, [notes]) // Dependency array ensures this runs whenever `matieres` is updated + + /** + * Update formData2 whenever matieres change + */ + useEffect(() => { + const initialFormData = notesRepech.reduce((acc, mat) => { + acc[mat.id] = mat.note // Initialize each key with an empty string + return acc + }, {}) + setFormData2(initialFormData) + }, [notesRepech]) // Dependency array ensures this runs whenever `matieres` is updated + + const submitForm = async (e) => { + e.preventDefault() + let mention_id = etudiant.mention_id + console.log('normal submited') + let annee_scolaire = etudiant.annee_scolaire + let response = await window.notes.updateNote({ + formData, + niveau, + id, + mention_id, + annee_scolaire + }) + + if (response.changes) { + setMessage('Modification des notes terminer avec succès') + setStatus(200) + setOpen(true) + window.noteRepech.getNotesRepech({ id, niveau, mention_id }).then((response) => { + setNotesRepech(response) + }) + + window.notes.getNotes({ id, niveau, mention_id }).then((response) => { + setNotes(response) + }) + } + } + + const submitForm2 = async (e) => { + e.preventDefault() + let mention_id = etudiant.mention_id + console.log('rattrapage submited') + let response = await window.noteRepech.updateNoteRepech({ formData2, niveau, id }) + + console.log(response) + if (response.changes) { + setMessage('Modification des notes terminer avec succès') + setStatus(200) + setOpen(true) + window.noteRepech.getNotesRepech({ id, niveau, mention_id }).then((response) => { + setNotesRepech(response) + }) + + window.notes.getNotes({ id, niveau, mention_id }).then((response) => { + setNotes(response) + }) + } + } + + const [status, setStatus] = useState(200) + const [message, setMessage] = useState('') + + /** + * hook to open modal + */ + const [open, setOpen] = useState(false) + + /** + * function to close modal + */ + const handleClose = () => setOpen(false) + + /** + * function to return the view Modal + * + * @returns {JSX} + */ + const modals = () => ( + + + {status === 200 ? ( + + {message} + + ) : ( + + {message} + + )} + + + + + + ) + + const nom = useRef() + + const changeScreen = () => { + setScreenRattrapage(!screenRattrapage) + } + + return ( +
+ {modals()} +
+
+
+

Mise a jour des notes

+
+ window.history.back()}> + + +
+
+
+
+ + {/* displaying the form */} +
+ + + {!screenRattrapage ? ( +
+

Mise a jour des notes

+ {/* {/* map the all matiere and note to the form */} + + {notes.map((note) => ( + + setFormData({ ...formData, [note.id]: e.target.value }) // Update the specific key + } + InputProps={{ + startAdornment: ( + + + + ) + }} + inputRef={nom} + sx={{ + '& .MuiOutlinedInput-root': { + '&:hover fieldset': { + borderColor: '#ff9800' // Set the border color on hover + } + }, + '& .MuiInputBase-input::placeholder': { + fontSize: '11px' // Set the placeholder font size + } + }} + /> + + ))} + + + + + +
+ ) : ( +
+

Mise a jour des notes de Rattrapage

+ {/* {/* map the all matiere and note to the form */} + + {notesRepech.length === 0 ? ( + // Show this message if notesRepech is empty + +

+ L'étudiant a validé tous les crédits. +

+
+ ) : ( + // Render form fields if notesRepech contains data + notesRepech.map((note) => ( + + setFormData2({ ...formData2, [note.id]: e.target.value }) // Update the specific key + } + InputProps={{ + startAdornment: ( + + + + ) + }} + inputRef={nom} + sx={{ + '& .MuiOutlinedInput-root': { + '&:hover fieldset': { + borderColor: '#ff9800' // Set the border color on hover + } + }, + '& .MuiInputBase-input::placeholder': { + fontSize: '11px' // Set the placeholder font size + } + }} + /> + + )) + )} +
+ + + + + +
+ )} +
+
+
+
+ ) +} + +export default SingleNotes diff --git a/src/renderer/src/components/SinleMention.jsx b/src/renderer/src/components/SinleMention.jsx new file mode 100644 index 0000000..cb2bce5 --- /dev/null +++ b/src/renderer/src/components/SinleMention.jsx @@ -0,0 +1,272 @@ +import React, { useEffect, useRef, useState } from 'react' +import classe from '../assets/AllStyleComponents.module.css' +import classeHome from '../assets/Home.module.css' +import { Link, useParams } from 'react-router-dom' +import { IoMdReturnRight } from 'react-icons/io' +import { Box, InputAdornment, Typography, Modal, TextField, Grid, Button } from '@mui/material' +import ChangeCapital from './function/ChangeCapitalLetter' +import { FaClipboardList } from 'react-icons/fa6' +import { IoBookmark } from 'react-icons/io5' +import svgSuccess from '../assets/success.svg' + +const SinleMention = () => { + const { id } = useParams() + const [mentions, setMention] = useState([]) + const [formData, setFormData] = useState({ + nom: '', + uniter: '', + id: '' + }) + + const [errors, setErrors] = useState({ + nom: false, + uniter: false + }) + + useEffect(() => { + window.mention.getSingleMention({ id }).then((response) => { + setMention(response) + }) + }, []) + + useEffect(() => { + if (mentions) { + setFormData({ + nom: mentions.nom || '', + uniter: mentions.uniter || '', + id: mentions.id || id + }) + } + }, [mentions]) + + const nomRef = useRef() + const uniterRef = useRef() + + /** + * function to set the data in state + * @param {*} e + */ + const handleInputChange = (e) => { + const { name, value } = e.target + setFormData({ + ...formData, + [name]: value + }) + setErrors({ + ...errors, + [name]: false // Reset the error when user starts typing + }) + } + + // Helper function to get helperText dynamically + const getHelperText = (field) => (errors[field] ? 'Ce champ est requis.' : '') + + const formSubmit = async (e) => { + e.preventDefault() + const newErrors = {} + let hasError = false + + // Check for empty fields + Object.keys(formData).forEach((key) => { + const value = formData[key] + if (typeof value === 'string' && !value.trim()) { + newErrors[key] = true // Set error for empty fields + hasError = true + } + }) + + setErrors(newErrors) + + if (!hasError) { + try { + let response = await window.mention.updateMention(formData) + + if (response.changes) { + setOpen(true) + } + } catch (error) { + console.log(error) + } + } + } + + /** + * 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 = () => ( + + + + {' '} + Mention modifier avec succes + + + + + + + ) + + return ( +
+ {modals()} +
+
+
+

Mise a jour mention

+
+ + + +
+
+
+
+ +
+ + +
+ + + ChangeCapital(nomRef)} + InputProps={{ + startAdornment: ( + + + + ) + }} + helperText={getHelperText('nom')} + inputRef={nomRef} + sx={{ + marginBottom: '5px', + '& .MuiOutlinedInput-root': { + '&:hover fieldset': { + borderColor: '#ff9800' // Set the border color on hover + } + } + }} + /> + + + ChangeCapital(uniterRef)} + InputProps={{ + startAdornment: ( + + + + ) + }} + inputRef={uniterRef} + sx={{ + marginBottom: '5px', + '& .MuiOutlinedInput-root': { + '&:hover fieldset': { + borderColor: '#ff9800' // Set the border color on hover + } + } + }} + /> + + + + + +
+
+
+
+
+ ) +} + +export default SinleMention diff --git a/src/renderer/src/components/Student.jsx b/src/renderer/src/components/Student.jsx new file mode 100644 index 0000000..d663d24 --- /dev/null +++ b/src/renderer/src/components/Student.jsx @@ -0,0 +1,561 @@ +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 ( +
+ {matchingNote ? ( + + + + Voir les notes + + + ) : ( + + + + Ajouter un notes à cet étudiant + + + )} +
+ ) + } + + /** + * 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) => ( + {'image + ) + }, + { + field: 'action', + headerName: 'Action', + width: 300, + renderCell: (params) => ( +
+ + + + + + + Verification Frais de Formation + + + Print(params.value)}> + + + Exporter carte d'etudiants + + + + + + {/* Groupe 1 : Certificat */} + + + + + + Télecharger le Certificat de scolariter + + + + + {/* Groupe 2 : Stage (affiché seulement pour L2) */} + + {params.row.niveau !== 'L1' && ( + + + + Télecharger l'autorisation de stage + + + )} + + + + Télecharger le recepissé d'inscription + + + + + +
+ ) + } + ] + + 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); + } + } + + const paginationModel = { page: 0, pageSize: 5 } + + // 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: etudiant.date_delivrence, + 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 + } + + /** + * function to filter the data in dataGrid by Niveau + */ + const FilterData = async (e) => { + let niveau = e.target.value + if (niveau !== '') { + let data = await window.etudiants.FilterDataByNiveau({ niveau }) + setEtudiants(data) + } else { + window.etudiants.getEtudiants().then((response) => { + setEtudiants(response) + }) + } + } + + 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 ( +
+ +
+
+
+

Etudiants

+ + + +
+
+ {/* bare des filtre */} +
+ {/* filtre par niveau */} +
+ + + Niveau + + + +
+
+
+ + {/* display the data-grid students */} +
+
+ + +
+ +
+
+
+ + + +
+
+
+ ) +} + +export default Student diff --git a/src/renderer/src/components/SystemeNote.jsx b/src/renderer/src/components/SystemeNote.jsx new file mode 100644 index 0000000..12904de --- /dev/null +++ b/src/renderer/src/components/SystemeNote.jsx @@ -0,0 +1,362 @@ +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 { IoMdPersonAdd, IoMdReturnRight } from 'react-icons/io' +import { Link } from 'react-router-dom' +import { Box, Button, InputAdornment, Typography, Modal, TextField, Grid } from '@mui/material' +import { CgNotes } from 'react-icons/cg' +import { FaAngleDoubleUp, FaAngleDoubleDown, FaCog } from 'react-icons/fa' +import validateNOteSystem from './validation/NoteSystem' +import svgError from '../assets/error.svg' +import svgSuccess from '../assets/success.svg' +import { Tooltip } from 'react-tooltip' +import ModalFormMultiplicateur from './ModalFormMultiplicateur' + +const SystemeNote = () => { + const [formData, setFormData] = useState({ + id: '', + admis: '', + redouble: '', + renvoyer: '' + }) + + const [noteSy, setNoteSy] = useState([]) + const [status, setStatus] = useState(200) + + /** + * function to set the data in state + * @param {*} e + */ + const handleInputChange = (e) => { + const { name, value } = e.target + setFormData((prevData) => ({ + ...prevData, + [name]: value + })) + } + + useEffect(() => { + window.notesysteme.getSyteme().then((response) => { + setNoteSy(response) + }) + }, []) + + useEffect(() => { + if (noteSy) { + setFormData({ + id: noteSy.id, + admis: noteSy.admis || 0, + redouble: noteSy.redouble || 0, + renvoyer: noteSy.renvoyer || 0 + }) + } + }, [noteSy]) + + const formSubmit = async (e) => { + e.preventDefault() + let valid = validateNOteSystem(admisRef.current, redoubleRef.current, renvoyerRef.current) + + if (valid) { + let response = await window.notesysteme.updateNoteSysteme(formData) + if (response.changes) { + setStatus(200) + setOpen(true) + } + } else { + setStatus(400) + setOpen(true) + } + } + + const admisRef = useRef() + const redoubleRef = useRef() + const renvoyerRef = useRef() + + /** + * 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 = () => ( + + + {status === 200 ? ( + + {' '} + Modification des notes a été effectuée avec succès + + ) : ( + + {' '} + Vérifiez les champs vides ou les séparateurs (doivent être un point) + + )} + + + + + + ) + + const [openForm, setOpenForm] = useState(false) + + const closeForm = () => { + setOpenForm(false) + } + + const openThisForm = () => { + setOpenForm(true) + } + + return ( +
+ + {modals()} + +
+
+
+

+ + Système d'organisation des notes +

+ + + + + Changer le multiplicateur (credit * M) + +
+
+
+
+ + +
+ + + + + + + + + + + + + + + + + +
Status :AdmisRedoubleRenvoyer
Notes : + {/* */} + + + + + ), + endAdornment: ( + + + + ) + }} + inputRef={admisRef} + sx={{ + '& .MuiOutlinedInput-root': { + '&:hover fieldset': { + borderColor: '#ff9800' // Set the border color on hover + } + }, + '& .MuiInputBase-input::placeholder': { + fontSize: '11px' // Set the placeholder font size + } + }} + /> + + {/* */} + + {/* */} + + + + + ), + endAdornment: ( + + + + ) + }} + inputRef={redoubleRef} + sx={{ + '& .MuiOutlinedInput-root': { + '&:hover fieldset': { + borderColor: '#ff9800' // Set the border color on hover + } + }, + '& .MuiInputBase-input::placeholder': { + fontSize: '11px' // Set the placeholder font size + } + }} + /> + + {/* */} + + {/* */} + + + + + ), + endAdornment: ( + + + + ) + }} + inputRef={renvoyerRef} + sx={{ + '& .MuiOutlinedInput-root': { + '&:hover fieldset': { + borderColor: '#ff9800' // Set the border color on hover + } + }, + '& .MuiInputBase-input::placeholder': { + fontSize: '11px' // Set the placeholder font size + } + }} + /> + + {/* */} +
+ + + +
+
+
+
+
+ ) +} + +export default SystemeNote diff --git a/src/renderer/src/components/TesteDatagrid.jsx b/src/renderer/src/components/TesteDatagrid.jsx new file mode 100644 index 0000000..3ea96c7 --- /dev/null +++ b/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( + + {isFirstRow && itemIdx === 0 ? ( + + {semestreKey} + + ) : null} + + {itemIdx === 0 ? ( + + {unitKey} + + ) : null} + + + {item.nom} + + + {' '} + {item.credit} + + + {item.note} + + + {/* Ensure this renders only once for the unitArray */} + {itemIdx === 0 ? ( + + + {( + unitArray.reduce((sum, item) => sum + item.credit * item.note, 0) / + unitArray.reduce((sum, item) => sum + item.credit, 0) + ).toFixed(2)} + + + ) : null} + + ) + }) + }) + }) + return rows + } + + return ( +
+
+
+ +
+ image en tete +
+
+ ECOLE SUPERIEURE POLYTECHNIQUE + REPOBLIKAN’I MADAGASIKARA + Fitiavana-Tanindrazana-Fandrosoana + ********************* +

RELEVÉE DE NOTE

+
+
+
+ Nom & Prénoms: + {nomPrenom} +
+
+ Année scolaire: + {annee_scolaire} + Niveau: + {niveau} + inscription: + {inscription} +
+
+ + + + + + + + + + + + + {generateTableRows(combinedSemesters)} + + + + + + + + + + + + + + + + +
+ Unités
d’Enseignement
(UE) +
+ Eléments constitutifs + + Crédits + + Note + + Moyenne +
+ {' '} + Total crédit: + + {crontCredit()} +
+ Moyenne générale : + + {countMoyenneGeneral()} +
+ Observation : + + +
+
+
+ Décision de jury : + +
+

Le Directeur

+
+

RATSIMBAZAFY Christian Pierre

+
+
+
+
+
+ ) +} + +export default TesteDatagrid diff --git a/src/renderer/src/components/TrancheEcolage.jsx b/src/renderer/src/components/TrancheEcolage.jsx new file mode 100644 index 0000000..b209e57 --- /dev/null +++ b/src/renderer/src/components/TrancheEcolage.jsx @@ -0,0 +1,194 @@ +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 AjoutTranche from './AjoutTranche' +import { Tooltip } from 'react-tooltip' +import { FaPenToSquare } from 'react-icons/fa6' +import { FaTrash } from 'react-icons/fa' +import UpdateTranche from './UpdateTranche' +import DeleteTranche from './DeleteTranche' + +const TrancheEcolage = () => { + const { id } = useParams() + const [tranche, setTranche] = useState([]) + const [etudiant, setEtudiant] = useState({}) + + useEffect(() => { + window.etudiants.getTranche({ id }).then((response) => { + setTranche(response) + }) + + window.etudiants.getSingle({ id }).then((response) => { + setEtudiant(response) + }) + }, []) + + const [openAdd, setOpenAdd] = useState(false) + const onCloseAdd = () => setOpenAdd(false) + + const openAddFunction = () => { + setOpenAdd(true) + } + + const [isSubmited, setIsSubmited] = useState(false) + const handleFormSubmit = (status) => { + setIsSubmited(status) + } + + const [openUpdate, setOpenUpdate] = useState(false) + const onCloseUpdate = () => setOpenUpdate(false) + const [idToSend, setIdToSend] = useState(null) + const [idToSend2, setIdToSend2] = useState(null) + + const openUpdateFunction = (id) => { + setOpenUpdate(true) + setIdToSend(id) + } + + const [openDelete, setOpenDelete] = useState(false) + const onCloseDelete = () => setOpenDelete(false) + const openDeleteFunction = (id) => { + setOpenDelete(true) + setIdToSend2(id) + } + + useEffect(() => { + if (isSubmited) { + window.etudiants.getTranche({ id }).then((response) => { + setTranche(response) + }) + setIsSubmited(false) + } + }, [isSubmited]) + + return ( +
+ + + +
+
+
+

Tranche d'Ecolage

+
+ + + + window.history.back()}> + + +
+
+
+
+ +
+ + + + + + + + + + + + + + + {tranche.map((tranch, index) => ( + + + + + + + ))} + +
+
+ Evolution d'écolage de {etudiant.nom} {etudiant.prenom} +
+
Tranche N°DésignationMontantAction
{index + 1}{tranch.tranchename}{Number(tranch.montant).toLocaleString(0, 3)} + + +
+
+
+
+ ) +} + +export default TrancheEcolage diff --git a/src/renderer/src/components/UpdateModalProf.jsx b/src/renderer/src/components/UpdateModalProf.jsx new file mode 100644 index 0000000..735b411 --- /dev/null +++ b/src/renderer/src/components/UpdateModalProf.jsx @@ -0,0 +1,179 @@ +import React, { useEffect, useRef, useState } from 'react' +import { + Dialog, + DialogActions, + DialogContent, + DialogTitle, + TextField, + Button, + InputAdornment, + Box, + Grid +} from '@mui/material' +import ChangeCapital from './function/ChangeCapitalLetter' +import ChangeCapitalize from './function/ChangeCapitalizeLetter' +import { PiChalkboardTeacher } from 'react-icons/pi' +import { MdContactPhone, MdDateRange } from 'react-icons/md' + +const UpdateModalProf = ({ open, onClose, matiere_id, onSubmitSuccess }) => { + const [formData, setFormData] = useState({ + nom_enseignant: '', + prenom_enseignant: '', + contact: '', + date: '', + matiere_id: '' + }) + + const nomRefs = useRef() + const prenomRefs = useRef() + + const handleChange = (e) => { + const { name, value } = e.target + setFormData({ ...formData, [name]: value }) + } + + useEffect(() => { + let id = matiere_id + if (id !== '') { + window.matieres.getSingleProf({ id }).then((response) => { + setFormData((prev) => ({ + ...prev, + nom_enseignant: response.nom_enseignant, + prenom_enseignant: response.prenom_enseignant, + contact: response.contact, + date: response.date, + matiere_id: matiere_id + })) + }) + } + }, [matiere_id]) + + const handleSubmit = async (e) => { + e.preventDefault() + let response = await window.matieres.updateProf(formData) + + if (response.changes) { + onSubmitSuccess(true) + onClose() // Close the modal after submission + } + } + + return ( + +
+ mise à jour enseignant + + + + + ChangeCapital(nomRefs)} + onChange={handleChange} + InputProps={{ + startAdornment: ( + + + + ) + }} + /> + + + ChangeCapitalize(prenomRefs)} + onChange={handleChange} + InputProps={{ + startAdornment: ( + + + + ) + }} + /> + + + + + + ) + }} + /> + + + + + + ) + }} + /> + + + + + + + + +
+
+ ) +} + +export default UpdateModalProf diff --git a/src/renderer/src/components/UpdateParcour.jsx b/src/renderer/src/components/UpdateParcour.jsx new file mode 100644 index 0000000..f00e6f4 --- /dev/null +++ b/src/renderer/src/components/UpdateParcour.jsx @@ -0,0 +1,178 @@ +import React, { useEffect, useRef, useState } from 'react' +import { + Dialog, + DialogActions, + DialogContent, + DialogTitle, + TextField, + Button, + Autocomplete, + InputAdornment, + Box, + Grid +} from '@mui/material' +import { MdRule } from 'react-icons/md' +import { FaClipboardList } from 'react-icons/fa' + +const AddParcours = ({ open, onClose, onSubmitSuccess, id }) => { + const [formData, setFormData] = useState({ + nom: '', + uniter: '', + mention_id: '', + id: '' + }) + + const [mention, setMention] = useState([]) + const [parcour, setParcour] = useState([]) + + useEffect(() => { + window.mention.getMention().then((response) => { + setMention(response) + }) + }, []) + + useEffect(() => { + if (id) { + window.notesysteme.getSingleParcours({ id }).then((response) => { + setParcour(response) + }) + } + }, [id]) + + useEffect(() => { + if (parcour) { + setFormData({ + nom: parcour.nom, + uniter: parcour.uniter, + mention_id: parcour.mention_id, + id: parcour.id + }) + } + }, [parcour]) + + const handleChange = (e) => { + const { name, value } = e.target + setFormData({ ...formData, [name]: value }) + } + + const handleSubmit = async (e) => { + e.preventDefault() + let response = await window.notesysteme.updateParcours(formData) + + if (response.changes) { + onSubmitSuccess(true) + onClose() // Close the modal after submission + } + } + + return ( + +
+ Mise à jour du parcour + + + + + + + + ) + }} + /> + + + + + + ) + }} + /> + + + option.nom || ''} // Safely access `nom` + value={mention.find((item) => item.id === formData.mention_id) || null} // Bind selected value to form data + onChange={(event, newValue) => { + setFormData((prevData) => ({ + ...prevData, + mention_id: newValue ? newValue.id : null // Store the ID of the selected mention + })) + }} + isOptionEqualToValue={(option, value) => option.id === value.id} // Ensure correct matching of options + renderInput={(params) => ( + + + + + {params.InputProps.startAdornment} + + ) + }} + sx={{ + marginBottom: '5px', + '& .MuiOutlinedInput-root': { + '&:hover fieldset': { + borderColor: '#ff9800' // Set border color on hover + } + } + }} + /> + )} + /> + + + + + + + + +
+
+ ) +} + +export default AddParcours diff --git a/src/renderer/src/components/UpdateTranche.jsx b/src/renderer/src/components/UpdateTranche.jsx new file mode 100644 index 0000000..be70be6 --- /dev/null +++ b/src/renderer/src/components/UpdateTranche.jsx @@ -0,0 +1,128 @@ +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() + console.log(formData) + let response = await window.etudiants.updateTranche(formData) + + if (response.changes) { + onClose() + onSubmitSuccess(true) + } + } + + return ( + +
+ Ajout tranche + + + + + + + + ) + }} + /> + + + + + + ) + }} + /> + + + + + + + + +
+
+ ) +} + +export default UpdateTranche diff --git a/src/renderer/src/components/function/CalculNote.js b/src/renderer/src/components/function/CalculNote.js new file mode 100644 index 0000000..e0fb7fb --- /dev/null +++ b/src/renderer/src/components/function/CalculNote.js @@ -0,0 +1,5 @@ +const calculNote = (note, coeficient) => { + return note * coeficient +} + +export default calculNote diff --git a/src/renderer/src/components/function/ChangeCapitalLetter.js b/src/renderer/src/components/function/ChangeCapitalLetter.js new file mode 100644 index 0000000..70fee34 --- /dev/null +++ b/src/renderer/src/components/function/ChangeCapitalLetter.js @@ -0,0 +1,7 @@ +const ChangeCapital = (ref) => { + if (ref.current) { + ref.current.value = ref.current.value.toUpperCase() + } +} + +export default ChangeCapital diff --git a/src/renderer/src/components/function/ChangeCapitalizeLetter.js b/src/renderer/src/components/function/ChangeCapitalizeLetter.js new file mode 100644 index 0000000..4f33dd5 --- /dev/null +++ b/src/renderer/src/components/function/ChangeCapitalizeLetter.js @@ -0,0 +1,11 @@ +const ChangeCapitalize = (ref) => { + if (ref.current) { + ref.current.value = ref.current.value + .toLowerCase() // Ensure all text is lowercase first + .split(' ') // Split the text into words by spaces + .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) // Capitalize the first letter of each word + .join(' ') // Join the words back with spaces + } +} + +export default ChangeCapitalize diff --git a/src/renderer/src/components/function/FonctionRelever.js b/src/renderer/src/components/function/FonctionRelever.js new file mode 100644 index 0000000..19b23d8 --- /dev/null +++ b/src/renderer/src/components/function/FonctionRelever.js @@ -0,0 +1,41 @@ +export const getmentionAfterNotes = (notes) => { + if (notes >= 10 && notes < 12) { + return 'Passable' + } else if (notes >= 12 && notes < 14) { + return 'Assez bien' + } else if (notes >= 14 && notes < 16) { + return 'Bien' + } else if (notes >= 16) { + return 'Très bien' + } else { + return 'Insuffisant' + } +} + +function nextLevel(niveau) { + if (niveau == 'L1') { + return 'L2' + } else if (niveau == 'L2') { + return 'L3' + } else if (niveau == 'L3') { + return 'M1' + } else if (niveau == 'M1') { + return 'M2' + } else if (niveau == 'M2') { + return 'D1' + } else if (niveau == 'D1') { + return 'D2' + } else if (niveau == 'D2') { + return 'D3' + } else if (niveau == 'D3') { + return 'PHD' + } +} + +export const descisionJury = (notes, niveau) => { + if (notes >= 10) { + return `Admis en ${nextLevel(niveau)}` + } else { + return 'Vous redoublez' + } +} diff --git a/src/renderer/src/components/function/GenerateFiche.js b/src/renderer/src/components/function/GenerateFiche.js new file mode 100644 index 0000000..cc49023 --- /dev/null +++ b/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 Prénom', '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() +} diff --git a/src/renderer/src/components/function/GetSemestre.js b/src/renderer/src/components/function/GetSemestre.js new file mode 100644 index 0000000..5542db6 --- /dev/null +++ b/src/renderer/src/components/function/GetSemestre.js @@ -0,0 +1,23 @@ +function getSemestre(niveau) { + if (niveau === 'L1') { + return ['S1', 'S2'] + } else if (niveau === 'L2') { + return ['S3', 'S4'] + } else if (niveau === 'L3') { + return ['S5', 'S6'] + } else if (niveau === 'M1') { + return ['S7', 'S8'] + } else if (niveau === 'M2') { + return ['S9', 'S10'] + } else if (niveau === 'D') { + return ['S11', 'S12'] + } else if (niveau === 'D2') { + return ['S13', 'S14'] + } else if (niveau === 'D3') { + return ['S15', 'S16'] + } else { + return [] // Return an empty array if the niveau is not found + } +} + +export default getSemestre diff --git a/src/renderer/src/components/function/PDFEditor.js b/src/renderer/src/components/function/PDFEditor.js new file mode 100644 index 0000000..ea82ea5 --- /dev/null +++ b/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 prénom: ${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 diff --git a/src/renderer/src/components/function/PDFEditorCertificate.js b/src/renderer/src/components/function/PDFEditorCertificate.js new file mode 100644 index 0000000..a0fbbc8 --- /dev/null +++ b/src/renderer/src/components/function/PDFEditorCertificate.js @@ -0,0 +1,54 @@ +import { PDFDocument } from 'pdf-lib' +import { saveAs } from 'file-saver' +import PDF from '../../assets/certificat scolariter.pdf' + +const PDFEditorCertificat = async (data) => { + function formatDate(dateString) { + const dateObject = new Date(dateString) + return dateObject.toLocaleDateString('fr-FR', { + day: '2-digit', + month: 'long', + year: 'numeric' + }) + } + + function writeniveau(niveaus) { + const niveauxMap = { + L1: '1e année de licences', + L2: '2e année de licences', + L3: '3e année de licences', + M1: '1e année de master', + M2: '2e année de master', + D1: '1e année en doctorat', + D2: '2e année en doctorat', + D3: '3e année en doctorat' + } + return niveauxMap[niveaus] || 'Niveau inconnu' + } + + const existingPdfBytes = await fetch(PDF).then((res) => res.arrayBuffer()) + + // Load the existing PDF + const pdfDoc = await PDFDocument.load(existingPdfBytes) + + // Get the form fields and fill them + const form = pdfDoc.getForm() + form.getTextField('f1').setText(data.f1) + form.getTextField('f2').setText(formatDate(data.f2)) + form.getTextField('f3').setText(data.f3) // Nom du père + form.getTextField('f4').setText(data.f4) // Nom de la mère + form.getTextField('f5').setText(writeniveau(data.f5)) + form.getTextField('f6').setText(data.f6) + form.getTextField('f7').setText(data.f7) + form.getTextField('f8').setText(data.f8) + form.getTextField('f9').setText(data.f9) // Nom du service + + // Save the modified PDF + const pdfBytes = await pdfDoc.save() + + // Trigger the download + const blob = new Blob([pdfBytes], { type: 'application/pdf' }) + saveAs(blob, `certificat_de_scolarite_${data.f1}.pdf`) +} + +export default PDFEditorCertificat diff --git a/src/renderer/src/components/function/PDFEditorRecepisse.js b/src/renderer/src/components/function/PDFEditorRecepisse.js new file mode 100644 index 0000000..9529704 --- /dev/null +++ b/src/renderer/src/components/function/PDFEditorRecepisse.js @@ -0,0 +1,31 @@ +import { PDFDocument } from 'pdf-lib' +import { saveAs } from 'file-saver' +import PDF from '../../assets/recepice.pdf' +import dayjs from 'dayjs' + +const PDFEditorRecepice = async (data) => { + const existingPdfBytes = await fetch(PDF).then((res) => res.arrayBuffer()) + const fullDate = dayjs().format('YYYY-MM-DD') + + // Load the existing PDF + const pdfDoc = await PDFDocument.load(existingPdfBytes) + + // Get the form fields and fill them + const form = pdfDoc.getForm() + form.getTextField('f1').setText(data.f1) + form.getTextField('f2').setText(data.f2) + form.getTextField('f3').setText(data.f3) + form.getTextField('f4').setText(data.f4) + form.getTextField('f5').setText(fullDate) + form.getTextField('f6').setText(data.f6) + form.getTextField('f7').setText(data.f7) + + // Save the modified PDF + const pdfBytes = await pdfDoc.save() + + // Trigger the download + const blob = new Blob([pdfBytes], { type: 'application/pdf' }) + saveAs(blob, `recepisser_${data.f3}.pdf`) +} + +export default PDFEditorRecepice diff --git a/src/renderer/src/components/function/PDFEditorStage.js b/src/renderer/src/components/function/PDFEditorStage.js new file mode 100644 index 0000000..617259a --- /dev/null +++ b/src/renderer/src/components/function/PDFEditorStage.js @@ -0,0 +1,27 @@ +import { PDFDocument } from 'pdf-lib' +import { saveAs } from 'file-saver' +import PDF from '../../assets/autorisationstage.pdf' +import dayjs from 'dayjs' + +const PDFEditorStage = async (data) => { + const existingPdfBytes = await fetch(PDF).then((res) => res.arrayBuffer()) + + const fullDate = dayjs().format('YYYY-MM-DD') + + // Load the existing PDF + const pdfDoc = await PDFDocument.load(existingPdfBytes) + + // Get the form fields and fill them + const form = pdfDoc.getForm() + form.getTextField('f1').setText(data.f1) + form.getTextField('f2').setText(data.f1) + + // Save the modified PDF + const pdfBytes = await pdfDoc.save() + + // Trigger the download + const blob = new Blob([pdfBytes], { type: 'application/pdf' }) + saveAs(blob, `autorisation_stage${fullDate}.pdf`) +} + +export default PDFEditorStage diff --git a/src/renderer/src/components/function/PDFEditorV2.js b/src/renderer/src/components/function/PDFEditorV2.js new file mode 100644 index 0000000..5cf8899 --- /dev/null +++ b/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 prénom: ${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) +} diff --git a/src/renderer/src/components/function/PDFReader.jsx b/src/renderer/src/components/function/PDFReader.jsx new file mode 100644 index 0000000..5f85534 --- /dev/null +++ b/src/renderer/src/components/function/PDFReader.jsx @@ -0,0 +1,18 @@ +import {} from '@react-pdf-viewer/DefaultLayout' + +const PDFReader = () => { + return ( + + + + Section #1 + + + Section #2 + + + + ) +} + +export default PDFReader diff --git a/src/renderer/src/components/function/ProcessNote.js b/src/renderer/src/components/function/ProcessNote.js new file mode 100644 index 0000000..5199cf2 --- /dev/null +++ b/src/renderer/src/components/function/ProcessNote.js @@ -0,0 +1,34 @@ +// Function to process and combine notes +function ProcessNotes(notes) { + const groupedBySemestre = notes.reduce((acc, note) => { + if (!acc[note.semestre]) acc[note.semestre] = {} + if (!acc[note.semestre][note.ue]) acc[note.semestre][note.ue] = [] + + acc[note.semestre][note.ue].push(note) + return acc + }, {}) + + const result = {} + for (const semestre in groupedBySemestre) { + result[semestre] = [] + for (const ue in groupedBySemestre[semestre]) { + const elements = groupedBySemestre[semestre][ue] + const totalCredits = elements.reduce((sum, el) => sum + el.credit, 0) + const moyenne = elements.reduce((sum, el) => sum + el.note, 0) / elements.length + + elements.forEach((el, index) => { + result[semestre].push({ + ue, + element: el.element, + credits: el.credits, + note: el.note, + totalCredits: index === 0 ? totalCredits : '', // Only display on the first row for each UE + moyenne: index === 0 ? moyenne.toFixed(2) : '' // Only display on the first row for each UE + }) + }) + } + } + return result +} + +export default ProcessNotes diff --git a/src/renderer/src/components/validation/AddAdmin.js b/src/renderer/src/components/validation/AddAdmin.js new file mode 100644 index 0000000..b89e52f --- /dev/null +++ b/src/renderer/src/components/validation/AddAdmin.js @@ -0,0 +1,53 @@ +/** + * function to use to validate the add admin + * @param {*} username + * @param {*} email + * @param {*} password + * @param {*} usernameError + * @param {*} emailError + * @param {*} passwordError + * @returns Boolean + */ +const validationAddAdmin = ( + username, + email, + password, + usernameError, + emailError, + passwordError +) => { + let isValid = true + + const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/ + + // Email validation + if (email.value === '') { + isValid = false + emailError.textContent = 'Email est requis' + } else if (!emailPattern.test(email.value)) { + isValid = false + emailError.textContent = 'Entrez une adresse email valide' + } else { + emailError.textContent = '' + } + + // Password validation + if (password.value === '' || password.value.length < 6) { + isValid = false + passwordError.textContent = 'Le mot de passe doit comporter plus de 6 caractères.' + } else { + passwordError.textContent = '' + } + + // username validation + if (username.value === '') { + isValid = false + usernameError.textContent = 'username réquis.' + } else { + usernameError.textContent = '' + } + + return isValid +} + +export default validationAddAdmin diff --git a/src/renderer/src/components/validation/AddNiveau.js b/src/renderer/src/components/validation/AddNiveau.js new file mode 100644 index 0000000..f907faa --- /dev/null +++ b/src/renderer/src/components/validation/AddNiveau.js @@ -0,0 +1,24 @@ +/** + * + * @param {*} nom + * @param {*} error + * @param {Array} arrayNiveau + * @returns boolean + */ +const validationNote = (nom, error, arrayNiveau) => { + let isValid = true + + if (nom.value === '') { + error.textContent = 'Nom requis pour soumettre la formulaire' + isValid = false + } else if (arrayNiveau.includes(nom.value) === true) { + error.textContent = `${nom.value} existe déjà` + isValid = false + } else { + error.textContent = '' + } + + return isValid +} + +export default validationNote diff --git a/src/renderer/src/components/validation/AddNote.js b/src/renderer/src/components/validation/AddNote.js new file mode 100644 index 0000000..100b363 --- /dev/null +++ b/src/renderer/src/components/validation/AddNote.js @@ -0,0 +1,25 @@ +/** + * function used to validate add note + * @returns boolean + */ +const validateAddNote = () => { + let input = document.querySelectorAll('.inputAddNote input') + let isValid = false + let count = 0 + + for (let index = 0; index < input.length; index++) { + if (input[index].value == undefined || input[index].value == '') { + count += 1 + } + } + + if (count < input.length) { + isValid = true + } else { + isValid = false + } + + return isValid +} + +export default validateAddNote diff --git a/src/renderer/src/components/validation/ForgotPassword.js b/src/renderer/src/components/validation/ForgotPassword.js new file mode 100644 index 0000000..2e8b260 --- /dev/null +++ b/src/renderer/src/components/validation/ForgotPassword.js @@ -0,0 +1,50 @@ +/** + * function to make validation in the forgot password + * @param {*} email + * @param {*} password + * @param {*} passwordC + * @param {*} emailError + * @param {*} passwordError + * @param {*} passwordCError + */ +export const validationForgotPassword = ( + email, + password, + passwordC, + emailError, + passwordError, + passwordCError +) => { + let isValid = true + + const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/ + + // Email validation + if (email.value === '') { + isValid = false + emailError.textContent = 'Email est requis' + } else if (!emailPattern.test(email.value)) { + isValid = false + emailError.textContent = 'Entrez une adresse email valide' + } else { + emailError.textContent = '' + } + + // Password validation + if (password.value === '' || password.value.length < 6) { + isValid = false + passwordError.textContent = 'Le mot de passe doit comporter plus de 6 caractères.' + } else { + passwordError.textContent = '' + } + + // Confirm Password validation + if (passwordC.value !== password.value) { + isValid = false + passwordCError.textContent = 'Le mot de passe ne correspond pas.' + } else { + passwordCError.textContent = '' + } + + return isValid +} diff --git a/src/renderer/src/components/validation/Login.js b/src/renderer/src/components/validation/Login.js new file mode 100644 index 0000000..15cf5ea --- /dev/null +++ b/src/renderer/src/components/validation/Login.js @@ -0,0 +1,50 @@ +/** + * function to validate the email and password input + * + * @param {any} userNameRef + * @param {any} password + * @param {any} userNameError + * @param {any} passwordError + * @returns {boolean} + */ +export const ValidationLogin = (userNameRef, password, userNameError, passwordError) => { + let isValid = true + + // Validate username + if (userNameRef.value == '' || userNameRef.value == null) { + isValid = false + userNameError.textContent = "Entrer un nom d'utilisateur" + } + // Validate username + else if (password.value == '' || password.value.length < 6) { + passwordError.textContent = `mot de passe doit être plus de 6 caractères.` + isValid = false + } else { + isValid = true + userNameError.textContent = '' + passwordError.textContent = '' + } + + // remake varification username + if (userNameRef.value !== '') { + userNameError.textContent = '' + } + + // remake varification password + if (password.value.length > 6) { + passwordError.textContent = '' + } else { + passwordError.textContent = `mot de passe doit être plus de 6 caractères.` + } + + return isValid +} + +/** + * function to use when user submit an invalid information + * + * @param {any} userNameError + */ +export const invalidCredential = (userNameError) => { + userNameError.textContent = "Nom d'utilisateur ou Mot de passe incorrect" +} diff --git a/src/renderer/src/components/validation/NoteSystem.js b/src/renderer/src/components/validation/NoteSystem.js new file mode 100644 index 0000000..85200c2 --- /dev/null +++ b/src/renderer/src/components/validation/NoteSystem.js @@ -0,0 +1,29 @@ +/** + * function to validate ours systeme note + * @param {*} admis + * @param {*} redouble + * @param {*} renvoyer + * @returns Boolean + */ +const validateNOteSystem = (admis, redouble, renvoyer) => { + let isValid = true + + // Check for 'admis' + if (admis.value === '' || isNaN(Number(admis.value))) { + isValid = false + } + + // Check for 'redouble' + if (redouble.value === '' || isNaN(Number(redouble.value))) { + isValid = false + } + + // Check for 'renvoyer' + if (renvoyer.value === '' || isNaN(Number(renvoyer.value))) { + isValid = false + } + + return isValid +} + +export default validateNOteSystem diff --git a/src/renderer/src/components/validation/ReleverDeNote.js b/src/renderer/src/components/validation/ReleverDeNote.js new file mode 100644 index 0000000..54e44de --- /dev/null +++ b/src/renderer/src/components/validation/ReleverDeNote.js @@ -0,0 +1,23 @@ +/** + * function used to validate export relever de note + * @returns {Boolean} + */ +const ReleverDeNote = () => { + let allInput = document.querySelectorAll('.inputToValidateExport input') + let isValid = true + + /** + * each input we will test if there is an empty field, if there is an empty field + * then we will stop the script and send a false value instead of true + */ + for (let index = 0; index < allInput.length; index++) { + if (allInput[index].value == '') { + isValid = false + break + } + } + + return isValid +} + +export default ReleverDeNote diff --git a/src/renderer/src/components/validation/Setting.js b/src/renderer/src/components/validation/Setting.js new file mode 100644 index 0000000..0cb5930 --- /dev/null +++ b/src/renderer/src/components/validation/Setting.js @@ -0,0 +1,74 @@ +/** + * Function to use when user updates their profile + * + * @param {*} username + * @param {*} email + * @param {*} password + * @param {*} newPassword + * @param {*} usernameError + * @param {*} emailError + * @param {*} passwordError + * @param {*} newPasswordError + * @returns Boolean + */ +const validationSetting = ( + username, + email, + password, + newPassword, + usernameError, + emailError, + passwordError, + newPasswordError +) => { + let isValid = true + + const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/ + + // Email validation + if (email && email.value !== undefined) { + if (email.value === '') { + isValid = false + emailError.textContent = 'Email est requis' + } else if (!emailPattern.test(email.value)) { + isValid = false + emailError.textContent = 'Entrez une adresse email valide' + } else { + emailError.textContent = '' + } + } + + // Password validation + if (password && password.value !== undefined) { + if (password.value === '' || password.value.length < 6) { + isValid = false + passwordError.textContent = 'Le mot de passe doit comporter plus de 6 caractères.' + } else { + passwordError.textContent = '' + } + } + + // Username validation + if (username && username.value !== undefined) { + if (username.value === '') { + isValid = false + usernameError.textContent = 'Username requis.' + } else { + usernameError.textContent = '' + } + } + + // New password validation + if (newPassword && newPassword.value !== undefined) { + if (newPassword.value !== '' && newPassword.value.length < 6) { + isValid = false + newPasswordError.textContent = 'Le mot de passe doit comporter plus de 6 caractères.' + } else { + newPasswordError.textContent = '' + } + } + + return isValid +} + +export default validationSetting diff --git a/src/renderer/src/components/validation/SingleNiveau.js b/src/renderer/src/components/validation/SingleNiveau.js new file mode 100644 index 0000000..56ade01 --- /dev/null +++ b/src/renderer/src/components/validation/SingleNiveau.js @@ -0,0 +1,24 @@ +/** + * function to validate + * @param {*} nom + * @param {*} error + * @param {*} arrayNiveau + * @returns Boolean + */ +const validationSingleNiveau = (nom, error, arrayNiveau) => { + let isValid = true + + if (nom.value === '') { + error.textContent = 'Nom requis pour soumettre la formulaire' + isValid = false + } else if (arrayNiveau.includes(nom.value) === true) { + error.textContent = `${nom.value} existe déjà` + isValid = false + } else { + error.textContent = '' + } + + return isValid +} + +export default validationSingleNiveau diff --git a/src/renderer/src/components/validation/ValidationAddAnneeScolaire.js b/src/renderer/src/components/validation/ValidationAddAnneeScolaire.js new file mode 100644 index 0000000..3fab8d8 --- /dev/null +++ b/src/renderer/src/components/validation/ValidationAddAnneeScolaire.js @@ -0,0 +1,18 @@ +/** + * function validation année scolaire form + * @param {*} code + * @param {*} debut + * @param {*} fin + * @returns {Boolean} + */ +const validationAnneeScolaire = (code, debut, fin) => { + let isValid = true + + if (code.value === '' || debut.value === '' || fin.value === '') { + isValid = false + } + + return isValid +} + +export default validationAnneeScolaire diff --git a/src/renderer/src/components/validation/ValidationMatiereAdd.js b/src/renderer/src/components/validation/ValidationMatiereAdd.js new file mode 100644 index 0000000..4027123 --- /dev/null +++ b/src/renderer/src/components/validation/ValidationMatiereAdd.js @@ -0,0 +1,25 @@ +/** + * function to validate add matiere + * + * @param {*} nom + * @param {*} error + * @param {*} arrayMatiere + * @returns Boolean + */ +const validationMatiereAdd = (nom, error, arrayMatiere, creditRef) => { + let isValid = true + + if (nom.value === '' || creditRef.value === null || creditRef.value === '') { + error.textContent = 'les champs sont requis pour soumettre la formulaire' + isValid = false + } else if (arrayMatiere.includes(nom.value) === true) { + error.textContent = `${nom.value} existe déjà` + isValid = false + } else { + error.textContent = '' + } + + return isValid +} + +export default validationMatiereAdd diff --git a/src/renderer/src/contexts/AuthContext.jsx b/src/renderer/src/contexts/AuthContext.jsx new file mode 100644 index 0000000..c963ce5 --- /dev/null +++ b/src/renderer/src/contexts/AuthContext.jsx @@ -0,0 +1,38 @@ +import { createContext, useContext, useState } from 'react' + +const AuthContext = createContext({ + user: null, + token: null, + setUser: () => {}, + setToken: () => {} +}) + +export const AuthContextProvider = ({ children }) => { + const [user, setUser] = useState() + const [token, _setToken] = useState(localStorage.getItem('ACCESS_TOKEN')) + + const setToken = (token) => { + _setToken(token) + + if (token) { + localStorage.setItem('ACCESS_TOKEN', token) + } else { + localStorage.removeItem('ACCESS_TOKEN') + } + } + + return ( + + {children} + + ) +} + +export const useAuthContext = () => useContext(AuthContext) diff --git a/src/renderer/src/contexts/BackgroundContext.jsx b/src/renderer/src/contexts/BackgroundContext.jsx new file mode 100644 index 0000000..8601ce2 --- /dev/null +++ b/src/renderer/src/contexts/BackgroundContext.jsx @@ -0,0 +1,41 @@ +import React, { useEffect, useState } from 'react' +import bgImage1 from '../assets/bg1-min.jpg' +import bgImage2 from '../assets/bg2-min.jpg' + +const BackgroundContext = ({ children }) => { + const [backgroundImage, setBackgroundImage] = useState('') // Background image URL + const [loading, setLoading] = useState(true) // To manage loading state + + useEffect(() => { + const images = [bgImage1, bgImage2] + + // Select a random image + const randomImage = images[Math.floor(Math.random() * images.length)] + + // Preload the image + const img = new Image() + img.src = randomImage + + img.onload = () => { + setBackgroundImage(randomImage) // Set the background image once loaded + setLoading(false) // Stop loading + } + }, []) + + return ( +
+ {loading ? '' : children} + {/* Optionally add a loader while the background is being loaded */} +
+ ) +} + +export default BackgroundContext diff --git a/src/renderer/src/contexts/MoyenneDeClasseContext.jsx b/src/renderer/src/contexts/MoyenneDeClasseContext.jsx new file mode 100644 index 0000000..b440223 --- /dev/null +++ b/src/renderer/src/contexts/MoyenneDeClasseContext.jsx @@ -0,0 +1,11 @@ +import React, { createContext, useState } from 'react' + +// Create the context +export const DataContext = createContext() + +// Create the provider component +export const DataProvider = ({ children }) => { + const [data, setData] = useState(null) // State to hold data + + return {children} +} diff --git a/src/renderer/src/images/fab.jpg b/src/renderer/src/images/fab.jpg new file mode 100644 index 0000000..6239012 Binary files /dev/null and b/src/renderer/src/images/fab.jpg differ diff --git a/src/renderer/src/images/icon.png b/src/renderer/src/images/icon.png new file mode 100644 index 0000000..cf9e8b2 Binary files /dev/null and b/src/renderer/src/images/icon.png differ diff --git a/src/renderer/src/layouts/DefaultLayout.jsx b/src/renderer/src/layouts/DefaultLayout.jsx new file mode 100644 index 0000000..045a30e --- /dev/null +++ b/src/renderer/src/layouts/DefaultLayout.jsx @@ -0,0 +1,35 @@ +import React from 'react' +import { Outlet, Navigate } from 'react-router-dom' +import { useAuthContext } from '../contexts/AuthContext' +import classe from '../assets/DefaultLayout.module.css' +import Navbar from '../components/Navbar' +import Sidenav from '../components/Sidenav' + +const DefaultLayout = () => { + const { token } = useAuthContext() + + if (!token) { + return + } + + return ( +
+
+ {/* Nabvar */} + {/* */} + + {/* Content Area with Sidenav and Outlet */} +
+
+ +
+
+ +
+
+
+
+ ) +} + +export default DefaultLayout diff --git a/src/renderer/src/layouts/LoginLayout.jsx b/src/renderer/src/layouts/LoginLayout.jsx new file mode 100644 index 0000000..264f67c --- /dev/null +++ b/src/renderer/src/layouts/LoginLayout.jsx @@ -0,0 +1,50 @@ +import React, { useEffect, useState } from 'react' +import { Outlet, Navigate } from 'react-router-dom' +import { useAuthContext } from '../contexts/AuthContext' +import Navbar from '../components/Navbar' +import bgImage1 from '../assets/bg1-min.jpg' +import bgImage2 from '../assets/bg2-min.jpg' + +const LoginLayout = () => { + const { token } = useAuthContext() + + if (token) { + return + } + + const [backgroundImage, setBackgroundImage] = useState('') // Background image URL + const [loading, setLoading] = useState(true) // To manage loading state + + // useEffect(() => { + const images = [bgImage1, bgImage2] + + // Select a random image + const randomImage = images[Math.floor(Math.random() * images.length)] + + // Preload the image + const img = new Image() + img.src = randomImage + + img.onload = () => { + setBackgroundImage(randomImage) // Set the background image once loaded + setLoading(false) // Stop loading + } + // }, []); + + return ( +
+ + {/* Optionally add a loader while the background is being loaded */} +
+ ) +} + +export default LoginLayout diff --git a/src/renderer/src/main.jsx b/src/renderer/src/main.jsx new file mode 100644 index 0000000..8801049 --- /dev/null +++ b/src/renderer/src/main.jsx @@ -0,0 +1,12 @@ +import './assets/base.css' +import React from 'react' +import ReactDOM from 'react-dom/client' +import 'bootstrap/dist/css/bootstrap.min.css' +import App from './App' +import 'react-tooltip/dist/react-tooltip.css' + +ReactDOM.createRoot(document.getElementById('root')).render( + + + +) diff --git a/src/renderer/src/test/ModalAddEtudiantsTest.jsx b/src/renderer/src/test/ModalAddEtudiantsTest.jsx new file mode 100644 index 0000000..c3dfb1c --- /dev/null +++ b/src/renderer/src/test/ModalAddEtudiantsTest.jsx @@ -0,0 +1,76 @@ +import React, { useState } from 'react' +import { TextField, InputAdornment, Button } from '@mui/material' +import { FaUser } from 'react-icons/fa' + +const AddStudentForm = () => { + const [formData, setFormData] = useState({ nom: '' }) + const [errors, setErrors] = useState({ nom: '' }) + + const handleInputChange = (e) => { + const { name, value } = e.target + + // Set the form data + setFormData((prevData) => ({ + ...prevData, + [name]: value + })) + + // Clear errors when the user starts typing again + setErrors((prevErrors) => ({ + ...prevErrors, + [name]: '' // Clear the error for the specific field + })) + } + + const validate = () => { + let valid = true + let newErrors = {} + + // Example validation for "nom" field (it must not be empty) + if (!formData.nom.trim()) { + newErrors.nom = 'Nom is required' + valid = false + } + + setErrors(newErrors) + return valid + } + + const handleSubmit = (e) => { + e.preventDefault() + + // Validate the form + if (validate()) { + // Form is valid, handle the submission logic + console.log(formData) + } + } + + return ( +
+ + + + ) + }} + /> + + + + ) +} + +export default AddStudentForm diff --git a/src/renderer/src/test/TestPSD.JSX b/src/renderer/src/test/TestPSD.JSX new file mode 100644 index 0000000..810cfc7 --- /dev/null +++ b/src/renderer/src/test/TestPSD.JSX @@ -0,0 +1,75 @@ +import React, { useEffect, useRef, useState } from 'react' +import PSD from 'psd' + +function PSDViewer({ file }) { + const [imageSrc, setImageSrc] = useState('') + const [text, setText] = useState('Your Text Here') + const canvasRef = useRef(null) + + useEffect(() => { + const loadPSD = async () => { + try { + const psd = await PSD.fromURL(file) + const image = psd.image.toBase64() // Generate Base64 image + setImageSrc(`data:image/png;base64,${image}`) + } catch (error) { + console.error('Error loading PSD file:', error) + } + } + + loadPSD() + }, [file]) + + useEffect(() => { + if (imageSrc && canvasRef.current) { + const canvas = canvasRef.current + const ctx = canvas.getContext('2d') + + // Load the PSD image into the canvas + const img = new Image() + img.src = imageSrc + img.onload = () => { + canvas.width = img.width + canvas.height = img.height + ctx.drawImage(img, 0, 0) + + // Add text overlay + ctx.font = '48px Arial' + ctx.fillStyle = 'red' // Customize text color + ctx.fillText(text, 50, 100) // Customize position + } + } + }, [imageSrc, text]) + + const handleDownload = () => { + const canvas = canvasRef.current + const link = document.createElement('a') + link.download = 'edited-psd.png' + link.href = canvas.toDataURL('image/png') + link.click() + } + + return ( +
+

PSD Viewer and Editor

+ setText(e.target.value)} + placeholder="Enter text to overlay" + /> +
{imageSrc ? :

Loading PSD file...

}
+ +
+ ) +} + +function App() { + return ( +
+ +
+ ) +} + +export default App diff --git a/src/renderer/src/test/expalintolbar.txt b/src/renderer/src/test/expalintolbar.txt new file mode 100644 index 0000000..b666c75 --- /dev/null +++ b/src/renderer/src/test/expalintolbar.txt @@ -0,0 +1,101 @@ +To save changes made by the **DataGrid Toolbar Column Chooser** (``) to a temporary Excel file each time the visibility of columns changes, you can use the `onColumnVisibilityModelChange` event of the MUI `DataGrid`. This event triggers whenever column visibility changes, allowing you to update the temp file based on visible columns. + +Here's a step-by-step guide on implementing this: + +### 1. Add `onColumnVisibilityModelChange` Event to `DataGrid` + +Attach `onColumnVisibilityModelChange` to listen for column visibility changes. When triggered, this event can be used to filter the data based on the visible columns, generate a temporary Excel file (`tempFile`), and save it. + +### 2. Filter Data Based on Visible Columns and Export to Excel + +With `xlsx`, filter only the visible columns and write them to a temporary file each time the visibility model changes. + +### Code Implementation + +```javascript +import React, { useState } from 'react'; +import { DataGrid, GridToolbarContainer, GridToolbarColumnsButton } from '@mui/x-data-grid'; +import XLSX from 'xlsx'; + +const ExampleDataGrid = () => { + const [tableData, setTableData] = useState([ + { id: 1, nom: 'sakalava', prenom: 'gagag', age: 25 }, + { id: 2, nom: 'wild', prenom: 'fire', age: 30 }, + ]); + + const [columnVisibilityModel, setColumnVisibilityModel] = useState({ + nom: true, + prenom: true, + age: true, + }); + + const columns = [ + { field: 'nom', headerName: 'Nom', width: 200 }, + { field: 'prenom', headerName: 'Prenom', width: 200 }, + { field: 'age', headerName: 'Age', width: 100 }, + ]; + + // Handle column visibility changes + const handleColumnVisibilityChange = (newModel) => { + setColumnVisibilityModel(newModel); + updateTempFile(newModel); + }; + + // Update temp file based on visible columns + const updateTempFile = (visibilityModel) => { + const visibleColumns = columns + .filter((col) => visibilityModel[col.field]) + .map((col) => col.field); + + // Filter data based on visible columns + const filteredData = tableData.map((row) => { + const filteredRow = {}; + visibleColumns.forEach((col) => { + filteredRow[col] = row[col]; + }); + return filteredRow; + }); + + // Create a new worksheet and workbook + const worksheet = XLSX.utils.json_to_sheet(filteredData); + const workbook = XLSX.utils.book_new(); + XLSX.utils.book_append_sheet(workbook, worksheet, 'Sheet1'); + + // Write to a temporary file (Blob) + const tempFile = XLSX.write(workbook, { bookType: 'xlsx', type: 'blob' }); + + // Send tempFile to backend or save locally + console.log('Temporary file updated', tempFile); + }; + + return ( +
+ ( + + + + ), + }} + /> +
+ ); +}; + +export default ExampleDataGrid; +``` + +### Explanation + +1. **Event Listener**: `onColumnVisibilityModelChange` listens for visibility changes and updates `columnVisibilityModel`. +2. **Filtering Visible Columns**: `updateTempFile` uses `columnVisibilityModel` to filter `tableData` for only the visible columns. +3. **Writing to Excel**: The filtered data is written to a new workbook (`tempFile`) as an Excel file, which can be sent to the backend or saved locally. + +### Optional: Trigger Backend Upload + +To upload `tempFile` to your backend, use `FormData` and an `axios` POST request in `updateTempFile`. This ensures the temporary Excel file is updated on each column change event. diff --git a/src/renderer/src/test/qr.html b/src/renderer/src/test/qr.html new file mode 100644 index 0000000..3ec019c --- /dev/null +++ b/src/renderer/src/test/qr.html @@ -0,0 +1,54 @@ + + + + + + + Document + + +
+
+
+

université de toamasina

+

***********

+

+ ecole superieure
+ polytechnique +

+
+
+
+ +
+
+

Nom : BE

+

Prénom : Joseph Fabrice

+

Date de naissance : 11-12-2001

+

Niveau : L3

+

Année scolaire : 2023-2024

+

Num inscription : 12345678900

+
+
+
+
+
+
+

QR en ligne

+ +
+
+

QR locale

+ +
+
+
+
+ + diff --git a/src/renderer/src/test/relever.html b/src/renderer/src/test/relever.html new file mode 100644 index 0000000..ece43b4 --- /dev/null +++ b/src/renderer/src/test/relever.html @@ -0,0 +1,74 @@ + + + + + + Relevé de Note + + + +
+

REPOBLIKAN’I MADAGASIKARA

+

Fitiavana-Tanindrazana-Fandrosoana

+
+

MINISTÈRE DE L’ENSEIGNEMENT SUPÉRIEUR ET DE LA RECHERCHE SCIENTIFIQUE

+

UNIVERSITÉ DE TOAMASINA

+

ECOLE SUPERIEURE POLYTECHNIQUE

+

Fahaizaña sy Fañahy

+
+ +
+

RELEVÉE DE NOTE

+

Nom & Prénoms : F3

+

+ Niveau : L1 Année scolaire : 2022-2023 +

+

N° inscription : F42

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Unités d’Enseignement (UE)Eléments constitutifsCréditsNoteMoyenne
UEMI1Algèbre4F6F8
UEMI1Analyse5F5
UEPI1Mécanique Général I4F9F13
Total crédit30
+ +

Moyenne générale : F41

+

Observation : F47

+

Décision de jury : F46

+

Le Directeur : RATSIMBAZAFY Christian Pierre

+
+ + diff --git a/src/renderer/src/test/select2.jsx b/src/renderer/src/test/select2.jsx new file mode 100644 index 0000000..f1aa5f4 --- /dev/null +++ b/src/renderer/src/test/select2.jsx @@ -0,0 +1,44 @@ +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 + 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([]) + } +} diff --git a/src/renderer/src/test/style.css b/src/renderer/src/test/style.css new file mode 100644 index 0000000..ef64334 --- /dev/null +++ b/src/renderer/src/test/style.css @@ -0,0 +1,54 @@ +body { + font-family: Arial, sans-serif; + line-height: 1.6; + margin: 20px; +} + +.header { + text-align: center; +} + +.header h1, +.header h2, +.header h3 { + margin: 0; +} + +hr { + border: 1px solid black; + margin: 20px 0; +} + +.releve { + margin-top: 20px; +} + +table { + width: 100%; + border-collapse: collapse; + margin-bottom: 20px; +} + +table, +th, +td { + border: 1px solid black; +} + +th, +td { + padding: 10px; + text-align: left; +} + +th { + background-color: #f2f2f2; +} + +td { + text-align: center; +} + +strong { + font-weight: bold; +} diff --git a/src/renderer/src/test/testDate.txt b/src/renderer/src/test/testDate.txt new file mode 100644 index 0000000..edc8945 --- /dev/null +++ b/src/renderer/src/test/testDate.txt @@ -0,0 +1,84 @@ +In JavaScript, you can manipulate dates and work with months and years easily using the `Date` object. To check if the current month is July of the current year, you can do the following: + +### Steps: +1. **Get the current date**: You can use `new Date()` to get the current date. +2. **Check the current month**: Use `.getMonth()` to get the month (note that months are zero-indexed in JavaScript, meaning `0` is January, `1` is February, and so on). +3. **Check the current year**: Use `.getFullYear()` to get the current year. + +Here’s an example of how to check if it's July of the current year and perform an action: + +### Example Code: + +```javascript +// Get the current date +const currentDate = new Date(); + +// Get the current month (zero-indexed, so 6 represents July) +const currentMonth = currentDate.getMonth(); + +// Get the current year +const currentYear = currentDate.getFullYear(); + +// Check if the current month is July (month 6) and if it is the current year +if (currentMonth === 6) { // July is month 6 (zero-indexed) + console.log("We are in July this year!"); + // Perform your action here +} else { + console.log("It is not July."); +} +``` + +### **Explanation**: +1. `currentDate.getMonth()` will return the current month (0-11), so `6` represents July. +2. `currentDate.getFullYear()` will return the current year as a 4-digit number (e.g., `2024`). +3. The `if (currentMonth === 6)` checks if the current month is July. + +### **Performing Actions Based on Month and Year**: +You can replace the `console.log()` statement with any action or function you need to perform if the condition is met. For example: + +```javascript +if (currentMonth === 6 && currentYear === 2024) { + // Perform action for July 2024 + alert("It is July 2024! Performing action..."); +} else { + // Action for other months or years + alert("Not July 2024."); +} +``` + +### **Bonus: Working with Day.js or Moment.js**: +If you prefer working with date manipulation libraries like **Day.js** or **Moment.js**, you can make the code simpler. + +#### With **Day.js**: +```javascript +const dayjs = require('dayjs'); // If using Node.js, make sure to install dayjs first + +// Get the current date +const currentDate = dayjs(); + +// Check if we are in July this year +if (currentDate.month() === 6) { // 6 represents July + console.log("We are in July this year!"); +} +``` + +#### With **Moment.js**: +```javascript +const moment = require('moment'); // If using Node.js, make sure to install moment.js first + +// Get the current date +const currentDate = moment(); + +// Check if we are in July this year +if (currentDate.month() === 6) { // 6 represents July + console.log("We are in July this year!"); +} +``` + +### Summary: +- You can use `new Date()` to get the current date. +- Use `.getMonth()` to get the current month (remember it’s 0-indexed). +- Use `.getFullYear()` to get the current year. +- For better handling of dates and months, consider using libraries like **Day.js** or **Moment.js** for cleaner and more readable code. + +Let me know if you need more help with date manipulation! diff --git a/text.txt b/text.txt new file mode 100644 index 0000000..a28deb1 --- /dev/null +++ b/text.txt @@ -0,0 +1,417 @@ +14/ raha entree eo amlay username dia makeo am password raha vide ilay password sinon se connecter raha tsy vide ilay password +1/ Mot Login => Se connecter +2/ Mila ahena ny taille anlay titre rehetra +3/ Ajout etudiant miala ilay ANNULER +4/ Asina boutton retour rehefa manao ajout am page rehetra (Voir capture) +5/ Mampiasa librairie fileinput amlay upload photos rehetra +6/ Gestion de note: + => Choix de la classe + => Choix de l'eleve + => maka ny matiere rehetra par classe (mitovy amlay actuelle) +7/ Asiana gestion de classe +8/ Asiana gestion de note (Liste eleve par classe atao tableau) +9/ lay amsary profil etsy ambany ny contenue atao modification de mot de passe ny contenue +10/ Dashboard + => Nombre d'eleve + => Nombre de classe +11/ Ny tena important amzay ny generation de code QR par creation d'eleve +12/ Generation de carte d'etudiant + +13/ dia asiana page a propos koa ilay logiciel + +Pour moi : 6, 7, 8, 9, 10, 11, 12 +reto avy ny mila anlay API. +1/ Mise a jour du logiciel +2/ synchronisation des donnee. + - mpianatra (vita) + - classe + - matiere + - note + +. ajout etudiant, am le anarana refa mvoka carte lasa undefined +ASINA MESSAGE DE SUCCES SY EREUR NY AJOUT NOTE + +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 + +Yes, you can use `electron-updater` with other providers besides GitHub for managing app updates. Some popular alternatives include using a custom server or a file-based hosting solution like AWS S3, DigitalOcean, or even your own web server. Here’s how you can set it up. + +### Using a Custom Server for Updates + +#### 1. **Host Update Files on a Web Server** +You can host the update files (`.exe`, `.zip`, or `.dmg` for macOS) on your own server. This server only needs to serve the files—no GitHub release process required. + +#### Steps to set up: + +1. **Configure `package.json` for a custom provider:** + Update your `package.json` to point to the URL where the updates will be hosted. Here's an example configuration for using a custom server: + + ```json + { + "name": "your-app", + "version": "1.0.0", + "build": { + "publish": { + "provider": "generic", + "url": "https://yourserver.com/updates" + } + } + } + ``` + + - The `provider` is set to `"generic"`. + - The `url` is the base URL where your update files will be located. + +2. **Upload your release files to the server:** + After building your Electron app with a tool like `electron-builder`, you'll get your `*.exe`, `*.nupkg`, or other update files (for macOS, Linux). Upload these to your server under the `updates` folder specified in the URL. + + Example file structure on your server: + ``` + https://yourserver.com/updates/ + ├── latest.yml + ├── your-app-1.0.0.exe + ├── your-app-1.0.0.nupkg + ``` + + The `latest.yml` file is automatically generated by `electron-builder` and contains metadata about the latest version. The updater uses this file to determine if a new update is available. + +3. **Trigger Updates in Your Electron App:** + In your Electron app, set up the `autoUpdater` to check for updates and download them from your custom server. The logic will remain the same as when using GitHub releases. + + ```js + const { autoUpdater } = require('electron-updater'); + const log = require('electron-log'); + + autoUpdater.logger = log; + autoUpdater.logger.transports.file.level = 'info'; + + autoUpdater.on('update-available', (info) => { + log.info('Update available:', info); + }); + + autoUpdater.on('update-downloaded', (info) => { + log.info('Update downloaded:', info); + const dialogOpts = { + type: 'info', + buttons: ['Restart', 'Later'], + title: 'Update Available', + message: 'A new version has been downloaded. Restart the application to apply the update.', + detail: `Version ${info.version} is ready to install.`, + }; + + dialog.showMessageBox(dialogOpts).then((returnValue) => { + if (returnValue.response === 0) { + autoUpdater.quitAndInstall(); + } + }); + }); + + autoUpdater.checkForUpdatesAndNotify(); + ``` + +4. **Build and Deploy:** + When you build your app using `electron-builder`, it will create the necessary update files (`*.exe`, `.zip`, `.nupkg`, `latest.yml`, etc.). Upload these to the server so that the app can detect new versions. + +#### 2. **Use AWS S3 as a File Host** +You can use Amazon S3 to host the update files: + +1. **Configure `package.json` for AWS S3:** + ```json + { + "name": "your-app", + "version": "1.0.0", + "build": { + "publish": { + "provider": "s3", + "bucket": "your-bucket-name", + "region": "us-west-1" + } + } + } + ``` + +2. **Set Up AWS Credentials:** + You'll need to set AWS credentials in your environment to allow `electron-builder` to upload files to S3. + + ```bash + export AWS_ACCESS_KEY_ID=your-access-key + export AWS_SECRET_ACCESS_KEY=your-secret-key + ``` + +3. **Build and Upload:** + Build your app using `electron-builder`, and it will upload the necessary update files to your specified S3 bucket automatically. + +#### 3. **Use DigitalOcean Spaces (or similar)** +DigitalOcean Spaces works similarly to S3: + +1. **Configure `package.json` for DigitalOcean Spaces:** + ```json + { + "name": "your-app", + "version": "1.0.0", + "build": { + "publish": { + "provider": "s3", + "bucket": "your-space-name", + "endpoint": "https://nyc3.digitaloceanspaces.com", + "region": "nyc3" + } + } + } + ``` + +2. **Build and Upload:** + Use `electron-builder` to build and upload your files to the DigitalOcean Space. + +### Summary of Providers You Can Use with `electron-updater`: +- **Generic:** Host updates on any web server (requires a URL to the update files). +- **S3:** Amazon S3 bucket hosting. +- **Spaces:** DigitalOcean Spaces hosting. +- **GitLab:** Use GitLab releases to host updates. +- **Bintray:** For those who have a Bintray account (soon to be deprecated). +- **Custom Server:** Build your own server API to manage releases. + +Each of these options allows you to bypass GitHub and have full control over where you host your update files. + +Let me know if you'd like help setting up one of these methods! + +To handle both XLSX and CSV file uploads in a React component, read the first 5 rows of data, and display it in a table format, you can use the following approach. This will combine the functionalities for both file types using the `xlsx` and `papaparse` libraries. + +### Step-by-Step Implementation + +1. **Install Required Libraries**: + Make sure to install `xlsx` and `papaparse` in your project if you haven't already: + + ```bash + npm install xlsx papaparse + ``` + +2. **Create the React Component**: + Here’s a complete example of a React component that handles both XLSX and CSV files: + +```javascript +import React, { useState } from 'react'; +import XLSX from 'xlsx'; +import Papa from 'papaparse'; + +const FileUploader = () => { + const [data, setData] = useState([]); + const [error, setError] = useState(''); + + const handleFileChange = (event) => { + const file = event.target.files[0]; + if (!file) { + setError('No file selected'); + return; + } + + const fileExtension = file.name.split('.').pop().toLowerCase(); + + if (fileExtension === 'xlsx') { + // Read XLSX file + const reader = new FileReader(); + reader.onload = (e) => { + const data = new Uint8Array(e.target.result); + const workbook = XLSX.read(data, { type: 'array' }); + const worksheet = workbook.Sheets[workbook.SheetNames[0]]; + const rows = XLSX.utils.sheet_to_json(worksheet, { header: 1 }); + setData(rows.slice(0, 5)); // Get the first 5 rows + }; + reader.readAsArrayBuffer(file); + } else if (fileExtension === 'csv') { + // Read CSV file + const reader = new FileReader(); + reader.onload = (e) => { + const fileContent = e.target.result; + Papa.parse(fileContent, { + complete: (results) => { + setData(results.data.slice(0, 5)); // Get the first 5 rows + }, + header: false, // Change to true if your CSV has headers + }); + }; + reader.readAsText(file); + } else { + setError('Unsupported file format. Please upload .xlsx or .csv file.'); + } + }; + + return ( +
+ + {error &&
{error}
} +

First 5 Rows:

+ + + + + + + + + + + + + + {data.map((row, index) => ( + + {row.map((cell, cellIndex) => ( + + ))} + + ))} + +
NomPrénomPhotosDate de NaissancesNiveauAnnée ScolaireNum Inscription
{cell}
+
+ ); +}; + +export default FileUploader; +``` + +### Explanation of the Code + +1. **File Input**: The `` allows users to upload either an XLSX or CSV file. + +2. **Handling File Change**: + - When a file is selected, the `handleFileChange` function is triggered. + - It checks the file extension to determine whether it's an `.xlsx` or `.csv` file. + +3. **Reading XLSX Files**: + - If the file is an XLSX file, it uses `FileReader` to read the file and `xlsx` to parse it. + - It retrieves the first sheet and converts it into a JSON format. The first 5 rows are stored in the state. + +4. **Reading CSV Files**: + - For CSV files, it uses `FileReader` and `papaparse` to parse the CSV content. + - It stores the first 5 rows in the state. + +5. **Displaying Data**: + - The data is displayed in a simple HTML table format. The table structure includes headers for clarity. + +### Step 3: Use the Component in Your App + +You can use the `FileUploader` component in your main application component: + +```javascript +import React from 'react'; +import FileUploader from './FileUploader'; + +const App = () => { + return ( +
+

File Upload Example

+ +
+ ); +}; + +export default App; +``` + +### Important Notes +- **Header Handling**: If your CSV files have headers and you want to include them, set `header: true` in the `Papa.parse` options. Adjust the table rendering accordingly. +- **Data Format**: Make sure the data format in the uploaded files aligns with your application's needs (e.g., the number of columns). +- **Error Handling**: The component currently displays a simple error message if the file format is unsupported. You might want to expand this with more detailed error handling and user feedback. + +This implementation provides a basic but functional way to read and display data from both XLSX and CSV files in a React application. You can further enhance it based on your specific requirements, such as adding validation, processing the data further, or integrating it with your database. + +miala ny license, +description : logiciel de gestion d'universiter +email : director@c4m.mg +tel : 0348415301 +SELECT SOME AN ILAY NOTE AMZAY REFA SUPERIEUR A ZERO IZY VAO ATAO EFA MISY + +rehefa manao ajout ohatra hoe etudiant liste deroulant ilay niveau +puis modification lasa champ de texte dia zay mila respecter-na + +---------------------------------------------------------------------------------------------------------------- +parametre systeme de note, de passan sa redoublon +algorithe de hoe redoublonsa passans selon an le noteliste de note sty aseo directe fa atao genre dsongade mampiditra nore fa relever de notre ampiditra eo de msemlectioner niveau su eleve, +note generale ana classe retra amzay tsy manasarojavatra, zany hoe ita eo liste ana classe retra am anneé scolaire iton de mipoitra ny moyenne de classe, de ita ao daoly mpianatra am classe io sy ny note any fa trsy afaka atao nininona, +afaka admis automatique koa izy aveo +asina archive daoly ilay note aveo +asina statut des eleve koa hoe passants sa redoublon sa renvoyer +asina filliere koa hoe iniona ny filliere any anavahana anzy + + +______________________________________________________________________________________________________________________ +import { PDFDocument } from "pdf-lib"; +import { saveAs } from "file-saver"; +import pdf from '../../assets/carte_etudiant.pdf'; + +const PDFEditor = async (data) => { + // Load the existing PDF file + const existingPdfBytes = await fetch(pdf).then((res) => + res.arrayBuffer() + ); + + // Load the PDF document + const pdfDoc = await PDFDocument.load(existingPdfBytes); + + // Get the form in the PDF + const form = pdfDoc.getForm(); + + // Set text fields + form.getTextField("f1").setText(data.f1); + form.getTextField("f2").setText(data.f2); + form.getTextField("f3").setText(data.f3); + form.getTextField("f4").setText(data.f4); + form.getTextField("f5").setText(data.f5); + + // Add image to replace form field 'f8' (assuming the image is a base64-encoded string) + // let base64Image = data.f8; // Base64-encoded image string + + // const pages = pdfDoc.getPages(); + // // Loop through each page to find the text + // pages.forEach((page) => { + // const textContent = page.getTextContent(); // PDF-lib doesn't directly support text search, so you may need to implement your own extraction + + // // For this example, we're assuming you already have the coordinates of the text (e.g., F8) + // const textCoords = getTextCoordinates(textContent, "f8"); + + // // Now replace the text with an image at the found coordinates + // if (textCoords) { + // const { x, y } = textCoords; + + // // Add the image at the position + // page.drawImage(base64Image, { + // x, + // y, + // width: image.width, + // height: image.height, + // }); + // } + // }); + + // Flatten the form so that the data becomes part of the content + form.flatten(); + + // Save the modified PDF + const pdfBytes = await pdfDoc.save(); + + // Trigger download of the PDF + const blob = new Blob([pdfBytes], { type: "application/pdf" }); + saveAs(blob, "modified_form.pdf"); +}; + +export default PDFEditor; +const form2 = pdfDoc2.getForm(); + const field = form2.getField("f6"); + if (field) { + form2.removeField(field); + } + +◕‿‿◕ + + + +◕‿‿◕ + + + + +----------------------------------------------------------------------------------------------------------- +relever de note +rectification des vue Notes +get certificate +formations