Skip to content

Commit

Permalink
feat: implement creation and editing of new selection question type (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
sjschlapbach authored Jan 7, 2025
1 parent b7dd5b3 commit d11d883
Show file tree
Hide file tree
Showing 33 changed files with 1,401 additions and 122 deletions.
21 changes: 3 additions & 18 deletions apps/frontend-manage/src/components/questions/Question.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,4 @@
import {
faComment as faCommentRegular,
faRectangleList as faListRegular,
faCircleQuestion as faQuestionRegular,
} from '@fortawesome/free-regular-svg-icons'
import { IconDefinition, faArchive } from '@fortawesome/free-solid-svg-icons'
import { faArchive } from '@fortawesome/free-solid-svg-icons'
import { Button, Checkbox, H2, H3, Modal } from '@uzh-bf/design-system'
import { Badge } from '@uzh-bf/design-system/dist/future'
import React, { useState } from 'react'
Expand Down Expand Up @@ -38,16 +33,6 @@ const StatusColors: Record<ElementStatus, string> = {
[ElementStatus.Ready]: 'bg-green-400',
}

const ElementIcons: Record<ElementType, IconDefinition> = {
FLASHCARD: faListRegular,
CONTENT: faCommentRegular,
SC: faQuestionRegular,
MC: faQuestionRegular,
KPRIM: faQuestionRegular,
FREE_TEXT: faQuestionRegular,
NUMERICAL: faQuestionRegular,
}

export interface QuestionDragDropTypes {
id: number
type: ElementType
Expand Down Expand Up @@ -207,7 +192,7 @@ function Question({
<ElementEditModal
handleSetIsOpen={setIsModificationModalOpen}
isOpen={isModificationModalOpen}
questionId={id}
elementId={id}
mode={ElementEditMode.EDIT}
/>
)}
Expand All @@ -227,7 +212,7 @@ function Question({
<ElementEditModal
handleSetIsOpen={setIsDuplicationModalOpen}
isOpen={isDuplicationModalOpen}
questionId={id}
elementId={id}
mode={ElementEditMode.DUPLICATE}
/>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,18 +49,18 @@ function QuestionList({
<div className="bg-uzh-blue-400 space-y-1 md:space-y-2">
{questions.map((question) => (
<Question
key={`question-list-element-${question.id}`}
checked={!!selectedQuestions[question.id]}
id={question.id}
isArchived={question.isArchived ?? false}
key={question.id}
tags={question.tags || []}
handleTagClick={handleTagClick}
title={question.name}
status={question.status}
type={question.type}
content={question.content}
hasAnswerFeedbacks={
'options' in question
'options' in question && 'hasAnswerFeedbacks' in question.options
? (question.options.hasAnswerFeedbacks ?? false)
: true
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
ManipulateFlashcardElementDocument,
ManipulateFreeTextQuestionDocument,
ManipulateNumericalQuestionDocument,
ManipulateSelectionQuestionDocument,
UpdateElementInstancesDocument,
} from '@klicker-uzh/graphql/dist/ops'
import { Button, Modal } from '@uzh-bf/design-system'
Expand All @@ -30,6 +31,7 @@ import {
prepareFlashcardArgs,
prepareFreeTextArgs,
prepareNumericalArgs,
prepareSelectionArgs,
} from './helpers'
import AnswerFeedbackSetting from './options/AnswerFeedbackSetting'
import ChoicesOptions from './options/ChoicesOptions'
Expand All @@ -38,6 +40,7 @@ import FreeTextOptions from './options/FreeTextOptions'
import NumericalOptions from './options/NumericalOptions'
import OptionsLabel from './options/OptionsLabel'
import SampleSolutionSetting from './options/SampleSolutionSetting'
import SelectionOptions from './options/SelectionOptions'
import useElementFormInitialValues from './useElementFormInitialValues'
import useValidationSchema from './useValidationSchema'

Expand All @@ -50,17 +53,16 @@ export enum ElementEditMode {
interface ElementEditModalProps {
isOpen: boolean
handleSetIsOpen: (open: boolean) => void
questionId?: number
elementId?: number
mode: ElementEditMode
}

function ElementEditModal({
isOpen,
handleSetIsOpen,
questionId,
elementId,
mode,
}: ElementEditModalProps): React.ReactElement {
// TODO: styling of tooltips - some are too wide
const t = useTranslations()
const questionManipulationSchema = useValidationSchema()

Expand All @@ -72,8 +74,8 @@ function ElementEditModal({
const { loading: loadingQuestion, data: dataQuestion } = useQuery(
GetSingleQuestionDocument,
{
variables: { id: questionId! },
skip: typeof questionId === 'undefined',
variables: { id: elementId! },
skip: typeof elementId === 'undefined',
}
)

Expand All @@ -92,6 +94,9 @@ function ElementEditModal({
const [manipulateFreeTextQuestion] = useMutation(
ManipulateFreeTextQuestionDocument
)
const [manipulateSelectionQuestion] = useMutation(
ManipulateSelectionQuestionDocument
)
const [updateElementInstances] = useMutation(UpdateElementInstancesDocument)

const initialValues = useElementFormInitialValues({
Expand All @@ -116,7 +121,7 @@ function ElementEditModal({
switch (values.type) {
case ElementType.Content: {
const args = prepareContentArgs({
questionId,
elementId,
isDuplication,
values,
})
Expand All @@ -136,7 +141,7 @@ function ElementEditModal({

case ElementType.Flashcard: {
const args = prepareFlashcardArgs({
questionId,
elementId,
isDuplication,
values,
})
Expand All @@ -158,7 +163,7 @@ function ElementEditModal({
case ElementType.Mc:
case ElementType.Kprim: {
const args = prepareChoicesArgs({
questionId,
elementId,
isDuplication,
values,
})
Expand All @@ -177,7 +182,7 @@ function ElementEditModal({
}
case ElementType.Numerical: {
const args = prepareNumericalArgs({
questionId,
elementId,
isDuplication,
values,
})
Expand All @@ -196,7 +201,7 @@ function ElementEditModal({
}
case ElementType.FreeText: {
const args = prepareFreeTextArgs({
questionId,
elementId,
isDuplication,
values,
})
Expand All @@ -213,15 +218,34 @@ function ElementEditModal({
if (data?.__typename !== 'FreeTextElement' || !data.id) return
break
}
case ElementType.Selection: {
const args = prepareSelectionArgs({
elementId,
isDuplication,
values,
})

const result = await manipulateSelectionQuestion({
variables: args,
refetchQueries: [
{ query: GetUserQuestionsDocument },
{ query: GetUserTagsDocument },
],
})

const data = result.data?.manipulateSelectionQuestion
if (data?.__typename !== 'SelectionElement' || !data.id) return
break
}

default:
break
}

if (mode === ElementEditMode.EDIT && updateInstances) {
if (questionId !== null && typeof questionId !== 'undefined') {
if (elementId !== null && typeof elementId !== 'undefined') {
await updateElementInstances({
variables: { elementId: questionId },
variables: { elementId: elementId },
})
}
}
Expand Down Expand Up @@ -318,6 +342,10 @@ function ElementEditModal({
{values.type === ElementType.FreeText && (
<FreeTextOptions values={values} />
)}

{values.type === ElementType.Selection && (
<SelectionOptions values={values} />
)}
</Form>

{Object.keys(errors).length !== 0 && (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,28 @@ function ElementFormErrors({
'manage.questionForms.possibleSolutions'
)}: ${errors.options.solutions}`}</li>
)}

{/* error messages specific to NR questions */}
{'options' in errors &&
errors.options &&
'answerCollection' in errors.options &&
errors.options.answerCollection && (
<li>{`${t('manage.questionForms.answerCollection')}: ${errors.options.answerCollection}`}</li>
)}

{'options' in errors &&
errors.options &&
'numberOfInputs' in errors.options &&
errors.options.numberOfInputs && (
<li>{`${t('manage.questionForms.numberOfInputs')}: ${errors.options.numberOfInputs}`}</li>
)}

{'options' in errors &&
errors.options &&
'correctAnswers' in errors.options &&
errors.options.correctAnswers && (
<li>{`${t('manage.questionForms.correctAnswerOptions')}: ${errors.options.correctAnswers}`}</li>
)}
</ul>
</UserNotification>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,21 @@ import {
ElementFormTypesFlashcard,
ElementFormTypesFreeText,
ElementFormTypesNumerical,
ElementFormTypesSelection,
} from './types'

interface PrepareContentArgsProps {
questionId?: number
elementId?: number
isDuplication: boolean
values: ElementFormTypesContent
}
export function prepareContentArgs({
questionId,
elementId,
isDuplication,
values,
}: PrepareContentArgsProps) {
return {
id: isDuplication ? undefined : questionId,
id: isDuplication ? undefined : elementId,
name: values.name,
status: values.status,
content: values.content,
Expand All @@ -27,17 +28,17 @@ export function prepareContentArgs({
}

interface PrepareFlashcardArgsProps {
questionId?: number
elementId?: number
isDuplication: boolean
values: ElementFormTypesFlashcard
}
export function prepareFlashcardArgs({
questionId,
elementId,
isDuplication,
values,
}: PrepareFlashcardArgsProps) {
return {
id: isDuplication ? undefined : questionId,
id: isDuplication ? undefined : elementId,
name: values.name,
status: values.status,
content: values.content,
Expand All @@ -48,17 +49,17 @@ export function prepareFlashcardArgs({
}

interface PrepareChoicesArgsProps {
questionId?: number
elementId?: number
isDuplication: boolean
values: ElementFormTypesChoices
}
export function prepareChoicesArgs({
questionId,
elementId,
isDuplication,
values,
}: PrepareChoicesArgsProps) {
return {
id: isDuplication ? undefined : questionId,
id: isDuplication ? undefined : elementId,
name: values.name,
type: values.type,
status: values.status,
Expand Down Expand Up @@ -87,17 +88,17 @@ export function prepareChoicesArgs({
}

interface PrepareNumericalArgsProps {
questionId?: number
elementId?: number
isDuplication: boolean
values: ElementFormTypesNumerical
}
export function prepareNumericalArgs({
questionId,
elementId,
isDuplication,
values,
}: PrepareNumericalArgsProps) {
return {
id: isDuplication ? undefined : questionId,
id: isDuplication ? undefined : elementId,
name: values.name,
status: values.status,
content: values.content,
Expand Down Expand Up @@ -155,17 +156,17 @@ export function prepareNumericalArgs({
}

interface PrepareFreeTextArgsProps {
questionId?: number
elementId?: number
isDuplication: boolean
values: ElementFormTypesFreeText
}
export function prepareFreeTextArgs({
questionId,
elementId,
isDuplication,
values,
}: PrepareFreeTextArgsProps) {
return {
id: isDuplication ? undefined : questionId,
id: isDuplication ? undefined : elementId,
name: values.name,
status: values.status,
content: values.content,
Expand All @@ -191,3 +192,34 @@ export function prepareFreeTextArgs({
tags: values.tags,
}
}

interface PrepareSelectionArgsProps {
elementId?: number
isDuplication: boolean
values: ElementFormTypesSelection
}
export function prepareSelectionArgs({
elementId,
isDuplication,
values,
}: PrepareSelectionArgsProps) {
return {
id: isDuplication ? undefined : elementId,
name: values.name,
status: values.status,
content: values.content,
explanation:
!values.explanation?.match(/^(<br>(\n)*)$/g) && values.explanation !== ''
? values.explanation
: null,
pointsMultiplier: parseInt(values.pointsMultiplier),

options: {
hasSampleSolution: values.options.hasSampleSolution,
answerCollection: parseInt(values.options.answerCollection),
numberOfInputs: parseInt(values.options.numberOfInputs),
correctAnswers: values.options.correctAnswers,
},
tags: values.tags,
}
}
Loading

0 comments on commit d11d883

Please sign in to comment.