diff --git a/client/src/hooks/useSettings.tsx b/client/src/hooks/useSettings.tsx index 28a0b6df1..e65d5055b 100644 --- a/client/src/hooks/useSettings.tsx +++ b/client/src/hooks/useSettings.tsx @@ -18,6 +18,7 @@ interface ContextType { const DEFAULT_SETTINGS: IClientSettings = { defaultTeamSize: 1, canTutorExcuseStudents: false, + excludeStudentsByStatus: false, gradingFilename: '', tutorialGradingFilename: '', }; diff --git a/client/src/pages/dashboard/components/AdminStatsCard.tsx b/client/src/pages/dashboard/components/AdminStatsCard.tsx index 28e67cca7..b1968c664 100644 --- a/client/src/pages/dashboard/components/AdminStatsCard.tsx +++ b/client/src/pages/dashboard/components/AdminStatsCard.tsx @@ -4,6 +4,8 @@ import RED from '@mui/material/colors/red'; import createStyles from '@mui/styles/createStyles'; import makeStyles from '@mui/styles/makeStyles'; import Chart from 'react-google-charts'; +import { StudentStatus } from 'shared/model/Student'; +import { useSettings } from '../../../hooks/useSettings'; import { StudentByTutorialSlotSummaryMap } from '../../../typings/types'; const useStyles = makeStyles((theme: Theme) => @@ -31,6 +33,7 @@ interface Props { function AdminStatsCard({ studentsByTutorialSummary: summaries }: Props): JSX.Element { const classes = useStyles(); const theme = useTheme(); + const { settings } = useSettings(); function getScheinPassedStatsOfAllStudents() { const data: [string, number, number][] = []; @@ -39,10 +42,14 @@ function AdminStatsCard({ studentsByTutorialSummary: summaries }: Props): JSX.El let passed = 0; let notPassed = 0; summaries.forEach((summary) => { - if (summary.passed) { - passed += 1; - } else { - notPassed += 1; + const shouldIncludeStudent = + !settings.excludeStudentsByStatus || + ![StudentStatus.NO_SCHEIN_REQUIRED, StudentStatus.INACTIVE].includes( + summary.student.status + ); + + if (shouldIncludeStudent) { + summary.passed ? passed++ : notPassed++; } }); diff --git a/client/src/pages/dashboard/components/ScheinCrtieriaStatsCard.tsx b/client/src/pages/dashboard/components/ScheinCrtieriaStatsCard.tsx index 1d75319ba..51fe1ca84 100644 --- a/client/src/pages/dashboard/components/ScheinCrtieriaStatsCard.tsx +++ b/client/src/pages/dashboard/components/ScheinCrtieriaStatsCard.tsx @@ -5,7 +5,9 @@ import convert from 'color-convert'; import { useEffect, useState } from 'react'; import Chart from 'react-google-charts'; import { IScheinCriteria, ScheinCriteriaStatus } from 'shared/model/ScheinCriteria'; +import { StudentStatus } from 'shared/model/Student'; import { getAllScheinCriterias } from '../../../hooks/fetching/Scheincriteria'; +import { useSettings } from '../../../hooks/useSettings'; import { useTranslation } from '../../../util/lang/configI18N'; import { TutorialSummaryInfo } from '../Dashboard'; @@ -47,6 +49,7 @@ function ScheinCriteriaStatsCard({ const classes = useStyles(); const theme = useTheme(); const { t } = useTranslation('scheincriteria'); + const { settings } = useSettings(); const numberOfStudents = Object.keys(value.studentInfos).length; const { backgroundColor, colors, fontStyle } = theme.mixins.chart(theme); @@ -57,6 +60,13 @@ function ScheinCriteriaStatsCard({ function filterSummaries(critId: string): ScheinCriteriaStatus[] { return Object.values(value.studentInfos) + .filter( + (studentInfo) => + !settings.excludeStudentsByStatus || + ![StudentStatus.NO_SCHEIN_REQUIRED, StudentStatus.INACTIVE].includes( + studentInfo.student.status + ) + ) .filter((studentInfo) => Object.keys(studentInfo.scheinCriteriaSummary).includes(critId)) .map((summary) => summary.scheinCriteriaSummary[critId]); } @@ -124,6 +134,13 @@ function ScheinCriteriaStatsCard({ function filterAndSortSummaries(critId: string): ScheinCriteriaStatus[] { return Object.values(value.studentInfos) + .filter( + (studentInfo) => + !settings.excludeStudentsByStatus || + ![StudentStatus.NO_SCHEIN_REQUIRED, StudentStatus.INACTIVE].includes( + studentInfo.student.status + ) + ) .filter((studentInfo) => Object.keys(studentInfo.scheinCriteriaSummary).includes(critId)) .sort((a, b) => { const teamIdA = a.student.team?.id || ''; diff --git a/client/src/pages/dashboard/components/ScheinPassedStatsCard.tsx b/client/src/pages/dashboard/components/ScheinPassedStatsCard.tsx index 6c6d39cb8..aa518275d 100644 --- a/client/src/pages/dashboard/components/ScheinPassedStatsCard.tsx +++ b/client/src/pages/dashboard/components/ScheinPassedStatsCard.tsx @@ -1,8 +1,9 @@ import { CircularProgress, Paper, Theme, Typography, useTheme } from '@mui/material'; import createStyles from '@mui/styles/createStyles'; import makeStyles from '@mui/styles/makeStyles'; -import React from 'react'; import Chart from 'react-google-charts'; +import { StudentStatus } from 'shared/model/Student'; +import { useSettings } from '../../../hooks/useSettings'; import { TutorialSummaryInfo } from '../Dashboard'; const useStyles = makeStyles((theme: Theme) => @@ -33,6 +34,7 @@ interface ScheinPassedStatsCardProps { function ScheinPassedStatsCard({ value }: ScheinPassedStatsCardProps): JSX.Element { const classes = useStyles(); const theme = useTheme(); + const { settings } = useSettings(); const { backgroundColor, fontStyle } = theme.mixins.chart(theme); @@ -42,8 +44,14 @@ function ScheinPassedStatsCard({ value }: ScheinPassedStatsCardProps): JSX.Eleme let notPassedValue = 0; const data: (string | number)[][] = []; - Object.values(value.studentInfos).forEach((item) => { - item.passed ? (passedValue += 1) : (notPassedValue += 1); + Object.values(value.studentInfos).forEach(({ student, passed }) => { + const shouldIncludeStudent = + !settings.excludeStudentsByStatus || + ![StudentStatus.NO_SCHEIN_REQUIRED, StudentStatus.INACTIVE].includes(student.status); + + if (shouldIncludeStudent) { + passed ? passedValue++ : notPassedValue++; + } }); const passed = ['Bestanden', passedValue]; diff --git a/client/src/pages/dashboard/components/TutorialStatsCard.tsx b/client/src/pages/dashboard/components/TutorialStatsCard.tsx index 242800c11..1699fe9af 100644 --- a/client/src/pages/dashboard/components/TutorialStatsCard.tsx +++ b/client/src/pages/dashboard/components/TutorialStatsCard.tsx @@ -2,9 +2,9 @@ import { Paper, Table, TableBody, TableCell, TableRow, Theme } from '@mui/materi import createStyles from '@mui/styles/createStyles'; import makeStyles from '@mui/styles/makeStyles'; import { DateTime } from 'luxon'; -import React from 'react'; +import { StudentStatus } from 'shared/model/Student'; +import { useSettings } from '../../../hooks/useSettings'; import { TutorialSummaryInfo } from '../Dashboard'; - const useStyles = makeStyles((theme: Theme) => createStyles({ statsPaper: { @@ -32,7 +32,14 @@ interface TutorialStatsCardProps { function TutorialStatsCard({ value }: TutorialStatsCardProps): JSX.Element { const classes = useStyles(); + const { settings } = useSettings(); const { studentInfos, tutorial } = value; + let activeStudents = 0; + Object.values(studentInfos).forEach(({ student }) => { + if (student.status === StudentStatus.ACTIVE) { + activeStudents++; + } + }); return ( @@ -40,7 +47,11 @@ function TutorialStatsCard({ value }: TutorialStatsCardProps): JSX.Element { Teilnehmer: - {Object.values(studentInfos).length} + + {settings.excludeStudentsByStatus + ? `${activeStudents} aktive, ${Object.values(studentInfos).length} insgesamt` + : Object.values(studentInfos).length} + <> diff --git a/client/src/pages/settings/SettingsPage.helpers.ts b/client/src/pages/settings/SettingsPage.helpers.ts index 1365e4002..a66396bd5 100644 --- a/client/src/pages/settings/SettingsPage.helpers.ts +++ b/client/src/pages/settings/SettingsPage.helpers.ts @@ -3,6 +3,7 @@ import * as Yup from 'yup'; export const validationSchema = Yup.object().shape({ canTutorExcuseStudents: Yup.boolean().required('Benötigt'), + excludeStudentsByStatus: Yup.boolean().required('Benötigt'), defaultTeamSize: Yup.number() .integer('Muss eine ganze Zahl sein.') .min(1, 'Muss mindestens 1 sein.') @@ -52,6 +53,7 @@ export const validationSchema = Yup.object().shape({ export interface FormState { defaultTeamSize: string; canTutorExcuseStudents: boolean; + excludeStudentsByStatus: boolean; gradingFilename: string; tutorialGradingFilename: string; mailingConfig: { @@ -68,12 +70,14 @@ export function getInitialValues(settings: IClientSettings): FormState { const { mailingConfig, canTutorExcuseStudents, + excludeStudentsByStatus, gradingFilename, tutorialGradingFilename, defaultTeamSize, } = settings; return { canTutorExcuseStudents: canTutorExcuseStudents, + excludeStudentsByStatus: excludeStudentsByStatus, defaultTeamSize: `${defaultTeamSize}`, gradingFilename, tutorialGradingFilename, @@ -91,6 +95,7 @@ export function getInitialValues(settings: IClientSettings): FormState { export function convertFormStateToDTO(values: FormState): IClientSettings { const dto: IClientSettings = { canTutorExcuseStudents: values.canTutorExcuseStudents, + excludeStudentsByStatus: values.excludeStudentsByStatus, defaultTeamSize: Number.parseInt(values.defaultTeamSize), gradingFilename: values.gradingFilename, tutorialGradingFilename: values.tutorialGradingFilename, diff --git a/client/src/pages/settings/components/SettingsPage.form.tsx b/client/src/pages/settings/components/SettingsPage.form.tsx index c73dd9fe6..a3ac517fa 100644 --- a/client/src/pages/settings/components/SettingsPage.form.tsx +++ b/client/src/pages/settings/components/SettingsPage.form.tsx @@ -2,7 +2,7 @@ import { Box, Button, Typography } from '@mui/material'; import createStyles from '@mui/styles/createStyles'; import makeStyles from '@mui/styles/makeStyles'; import { useFormikContext } from 'formik'; -import React, { useCallback } from 'react'; +import { useCallback } from 'react'; import FormikCheckbox from '../../../components/forms/components/FormikCheckbox'; import FormikDebugDisplay from '../../../components/forms/components/FormikDebugDisplay'; import FormikTextField from '../../../components/forms/components/FormikTextField'; @@ -100,6 +100,14 @@ function SettingsPageForm(): JSX.Element { + Scheinstatus + + + + Dateinamen { it.each([ { ...DEFAULT_SETTINGS, defaultTeamSize: 5 }, { ...DEFAULT_SETTINGS, canTutorExcuseStudents: true }, + { ...DEFAULT_SETTINGS, excludeStudentsByStatus: true }, { ...DEFAULT_SETTINGS, defaultTeamSize: 3, diff --git a/server/src/shared/model/Settings.ts b/server/src/shared/model/Settings.ts index c8169974a..0fd6e3d82 100644 --- a/server/src/shared/model/Settings.ts +++ b/server/src/shared/model/Settings.ts @@ -14,6 +14,7 @@ export interface IMailingSettings { export interface IClientSettings { defaultTeamSize: number; canTutorExcuseStudents: boolean; + excludeStudentsByStatus: boolean; /** Filename for gradings for teams / students __without__ extension. */ gradingFilename: string; /** Filename for file for gradings of all team of a tutorial __without__ extension. */