178 lines
6.9 KiB
TypeScript
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;
|