Merged in feature/ExamGenRework (pull request #110)

isPractice hotfix

Approved-by: Tiago Ribeiro
This commit is contained in:
carlos.mesquita
2024-11-12 18:14:31 +00:00
committed by Tiago Ribeiro
21 changed files with 109 additions and 28 deletions

View File

@@ -351,7 +351,19 @@ const EXERCISES: ExerciseGen[] = [
type: "speaking_1", type: "speaking_1",
icon: FaComments, icon: FaComments,
extra: [ extra: [
generate() generate(),
{
label: "First Topic",
param: "first_topic",
value: "",
type: "text"
},
{
label: "Second Topic",
param: "second_topic",
value: "",
type: "text"
},
], ],
module: "speaking" module: "speaking"
}, },
@@ -360,7 +372,13 @@ const EXERCISES: ExerciseGen[] = [
type: "speaking_2", type: "speaking_2",
icon: FaUserFriends, icon: FaUserFriends,
extra: [ extra: [
generate() generate(),
{
label: "Topic",
param: "topic",
value: "",
type: "text"
},
], ],
module: "speaking" module: "speaking"
}, },
@@ -369,7 +387,13 @@ const EXERCISES: ExerciseGen[] = [
type: "speaking_3", type: "speaking_3",
icon: FaHandshake, icon: FaHandshake,
extra: [ extra: [
generate() generate(),
{
label: "Topic",
param: "topic",
value: "",
type: "text"
},
], ],
module: "speaking" module: "speaking"
}, },

View File

@@ -112,6 +112,7 @@ const FillBlanksLetters: React.FC<{ exercise: FillBlanksExercise; sectionId: num
newState.exercises = newState.exercises.map((ex) => newState.exercises = newState.exercises.map((ex) =>
ex.id === exercise.id ? updatedExercise : ex ex.id === exercise.id ? updatedExercise : ex
); );
setLocal((prev) => ({...prev, isPractice: !local.isPractice}))
dispatch({ type: 'UPDATE_SECTION_STATE', payload: { sectionId, update: newState, module: currentModule } }); dispatch({ type: 'UPDATE_SECTION_STATE', payload: { sectionId, update: newState, module: currentModule } });
} }
}); });

View File

@@ -108,6 +108,7 @@ const FillBlanksMC: React.FC<{ exercise: FillBlanksExercise; sectionId: number }
newState.exercises = newState.exercises.map((ex) => newState.exercises = newState.exercises.map((ex) =>
ex.id === exercise.id ? updatedExercise : ex ex.id === exercise.id ? updatedExercise : ex
); );
setLocal((prev) => ({...prev, isPractice: !local.isPractice}))
dispatch({ type: 'UPDATE_SECTION_STATE', payload: { sectionId, update: newState, module: currentModule } }); dispatch({ type: 'UPDATE_SECTION_STATE', payload: { sectionId, update: newState, module: currentModule } });
} }
}); });

View File

@@ -79,6 +79,7 @@ const WriteBlanksFill: React.FC<{ exercise: WriteBlanksExercise; sectionId: numb
newState.exercises = newState.exercises.map((ex) => newState.exercises = newState.exercises.map((ex) =>
ex.id === exercise.id ? updatedExercise : ex ex.id === exercise.id ? updatedExercise : ex
); );
setLocal((prev) => ({...prev, isPractice: !local.isPractice}))
dispatch({ type: 'UPDATE_SECTION_STATE', payload: { sectionId, update: newState, module: currentModule } }); dispatch({ type: 'UPDATE_SECTION_STATE', payload: { sectionId, update: newState, module: currentModule } });
} }
}); });

View File

@@ -76,6 +76,7 @@ const MatchSentences: React.FC<{ exercise: MatchSentencesExercise, sectionId: nu
newState.exercises = newState.exercises.map((ex) => newState.exercises = newState.exercises.map((ex) =>
ex.id === exercise.id ? updatedExercise : ex ex.id === exercise.id ? updatedExercise : ex
); );
setLocal((prev) => ({...prev, isPractice: !local.isPractice}))
dispatch({ type: 'UPDATE_SECTION_STATE', payload: { sectionId, update: newState, module: currentModule } }); dispatch({ type: 'UPDATE_SECTION_STATE', payload: { sectionId, update: newState, module: currentModule } });
} }
}); });

