Skip to content

Commit

Permalink
πŸ›πŸ’„ Fix spo weights and rework grade page (#319)
Browse files Browse the repository at this point in the history
* πŸ›πŸ’„ Fix spo weights and rework grade page

* πŸ§‘β€πŸ’» redact grades option for devs
  • Loading branch information
BuildmodeOne authored Aug 7, 2023
1 parent 27f9aeb commit 04a6349
Show file tree
Hide file tree
Showing 10 changed files with 180 additions and 64 deletions.
17 changes: 16 additions & 1 deletion rogue-thi-app/lib/backend-utils/grades-utils.js
Original file line number Diff line number Diff line change
@@ -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()
}
Expand All @@ -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
}

Expand Down Expand Up @@ -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) {
Expand Down
99 changes: 68 additions & 31 deletions rogue-thi-app/pages/grades.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<ListGroup.Item
className={`${styles.spoiler} ${className}`}
onClick={() => setShow(!show)}
>
<div className={getStyle(show)}>
{children}
</div>

<div className={`${styles.spoilerPlaceholder} ${getStyle(!show)}`}>
{t('grades.spoiler.reveal')}
</div>
</ListGroup.Item>
)
}

return (
<AppContainer>
<AppNavbar title={t('grades.appbar.title')}>
Expand All @@ -123,64 +153,71 @@ export default function Grades () {
<AppBody>
<ReactPlaceholder type="text" rows={3} ready={!!gradeAverage}>
{gradeAverage && gradeAverage.entries.length > 0 && (
<ListGroup>
<>
<h4 className={styles.heading}>
{t('grades.summary.title')}
{t('grades.summary.title')}
</h4>

<ListGroup.Item className={styles.gradeAverageContainer}>
<span className={styles.gradeAverage}>
{gradeAverage.resultMin !== gradeAverage.resultMax && '~'}
{formatNum(gradeAverage.result)}
</span>
{gradeAverage.resultMin !== gradeAverage.resultMax && (
<span className={styles.gradeAverageDisclaimer}>
{t('grades.summary.disclaimer', {
minAverage: formatNum(gradeAverage.resultMin),
maxAverage: formatNum(gradeAverage.resultMax)
})}
<ListGroup>
<Spoiler className={styles.gradeAverageContainer}>
<span className={styles.gradeAverage}>
{gradeAverage.resultMin !== gradeAverage.resultMax && '~'}
{formatNum(gradeAverage.result)}
</span>
)}
</ListGroup.Item>
</ListGroup>

{gradeAverage.resultMin !== gradeAverage.resultMax && (
<span className={styles.gradeAverageDisclaimer}>
{t('grades.summary.disclaimer', {
minAverage: formatNum(gradeAverage.resultMin),
maxAverage: formatNum(gradeAverage.resultMax)
})}
</span>
)}
</Spoiler>
</ListGroup>
</>
)}
</ReactPlaceholder>

<h4 className={styles.heading}>
{t('grades.gradesList.title')}
</h4>
<ListGroup>
<h4 className={styles.heading}>
{t('grades.gradesList.title')}
</h4>

<ReactPlaceholder type="text" rows={10} ready={grades}>
{grades && grades.map((item, idx) =>
<ListGroup.Item key={idx} className={styles.item}>
<div className={styles.left}>
{item.titel}<br />

<div className={styles.details}>
{t('grades.grade')}: {getLocalizedGrade(item.note)}<br />
<small className={styles.details}>
{t('grades.ects')}: {item.ects || t('grades.none')}
</div>
</small>
</div>

<div className={styles.grade}>
{getLocalizedGrade(item.note)}<br />
<small className={styles.details}>
{t('grades.grade')}
</small>
</div>
</ListGroup.Item>
)}
</ReactPlaceholder>
</ListGroup>

<h4 className={styles.heading}>
{t('grades.pendingList.title')}
</h4>
<ListGroup>
<h4 className={styles.heading}>
{t('grades.pendingList.title')}
</h4>

<ReactPlaceholder type="text" rows={10} ready={missingGrades}>
{missingGrades && missingGrades.map((item, idx) =>
<ListGroup.Item key={idx} className={styles.item}>
<div className={styles.left}>
{item.titel} ({item.stg}) <br />

<div className={styles.details}>
{t('grades.deadline')}: {item.frist || t('grades.none')}<br />
{t('grades.ects')}: {item.ects || t('grades.none')}
<small>
{t('grades.deadline')}: {item.frist || t('grades.none')}<br />
{t('grades.ects')}: {item.ects || t('grades.none')}
</small>
</div>
</div>
</ListGroup.Item>
Expand Down
46 changes: 30 additions & 16 deletions rogue-thi-app/pages/personal.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -164,10 +160,6 @@ export default function Personal () {
</div>
<span className="text-muted">
{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')})`}
</span>
</ListGroup.Item>
</ListGroup>
Expand Down Expand Up @@ -225,18 +217,28 @@ export default function Personal () {

<ListGroup>

<ListGroup.Item action
onClick={() => window.open('https://www3.primuss.de/cgi-bin/login/index.pl?FH=fhin', '_blank')}>
<ListGroup.Item
action
className={styles.interaction_row}
onClick={() => window.open('https://www3.primuss.de/cgi-bin/login/index.pl?FH=fhin', '_blank')}
>
<FontAwesomeIcon icon={faExternalLink} className={styles.interaction_icon} />
Primuss
</ListGroup.Item>

<ListGroup.Item action onClick={() => window.open('https://moodle.thi.de/moodle', '_blank')}>
<ListGroup.Item
action
className={styles.interaction_row} onClick={() => window.open('https://moodle.thi.de/moodle', '_blank')}
>
<FontAwesomeIcon icon={faExternalLink} className={styles.interaction_icon} />
Moodle
</ListGroup.Item>

<ListGroup.Item action onClick={() => window.open('https://outlook.thi.de/', '_blank')}>
<ListGroup.Item
action
className={styles.interaction_row}
onClick={() => window.open('https://outlook.thi.de/', '_blank')}
>
<FontAwesomeIcon icon={faExternalLink} className={styles.interaction_icon} />
E-Mail
</ListGroup.Item>
Expand All @@ -254,18 +256,30 @@ export default function Personal () {
<ListGroup>

{showDebug && (
<ListGroup.Item action onClick={() => router.push('/debug')}>
<ListGroup.Item
action
className={styles.interaction_row}
onClick={() => router.push('/debug')}
>
<FontAwesomeIcon icon={faBug} className={styles.interaction_icon} />
{t('personal.debug')}
</ListGroup.Item>
)}

<ListGroup.Item action onClick={() => window.open(PRIVACY_URL, '_blank')}>
<ListGroup.Item
action
className={styles.interaction_row}
onClick={() => window.open(PRIVACY_URL, '_blank')}
>
<FontAwesomeIcon icon={faShield} className={styles.interaction_icon} />
{t('personal.privacy')}
</ListGroup.Item>

<ListGroup.Item action onClick={() => router.push('/imprint')}>
<ListGroup.Item
action
className={styles.interaction_row}
onClick={() => router.push('/imprint')}
>
<FontAwesomeIcon icon={faGavel} className={styles.interaction_icon} />
{t('personal.imprint')}
</ListGroup.Item>
Expand Down
3 changes: 3 additions & 0 deletions rogue-thi-app/public/locales/de/grades.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
},
Expand Down
5 changes: 0 additions & 5 deletions rogue-thi-app/public/locales/de/personal.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
{
"personal": {
"title": "Profil",
"grades": {
"title": "Noten",
"missingWeightSingle": " Gewichtung fehlt",
"missingWeightMultiple": " Gewichtungen fehlen"
},
"overview": {
"grades": "Noten",
"ects": "ECTS",
Expand Down
3 changes: 3 additions & 0 deletions rogue-thi-app/public/locales/en/grades.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
},
Expand Down
5 changes: 0 additions & 5 deletions rogue-thi-app/public/locales/en/personal.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
{
"personal": {
"title": "Profile",
"grades": {
"title": "Grades",
"missingWeightSingle": " weight missing",
"missingWeightMultiple": " weights missing"
},
"overview": {
"grades": "grades",
"ects": "ECTS",
Expand Down
44 changes: 41 additions & 3 deletions rogue-thi-app/styles/Grades.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -11,31 +11,69 @@
}

.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;
}

.gradeAverageContainer {
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 {
display: inline-block;
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);
}
Loading

0 comments on commit 04a6349

Please sign in to comment.