From 04a63495017b2c0762adb76378421ec9fbbf2fa8 Mon Sep 17 00:00:00 2001 From: Philipp Opheys Date: Mon, 7 Aug 2023 11:03:58 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=F0=9F=92=84=20Fix=20spo=20weights?= =?UTF-8?q?=20and=20rework=20grade=20page=20(#319)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ๐Ÿ›๐Ÿ’„ Fix spo weights and rework grade page * ๐Ÿง‘โ€๐Ÿ’ป redact grades option for devs --- .../lib/backend-utils/grades-utils.js | 17 +++- rogue-thi-app/pages/grades.jsx | 99 +++++++++++++------ rogue-thi-app/pages/personal.jsx | 46 ++++++--- rogue-thi-app/public/locales/de/grades.json | 3 + rogue-thi-app/public/locales/de/personal.json | 5 - rogue-thi-app/public/locales/en/grades.json | 3 + rogue-thi-app/public/locales/en/personal.json | 5 - rogue-thi-app/styles/Grades.module.css | 44 ++++++++- rogue-thi-app/styles/Personal.module.css | 7 ++ spo-parser/combine_jsons.py | 15 ++- 10 files changed, 180 insertions(+), 64 deletions(-) diff --git a/rogue-thi-app/lib/backend-utils/grades-utils.js b/rogue-thi-app/lib/backend-utils/grades-utils.js index b849c6f2..afcae9f8 100644 --- a/rogue-thi-app/lib/backend-utils/grades-utils.js +++ b/rogue-thi-app/lib/backend-utils/grades-utils.js @@ -1,6 +1,8 @@ import API from '../backend/authenticated-api' import courseSPOs from '../../data/spo-grade-weights.json' +const redactGrades = process.env.NEXT_PUBLIC_REDACT_GRADES === 'true' || false + function simplifyName (x) { return x.replace(/\W|und|u\./g, '').toLowerCase() } @@ -20,6 +22,18 @@ async function getGradeList () { x.note = 'E' } }) + + /** + * Set NEXT_PUBLIC_REDACT_GRADES to true to redact your grades (e.g. for screenshots) + */ + if (redactGrades) { + gradeList.forEach(x => { + if (parseFloat(x.note)) { + x.note = '1.0' + } + }) + } + return gradeList } @@ -96,7 +110,8 @@ export async function loadGradeAverage () { if (grade && spoName && courseSPOs[spoName]) { const spo = courseSPOs[spoName] const name = simplifyName(x.titel) - const entry = spo.find(y => simplifyName(y.name) === name) + const spoEntries = spo.filter(y => simplifyName(y.name) === name) + const entry = spoEntries.find(y => !!y.weight) || spoEntries[0] const other = average.entries.find(y => y.simpleName === name) if (other) { diff --git a/rogue-thi-app/pages/grades.jsx b/rogue-thi-app/pages/grades.jsx index b3e343df..63fb8678 100644 --- a/rogue-thi-app/pages/grades.jsx +++ b/rogue-thi-app/pages/grades.jsx @@ -107,6 +107,36 @@ export default function Grades () { alert(t('grades.alerts.notImplemented')) } + /** + * A spoiler component that hides its children until clicked. + * @param {Object} props + * @param {string} props.className + * @param {React.ReactNode} props.children + * @returns {React.ReactElement} The spoiler component. + */ + function Spoiler ({ className, children }) { + const [show, setShow] = useState(false) + + function getStyle (visible) { + return visible ? styles.spoilerVisible : styles.spoilerHidden + } + + return ( + setShow(!show)} + > +
+ {children} +
+ +
+ {t('grades.spoiler.reveal')} +
+
+ ) + } + return ( @@ -123,55 +153,60 @@ export default function Grades () { {gradeAverage && gradeAverage.entries.length > 0 && ( - + <>

- {t('grades.summary.title')} + {t('grades.summary.title')}

- - - - {gradeAverage.resultMin !== gradeAverage.resultMax && '~'} - {formatNum(gradeAverage.result)} - - {gradeAverage.resultMin !== gradeAverage.resultMax && ( - - {t('grades.summary.disclaimer', { - minAverage: formatNum(gradeAverage.resultMin), - maxAverage: formatNum(gradeAverage.resultMax) - })} + + + + {gradeAverage.resultMin !== gradeAverage.resultMax && '~'} + {formatNum(gradeAverage.result)} - )} - -
+ + {gradeAverage.resultMin !== gradeAverage.resultMax && ( + + {t('grades.summary.disclaimer', { + minAverage: formatNum(gradeAverage.resultMin), + maxAverage: formatNum(gradeAverage.resultMax) + })} + + )} + + + )}
+

+ {t('grades.gradesList.title')} +

-

- {t('grades.gradesList.title')} -

- {grades && grades.map((item, idx) =>
{item.titel}
- -
- {t('grades.grade')}: {getLocalizedGrade(item.note)}
+ {t('grades.ects')}: {item.ects || t('grades.none')} -
+ +
+ +
+ {getLocalizedGrade(item.note)}
+ + {t('grades.grade')} +
)}
+

+ {t('grades.pendingList.title')} +

-

- {t('grades.pendingList.title')} -

- {missingGrades && missingGrades.map((item, idx) => @@ -179,8 +214,10 @@ export default function Grades () { {item.titel} ({item.stg})
- {t('grades.deadline')}: {item.frist || t('grades.none')}
- {t('grades.ects')}: {item.ects || t('grades.none')} + + {t('grades.deadline')}: {item.frist || t('grades.none')}
+ {t('grades.ects')}: {item.ects || t('grades.none')} +
diff --git a/rogue-thi-app/pages/personal.jsx b/rogue-thi-app/pages/personal.jsx index 1171064a..b41376a6 100644 --- a/rogue-thi-app/pages/personal.jsx +++ b/rogue-thi-app/pages/personal.jsx @@ -29,7 +29,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { FoodFilterContext, ShowDashboardModal, ShowLanguageModal, ShowPersonalDataModal, ShowThemeModal, ThemeContext } from './_app' import { NoSessionError, UnavailableSessionError, forgetSession } from '../lib/backend/thi-session-handler' import { USER_EMPLOYEE, USER_GUEST, USER_STUDENT, useUserKind } from '../lib/hooks/user-kind' -import { calculateECTS, loadGradeAverage, loadGrades } from '../lib/backend-utils/grades-utils' +import { calculateECTS, loadGrades } from '../lib/backend-utils/grades-utils' import API from '../lib/backend/authenticated-api' import styles from '../styles/Personal.module.css' @@ -42,7 +42,6 @@ const PRIVACY_URL = process.env.NEXT_PUBLIC_PRIVACY_URL export default function Personal () { const [userdata, setUserdata] = useState(null) - const [average, setAverage] = useState(null) const [ects, setEcts] = useState(null) const [grades, setGrades] = useState(null) const [missingGrades, setMissingGrades] = useState(null) @@ -96,9 +95,6 @@ export default function Personal () { data.pcounter = response.pcounter setUserdata(data) - const average = await loadGradeAverage() - setAverage(average) - const { finished, missing } = await loadGrades() setGrades(finished) setMissingGrades(missing) @@ -164,10 +160,6 @@ export default function Personal () { {ects !== null && `${ects} ${t('personal.overview.ects')} `} - {!isNaN(average?.result) && ' ยท '} - {!isNaN(average?.result) && 'โˆ… ' + average.result.toFixed(2).toString().replace('.', ',')} - {average?.missingWeight === 1 && ` (${average.missingWeight} ${t('personal.grades.missingWeightSingle')})`} - {average?.missingWeight > 1 && ` (${average.missingWeight} ${t('personal.grades.missingWeightMultiple')})`}
@@ -225,18 +217,28 @@ export default function Personal () { - window.open('https://www3.primuss.de/cgi-bin/login/index.pl?FH=fhin', '_blank')}> + window.open('https://www3.primuss.de/cgi-bin/login/index.pl?FH=fhin', '_blank')} + > Primuss - window.open('https://moodle.thi.de/moodle', '_blank')}> + window.open('https://moodle.thi.de/moodle', '_blank')} + > Moodle - window.open('https://outlook.thi.de/', '_blank')}> + window.open('https://outlook.thi.de/', '_blank')} + > E-Mail @@ -254,18 +256,30 @@ export default function Personal () { {showDebug && ( - router.push('/debug')}> + router.push('/debug')} + > {t('personal.debug')} )} - window.open(PRIVACY_URL, '_blank')}> + window.open(PRIVACY_URL, '_blank')} + > {t('personal.privacy')} - router.push('/imprint')}> + router.push('/imprint')} + > {t('personal.imprint')} diff --git a/rogue-thi-app/public/locales/de/grades.json b/rogue-thi-app/public/locales/de/grades.json index a6985520..c73f9e4d 100644 --- a/rogue-thi-app/public/locales/de/grades.json +++ b/rogue-thi-app/public/locales/de/grades.json @@ -16,6 +16,9 @@ "title": "Notenschnitt", "disclaimer": "Der genaue Notenschnitt kann nicht ermittelt werden und liegt zwischen {{minAverage} und {{maxAverage}}" }, + "spoiler": { + "reveal": "Klicken um anzuzeigen" + }, "gradesList": { "title": "Noten" }, diff --git a/rogue-thi-app/public/locales/de/personal.json b/rogue-thi-app/public/locales/de/personal.json index bd615fed..3b0296f8 100644 --- a/rogue-thi-app/public/locales/de/personal.json +++ b/rogue-thi-app/public/locales/de/personal.json @@ -1,11 +1,6 @@ { "personal": { "title": "Profil", - "grades": { - "title": "Noten", - "missingWeightSingle": " Gewichtung fehlt", - "missingWeightMultiple": " Gewichtungen fehlen" - }, "overview": { "grades": "Noten", "ects": "ECTS", diff --git a/rogue-thi-app/public/locales/en/grades.json b/rogue-thi-app/public/locales/en/grades.json index f130a261..6f530da5 100644 --- a/rogue-thi-app/public/locales/en/grades.json +++ b/rogue-thi-app/public/locales/en/grades.json @@ -16,6 +16,9 @@ "title": "Grade Average", "disclaimer": "The exact grade average cannot be determined and ranges between {{minAverage}} and {{maxAverage}}" }, + "spoiler": { + "reveal": "Click to reveal" + }, "gradesList": { "title": "Grades" }, diff --git a/rogue-thi-app/public/locales/en/personal.json b/rogue-thi-app/public/locales/en/personal.json index 118f60b6..07f912a4 100644 --- a/rogue-thi-app/public/locales/en/personal.json +++ b/rogue-thi-app/public/locales/en/personal.json @@ -1,11 +1,6 @@ { "personal": { "title": "Profile", - "grades": { - "title": "Grades", - "missingWeightSingle": " weight missing", - "missingWeightMultiple": " weights missing" - }, "overview": { "grades": "grades", "ects": "ECTS", diff --git a/rogue-thi-app/styles/Grades.module.css b/rogue-thi-app/styles/Grades.module.css index e73750b3..d1f81278 100644 --- a/rogue-thi-app/styles/Grades.module.css +++ b/rogue-thi-app/styles/Grades.module.css @@ -11,11 +11,17 @@ } .details { - color: gray; + color: var(--gray); +} + +.grade { + margin-left: 5px; + text-align: center; + align-self: center; } .right { - color: gray; + color: var(--gray); text-align: right; } @@ -23,15 +29,19 @@ display: flex; flex-direction: row; align-items: center; + padding: 15px 20px; } + .gradeAverage { font-weight: bold; font-size: 3rem; float: left; margin-right: 20px; + padding-bottom: 5px; } + .gradeAverageDisclaimer { - color: gray; + color: var(--gray); } .spacer { @@ -39,3 +49,31 @@ min-width: 10px; height: 1px; } + +.spoilerHidden { + opacity: 0; + transition: opacity 0.5s ease-in-out; +} + +.spoilerVisible { + opacity: 1; + transition: opacity 0.5s ease-in-out; +} + +.spoilerPlaceholder { + position: absolute; + width: 100%; + height: 100%; + top: 0; + left: 0; + display: flex; + justify-content: center; + align-items: center; + border-radius: 3px; + color: var(--light); + font-size: 1rem; + font-weight: bold; + border: inherit; + background-color: color-mix(in srgb, var(--primary), #ffffff00); + box-shadow: inset 0 0 25px 0px var(--primary); +} diff --git a/rogue-thi-app/styles/Personal.module.css b/rogue-thi-app/styles/Personal.module.css index 845045f5..0559db34 100644 --- a/rogue-thi-app/styles/Personal.module.css +++ b/rogue-thi-app/styles/Personal.module.css @@ -21,6 +21,13 @@ float: right; } +.interaction_row { + display: flex; + flex-direction: row-reverse; + justify-content: space-between; + align-items: center; +} + .logout_button { display: flex; justify-content: center; diff --git a/spo-parser/combine_jsons.py b/spo-parser/combine_jsons.py index 7c7ed7bb..c108c4ce 100644 --- a/spo-parser/combine_jsons.py +++ b/spo-parser/combine_jsons.py @@ -1,12 +1,21 @@ import os import json +import re + +simplifyName = re.compile(r'\W|und|u\./g') +def simplify(name): + return simplifyName.sub("", name).lower() result = {} for filename in os.listdir("./weightings/"): name = filename.replace(".json", "") with open("./weightings/" + filename) as fd: - result[name] = json.load(fd) + content = json.load(fd) + for entry in content: + entry["name"] = simplify(entry["name"]) + + result[name] = content ects_sums = [] for name in result: @@ -21,5 +30,5 @@ for name, ects in ects_sums: print("{:3d} {}".format(ects, name)) -with open("spo-grade-weights.json", "w+") as fd: - json.dump(result, fd) \ No newline at end of file +with open("spo-grade-weights.json", "w+", encoding="utf-8") as fd: + json.dump(result, fd, ensure_ascii=False)