diff --git a/app/static/css/bulletin-but.css b/app/static/css/bulletin-but.css new file mode 100644 index 000000000..46d915840 --- /dev/null +++ b/app/static/css/bulletin-but.css @@ -0,0 +1,238 @@ +/* Bulletin BUT, Seb. L. 2021-12-06 */ +/*******************/ +/* Styles généraux */ +/*******************/ +:root{ + --couleurPrincipale: #09c; + --couleurSecondaire: #0c9; + --couleurIntense: #c09; +} +body{ + font-family: arial; + background: #f0f0f0; +} +.wait{ + width: 60px; + height: 6px; + margin: auto; + background: #424242; /* la réponse à tout */ + animation: wait .4s infinite alternate; +} +@keyframes wait{ + 100%{transform: translateY(40px) rotate(1turn);} +} +main{ + max-width: 1000px; + margin: auto; + display: none; +} +.ready .wait{display: none;} +.ready main{display: block;} +h2{ + margin: 0; + color: var(--couleurPrincipale) +} +section{ + background: #FFF; + border-radius: 16px; + border: 1px solid #AAA; + padding: 16px 32px; + margin: 8px 0; +} +section>div:nth-child(1){ + display: flex; + justify-content: space-between; + align-items: center; + gap: 8px; +} +.CTA_Liste{ + display: flex; + gap: 4px; + align-items: center; + background: var(--couleurIntense); + color: #FFF; + padding: 4px 8px; + border-radius: 4px; + box-shadow: 0 2px 2px rgba(0,0,0,0.26); + cursor: pointer; +} +.CTA_Liste>svg{ + transition: 0.2s; +} +.CTA_Liste:hover{ + outline: 2px solid #424242; +} +.listeOff svg{ + transform: rotate(180deg); +} +.listeOff .syntheseModule, +.listeOff .eval{ + display: none; +} + +/***********************/ +/* Options d'affichage */ +/***********************/ +.hide_abs .absences, +.hide_abs_modules .module>.absences, +.hide_all_evals .eval, +.hide_coef .synthese em, +.hide_coef .eval>em, +.hide_date_inscr .dateInscription, +.hide_ects .ects + +{ + display: none; +} + +/************/ +/* Etudiant */ +/************/ +.etudiant{ + display: flex; + align-items: center; + gap: 16px; + border-color: var(--couleurPrincipale); + background: var(--couleurPrincipale); + color: #FFF; +} +.civilite{ + font-weight: bold; +} + +/************/ +/* Semestre */ +/************/ +.infoSemestre{ + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 4px; +} +.infoSemestre>div{ + border: 1px solid var(--couleurIntense); + padding: 4px 8px; + border-radius: 4px; + display: grid; + grid-template-columns: auto auto; + column-gap: 4px; +} +.infoSemestre>div:nth-child(1){ + margin-right: auto; +} +.infoSemestre>div>div:nth-child(even){ + text-align: right; +} +.rang{ + text-decoration: underline var(--couleurIntense); +} + +.enteteSemestre{ + color: var(--couleurSecondaire); + font-weight: bold; + font-size: 20px; + margin-bottom: 4px; +} + +/***************/ +/* Synthèse */ +/***************/ +.synthese>.ue{ + background: var(--couleurPrincipale); +} +.synthese h3{ + background: var(--couleurPrincipale); +} +.synthese em, +.eval em{ + opacity: 0.6; + width: 70px; + display: inline-block; +} + +/***************/ +/* Evaluations */ +/***************/ +.module, .ue{ + background: var(--couleurSecondaire); + color: #FFF; + padding: 4px 32px; + border-radius: 4px; + display: flex; + gap: 16px; + margin: 4px 0 2px 0; + overflow: auto; +} +h3{ + display: flex; + align-items: center; + margin: 0 auto 0 0; + position: sticky; + left: -32px; + z-index: 1; + font-size: 16px; + background: var(--couleurSecondaire); +} + +.moyenne{ + font-weight: bold; + text-align: right; +} +.info{ + opacity: 0.9; +} +.eval, .syntheseModule{ + position: relative; + display: flex; + justify-content: space-between; + margin: 0 28px; + padding: 0px 4px; + border-bottom: 1px solid #aaa; +} +.eval:hover, .syntheseModule:hover{ + background: var(--couleurIntense); + color: #FFF; +} +.complement{ + pointer-events:none; + position: absolute; + bottom: 100%; + right: 0; + padding: 8px; + border-radius: 4px; + background: #FFF; + color: #000; + border: 1px solid var(--couleurIntense); + opacity: 0; + display: grid; + grid-template-columns: auto auto; + column-gap: 4px; +} +.eval:hover .complement{ + opacity: 1; + z-index: 1; +} +.complement>div:nth-child(even){ + text-align: right; +} +.complement>div:nth-child(1), +.complement>div:nth-child(2){ + font-weight: bold; +} +.complement>div:nth-child(1), +.complement>div:nth-child(7){ + margin-bottom: 8px; +} + +.absences{ + display: grid; + grid-template-columns: auto auto; + column-gap: 4px; + text-align: right; + border-left: 1px solid; + padding-left: 16px; +} +.absences>div:nth-child(1), +.absences>div:nth-child(2){ + font-weight: bold; +} \ No newline at end of file diff --git a/app/static/js/bulletin-but.js b/app/static/js/bulletin-but.js new file mode 100644 index 000000000..8cd54d899 --- /dev/null +++ b/app/static/js/bulletin-but.js @@ -0,0 +1,243 @@ +/* Il manque : + - rangs + - Synthèse : moyenne UE + - Synthèse : min, max, moy classe + - Synthèse : absences + - Eval : absences + +Moi : + "show_codemodules" :true, + "show_minmax": true, + "show_minmax_eval": true, + "show_minmax_mod": false, + "show_mod_rangs": false, + "show_moypromo": true, + "show_rangs": true, + "show_ue_cap_current": true, + "show_ue_cap_details": true, + "show_ue_rangs": true, + "show_uevalid": true, +*/ +/*****************************/ +/* Gestionnaire d'événements */ +/*****************************/ +document.querySelectorAll(".CTA_Liste").forEach(e => { + e.addEventListener("click", listeOnOff) +}) + +function listeOnOff() { + this.parentElement.parentElement.classList.toggle("listeOff") +} +/*****************************/ +/* Recupération et affichage */ +/*****************************/ + +fetch(dataSrc) + .then(r => { return r.json() }) + .then(json => showData(json)) + +function showData(data) { + showInformations(data); + showSemestre(data); + showSynthese(data); + showEvaluations(data); + + setOptions(data.options); + + document.body.classList.add("ready"); +} + +/********************************/ +/* Informations sur l'étudiant */ +/********************************/ +function showInformations(data) { + document.querySelector(".studentPic").src = data.etudiant.photo_url || "default_Student.svg"; + + let output = ` +
+
+ ${civilite(data.etudiant.civilite)} + ${data.etudiant.nom} + ${data.etudiant.prenom} + né${(data.etudiant.civilite == "F") ? "e" : ""} le ${ISOToDate(data.etudiant.date_naissance)} +
+
+ Numéro étudiant : ${data.etudiant.code_nip} + Code INE : ${data.etudiant.code_ine} +
+
${data.formation.titre}
+ +
+ `; + + document.querySelector(".infoEtudiant").innerHTML = output; +} + +/*******************************/ +/* Information sur le semestre */ +/*******************************/ +function showSemestre(data) { + document.querySelector("h2").innerHTML += data.semestre.numero; + document.querySelector(".dateInscription").innerHTML += ISOToDate(data.semestre.inscription); + let output = ` +
+
Moyenne
${data.semestre.notes.value}
+
Rang :
${data.semestre.rang.value} / ${data.semestre.rang.total}
+
Max. promo. :
${data.semestre.notes.max}
+
Moy. promo. :
${data.semestre.notes.moy}
+
Min. promo. :
${data.semestre.notes.min}
+
+ ${data.semestre.groupes.map(groupe => { + return ` +
+
Groupe
${groupe.nom}
+
Rang :
${groupe.rang.value} / ${groupe.rang.total}
+
Max. groupe :
${groupe.notes.max}
+
Moy. groupe :
${groupe.notes.min}
+
Min. groupe :
${groupe.notes.min}
+
+ `; + }).join("") + } + `; + document.querySelector(".infoSemestre").innerHTML = output; +} + +/*******************************/ +/* Synthèse */ +/*******************************/ +function showSynthese(data) { + let output = ``; + Object.entries(data.ues).forEach(([ue, dataUE]) => { + output += ` +
+

+ ${(dataUE.competence) ? dataUE.competence + " - " : ""}${ue} +

+
+
Moyenne : ${dataUE.moyenne?.value || "-"}
+
+ Bonus : ${dataUE.bonus || 0} - + Malus : ${dataUE.malus || 0} +  - + ECTS : ${dataUE.ECTS.acquis} / ${dataUE.ECTS.total} + +
+
+
+
Abs inj.
${dataUE.absences?.injustifie || 0}
+
Total
${dataUE.absences?.total || 0}
+
+
+ ${synthese(dataUE.ressources)} + ${synthese(dataUE.saes)} + `; + }); + document.querySelector(".synthese").innerHTML = output; + + function synthese(modules) { + let output = ""; + Object.entries(modules).forEach(([module, dataModule]) => { + let titre = data.ressources[module]?.titre || data.saes[module]?.titre; + output += ` +
+
${module} - ${titre}
+
+ ${dataModule.moyenne} + Coef. ${dataModule.coef} +
+
+ `; + }) + return output; + } +} +/*******************************/ +/* Evaluations */ +/*******************************/ +function showEvaluations(data) { + document.querySelector(".evaluations").innerHTML = module(data.ressources); + document.querySelector(".sae").innerHTML += module(data.saes); + + function module(module) { + let output = ""; + Object.entries(module).forEach(([numero, content]) => { + output += ` +
+
+

${numero} - ${content.titre}

+
+
Moyenne indicative : ${content.moyenne.value}
+
+ Classe : ${content.moyenne.moy} - + Max : ${content.moyenne.max} - + Min : ${content.moyenne.min} +
+
+
+
Abs inj.
${content.absences?.injustifie || 0}
+
Total
${content.absences?.total || 0}
+
+
+ ${evaluation(content.evaluations)} +
+ `; + }) + return output; + } + + function evaluation(evaluations) { + let output = ""; + evaluations.forEach(eval => { + output += ` +
+
${eval.description}
+
+ ${eval.note.value} + Coef. ${eval.coef} +
+
+
Coef
${eval.coef}
+
Max. promo.
${eval.note.max}
+
Moy. promo.
${eval.note.moy}
+
Min. promo.
${eval.note.min}
+ ${Object.entries(eval.poids).map(([UE, poids]) => { + return ` +
Poids ${UE}
+
${poids}
+ `; + }).join("")} +
+
+ `; + }) + return output; + } +} + +/********************/ +/* Options */ +/********************/ +function setOptions(options) { + Object.entries(options).forEach(([option, value]) => { + if (value === false) { + document.body.classList.add(option.replace("show", "hide")) + } + }) +} + + +/********************/ +/* Fonctions d'aide */ +/********************/ +function civilite(txt) { + switch (txt) { + case "M": return "M."; + case "F": return "Mme"; + default: return ""; + } +} + +function ISOToDate(ISO) { + return ISO.split("-").reverse().join("/"); +} diff --git a/app/templates/but/bulletin.html b/app/templates/but/bulletin.html new file mode 100644 index 000000000..74232ff97 --- /dev/null +++ b/app/templates/but/bulletin.html @@ -0,0 +1,92 @@ + + + + + + + + + Relevé de notes + + + + +
+
+ + + +
+ Photo de l'étudiant +
+
+ + + + +
+

Semestre

+
Inscrit le
+ Les moyennes servent à situer l'étudiant dans la promotion et ne correspondent pas à des validations de + compétences ou d'UE. +
+
+ + + + +
+
+
+

Synthèse

+ La moyenne des ressources dans une UE dépend des poids donnés aux évaluations. +
+
+ Liste + + +
+
+
+
+ + + + +
+
+

Ressources

+
+ Liste + + +
+
+
+
+ +
+
+

SAÉ

+
+ Liste + + +
+
+
+
+ +
+ + + + + + + \ No newline at end of file diff --git a/app/views/notes.py b/app/views/notes.py index 6627d0242..a79a05417 100644 --- a/app/views/notes.py +++ b/app/views/notes.py @@ -38,7 +38,7 @@ from operator import itemgetter from xml.etree import ElementTree import flask -from flask import url_for, jsonify +from flask import url_for, jsonify, render_template from flask import current_app, g, request from flask_login import current_user from werkzeug.utils import redirect @@ -290,8 +290,21 @@ def formsemestre_bulletinetud( formsemestre = FormSemestre.query.get_or_404(formsemestre_id) if formsemestre.formation.is_apc(): etud = models.Identite.query.get_or_404(etudid) - r = bulletin_but.ResultatsSemestreBUT(formsemestre) - return jsonify(r.bulletin_etud(etud, formsemestre)) + if format == "json": + r = bulletin_but.ResultatsSemestreBUT(formsemestre) + return jsonify(r.bulletin_etud(etud, formsemestre)) + elif format == "html": + return render_template( + "but/bulletin.html", + title="Bulletin BUT", + bul_url=url_for( + "notes.formsemestre_bulletinetud", + scodoc_dept=g.scodoc_dept, + formsemestre_id=formsemestre_id, + etudid=etudid, + format="json", + ), + ) return sco_bulletins.formsemestre_bulletinetud( etudid=etudid,