@@ -8,6 +8,9 @@ import { IoTextOutline } from 'react-icons/io5';
|
|||||||
import { Switch } from '@headlessui/react';
|
import { Switch } from '@headlessui/react';
|
||||||
import useExamEditorStore from '@/stores/examEditor';
|
import useExamEditorStore from '@/stores/examEditor';
|
||||||
import { Module } from '@/interfaces';
|
import { Module } from '@/interfaces';
|
||||||
|
import { capitalize } from 'lodash';
|
||||||
|
import Select from '@/components/Low/Select';
|
||||||
|
import { Difficulty } from '@/interfaces/exam';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
module: Module;
|
module: Module;
|
||||||
@@ -36,6 +39,18 @@ const ExerciseWizard: React.FC<Props> = ({
|
|||||||
onDiscard,
|
onDiscard,
|
||||||
}) => {
|
}) => {
|
||||||
const [configurations, setConfigurations] = useState<ExerciseConfig[]>([]);
|
const [configurations, setConfigurations] = useState<ExerciseConfig[]>([]);
|
||||||
|
const { currentModule } = useExamEditorStore();
|
||||||
|
const { difficulty } = useExamEditorStore(state => state.modules[currentModule]);
|
||||||
|
|
||||||
|
const randomDiff = difficulty.length === 1
|
||||||
|
? capitalize(difficulty[0])
|
||||||
|
: difficulty.length == 0 ?
|
||||||
|
"Random" :
|
||||||
|
`Selected (${difficulty.sort().map(dif => capitalize(dif)).join(", ")})` as Difficulty;
|
||||||
|
|
||||||
|
const DIFFICULTIES = difficulty.length === 1
|
||||||
|
? ["A1", "A2", "B1", "B2", "C1", "C2", "Random"]
|
||||||
|
: ["A1", "A2", "B1", "B2", "C1", "C2", randomDiff, "Random"];
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const initialConfigs = selectedExercises.map(exerciseType => {
|
const initialConfigs = selectedExercises.map(exerciseType => {
|
||||||
@@ -164,7 +179,7 @@ const ExerciseWizard: React.FC<Props> = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const inputValue = Number(config.params[param.param || '1'].toString());
|
const inputValue = Number(config.params[param.param || '1'].toString()) || config.params[param.param!];
|
||||||
const isParagraphMatch = config.type.split("?name=")[1] === "paragraphMatch";
|
const isParagraphMatch = config.type.split("?name=")[1] === "paragraphMatch";
|
||||||
const maxParagraphs = isParagraphMatch ? extraArgs!.text.split("\n\n").length : 50;
|
const maxParagraphs = isParagraphMatch ? extraArgs!.text.split("\n\n").length : 50;
|
||||||
|
|
||||||
@@ -183,9 +198,23 @@ const ExerciseWizard: React.FC<Props> = ({
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
{param.param === "difficulty" ?
|
||||||
|
<Select
|
||||||
|
options={DIFFICULTIES.map((x) => ({ value: x, label: x }))}
|
||||||
|
onChange={(value) => {
|
||||||
|
handleParameterChange(
|
||||||
|
exerciseIndex,
|
||||||
|
param.param || '',
|
||||||
|
value?.value || ''
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
value={{ value: config.params[param.param] !== "" ? config.params[param.param] as string : randomDiff , label: config.params[param.param] !== "" ? config.params[param.param] as string : randomDiff }}
|
||||||
|
flat
|
||||||
|
/>
|
||||||
|
:
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
value={inputValue}
|
value={inputValue as number}
|
||||||
onChange={(e) => handleParameterChange(
|
onChange={(e) => handleParameterChange(
|
||||||
exerciseIndex,
|
exerciseIndex,
|
||||||
param.param || '',
|
param.param || '',
|
||||||
@@ -195,6 +224,8 @@ const ExerciseWizard: React.FC<Props> = ({
|
|||||||
min={1}
|
min={1}
|
||||||
max={maxParagraphs}
|
max={maxParagraphs}
|
||||||
/>
|
/>
|
||||||
|
}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ import {
|
|||||||
FaQuestionCircle,
|
FaQuestionCircle,
|
||||||
} from 'react-icons/fa';
|
} from 'react-icons/fa';
|
||||||
import { ExerciseGen } from './generatedExercises';
|
import { ExerciseGen } from './generatedExercises';
|
||||||
import { MdRadioButtonChecked } from 'react-icons/md';
|
|
||||||
import { BsListCheck } from 'react-icons/bs';
|
import { BsListCheck } from 'react-icons/bs';
|
||||||
|
|
||||||
const quantity = (quantity: number, tooltip?: string) => {
|
const quantity = (quantity: number, tooltip?: string) => {
|
||||||
@@ -32,6 +31,14 @@ const quantity = (quantity: number, tooltip?: string) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const difficulty = () => {
|
||||||
|
return {
|
||||||
|
param: "difficulty",
|
||||||
|
label: "Difficulty",
|
||||||
|
tooltip: "Exercise difficulty",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const generate = () => {
|
const generate = () => {
|
||||||
return {
|
return {
|
||||||
param: "generate",
|
param: "generate",
|
||||||
@@ -52,6 +59,7 @@ const reading = (passage: number) => {
|
|||||||
value: "multipleChoice"
|
value: "multipleChoice"
|
||||||
},
|
},
|
||||||
quantity(5, "Quantity of Multiple Choice Questions"),
|
quantity(5, "Quantity of Multiple Choice Questions"),
|
||||||
|
difficulty(),
|
||||||
generate()
|
generate()
|
||||||
],
|
],
|
||||||
module: "reading"
|
module: "reading"
|
||||||
@@ -73,6 +81,7 @@ const reading = (passage: number) => {
|
|||||||
value: 1
|
value: 1
|
||||||
},
|
},
|
||||||
quantity(4, "Quantity of Blanks"),
|
quantity(4, "Quantity of Blanks"),
|
||||||
|
difficulty(),
|
||||||
generate()
|
generate()
|
||||||
],
|
],
|
||||||
module: "reading"
|
module: "reading"
|
||||||
@@ -94,6 +103,7 @@ const reading = (passage: number) => {
|
|||||||
value: 3
|
value: 3
|
||||||
},
|
},
|
||||||
quantity(4, "Quantity of Blanks"),
|
quantity(4, "Quantity of Blanks"),
|
||||||
|
difficulty(),
|
||||||
generate()
|
generate()
|
||||||
],
|
],
|
||||||
module: "reading"
|
module: "reading"
|
||||||
@@ -109,6 +119,7 @@ const reading = (passage: number) => {
|
|||||||
value: "trueFalse"
|
value: "trueFalse"
|
||||||
},
|
},
|
||||||
quantity(4, "Quantity of Statements"),
|
quantity(4, "Quantity of Statements"),
|
||||||
|
difficulty(),
|
||||||
generate()
|
generate()
|
||||||
],
|
],
|
||||||
module: "reading"
|
module: "reading"
|
||||||
@@ -124,6 +135,7 @@ const reading = (passage: number) => {
|
|||||||
value: "paragraphMatch"
|
value: "paragraphMatch"
|
||||||
},
|
},
|
||||||
quantity(5, "Quantity of Matches"),
|
quantity(5, "Quantity of Matches"),
|
||||||
|
difficulty(),
|
||||||
generate()
|
generate()
|
||||||
],
|
],
|
||||||
module: "reading"
|
module: "reading"
|
||||||
@@ -143,6 +155,7 @@ const reading = (passage: number) => {
|
|||||||
value: "ideaMatch"
|
value: "ideaMatch"
|
||||||
},
|
},
|
||||||
quantity(5, "Quantity of Ideas"),
|
quantity(5, "Quantity of Ideas"),
|
||||||
|
difficulty(),
|
||||||
generate()
|
generate()
|
||||||
],
|
],
|
||||||
module: "reading"
|
module: "reading"
|
||||||
@@ -165,6 +178,7 @@ const listening = (section: number) => {
|
|||||||
value: section == 3 ? "multipleChoice3Options" : "multipleChoice"
|
value: section == 3 ? "multipleChoice3Options" : "multipleChoice"
|
||||||
},
|
},
|
||||||
quantity(5, "Quantity of Multiple Choice Questions"),
|
quantity(5, "Quantity of Multiple Choice Questions"),
|
||||||
|
difficulty(),
|
||||||
generate()
|
generate()
|
||||||
],
|
],
|
||||||
module: "listening"
|
module: "listening"
|
||||||
@@ -180,6 +194,7 @@ const listening = (section: number) => {
|
|||||||
value: "writeBlanksQuestions"
|
value: "writeBlanksQuestions"
|
||||||
},
|
},
|
||||||
quantity(5, "Quantity of Blanks"),
|
quantity(5, "Quantity of Blanks"),
|
||||||
|
difficulty(),
|
||||||
generate()
|
generate()
|
||||||
],
|
],
|
||||||
module: "listening"
|
module: "listening"
|
||||||
@@ -195,6 +210,7 @@ const listening = (section: number) => {
|
|||||||
value: "trueFalse"
|
value: "trueFalse"
|
||||||
},
|
},
|
||||||
quantity(4, "Quantity of Statements"),
|
quantity(4, "Quantity of Statements"),
|
||||||
|
difficulty(),
|
||||||
generate()
|
generate()
|
||||||
],
|
],
|
||||||
module: "listening"
|
module: "listening"
|
||||||
@@ -214,6 +230,7 @@ const listening = (section: number) => {
|
|||||||
value: "writeBlanksFill"
|
value: "writeBlanksFill"
|
||||||
},
|
},
|
||||||
quantity(5, "Quantity of Blanks"),
|
quantity(5, "Quantity of Blanks"),
|
||||||
|
difficulty(),
|
||||||
generate()
|
generate()
|
||||||
],
|
],
|
||||||
module: "listening"
|
module: "listening"
|
||||||
@@ -231,6 +248,7 @@ const listening = (section: number) => {
|
|||||||
value: "writeBlanksForm"
|
value: "writeBlanksForm"
|
||||||
},
|
},
|
||||||
quantity(5, "Quantity of Blanks"),
|
quantity(5, "Quantity of Blanks"),
|
||||||
|
difficulty(),
|
||||||
generate()
|
generate()
|
||||||
],
|
],
|
||||||
module: "listening"
|
module: "listening"
|
||||||
@@ -251,6 +269,7 @@ const EXERCISES: ExerciseGen[] = [
|
|||||||
value: "multipleChoice"
|
value: "multipleChoice"
|
||||||
},
|
},
|
||||||
quantity(10, "Amount"),
|
quantity(10, "Amount"),
|
||||||
|
difficulty(),
|
||||||
generate()
|
generate()
|
||||||
],
|
],
|
||||||
module: "level"
|
module: "level"
|
||||||
@@ -265,6 +284,7 @@ const EXERCISES: ExerciseGen[] = [
|
|||||||
value: "mcBlank"
|
value: "mcBlank"
|
||||||
},
|
},
|
||||||
quantity(10, "Amount"),
|
quantity(10, "Amount"),
|
||||||
|
difficulty(),
|
||||||
generate()
|
generate()
|
||||||
],
|
],
|
||||||
module: "level"
|
module: "level"
|
||||||
@@ -279,6 +299,7 @@ const EXERCISES: ExerciseGen[] = [
|
|||||||
value: "mcUnderline"
|
value: "mcUnderline"
|
||||||
},
|
},
|
||||||
quantity(10, "Amount"),
|
quantity(10, "Amount"),
|
||||||
|
difficulty(),
|
||||||
generate()
|
generate()
|
||||||
],
|
],
|
||||||
module: "level"
|
module: "level"
|
||||||
@@ -294,6 +315,7 @@ const EXERCISES: ExerciseGen[] = [
|
|||||||
param: "text_size",
|
param: "text_size",
|
||||||
value: "250"
|
value: "250"
|
||||||
},
|
},
|
||||||
|
difficulty(),
|
||||||
generate()
|
generate()
|
||||||
],
|
],
|
||||||
module: "level"
|
module: "level"
|
||||||
@@ -313,6 +335,7 @@ const EXERCISES: ExerciseGen[] = [
|
|||||||
param: "text_size",
|
param: "text_size",
|
||||||
value: "250"
|
value: "250"
|
||||||
},
|
},
|
||||||
|
difficulty(),
|
||||||
generate()
|
generate()
|
||||||
],
|
],
|
||||||
module: "level"
|
module: "level"
|
||||||
@@ -345,6 +368,7 @@ const EXERCISES: ExerciseGen[] = [
|
|||||||
param: "text_size",
|
param: "text_size",
|
||||||
value: "700"
|
value: "700"
|
||||||
},
|
},
|
||||||
|
difficulty(),
|
||||||
generate()
|
generate()
|
||||||
],
|
],
|
||||||
module: "level"
|
module: "level"
|
||||||
@@ -360,6 +384,7 @@ const EXERCISES: ExerciseGen[] = [
|
|||||||
value: "",
|
value: "",
|
||||||
type: "text"
|
type: "text"
|
||||||
},
|
},
|
||||||
|
difficulty(),
|
||||||
generate()
|
generate()
|
||||||
],
|
],
|
||||||
module: "writing"
|
module: "writing"
|
||||||
@@ -375,6 +400,7 @@ const EXERCISES: ExerciseGen[] = [
|
|||||||
value: "",
|
value: "",
|
||||||
type: "text"
|
type: "text"
|
||||||
},
|
},
|
||||||
|
difficulty(),
|
||||||
generate()
|
generate()
|
||||||
],
|
],
|
||||||
module: "writing"
|
module: "writing"
|
||||||
@@ -384,6 +410,7 @@ const EXERCISES: ExerciseGen[] = [
|
|||||||
type: "speaking_1",
|
type: "speaking_1",
|
||||||
icon: FaComments,
|
icon: FaComments,
|
||||||
extra: [
|
extra: [
|
||||||
|
difficulty(),
|
||||||
generate(),
|
generate(),
|
||||||
{
|
{
|
||||||
label: "First Topic",
|
label: "First Topic",
|
||||||
@@ -405,6 +432,7 @@ const EXERCISES: ExerciseGen[] = [
|
|||||||
type: "speaking_2",
|
type: "speaking_2",
|
||||||
icon: FaUserFriends,
|
icon: FaUserFriends,
|
||||||
extra: [
|
extra: [
|
||||||
|
difficulty(),
|
||||||
generate(),
|
generate(),
|
||||||
{
|
{
|
||||||
label: "Topic",
|
label: "Topic",
|
||||||
@@ -420,6 +448,7 @@ const EXERCISES: ExerciseGen[] = [
|
|||||||
type: "speaking_3",
|
type: "speaking_3",
|
||||||
icon: FaHandshake,
|
icon: FaHandshake,
|
||||||
extra: [
|
extra: [
|
||||||
|
difficulty(),
|
||||||
generate(),
|
generate(),
|
||||||
{
|
{
|
||||||
label: "Topic",
|
label: "Topic",
|
||||||
|
|||||||
@@ -17,6 +17,6 @@ export interface ExerciseGen {
|
|||||||
type: string;
|
type: string;
|
||||||
icon: IconType;
|
icon: IconType;
|
||||||
sectionId?: number;
|
sectionId?: number;
|
||||||
extra?: { param?: string; value?: string | number | boolean; label?: string; tooltip?: string, type?: string}[];
|
extra?: { param: string; value?: string | number | boolean; label?: string; tooltip?: string, type?: string}[];
|
||||||
module: string
|
module: string
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,12 +13,13 @@ import { BsArrowRepeat } from "react-icons/bs";
|
|||||||
interface ExercisePickerProps {
|
interface ExercisePickerProps {
|
||||||
module: string;
|
module: string;
|
||||||
sectionId: number;
|
sectionId: number;
|
||||||
difficulty: string;
|
|
||||||
extraArgs?: Record<string, any>;
|
extraArgs?: Record<string, any>;
|
||||||
levelSectionId?: number;
|
levelSectionId?: number;
|
||||||
level?: boolean;
|
level?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const DIFFICULTIES: string[] = ["A1", "A2", "B1", "B2", "C1", "C2"];
|
||||||
|
|
||||||
const ExercisePicker: React.FC<ExercisePickerProps> = ({
|
const ExercisePicker: React.FC<ExercisePickerProps> = ({
|
||||||
module,
|
module,
|
||||||
sectionId,
|
sectionId,
|
||||||
@@ -26,7 +27,7 @@ const ExercisePicker: React.FC<ExercisePickerProps> = ({
|
|||||||
levelSectionId,
|
levelSectionId,
|
||||||
level = false
|
level = false
|
||||||
}) => {
|
}) => {
|
||||||
const { currentModule, dispatch } = useExamEditorStore();
|
const { currentModule } = useExamEditorStore();
|
||||||
const { difficulty, sections } = useExamEditorStore((store) => store.modules[level ? "level" : currentModule]);
|
const { difficulty, sections } = useExamEditorStore((store) => store.modules[level ? "level" : currentModule]);
|
||||||
const section = sections.find((s) => s.sectionId === (level ? levelSectionId : sectionId));
|
const section = sections.find((s) => s.sectionId === (level ? levelSectionId : sectionId));
|
||||||
|
|
||||||
@@ -67,6 +68,9 @@ const ExercisePicker: React.FC<ExercisePickerProps> = ({
|
|||||||
}),
|
}),
|
||||||
...(config.params.max_words !== undefined && {
|
...(config.params.max_words !== undefined && {
|
||||||
max_words: Number(config.params.max_words)
|
max_words: Number(config.params.max_words)
|
||||||
|
}),
|
||||||
|
...((DIFFICULTIES.includes(config.params.difficulty as string) || config.params.difficulty === "Random") && {
|
||||||
|
difficulty: config.params.difficulty
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@@ -100,8 +104,8 @@ const ExercisePicker: React.FC<ExercisePickerProps> = ({
|
|||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: {
|
body: {
|
||||||
...context,
|
...context,
|
||||||
exercises: exercises,
|
exercises,
|
||||||
difficulty: difficulty
|
difficulty
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
(data: any) => [{
|
(data: any) => [{
|
||||||
@@ -112,7 +116,11 @@ const ExercisePicker: React.FC<ExercisePickerProps> = ({
|
|||||||
);
|
);
|
||||||
} else if (module === "writing") {
|
} else if (module === "writing") {
|
||||||
configurations.forEach((config) => {
|
configurations.forEach((config) => {
|
||||||
let queryParams = config.params.topic !== '' ? { topic: config.params.topic as string } : undefined;
|
let queryParams = {
|
||||||
|
difficulty: config.params.difficulty ? config.params.difficulty as string: difficulty,
|
||||||
|
...(config.params.topic !== '' && { topic: config.params.topic as string })
|
||||||
|
};
|
||||||
|
|
||||||
generate(
|
generate(
|
||||||
config.type === 'writing_letter' ? 1 : 2,
|
config.type === 'writing_letter' ? 1 : 2,
|
||||||
"writing",
|
"writing",
|
||||||
@@ -122,7 +130,8 @@ const ExercisePicker: React.FC<ExercisePickerProps> = ({
|
|||||||
queryParams
|
queryParams
|
||||||
},
|
},
|
||||||
(data: any) => [{
|
(data: any) => [{
|
||||||
prompt: data.question
|
prompt: data.question,
|
||||||
|
difficulty: data.difficulty
|
||||||
}],
|
}],
|
||||||
levelSectionId,
|
levelSectionId,
|
||||||
level
|
level
|
||||||
@@ -135,6 +144,7 @@ const ExercisePicker: React.FC<ExercisePickerProps> = ({
|
|||||||
topic: config.params.topic as string,
|
topic: config.params.topic as string,
|
||||||
first_topic: config.params.first_topic as string,
|
first_topic: config.params.first_topic as string,
|
||||||
second_topic: config.params.second_topic as string,
|
second_topic: config.params.second_topic as string,
|
||||||
|
difficulty: config.params.difficulty ? config.params.difficulty as string: difficulty,
|
||||||
}).filter(([_, value]) => value && value !== '')
|
}).filter(([_, value]) => value && value !== '')
|
||||||
);
|
);
|
||||||
let query = Object.keys(queryParams).length === 0 ? undefined : queryParams;
|
let query = Object.keys(queryParams).length === 0 ? undefined : queryParams;
|
||||||
@@ -152,19 +162,22 @@ const ExercisePicker: React.FC<ExercisePickerProps> = ({
|
|||||||
return [{
|
return [{
|
||||||
prompts: data.questions,
|
prompts: data.questions,
|
||||||
first_topic: data.first_topic,
|
first_topic: data.first_topic,
|
||||||
second_topic: data.second_topic
|
second_topic: data.second_topic,
|
||||||
|
difficulty: data.difficulty
|
||||||
}];
|
}];
|
||||||
case 2:
|
case 2:
|
||||||
return [{
|
return [{
|
||||||
topic: data.topic,
|
topic: data.topic,
|
||||||
question: data.question,
|
question: data.question,
|
||||||
prompts: data.prompts,
|
prompts: data.prompts,
|
||||||
suffix: data.suffix
|
suffix: data.suffix,
|
||||||
|
difficulty: data.difficulty
|
||||||
}];
|
}];
|
||||||
case 3:
|
case 3:
|
||||||
return [{
|
return [{
|
||||||
topic: data.topic,
|
topic: data.topic,
|
||||||
questions: data.questions
|
questions: data.questions,
|
||||||
|
difficulty: data.difficulty
|
||||||
}];
|
}];
|
||||||
default:
|
default:
|
||||||
return [data];
|
return [data];
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { FillBlanksExercise, ReadingPart } from "@/interfaces/exam";
|
import { Difficulty, FillBlanksExercise, ReadingPart } from "@/interfaces/exam";
|
||||||
import { useEffect, useReducer, useState } from "react";
|
import { useCallback, useEffect, useReducer, useState } from "react";
|
||||||
import BlanksEditor from "..";
|
import BlanksEditor from "..";
|
||||||
import { Card, CardContent } from "@/components/ui/card";
|
import { Card, CardContent } from "@/components/ui/card";
|
||||||
import { MdEdit, MdEditOff } from "react-icons/md";
|
import { MdEdit, MdEditOff } from "react-icons/md";
|
||||||
@@ -21,6 +21,7 @@ interface Word {
|
|||||||
|
|
||||||
const FillBlanksLetters: React.FC<{ exercise: FillBlanksExercise; sectionId: number }> = ({ exercise, sectionId }) => {
|
const FillBlanksLetters: React.FC<{ exercise: FillBlanksExercise; sectionId: number }> = ({ exercise, sectionId }) => {
|
||||||
const { currentModule, dispatch } = useExamEditorStore();
|
const { currentModule, dispatch } = useExamEditorStore();
|
||||||
|
const difficulty = useExamEditorStore((state) => state.modules[currentModule].difficulty);
|
||||||
const { state } = useExamEditorStore(
|
const { state } = useExamEditorStore(
|
||||||
(state) => state.modules[currentModule].sections.find((section) => section.sectionId === sectionId)!
|
(state) => state.modules[currentModule].sections.find((section) => section.sectionId === sectionId)!
|
||||||
);
|
);
|
||||||
@@ -249,12 +250,24 @@ const FillBlanksLetters: React.FC<{ exercise: FillBlanksExercise; sectionId: num
|
|||||||
setEditingAlert(editing, setAlerts);
|
setEditingAlert(editing, setAlerts);
|
||||||
}, [editing])
|
}, [editing])
|
||||||
|
|
||||||
|
const saveDifficulty = useCallback((diff: Difficulty) => {
|
||||||
|
if (!difficulty.includes(diff)) {
|
||||||
|
dispatch({ type: 'UPDATE_MODULE', payload: { updates: { difficulty: [...difficulty, diff]} } });
|
||||||
|
}
|
||||||
|
const updatedExercise = { ...exercise, difficulty: diff };
|
||||||
|
const newState = { ...section };
|
||||||
|
newState.exercises = newState.exercises.map((ex) => ex.id === exercise.id ? updatedExercise : ex );
|
||||||
|
dispatch({ type: 'UPDATE_SECTION_STATE', payload: { sectionId, update: newState, module: currentModule } });
|
||||||
|
}, [currentModule, difficulty, dispatch, exercise, section, sectionId]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<BlanksEditor
|
<BlanksEditor
|
||||||
alerts={alerts}
|
alerts={alerts}
|
||||||
editing={editing}
|
editing={editing}
|
||||||
state={blanksState}
|
state={blanksState}
|
||||||
|
difficulty={exercise.difficulty}
|
||||||
|
saveDifficulty={saveDifficulty}
|
||||||
blanksDispatcher={blanksDispatcher}
|
blanksDispatcher={blanksDispatcher}
|
||||||
description="Place blanks and assign words from the word bank"
|
description="Place blanks and assign words from the word bank"
|
||||||
initialText={local.text}
|
initialText={local.text}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { FillBlanksExercise, FillBlanksMCOption, ReadingPart } from "@/interfaces/exam";
|
import { Difficulty, FillBlanksExercise, FillBlanksMCOption, ReadingPart } from "@/interfaces/exam";
|
||||||
import { useEffect, useReducer, useState } from "react";
|
import { useCallback, useEffect, useReducer, useState } from "react";
|
||||||
import BlanksEditor from "..";
|
import BlanksEditor from "..";
|
||||||
import { Card, CardContent } from "@/components/ui/card";
|
import { Card, CardContent } from "@/components/ui/card";
|
||||||
import useExamEditorStore from "@/stores/examEditor";
|
import useExamEditorStore from "@/stores/examEditor";
|
||||||
@@ -15,6 +15,7 @@ import MCOption from "./MCOption";
|
|||||||
|
|
||||||
const FillBlanksMC: React.FC<{ exercise: FillBlanksExercise; sectionId: number }> = ({ exercise, sectionId }) => {
|
const FillBlanksMC: React.FC<{ exercise: FillBlanksExercise; sectionId: number }> = ({ exercise, sectionId }) => {
|
||||||
const { currentModule, dispatch } = useExamEditorStore();
|
const { currentModule, dispatch } = useExamEditorStore();
|
||||||
|
const difficulty = useExamEditorStore((state) => state.modules[currentModule].difficulty);
|
||||||
const { state } = useExamEditorStore(
|
const { state } = useExamEditorStore(
|
||||||
(state) => state.modules[currentModule].sections.find((section) => section.sectionId === sectionId)!
|
(state) => state.modules[currentModule].sections.find((section) => section.sectionId === sectionId)!
|
||||||
);
|
);
|
||||||
@@ -257,12 +258,24 @@ const FillBlanksMC: React.FC<{ exercise: FillBlanksExercise; sectionId: number }
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [blanksState.blanks]);
|
}, [blanksState.blanks]);
|
||||||
|
|
||||||
|
const saveDifficulty = useCallback((diff: Difficulty) => {
|
||||||
|
if (!difficulty.includes(diff)) {
|
||||||
|
dispatch({ type: 'UPDATE_MODULE', payload: { updates: { difficulty: [...difficulty, diff]} } });
|
||||||
|
}
|
||||||
|
const updatedExercise = { ...exercise, difficulty: diff };
|
||||||
|
const newState = { ...section };
|
||||||
|
newState.exercises = newState.exercises.map((ex) => ex.id === exercise.id ? updatedExercise : ex );
|
||||||
|
dispatch({ type: 'UPDATE_SECTION_STATE', payload: { sectionId, update: newState, module: currentModule } });
|
||||||
|
}, [currentModule, difficulty, dispatch, exercise, section, sectionId]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<BlanksEditor
|
<BlanksEditor
|
||||||
alerts={alerts}
|
alerts={alerts}
|
||||||
editing={editing}
|
editing={editing}
|
||||||
state={blanksState}
|
state={blanksState}
|
||||||
|
difficulty={exercise.difficulty}
|
||||||
|
saveDifficulty={saveDifficulty}
|
||||||
blanksDispatcher={blanksDispatcher}
|
blanksDispatcher={blanksDispatcher}
|
||||||
description="Place blanks and select the correct answer from multiple choice options"
|
description="Place blanks and select the correct answer from multiple choice options"
|
||||||
initialText={local.text}
|
initialText={local.text}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import useSectionEdit from "@/components/ExamEditor/Hooks/useSectionEdit";
|
import useSectionEdit from "@/components/ExamEditor/Hooks/useSectionEdit";
|
||||||
import { Card, CardContent } from "@/components/ui/card";
|
import { Card, CardContent } from "@/components/ui/card";
|
||||||
import { WriteBlanksExercise, ReadingPart } from "@/interfaces/exam";
|
import { WriteBlanksExercise, ReadingPart, Difficulty } from "@/interfaces/exam";
|
||||||
import useExamEditorStore from "@/stores/examEditor";
|
import useExamEditorStore from "@/stores/examEditor";
|
||||||
import { useState, useReducer, useEffect } from "react";
|
import { useState, useReducer, useEffect, useCallback } from "react";
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
import BlanksEditor from "..";
|
import BlanksEditor from "..";
|
||||||
import { AlertItem } from "../../Shared/Alert";
|
import { AlertItem } from "../../Shared/Alert";
|
||||||
@@ -13,6 +13,7 @@ import AlternativeSolutions from "./AlternativeSolutions";
|
|||||||
|
|
||||||
const WriteBlanksFill: React.FC<{ exercise: WriteBlanksExercise; sectionId: number }> = ({ exercise, sectionId }) => {
|
const WriteBlanksFill: React.FC<{ exercise: WriteBlanksExercise; sectionId: number }> = ({ exercise, sectionId }) => {
|
||||||
const { currentModule, dispatch } = useExamEditorStore();
|
const { currentModule, dispatch } = useExamEditorStore();
|
||||||
|
const difficulty = useExamEditorStore((state) => state.modules[currentModule].difficulty);
|
||||||
const { state } = useExamEditorStore(
|
const { state } = useExamEditorStore(
|
||||||
(state) => state.modules[currentModule].sections.find((section) => section.sectionId === sectionId)!
|
(state) => state.modules[currentModule].sections.find((section) => section.sectionId === sectionId)!
|
||||||
);
|
);
|
||||||
@@ -161,6 +162,16 @@ const WriteBlanksFill: React.FC<{ exercise: WriteBlanksExercise; sectionId: numb
|
|||||||
setEditingAlert(editing, setAlerts);
|
setEditingAlert(editing, setAlerts);
|
||||||
}, [editing]);
|
}, [editing]);
|
||||||
|
|
||||||
|
const saveDifficulty = useCallback((diff: Difficulty) => {
|
||||||
|
if (!difficulty.includes(diff)) {
|
||||||
|
dispatch({ type: 'UPDATE_MODULE', payload: { updates: { difficulty: [...difficulty, diff]} } });
|
||||||
|
}
|
||||||
|
const updatedExercise = { ...exercise, difficulty: diff };
|
||||||
|
const newState = { ...section };
|
||||||
|
newState.exercises = newState.exercises.map((ex) => ex.id === exercise.id ? updatedExercise : ex );
|
||||||
|
dispatch({ type: 'UPDATE_SECTION_STATE', payload: { sectionId, update: newState, module: currentModule } });
|
||||||
|
}, [currentModule, difficulty, dispatch, exercise, section, sectionId]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<BlanksEditor
|
<BlanksEditor
|
||||||
@@ -171,6 +182,8 @@ const WriteBlanksFill: React.FC<{ exercise: WriteBlanksExercise; sectionId: numb
|
|||||||
blanksDispatcher={blanksDispatcher}
|
blanksDispatcher={blanksDispatcher}
|
||||||
description={local.prompt}
|
description={local.prompt}
|
||||||
initialText={local.text}
|
initialText={local.text}
|
||||||
|
difficulty={exercise.difficulty}
|
||||||
|
saveDifficulty={saveDifficulty}
|
||||||
module={currentModule}
|
module={currentModule}
|
||||||
showBlankBank={true}
|
showBlankBank={true}
|
||||||
onBlankSelect={(blankId) => setSelectedBlankId(blankId?.toString() || null)}
|
onBlankSelect={(blankId) => setSelectedBlankId(blankId?.toString() || null)}
|
||||||
|
|||||||
@@ -20,12 +20,15 @@ import { Card, CardContent } from "@/components/ui/card";
|
|||||||
import { Blank, DropZone } from "./DragNDrop";
|
import { Blank, DropZone } from "./DragNDrop";
|
||||||
import { getTextSegments, BlankState, BlanksState, BlanksAction, BlankToken } from "./BlanksReducer";
|
import { getTextSegments, BlankState, BlanksState, BlanksAction, BlankToken } from "./BlanksReducer";
|
||||||
import PromptEdit from "../Shared/PromptEdit";
|
import PromptEdit from "../Shared/PromptEdit";
|
||||||
|
import { Difficulty } from "@/interfaces/exam";
|
||||||
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
title?: string;
|
title?: string;
|
||||||
initialText: string;
|
initialText: string;
|
||||||
description: string;
|
description: string;
|
||||||
|
difficulty?: Difficulty;
|
||||||
|
saveDifficulty: (difficulty: Difficulty) => void;
|
||||||
state: BlanksState;
|
state: BlanksState;
|
||||||
module: string;
|
module: string;
|
||||||
editing: boolean;
|
editing: boolean;
|
||||||
@@ -49,6 +52,8 @@ const BlanksEditor: React.FC<Props> = ({
|
|||||||
title = "Fill Blanks",
|
title = "Fill Blanks",
|
||||||
initialText,
|
initialText,
|
||||||
description,
|
description,
|
||||||
|
difficulty,
|
||||||
|
saveDifficulty,
|
||||||
state,
|
state,
|
||||||
editing,
|
editing,
|
||||||
module,
|
module,
|
||||||
@@ -169,6 +174,8 @@ const BlanksEditor: React.FC<Props> = ({
|
|||||||
title={title}
|
title={title}
|
||||||
description={description}
|
description={description}
|
||||||
editing={editing}
|
editing={editing}
|
||||||
|
difficulty={difficulty}
|
||||||
|
saveDifficulty={saveDifficulty}
|
||||||
handleSave={onSave}
|
handleSave={onSave}
|
||||||
handleDelete={onDelete}
|
handleDelete={onDelete}
|
||||||
handleDiscard={onDiscard}
|
handleDiscard={onDiscard}
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import React, { useState, useMemo, useEffect } from 'react';
|
import React, { useState, useMemo, useEffect, useCallback } from 'react';
|
||||||
import {
|
import {
|
||||||
MdAdd,
|
MdAdd,
|
||||||
MdVisibility,
|
MdVisibility,
|
||||||
MdVisibilityOff
|
MdVisibilityOff
|
||||||
} from 'react-icons/md';
|
} from 'react-icons/md';
|
||||||
import { MatchSentencesExercise, ReadingPart } from '@/interfaces/exam';
|
import { Difficulty, MatchSentencesExercise, ReadingPart } from '@/interfaces/exam';
|
||||||
import Alert, { AlertItem } from '../Shared/Alert';
|
import Alert, { AlertItem } from '../Shared/Alert';
|
||||||
import ReferenceViewer from './ParagraphViewer';
|
import ReferenceViewer from './ParagraphViewer';
|
||||||
import Header from '../../Shared/Header';
|
import Header from '../../Shared/Header';
|
||||||
@@ -21,6 +21,7 @@ import PromptEdit from '../Shared/PromptEdit';
|
|||||||
|
|
||||||
const MatchSentences: React.FC<{ exercise: MatchSentencesExercise, sectionId: number }> = ({ exercise, sectionId }) => {
|
const MatchSentences: React.FC<{ exercise: MatchSentencesExercise, sectionId: number }> = ({ exercise, sectionId }) => {
|
||||||
const { currentModule, dispatch } = useExamEditorStore();
|
const { currentModule, dispatch } = useExamEditorStore();
|
||||||
|
const difficulty = useExamEditorStore((state) => state.modules[currentModule].difficulty);
|
||||||
const { state } = useExamEditorStore(
|
const { state } = useExamEditorStore(
|
||||||
(state) => state.modules[currentModule].sections.find((section) => section.sectionId === sectionId)!
|
(state) => state.modules[currentModule].sections.find((section) => section.sectionId === sectionId)!
|
||||||
);
|
);
|
||||||
@@ -147,12 +148,24 @@ const MatchSentences: React.FC<{ exercise: MatchSentencesExercise, sectionId: nu
|
|||||||
updateLocal(handleMatchSentencesReorder(event, local));
|
updateLocal(handleMatchSentencesReorder(event, local));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const saveDifficulty = useCallback((diff: Difficulty) => {
|
||||||
|
if (!difficulty.includes(diff)) {
|
||||||
|
dispatch({ type: 'UPDATE_MODULE', payload: { updates: { difficulty: [...difficulty, diff]} } });
|
||||||
|
}
|
||||||
|
const updatedExercise = { ...exercise, difficulty: diff };
|
||||||
|
const newState = { ...section };
|
||||||
|
newState.exercises = newState.exercises.map((ex) => ex.id === exercise.id ? updatedExercise : ex );
|
||||||
|
dispatch({ type: 'UPDATE_SECTION_STATE', payload: { sectionId, update: newState, module: currentModule } });
|
||||||
|
}, [currentModule, difficulty, dispatch, exercise, section, sectionId]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col mx-auto p-2">
|
<div className="flex flex-col mx-auto p-2">
|
||||||
<Header
|
<Header
|
||||||
title={exercise.variant && exercise.variant == "ideaMatch" ? "Idea Match" : "Paragraph Match"}
|
title={exercise.variant && exercise.variant == "ideaMatch" ? "Idea Match" : "Paragraph Match"}
|
||||||
description={`Edit ${exercise.variant && exercise.variant == "ideaMatch" ? "ideas/opinions" : "headings"} and their matches`}
|
description={`Edit ${exercise.variant && exercise.variant == "ideaMatch" ? "ideas/opinions" : "headings"} and their matches`}
|
||||||
editing={editing}
|
editing={editing}
|
||||||
|
difficulty={exercise.difficulty}
|
||||||
|
saveDifficulty={saveDifficulty}
|
||||||
handleSave={handleSave}
|
handleSave={handleSave}
|
||||||
handleDelete={handleDelete}
|
handleDelete={handleDelete}
|
||||||
handleDiscard={handleDiscard}
|
handleDiscard={handleDiscard}
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ import UnderlineQuestion from "./UnderlineQuestion";
|
|||||||
import useSectionEdit from "@/components/ExamEditor/Hooks/useSectionEdit";
|
import useSectionEdit from "@/components/ExamEditor/Hooks/useSectionEdit";
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
import setEditingAlert from "../../Shared/setEditingAlert";
|
import setEditingAlert from "../../Shared/setEditingAlert";
|
||||||
import { LevelPart, ListeningPart, MultipleChoiceExercise, MultipleChoiceQuestion, ReadingPart } from "@/interfaces/exam";
|
import { Difficulty, LevelPart, ListeningPart, MultipleChoiceExercise, MultipleChoiceQuestion, ReadingPart } from "@/interfaces/exam";
|
||||||
import useExamEditorStore from "@/stores/examEditor";
|
import useExamEditorStore from "@/stores/examEditor";
|
||||||
import { useEffect, useState } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
import { MdAdd } from "react-icons/md";
|
import { MdAdd } from "react-icons/md";
|
||||||
import Alert, { AlertItem } from "../../Shared/Alert";
|
import Alert, { AlertItem } from "../../Shared/Alert";
|
||||||
import PromptEdit from "../../Shared/PromptEdit";
|
import PromptEdit from "../../Shared/PromptEdit";
|
||||||
@@ -18,6 +18,7 @@ const UnderlineMultipleChoice: React.FC<{exercise: MultipleChoiceExercise, secti
|
|||||||
sectionId,
|
sectionId,
|
||||||
}) => {
|
}) => {
|
||||||
const { currentModule, dispatch } = useExamEditorStore();
|
const { currentModule, dispatch } = useExamEditorStore();
|
||||||
|
const difficulty = useExamEditorStore((state) => state.modules[currentModule].difficulty);
|
||||||
const { state } = useExamEditorStore(
|
const { state } = useExamEditorStore(
|
||||||
(state) => state.modules[currentModule].sections.find((section) => section.sectionId === sectionId)!
|
(state) => state.modules[currentModule].sections.find((section) => section.sectionId === sectionId)!
|
||||||
);
|
);
|
||||||
@@ -113,12 +114,24 @@ const UnderlineMultipleChoice: React.FC<{exercise: MultipleChoiceExercise, secti
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const saveDifficulty = useCallback((diff: Difficulty) => {
|
||||||
|
if (!difficulty.includes(diff)) {
|
||||||
|
dispatch({ type: 'UPDATE_MODULE', payload: { updates: { difficulty: [...difficulty, diff]} } });
|
||||||
|
}
|
||||||
|
const updatedExercise = { ...exercise, difficulty: diff };
|
||||||
|
const newState = { ...section };
|
||||||
|
newState.exercises = newState.exercises.map((ex) => ex.id === exercise.id ? updatedExercise : ex );
|
||||||
|
dispatch({ type: 'UPDATE_SECTION_STATE', payload: { sectionId, update: newState, module: currentModule } });
|
||||||
|
}, [currentModule, difficulty, dispatch, exercise, section, sectionId]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="p-4">
|
<div className="p-4">
|
||||||
<Header
|
<Header
|
||||||
title='Underline Multiple Choice Exercise'
|
title='Underline Multiple Choice Exercise'
|
||||||
description="Edit questions with 4 underline options each"
|
description="Edit questions with 4 underline options each"
|
||||||
editing={editing}
|
editing={editing}
|
||||||
|
difficulty={exercise.difficulty}
|
||||||
|
saveDifficulty={saveDifficulty}
|
||||||
handleSave={handleSave}
|
handleSave={handleSave}
|
||||||
handleDelete={handleDelete}
|
handleDelete={handleDelete}
|
||||||
handlePractice={handlePractice}
|
handlePractice={handlePractice}
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useCallback, useEffect, useState } from 'react';
|
||||||
import { Card, CardContent } from '@/components/ui/card';
|
import { Card, CardContent } from '@/components/ui/card';
|
||||||
import {
|
import {
|
||||||
MdAdd,
|
MdAdd,
|
||||||
MdEdit,
|
MdEdit,
|
||||||
MdEditOff,
|
MdEditOff,
|
||||||
} from 'react-icons/md';
|
} from 'react-icons/md';
|
||||||
import { ReadingPart, MultipleChoiceExercise, MultipleChoiceQuestion, LevelPart, ListeningPart } from '@/interfaces/exam';
|
import { ReadingPart, MultipleChoiceExercise, MultipleChoiceQuestion, LevelPart, ListeningPart, Difficulty } from '@/interfaces/exam';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import useExamEditorStore from '@/stores/examEditor';
|
import useExamEditorStore from '@/stores/examEditor';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
@@ -74,7 +74,8 @@ const validateMultipleChoiceQuestions = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
const MultipleChoice: React.FC<MultipleChoiceProps> = ({ exercise, sectionId, optionsQuantity }) => {
|
const MultipleChoice: React.FC<MultipleChoiceProps> = ({ exercise, sectionId, optionsQuantity }) => {
|
||||||
const { currentModule, dispatch} = useExamEditorStore();
|
const { currentModule, dispatch } = useExamEditorStore();
|
||||||
|
const difficulty = useExamEditorStore((state) => state.modules[currentModule].difficulty);
|
||||||
const { state } = useExamEditorStore(
|
const { state } = useExamEditorStore(
|
||||||
(state) => state.modules[currentModule].sections.find((section) => section.sectionId === sectionId)!
|
(state) => state.modules[currentModule].sections.find((section) => section.sectionId === sectionId)!
|
||||||
);
|
);
|
||||||
@@ -202,12 +203,24 @@ const MultipleChoice: React.FC<MultipleChoiceProps> = ({ exercise, sectionId, op
|
|||||||
setLocal(handleMultipleChoiceReorder(event, local));
|
setLocal(handleMultipleChoiceReorder(event, local));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const saveDifficulty = useCallback((diff: Difficulty) => {
|
||||||
|
if (!difficulty.includes(diff)) {
|
||||||
|
dispatch({ type: 'UPDATE_MODULE', payload: { updates: { difficulty: [...difficulty, diff]} } });
|
||||||
|
}
|
||||||
|
const updatedExercise = { ...exercise, difficulty: diff };
|
||||||
|
const newState = { ...section };
|
||||||
|
newState.exercises = newState.exercises.map((ex) => ex.id === exercise.id ? updatedExercise : ex );
|
||||||
|
dispatch({ type: 'UPDATE_SECTION_STATE', payload: { sectionId, update: newState, module: currentModule } });
|
||||||
|
}, [currentModule, difficulty, dispatch, exercise, section, sectionId]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="p-4">
|
<div className="p-4">
|
||||||
<Header
|
<Header
|
||||||
title='Multiple Choice Exercise'
|
title='Multiple Choice Exercise'
|
||||||
description={`Edit questions with ${optionsQuantity} options each`}
|
description={`Edit questions with ${optionsQuantity} options each`}
|
||||||
editing={editing}
|
editing={editing}
|
||||||
|
difficulty={exercise.difficulty}
|
||||||
|
saveDifficulty={saveDifficulty}
|
||||||
handleSave={handleSave}
|
handleSave={handleSave}
|
||||||
handleDelete={handleDelete}
|
handleDelete={handleDelete}
|
||||||
handleDiscard={handleDiscard}
|
handleDiscard={handleDiscard}
|
||||||
|
|||||||
@@ -5,10 +5,10 @@ import { AiOutlineUnorderedList, AiOutlinePlus, AiOutlineDelete } from 'react-ic
|
|||||||
import { Tooltip } from "react-tooltip";
|
import { Tooltip } from "react-tooltip";
|
||||||
import Header from "../../Shared/Header";
|
import Header from "../../Shared/Header";
|
||||||
import GenLoader from "../Shared/GenLoader";
|
import GenLoader from "../Shared/GenLoader";
|
||||||
import { useEffect, useState } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
import useSectionEdit from "../../Hooks/useSectionEdit";
|
import useSectionEdit from "../../Hooks/useSectionEdit";
|
||||||
import useExamEditorStore from "@/stores/examEditor";
|
import useExamEditorStore from "@/stores/examEditor";
|
||||||
import { InteractiveSpeakingExercise, LevelPart } from "@/interfaces/exam";
|
import { Difficulty, InteractiveSpeakingExercise, LevelPart } from "@/interfaces/exam";
|
||||||
import { BsFileText } from "react-icons/bs";
|
import { BsFileText } from "react-icons/bs";
|
||||||
import { FaChevronLeft, FaChevronRight } from "react-icons/fa6";
|
import { FaChevronLeft, FaChevronRight } from "react-icons/fa6";
|
||||||
import { RiVideoLine } from "react-icons/ri";
|
import { RiVideoLine } from "react-icons/ri";
|
||||||
@@ -21,7 +21,8 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const InteractiveSpeaking: React.FC<Props> = ({ sectionId, exercise, module = "speaking" }) => {
|
const InteractiveSpeaking: React.FC<Props> = ({ sectionId, exercise, module = "speaking" }) => {
|
||||||
const { dispatch } = useExamEditorStore();
|
const { currentModule, dispatch } = useExamEditorStore();
|
||||||
|
const difficulty = useExamEditorStore((state) => state.modules[currentModule].difficulty);
|
||||||
const [local, setLocal] = useState(exercise);
|
const [local, setLocal] = useState(exercise);
|
||||||
const [currentVideoIndex, setCurrentVideoIndex] = useState(0);
|
const [currentVideoIndex, setCurrentVideoIndex] = useState(0);
|
||||||
|
|
||||||
@@ -105,13 +106,17 @@ const InteractiveSpeaking: React.FC<Props> = ({ sectionId, exercise, module = "s
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (genResult && generating === "speakingScript") {
|
if (genResult && generating === "speakingScript") {
|
||||||
|
if (!difficulty.includes(genResult.result[0].difficulty)) {
|
||||||
|
dispatch({ type: 'UPDATE_MODULE', payload: { updates: { difficulty: [...difficulty, genResult.result[0].difficulty]} } });
|
||||||
|
}
|
||||||
const updatedLocal = {
|
const updatedLocal = {
|
||||||
...local,
|
...local,
|
||||||
title: genResult.result[0].title,
|
title: genResult.result[0].title,
|
||||||
prompts: genResult.result[0].prompts.map((item: any) => ({
|
prompts: genResult.result[0].prompts.map((item: any) => ({
|
||||||
text: item || "",
|
text: item || "",
|
||||||
video_url: ""
|
video_url: ""
|
||||||
}))
|
})),
|
||||||
|
difficulty: genResult.result[0].difficulty
|
||||||
};
|
};
|
||||||
setEditing(true);
|
setEditing(true);
|
||||||
setLocal(updatedLocal);
|
setLocal(updatedLocal);
|
||||||
@@ -158,13 +163,17 @@ const InteractiveSpeaking: React.FC<Props> = ({ sectionId, exercise, module = "s
|
|||||||
const isGenerating = levelGenerating?.includes(`${local.id}-speakingScript`);
|
const isGenerating = levelGenerating?.includes(`${local.id}-speakingScript`);
|
||||||
|
|
||||||
if (speakingScript && isGenerating) {
|
if (speakingScript && isGenerating) {
|
||||||
|
if (!difficulty.includes(speakingScript.result[0].difficulty)) {
|
||||||
|
dispatch({ type: 'UPDATE_MODULE', payload: { updates: { difficulty: [...difficulty, speakingScript.result[0].difficulty]} } });
|
||||||
|
}
|
||||||
const updatedLocal = {
|
const updatedLocal = {
|
||||||
...local,
|
...local,
|
||||||
title: speakingScript.result[0].title,
|
title: speakingScript.result[0].title,
|
||||||
prompts: speakingScript.result[0].prompts.map((item: any) => ({
|
prompts: speakingScript.result[0].prompts.map((item: any) => ({
|
||||||
text: item || "",
|
text: item || "",
|
||||||
video_url: ""
|
video_url: ""
|
||||||
}))
|
})),
|
||||||
|
difficulty: speakingScript.result[0].difficulty
|
||||||
};
|
};
|
||||||
setEditing(true);
|
setEditing(true);
|
||||||
setLocal(updatedLocal);
|
setLocal(updatedLocal);
|
||||||
@@ -264,6 +273,21 @@ const InteractiveSpeaking: React.FC<Props> = ({ sectionId, exercise, module = "s
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const saveDifficulty = useCallback((diff: Difficulty)=> {
|
||||||
|
if (!difficulty.includes(diff)) {
|
||||||
|
dispatch({ type: 'UPDATE_MODULE', payload: { updates: { difficulty: [...difficulty, diff]} } });
|
||||||
|
}
|
||||||
|
if (module !== "level") {
|
||||||
|
const updatedExercise = { ...exercise, difficulty: diff };
|
||||||
|
dispatch({ type: 'UPDATE_SECTION_STATE', payload: { sectionId, update: updatedExercise, module: currentModule } });
|
||||||
|
} else {
|
||||||
|
const updatedExercise = { ...exercise, difficulty: diff };
|
||||||
|
const newState = { ...state as LevelPart };
|
||||||
|
newState.exercises = (newState as LevelPart).exercises.map((ex) => ex.id === exercise.id ? updatedExercise : ex );
|
||||||
|
dispatch({ type: 'UPDATE_SECTION_STATE', payload: { sectionId, update: newState, module: currentModule } });
|
||||||
|
}
|
||||||
|
}, [currentModule, difficulty, dispatch, exercise, module, sectionId, state]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className='relative pb-4'>
|
<div className='relative pb-4'>
|
||||||
@@ -271,6 +295,8 @@ const InteractiveSpeaking: React.FC<Props> = ({ sectionId, exercise, module = "s
|
|||||||
title={`Interactive Speaking Script`}
|
title={`Interactive Speaking Script`}
|
||||||
description='Generate or write the scripts for the videos.'
|
description='Generate or write the scripts for the videos.'
|
||||||
editing={editing}
|
editing={editing}
|
||||||
|
difficulty={exercise.difficulty}
|
||||||
|
saveDifficulty={saveDifficulty}
|
||||||
handleSave={handleSave}
|
handleSave={handleSave}
|
||||||
handleEdit={handleEdit}
|
handleEdit={handleEdit}
|
||||||
handleDiscard={handleDiscard}
|
handleDiscard={handleDiscard}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useCallback, useEffect, useState } from 'react';
|
||||||
import AutoExpandingTextArea from "@/components/Low/AutoExpandingTextarea";
|
import AutoExpandingTextArea from "@/components/Low/AutoExpandingTextarea";
|
||||||
import { Card, CardContent } from "@/components/ui/card";
|
import { Card, CardContent } from "@/components/ui/card";
|
||||||
import { AiOutlineUnorderedList, AiOutlinePlus, AiOutlineDelete } from 'react-icons/ai';
|
import { AiOutlineUnorderedList, AiOutlinePlus, AiOutlineDelete } from 'react-icons/ai';
|
||||||
@@ -6,7 +6,7 @@ import Header from "../../Shared/Header";
|
|||||||
import GenLoader from "../Shared/GenLoader";
|
import GenLoader from "../Shared/GenLoader";
|
||||||
import useSectionEdit from "../../Hooks/useSectionEdit";
|
import useSectionEdit from "../../Hooks/useSectionEdit";
|
||||||
import useExamEditorStore from "@/stores/examEditor";
|
import useExamEditorStore from "@/stores/examEditor";
|
||||||
import { InteractiveSpeakingExercise, LevelPart } from "@/interfaces/exam";
|
import { Difficulty, InteractiveSpeakingExercise, LevelPart } from "@/interfaces/exam";
|
||||||
import { BsFileText } from "react-icons/bs";
|
import { BsFileText } from "react-icons/bs";
|
||||||
import { RiVideoLine } from 'react-icons/ri';
|
import { RiVideoLine } from 'react-icons/ri';
|
||||||
import { FaChevronLeft, FaChevronRight } from 'react-icons/fa6';
|
import { FaChevronLeft, FaChevronRight } from 'react-icons/fa6';
|
||||||
@@ -19,7 +19,8 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const Speaking1: React.FC<Props> = ({ sectionId, exercise, module = "speaking" }) => {
|
const Speaking1: React.FC<Props> = ({ sectionId, exercise, module = "speaking" }) => {
|
||||||
const { dispatch } = useExamEditorStore();
|
const { currentModule, dispatch } = useExamEditorStore();
|
||||||
|
const difficulty = useExamEditorStore((state) => state.modules[currentModule].difficulty);
|
||||||
const [local, setLocal] = useState(() => {
|
const [local, setLocal] = useState(() => {
|
||||||
const defaultPrompts = [
|
const defaultPrompts = [
|
||||||
{ text: "Hello my name is {avatar}, what is yours?", video_url: "" },
|
{ text: "Hello my name is {avatar}, what is yours?", video_url: "" },
|
||||||
@@ -118,6 +119,9 @@ const Speaking1: React.FC<Props> = ({ sectionId, exercise, module = "speaking" }
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (genResult && generating === "speakingScript") {
|
if (genResult && generating === "speakingScript") {
|
||||||
|
if (!difficulty.includes(genResult.result[0].difficulty)) {
|
||||||
|
dispatch({ type: 'UPDATE_MODULE', payload: { updates: { difficulty: [...difficulty, genResult.result[0].difficulty]} } });
|
||||||
|
}
|
||||||
const updatedLocal = {
|
const updatedLocal = {
|
||||||
...local,
|
...local,
|
||||||
first_title: genResult.result[0].first_topic,
|
first_title: genResult.result[0].first_topic,
|
||||||
@@ -129,7 +133,8 @@ const Speaking1: React.FC<Props> = ({ sectionId, exercise, module = "speaking" }
|
|||||||
text: item,
|
text: item,
|
||||||
video_url: ""
|
video_url: ""
|
||||||
}))
|
}))
|
||||||
]
|
],
|
||||||
|
difficulty: genResult.result[0].difficulty
|
||||||
};
|
};
|
||||||
setEditing(true);
|
setEditing(true);
|
||||||
setLocal(updatedLocal);
|
setLocal(updatedLocal);
|
||||||
@@ -176,10 +181,14 @@ const Speaking1: React.FC<Props> = ({ sectionId, exercise, module = "speaking" }
|
|||||||
const isGenerating = levelGenerating?.includes(`${local.id}-speakingScript`);
|
const isGenerating = levelGenerating?.includes(`${local.id}-speakingScript`);
|
||||||
|
|
||||||
if (speakingScript && isGenerating) {
|
if (speakingScript && isGenerating) {
|
||||||
|
if (!difficulty.includes(speakingScript.result[0].difficulty)) {
|
||||||
|
dispatch({ type: 'UPDATE_MODULE', payload: { updates: { difficulty: [...difficulty, speakingScript.result[0].difficulty]} } });
|
||||||
|
}
|
||||||
const updatedLocal = {
|
const updatedLocal = {
|
||||||
...local,
|
...local,
|
||||||
first_title: speakingScript.result[0].first_topic,
|
first_title: speakingScript.result[0].first_topic,
|
||||||
second_title: speakingScript.result[0].second_topic,
|
second_title: speakingScript.result[0].second_topic,
|
||||||
|
difficulty: speakingScript.result[0].difficulty,
|
||||||
prompts: [
|
prompts: [
|
||||||
local.prompts[0],
|
local.prompts[0],
|
||||||
local.prompts[1],
|
local.prompts[1],
|
||||||
@@ -300,6 +309,21 @@ const Speaking1: React.FC<Props> = ({ sectionId, exercise, module = "speaking" }
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const saveDifficulty = useCallback((diff: Difficulty)=> {
|
||||||
|
if (!difficulty.includes(diff)) {
|
||||||
|
dispatch({ type: 'UPDATE_MODULE', payload: { updates: { difficulty: [...difficulty, diff]} } });
|
||||||
|
}
|
||||||
|
if (module !== "level") {
|
||||||
|
const updatedExercise = { ...exercise, difficulty: diff };
|
||||||
|
dispatch({ type: 'UPDATE_SECTION_STATE', payload: { sectionId, update: updatedExercise, module: currentModule } });
|
||||||
|
} else {
|
||||||
|
const updatedExercise = { ...exercise, difficulty: diff };
|
||||||
|
const newState = { ...state as LevelPart };
|
||||||
|
newState.exercises = (newState as LevelPart).exercises.map((ex) => ex.id === exercise.id ? updatedExercise : ex );
|
||||||
|
dispatch({ type: 'UPDATE_SECTION_STATE', payload: { sectionId, update: newState, module: currentModule } });
|
||||||
|
}
|
||||||
|
}, [currentModule, difficulty, dispatch, exercise, module, sectionId, state]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className='relative pb-4'>
|
<div className='relative pb-4'>
|
||||||
@@ -307,6 +331,8 @@ const Speaking1: React.FC<Props> = ({ sectionId, exercise, module = "speaking" }
|
|||||||
title={`Speaking 1 Script`}
|
title={`Speaking 1 Script`}
|
||||||
description='Generate or write the scripts for the videos.'
|
description='Generate or write the scripts for the videos.'
|
||||||
editing={editing}
|
editing={editing}
|
||||||
|
difficulty={exercise.difficulty}
|
||||||
|
saveDifficulty={saveDifficulty}
|
||||||
handleSave={handleSave}
|
handleSave={handleSave}
|
||||||
handleEdit={handleEdit}
|
handleEdit={handleEdit}
|
||||||
handleDiscard={handleDiscard}
|
handleDiscard={handleDiscard}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import AutoExpandingTextArea from "@/components/Low/AutoExpandingTextarea";
|
import AutoExpandingTextArea from "@/components/Low/AutoExpandingTextarea";
|
||||||
import { Card, CardContent } from "@/components/ui/card";
|
import { Card, CardContent } from "@/components/ui/card";
|
||||||
import { AiOutlinePlus, AiOutlineDelete } from 'react-icons/ai';
|
import { AiOutlinePlus, AiOutlineDelete } from 'react-icons/ai';
|
||||||
import { LevelPart, SpeakingExercise } from "@/interfaces/exam";
|
import { Difficulty, LevelPart, SpeakingExercise } from "@/interfaces/exam";
|
||||||
import useExamEditorStore from "@/stores/examEditor";
|
import useExamEditorStore from "@/stores/examEditor";
|
||||||
import { useEffect, useState } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
import useSectionEdit from "../../Hooks/useSectionEdit";
|
import useSectionEdit from "../../Hooks/useSectionEdit";
|
||||||
import Header from "../../Shared/Header";
|
import Header from "../../Shared/Header";
|
||||||
import { Tooltip } from "react-tooltip";
|
import { Tooltip } from "react-tooltip";
|
||||||
@@ -21,7 +21,8 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const Speaking2: React.FC<Props> = ({ sectionId, exercise, module = "speaking" }) => {
|
const Speaking2: React.FC<Props> = ({ sectionId, exercise, module = "speaking" }) => {
|
||||||
const { dispatch } = useExamEditorStore();
|
const { currentModule, dispatch } = useExamEditorStore();
|
||||||
|
const difficulty = useExamEditorStore((state) => state.modules[currentModule].difficulty);
|
||||||
const [local, setLocal] = useState(exercise);
|
const [local, setLocal] = useState(exercise);
|
||||||
|
|
||||||
const { sections } = useExamEditorStore((store) => store.modules[module]);
|
const { sections } = useExamEditorStore((store) => store.modules[module]);
|
||||||
@@ -104,11 +105,15 @@ const Speaking2: React.FC<Props> = ({ sectionId, exercise, module = "speaking" }
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (genResult && generating === "speakingScript") {
|
if (genResult && generating === "speakingScript") {
|
||||||
|
if (!difficulty.includes(genResult.result[0].difficulty)) {
|
||||||
|
dispatch({ type: 'UPDATE_MODULE', payload: { updates: { difficulty: [...difficulty, genResult.result[0].difficulty]} } });
|
||||||
|
}
|
||||||
const updatedLocal = {
|
const updatedLocal = {
|
||||||
...local,
|
...local,
|
||||||
title: genResult.result[0].topic,
|
title: genResult.result[0].topic,
|
||||||
text: genResult.result[0].question,
|
text: genResult.result[0].question,
|
||||||
prompts: genResult.result[0].prompts
|
prompts: genResult.result[0].prompts,
|
||||||
|
difficulty: genResult.result[0].difficulty
|
||||||
};
|
};
|
||||||
setEditing(true);
|
setEditing(true);
|
||||||
setLocal(updatedLocal);
|
setLocal(updatedLocal);
|
||||||
@@ -153,11 +158,15 @@ const Speaking2: React.FC<Props> = ({ sectionId, exercise, module = "speaking" }
|
|||||||
const speakingScript = levelGenResults.find((res) => res.generating === `${local.id}-speakingScript`);
|
const speakingScript = levelGenResults.find((res) => res.generating === `${local.id}-speakingScript`);
|
||||||
const generating = levelGenerating.find((res) => res === `${local.id}-speakingScript`);
|
const generating = levelGenerating.find((res) => res === `${local.id}-speakingScript`);
|
||||||
if (speakingScript && generating) {
|
if (speakingScript && generating) {
|
||||||
|
if (!difficulty.includes(speakingScript.result[0].difficulty)) {
|
||||||
|
dispatch({ type: 'UPDATE_MODULE', payload: { updates: { difficulty: [...difficulty, speakingScript.result[0].difficulty]} } });
|
||||||
|
}
|
||||||
const updatedLocal = {
|
const updatedLocal = {
|
||||||
...local,
|
...local,
|
||||||
title: speakingScript.result[0].topic,
|
title: speakingScript.result[0].topic,
|
||||||
text: speakingScript.result[0].question,
|
text: speakingScript.result[0].question,
|
||||||
prompts: speakingScript.result[0].prompts
|
prompts: speakingScript.result[0].prompts,
|
||||||
|
difficulty: speakingScript.result[0].difficulty
|
||||||
};
|
};
|
||||||
setEditing(true);
|
setEditing(true);
|
||||||
setLocal(updatedLocal);
|
setLocal(updatedLocal);
|
||||||
@@ -244,6 +253,21 @@ const Speaking2: React.FC<Props> = ({ sectionId, exercise, module = "speaking" }
|
|||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
const saveDifficulty = useCallback((diff: Difficulty)=> {
|
||||||
|
if (!difficulty.includes(diff)) {
|
||||||
|
dispatch({ type: 'UPDATE_MODULE', payload: { updates: { difficulty: [...difficulty, diff]} } });
|
||||||
|
}
|
||||||
|
if (module !== "level") {
|
||||||
|
const updatedExercise = { ...exercise, difficulty: diff };
|
||||||
|
dispatch({ type: 'UPDATE_SECTION_STATE', payload: { sectionId, update: updatedExercise, module: currentModule } });
|
||||||
|
} else {
|
||||||
|
const updatedExercise = { ...exercise, difficulty: diff };
|
||||||
|
const newState = { ...state as LevelPart };
|
||||||
|
newState.exercises = (newState as LevelPart).exercises.map((ex) => ex.id === exercise.id ? updatedExercise : ex );
|
||||||
|
dispatch({ type: 'UPDATE_SECTION_STATE', payload: { sectionId, update: newState, module: currentModule } });
|
||||||
|
}
|
||||||
|
}, [currentModule, difficulty, dispatch, exercise, module, sectionId, state]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className='relative pb-4'>
|
<div className='relative pb-4'>
|
||||||
@@ -251,6 +275,8 @@ const Speaking2: React.FC<Props> = ({ sectionId, exercise, module = "speaking" }
|
|||||||
title={`Speaking ${module === "level" ? local.sectionId : sectionId} Script`}
|
title={`Speaking ${module === "level" ? local.sectionId : sectionId} Script`}
|
||||||
description='Generate or write the script for the video.'
|
description='Generate or write the script for the video.'
|
||||||
editing={editing}
|
editing={editing}
|
||||||
|
difficulty={exercise.difficulty}
|
||||||
|
saveDifficulty={saveDifficulty}
|
||||||
handleSave={handleSave}
|
handleSave={handleSave}
|
||||||
handleEdit={handleEdit}
|
handleEdit={handleEdit}
|
||||||
handleDiscard={handleDiscard}
|
handleDiscard={handleDiscard}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useCallback, useEffect, useState } from 'react';
|
||||||
import {
|
import {
|
||||||
MdAdd,
|
MdAdd,
|
||||||
} from 'react-icons/md';
|
} from 'react-icons/md';
|
||||||
import Alert, { AlertItem } from '../Shared/Alert';
|
import Alert, { AlertItem } from '../Shared/Alert';
|
||||||
import { ReadingPart, TrueFalseExercise } from '@/interfaces/exam';
|
import { Difficulty, ReadingPart, TrueFalseExercise } from '@/interfaces/exam';
|
||||||
import QuestionsList from '../Shared/QuestionsList';
|
import QuestionsList from '../Shared/QuestionsList';
|
||||||
import Header from '../../Shared/Header';
|
import Header from '../../Shared/Header';
|
||||||
import SortableQuestion from '../Shared/SortableQuestion';
|
import SortableQuestion from '../Shared/SortableQuestion';
|
||||||
@@ -19,6 +19,7 @@ import PromptEdit from '../Shared/PromptEdit';
|
|||||||
|
|
||||||
const TrueFalse: React.FC<{ exercise: TrueFalseExercise, sectionId: number }> = ({ exercise, sectionId }) => {
|
const TrueFalse: React.FC<{ exercise: TrueFalseExercise, sectionId: number }> = ({ exercise, sectionId }) => {
|
||||||
const { currentModule, dispatch } = useExamEditorStore();
|
const { currentModule, dispatch } = useExamEditorStore();
|
||||||
|
const difficulty = useExamEditorStore((state) => state.modules[currentModule].difficulty);
|
||||||
const { state } = useExamEditorStore(
|
const { state } = useExamEditorStore(
|
||||||
(state) => state.modules[currentModule].sections.find((section) => section.sectionId === sectionId)!
|
(state) => state.modules[currentModule].sections.find((section) => section.sectionId === sectionId)!
|
||||||
);
|
);
|
||||||
@@ -131,12 +132,24 @@ const TrueFalse: React.FC<{ exercise: TrueFalseExercise, sectionId: number }> =
|
|||||||
setLocal(handleTrueFalseReorder(event, local));
|
setLocal(handleTrueFalseReorder(event, local));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const saveDifficulty = useCallback((diff: Difficulty) => {
|
||||||
|
if (!difficulty.includes(diff)) {
|
||||||
|
dispatch({ type: 'UPDATE_MODULE', payload: { updates: { difficulty: [...difficulty, diff]} } });
|
||||||
|
}
|
||||||
|
const updatedExercise = { ...exercise, difficulty: diff };
|
||||||
|
const newState = { ...section };
|
||||||
|
newState.exercises = newState.exercises.map((ex) => ex.id === exercise.id ? updatedExercise : ex );
|
||||||
|
dispatch({ type: 'UPDATE_SECTION_STATE', payload: { sectionId, update: newState, module: currentModule } });
|
||||||
|
}, [currentModule, difficulty, dispatch, exercise, section, sectionId]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="p-4">
|
<div className="p-4">
|
||||||
<Header
|
<Header
|
||||||
title='True/False/Not Given Exercise'
|
title='True/False/Not Given Exercise'
|
||||||
description='Edit questions and their solutions'
|
description='Edit questions and their solutions'
|
||||||
editing={editing}
|
editing={editing}
|
||||||
|
difficulty={exercise.difficulty}
|
||||||
|
saveDifficulty={saveDifficulty}
|
||||||
handleSave={handleSave}
|
handleSave={handleSave}
|
||||||
handleDelete={handleDelete}
|
handleDelete={handleDelete}
|
||||||
handleDiscard={handleDiscard}
|
handleDiscard={handleDiscard}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect, useCallback } from 'react';
|
||||||
import { Card, CardContent } from '@/components/ui/card';
|
import { Card, CardContent } from '@/components/ui/card';
|
||||||
import {
|
import {
|
||||||
MdAdd,
|
MdAdd,
|
||||||
@@ -13,7 +13,7 @@ import Header from '../../Shared/Header';
|
|||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import Alert, { AlertItem } from '../Shared/Alert';
|
import Alert, { AlertItem } from '../Shared/Alert';
|
||||||
import AutoExpandingTextArea from '@/components/Low/AutoExpandingTextarea';
|
import AutoExpandingTextArea from '@/components/Low/AutoExpandingTextarea';
|
||||||
import { ReadingPart, WriteBlanksExercise } from '@/interfaces/exam';
|
import { Difficulty, ReadingPart, WriteBlanksExercise } from '@/interfaces/exam';
|
||||||
import useExamEditorStore from '@/stores/examEditor';
|
import useExamEditorStore from '@/stores/examEditor';
|
||||||
import useSectionEdit from '../../Hooks/useSectionEdit';
|
import useSectionEdit from '../../Hooks/useSectionEdit';
|
||||||
import setEditingAlert from '../Shared/setEditingAlert';
|
import setEditingAlert from '../Shared/setEditingAlert';
|
||||||
@@ -25,8 +25,8 @@ import PromptEdit from '../Shared/PromptEdit';
|
|||||||
|
|
||||||
|
|
||||||
const WriteBlanks: React.FC<{ sectionId: number; exercise: WriteBlanksExercise; }> = ({ sectionId, exercise }) => {
|
const WriteBlanks: React.FC<{ sectionId: number; exercise: WriteBlanksExercise; }> = ({ sectionId, exercise }) => {
|
||||||
|
|
||||||
const { currentModule, dispatch } = useExamEditorStore();
|
const { currentModule, dispatch } = useExamEditorStore();
|
||||||
|
const difficulty = useExamEditorStore((state) => state.modules[currentModule].difficulty);
|
||||||
const { state } = useExamEditorStore(
|
const { state } = useExamEditorStore(
|
||||||
(state) => state.modules[currentModule].sections.find((section) => section.sectionId === sectionId)!
|
(state) => state.modules[currentModule].sections.find((section) => section.sectionId === sectionId)!
|
||||||
);
|
);
|
||||||
@@ -231,12 +231,24 @@ const WriteBlanks: React.FC<{ sectionId: number; exercise: WriteBlanksExercise;
|
|||||||
}, [local.solutions]);
|
}, [local.solutions]);
|
||||||
|
|
||||||
|
|
||||||
|
const saveDifficulty = useCallback((diff: Difficulty) => {
|
||||||
|
if (!difficulty.includes(diff)) {
|
||||||
|
dispatch({ type: 'UPDATE_MODULE', payload: { updates: { difficulty: [...difficulty, diff]} } });
|
||||||
|
}
|
||||||
|
const updatedExercise = { ...exercise, difficulty: diff };
|
||||||
|
const newState = { ...section };
|
||||||
|
newState.exercises = newState.exercises.map((ex) => ex.id === exercise.id ? updatedExercise : ex );
|
||||||
|
dispatch({ type: 'UPDATE_SECTION_STATE', payload: { sectionId, update: newState, module: currentModule } });
|
||||||
|
}, [currentModule, difficulty, dispatch, exercise, section, sectionId]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="p-4">
|
<div className="p-4">
|
||||||
<Header
|
<Header
|
||||||
title={"Write Blanks: Questions"}
|
title={"Write Blanks: Questions"}
|
||||||
description="Edit questions and their solutions"
|
description="Edit questions and their solutions"
|
||||||
editing={editing}
|
editing={editing}
|
||||||
|
difficulty={exercise.difficulty}
|
||||||
|
saveDifficulty={saveDifficulty}
|
||||||
handleSave={handleSave}
|
handleSave={handleSave}
|
||||||
handleDiscard={handleDiscard}
|
handleDiscard={handleDiscard}
|
||||||
handleDelete={handleDelete}
|
handleDelete={handleDelete}
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import AutoExpandingTextArea from "@/components/Low/AutoExpandingTextarea";
|
import AutoExpandingTextArea from "@/components/Low/AutoExpandingTextarea";
|
||||||
import { Card, CardContent } from "@/components/ui/card";
|
import { Card, CardContent } from "@/components/ui/card";
|
||||||
import { WriteBlanksExercise, ReadingPart } from "@/interfaces/exam";
|
import { WriteBlanksExercise, ReadingPart, Difficulty } from "@/interfaces/exam";
|
||||||
import useExamEditorStore from "@/stores/examEditor";
|
import useExamEditorStore from "@/stores/examEditor";
|
||||||
import { DragEndEvent } from "@dnd-kit/core";
|
import { DragEndEvent } from "@dnd-kit/core";
|
||||||
import { arrayMove } from "@dnd-kit/sortable";
|
import { arrayMove } from "@dnd-kit/sortable";
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect, useCallback } from "react";
|
||||||
import { MdEditOff, MdEdit, MdDelete, MdAdd } from "react-icons/md";
|
import { MdEditOff, MdEdit, MdDelete, MdAdd } from "react-icons/md";
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
import useSectionEdit from "../../Hooks/useSectionEdit";
|
import useSectionEdit from "../../Hooks/useSectionEdit";
|
||||||
@@ -21,6 +21,7 @@ import PromptEdit from "../Shared/PromptEdit";
|
|||||||
|
|
||||||
const WriteBlanksForm: React.FC<{ sectionId: number; exercise: WriteBlanksExercise }> = ({ sectionId, exercise }) => {
|
const WriteBlanksForm: React.FC<{ sectionId: number; exercise: WriteBlanksExercise }> = ({ sectionId, exercise }) => {
|
||||||
const { currentModule, dispatch } = useExamEditorStore();
|
const { currentModule, dispatch } = useExamEditorStore();
|
||||||
|
const difficulty = useExamEditorStore((state) => state.modules[currentModule].difficulty);
|
||||||
const { state } = useExamEditorStore(
|
const { state } = useExamEditorStore(
|
||||||
(state) => state.modules[currentModule].sections.find((section) => section.sectionId === sectionId)!
|
(state) => state.modules[currentModule].sections.find((section) => section.sectionId === sectionId)!
|
||||||
);
|
);
|
||||||
@@ -208,12 +209,24 @@ const WriteBlanksForm: React.FC<{ sectionId: number; exercise: WriteBlanksExerci
|
|||||||
updateLocal({ ...local, text: newText });
|
updateLocal({ ...local, text: newText });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const saveDifficulty = useCallback((diff: Difficulty) => {
|
||||||
|
if (!difficulty.includes(diff)) {
|
||||||
|
dispatch({ type: 'UPDATE_MODULE', payload: { updates: { difficulty: [...difficulty, diff]} } });
|
||||||
|
}
|
||||||
|
const updatedExercise = { ...exercise, difficulty: diff };
|
||||||
|
const newState = { ...section };
|
||||||
|
newState.exercises = newState.exercises.map((ex) => ex.id === exercise.id ? updatedExercise : ex );
|
||||||
|
dispatch({ type: 'UPDATE_SECTION_STATE', payload: { sectionId, update: newState, module: currentModule } });
|
||||||
|
}, [currentModule, difficulty, dispatch, exercise, section, sectionId]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="p-4">
|
<div className="p-4">
|
||||||
<Header
|
<Header
|
||||||
title="Write Blanks: Form Exercise"
|
title="Write Blanks: Form Exercise"
|
||||||
description="Edit questions and their solutions"
|
description="Edit questions and their solutions"
|
||||||
editing={editing}
|
editing={editing}
|
||||||
|
difficulty={exercise.difficulty}
|
||||||
|
saveDifficulty={saveDifficulty}
|
||||||
handleSave={handleSave}
|
handleSave={handleSave}
|
||||||
handleDiscard={handleDiscard}
|
handleDiscard={handleDiscard}
|
||||||
handleDelete={handleDelete}
|
handleDelete={handleDelete}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { useCallback, useEffect, useState } from "react";
|
|||||||
import useExamEditorStore from "@/stores/examEditor";
|
import useExamEditorStore from "@/stores/examEditor";
|
||||||
import ExamEditorStore, { ModuleState } from "@/stores/examEditor/types";
|
import ExamEditorStore, { ModuleState } from "@/stores/examEditor/types";
|
||||||
import AutoExpandingTextArea from "@/components/Low/AutoExpandingTextarea";
|
import AutoExpandingTextArea from "@/components/Low/AutoExpandingTextarea";
|
||||||
import { LevelPart, WritingExercise } from "@/interfaces/exam";
|
import { Difficulty, LevelPart, WritingExercise } from "@/interfaces/exam";
|
||||||
import Header from "../../Shared/Header";
|
import Header from "../../Shared/Header";
|
||||||
import Alert, { AlertItem } from "../Shared/Alert";
|
import Alert, { AlertItem } from "../Shared/Alert";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
@@ -20,6 +20,7 @@ interface Props {
|
|||||||
|
|
||||||
const Writing: React.FC<Props> = ({ sectionId, exercise, module, index }) => {
|
const Writing: React.FC<Props> = ({ sectionId, exercise, module, index }) => {
|
||||||
const { currentModule, dispatch } = useExamEditorStore();
|
const { currentModule, dispatch } = useExamEditorStore();
|
||||||
|
const difficulty = useExamEditorStore((state) => state.modules[currentModule].difficulty);
|
||||||
const { type, academic_url } = useExamEditorStore(
|
const { type, academic_url } = useExamEditorStore(
|
||||||
(state) => state.modules[currentModule]
|
(state) => state.modules[currentModule]
|
||||||
);
|
);
|
||||||
@@ -39,6 +40,7 @@ const Writing: React.FC<Props> = ({ sectionId, exercise, module, index }) => {
|
|||||||
onSave: () => {
|
onSave: () => {
|
||||||
const newExercise = { ...local } as WritingExercise;
|
const newExercise = { ...local } as WritingExercise;
|
||||||
newExercise.prompt = prompt;
|
newExercise.prompt = prompt;
|
||||||
|
newExercise.difficulty = exercise.difficulty;
|
||||||
setAlerts([]);
|
setAlerts([]);
|
||||||
setEditing(false);
|
setEditing(false);
|
||||||
if (!level) {
|
if (!level) {
|
||||||
@@ -86,6 +88,12 @@ const Writing: React.FC<Props> = ({ sectionId, exercise, module, index }) => {
|
|||||||
if (genResult) {
|
if (genResult) {
|
||||||
setEditing(true);
|
setEditing(true);
|
||||||
setPrompt(genResult.result[0].prompt);
|
setPrompt(genResult.result[0].prompt);
|
||||||
|
if (!difficulty.includes(genResult.result[0].difficulty)) {
|
||||||
|
dispatch({ type: 'UPDATE_MODULE', payload: { updates: { difficulty: [...difficulty, genResult.result[0].difficulty]} } });
|
||||||
|
}
|
||||||
|
const updatedExercise = { ...exercise, difficulty: genResult.result[0].difficulty };
|
||||||
|
dispatch({ type: 'UPDATE_SECTION_STATE', payload: { sectionId, update: updatedExercise, module: currentModule } });
|
||||||
|
|
||||||
dispatch({ type: "UPDATE_SECTION_SINGLE_FIELD", payload: { sectionId, module: currentModule, field: "generating", value: undefined } })
|
dispatch({ type: "UPDATE_SECTION_SINGLE_FIELD", payload: { sectionId, module: currentModule, field: "generating", value: undefined } })
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
@@ -95,6 +103,21 @@ const Writing: React.FC<Props> = ({ sectionId, exercise, module, index }) => {
|
|||||||
setEditingAlert(prompt !== local.prompt, setAlerts);
|
setEditingAlert(prompt !== local.prompt, setAlerts);
|
||||||
}, [prompt, local.prompt]);
|
}, [prompt, local.prompt]);
|
||||||
|
|
||||||
|
const saveDifficulty = useCallback((diff: Difficulty)=> {
|
||||||
|
if (!difficulty.includes(diff)) {
|
||||||
|
dispatch({ type: 'UPDATE_MODULE', payload: { updates: { difficulty: [...difficulty, diff]} } });
|
||||||
|
}
|
||||||
|
if (!level) {
|
||||||
|
const updatedExercise = { ...exercise, difficulty: diff };
|
||||||
|
dispatch({ type: 'UPDATE_SECTION_STATE', payload: { sectionId, update: updatedExercise, module: currentModule } });
|
||||||
|
} else {
|
||||||
|
const updatedExercise = { ...exercise, difficulty: diff };
|
||||||
|
const newState = { ...state as LevelPart };
|
||||||
|
newState.exercises = (newState as LevelPart).exercises.map((ex) => ex.id === exercise.id ? updatedExercise : ex );
|
||||||
|
dispatch({ type: 'UPDATE_SECTION_STATE', payload: { sectionId, update: newState, module: currentModule } });
|
||||||
|
}
|
||||||
|
}, [currentModule, difficulty, dispatch, exercise, level, sectionId, state]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className={clsx('relative', level ? "px-4 mt-2" : "pb-2")}>
|
<div className={clsx('relative', level ? "px-4 mt-2" : "pb-2")}>
|
||||||
@@ -102,6 +125,8 @@ const Writing: React.FC<Props> = ({ sectionId, exercise, module, index }) => {
|
|||||||
title={`${sectionId === 1 ? (type === "academic" ? "Visual Information" : "Letter") : "Essay"} Instructions`}
|
title={`${sectionId === 1 ? (type === "academic" ? "Visual Information" : "Letter") : "Essay"} Instructions`}
|
||||||
description='Generate or edit the instructions for the task'
|
description='Generate or edit the instructions for the task'
|
||||||
editing={editing}
|
editing={editing}
|
||||||
|
difficulty={exercise.difficulty}
|
||||||
|
saveDifficulty={saveDifficulty}
|
||||||
handleSave={handleSave}
|
handleSave={handleSave}
|
||||||
handleDelete={module == "level" ? handleDelete : undefined}
|
handleDelete={module == "level" ? handleDelete : undefined}
|
||||||
handleEdit={handleEdit}
|
handleEdit={handleEdit}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { SortableContext, verticalListSortingStrategy } from "@dnd-kit/sortable";
|
import { SortableContext, verticalListSortingStrategy } from "@dnd-kit/sortable";
|
||||||
import SortableSection from "../../Shared/SortableSection";
|
import SortableSection from "../../Shared/SortableSection";
|
||||||
import { Exercise, InteractiveSpeakingExercise, LevelPart, ListeningPart, ReadingPart, SpeakingExercise, WritingExercise } from "@/interfaces/exam";
|
import { Difficulty, Exercise, InteractiveSpeakingExercise, LevelPart, ListeningPart, ReadingPart, SpeakingExercise, WritingExercise } from "@/interfaces/exam";
|
||||||
import ExerciseItem from "./types";
|
import ExerciseItem from "./types";
|
||||||
import Dropdown from "@/components/Dropdown";
|
import Dropdown from "@/components/Dropdown";
|
||||||
import useExamEditorStore from "@/stores/examEditor";
|
import useExamEditorStore from "@/stores/examEditor";
|
||||||
@@ -32,8 +32,7 @@ const SectionExercises: React.FC<{ sectionId: number; }> = ({ sectionId }) => {
|
|||||||
const dispatch = useExamEditorStore(state => state.dispatch);
|
const dispatch = useExamEditorStore(state => state.dispatch);
|
||||||
const currentModule = useExamEditorStore(state => state.currentModule);
|
const currentModule = useExamEditorStore(state => state.currentModule);
|
||||||
|
|
||||||
const sections = useExamEditorStore(state => state.modules[currentModule].sections);
|
const {sections, expandedSections, difficulty} = useExamEditorStore(state => state.modules[currentModule]);
|
||||||
const expandedSections = useExamEditorStore(state => state.modules[currentModule].expandedSections);
|
|
||||||
|
|
||||||
const section = useExamEditorStore(
|
const section = useExamEditorStore(
|
||||||
state => state.modules[currentModule].sections.find(
|
state => state.modules[currentModule].sections.find(
|
||||||
@@ -50,6 +49,15 @@ const SectionExercises: React.FC<{ sectionId: number; }> = ({ sectionId }) => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (genResult && genResult.generating === "exercises" && genResult.module === currentModule) {
|
if (genResult && genResult.generating === "exercises" && genResult.module === currentModule) {
|
||||||
const newExercises = genResult.result[0].exercises;
|
const newExercises = genResult.result[0].exercises;
|
||||||
|
|
||||||
|
const newDifficulties = newExercises
|
||||||
|
.map((ex: Exercise) => ex.difficulty)
|
||||||
|
.filter((diff: Difficulty) => !difficulty.includes(diff));
|
||||||
|
|
||||||
|
if (newDifficulties.length > 0) {
|
||||||
|
dispatch({ type: 'UPDATE_MODULE', payload: { updates: { difficulty: [...difficulty, ...newDifficulties]} } });
|
||||||
|
}
|
||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: "UPDATE_SECTION_STATE", payload: {
|
type: "UPDATE_SECTION_STATE", payload: {
|
||||||
sectionId,
|
sectionId,
|
||||||
@@ -85,6 +93,18 @@ const SectionExercises: React.FC<{ sectionId: number; }> = ({ sectionId }) => {
|
|||||||
) => {
|
) => {
|
||||||
const nonWritingOrSpeaking = results[0]?.generating.startsWith("exercises");
|
const nonWritingOrSpeaking = results[0]?.generating.startsWith("exercises");
|
||||||
|
|
||||||
|
const newExercises = assignExercisesFn(results);
|
||||||
|
|
||||||
|
const newDifficulties = newExercises
|
||||||
|
.map((ex: Exercise) => ex.difficulty)
|
||||||
|
.filter((diff: Difficulty | undefined): diff is Difficulty =>
|
||||||
|
diff !== undefined && !difficulty.includes(diff)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (newDifficulties.length > 0) {
|
||||||
|
dispatch({ type: 'UPDATE_MODULE', payload: { updates: { difficulty: [...difficulty, ...newDifficulties]} } });
|
||||||
|
}
|
||||||
|
|
||||||
const updates = [
|
const updates = [
|
||||||
{
|
{
|
||||||
type: "UPDATE_SECTION_STATE",
|
type: "UPDATE_SECTION_STATE",
|
||||||
@@ -94,7 +114,7 @@ const SectionExercises: React.FC<{ sectionId: number; }> = ({ sectionId }) => {
|
|||||||
update: {
|
update: {
|
||||||
exercises: [
|
exercises: [
|
||||||
...sectionState.exercises,
|
...sectionState.exercises,
|
||||||
...assignExercisesFn(results)
|
...newExercises
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -168,6 +188,7 @@ const SectionExercises: React.FC<{ sectionId: number; }> = ({ sectionId }) => {
|
|||||||
results.map(res => ({
|
results.map(res => ({
|
||||||
...writingTask(res.generating === "writing_letter" ? 1 : 2),
|
...writingTask(res.generating === "writing_letter" ? 1 : 2),
|
||||||
prompt: res.result[0].prompt,
|
prompt: res.result[0].prompt,
|
||||||
|
difficulty: res.result[0].difficulty,
|
||||||
variant: res.generating === "writing_letter" ? "letter" : "essay"
|
variant: res.generating === "writing_letter" ? "letter" : "essay"
|
||||||
}) as WritingExercise);
|
}) as WritingExercise);
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ const getSpeakingTaskData = (taskNumber: number, data: any) => {
|
|||||||
video_url: ""
|
video_url: ""
|
||||||
}))
|
}))
|
||||||
],
|
],
|
||||||
|
difficulty: data.difficulty,
|
||||||
sectionId: 1,
|
sectionId: 1,
|
||||||
};
|
};
|
||||||
case 2:
|
case 2:
|
||||||
@@ -29,6 +30,7 @@ const getSpeakingTaskData = (taskNumber: number, data: any) => {
|
|||||||
title: data.topic,
|
title: data.topic,
|
||||||
text: data.question,
|
text: data.question,
|
||||||
prompts: data.prompts,
|
prompts: data.prompts,
|
||||||
|
difficulty: data.difficulty,
|
||||||
sectionId: 2,
|
sectionId: 2,
|
||||||
type: "speaking"
|
type: "speaking"
|
||||||
};
|
};
|
||||||
@@ -39,6 +41,7 @@ const getSpeakingTaskData = (taskNumber: number, data: any) => {
|
|||||||
text: item || "",
|
text: item || "",
|
||||||
video_url: ""
|
video_url: ""
|
||||||
})),
|
})),
|
||||||
|
difficulty: data.difficulty,
|
||||||
sectionId: 3,
|
sectionId: 3,
|
||||||
};
|
};
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { Module } from "@/interfaces";
|
|||||||
|
|
||||||
interface GeneratorConfig {
|
interface GeneratorConfig {
|
||||||
method: 'GET' | 'POST';
|
method: 'GET' | 'POST';
|
||||||
queryParams?: Record<string, string>;
|
queryParams?: Record<string, string | string[]>;
|
||||||
files?: Record<string, string>;
|
files?: Record<string, string>;
|
||||||
body?: Record<string, any>;
|
body?: Record<string, any>;
|
||||||
}
|
}
|
||||||
@@ -61,9 +61,21 @@ export function generate(
|
|||||||
|
|
||||||
setGenerating(level ? levelSectionId! : sectionId, type, level);
|
setGenerating(level ? levelSectionId! : sectionId, type, level);
|
||||||
|
|
||||||
const queryString = config.queryParams
|
function buildQueryString(params: Record<string, string | string[]>): string {
|
||||||
? new URLSearchParams(config.queryParams).toString()
|
const searchParams = new URLSearchParams();
|
||||||
: '';
|
|
||||||
|
Object.entries(params).forEach(([key, value]) => {
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
value.forEach(v => searchParams.append(key, v));
|
||||||
|
} else {
|
||||||
|
searchParams.append(key, value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return searchParams.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
const queryString = config.queryParams ? buildQueryString(config.queryParams) : '';
|
||||||
|
|
||||||
const url = `/api/exam/generate/${module}/${sectionId}${queryString ? `?${queryString}` : ''}`;
|
const url = `/api/exam/generate/${module}/${sectionId}${queryString ? `?${queryString}` : ''}`;
|
||||||
|
|
||||||
|
|||||||
@@ -273,7 +273,6 @@ const LevelSettings: React.FC = () => {
|
|||||||
<ExercisePicker
|
<ExercisePicker
|
||||||
module="level"
|
module="level"
|
||||||
sectionId={focusedSection}
|
sectionId={focusedSection}
|
||||||
difficulty={difficulty}
|
|
||||||
/>
|
/>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
</div>
|
</div>
|
||||||
@@ -335,7 +334,6 @@ const LevelSettings: React.FC = () => {
|
|||||||
<ExercisePicker
|
<ExercisePicker
|
||||||
module="writing"
|
module="writing"
|
||||||
sectionId={focusedSection}
|
sectionId={focusedSection}
|
||||||
difficulty={difficulty}
|
|
||||||
levelSectionId={focusedSection}
|
levelSectionId={focusedSection}
|
||||||
level
|
level
|
||||||
/>
|
/>
|
||||||
@@ -370,7 +368,6 @@ const LevelSettings: React.FC = () => {
|
|||||||
<ExercisePicker
|
<ExercisePicker
|
||||||
module="speaking"
|
module="speaking"
|
||||||
sectionId={focusedSection}
|
sectionId={focusedSection}
|
||||||
difficulty={difficulty}
|
|
||||||
levelSectionId={focusedSection}
|
levelSectionId={focusedSection}
|
||||||
level
|
level
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -297,7 +297,6 @@ const ListeningComponents: React.FC<Props> = ({ currentSection, localSettings, u
|
|||||||
<ExercisePicker
|
<ExercisePicker
|
||||||
module="listening"
|
module="listening"
|
||||||
sectionId={levelId !== undefined ? levelId : focusedSection}
|
sectionId={levelId !== undefined ? levelId : focusedSection}
|
||||||
difficulty={difficulty}
|
|
||||||
extraArgs={{ script: currentSection === undefined || currentSection.audio === undefined ? "" : currentSection.script }}
|
extraArgs={{ script: currentSection === undefined || currentSection.audio === undefined ? "" : currentSection.script }}
|
||||||
levelSectionId={focusedSection}
|
levelSectionId={focusedSection}
|
||||||
level={level}
|
level={level}
|
||||||
|
|||||||
@@ -95,7 +95,6 @@ const ReadingComponents: React.FC<Props> = ({localSettings, updateLocalAndSchedu
|
|||||||
<ExercisePicker
|
<ExercisePicker
|
||||||
module="reading"
|
module="reading"
|
||||||
sectionId={levelId !== undefined ? levelId : focusedSection}
|
sectionId={levelId !== undefined ? levelId : focusedSection}
|
||||||
difficulty={difficulty}
|
|
||||||
extraArgs={{ text: currentSection === undefined || currentSection.text === undefined ? "" : currentSection.text.content }}
|
extraArgs={{ text: currentSection === undefined || currentSection.text === undefined ? "" : currentSection.text.content }}
|
||||||
levelSectionId={focusedSection}
|
levelSectionId={focusedSection}
|
||||||
level={level}
|
level={level}
|
||||||
|
|||||||
@@ -7,11 +7,15 @@ import Input from "@/components/Low/Input";
|
|||||||
import GenerateBtn from "../Shared/GenerateBtn";
|
import GenerateBtn from "../Shared/GenerateBtn";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import { FaFemale, FaMale } from "react-icons/fa";
|
import { FaFemale, FaMale } from "react-icons/fa";
|
||||||
import { InteractiveSpeakingExercise, LevelPart, SpeakingExercise } from "@/interfaces/exam";
|
import { Difficulty, InteractiveSpeakingExercise, LevelPart, SpeakingExercise } from "@/interfaces/exam";
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
import { generateVideos } from "../Shared/generateVideos";
|
import { generateVideos } from "../Shared/generateVideos";
|
||||||
import { Module } from "@/interfaces";
|
import { Module } from "@/interfaces";
|
||||||
import useCanGenerate from "./useCanGenerate";
|
import useCanGenerate from "./useCanGenerate";
|
||||||
|
import ReactSelect, { components } from "react-select";
|
||||||
|
import { capitalize } from "lodash";
|
||||||
|
import Option from "@/interfaces/option";
|
||||||
|
import { MdSignalCellularAlt } from "react-icons/md";
|
||||||
|
|
||||||
export interface Avatar {
|
export interface Avatar {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -36,9 +40,25 @@ const SpeakingComponents: React.FC<Props> = ({ localSettings, updateLocalAndSche
|
|||||||
|
|
||||||
const [selectedAvatar, setSelectedAvatar] = useState<Avatar | null>(null);
|
const [selectedAvatar, setSelectedAvatar] = useState<Avatar | null>(null);
|
||||||
|
|
||||||
|
const randomDiff = difficulty.length === 1
|
||||||
|
? capitalize(difficulty[0])
|
||||||
|
: difficulty.length == 0 ?
|
||||||
|
"Random" :
|
||||||
|
`Selected (${difficulty.sort().map(dif => capitalize(dif)).join(", ")})` as Difficulty;
|
||||||
|
|
||||||
|
const DIFFICULTIES = difficulty.length === 1
|
||||||
|
? ["A1", "A2", "B1", "B2", "C1", "C2", "Random"]
|
||||||
|
: ["A1", "A2", "B1", "B2", "C1", "C2", randomDiff, "Random"];
|
||||||
|
|
||||||
|
const difficultyOptions: Option[] = DIFFICULTIES.map(level => ({
|
||||||
|
label: level,
|
||||||
|
value: level
|
||||||
|
}));
|
||||||
|
const [specificDiff, setSpecificDiff] = useState(randomDiff);
|
||||||
|
|
||||||
const generateScript = useCallback((scriptSectionId: number) => {
|
const generateScript = useCallback((scriptSectionId: number) => {
|
||||||
const queryParams: {
|
const queryParams: {
|
||||||
difficulty: string;
|
difficulty: string[];
|
||||||
first_topic?: string;
|
first_topic?: string;
|
||||||
second_topic?: string;
|
second_topic?: string;
|
||||||
topic?: string;
|
topic?: string;
|
||||||
@@ -70,19 +90,22 @@ const SpeakingComponents: React.FC<Props> = ({ localSettings, updateLocalAndSche
|
|||||||
return [{
|
return [{
|
||||||
prompts: data.questions,
|
prompts: data.questions,
|
||||||
first_topic: data.first_topic,
|
first_topic: data.first_topic,
|
||||||
second_topic: data.second_topic
|
second_topic: data.second_topic,
|
||||||
|
difficulty: specificDiff.length == 2 ? specificDiff : difficulty,
|
||||||
}];
|
}];
|
||||||
case 2:
|
case 2:
|
||||||
return [{
|
return [{
|
||||||
topic: data.topic,
|
topic: data.topic,
|
||||||
question: data.question,
|
question: data.question,
|
||||||
prompts: data.prompts,
|
prompts: data.prompts,
|
||||||
suffix: data.suffix
|
suffix: data.suffix,
|
||||||
|
difficulty: specificDiff.length == 2 ? specificDiff : difficulty,
|
||||||
}];
|
}];
|
||||||
case 3:
|
case 3:
|
||||||
return [{
|
return [{
|
||||||
title: data.topic,
|
title: data.topic,
|
||||||
prompts: data.questions
|
prompts: data.questions,
|
||||||
|
difficulty: specificDiff.length == 2 ? specificDiff : difficulty,
|
||||||
}];
|
}];
|
||||||
default:
|
default:
|
||||||
return [data];
|
return [data];
|
||||||
@@ -92,7 +115,7 @@ const SpeakingComponents: React.FC<Props> = ({ localSettings, updateLocalAndSche
|
|||||||
level
|
level
|
||||||
);
|
);
|
||||||
|
|
||||||
}, [difficulty, level, section.sectionId, focusedSection, id, sectionId, localSettings.speakingTopic, localSettings.speakingSecondTopic]);
|
}, [difficulty, level, section.sectionId, focusedSection, id, sectionId, localSettings.speakingTopic, localSettings.speakingSecondTopic, specificDiff]);
|
||||||
|
|
||||||
const onTopicChange = useCallback((speakingTopic: string) => {
|
const onTopicChange = useCallback((speakingTopic: string) => {
|
||||||
updateLocalAndScheduleGlobal({ speakingTopic });
|
updateLocalAndScheduleGlobal({ speakingTopic });
|
||||||
@@ -192,7 +215,7 @@ const SpeakingComponents: React.FC<Props> = ({ localSettings, updateLocalAndSche
|
|||||||
contentWrapperClassName={level ? `border border-ielts-speaking` : ''}
|
contentWrapperClassName={level ? `border border-ielts-speaking` : ''}
|
||||||
>
|
>
|
||||||
|
|
||||||
<div className={clsx("gap-2 px-2 pb-4", secId === 1 ? "flex flex-col w-full" : "flex flex-row items-center")}>
|
<div className="gap-4 px-2 pb-4 flex flex-col w-full">
|
||||||
<div className="flex flex-col flex-grow gap-4 px-2">
|
<div className="flex flex-col flex-grow gap-4 px-2">
|
||||||
<label className="font-normal text-base text-mti-gray-dim">{`${secId === 1 ? "First Topic" : "Topic"}`} (Optional)</label>
|
<label className="font-normal text-base text-mti-gray-dim">{`${secId === 1 ? "First Topic" : "Topic"}`} (Optional)</label>
|
||||||
<Input
|
<Input
|
||||||
@@ -201,8 +224,9 @@ const SpeakingComponents: React.FC<Props> = ({ localSettings, updateLocalAndSche
|
|||||||
placeholder="Topic"
|
placeholder="Topic"
|
||||||
name="category"
|
name="category"
|
||||||
onChange={onTopicChange}
|
onChange={onTopicChange}
|
||||||
roundness="full"
|
roundness="xl"
|
||||||
value={localSettings.speakingTopic}
|
value={localSettings.speakingTopic}
|
||||||
|
thin
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{secId === 1 &&
|
{secId === 1 &&
|
||||||
@@ -214,12 +238,69 @@ const SpeakingComponents: React.FC<Props> = ({ localSettings, updateLocalAndSche
|
|||||||
placeholder="Topic"
|
placeholder="Topic"
|
||||||
name="category"
|
name="category"
|
||||||
onChange={onSecondTopicChange}
|
onChange={onSecondTopicChange}
|
||||||
roundness="full"
|
roundness="xl"
|
||||||
value={localSettings.speakingSecondTopic}
|
value={localSettings.speakingSecondTopic}
|
||||||
|
thin
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
<div className={clsx("flex h-16 mb-1", secId === 1 ? "justify-center mt-4" : "self-end")}>
|
<div className="flex flex-col gap-2 px-2">
|
||||||
|
<label className="block font-normal text-base text-mti-gray-dim mb-2">Difficulty (Optional)</label>
|
||||||
|
<ReactSelect
|
||||||
|
options={difficultyOptions}
|
||||||
|
value={difficultyOptions.find(opt => opt.value === specificDiff)}
|
||||||
|
onChange={(value) => setSpecificDiff(value!.value as Difficulty)}
|
||||||
|
menuPortalTarget={document?.body}
|
||||||
|
components={{
|
||||||
|
IndicatorSeparator: null,
|
||||||
|
ValueContainer: ({ children, ...props }) => (
|
||||||
|
<components.ValueContainer {...props}>
|
||||||
|
<div className="flex flex-row gap-2 items-center pl-4">
|
||||||
|
<MdSignalCellularAlt size={14} className="text-gray-600" />
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</components.ValueContainer>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
styles={{
|
||||||
|
menuPortal: (base) => ({ ...base, zIndex: 9999 }),
|
||||||
|
control: (styles) => ({
|
||||||
|
...styles,
|
||||||
|
minHeight: '50px',
|
||||||
|
border: '1px solid #e5e7eb',
|
||||||
|
borderRadius: '0.5rem',
|
||||||
|
boxShadow: 'none',
|
||||||
|
backgroundColor: 'white',
|
||||||
|
cursor: 'pointer',
|
||||||
|
'&:hover': {
|
||||||
|
border: '1px solid #e5e7eb',
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
valueContainer: (styles) => ({
|
||||||
|
...styles,
|
||||||
|
padding: '0 8px',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center'
|
||||||
|
}),
|
||||||
|
input: (styles) => ({
|
||||||
|
...styles,
|
||||||
|
margin: '0',
|
||||||
|
padding: '0'
|
||||||
|
}),
|
||||||
|
dropdownIndicator: (styles) => ({
|
||||||
|
...styles,
|
||||||
|
padding: '8px'
|
||||||
|
}),
|
||||||
|
option: (styles, state) => ({
|
||||||
|
...styles,
|
||||||
|
backgroundColor: state.isFocused ? "#D5D9F0" : state.isSelected ? "#7872BF" : "white",
|
||||||
|
color: state.isFocused ? "black" : styles.color,
|
||||||
|
}),
|
||||||
|
}}
|
||||||
|
className="text-sm"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex h-16 mb-1 justify-center mt-4">
|
||||||
<GenerateBtn
|
<GenerateBtn
|
||||||
module="speaking"
|
module="speaking"
|
||||||
genType={`${id ? `${id}-` : ''}speakingScript`}
|
genType={`${id ? `${id}-` : ''}speakingScript`}
|
||||||
|
|||||||
@@ -5,9 +5,13 @@ import { generate } from "../Shared/Generate";
|
|||||||
import GenerateBtn from "../Shared/GenerateBtn";
|
import GenerateBtn from "../Shared/GenerateBtn";
|
||||||
import { LevelSectionSettings, WritingSectionSettings } from "@/stores/examEditor/types";
|
import { LevelSectionSettings, WritingSectionSettings } from "@/stores/examEditor/types";
|
||||||
import useExamEditorStore from "@/stores/examEditor";
|
import useExamEditorStore from "@/stores/examEditor";
|
||||||
import { WritingExercise } from "@/interfaces/exam";
|
import { Difficulty, WritingExercise } from "@/interfaces/exam";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import { FaFileUpload } from "react-icons/fa";
|
import { FaFileUpload } from "react-icons/fa";
|
||||||
|
import ReactSelect, { components } from "react-select";
|
||||||
|
import Option from "@/interfaces/option"
|
||||||
|
import { MdSignalCellularAlt } from "react-icons/md";
|
||||||
|
import { capitalize } from "lodash";
|
||||||
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -25,6 +29,23 @@ const WritingComponents: React.FC<Props> = ({ localSettings, updateLocalAndSched
|
|||||||
type,
|
type,
|
||||||
academic_url
|
academic_url
|
||||||
} = useExamEditorStore((store) => store.modules["writing"]);
|
} = useExamEditorStore((store) => store.modules["writing"]);
|
||||||
|
|
||||||
|
const randomDiff = difficulty.length === 1
|
||||||
|
? capitalize(difficulty[0])
|
||||||
|
: difficulty.length == 0 ?
|
||||||
|
"Random" :
|
||||||
|
`Selected (${difficulty.sort().map(dif => capitalize(dif)).join(", ")})` as Difficulty;
|
||||||
|
|
||||||
|
const DIFFICULTIES = difficulty.length === 1
|
||||||
|
? ["A1", "A2", "B1", "B2", "C1", "C2", "Random"]
|
||||||
|
: ["A1", "A2", "B1", "B2", "C1", "C2", randomDiff, "Random"];
|
||||||
|
|
||||||
|
const difficultyOptions: Option[] = DIFFICULTIES.map(level => ({
|
||||||
|
label: level,
|
||||||
|
value: level
|
||||||
|
}));
|
||||||
|
const [specificDiff, setSpecificDiff] = useState(randomDiff);
|
||||||
|
|
||||||
const generatePassage = useCallback((sectionId: number) => {
|
const generatePassage = useCallback((sectionId: number) => {
|
||||||
if (type === "academic" && academic_url !== undefined && sectionId == 1) {
|
if (type === "academic" && academic_url !== undefined && sectionId == 1) {
|
||||||
generate(
|
generate(
|
||||||
@@ -34,7 +55,7 @@ const WritingComponents: React.FC<Props> = ({ localSettings, updateLocalAndSched
|
|||||||
{
|
{
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
queryParams: {
|
queryParams: {
|
||||||
difficulty,
|
difficulty: specificDiff.length == 2 ? [specificDiff] : difficulty,
|
||||||
type: type!
|
type: type!
|
||||||
},
|
},
|
||||||
files: {
|
files: {
|
||||||
@@ -42,7 +63,8 @@ const WritingComponents: React.FC<Props> = ({ localSettings, updateLocalAndSched
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
(data: any) => [{
|
(data: any) => [{
|
||||||
prompt: data.question
|
prompt: data.question,
|
||||||
|
difficulty: data.difficulty
|
||||||
}]
|
}]
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
@@ -53,18 +75,18 @@ const WritingComponents: React.FC<Props> = ({ localSettings, updateLocalAndSched
|
|||||||
{
|
{
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
queryParams: {
|
queryParams: {
|
||||||
difficulty,
|
difficulty: specificDiff.length == 2 ? [specificDiff] : difficulty,
|
||||||
type: type!,
|
type: type!,
|
||||||
...(localSettings.writingTopic && { topic: localSettings.writingTopic })
|
...(localSettings.writingTopic && { topic: localSettings.writingTopic })
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
(data: any) => [{
|
(data: any) => [{
|
||||||
prompt: data.question
|
prompt: data.question,
|
||||||
|
difficulty: data.difficulty
|
||||||
}]
|
}]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
}, [type, academic_url, currentModule, specificDiff, difficulty, localSettings.writingTopic]);
|
||||||
}, [localSettings.writingTopic, difficulty, academic_url]);
|
|
||||||
|
|
||||||
const onTopicChange = useCallback((writingTopic: string) => {
|
const onTopicChange = useCallback((writingTopic: string) => {
|
||||||
updateLocalAndScheduleGlobal({ writingTopic });
|
updateLocalAndScheduleGlobal({ writingTopic });
|
||||||
@@ -96,10 +118,66 @@ const WritingComponents: React.FC<Props> = ({ localSettings, updateLocalAndSched
|
|||||||
setIsOpen={(isOpen: boolean) => updateLocalAndScheduleGlobal({ isImageUploadOpen: isOpen }, false)}
|
setIsOpen={(isOpen: boolean) => updateLocalAndScheduleGlobal({ isImageUploadOpen: isOpen }, false)}
|
||||||
contentWrapperClassName={level ? `border border-ielts-writing` : ''}
|
contentWrapperClassName={level ? `border border-ielts-writing` : ''}
|
||||||
>
|
>
|
||||||
<div className="flex flex-row gap-2 items-center px-2 pb-4">
|
<div className="flex w-full flex-row gap-2 items-center px-2 pb-4">
|
||||||
|
<div className="flex w-full flex-col gap-4">
|
||||||
<div className="flex flex-row p-2 gap-4">
|
<div className="flex flex-col gap-2">
|
||||||
<span className="bg-gray-100 px-3.5 py-2.5 rounded-lg border border-gray-300 text-mti-gray-dim">
|
<label className="block font-normal text-base text-mti-gray-dim mb-2">Difficulty (Optional)</label>
|
||||||
|
<ReactSelect
|
||||||
|
options={difficultyOptions}
|
||||||
|
value={difficultyOptions.find(opt => opt.value === specificDiff)}
|
||||||
|
onChange={(value) => setSpecificDiff(value!.value as Difficulty)}
|
||||||
|
menuPortalTarget={document?.body}
|
||||||
|
components={{
|
||||||
|
IndicatorSeparator: null,
|
||||||
|
ValueContainer: ({ children, ...props }) => (
|
||||||
|
<components.ValueContainer {...props}>
|
||||||
|
<div className="flex flex-row gap-2 items-center pl-4">
|
||||||
|
<MdSignalCellularAlt size={14} className="text-gray-600" />
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</components.ValueContainer>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
styles={{
|
||||||
|
menuPortal: (base) => ({ ...base, zIndex: 9999 }),
|
||||||
|
control: (styles) => ({
|
||||||
|
...styles,
|
||||||
|
minHeight: '50px',
|
||||||
|
border: '1px solid #e5e7eb',
|
||||||
|
borderRadius: '0.5rem',
|
||||||
|
boxShadow: 'none',
|
||||||
|
backgroundColor: 'white',
|
||||||
|
cursor: 'pointer',
|
||||||
|
'&:hover': {
|
||||||
|
border: '1px solid #e5e7eb',
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
valueContainer: (styles) => ({
|
||||||
|
...styles,
|
||||||
|
padding: '0 8px',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center'
|
||||||
|
}),
|
||||||
|
input: (styles) => ({
|
||||||
|
...styles,
|
||||||
|
margin: '0',
|
||||||
|
padding: '0'
|
||||||
|
}),
|
||||||
|
dropdownIndicator: (styles) => ({
|
||||||
|
...styles,
|
||||||
|
padding: '8px'
|
||||||
|
}),
|
||||||
|
option: (styles, state) => ({
|
||||||
|
...styles,
|
||||||
|
backgroundColor: state.isFocused ? "#D5D9F0" : state.isSelected ? "#7872BF" : "white",
|
||||||
|
color: state.isFocused ? "black" : styles.color,
|
||||||
|
}),
|
||||||
|
}}
|
||||||
|
className="text-sm"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-row justify-between gap-4">
|
||||||
|
<span className="bg-gray-100 px-3.5 py-2.5 rounded-lg border border-gray-300 text-mti-gray-dim flex-grow">
|
||||||
Upload a graph, chart or diagram
|
Upload a graph, chart or diagram
|
||||||
</span>
|
</span>
|
||||||
<input
|
<input
|
||||||
@@ -124,38 +202,90 @@ const WritingComponents: React.FC<Props> = ({ localSettings, updateLocalAndSched
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</Dropdown>}
|
</Dropdown>}
|
||||||
{
|
{
|
||||||
(type !== "academic" || (type === "academic" && academic_url !== undefined)) && <Dropdown
|
(type !== "academic" || (type === "academic" && focusedSection == 2)) && <Dropdown
|
||||||
title="Generate Instructions"
|
title="Generate Instructions"
|
||||||
module={"writing"}
|
module={"writing"}
|
||||||
open={localSettings.isWritingTopicOpen}
|
open={localSettings.isWritingTopicOpen}
|
||||||
setIsOpen={(isOpen: boolean) => updateLocalAndScheduleGlobal({ isWritingTopicOpen: isOpen }, false)}
|
setIsOpen={(isOpen: boolean) => updateLocalAndScheduleGlobal({ isWritingTopicOpen: isOpen }, false)}
|
||||||
contentWrapperClassName={level ? `border border-ielts-writing` : ''}
|
contentWrapperClassName={level ? `border border-ielts-writing` : ''}
|
||||||
>
|
>
|
||||||
|
<div className="px-2 pb-4 flex flex-col w-full">
|
||||||
<div className="flex flex-row gap-2 items-center px-2 pb-4">
|
<div className="flex flex-col gap-4">
|
||||||
{type !== "academic" ?
|
<div className="flex flex-col gap-2">
|
||||||
<div className="flex flex-col flex-grow gap-4 px-2">
|
<label className="block font-normal text-base text-mti-gray-dim mb-2">Topic (Optional)</label>
|
||||||
<label className="font-normal text-base text-mti-gray-dim">Topic (Optional)</label>
|
<div className="flex gap-2 min-w-0">
|
||||||
<Input
|
<Input
|
||||||
key={`section-${focusedSection}`}
|
key={`section-${focusedSection}`}
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Topic"
|
placeholder="Topic"
|
||||||
name="category"
|
name="category"
|
||||||
onChange={onTopicChange}
|
onChange={onTopicChange}
|
||||||
roundness="full"
|
roundness="xl"
|
||||||
value={localSettings.writingTopic}
|
value={localSettings.writingTopic}
|
||||||
|
thin
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
:
|
|
||||||
<div className="flex flex-col flex-grow gap-4 px-2">
|
|
||||||
<span className="bg-gray-100 px-3.5 py-2.5 rounded-lg border border-gray-300 text-mti-gray-dim">
|
|
||||||
Generate instructions based on the uploaded image.
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<label className="block font-normal text-base text-mti-gray-dim mb-2">Difficulty (Optional)</label>
|
||||||
|
<ReactSelect
|
||||||
|
options={difficultyOptions}
|
||||||
|
value={difficultyOptions.find(opt => opt.value === specificDiff)}
|
||||||
|
onChange={(value) => setSpecificDiff(value!.value as Difficulty)}
|
||||||
|
menuPortalTarget={document?.body}
|
||||||
|
components={{
|
||||||
|
IndicatorSeparator: null,
|
||||||
|
ValueContainer: ({ children, ...props }) => (
|
||||||
|
<components.ValueContainer {...props}>
|
||||||
|
<div className="flex flex-row gap-2 items-center pl-4">
|
||||||
|
<MdSignalCellularAlt size={14} className="text-gray-600" />
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</components.ValueContainer>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
styles={{
|
||||||
|
menuPortal: (base) => ({ ...base, zIndex: 9999 }),
|
||||||
|
control: (styles) => ({
|
||||||
|
...styles,
|
||||||
|
minHeight: '50px',
|
||||||
|
border: '1px solid #e5e7eb',
|
||||||
|
borderRadius: '0.5rem',
|
||||||
|
boxShadow: 'none',
|
||||||
|
backgroundColor: 'white',
|
||||||
|
cursor: 'pointer',
|
||||||
|
'&:hover': {
|
||||||
|
border: '1px solid #e5e7eb',
|
||||||
}
|
}
|
||||||
<div className="flex self-end h-16 mb-1">
|
}),
|
||||||
|
valueContainer: (styles) => ({
|
||||||
|
...styles,
|
||||||
|
padding: '0 8px',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center'
|
||||||
|
}),
|
||||||
|
input: (styles) => ({
|
||||||
|
...styles,
|
||||||
|
margin: '0',
|
||||||
|
padding: '0'
|
||||||
|
}),
|
||||||
|
dropdownIndicator: (styles) => ({
|
||||||
|
...styles,
|
||||||
|
padding: '8px'
|
||||||
|
}),
|
||||||
|
option: (styles, state) => ({
|
||||||
|
...styles,
|
||||||
|
backgroundColor: state.isFocused ? "#D5D9F0" : state.isSelected ? "#7872BF" : "white",
|
||||||
|
color: state.isFocused ? "black" : styles.color,
|
||||||
|
}),
|
||||||
|
}}
|
||||||
|
className="text-sm"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex w-full h-full justify-center items-center mt-2">
|
||||||
<GenerateBtn
|
<GenerateBtn
|
||||||
genType="writing"
|
genType="writing"
|
||||||
module={"writing"}
|
module={"writing"}
|
||||||
@@ -164,6 +294,7 @@ const WritingComponents: React.FC<Props> = ({ localSettings, updateLocalAndSched
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
}
|
}
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -1,13 +1,18 @@
|
|||||||
import { Module } from "@/interfaces";
|
import { Module } from "@/interfaces";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import { ReactNode } from "react";
|
import { ReactNode, useEffect, useState } from "react";
|
||||||
import { MdDelete, MdEdit, MdEditOff, MdRefresh, MdSave } from "react-icons/md";
|
import { MdDelete, MdEdit, MdEditOff, MdRefresh, MdSave, MdSignalCellularAlt } from "react-icons/md";
|
||||||
import { HiOutlineClipboardCheck, HiOutlineClipboardList } from "react-icons/hi";
|
import { HiOutlineClipboardCheck, HiOutlineClipboardList } from "react-icons/hi";
|
||||||
|
import { Difficulty } from "@/interfaces/exam";
|
||||||
|
import Option from "@/interfaces/option";
|
||||||
|
import ReactSelect, { components } from "react-select";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
title: string;
|
title: string;
|
||||||
description: string;
|
description: string;
|
||||||
editing: boolean;
|
editing: boolean;
|
||||||
|
difficulty?: Difficulty;
|
||||||
|
saveDifficulty?: (diff: Difficulty) => void;
|
||||||
module?: Module;
|
module?: Module;
|
||||||
handleSave: () => void;
|
handleSave: () => void;
|
||||||
handleDiscard: () => void;
|
handleDiscard: () => void;
|
||||||
@@ -19,20 +24,27 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const Header: React.FC<Props> = ({
|
const Header: React.FC<Props> = ({
|
||||||
title, description, editing, isEvaluationEnabled, handleSave, handleDiscard, handleDelete, handleEdit, handlePractice, children, module }) => {
|
title, description, editing, difficulty, saveDifficulty, isEvaluationEnabled, handleSave, handleDiscard, handleDelete, handleEdit, handlePractice, children, module
|
||||||
|
}) => {
|
||||||
|
const DIFFICULTIES: Difficulty[] = ["A1", "A2", "B1", "B2", "C1", "C2"];
|
||||||
|
const difficultyOptions: Option[] = DIFFICULTIES.map(level => ({
|
||||||
|
label: level,
|
||||||
|
value: level
|
||||||
|
}));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex justify-between items-center mb-6 text-sm">
|
<div className="flex flex-col md:flex-row items-start md:items-center mb-6 text-sm">
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-2xl font-bold text-gray-800">{title}</h1>
|
<h1 className="text-2xl font-bold text-gray-800">{title}</h1>
|
||||||
<p className="text-gray-600 mt-1">{description}</p>
|
<p className="text-gray-600 mt-1">{description}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-2">
|
<div className="flex w-[50%] ml-auto justify-end flex-wrap gap-2">
|
||||||
{children}
|
<div className="flex flex-wrap gap-2 justify-end">
|
||||||
<button
|
<button
|
||||||
onClick={handleSave}
|
onClick={handleSave}
|
||||||
disabled={!editing}
|
disabled={!editing}
|
||||||
className={
|
className={
|
||||||
clsx("px-4 py-2 rounded-lg flex items-center gap-2 transition-all duration-200",
|
clsx("px-3 py-2 rounded-lg flex items-center gap-1 transition-all duration-200 min-w-[90px] justify-center",
|
||||||
editing ? 'bg-green-500 text-white hover:bg-green-600' : 'bg-gray-100 text-gray-400 cursor-not-allowed'
|
editing ? 'bg-green-500 text-white hover:bg-green-600' : 'bg-gray-100 text-gray-400 cursor-not-allowed'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -43,7 +55,7 @@ const Header: React.FC<Props> = ({
|
|||||||
onClick={handleDiscard}
|
onClick={handleDiscard}
|
||||||
disabled={!editing}
|
disabled={!editing}
|
||||||
className={clsx(
|
className={clsx(
|
||||||
"px-4 py-2 rounded-lg flex items-center gap-2 transition-all duration-200",
|
"px-3 py-2 rounded-lg flex items-center gap-1 transition-all duration-200 min-w-[90px] justify-center",
|
||||||
editing ? 'bg-gray-500 text-white hover:bg-gray-600' : 'bg-gray-100 text-gray-400 cursor-not-allowed'
|
editing ? 'bg-gray-500 text-white hover:bg-gray-600' : 'bg-gray-100 text-gray-400 cursor-not-allowed'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -53,7 +65,7 @@ const Header: React.FC<Props> = ({
|
|||||||
{handleEdit && (
|
{handleEdit && (
|
||||||
<button
|
<button
|
||||||
onClick={handleEdit}
|
onClick={handleEdit}
|
||||||
className={`px-4 py-2 bg-ielts-${module}/80 text-white hover:bg-ielts-${module} rounded-lg transition-all duration-200 flex items-center gap-2`}
|
className={`px-3 py-2 bg-ielts-${module}/80 text-white hover:bg-ielts-${module} rounded-lg transition-all duration-200 flex items-center gap-1 min-w-[90px] justify-center`}
|
||||||
>
|
>
|
||||||
{editing ? <MdEditOff size={18} /> : <MdEdit size={18} />}
|
{editing ? <MdEditOff size={18} /> : <MdEdit size={18} />}
|
||||||
Edit
|
Edit
|
||||||
@@ -63,7 +75,7 @@ const Header: React.FC<Props> = ({
|
|||||||
<button
|
<button
|
||||||
onClick={handlePractice}
|
onClick={handlePractice}
|
||||||
className={clsx(
|
className={clsx(
|
||||||
"px-4 py-2 rounded-lg flex items-center gap-2 transition-all duration-200",
|
"px-3 py-2 rounded-lg flex items-center gap-1 transition-all duration-200 min-w-[90px] justify-center",
|
||||||
isEvaluationEnabled
|
isEvaluationEnabled
|
||||||
? 'bg-amber-500 text-white hover:bg-amber-600'
|
? 'bg-amber-500 text-white hover:bg-amber-600'
|
||||||
: 'bg-gray-200 text-gray-600 hover:bg-gray-300'
|
: 'bg-gray-200 text-gray-600 hover:bg-gray-300'
|
||||||
@@ -76,12 +88,71 @@ const Header: React.FC<Props> = ({
|
|||||||
{handleDelete && (
|
{handleDelete && (
|
||||||
<button
|
<button
|
||||||
onClick={handleDelete}
|
onClick={handleDelete}
|
||||||
className="px-4 py-2 bg-white border border-red-500 text-red-500 hover:bg-red-50 rounded-lg transition-all duration-200 flex items-center gap-2"
|
className="px-3 py-2 bg-white border border-red-500 text-red-500 hover:bg-red-50 rounded-lg transition-all duration-200 flex items-center gap-1 min-w-[90px] justify-center"
|
||||||
>
|
>
|
||||||
<MdDelete size={18} />
|
<MdDelete size={18} />
|
||||||
Delete
|
Delete
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
{difficulty !== undefined && (
|
||||||
|
<div className="w-[92px]">
|
||||||
|
<ReactSelect
|
||||||
|
options={difficultyOptions}
|
||||||
|
value={difficultyOptions.find(opt => opt.value === difficulty)}
|
||||||
|
onChange={(value) => saveDifficulty!(value!.value as Difficulty)}
|
||||||
|
menuPortalTarget={document?.body}
|
||||||
|
components={{
|
||||||
|
IndicatorSeparator: null,
|
||||||
|
ValueContainer: ({ children, ...props }) => (
|
||||||
|
<components.ValueContainer {...props}>
|
||||||
|
<div className="flex flex-row gap-2 items-center pl-0.5">
|
||||||
|
<MdSignalCellularAlt size={14} className="text-gray-600" />
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</components.ValueContainer>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
styles={{
|
||||||
|
menuPortal: (base) => ({ ...base, zIndex: 9999 }),
|
||||||
|
control: (styles) => ({
|
||||||
|
...styles,
|
||||||
|
minHeight: '40px',
|
||||||
|
border: '1px solid #e5e7eb',
|
||||||
|
borderRadius: '0.5rem',
|
||||||
|
boxShadow: 'none',
|
||||||
|
backgroundColor: '#f3f4f6',
|
||||||
|
cursor: 'pointer',
|
||||||
|
'&:hover': {
|
||||||
|
border: '1px solid #e5e7eb',
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
valueContainer: (styles) => ({
|
||||||
|
...styles,
|
||||||
|
padding: '0 8px',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center'
|
||||||
|
}),
|
||||||
|
input: (styles) => ({
|
||||||
|
...styles,
|
||||||
|
margin: '0',
|
||||||
|
padding: '0'
|
||||||
|
}),
|
||||||
|
dropdownIndicator: (styles) => ({
|
||||||
|
...styles,
|
||||||
|
padding: '8px'
|
||||||
|
}),
|
||||||
|
option: (styles, state) => ({
|
||||||
|
...styles,
|
||||||
|
backgroundColor: state.isFocused ? "#D5D9F0" : state.isSelected ? "#7872BF" : "white",
|
||||||
|
color: state.isFocused ? "black" : styles.color,
|
||||||
|
}),
|
||||||
|
}}
|
||||||
|
className="text-sm"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -50,8 +50,10 @@ const ResetModule: React.FC<Props> = ({ module, isOpen, setIsOpen, setNumberOfLe
|
|||||||
const handleResetModule = () => {
|
const handleResetModule = () => {
|
||||||
dispatch({ type: 'RESET_MODULE', payload: { module } });
|
dispatch({ type: 'RESET_MODULE', payload: { module } });
|
||||||
setIsOpen(false);
|
setIsOpen(false);
|
||||||
|
if (module === "level") {
|
||||||
setNumberOfLevelParts(1);
|
setNumberOfLevelParts(1);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Transition
|
<Transition
|
||||||
|
|||||||
@@ -148,9 +148,23 @@ const ExamEditor: React.FC<{ levelParts?: number }> = ({ levelParts = 0 }) => {
|
|||||||
<div className="flex flex-col gap-3 flex-grow">
|
<div className="flex flex-col gap-3 flex-grow">
|
||||||
<label className="font-normal text-base text-mti-gray-dim">Difficulty</label>
|
<label className="font-normal text-base text-mti-gray-dim">Difficulty</label>
|
||||||
<Select
|
<Select
|
||||||
options={DIFFICULTIES.map((x) => ({ value: x, label: capitalize(x) }))}
|
isMulti={true}
|
||||||
onChange={(value) => value && updateModule({ difficulty: value.value as Difficulty })}
|
options={DIFFICULTIES.map((x) => ({
|
||||||
value={{ value: difficulty, label: capitalize(difficulty) }}
|
value: x,
|
||||||
|
label: capitalize(x)
|
||||||
|
}))}
|
||||||
|
onChange={(values) => {
|
||||||
|
const selectedDifficulties = values ? values.map(v => v.value as Difficulty) : [];
|
||||||
|
updateModule({ difficulty: selectedDifficulties });
|
||||||
|
}}
|
||||||
|
value={
|
||||||
|
difficulty
|
||||||
|
? difficulty.map(d => ({
|
||||||
|
value: d,
|
||||||
|
label: capitalize(d)
|
||||||
|
}))
|
||||||
|
: null
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{(sectionLabels.length != 0 && currentModule !== "level") ? (
|
{(sectionLabels.length != 0 && currentModule !== "level") ? (
|
||||||
@@ -202,9 +216,13 @@ const ExamEditor: React.FC<{ levelParts?: number }> = ({ levelParts = 0 }) => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{currentModule === "listening" && <ListeningInstructions />}
|
{currentModule === "listening" && <ListeningInstructions />}
|
||||||
{["reading", "listening", "level"].includes(currentModule) && <Button onClick={() => setIsResetModuleOpen(true)} customColor={`bg-ielts-${currentModule}/70 hover:bg-ielts-${currentModule} border-ielts-${currentModule}`} className={`text-white self-end`}>
|
<Button
|
||||||
|
onClick={() => setIsResetModuleOpen(true)}
|
||||||
|
customColor={`bg-ielts-${currentModule}/70 hover:bg-ielts-${currentModule} border-ielts-${currentModule}`}
|
||||||
|
className={`text-white self-end`}
|
||||||
|
>
|
||||||
Reset Module
|
Reset Module
|
||||||
</Button>}
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-row gap-8">
|
<div className="flex flex-row gap-8">
|
||||||
<Settings />
|
<Settings />
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import ReactSelect, { GroupBase, StylesConfig } from "react-select";
|
|||||||
import Option from "@/interfaces/option";
|
import Option from "@/interfaces/option";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
defaultValue?: Option;
|
defaultValue?: Option | Option[];
|
||||||
value?: Option | null;
|
value?: Option | Option[] | null;
|
||||||
options: Option[];
|
options: Option[];
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
@@ -13,6 +13,7 @@ interface Props {
|
|||||||
styles?: StylesConfig<Option, boolean, GroupBase<Option>>;
|
styles?: StylesConfig<Option, boolean, GroupBase<Option>>;
|
||||||
className?: string;
|
className?: string;
|
||||||
label?: string;
|
label?: string;
|
||||||
|
flat?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
interface MultiProps {
|
interface MultiProps {
|
||||||
@@ -25,7 +26,7 @@ interface SingleProps {
|
|||||||
onChange: (value: Option | null) => void
|
onChange: (value: Option | null) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Select({ value, isMulti, defaultValue, options, placeholder, disabled, onChange, styles, isClearable, label, className }: Props & (MultiProps | SingleProps)) {
|
export default function Select({ value, isMulti, defaultValue, options, placeholder, disabled, onChange, styles, isClearable, label, className, flat }: Props & (MultiProps | SingleProps)) {
|
||||||
const [target, setTarget] = useState<HTMLElement>();
|
const [target, setTarget] = useState<HTMLElement>();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -41,8 +42,9 @@ export default function Select({ value, isMulti, defaultValue, options, placehol
|
|||||||
styles
|
styles
|
||||||
? undefined
|
? undefined
|
||||||
: clsx(
|
: clsx(
|
||||||
"placeholder:text-mti-gray-cool border-mti-gray-platinum w-full rounded-full border bg-white px-4 py-4 text-sm font-normal focus:outline-none",
|
"placeholder:text-mti-gray-cool border-mti-gray-platinum w-full border bg-white text-sm font-normal focus:outline-none",
|
||||||
disabled && "!bg-mti-gray-platinum/40 !text-mti-gray-dim cursor-not-allowed",
|
disabled && "!bg-mti-gray-platinum/40 !text-mti-gray-dim cursor-not-allowed",
|
||||||
|
flat ? "rounded-md" : "px-4 py-4 rounded-full",
|
||||||
className,
|
className,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -103,7 +103,6 @@ const Level: React.FC<ExamProps<LevelExam>> = ({ exam, showSolutions = false, pr
|
|||||||
|
|
||||||
const hasPractice = useMemo(() => {
|
const hasPractice = useMemo(() => {
|
||||||
if (partIndex > -1 && partIndex < exam.parts.length && !showPartDivider) {
|
if (partIndex > -1 && partIndex < exam.parts.length && !showPartDivider) {
|
||||||
console.log(exam.parts[partIndex].exercises.some(e => e.isPractice))
|
|
||||||
return exam.parts[partIndex].exercises.some(e => e.isPractice)
|
return exam.parts[partIndex].exercises.some(e => e.isPractice)
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ export interface ExamBase {
|
|||||||
minTimer: number;
|
minTimer: number;
|
||||||
isDiagnostic: boolean;
|
isDiagnostic: boolean;
|
||||||
variant?: Variant;
|
variant?: Variant;
|
||||||
difficulty?: Difficulty;
|
difficulty?: Difficulty | Difficulty[];
|
||||||
owners?: string[];
|
owners?: string[];
|
||||||
entities?: string[]
|
entities?: string[]
|
||||||
shuffle?: boolean;
|
shuffle?: boolean;
|
||||||
@@ -177,6 +177,7 @@ export interface WritingExercise extends Section {
|
|||||||
topic?: string;
|
topic?: string;
|
||||||
variant?: string;
|
variant?: string;
|
||||||
isPractice?: boolean
|
isPractice?: boolean
|
||||||
|
difficulty?: Difficulty
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AIDetectionAttributes {
|
export interface AIDetectionAttributes {
|
||||||
@@ -212,6 +213,7 @@ export interface SpeakingExercise extends Section {
|
|||||||
}[];
|
}[];
|
||||||
topic?: string;
|
topic?: string;
|
||||||
isPractice?: boolean
|
isPractice?: boolean
|
||||||
|
difficulty?: Difficulty
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface InteractiveSpeakingExercise extends Section {
|
export interface InteractiveSpeakingExercise extends Section {
|
||||||
@@ -232,6 +234,7 @@ export interface InteractiveSpeakingExercise extends Section {
|
|||||||
second_topic?: string;
|
second_topic?: string;
|
||||||
variant?: "initial" | "final";
|
variant?: "initial" | "final";
|
||||||
isPractice?: boolean
|
isPractice?: boolean
|
||||||
|
difficulty?: Difficulty
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface FillBlanksMCOption {
|
export interface FillBlanksMCOption {
|
||||||
@@ -261,6 +264,7 @@ export interface FillBlanksExercise {
|
|||||||
}[];
|
}[];
|
||||||
variant?: string;
|
variant?: string;
|
||||||
isPractice?: boolean
|
isPractice?: boolean
|
||||||
|
difficulty?: Difficulty
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TrueFalseExercise {
|
export interface TrueFalseExercise {
|
||||||
@@ -270,6 +274,7 @@ export interface TrueFalseExercise {
|
|||||||
questions: TrueFalseQuestion[];
|
questions: TrueFalseQuestion[];
|
||||||
userSolutions: { id: string; solution: "true" | "false" | "not_given" }[];
|
userSolutions: { id: string; solution: "true" | "false" | "not_given" }[];
|
||||||
isPractice?: boolean
|
isPractice?: boolean
|
||||||
|
difficulty?: Difficulty
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TrueFalseQuestion {
|
export interface TrueFalseQuestion {
|
||||||
@@ -294,6 +299,7 @@ export interface WriteBlanksExercise {
|
|||||||
}[];
|
}[];
|
||||||
variant?: string;
|
variant?: string;
|
||||||
isPractice?: boolean
|
isPractice?: boolean
|
||||||
|
difficulty?: Difficulty
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MatchSentencesExercise {
|
export interface MatchSentencesExercise {
|
||||||
@@ -306,6 +312,7 @@ export interface MatchSentencesExercise {
|
|||||||
options: MatchSentenceExerciseOption[];
|
options: MatchSentenceExerciseOption[];
|
||||||
variant?: string;
|
variant?: string;
|
||||||
isPractice?: boolean
|
isPractice?: boolean
|
||||||
|
difficulty?: Difficulty
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MatchSentenceExerciseSentence {
|
export interface MatchSentenceExerciseSentence {
|
||||||
@@ -331,6 +338,7 @@ export interface MultipleChoiceExercise {
|
|||||||
content: string;
|
content: string;
|
||||||
}
|
}
|
||||||
isPractice?: boolean
|
isPractice?: boolean
|
||||||
|
difficulty?: Difficulty
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MultipleChoiceQuestion {
|
export interface MultipleChoiceQuestion {
|
||||||
|
|||||||
@@ -80,7 +80,6 @@ async function patch(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
await Promise.all(newParticipants.map(async (p) => await updateExpiryDateOnGroup(p, group.admin)));
|
await Promise.all(newParticipants.map(async (p) => await updateExpiryDateOnGroup(p, group.admin)));
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(req.body);
|
|
||||||
await db.collection("groups").updateOne({id}, {$set: {id, ...req.body}}, {upsert: true});
|
await db.collection("groups").updateOne({id}, {$set: {id, ...req.body}}, {upsert: true});
|
||||||
|
|
||||||
res.status(200).json({ok: true});
|
res.status(200).json({ok: true});
|
||||||
|
|||||||
@@ -153,14 +153,14 @@ export const defaultSectionSettings = (module: Module, sectionId: number, part?:
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const defaultModuleSettings = (module: Module, minTimer: number): ModuleState => {
|
const defaultModuleSettings = (module: Module, minTimer: number, reset: boolean = false): ModuleState => {
|
||||||
const state: ModuleState = {
|
const state: ModuleState = {
|
||||||
examLabel: defaultExamLabel(module),
|
examLabel: defaultExamLabel(module),
|
||||||
minTimer,
|
minTimer,
|
||||||
difficulty: sample(["A1", "A2", "B1", "B2", "C1", "C2"] as Difficulty[])!,
|
difficulty: [sample(["A1", "A2", "B1", "B2", "C1", "C2"] as Difficulty[])!],
|
||||||
isPrivate: true,
|
isPrivate: true,
|
||||||
sectionLabels: sectionLabels(module),
|
sectionLabels: sectionLabels(module),
|
||||||
expandedSections: [1],
|
expandedSections: [(reset && (module === "writing" || module === "speaking")) ? 0 : 1],
|
||||||
focusedSection: 1,
|
focusedSection: 1,
|
||||||
sections: [defaultSectionSettings(module, 1)],
|
sections: [defaultSectionSettings(module, 1)],
|
||||||
importModule: true,
|
importModule: true,
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ export const rootReducer = (
|
|||||||
return {
|
return {
|
||||||
modules: {
|
modules: {
|
||||||
...state.modules,
|
...state.modules,
|
||||||
[module]: defaultModuleSettings(module, timer),
|
[module]: defaultModuleSettings(module, timer, true),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
case 'FULL_RESET':
|
case 'FULL_RESET':
|
||||||
|
|||||||
@@ -125,7 +125,7 @@ export interface ModuleState {
|
|||||||
examLabel: string;
|
examLabel: string;
|
||||||
sections: SectionState[];
|
sections: SectionState[];
|
||||||
minTimer: number;
|
minTimer: number;
|
||||||
difficulty: Difficulty;
|
difficulty: Difficulty[];
|
||||||
isPrivate: boolean;
|
isPrivate: boolean;
|
||||||
sectionLabels: {id: number; label: string;}[];
|
sectionLabels: {id: number; label: string;}[];
|
||||||
expandedSections: number[];
|
expandedSections: number[];
|
||||||
|
|||||||
Reference in New Issue
Block a user