diff --git a/client/public/static/locales/de/scheincriteria.json b/client/public/static/locales/de/scheincriteria.json index 9114a73ba..337d6ceff 100644 --- a/client/public/static/locales/de/scheincriteria.json +++ b/client/public/static/locales/de/scheincriteria.json @@ -30,5 +30,8 @@ "UNIT_LABEL_PRESENTATION_plural" : "Präsentationen", "UNIT_LABEL_SHEET_plural" : "Blätter", "true" : "Ja", - "false" : "Nein" + "false" : "Nein", + "ACHIEVED_PIE_CHART_LABEL_achieved": "Bestanden", + "ACHIEVED_PIE_CHART_LABEL_notAchieved": "Nicht bestanden", + "ACHIEVED_PIE_CHART_LABEL_notPresent": "Abwesend" } \ No newline at end of file diff --git a/client/src/components/info-paper/ChartPaper.tsx b/client/src/components/info-paper/ChartPaper.tsx new file mode 100644 index 000000000..3e2788bfa --- /dev/null +++ b/client/src/components/info-paper/ChartPaper.tsx @@ -0,0 +1,70 @@ +import { CircularProgress, PaperProps } from '@material-ui/core'; +import { createStyles, makeStyles, useTheme } from '@material-ui/core/styles'; +import React from 'react'; +import Chart from 'react-google-charts'; +import { ReactGoogleChartProps } from 'react-google-charts/dist/types'; +import InfoPaper from './InfoPaper'; + +const useStyles = makeStyles(theme => + createStyles({ + summaryPaper: { + flex: 1, + background: theme.palette.background.default, + padding: theme.spacing(1.5), + }, + title: { + textAlign: 'center', + }, + chart: { + width: '100%', + height: '100%', + }, + loader: { + position: 'absolute', + top: '50%', + left: '50%', + }, + }) +); + +interface Props extends ReactGoogleChartProps { + title: string; + PaperProps?: PaperProps; +} + +function ChartPaper({ PaperProps, title, ...chartProps }: Props): JSX.Element { + const classes = useStyles(); + const theme = useTheme(); + const { fontStyle } = theme.mixins.chart(theme); + + return ( + + } + /> + + ); +} + +export default ChartPaper; diff --git a/client/src/components/info-paper/InfoPaper.tsx b/client/src/components/info-paper/InfoPaper.tsx new file mode 100644 index 000000000..46419022a --- /dev/null +++ b/client/src/components/info-paper/InfoPaper.tsx @@ -0,0 +1,37 @@ +import { Paper, PaperProps, Typography } from '@material-ui/core'; +import { createStyles, makeStyles } from '@material-ui/core/styles'; +import clsx from 'clsx'; +import React from 'react'; + +const useStyles = makeStyles(theme => + createStyles({ + summaryPaper: { + flex: 1, + background: theme.palette.background.default, + padding: theme.spacing(1.5), + position: 'relative', + }, + title: { + textAlign: 'center', + }, + }) +); + +interface Props extends PaperProps { + title: string; + children?: React.ReactNode; +} + +function InfoPaper({ children, title, className, ...props }: Props): JSX.Element { + const classes = useStyles(); + + return ( + + {title} + + {children} + + ); +} + +export default InfoPaper; diff --git a/client/src/hooks/fetching/Scheincriteria.ts b/client/src/hooks/fetching/Scheincriteria.ts index 851d6969d..956bc4d7d 100644 --- a/client/src/hooks/fetching/Scheincriteria.ts +++ b/client/src/hooks/fetching/Scheincriteria.ts @@ -1,6 +1,10 @@ import axios from './Axios'; import { FormDataResponse } from '../../components/generatedForm/types/FieldData'; -import { ScheinCriteriaResponse, ScheinCriteriaDTO } from 'shared/dist/model/ScheinCriteria'; +import { + ScheinCriteriaResponse, + ScheinCriteriaDTO, + CriteriaInformation, +} from 'shared/dist/model/ScheinCriteria'; export async function getAllScheinCriterias(): Promise { const response = await axios.get('scheincriteria'); @@ -12,6 +16,18 @@ export async function getAllScheinCriterias(): Promise return Promise.reject(`Wrong response code (${response.status}).`); } +export async function getScheincriteriaInformation( + criteriaId: string +): Promise { + const response = await axios.get(`scheincriteria/${criteriaId}/info`); + + if (response.status === 200) { + return response.data; + } + + return Promise.reject(`Wrong response code (${response.status}).`); +} + export async function getScheinCriteriaFormData(): Promise { const response = await axios.get('scheincriteria/form'); diff --git a/client/src/hooks/fetching/Student.ts b/client/src/hooks/fetching/Student.ts index ca07ca498..60219212b 100644 --- a/client/src/hooks/fetching/Student.ts +++ b/client/src/hooks/fetching/Student.ts @@ -1,6 +1,9 @@ import { Attendance, AttendanceDTO } from 'shared/dist/model/Attendance'; import { UpdatePointsDTO } from 'shared/dist/model/Points'; -import { ScheinCriteriaSummary } from 'shared/dist/model/ScheinCriteria'; +import { + ScheinCriteriaSummary, + ScheincriteriaSummaryByStudents, +} from 'shared/dist/model/ScheinCriteria'; import { CakeCountDTO, PresentationPointsDTO, @@ -152,12 +155,10 @@ export async function getScheinCriteriaSummaryOfStudent( return Promise.reject(`Wrong status code (${response.status}).`); } -export async function getScheinCriteriaSummaryOfAllStudents(): Promise<{ - [studentId: string]: ScheinCriteriaSummary; -}> { - const response = await axios.get<{ [studentId: string]: ScheinCriteriaSummary }>( - `/scheincriteria/student` - ); +export async function getScheinCriteriaSummaryOfAllStudents(): Promise< + ScheincriteriaSummaryByStudents +> { + const response = await axios.get(`/scheincriteria/student`); if (response.status === 200) { return response.data; diff --git a/client/src/routes/Routing.helpers.ts b/client/src/routes/Routing.helpers.ts index 39ee403f5..59af9df7e 100644 --- a/client/src/routes/Routing.helpers.ts +++ b/client/src/routes/Routing.helpers.ts @@ -122,3 +122,7 @@ export function getStudentInfoPath({ studentId, tutorialId }: StudentInfoParams) .replace(':tutorialId?', tutorialId ?? '') .replace(/\/\/+/, '/'); } + +export function getScheincriteriaInfoPath(criteriaId: string): string { + return RoutingPath.SCHEIN_CRITERIAS_INFO.replace(':id', criteriaId).replace(/\/\/+/, '/'); +} diff --git a/client/src/routes/Routing.routes.tsx b/client/src/routes/Routing.routes.tsx index 75ecdc70a..505005517 100644 --- a/client/src/routes/Routing.routes.tsx +++ b/client/src/routes/Routing.routes.tsx @@ -35,6 +35,7 @@ import UserManagement from '../view/usermanagement/UserManagement'; import ScheinexamPointsOverview from '../view/points-scheinexam/overview/ScheinexamPointsOverview'; import EnterScheinexamPoints from '../view/points-scheinexam/enter-form/EnterScheinexamPoints'; import StudentInfo from '../view/studentmanagement/student-info/StudentInfo'; +import CriteriaInfoView from '../view/criteria-info-view/CriteriaInfoView'; export enum RoutingPath { ROOT = '/', @@ -52,7 +53,8 @@ export enum RoutingPath { MANAGE_USERS = '/admin/usermanagement', MANAGE_TUTORIALS = '/admin/tutorialmanagement', MANAGE_TUTORIALS_SUBSTITUTES = '/admin/tutorialmanagement/:tutorialid/substitute', - MANAGE_SCHEIN_CRITERIAS = '/admin/scheincriteriamanagement', + MANAGE_SCHEIN_CRITERIAS = '/admin/scheincriterias', + SCHEIN_CRITERIAS_INFO = '/admin/scheincriterias/info/:id', MANAGE_ATTENDANCES = '/admin/attendances', MANAGE_SHEETS = '/admin/sheets', MANAGE_ALL_STUDENTS = '/admin/students', @@ -224,6 +226,15 @@ export const ROUTES: readonly RouteType[] = [ isInDrawer: true, isPrivate: true, }, + { + path: RoutingPath.SCHEIN_CRITERIAS_INFO, + title: 'Scheinkriterien', + component: CriteriaInfoView, + icon: ScriptTextIcon, + roles: [Role.ADMIN, Role.EMPLOYEE], + isInDrawer: false, + isPrivate: true, + }, { path: RoutingPath.MANAGE_SCHEIN_CRITERIAS, title: 'Scheinkriterien', diff --git a/client/src/view/attendance/AttendanceManager.tsx b/client/src/view/attendance/AttendanceManager.tsx index ca03c89cd..4d493f35c 100644 --- a/client/src/view/attendance/AttendanceManager.tsx +++ b/client/src/view/attendance/AttendanceManager.tsx @@ -6,18 +6,18 @@ import { useSnackbar } from 'notistack'; import React, { ChangeEvent, useEffect, useState } from 'react'; import { Attendance, AttendanceDTO, AttendanceState } from 'shared/dist/model/Attendance'; import { Student, StudentStatus } from 'shared/dist/model/Student'; -import { LoggedInUser, TutorInfo } from 'shared/dist/model/User'; +import { LoggedInUser } from 'shared/dist/model/User'; +import { NoteFormCallback } from '../../components/attendance-controls/components/AttendanceNotePopper'; import CustomSelect from '../../components/CustomSelect'; import DateOfTutorialSelection from '../../components/DateOfTutorialSelection'; -import SubmitButton from '../../components/loading/SubmitButton'; import LoadingSpinner from '../../components/loading/LoadingSpinner'; +import SubmitButton from '../../components/loading/SubmitButton'; import TableWithPadding from '../../components/TableWithPadding'; import { useAxios } from '../../hooks/FetchingService'; import { useLogin } from '../../hooks/LoginService'; import { TutorialWithFetchedStudents as Tutorial } from '../../typings/types'; import { parseDateToMapKey, saveBlob } from '../../util/helperFunctions'; import StudentAttendanceRow from './components/StudentsAttendanceRow'; -import { NoteFormCallback } from '../../components/attendance-controls/components/AttendanceNotePopper'; const useStyles = makeStyles((theme: Theme) => createStyles({ @@ -112,7 +112,6 @@ function AttendanceManager({ tutorial: tutorialFromProps }: Props): JSX.Element const [date, setDate] = useState(undefined); - const [tutorInfo, setTutorInfo] = useState(); const [tutorial, setTutorial] = useState(); const [tutorials, setTutorials] = useState([]); @@ -128,7 +127,6 @@ function AttendanceManager({ tutorial: tutorialFromProps }: Props): JSX.Element setAttendanceOfStudent, setCakeCountForStudent, getAllTutorialsAndFetchStudents, - getTutorInfoOfTutorial, getAttendancePDF, } = useAxios(); @@ -137,16 +135,11 @@ function AttendanceManager({ tutorial: tutorialFromProps }: Props): JSX.Element }, [tutorialFromProps]); useEffect(() => { - let students: Student[] = []; - - if (tutorial) { - students = tutorial.students; - getTutorInfoOfTutorial(tutorial.id).then(info => setTutorInfo(info)); - } + const students: Student[] = tutorial?.students ?? []; setFetchedStudents(students); setDate(undefined); - }, [tutorial, getTutorInfoOfTutorial]); + }, [tutorial]); useEffect(() => { setFilteredStudents(getFilteredStudents(fetchedStudents, filterOption)); @@ -378,7 +371,7 @@ function AttendanceManager({ tutorial: tutorialFromProps }: Props): JSX.Element color='primary' isSubmitting={isLoadingPDF} onClick={printAttendanceSheet} - disabled={!tutorial || !date || !tutorInfo} + disabled={!tutorial || !date} tooltipText='Erstellt eine Unterschriftenliste für den ausgewählten Tag.' > Unterschriftenliste erstellen diff --git a/client/src/view/criteria-info-view/CriteriaInfoView.tsx b/client/src/view/criteria-info-view/CriteriaInfoView.tsx new file mode 100644 index 000000000..840deb0c4 --- /dev/null +++ b/client/src/view/criteria-info-view/CriteriaInfoView.tsx @@ -0,0 +1,282 @@ +import { + Box, + Chip, + Grid, + Table, + TableBody, + TableCell, + TableRow, + Typography, +} from '@material-ui/core'; +import { createStyles, makeStyles, useTheme } from '@material-ui/core/styles'; +import React, { useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useParams } from 'react-router'; +import { PointMap } from 'shared/dist/model/Points'; +import { CriteriaInformation, CriteriaInformationItem } from 'shared/dist/model/ScheinCriteria'; +import { HasExercises } from 'shared/dist/model/Sheet'; +import { Student } from 'shared/dist/model/Student'; +import { getNameOfEntity } from 'shared/dist/util/helpers'; +import BackButton from '../../components/BackButton'; +import CustomSelect, { OnChangeHandler } from '../../components/CustomSelect'; +import ChartPaper from '../../components/info-paper/ChartPaper'; +import InfoPaper from '../../components/info-paper/InfoPaper'; +import PaperTableRow from '../../components/PaperTableRow'; +import Placeholder from '../../components/Placeholder'; +import PointsTable from '../../components/points-table/PointsTable'; +import StudentAvatar from '../../components/student-icon/StudentAvatar'; +import TableWithPadding from '../../components/TableWithPadding'; +import { getScheincriteriaInformation } from '../../hooks/fetching/Scheincriteria'; +import { getAllStudents } from '../../hooks/fetching/Student'; +import { useErrorSnackbar } from '../../hooks/useErrorSnackbar'; +import { RoutingPath } from '../../routes/Routing.routes'; +import { i18nNamespace } from '../../util/lang/configI18N'; + +const useStyles = makeStyles(theme => + createStyles({ + backButton: { + marginRight: theme.spacing(2), + alignSelf: 'center', + }, + graphGrid: { + marginTop: theme.spacing(1.5), + marginBottom: theme.spacing(2), + }, + studentRow: { + marginBottom: theme.spacing(2), + }, + }) +); + +interface PathParams { + id: string; +} + +function CriteriaInfoView(): JSX.Element { + const classes = useStyles(); + const theme = useTheme(); + + const { id: criteriaId } = useParams(); + const { setError } = useErrorSnackbar(); + + const { t } = useTranslation(i18nNamespace.SCHEINCRITERIA); + + const [students, setStudents] = useState(); + const [criteriaInfo, setCriteriaInfo] = useState(); + const [information, setInformation] = useState(); + const [selectedSheetOrExam, setSelectetSheetOrExam] = useState(); + + useEffect(() => { + getAllStudents() + .then(response => { + setStudents(response); + }) + .catch(() => { + setError('Studierende konnten nicht abgerufen werden.'); + setStudents(undefined); + }); + }, [setError]); + + useEffect(() => { + getScheincriteriaInformation(criteriaId) + .then(response => { + setCriteriaInfo(response); + }) + .catch(() => { + setError('Informationen über das Kriterium konnten nicht abgerufen werden.'); + }); + }, [criteriaId, setError]); + + useEffect(() => { + setInformation( + selectedSheetOrExam ? criteriaInfo?.information[selectedSheetOrExam.id] : undefined + ); + }, [criteriaInfo, selectedSheetOrExam]); + + const handleSheetOrExamChange: OnChangeHandler = event => { + if (!criteriaInfo?.sheetsOrExams) { + return; + } + + const exam = criteriaInfo.sheetsOrExams.find(exam => exam.id === event.target.value); + + setSelectetSheetOrExam(exam); + }; + + return ( + + + + + {criteriaInfo && {criteriaInfo.name}} + + + + {criteriaInfo && criteriaInfo.sheetsOrExams && ( + + `Scheinklausur #${item.no}`} + itemToValue={item => item.id} + onChange={handleSheetOrExamChange} + value={selectedSheetOrExam?.id ?? ''} + /> + + + {!!selectedSheetOrExam && !!information && ( + <> + + + [ + t(`ACHIEVED_PIE_CHART_LABEL_${key}`), + value, + ]), + ]} + options={{ + slices: { + 0: { color: theme.palette.green.dark }, + 1: { color: theme.palette.red.dark }, + 2: { color: theme.palette.orange.dark }, + }, + legend: { + position: 'labeled', + }, + pieSliceText: 'value', + }} + /> + + + {information.distribution && ( + + Number.parseInt(a[0]) - Number.parseInt(b[0])) + .map(([key, info]) => [ + key, + info.value, + info.aboveThreshhold + ? theme.palette.green.dark + : theme.palette.red.dark, + info.value, + ]), + ]} + options={{ + slices: { + 0: { color: theme.palette.green.dark }, + 1: { color: theme.palette.red.dark }, + 2: { color: theme.palette.orange.dark }, + }, + legend: { + position: 'none', + }, + }} + /> + + )} + + {information.averages && ( + + + + + {Object.entries(information.averages).map(([identifier, info]) => ( + + {identifier} + {`${Math.round(info.value * 100) / + 100} / ${info.total}`} + + ))} + +
+
+
+ )} +
+ + + + {!!students && !!selectedSheetOrExam && ( + { + const hasPassed = criteriaInfo.studentSummaries[student.id].passed; + const hasAttended = new PointMap(student.scheinExamResults).has( + selectedSheetOrExam.id + ); + + return ( + } + className={classes.studentRow} + > + + + + {hasAttended ? ( + + ) : ( + + )} + + + + + + + + + ); + }} + /> + )} + + + + )} +
+
+ )} +
+
+ ); +} + +export default CriteriaInfoView; diff --git a/client/src/view/scheincriteriamanagement/components/ScheinCriteriaRow.tsx b/client/src/view/scheincriteriamanagement/components/ScheinCriteriaRow.tsx index 3a0cc8d35..ac07fd38a 100644 --- a/client/src/view/scheincriteriamanagement/components/ScheinCriteriaRow.tsx +++ b/client/src/view/scheincriteriamanagement/components/ScheinCriteriaRow.tsx @@ -1,11 +1,14 @@ import { TableCell, Typography } from '@material-ui/core'; import { createStyles, makeStyles, Theme } from '@material-ui/core/styles'; +import { InformationOutline as InfoIcon } from 'mdi-material-ui'; import React from 'react'; import { useTranslation } from 'react-i18next'; import { ScheinCriteriaResponse as ScheinCriteria } from 'shared/dist/model/ScheinCriteria'; import EntityListItemMenu from '../../../components/list-item-menu/EntityListItemMenu'; import PaperTableRow, { PaperTableRowProps } from '../../../components/PaperTableRow'; import { i18nNamespace } from '../../../util/lang/configI18N'; +import { useHistory } from 'react-router'; +import { getScheincriteriaInfoPath } from '../../../routes/Routing.helpers'; const useStyles = makeStyles((theme: Theme) => createStyles({ @@ -31,6 +34,7 @@ function ScheinCriteriaRow({ ...rest }: Props): JSX.Element { const classes = useStyles(); + const history = useHistory(); const { t } = useTranslation(i18nNamespace.SCHEINCRITERIA); return ( @@ -41,6 +45,15 @@ function ScheinCriteriaRow({ onEditCriteriaClicked(criteria)} onDeleteClicked={() => onDeleteCriteriaClicked(criteria)} + additionalItems={[ + { + primary: 'Informationen', + Icon: InfoIcon, + onClick: () => { + history.push(getScheincriteriaInfoPath(criteria.id)); + }, + }, + ]} /> } {...rest} diff --git a/client/src/view/studentmanagement/AllStudentsAdminView.tsx b/client/src/view/studentmanagement/AllStudentsAdminView.tsx index 84c4ed2bf..a954c6e8b 100644 --- a/client/src/view/studentmanagement/AllStudentsAdminView.tsx +++ b/client/src/view/studentmanagement/AllStudentsAdminView.tsx @@ -8,7 +8,6 @@ import { useSnackbar } from 'notistack'; import React, { useEffect, useState } from 'react'; import { Attendance } from 'shared/dist/model/Attendance'; import { PointMap } from 'shared/dist/model/Points'; -import { ScheinCriteriaSummary } from 'shared/dist/model/ScheinCriteria'; import { Tutorial } from 'shared/dist/model/Tutorial'; import SubmitButton from '../../components/loading/SubmitButton'; import { getScheinStatusPDF } from '../../hooks/fetching/Files'; @@ -49,20 +48,11 @@ function AdminStudentManagement(): JSX.Element { const [isCreatingCSVFile, setCreatingCSVFile] = useState(false); const [isCreatingScheinStatus, setCreatingScheinStatus] = useState(false); const [tutorials, setTutorials] = useState([]); - const [summaries, setSummaries] = useState<{ [studentId: string]: ScheinCriteriaSummary }>({}); const [{ students }] = useStudentStore(); const { enqueueSnackbar } = useSnackbar(); useEffect(() => { - getScheinCriteriaSummaryOfAllStudents() - .then(response => setSummaries(response)) - .catch(() => - enqueueSnackbar('Konnte Ergebnisse der Scheinkriterien nicht abrufen.', { - variant: 'error', - }) - ); - getAllTutorials().then(response => setTutorials(response)); }, [enqueueSnackbar]); @@ -82,6 +72,8 @@ function AdminStudentManagement(): JSX.Element { async function generateCSVFile() { setCreatingCSVFile(true); + + const summaries = await getScheinCriteriaSummaryOfAllStudents(); const dataArray: Row[] = []; for (const { @@ -137,7 +129,6 @@ function AdminStudentManagement(): JSX.Element { return ( ; +type PropType = RouteComponentProps; function unifyFilterableText(text: string): string { return _.deburr(text).toLowerCase(); @@ -53,32 +50,15 @@ export function getFilteredStudents( }); } -function TutorStudentmanagement({ match: { params }, enqueueSnackbar }: PropType): JSX.Element { +function TutorStudentmanagement({ match: { params } }: PropType): JSX.Element { const classes = useStyles(); - - const [summaries, setSummaries] = useState<{ [studentId: string]: ScheinCriteriaSummary }>({}); - const tutorialId: string = params.tutorialId; - useEffect(() => { - (async function() { - getScheinCriteriaSummariesOfAllStudentsOfTutorial(tutorialId) - .then(response => setSummaries(response)) - .catch(() => { - enqueueSnackbar('Konnte Ergebnisse der Scheinkriterien nicht abrufen.', { - variant: 'error', - }); - - return []; - }); - })(); - }, [tutorialId, enqueueSnackbar]); - return ( -
{}
+
{}
); } -export default withRouter(withSnackbar(TutorStudentmanagement)); +export default withRouter(TutorStudentmanagement); diff --git a/client/src/view/studentmanagement/student-info/components/CriteriaCharts.tsx b/client/src/view/studentmanagement/student-info/components/CriteriaCharts.tsx index d8a7440da..028e492a3 100644 --- a/client/src/view/studentmanagement/student-info/components/CriteriaCharts.tsx +++ b/client/src/view/studentmanagement/student-info/components/CriteriaCharts.tsx @@ -1,69 +1,39 @@ +import { Grid, GridProps } from '@material-ui/core'; +import { useTheme } from '@material-ui/core/styles'; import React from 'react'; -import { makeStyles, createStyles, useTheme } from '@material-ui/core/styles'; -import { Grid, Paper, Typography, CircularProgress, GridProps } from '@material-ui/core'; -import Chart from 'react-google-charts'; import { ScheinCriteriaSummary } from 'shared/dist/model/ScheinCriteria'; - -const useStyles = makeStyles(theme => - createStyles({ - summaryPaper: { - flex: 1, - background: theme.palette.background.default, - padding: theme.spacing(1.5), - }, - title: { - textAlign: 'center', - }, - chart: { - width: '100%', - height: '100%', - }, - loader: { - position: 'absolute', - top: '50%', - left: '50%', - }, - }) -); +import ChartPaper from '../../../../components/info-paper/ChartPaper'; interface Props extends GridProps { scheinStatus: ScheinCriteriaSummary; } function CriteriaCharts({ scheinStatus, ...props }: Props): JSX.Element { - const classes = useStyles(); const theme = useTheme(); - const { fontStyle } = theme.mixins.chart(theme); return ( {Object.values(scheinStatus.scheinCriteriaSummary).map(summary => ( - - {summary.name} - - } - data={[ - ['Status', 'Anzahl'], - ['Erfüllt', summary.achieved], - ['Nicht erfüllt', summary.total - summary.achieved], - ]} - options={{ - backgroundColor: 'transparent', - ...fontStyle, - slices: { - 0: { color: theme.palette.green.dark }, - 1: { color: theme.palette.red.dark }, - }, - legend: { - ...fontStyle, - }, - }} - /> - + ))} diff --git a/client/src/view/studentmanagement/student-overview/Studentoverview.tsx b/client/src/view/studentmanagement/student-overview/Studentoverview.tsx index fba59bb32..d14771e37 100644 --- a/client/src/view/studentmanagement/student-overview/Studentoverview.tsx +++ b/client/src/view/studentmanagement/student-overview/Studentoverview.tsx @@ -48,14 +48,12 @@ type SummariesByStudent = { [studentId: string]: ScheinCriteriaSummary }; interface Props { tutorials?: Tutorial[]; - summaries: SummariesByStudent; allowChangeTutorial?: boolean; additionalTopBarItem?: React.ReactNode; } function Studentoverview({ tutorials, - summaries, allowChangeTutorial, additionalTopBarItem, }: Props): JSX.Element { @@ -189,7 +187,6 @@ function Studentoverview({ - createStyles({ - root: { - flexGrow: 1, - }, - statusProgress: { - maxWidth: '200px', - // width: '50%', - margin: theme.spacing(1), - }, - content: { - '&:hover': { - background: 'rgba(0, 0, 0, 0.1)', - cursor: 'pointer', - }, - '& td': { - // This prevents table-cells from getting too small if other cells take up more space. - whiteSpace: 'nowrap', - '&:nth-last-of-type(2)': { - width: '100%', - }, - }, - }, - infoBlock: { - display: 'grid', - gridRowGap: theme.spacing(1), - gridColumnGap: theme.spacing(2), - gridTemplateColumns: 'max-content 1fr', - }, - noBottomBorder: { - '& > td': { - borderBottom: 'none', - }, - }, - showInfoIcon: { - transition: theme.transitions.create('transform', { - easing: theme.transitions.easing.easeInOut, - duration: theme.transitions.duration.shorter, - }), - transform: 'rotate(0deg)', - }, - showInfoIconOpened: { - transform: 'rotate(-180deg)', - }, - infoRowCell: { - paddingTop: theme.spacing(0), - }, - infoRowContent: { - maxWidth: '95%', - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - marginLeft: 'auto', - marginRight: 'auto', - }, - infoRowTable: { - width: 'unset', - }, - progressBarCell: { - width: '100%', - }, - emailButton: { - paddingRight: theme.spacing(1), - }, - emailIcon: { - marginLeft: theme.spacing(1), - }, - }) -); - -interface Props extends TableRowProps { - student: StudentWithFetchedTeam; - showTutorial?: boolean; - tutorials?: Tutorial[]; - summary?: ScheinCriteriaSummary; - onEditStudentClicked: (student: StudentWithFetchedTeam) => void; - onDeleteStudentClicked: (student: StudentWithFetchedTeam) => void; - onChangeTutorialClicked?: (student: StudentWithFetchedTeam) => void; -} - -interface CustomAvatarProps { - icon?: SvgIconComponent; - AvatarProps?: AvatarProps; - avatarTooltip?: string; -} - -function calculateProgress(summary: ScheinCriteriaSummary) { - let achieved = 0; - let total = 0; - - Object.values(summary.scheinCriteriaSummary).forEach(status => { - const infos = Object.values(status.infos); - - if (status.passed) { - achieved += 1; - total += 1; - } else { - if (infos.length > 0) { - infos.forEach(info => { - achieved += info.achieved / info.total; - total += 1; - }); - } else { - achieved += status.achieved / status.total; - total += 1; - } - } - }); - - return (achieved / total) * 100; -} - -function ExtendableStudentRow({ - student, - className, - onClick, - showTutorial, - tutorials, - summary, - onEditStudentClicked, - onDeleteStudentClicked, - onChangeTutorialClicked, - ...rest -}: Props): JSX.Element { - const { firstname, lastname, team } = student; - const classes = useStyles(); - - const [showInfoBox, setShowInfoBox] = useState(false); - - const tutorial = tutorials && tutorials.find(t => t.id === student.tutorial); - const additionalMenuItems: ListItem[] = [ - { - primary: 'E-Mail', - Icon: MailIcon, - disabled: !student.email, - onClick: () => { - window.location.href = `mailto:${student.email}`; - }, - }, - ]; - - if (onChangeTutorialClicked) { - additionalMenuItems.push({ - primary: 'Tutorium wechseln', - onClick: () => onChangeTutorialClicked(student), - Icon: AccountSwitch, - }); - } - - return ( - <> - } - className={clsx(classes.content, className, showInfoBox && classes.noBottomBorder)} - onClick={() => { - setShowInfoBox(!showInfoBox); - }} - buttonCellContent={ - <> - { - e.stopPropagation(); - setShowInfoBox(!showInfoBox); - }} - > - - - onEditStudentClicked(student)} - onDeleteClicked={() => onDeleteStudentClicked(student)} - stopClickPropagation - additionalItems={additionalMenuItems} - /> - - } - > - - - - - - - - - - {showInfoBox && summary && ( - - -
- -
-
-
- )} - - ); -} - -export default ExtendableStudentRow; diff --git a/client/src/view/studentmanagement/student-overview/components/StudentRow.tsx b/client/src/view/studentmanagement/student-overview/components/StudentRow.tsx index 1f19eb99d..c4c7a0264 100644 --- a/client/src/view/studentmanagement/student-overview/components/StudentRow.tsx +++ b/client/src/view/studentmanagement/student-overview/components/StudentRow.tsx @@ -1,23 +1,20 @@ -import React from 'react'; -import PaperTableRow, { PaperTableRowProps } from '../../../../components/PaperTableRow'; -import { Student, TeamInStudent } from 'shared/dist/model/Student'; -import { getNameOfEntity } from 'shared/dist/util/helpers'; -import StudentAvatar from '../../../../components/student-icon/StudentAvatar'; -import EntityListItemMenu from '../../../../components/list-item-menu/EntityListItemMenu'; -import { ScheinCriteriaSummary } from 'shared/dist/model/ScheinCriteria'; -import { TableCell, Button } from '@material-ui/core'; +import { Button, TableCell } from '@material-ui/core'; +import { createStyles, makeStyles } from '@material-ui/core/styles'; import { + AccountSwitch as ChangeTutorialIcon, InformationOutline as InfoIcon, Mail as MailIcon, - AccountSwitch as ChangeTutorialIcon, } from 'mdi-material-ui'; -import StatusProgress from '../../../management/components/StatusProgress'; -import { calculateProgress } from './StudenRow.helpers'; +import React from 'react'; import { Link } from 'react-router-dom'; +import { Student, TeamInStudent } from 'shared/dist/model/Student'; +import { getNameOfEntity } from 'shared/dist/util/helpers'; +import EntityListItemMenu from '../../../../components/list-item-menu/EntityListItemMenu'; +import { ListItem } from '../../../../components/list-item-menu/ListItemMenu'; +import PaperTableRow, { PaperTableRowProps } from '../../../../components/PaperTableRow'; +import StudentAvatar from '../../../../components/student-icon/StudentAvatar'; import { getStudentInfoPath } from '../../../../routes/Routing.helpers'; -import { makeStyles, createStyles } from '@material-ui/core/styles'; import { useStudentStore } from '../../student-store/StudentStore'; -import { ListItem } from '../../../../components/list-item-menu/ListItemMenu'; const useStyles = makeStyles(theme => createStyles({ @@ -38,7 +35,6 @@ type StudentCallback = (student: Student) => void; interface Props extends PaperTableRowProps { student: Student; - criteriaSummary?: ScheinCriteriaSummary; onEdit: StudentCallback; onDelete: StudentCallback; onChangeTutorial?: StudentCallback; @@ -62,7 +58,6 @@ function getSubtext({ team, prefix }: GetSubtextParams): string { function StudentRow({ student, - criteriaSummary, subtextPrefix, className, onEdit, @@ -103,22 +98,10 @@ function StudentRow({ onEdit(student)} onDeleteClicked={() => onDelete(student)} + additionalItems={additionalMenuItems} /> } > - - - -