Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[frontend/backend] add global observation #1546

Open
wants to merge 6 commits into
base: issue/1080-inject-result
Choose a base branch
from
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
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,7 @@ public class ReportInput {

@JsonProperty("report_informations")
private List<ReportInformationInput> reportInformations;

@JsonProperty("report_global_observation")
private String globalObservation;
}
9 changes: 9 additions & 0 deletions openbas-front/src/actions/reports/report-actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,15 @@ export const updateReportForExercise = (
return putReferential(schema.report, uri, data)(dispatch);
};

export const updateReportGlobalObservation = (
exerciseId: Exercise['exercise_id'],
reportId: Report['report_id'],
data: ReportInput,
) => {
const uri = `/api/exercises/${exerciseId}/reports/${reportId}`;
return simplePutCall(uri, data);
};

export const updateReportInjectCommentForExercise = (
exerciseId: Exercise['exercise_id'],
reportId: Report['report_id'],
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import React, { CSSProperties, useState } from 'react';
import { Paper, Typography } from '@mui/material';
import MarkDownField from '../../../../components/fields/MarkDownField';
import ExpandableMarkdown from '../../../../components/ExpandableMarkdown';

interface Props {
label: string
initialValue: string,
style?: CSSProperties,
onBlur: (observation: string) => void,
canWrite?: boolean
}

const ReportGlobalObservation: React.FC<Props> = ({
label,
initialValue = '',
style = {},
onBlur,
canWrite,
}) => {
const [globalObservation, setGlobalObservationRef] = useState<string>(initialValue);

return (
<div style={style}>
<Typography variant="h4" gutterBottom>
{label}
</Typography>

<Paper variant="outlined" sx={{ ...!canWrite && { padding: '10px 15px 10px 15px' } }}>
{canWrite
? <MarkDownField
onChange={(value: string) => setGlobalObservationRef(value)}
initialValue={globalObservation}
onBlur={() => onBlur(globalObservation)}
/> : <ExpandableMarkdown showAll source={globalObservation}/>
}
</Paper>
</div>
);
};

export default ReportGlobalObservation;
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ const Exercise = () => {
{exercise.exercise_status !== 'SCHEDULED' && (
<Grid item xs={12} style={{ marginTop: 25 }}>
<Typography variant="h4" gutterBottom style={{ marginBottom: 15 }}>
{t('Injects Results')}
{t('Injects results')}
</Typography>
<Paper classes={{ root: classes.paper }} variant="outlined">
<InjectDtoList
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useEffect, useState } from 'react';
import { Paper, Typography } from '@mui/material';
import type { Exercise, ExpectationResultsByType, InjectResultDTO, Report, ReportInformation, ReportInjectComment } from '../../../../../utils/api-types';
import type { Exercise, ExpectationResultsByType, InjectResultDTO, Report, ReportInformation, ReportInput } from '../../../../../utils/api-types';
import { useAppDispatch } from '../../../../../utils/hooks';
import { useFormatter } from '../../../../../components/i18n';
import ReportInformationType from './ReportInformationType';
Expand All @@ -22,9 +22,10 @@ import Loader from '../../../../../components/Loader';
import ExerciseMainInformation from '../ExerciseMainInformation';
import ResponsePie from '../../../common/injects/ResponsePie';
import InjectReportResult from './InjectReportResult';
import { updateReportInjectCommentForExercise } from '../../../../../actions/reports/report-actions';
import { updateReportGlobalObservation, updateReportInjectCommentForExercise } from '../../../../../actions/reports/report-actions';
import LessonsCategories from '../../../lessons/exercises/LessonsCategories';
import ExerciseDistribution from '../overview/ExerciseDistribution';
import ReportGlobalObservation from '../../../components/reports/ReportGlobalObservation';

interface Props {
report: Report,
Expand All @@ -37,6 +38,12 @@ const ExerciseReportContent: React.FC<Props> = ({ report, exerciseId, canWrite =
const { t } = useFormatter();
const [loading, setLoading] = useState(true);

const saveGlobalObservation = (comment: string) => updateReportGlobalObservation(exerciseId, report.report_id, {
report_informations: report.report_informations,
report_global_observation: comment,
report_name: report.report_name,
} as ReportInput);

const displayModule = (moduleType: ReportInformationType) => {
return report?.report_informations?.find((info: ReportInformation) => info.report_informations_type === moduleType)?.report_informations_display;
};
Expand Down Expand Up @@ -66,9 +73,7 @@ const ExerciseReportContent: React.FC<Props> = ({ report, exerciseId, canWrite =
const fetchPromises = [];
setLoading(true);
if (displayModule(ReportInformationType.MAIN_INFORMATION)) {
fetchPromises.push(
dispatch(fetchExercise(exerciseId)),
);
fetchPromises.push(dispatch(fetchExercise(exerciseId)));
}
if (displayModule(ReportInformationType.PLAYER_SURVEYS)) {
fetchPromises.push(
Expand All @@ -78,7 +83,7 @@ const ExerciseReportContent: React.FC<Props> = ({ report, exerciseId, canWrite =
);
}
Promise.all(fetchPromises).then(() => setLoading(false));
}, [report]);
}, [report.report_informations]);

const [exerciseExpectationResults, setResults] = useState<ExpectationResultsByType[] | null>(null);
const [injects, setInjects] = useState<InjectResultDTO[]>([]);
Expand All @@ -89,11 +94,11 @@ const ExerciseReportContent: React.FC<Props> = ({ report, exerciseId, canWrite =
}) => setResults(result.data));
}
if (displayModule(ReportInformationType.INJECT_RESULT)) {
exerciseInjectsResultDTO(exerciseId).then((result: { data: InjectResultDTO[] }) => {
exerciseInjectsResultDTO(exerciseId).then((result) => {
setInjects(result.data);
});
}
}, [report]);
}, [report.report_informations]);

if (loading) {
return <Loader/>;
Expand All @@ -102,7 +107,7 @@ const ExerciseReportContent: React.FC<Props> = ({ report, exerciseId, canWrite =
return (
<div id={`reportId_${report.report_id}`} style={{ padding: 20, display: 'flex', flexFlow: 'wrap', maxWidth: '1400px', margin: 'auto' }}>
<div style={{ width: '100%', textAlign: 'center', fontSize: 25, fontWeight: 500, margin: '10px' }}>
{report?.report_name}
{report.report_name}
</div>
{displayModule(ReportInformationType.MAIN_INFORMATION)
&& <div style={{ width: '50%', paddingRight: '25px' }}>
Expand All @@ -129,10 +134,19 @@ const ExerciseReportContent: React.FC<Props> = ({ report, exerciseId, canWrite =
initialInjectComments={report?.report_injects_comments}
injects={injects}
style={{ width: '100%', marginTop: 20 }}
onCommentSubmit={(value: ReportInjectComment) => updateReportInjectCommentForExercise(exerciseId, report.report_id, value)}
onCommentSubmit={(value) => updateReportInjectCommentForExercise(exerciseId, report.report_id, value)}
/>
)
}
{displayModule(ReportInformationType.GLOBAL_OBSERVATION)
&& <ReportGlobalObservation
label={t('Global observation')}
initialValue={report.report_global_observation || ''}
onBlur={saveGlobalObservation}
style={{ width: '100%', marginTop: 20 }}
canWrite={canWrite}
/>
}
{displayModule(ReportInformationType.PLAYER_SURVEYS)
&& <LessonsCategories
style={{ width: '100%', marginBottom: '60px' }}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,31 +1,69 @@
import React from 'react';
import { z } from 'zod';
import { z, ZodBoolean } from 'zod';
import { SubmitHandler, useForm } from 'react-hook-form';

import { Button, Checkbox, FormControlLabel, TextField, Typography } from '@mui/material';
import { zodResolver } from '@hookform/resolvers/zod';

import type { ReportInput, ReportInformationInput, Report } from '../../../../../utils/api-types';
import { zodImplement } from '../../../../../utils/Zod';
import { useFormatter } from '../../../../../components/i18n';
import ReportInformationType from './ReportInformationType';

interface Props {
onSubmit: SubmitHandler<ReportInput>;
handleCancel: () => void;
editing?: boolean;
initialValues?: Report,
}

interface ExerciseReportFormInput {
report_name: string;
report_main_information: boolean;
report_score_details: boolean;
report_inject_result: boolean;
report_global_observation: boolean;
report_player_surveys: boolean;
report_exercise_details: boolean;
}

interface ExerciseReportModulesConfig {
type: ReportInformationType;
name: 'report_main_information' | 'report_score_details' | 'report_inject_result' | 'report_player_surveys' | 'report_exercise_details' | 'report_name' | 'report_global_observation';
label: string;
}

const exerciseReportModulesConfig: ExerciseReportModulesConfig[] = [
{
type: ReportInformationType.MAIN_INFORMATION,
name: 'report_main_information',
label: 'General information',
},
{
type: ReportInformationType.SCORE_DETAILS,
name: 'report_score_details',
label: 'Score details',
},
{
type: ReportInformationType.INJECT_RESULT,
name: 'report_inject_result',
label: 'Injects results',
},
{
type: ReportInformationType.PLAYER_SURVEYS,
name: 'report_player_surveys',
label: 'Player surveys',
},
{
type: ReportInformationType.GLOBAL_OBSERVATION,
name: 'report_global_observation',
label: 'Global observation',
},
{
type: ReportInformationType.EXERCISE_DETAILS,
name: 'report_exercise_details',
label: 'Exercise details',
},
];

interface Props {
onSubmit: SubmitHandler<ReportInput>;
handleCancel: () => void;
editing?: boolean;
initialValues?: Report,
}
const ExerciseReportForm: React.FC<Props> = ({
onSubmit,
handleCancel,
Expand All @@ -39,57 +77,36 @@ const ExerciseReportForm: React.FC<Props> = ({
return initialValues?.report_informations?.find((info) => info.report_informations_type === type)?.report_informations_display ?? true;
};

const initialFormValues: ExerciseReportFormInput = {
report_name: initialValues?.report_name || '',
report_main_information: findReportInfo(ReportInformationType.MAIN_INFORMATION),
report_score_details: findReportInfo(ReportInformationType.SCORE_DETAILS),
report_inject_result: findReportInfo(ReportInformationType.INJECT_RESULT),
report_player_surveys: findReportInfo(ReportInformationType.PLAYER_SURVEYS),
report_exercise_details: findReportInfo(ReportInformationType.EXERCISE_DETAILS),
};
const initialModulesValues: Record<string, boolean> = {};
const modulesFormInput: Record<string, ZodBoolean > = {};
exerciseReportModulesConfig.forEach((moduleConfig) => {
initialModulesValues[moduleConfig.name] = findReportInfo(moduleConfig.type);
modulesFormInput[moduleConfig.name] = z.boolean();
});

const {
register,
handleSubmit,
formState: { errors, isSubmitting },
} = useForm<ExerciseReportFormInput>({
mode: 'onTouched',
resolver: zodResolver(
zodImplement<ExerciseReportFormInput>().with({
report_name: z.string().min(1, { message: t('Should not be empty') }),
report_main_information: z.boolean(),
report_score_details: z.boolean(),
report_inject_result: z.boolean(),
report_player_surveys: z.boolean(),
report_exercise_details: z.boolean(),
}),
),
defaultValues: initialFormValues,
resolver: zodResolver(z.object({
report_name: z.string().min(1, { message: t('Should not be empty') }),
...modulesFormInput,
})),
defaultValues: {
report_name: initialValues?.report_name || '',
...initialModulesValues,
},
});

const onSubmitHandler = (data: ExerciseReportFormInput) => {
const reportInformationList: ReportInformationInput[] = [
{
report_informations_type: ReportInformationType.MAIN_INFORMATION,
report_informations_display: data.report_main_information,
},
{
report_informations_type: ReportInformationType.SCORE_DETAILS,
report_informations_display: data.report_score_details,
},
{
report_informations_type: ReportInformationType.INJECT_RESULT,
report_informations_display: data.report_inject_result,
},
{
report_informations_type: ReportInformationType.PLAYER_SURVEYS,
report_informations_display: data.report_player_surveys,
},
{
report_informations_type: ReportInformationType.EXERCISE_DETAILS,
report_informations_display: data.report_exercise_details,
},
];
const reportInformationList: ReportInformationInput[] = exerciseReportModulesConfig.map((moduleConfig) => {
return {
report_informations_type: moduleConfig.type,
report_informations_display: !!data[moduleConfig.name],
};
});
onSubmit({ report_name: data.report_name, report_informations: reportInformationList });
};

Expand All @@ -115,51 +132,20 @@ const ExerciseReportForm: React.FC<Props> = ({
>
{t('Modules')}
</Typography>
<FormControlLabel
control={
<Checkbox
{...register('report_main_information')}
defaultChecked={initialFormValues?.report_main_information}
{ exerciseReportModulesConfig.map((moduleConfig) => {
return (
<FormControlLabel
key={moduleConfig.name}
control={
<Checkbox
{...register(moduleConfig.name)}
defaultChecked={initialModulesValues[moduleConfig.name]}
/>
}
label={t(moduleConfig.label)}
/>
}
label={t('General information')}
/>
<FormControlLabel
control={
<Checkbox
{...register('report_score_details')}
defaultChecked={initialFormValues?.report_score_details}
/>
}
label={t('Score details')}
/>
<FormControlLabel
control={
<Checkbox
{...register('report_inject_result')}
defaultChecked={initialFormValues?.report_inject_result}
/>
}
label={t('Injects results')}
/>
<FormControlLabel
control={
<Checkbox
{...register('report_player_surveys')}
defaultChecked={initialFormValues?.report_player_surveys}
/>
}
label={t('Player surveys')}
/>
<FormControlLabel
control={
<Checkbox
{...register('report_exercise_details')}
defaultChecked={initialFormValues?.report_exercise_details}
/>
}
label={t('Exercise details')}
/>
);
})}
<div style={{ gridColumn: 'span 2', marginTop: '20px', display: 'flex' }}>
<Button
style={{ marginLeft: 'auto' }}
Expand Down
Loading