Commit 404de8db authored by Marek Veselý's avatar Marek Veselý
Browse files

fix: render correct answers from backend (fix regex bug)

parent af55cfd8
Loading
Loading
Loading
Loading
+9 −22
Original line number Diff line number Diff line
@@ -24,6 +24,8 @@ import type { NavigateOptions } from '@tanstack/react-router'
import type { FC, FormEvent } from 'react'
import { useCallback, useMemo, useState } from 'react'
import Questionnaire from '../../../components/Questionnaire'
import type { QuestionAndAnswer } from '../../../components/Questionnaire/types'
import { mapQuestionsAndAnswers } from '../../../components/Questionnaire/utils'

interface TraineeQuestionnaireContentProps {
  teamState: TeamQuestionnaireStateWithQuestionnaire
@@ -48,33 +50,15 @@ const AnswerState = (
  teamStateId: string
) =>
  createSyncedStore<{
    questions: {
      question: Question
      answer: string[]
      error: string
    }[]
    questions: QuestionAndAnswer[]
    resetQuestions: () => void
    modifyAnswer: (questionId: string, answer: string) => void
    validate: () => boolean
  }>(
    set => ({
      questions: questions.map(question => ({
        question,
        answer:
          answers.find(answer => answer.question.id === question.id)?.answer ||
          [],
        error: '',
      })),
      questions: mapQuestionsAndAnswers({ questions, answers }),
      resetQuestions: () =>
        set({
          questions: questions.map(question => ({
            question,
            answer:
              answers.find(answer => answer.question.id === question.id)
                ?.answer || [],
            error: '',
          })),
        }),
        set({ questions: mapQuestionsAndAnswers({ questions, answers }) }),
      modifyAnswer: (questionId: string, answer: string) =>
        set(({ questions }) => ({
          questions: questions.map(question => {
@@ -162,7 +146,10 @@ const TraineeQuestionnaireContent: FC<TraineeQuestionnaireContentProps> = ({
    [answers, questions, teamStateId]
  )

  const questionsAndAnswers = useStore(state, state => state.questions)
  const questionsAndAnswers: QuestionAndAnswer[] = useStore(
    state,
    state => state.questions
  )

  const modifyAnswer = useStore(state, state => state.modifyAnswer)
  const validate = useStore(state, state => state.validate)
+13 −28
Original line number Diff line number Diff line
import {
  Classes,
  Colors,
  InputGroup,
  Label,
  TextArea,
  type Intent,
} from '@blueprintjs/core'
import { css, cx } from '@emotion/css'
import type { AutoFreeFormQuestionDetailsType } from '@inject/graphql'
import type { AutoFreeFormQuestionDetails } from '@inject/graphql'
import { breakWord } from '@inject/shared'
import { useMemo, type FC } from 'react'
import { RenderedContent } from '../RenderedContent'
import { contentWrapper, lengthDisplay } from './classes'
import { contentWrapper, highlightAnswer, lengthDisplay } from './classes'
import QuestionDescription from './QuestionDescription'
import type { QuestionProps } from './types'
import { notInInterval } from './utils'
@@ -26,35 +25,28 @@ const textArea = css`
  padding-bottom: 1.5rem !important;
`

const highlight = (correct: boolean) => css`
  border-bottom: 0.2rem solid ${correct ? Colors.GREEN5 : Colors.RED5};
const inputGroup = css`
  input {
    padding-right: 40px;
  }
`

const AutoFreeFormQuestion: FC<QuestionProps> = ({
  type,
  question,
  answer,
  correct,
  disabled: disabledProp,
  onChange,
  inInstructor,
  error,
  exerciseId,
}) => {
  const details = question.details as AutoFreeFormQuestionDetailsType
  const details = question.details as AutoFreeFormQuestionDetails

  const { multiline, correctAnswer, min, max, regex } = details
  const disabled = disabledProp || type === 'reviewing'

  const isCorrect = () => {
    if (!correctAnswer) {
      return false
    }
    if (regex) {
      return new RegExp(correctAnswer).test(answer[0])
    }
    return correctAnswer === answer[0]
  }

  const answerString = answer.at(0) || ''
  const intent: Intent = useMemo(
    () =>
@@ -97,7 +89,7 @@ const AutoFreeFormQuestion: FC<QuestionProps> = ({
              }
              autoResize
              className={cx(textArea, {
                [highlight(isCorrect())]: !!answer[0] && inInstructor,
                [highlightAnswer(correct)]: type === 'reviewing',
              })}
            />
          ) : (
@@ -106,16 +98,9 @@ const AutoFreeFormQuestion: FC<QuestionProps> = ({
              intent={intent}
              fill
              value={answer[0]}
              className={cx(
                css`
                  input {
                    padding-right: 40px;
                  }
                `,
                {
                  [highlight(isCorrect())]: !!answer[0] && inInstructor,
                }
              )}
              className={cx(inputGroup, {
                [highlightAnswer(correct)]: type === 'reviewing',
              })}
              onChange={
                onChange
                  ? event => onChange(event.currentTarget.value)
@@ -127,7 +112,7 @@ const AutoFreeFormQuestion: FC<QuestionProps> = ({
            {answer[0] ? answer[0].length : 0}
          </p>
        </div>
        {inInstructor && correctAnswer && (
        {type === 'reviewing' && correctAnswer && (
          <p
            className={cx(
              css`
+32 −9
Original line number Diff line number Diff line
import { Checkbox, Label } from '@blueprintjs/core'
import { Checkbox, Classes, Label } from '@blueprintjs/core'
import { css, cx } from '@emotion/css'
import type { MultipleChoiceQuestionDetailsType } from '@inject/graphql'
import { breakWord } from '@inject/shared'
import type { FC } from 'react'
import { RenderedContent } from '../RenderedContent'
import { highlightCorrect } from './classes'
import { correctOption, highlightAnswer } from './classes'
import type { QuestionProps } from './types'

const label = css`
@@ -20,6 +20,7 @@ const wrapper = css`

const MultipleChoiceQuestion: FC<QuestionProps> = ({
  answer,
  correct,
  inInstructor,
  question,
  exerciseId,
@@ -28,7 +29,7 @@ const MultipleChoiceQuestion: FC<QuestionProps> = ({
  onChange,
}) => {
  const details = question.details as MultipleChoiceQuestionDetailsType
  const { correctArray, labels } = details
  const { correctArray: correctIndexes, labels, exactMatch } = details

  return (
    <>
@@ -39,15 +40,22 @@ const MultipleChoiceQuestion: FC<QuestionProps> = ({
          inInstructor={inInstructor}
          renderedContent={question.content.rendered}
        />
        <div className={wrapper}>
        <div
          className={cx(
            {
              [highlightAnswer(correct)]: type === 'reviewing',
            },
            wrapper
          )}
        >
          {labels.map((label, index) => (
            <Checkbox
              className={cx({
                [highlightCorrect(
                  !!correctArray.find(
                    value => value === labels.indexOf(label) + 1
                  )
                )]: type === 'reviewing' && inInstructor,
                [correctOption]:
                  type === 'reviewing' &&
                  correctIndexes.some(
                    index => labels.indexOf(label) + 1 === index
                  ),
              })}
              checked={!!answer.find(val => val === (index + 1).toString())}
              key={label}
@@ -60,6 +68,21 @@ const MultipleChoiceQuestion: FC<QuestionProps> = ({
            </Checkbox>
          ))}
        </div>
        {type === 'reviewing' && (
          <p
            className={cx(
              css`
                padding-top: 0.6rem;
                margin: 0;
              `,
              Classes.TEXT_MUTED
            )}
          >
            {exactMatch
              ? 'All correct and no incorrect options must be selected'
              : 'At least one correct and no incorrect options must be selected'}
          </p>
        )}
      </Label>
    </>
  )
+12 −7
Original line number Diff line number Diff line
import { Radio, RadioGroup } from '@blueprintjs/core'
import { cx } from '@emotion/css'
import type { RadioQuestionDetails } from '@inject/graphql'
import { breakWord } from '@inject/shared'
import { type FC } from 'react'
import { RenderedContent } from '../RenderedContent'
import { highlightCorrect } from './classes'
import { correctOption, highlightAnswer } from './classes'
import type { QuestionProps } from './types'

const RadioButtonQuestion: FC<QuestionProps> = ({
  type,
  question,
  answer,
  correct,
  disabled,
  onChange,
  inInstructor,
  exerciseId,
}) => {
  const details = question.details as RadioQuestionDetails
  const { labels, correct } = details
  const { correct: correctIndex, labels } = details

  return (
    <RadioGroup
@@ -33,15 +35,18 @@ const RadioButtonQuestion: FC<QuestionProps> = ({
      onChange={
        onChange ? event => onChange(event.currentTarget.value) : () => {}
      }
      className={cx({
        [highlightAnswer(correct)]: type === 'reviewing',
      })}
    >
      {labels.map((label, index) => (
        <Radio
          disabled={disabled || type === 'reviewing'}
          className={
            type === 'reviewing'
              ? highlightCorrect(labels.indexOf(label) + 1 === correct)
              : undefined
          }
          className={cx({
            [correctOption]:
              type === 'reviewing' &&
              labels.indexOf(label) + 1 === correctIndex,
          })}
          key={label}
          value={(index + 1).toString()}
          label={label}
+32 −3
Original line number Diff line number Diff line
import { Colors } from '@blueprintjs/colors'
import { css } from '@emotion/css'
import type { QuestionnaireAnswer } from '@inject/graphql'

export const questionNumber = css`
  text-align: right;
`
export const highlightCorrect = (correct: boolean) => css`

export const correctOption = css`
  padding-bottom: 0.2rem;
  border-bottom: 0.2rem solid ${correct ? Colors.GREEN5 : 'transparent'};
  border-bottom: 0.2rem solid ${Colors.GREEN5};
`

export const highlightAnswer = (
  correct: QuestionnaireAnswer['correct'] | undefined
) => {
  if (!correct) {
    return css``
  }

  const color = (() => {
    switch (correct) {
      case 'CORRECT':
        return Colors.GREEN5
      case 'INCORRECT':
        return Colors.RED5
      case 'PARTIALLY_CORRECT':
        return Colors.ORANGE5
      case 'UNKNOWN':
        return 'transparent'
    }
  })()

  return css`
    border-bottom: 0.2rem solid ${color};
    margin-bottom: 0.2rem;
  `
}

export const contentWrapper = css`
  display: flex;
  column-gap: 0.3rem;
`

export const lengthDisplay = css`
  margin: 0;
  position: absolute;
  z-index: 10;
  right: 0.3rem;
  bottom: 0.1rem;
  color: white;
`
Loading