View File

@@ -107,6 +107,7 @@ const UnderlineMultipleChoice: React.FC<{exercise: MultipleChoiceExercise, secti
newState.exercises = newState.exercises.map((ex) => newState.exercises = newState.exercises.map((ex) =>
ex.id === exercise.id ? updatedExercise : ex ex.id === exercise.id ? updatedExercise : ex
); );
setLocal((prev) => ({...prev, isPractice: !local.isPractice}))
dispatch({ type: 'UPDATE_SECTION_STATE', payload: { sectionId, update: newState, module: currentModule } }); dispatch({ type: 'UPDATE_SECTION_STATE', payload: { sectionId, update: newState, module: currentModule } });
} }
}); });

View File

@@ -186,6 +186,7 @@ const MultipleChoice: React.FC<MultipleChoiceProps> = ({ exercise, sectionId, op
newState.exercises = newState.exercises.map((ex) => newState.exercises = newState.exercises.map((ex) =>
ex.id === exercise.id ? updatedExercise : ex ex.id === exercise.id ? updatedExercise : ex
); );
setLocal((prev) => ({...prev, isPractice: !local.isPractice}))
dispatch({ type: 'UPDATE_SECTION_STATE', payload: { sectionId, update: newState, module: currentModule } }); dispatch({ type: 'UPDATE_SECTION_STATE', payload: { sectionId, update: newState, module: currentModule } });
} }
}); });

View File

@@ -55,6 +55,7 @@ const InteractiveSpeaking: React.FC<Props> = ({ sectionId, exercise, module = "s
...state, ...state,
isPractice: !local.isPractice isPractice: !local.isPractice
}; };
setLocal((prev) => ({...prev, isPractice: !local.isPractice}))
dispatch({ type: 'UPDATE_SECTION_STATE', payload: { sectionId, update: updatedExercise, module: module } }); dispatch({ type: 'UPDATE_SECTION_STATE', payload: { sectionId, update: updatedExercise, module: module } });
}, },
}); });

View File

@@ -67,6 +67,7 @@ const Speaking1: React.FC<Props> = ({ sectionId, exercise, module = "speaking" }
...state, ...state,
isPractice: !local.isPractice isPractice: !local.isPractice
}; };
setLocal((prev) => ({...prev, isPractice: !local.isPractice}))
dispatch({ type: 'UPDATE_SECTION_STATE', payload: { sectionId, update: updatedExercise, module: module } }); dispatch({ type: 'UPDATE_SECTION_STATE', payload: { sectionId, update: updatedExercise, module: module } });
}, },
}); });

View File

@@ -54,6 +54,7 @@ const Speaking2: React.FC<Props> = ({ sectionId, exercise, module = "speaking" }
...state, ...state,
isPractice: !local.isPractice isPractice: !local.isPractice
}; };
setLocal((prev) => ({...prev, isPractice: !local.isPractice}))
dispatch({ type: 'UPDATE_SECTION_STATE', payload: { sectionId, update: updatedExercise, module: module } }); dispatch({ type: 'UPDATE_SECTION_STATE', payload: { sectionId, update: updatedExercise, module: module } });
}, },
}); });

View File

