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
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import { css } from '@emotion/react';
import { __ } from '@wordpress/i18n';
import { useEffect, useMemo } from 'react';
import { useEffect } from 'react';
import { Controller, useFieldArray, useFormContext } from 'react-hook-form';

import { useQuizModalContext } from '@CourseBuilderContexts/QuizModalContext';
import type { QuizForm } from '@CourseBuilderServices/quiz';
import FormDrawImage from '@TutorShared/components/fields/quiz/questions/FormDrawImage';
import FormSelectInput from '@TutorShared/components/fields/FormSelectInput';
import { spacing } from '@TutorShared/config/styles';
import { calculateQuizDataStatus } from '@TutorShared/utils/quiz';
import { styleUtils } from '@TutorShared/utils/style-utils';
Expand All @@ -28,15 +26,6 @@ const DrawImage = () => {
name: answersPath,
});

const thresholdOptions = useMemo(
() =>
[40, 50, 60, 70, 80, 90, 100].map((value) => ({
label: `${value}%`,
value,
})),
[],
);

// Ensure there is always a single option for this question type.
useEffect(() => {
if (!activeQuestionId) {
Expand Down Expand Up @@ -91,26 +80,16 @@ const DrawImage = () => {
questionId={activeQuestionId}
validationError={validationError}
setValidationError={setValidationError}
precisionControl={
<FormSelectInput
{...thresholdControllerProps}
label={__('Precision Level', 'tutor')}
options={thresholdOptions}
helpText={__(
'Minimum overlap score between student and instructor markings. Larger or smaller marked areas lower the score.',
'tutor',
)}
onChange={(option) => {
thresholdControllerProps.field.onChange(option.value);
if (calculateQuizDataStatus(activeQuestionDataStatus, QuizDataStatus.UPDATE)) {
form.setValue(
`questions.${activeQuestionIndex}._data_status`,
calculateQuizDataStatus(activeQuestionDataStatus, QuizDataStatus.UPDATE) as QuizDataStatus,
);
}
}}
/>
}
precisionControllerProps={thresholdControllerProps}
precisionTextDomain={'tutor'}
onPrecisionChange={() => {
if (calculateQuizDataStatus(activeQuestionDataStatus, QuizDataStatus.UPDATE)) {
form.setValue(
`questions.${activeQuestionIndex}._data_status`,
calculateQuizDataStatus(activeQuestionDataStatus, QuizDataStatus.UPDATE) as QuizDataStatus,
);
}
}}
/>
)}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,14 +100,20 @@ const ContentListTable = () => {
const columns: Column<ContentBankContent>[] = [
{
Header: totalItems ? (
<Checkbox
onChange={toggleSelection}
checked={getContentsQuery.isLoading || getContentsQuery.isRefetching ? false : handleAllIsChecked()}
label={__('Title', 'tutor')}
labelCss={styles.tableTitle}
isIndeterminate={fetchedContents.length > 0 && !handleAllIsChecked() && selectedContents.length > 0}
aria-label={__('Select all contents', 'tutor')}
/>
<div
onClick={(event) => event.stopPropagation()}
onMouseDown={(event) => event.stopPropagation()}
css={styles.headerCheckboxWrapper}
>
<Checkbox
onChange={toggleSelection}
checked={getContentsQuery.isLoading || getContentsQuery.isRefetching ? false : handleAllIsChecked()}
label={__('Title', 'tutor')}
labelCss={styles.tableTitle}
isIndeterminate={fetchedContents.length > 0 && !handleAllIsChecked() && selectedContents.length > 0}
aria-label={__('Select all contents', 'tutor')}
/>
</div>
) : (
__('# Title', 'tutor')
),
Expand Down Expand Up @@ -316,6 +322,10 @@ const styles = {
${typography.small('medium')};
color: ${colorTokens.text.hints};
`,
headerCheckboxWrapper: css`
display: flex;
align-items: center;
`,
checkboxLabel: css`
${typography.caption('medium')};
color: ${colorTokens.text.primary};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { __, _n, sprintf } from '@wordpress/i18n';
import { useMemo, useState } from 'react';
import { useFormContext } from 'react-hook-form';

import { tutorConfig } from '@TutorShared/config/config';

import { type ContentSelectionForm } from '@CourseBuilderComponents/modals/ContentBankContentSelectModal';
import Checkbox from '@TutorShared/atoms/CheckBox';
import { LoadingSection } from '@TutorShared/atoms/LoadingSpinner';
Expand All @@ -23,6 +25,98 @@ import SearchField from './SearchField';

type SortDirection = 'asc' | 'desc';

type QuestionTypeOption = {
label: string;
value: QuizQuestionType;
icon: IconCollection;
isPro: boolean;
};

const ALL_QUESTION_TYPE_OPTIONS: QuestionTypeOption[] = [
{
label: __('True/False', 'tutor'),
value: 'true_false',
icon: 'quizTrueFalse',
isPro: false,
},
{
label: __('Multiple Choice', 'tutor'),
value: 'multiple_choice',
icon: 'quizMultiChoice',
isPro: false,
},
{
label: __('Open Ended/Essay', 'tutor'),
value: 'open_ended',
icon: 'quizEssay',
isPro: false,
},
{
label: __('Fill in the Blanks', 'tutor'),
value: 'fill_in_the_blank',
icon: 'quizFillInTheBlanks',
isPro: false,
},
{
label: __('Short Answer', 'tutor'),
value: 'short_answer',
icon: 'quizShortAnswer',
isPro: true,
},
{
label: __('Matching', 'tutor'),
value: 'matching',
icon: 'quizImageMatching',
isPro: true,
},
{
label: __('Image Answering', 'tutor'),
value: 'image_answering',
icon: 'quizImageAnswer',
isPro: true,
},
{
label: __('Ordering', 'tutor'),
value: 'ordering',
icon: 'quizOrdering',
isPro: true,
},
{
label: __('Mark in the Image', 'tutor'),
value: 'draw_image',
icon: 'quizMarkInTheImage',
isPro: true,
},
{
label: __('Range', 'tutor'),
value: 'scale',
icon: 'quizRange',
isPro: true,
},
{
label: __('Pin', 'tutor'),
value: 'pin_image',
icon: 'quizPin',
isPro: true,
},
{
label: __('Graph', 'tutor'),
value: 'coordinates',
icon: 'quizGraph',
isPro: true,
},
];

const questionTypeOptions = tutorConfig.is_legacy_learning_mode
? ALL_QUESTION_TYPE_OPTIONS.filter(
(option) =>
option.value !== 'draw_image' &&
option.value !== 'pin_image' &&
option.value !== 'scale' &&
option.value !== 'coordinates',
)
: ALL_QUESTION_TYPE_OPTIONS;

const QuestionListTable = () => {
const { pageInfo, onPageChange, itemsPerPage, onFilterItems } = usePaginatedTable();
const form = useFormContext<ContentSelectionForm>();
Expand Down Expand Up @@ -82,73 +176,23 @@ const QuestionListTable = () => {
});
};

const questionTypeOptions: {
label: string;
value: QuizQuestionType;
icon: IconCollection;
isPro: boolean;
}[] = [
{
label: __('True/False', 'tutor'),
value: 'true_false',
icon: 'quizTrueFalse',
isPro: false,
},
{
label: __('Multiple Choice', 'tutor'),
value: 'multiple_choice',
icon: 'quizMultiChoice',
isPro: false,
},
{
label: __('Open Ended/Essay', 'tutor'),
value: 'open_ended',
icon: 'quizEssay',
isPro: false,
},
{
label: __('Fill in the Blanks', 'tutor'),
value: 'fill_in_the_blank',
icon: 'quizFillInTheBlanks',
isPro: false,
},
{
label: __('Short Answer', 'tutor'),
value: 'short_answer',
icon: 'quizShortAnswer',
isPro: true,
},
{
label: __('Matching', 'tutor'),
value: 'matching',
icon: 'quizImageMatching',
isPro: true,
},
{
label: __('Image Answering', 'tutor'),
value: 'image_answering',
icon: 'quizImageAnswer',
isPro: true,
},
{
label: __('Ordering', 'tutor'),
value: 'ordering',
icon: 'quizOrdering',
isPro: true,
},
];

const columns: Column<ContentBankContent>[] = [
{
Header: totalItems ? (
<Checkbox
onChange={handleToggleSelection}
checked={getContentsQuery.isLoading || getContentsQuery.isRefetching ? false : handleAllIsChecked()}
label={__('Title', 'tutor')}
labelCss={styles.tableTitle}
isIndeterminate={fetchedContents.length > 0 && !handleAllIsChecked() && selectedContents.length > 0}
aria-label={__('Select all questions', 'tutor')}
/>
<div
onClick={(event) => event.stopPropagation()}
onMouseDown={(event) => event.stopPropagation()}
css={styles.headerCheckboxWrapper}
>
<Checkbox
onChange={handleToggleSelection}
checked={getContentsQuery.isLoading || getContentsQuery.isRefetching ? false : handleAllIsChecked()}
label={__('Title', 'tutor')}
labelCss={styles.tableTitle}
isIndeterminate={fetchedContents.length > 0 && !handleAllIsChecked() && selectedContents.length > 0}
aria-label={__('Select all questions', 'tutor')}
/>
</div>
) : (
__('# Title', 'tutor')
),
Expand Down Expand Up @@ -341,6 +385,10 @@ const styles = {
${typography.small('regular')};
color: ${colorTokens.text.hints};
`,
headerCheckboxWrapper: css`
display: flex;
align-items: center;
`,
checkboxLabel: css`
${styleUtils.display.flex()};
align-items: center;
Expand Down
34 changes: 1 addition & 33 deletions assets/src/js/v3/entries/course-builder/services/quiz.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { Addons } from '@TutorShared/config/constants';
import { wpAjaxInstance } from '@TutorShared/utils/api';
import endpoints from '@TutorShared/utils/endpoints';
import type { ErrorResponse } from '@TutorShared/utils/form';
import { convertedQuestion } from '@TutorShared/utils/quiz';
import { convertedQuestion, normalizeSingleMaskQuestionAnswers } from '@TutorShared/utils/quiz';
import {
isDefined,
QuizDataStatus,
Expand Down Expand Up @@ -373,38 +373,6 @@ export const convertQuizFormDataToPayload = (
};
};

const normalizeSingleMaskQuestionAnswers = (questions: QuizQuestion[], deletedAnswerIds: ID[] = []) => {
const deletedAnswerIdsSet = new Set(deletedAnswerIds);

const normalizedQuestions = questions.map((question) => {
if (question.question_type !== 'draw_image' && question.question_type !== 'pin_image') {
return question;
}

const answers = Array.isArray(question.question_answers) ? question.question_answers : [];
if (answers.length <= 1) {
return question;
}

const [keptAnswer, ...extraAnswers] = answers;
extraAnswers.forEach((answer) => {
if (answer._data_status !== QuizDataStatus.NEW && answer.answer_id) {
deletedAnswerIdsSet.add(answer.answer_id);
}
});

return {
...question,
question_answers: keptAnswer ? [keptAnswer] : [],
};
});

return {
normalizedQuestions,
deletedAnswerIds: Array.from(deletedAnswerIdsSet),
};
};

export const convertQuizQuestionFormDataToPayloadForUpdate = (data: QuizQuestion): QuizUpdateQuestionPayload => {
return {
question_id: data.question_id,
Expand Down
Loading
Loading