Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion client/src/components/SplitButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,17 @@ interface Props extends ButtonGroupProps {
initiallySelected?: number;
variant?: ButtonProps['variant'];
color?: ButtonProps['color'];
onMenuItemClick?: (index: number) => void;
}

function SplitButton({ options, initiallySelected, variant, color, ...props }: Props): JSX.Element {
function SplitButton({
options,
initiallySelected,
variant,
color,
onMenuItemClick,
...props
}: Props): JSX.Element {
const [open, setOpen] = React.useState(false);
const anchorRef = React.useRef<HTMLDivElement>(null);
const logger = useLogger('SplitButton');
Expand All @@ -69,6 +77,10 @@ function SplitButton({ options, initiallySelected, variant, color, ...props }: P
) => {
setSelectedIndex(index);
setOpen(false);

if (onMenuItemClick) {
onMenuItemClick(index);
}
};

const handleToggle = () => {
Expand Down
3 changes: 2 additions & 1 deletion client/src/model/Grading.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Transform, Type } from 'class-transformer';
import { IExerciseGrading, IGrading } from 'shared/model/Gradings';
import { IExerciseGrading, IGrading, SheetState } from 'shared/model/Gradings';
import { Modify } from '../typings/Modify';
import { Exercise, Subexercise } from './Exercise';

Expand Down Expand Up @@ -60,6 +60,7 @@ export class Grading implements Modify<IGrading, Modified> {
readonly belongsToTeam!: boolean;
readonly comment?: string;
readonly additionalPoints?: number;
readonly sheetState?: SheetState;

@Type(() => ExerciseGrading)
@Transform(({ value }) => new Map(value))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,5 +89,6 @@ export function convertFormStateToGradingDTO({
createNewGrading: !prevGrading,
comment: values.comment,
additionalPoints,
sheetState: values.sheetState,
};
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { SheetState } from 'shared/model/Gradings';
import { Exercise, HasExercises } from '../../../../model/Exercise';
import { ExerciseGrading, Grading } from '../../../../model/Grading';
import { FormikSubmitCallback } from '../../../../types';
Expand All @@ -17,6 +18,7 @@ export interface PointsFormState {
exercises: {
[exerciseId: string]: PointsFormExerciseState;
};
sheetState?: SheetState;
}

export type PointsFormSubmitCallback = FormikSubmitCallback<PointsFormState>;
Expand Down Expand Up @@ -76,5 +78,6 @@ export function generateInitialValues({ sheet, grading }: InitialValuesOptions):
comment: grading?.comment ?? '',
additionalPoints: grading?.additionalPoints?.toString() ?? '0',
exercises,
sheetState: grading?.sheetState ? (grading.sheetState ?? SheetState.NO_STATE) : undefined,
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,14 @@ import makeStyles from '@mui/styles/makeStyles';
import clsx from 'clsx';
import { Formik, useFormikContext } from 'formik';
import React, { useEffect, useState } from 'react';
import { convertExercisePointInfoToString, getPointsOfAllExercises } from 'shared/model/Gradings';
import {
convertExercisePointInfoToString,
getPointsOfAllExercises,
SheetState,
} from 'shared/model/Gradings';
import FormikDebugDisplay from '../../../../components/forms/components/FormikDebugDisplay';
import SubmitButton from '../../../../components/loading/SubmitButton';
import SplitButton from '../../../../components/SplitButton';
import { useDialog } from '../../../../hooks/dialog-service/DialogService';
import { useKeyboardShortcut } from '../../../../hooks/useKeyboardShortcut';
import { Exercise } from '../../../../model/Exercise';
Expand All @@ -30,21 +35,22 @@ const useStyles = makeStyles((theme: Theme) =>
},
textBox: {
display: 'flex',
alignItems: 'center',
marginBottom: theme.spacing(1),
},
unsavedChangesText: {
marginLeft: theme.spacing(1),
},
pointsText: {
marginLeft: 'auto',
marginRight: theme.spacing(2),
},
exerciseBox: {
flex: 1,
},
buttonRow: {
display: 'flex',
justifyContent: 'flex-end',
// This prevents a flashing scrollbar if the form spinner is shown.
marginBottom: theme.spacing(0.5),
},
cancelButton: {
Expand Down Expand Up @@ -74,8 +80,7 @@ function EnterPointsForm({ grading, setIsAutoSubmitting, ...props }: Props): JSX
);

useEffect(() => {
const values = generateInitialValues({ grading, sheet });
setInitialValues(values);
setInitialValues(generateInitialValues({ grading, sheet }));
}, [grading, sheet]);

return (
Expand All @@ -94,9 +99,10 @@ function EnterPointsFormInner({
}: FormProps): JSX.Element {
const classes = useStyles();
const dialog = useDialog();

const formikContext = useFormikContext<PointsFormState>();
const { values, handleSubmit, resetForm, isSubmitting, dirty, submitForm } = formikContext;

const { values, handleSubmit, resetForm, isSubmitting, dirty, submitForm, setFieldValue } =
formikContext;

const achieved = getAchievedPointsFromState(values);
const total = getPointsOfAllExercises(sheet);
Expand Down Expand Up @@ -142,31 +148,73 @@ function EnterPointsFormInner({

useKeyboardShortcut([{ key: 's', modifiers: { ctrlKey: true } }], (e) => {
e.preventDefault();
if (dirty) submitForm();
});

if (!dirty) {
return;
const handleOnMenuItemClick = (index: number) => {
if (index === 0) {
setFieldValue('sheetState', SheetState.PASSED);
} else {
setFieldValue('sheetState', SheetState.NOT_PASSED);
}
};

submitForm();
});
const toggleSheetState = (newState: SheetState) => {
const newSheetState = values.sheetState === newState ? SheetState.NO_STATE : newState;
setFieldValue('sheetState', newSheetState);
};

const buttonColor =
values.sheetState === SheetState.PASSED
? 'success'
: values.sheetState === SheetState.NOT_PASSED
? 'error'
: 'inherit';

// unstable_usePrompt({
// message: 'Es gibt ungespeicherte Änderungen. Soll die Seite wirklich verlassen werden?',
// when: dirty,
// });
const initiallySelected =
values.sheetState === SheetState.PASSED
? 0
: values.sheetState === SheetState.NOT_PASSED
? 1
: 0;

return (
<>
<form {...props} onSubmit={handleSubmit} className={clsx(classes.root, className)}>
<div className={classes.textBox}>
{dirty && (
<Typography className={classes.unsavedChangesText}>
{<>Es gibt ungespeicherte Änderungen.</>}
Es gibt ungespeicherte Änderungen.
</Typography>
)}
<Typography
className={classes.pointsText}
>{`Gesamt: ${achieved} / ${totalPoints} Punkte`}</Typography>
<Typography className={classes.pointsText}>
{`Gesamt: ${achieved} / ${totalPoints} Punkte`}
</Typography>

<SplitButton
variant='outlined'
color={buttonColor}
initiallySelected={initiallySelected}
onMenuItemClick={handleOnMenuItemClick}
options={[
{
label: 'Als bestanden markieren',
ButtonProps: {
onClick: () => {
toggleSheetState(SheetState.PASSED);
},
},
},
{
label: 'Als nicht bestanden markieren',
ButtonProps: {
onClick: () => {
toggleSheetState(SheetState.NOT_PASSED);
},
},
},
]}
/>
</div>

<Box display='flex' flexDirection='column' flex={1}>
Expand Down
11 changes: 10 additions & 1 deletion server/src/database/entities/grading.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import {
PrimaryKey,
Property,
} from '@mikro-orm/core';
import { IExerciseGrading, IGrading } from 'shared/model/Gradings';
import { EncryptedEnumType } from '../types/encryption/EncryptedEnumType';
import { IExerciseGrading, IGrading, SheetState } from 'shared/model/Gradings';
import { v4 } from 'uuid';
import { ExerciseGradingDTO, GradingDTO } from '../../module/student/student.dto';
import { EncryptedMapType } from '../types/encryption/EncryptedMapType';
Expand Down Expand Up @@ -107,6 +108,9 @@ export class Grading {
@ManyToOne(() => ShortTest, { deleteRule: 'cascade' })
private shortTest?: ShortTest;

@Property({ type: EncryptedEnumType })
sheetState?: SheetState;

@Property({ nullable: false })
handInId?: string;

Expand Down Expand Up @@ -172,6 +176,10 @@ export class Grading {
this.additionalPoints = dto.additionalPoints ?? 0;
this.exerciseGradings = [];

if (dto.sheetState) {
this.sheetState = dto.sheetState;
}

for (const [exerciseId, exerciseGradingDTO] of dto.exerciseGradings) {
this.exerciseGradings.push(ExerciseGrading.fromDTO(exerciseId, exerciseGradingDTO));
}
Expand Down Expand Up @@ -202,6 +210,7 @@ export class Grading {
comment: this.comment,
belongsToTeam: this.belongsToTeam,
exerciseGradings: [...exerciseGradings],
sheetState: this.sheetState,
};
}

Expand Down
6 changes: 3 additions & 3 deletions server/src/module/markdown/markdown.service.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import { BadRequestException, Injectable } from '@nestjs/common';
import hljs from 'highlight.js';
import MarkdownIt from 'markdown-it';
import { convertExercisePointInfoToString } from 'shared/model/Gradings';
import { convertExercisePointInfoToString, SheetState } from 'shared/model/Gradings';
import { IStudentMarkdownData, ITeamMarkdownData } from 'shared/model/Markdown';
import { getNameOfEntity } from 'shared/util/helpers';
import { HasExercises } from '../../database/entities/ratedEntity.entity';
import { Team } from '../../database/entities/team.entity';
import { ScheinexamService } from '../scheinexam/scheinexam.service';
import { SheetService } from '../sheet/sheet.service';
import { ShortTestService } from '../short-test/short-test.service';
import { GradingService } from '../student/grading.service';
import { StudentService } from '../student/student.service';
import { TeamService } from '../team/team.service';
import {
Expand All @@ -23,7 +24,6 @@ import {
TeamGradings,
TeamMarkdownData,
} from './markdown.types';
import { GradingService } from '../student/grading.service';

@Injectable()
export class MarkdownService {
Expand Down Expand Up @@ -278,7 +278,7 @@ export class MarkdownService {
});

const totalPointInfo = convertExercisePointInfoToString(pointInfo.total);
const header = `# ${nameOfEntity}\n\n**Gesamt: ${pointInfo.achieved} / ${totalPointInfo}**`;
const header = `# ${nameOfEntity}${grading.sheetState && grading.sheetState !== SheetState.NO_STATE ? `: ${grading.sheetState}` : ''}\n\n**Gesamt: ${pointInfo.achieved} / ${totalPointInfo}**`;

return `${header}\n\n${exerciseMarkdown}`;
}
Expand Down
11 changes: 10 additions & 1 deletion server/src/module/student/student.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,12 @@ import {
ValidateNested,
} from 'class-validator';
import { AttendanceState, IAttendanceDTO } from 'shared/model/Attendance';
import { IExerciseGradingDTO, IGradingDTO, IPresentationPointsDTO } from 'shared/model/Gradings';
import {
IExerciseGradingDTO,
IGradingDTO,
IPresentationPointsDTO,
SheetState,
} from 'shared/model/Gradings';
import {
ICakeCountDTO,
ICreateStudentDTO,
Expand Down Expand Up @@ -148,6 +153,10 @@ export class GradingDTO implements IGradingDTO {
@IsOptional()
@IsNumber()
additionalPoints?: number;

@IsOptional()
@IsEnum(SheetState)
sheetState?: SheetState;
}

export class PresentationPointsDTO implements IPresentationPointsDTO {
Expand Down
8 changes: 8 additions & 0 deletions server/src/shared/model/Gradings.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import { IExercise, IHasExercises } from './HasExercises';

export enum SheetState {
PASSED = 'Bestanden',
NOT_PASSED = 'Nicht bestanden',
NO_STATE = 'Nicht bewertet',
}

export interface GradingResponseData {
studentId: string;
gradingData: IGrading | undefined;
Expand All @@ -19,6 +25,7 @@ export interface IGrading {
points: number;
comment?: string;
additionalPoints?: number;
sheetState?: SheetState;
}

export interface IExerciseGradingDTO {
Expand All @@ -36,6 +43,7 @@ export interface IGradingDTO {
shortTestId?: string;
comment?: string;
additionalPoints?: number;
sheetState?: SheetState;
}

export interface IPresentationPointsDTO {
Expand Down