@@ -3,21 +3,30 @@ import { SpeakingExercise, InteractiveSpeakingExercise } from "@/interfaces/exam
import Speaking2 from "./Speaking2"; import Speaking2 from "./Speaking2";
import InteractiveSpeaking from "./InteractiveSpeaking"; import InteractiveSpeaking from "./InteractiveSpeaking";
import Speaking1 from "./Speaking1"; import Speaking1 from "./Speaking1";
import { Module } from "@/interfaces";
interface Props { interface Props {
sectionId: number; sectionId: number;
exercise: SpeakingExercise | InteractiveSpeakingExercise; exercise: SpeakingExercise | InteractiveSpeakingExercise;
qId?: number;
module: Module;
} }
const Speaking: React.FC<Props> = ({ sectionId, exercise }) => { const Speaking: React.FC<Props> = ({ sectionId, exercise, qId, module = "speaking" }) => {
const { currentModule } = useExamEditorStore(); const { dispatch } = useExamEditorStore();
const { state } = useExamEditorStore( const { state } = useExamEditorStore(
(state) => state.modules[currentModule].sections.find((section) => section.sectionId === sectionId)! (state) => state.modules[module].sections.find((section) => section.sectionId === sectionId)!
); );
const onFocus = () => {
if (qId) {
dispatch({ type: "UPDATE_SECTION_SINGLE_FIELD", payload: { module, sectionId, field: "focusedExercise", value: { questionId: qId, id: exercise.id } } })
}
}
return ( return (
<> <>
<div className="mx-auto p-3 space-y-6"> <div tabIndex={0} className="mx-auto p-3 space-y-6" onFocus={onFocus}>
<div className="p-4"> <div className="p-4">
<div className="flex flex-col space-y-6"> <div className="flex flex-col space-y-6">
{sectionId === 1 && <Speaking1 sectionId={sectionId} exercise={state as InteractiveSpeakingExercise} />} {sectionId === 1 && <Speaking1 sectionId={sectionId} exercise={state as InteractiveSpeakingExercise} />}

View File

@@ -107,12 +107,13 @@ const TrueFalse: React.FC<{ exercise: TrueFalseExercise, sectionId: number }> =
onPractice: () => { onPractice: () => {
const updatedExercise = { const updatedExercise = {
...local, ...local,
isPractice: !local.isPractice isPractice: false
}; };
const newState = { ...section }; const newState = { ...section };
newState.exercises = newState.exercises.map((ex) => newState.exercises = newState.exercises.map((ex) =>
ex.id === exercise.id ? updatedExercise : ex ex.id === exercise.id ? updatedExercise : ex
); );
updateLocal({...local, isPractice: !local.isPractice})
dispatch({ type: 'UPDATE_SECTION_STATE', payload: { sectionId, update: newState, module: currentModule } }); dispatch({ type: 'UPDATE_SECTION_STATE', payload: { sectionId, update: newState, module: currentModule } });
} }
}); });
@@ -140,6 +141,7 @@ const TrueFalse: React.FC<{ exercise: TrueFalseExercise, sectionId: number }> =
handleDelete={handleDelete} handleDelete={handleDelete}
handleDiscard={handleDiscard} handleDiscard={handleDiscard}
handlePractice={handlePractice} handlePractice={handlePractice}
isEvaluationEnabled={!local.isPractice}
/> />
{alerts.length > 0 && <Alert className="mb-6" alerts={alerts} />} {alerts.length > 0 && <Alert className="mb-6" alerts={alerts} />}
<PromptEdit <PromptEdit

View File

@@ -85,6 +85,7 @@ const WriteBlanks: React.FC<{ sectionId: number; exercise: WriteBlanksExercise;
newState.exercises = newState.exercises.map((ex) => newState.exercises = newState.exercises.map((ex) =>
ex.id === exercise.id ? updatedExercise : ex ex.id === exercise.id ? updatedExercise : ex
); );
setLocal((prev) => ({...prev, isPractice: !local.isPractice}))
dispatch({ type: 'UPDATE_SECTION_STATE', payload: { sectionId, update: newState, module: currentModule } }); dispatch({ type: 'UPDATE_SECTION_STATE', payload: { sectionId, update: newState, module: currentModule } });
} }
}); });

View File

@@ -71,6 +71,7 @@ const WriteBlanksForm: React.FC<{ sectionId: number; exercise: WriteBlanksExerci
newState.exercises = newState.exercises.map((ex) => newState.exercises = newState.exercises.map((ex) =>
ex.id === exercise.id ? updatedExercise : ex ex.id === exercise.id ? updatedExercise : ex
); );
setLocal((prev) => ({...prev, isPractice: !local.isPractice}))
dispatch({ type: 'UPDATE_SECTION_STATE', payload: { sectionId, update: newState, module: currentModule } }); dispatch({ type: 'UPDATE_SECTION_STATE', payload: { sectionId, update: newState, module: currentModule } });
} }
}); });

View File

@@ -68,6 +68,7 @@ const Writing: React.FC<Props> = ({ sectionId, exercise, module, index }) => {
...state, ...state,
isPractice: !local.isPractice isPractice: !local.isPractice
}; };
setLocal((prev) => ({...prev, isPractice: !local.isPractice}))
dispatch({ type: 'UPDATE_SECTION_STATE', payload: { sectionId, update: newState, module: currentModule } }); dispatch({ type: 'UPDATE_SECTION_STATE', payload: { sectionId, update: newState, module: currentModule } });
} }
}); });

