Coursemology/coursemology2

View on GitHub
client/app/bundles/course/assessment/submission/components/answers/TextResponse/index.jsx

Summary

Maintainability
B
4 hrs
Test Coverage
import { Controller, useFormContext } from 'react-hook-form';
import { connect } from 'react-redux';
import { Typography } from '@mui/material';
import PropTypes from 'prop-types';

import FormRichTextField from 'lib/components/form/fields/RichTextField';
import { useAppSelector } from 'lib/hooks/store';

import UploadedFileView from '../../../containers/UploadedFileView';
import { questionShape } from '../../../propTypes';
import { getIsSavingAnswer } from '../../../selectors/answerFlags';
import FileInputField from '../../FileInput';
import TextResponseSolutions from '../../TextResponseSolutions';
import { attachmentRequirementMessage } from '../utils';

const TextResponse = (props) => {
  const {
    answerId,
    graderView,
    handleUploadTextResponseFiles,
    question,
    numAttachments,
    readOnly,
    saveAnswerAndUpdateClientVersion,
  } = props;
  const { control } = useFormContext();
  const isSaving = useAppSelector((state) =>
    getIsSavingAnswer(state, answerId),
  );
  const disableField = readOnly || isSaving;
  const { maxAttachments, isAttachmentRequired, maxAttachmentSize } = question;
  const allowUpload = maxAttachments !== 0;

  const readOnlyAnswer = (
    <Controller
      control={control}
      name={`${answerId}.answer_text`}
      render={({ field }) => (
        <Typography
          dangerouslySetInnerHTML={{ __html: field.value }}
          variant="body2"
        />
      )}
    />
  );

  const richtextAnswer = (
    <Controller
      control={control}
      name={`${answerId}.answer_text`}
      render={({ field, fieldState }) => (
        <FormRichTextField
          disabled={readOnly}
          field={{
            ...field,
            onChange: (event) => {
              field.onChange(event);
              saveAnswerAndUpdateClientVersion(answerId);
            },
          }}
          fieldState={fieldState}
          fullWidth
          InputLabelProps={{
            shrink: true,
          }}
          multiline
          renderIf={!readOnly && !question.autogradable}
          variant="standard"
        />
      )}
    />
  );

  const plaintextAnswer = (
    <Controller
      control={control}
      name={`${answerId}.answer_text`}
      render={({ field }) => (
        <textarea
          name={`${answerId}.answer_text`}
          onChange={(e) => {
            field.onChange(e.target.value);
            saveAnswerAndUpdateClientVersion(answerId);
          }}
          rows={5}
          style={{ width: '100%' }}
          value={field.value || ''}
        />
      )}
    />
  );

  const editableAnswer = question.autogradable
    ? plaintextAnswer
    : richtextAnswer;

  const isMultipleAttachmentsAllowed = maxAttachments - numAttachments > 1;
  const isFileUploadStillAllowed = maxAttachments > numAttachments;

  return (
    <div>
      {readOnly ? readOnlyAnswer : editableAnswer}
      {graderView && <TextResponseSolutions question={question} />}
      {allowUpload && (
        <UploadedFileView answerId={answerId} questionId={question.id} />
      )}
      {allowUpload && !readOnly && (
        <FileInputField
          disabled={disableField || !isFileUploadStillAllowed}
          isMultipleAttachmentsAllowed={isMultipleAttachmentsAllowed}
          maxAttachmentsAllowed={maxAttachments - numAttachments}
          maxAttachmentSize={maxAttachmentSize}
          name={`${answerId}.files`}
          numAttachments={numAttachments}
          onChangeCallback={() => handleUploadTextResponseFiles(answerId)}
        />
      )}
      {allowUpload && (
        <Typography variant="body2">
          {attachmentRequirementMessage(maxAttachments, isAttachmentRequired)}
        </Typography>
      )}
    </div>
  );
};

TextResponse.propTypes = {
  answerId: PropTypes.number.isRequired,
  graderView: PropTypes.bool.isRequired,
  handleUploadTextResponseFiles: PropTypes.func.isRequired,
  question: questionShape.isRequired,
  numAttachments: PropTypes.number,
  readOnly: PropTypes.bool.isRequired,
  saveAnswerAndUpdateClientVersion: PropTypes.func.isRequired,
};

function mapStateToProps(state, ownProps) {
  const { question } = ownProps;

  return {
    numAttachments:
      state.assessments.submission.attachments[question.id]?.length ?? 0,
  };
}

export default connect(mapStateToProps)(TextResponse);