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}
/>
}
>
-
-
-
-