Files
encoach_frontend/src/components/ExamEditor/Exercises/Speaking/index.tsx

178 lines
6.9 KiB
TypeScript

import { useCallback, useEffect, useState } from "react";
import useExamEditorStore from "@/stores/examEditor";
import { ModuleState } from "@/stores/examEditor/types";
import { SpeakingExercise, InteractiveSpeakingExercise } from "@/interfaces/exam";
import useSectionEdit from "../../Hooks/useSectionEdit";
import Header from "../../Shared/Header";
import GenLoader from "../Shared/GenLoader";
import { Card, CardContent } from "@/components/ui/card";
import AutoExpandingTextArea from "@/components/Low/AutoExpandingTextarea";
interface Props {
sectionId: number;
exercise: SpeakingExercise | InteractiveSpeakingExercise;
}
const Speaking: React.FC<Props> = ({ sectionId, exercise }) => {
const { currentModule, dispatch } = useExamEditorStore();
const { generating, genResult } = useExamEditorStore(
(state) => state.modules[currentModule].sections.find((section) => section.sectionId === sectionId)!
);
const { edit } = useExamEditorStore((store) => store.modules[currentModule]);
const [local, setLocal] = useState(exercise);
const [loading, setLoading] = useState(generating === "context");
const [questions, setQuestions] = useState(() => {
if (sectionId === 1) {
return (exercise as SpeakingExercise).prompts || Array(5).fill("");
} else if (sectionId === 2) {
return [(exercise as SpeakingExercise).text || "", ...(exercise as SpeakingExercise).prompts || Array(3).fill("")];
} else {
return (exercise as InteractiveSpeakingExercise).prompts?.map(p => p.text) || Array(5).fill("");
}
});
const updateModule = useCallback((updates: Partial<ModuleState>) => {
dispatch({ type: 'UPDATE_MODULE', payload: { updates } });
}, [dispatch]);
const { editing, setEditing, handleSave, handleDiscard, modeHandle } = useSectionEdit({
sectionId,
mode: "edit",
onSave: () => {
let newExercise;
if (sectionId === 1) {
newExercise = {
...local,
prompts: questions
} as SpeakingExercise;
} else if (sectionId === 2) {
newExercise = {
...local,
text: questions[0],
prompts: questions.slice(1),
} as SpeakingExercise;
} else {
// Section 3
newExercise = {
...local,
prompts: questions.map(text => ({
text,
video_url: (local as InteractiveSpeakingExercise).prompts?.[0]?.video_url || ""
}))
} as InteractiveSpeakingExercise;
}
setEditing(false);
dispatch({
type: "UPDATE_SECTION_STATE",
payload: {
sectionId: sectionId,
update: newExercise
}
});
},
onDiscard: () => {
setLocal(exercise);
if (sectionId === 1) {
setQuestions((exercise as SpeakingExercise).prompts || Array(5).fill(""));
} else if (sectionId === 2) {
setQuestions([(exercise as SpeakingExercise).text || "", ...(exercise as SpeakingExercise).prompts || Array(3).fill("")]);
} else {
setQuestions((exercise as InteractiveSpeakingExercise).prompts?.map(p => p.text) || Array(5).fill(""));
}
},
});
useEffect(() => {
const isLoading = generating === "context";
setLoading(isLoading);
if (isLoading) {
updateModule({ edit: Array.from(new Set([...edit, sectionId])) });
}
}, [generating, sectionId]);
useEffect(() => {
if (genResult && generating === "context") {
setEditing(true);
if (sectionId === 1) {
setQuestions(genResult[0].questions);
} else if (sectionId === 2) {
setQuestions([genResult[0].question, ...genResult[0].prompts]);
} else {
setQuestions(genResult[0].questions);
}
dispatch({
type: "UPDATE_SECTION_SINGLE_FIELD",
payload: {
sectionId,
module: currentModule,
field: "genResult",
value: undefined
}
});
}
}, [genResult, generating, dispatch, sectionId, setEditing, currentModule]);
const handleQuestionChange = (index: number, value: string) => {
setQuestions(prev => {
const newQuestions = [...prev];
newQuestions[index] = value;
return newQuestions;
});
};
const getQuestionLabel = (index: number) => {
if (sectionId === 2 && index === 0) {
return "Main Question";
} else if (sectionId === 2) {
return `Prompt ${index}`;
} else {
return `Question ${index + 1}`;
}
};
return (
<>
<div className='relative pb-4'>
<Header
title={`Speaking ${sectionId} Script`}
description='Generate or write the script for the video.'
editing={editing}
handleSave={handleSave}
modeHandle={modeHandle}
handleDiscard={handleDiscard}
/>
</div>
{loading ? (
<GenLoader module={currentModule} />
) : (
<div className="mx-auto p-3 space-y-6">
<Card>
<CardContent>
<div className="p-4">
<div className="flex flex-col space-y-6">
{questions.map((question: string, index: number) => (
<div key={index} className="flex flex-col">
<h2 className="font-semibold my-2">{getQuestionLabel(index)}</h2>
<AutoExpandingTextArea
value={question}
onChange={(text) => handleQuestionChange(index, text)}
className="w-full p-3 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-transparent min-h-[80px] transition-all"
placeholder={`Enter ${getQuestionLabel(index).toLowerCase()}`}
/>
</div>
))}
</div>
</div>
</CardContent>
</Card>
</div>
)}
</>
);
};
export default Speaking;