View File

@@ -7,6 +7,7 @@ import TrueFalse from "../../Exercises/TrueFalse";
import fillBlanks from "./fillBlanks"; import fillBlanks from "./fillBlanks";
import MatchSentences from "../../Exercises/MatchSentences"; import MatchSentences from "../../Exercises/MatchSentences";
import Writing from "../../Exercises/Writing"; import Writing from "../../Exercises/Writing";
import Speaking from "../../Exercises/Speaking";
const getExerciseItems = (exercises: Exercise[], sectionId: number): ExerciseItem[] => { const getExerciseItems = (exercises: Exercise[], sectionId: number): ExerciseItem[] => {
const items: ExerciseItem[] = exercises.map((exercise, index) => { const items: ExerciseItem[] = exercises.map((exercise, index) => {
@@ -78,6 +79,34 @@ const getExerciseItems = (exercises: Exercise[], sectionId: number): ExerciseIte
), ),
content: <Writing key={exercise.id} exercise={exercise} sectionId={sectionId} index={index} module="level" /> content: <Writing key={exercise.id} exercise={exercise} sectionId={sectionId} index={index} module="level" />
}; };
case "speaking":
return {
id: index.toString(),
sectionId,
label: (
<ExerciseLabel
type={`Speaking Section 2`}
firstId={exercise.sectionId!.toString()}
lastId={exercise.sectionId!.toString()}
prompt={exercise.prompts[2]}
/>
),
content: <Speaking key={exercise.id} exercise={exercise} sectionId={sectionId} qId={index} module="level" />
};
case "interactiveSpeaking":
return {
id: index.toString(),
sectionId,
label: (
<ExerciseLabel
type={`Speaking Section 2`}
firstId={exercise.sectionId!.toString()}
lastId={exercise.sectionId!.toString()}
prompt={exercise.prompts[2].text}
/>
),
content: <Speaking key={exercise.id} exercise={exercise} sectionId={sectionId} qId={index} module="level" />
};
default: default:
return {} as unknown as ExerciseItem; return {} as unknown as ExerciseItem;
} }

View File

@@ -181,7 +181,7 @@ const SectionExercises: React.FC<{ sectionId: number; }> = ({ sectionId }) => {
} }
if (currentModule == "writing") return background(<Writing sectionId={sectionId} exercise={currentSection.state as WritingExercise} module="writing" />); if (currentModule == "writing") return background(<Writing sectionId={sectionId} exercise={currentSection.state as WritingExercise} module="writing" />);
if (currentModule == "speaking") return background(<Speaking sectionId={sectionId} exercise={currentSection.state as SpeakingExercise} />); if (currentModule == "speaking") return background(<Speaking sectionId={sectionId} exercise={currentSection.state as SpeakingExercise} module="speaking" />);
const questions = questionItems(); const questions = questionItems();

View File

@@ -18,7 +18,7 @@ const SectionPicker: React.FC<Props> = ({
updateLocalAndScheduleGlobal updateLocalAndScheduleGlobal
}) => { }) => {
const { dispatch } = useExamEditorStore(); const { dispatch } = useExamEditorStore();
const [selectedValue, setSelectedValue] = React.useState<number | null>(null); const [selectedValue, setSelectedValue] = React.useState<number | undefined>(undefined);
const sectionState = useExamEditorStore(state => const sectionState = useExamEditorStore(state =>
state.modules["level"].sections.find((s) => s.sectionId === sectionId) state.modules["level"].sections.find((s) => s.sectionId === sectionId)
@@ -32,14 +32,16 @@ const SectionPicker: React.FC<Props> = ({
const openPicker = module === "reading" ? "isReadingPickerOpen" : "isListeningPickerOpen"; const openPicker = module === "reading" ? "isReadingPickerOpen" : "isListeningPickerOpen";
const handleSectionChange = (value: number) => { const handleSectionChange = (value: number) => {
setSelectedValue(value); const newValue = currentValue === value ? undefined : value;
setSelectedValue(newValue);
dispatch({ dispatch({
type: "UPDATE_SECTION_SINGLE_FIELD", type: "UPDATE_SECTION_SINGLE_FIELD",
payload: { payload: {
sectionId, sectionId,
module: "level", module: "level",
field: module === "reading" ? "readingSection" : "listeningSection", field: module === "reading" ? "readingSection" : "listeningSection",
value: value value: newValue
} }
}); });
}; };
@@ -71,13 +73,15 @@ const SectionPicker: React.FC<Props> = ({
? `bg-ielts-${module}/90 text-white` ? `bg-ielts-${module}/90 text-white`
: `hover:bg-ielts-${module}/70 text-gray-700`} : `hover:bg-ielts-${module}/70 text-gray-700`}
`} `}
onClick={(e) => {
e.preventDefault();
handleSectionChange(num);
}}
> >
<input <input
type="radio" type="checkbox"
name={`${module === "reading" ? 'passage' : 'section'}-${sectionId}`}
value={num}
checked={currentValue === num} checked={currentValue === num}
onChange={() => handleSectionChange(num)} onChange={() => {}}
className={` className={`
h-5 w-5 cursor-pointer h-5 w-5 cursor-pointer
accent-ielts-${module} accent-ielts-${module}

View File

@@ -114,7 +114,7 @@ const LevelSettings: React.FC = () => {
openDetachedTab("popout?type=Exam&module=level", router) openDetachedTab("popout?type=Exam&module=level", router)
} }
const speakingExercise = focusedExercise === undefined ? undefined : currentSection.exercises[focusedExercise] as SpeakingExercise | InteractiveSpeakingExercise; const speakingExercise = focusedExercise === undefined ? undefined : currentSection.exercises.find((ex) => ex.id === focusedExercise.id) as SpeakingExercise | InteractiveSpeakingExercise;
return ( return (
<SettingsEditor <SettingsEditor
@@ -249,7 +249,7 @@ const LevelSettings: React.FC = () => {
</Dropdown> </Dropdown>
{speakingExercise !== undefined && {speakingExercise !== undefined &&
<Dropdown title="Configure Speaking Exercise" className={ <Dropdown title={`Configure Speaking Exercise ${focusedExercise?.questionId}`} className={
clsx( clsx(
"w-full font-semibold flex justify-between items-center p-4 bg-gradient-to-r border", "w-full font-semibold flex justify-between items-center p-4 bg-gradient-to-r border",
"bg-ielts-speaking/70 border-ielts-speaking hover:bg-ielts-speaking", "bg-ielts-speaking/70 border-ielts-speaking hover:bg-ielts-speaking",

View File

@@ -136,6 +136,7 @@ export const defaultSectionSettings = (module: Module, sectionId: number, part?:
state: part !== undefined ? part : defaultSection(module, sectionId), state: part !== undefined ? part : defaultSection(module, sectionId),
generating: undefined, generating: undefined,
genResult: undefined, genResult: undefined,
focusedExercise: undefined,
expandedSubSections: [], expandedSubSections: [],
levelGenerating: [], levelGenerating: [],
levelGenResults: [] levelGenResults: []
@@ -143,7 +144,7 @@ export const defaultSectionSettings = (module: Module, sectionId: number, part?:
} }
const defaultModuleSettings = (module: Module, minTimer: number, states?: SectionState[]): ModuleState => { const defaultModuleSettings = (module: Module, minTimer: number): ModuleState => {
const state: ModuleState = { const state: ModuleState = {
examLabel: defaultExamLabel(module), examLabel: defaultExamLabel(module),
minTimer, minTimer,

View File

@@ -92,7 +92,7 @@ export interface SectionState {
genResult: {generating: string, result: Record<string, any>[], module: Module} | undefined; genResult: {generating: string, result: Record<string, any>[], module: Module} | undefined;
levelGenerating: Generating[]; levelGenerating: Generating[];
levelGenResults: {generating: string, result: Record<string, any>[], module: Module}[]; levelGenResults: {generating: string, result: Record<string, any>[], module: Module}[];
focusedExercise?: number; focusedExercise?: {questionId: number; id: string} | undefined;
writingSection?: number; writingSection?: number;
speakingSection?: number; speakingSection?: number;
readingSection?: number; readingSection?: number;