Forgot to stage it

This commit is contained in:
Carlos-Mesquita
2024-11-09 06:53:17 +00:00
parent 50481a836e
commit c507eae507
12 changed files with 887 additions and 189 deletions

View File

@@ -0,0 +1,222 @@
import AutoExpandingTextArea from "@/components/Low/AutoExpandingTextarea";
import { Card, CardContent } from "@/components/ui/card";
import { BiQuestionMark } from 'react-icons/bi';
import { AiOutlineUnorderedList, AiOutlinePlus, AiOutlineDelete } from 'react-icons/ai';
import { Tooltip } from "react-tooltip";
import Header from "../../Shared/Header";
import GenLoader from "../Shared/GenLoader";
import { useEffect, useState } from "react";
import useSectionEdit from "../../Hooks/useSectionEdit";
import useExamEditorStore from "@/stores/examEditor";
import { InteractiveSpeakingExercise } from "@/interfaces/exam";
import { BsFileText } from "react-icons/bs";
interface Props {
sectionId: number;
exercise: InteractiveSpeakingExercise;
}
const InteractiveSpeaking: React.FC<Props> = ({ sectionId, exercise }) => {
const { currentModule, dispatch } = useExamEditorStore();
const [local, setLocal] = useState(exercise);
const { generating, genResult } = useExamEditorStore(
(state) => state.modules[currentModule].sections.find((section) => section.sectionId === sectionId)!
);
const { editing, setEditing, handleSave, handleDiscard, modeHandle } = useSectionEdit({
sectionId,
mode: "edit",
onSave: () => {
setEditing(false);
dispatch({ type: "UPDATE_SECTION_STATE", payload: { sectionId: sectionId, update: local } });
},
onDiscard: () => {
setLocal(exercise);
},
onMode: () => { },
});
useEffect(() => {
if (genResult && generating === "context") {
setEditing(true);
setLocal({
...local,
title: genResult[0].title,
prompts: genResult[0].prompts.map((item: any) => ({
text: item || "",
video_url: ""
}))
});
dispatch({
type: "UPDATE_SECTION_SINGLE_FIELD",
payload: {
sectionId,
module: currentModule,
field: "genResult",
value: undefined
}
});
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [genResult, generating]);
const addPrompt = () => {
setLocal(prev => ({
...prev,
prompts: [...prev.prompts, { text: "", video_url: "" }]
}));
};
const removePrompt = (index: number) => {
setLocal(prev => ({
...prev,
prompts: prev.prompts.filter((_, i) => i !== index)
}));
};
const updatePrompt = (index: number, text: string) => {
setLocal(prev => {
const newPrompts = [...prev.prompts];
newPrompts[index] = { ...newPrompts[index], text };
return { ...prev, prompts: newPrompts };
});
};
const isUnedited = local.prompts.length === 0;
return (
<>
<div className='relative pb-4'>
<Header
title={`Interactive Speaking Script`}
description='Generate or write the scripts for the videos.'
editing={editing}
handleSave={handleSave}
modeHandle={modeHandle}
handleDiscard={handleDiscard}
mode="edit"
module="speaking"
/>
</div>
{generating ? (
<GenLoader module={currentModule} />
) : (
<>
{editing ? (
<>
<Card>
<CardContent>
<div className="flex flex-col py-2 mt-2">
<h2 className="font-semibold text-xl mb-2">Title</h2>
<AutoExpandingTextArea
value={local.title || ''}
onChange={(text) => setLocal(prev => ({ ...prev, title: 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 the title"
/>
</div>
</CardContent>
</Card>
<Card>
<CardContent>
<div className="flex items-center mb-4 mt-6">
<h2 className="font-semibold text-xl">Questions</h2>
</div>
<div className="space-y-5">
{local.prompts.length === 0 ? (
<div className="py-12 text-center bg-gray-200 rounded-lg border-2 border-dashed border-gray-400">
<p className="text-gray-600">No questions added yet</p>
</div>
) : (
local.prompts.map((prompt, index) => (
<Card key={index}>
<CardContent>
<div className="bg-gray-50 rounded-lg pt-4">
<div className="flex justify-between items-center mb-3">
<h3 className="font-medium text-gray-700">Question {index + 1}</h3>
<button
type="button"
className="p-1.5 text-gray-400 hover:text-red-500 hover:bg-red-50 rounded-full transition-colors"
onClick={() => removePrompt(index)}
>
<AiOutlineDelete className="h-5 w-5" />
</button>
</div>
<AutoExpandingTextArea
value={prompt.text}
onChange={(text) => updatePrompt(index, text)}
className="w-full p-3 border border-gray-200 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-transparent min-h-[80px] transition-all bg-white"
placeholder={`Enter question ${index + 1}`}
/>
</div>
</CardContent>
</Card>
))
)}
</div>
<div className="mt-6">
<button
type="button"
onClick={addPrompt}
className="w-full py-3 px-4 bg-gray-50 border border-gray-200 rounded-lg hover:bg-gray-100 transition-colors flex items-center justify-center gap-2 text-gray-600 font-medium"
>
<AiOutlinePlus className="h-5 w-5" />
Add Question
</button>
</div>
</CardContent>
</Card>
</>
) : isUnedited ? (
<p className="w-full text-gray-600 px-7 py-8 border-2 bg-white rounded-3xl whitespace-pre-line">
Generate or edit the questions!
</p>
) : (
<div className="space-y-6">
<Card>
<CardContent className="pt-6">
<div className="flex flex-col items-start gap-3">
<div className="flex flex-row mb-3 gap-4">
<BsFileText className="h-5 w-5 text-blue-500 mt-1" />
<h3 className="font-semibold text-xl">Title</h3>
</div>
<div className="w-full px-4 py-3 bg-white shadow-inner rounded-lg border border-gray-100">
<p className="text-lg">{local.title || 'Untitled'}</p>
</div>
</div>
</CardContent>
</Card>
<Card>
<CardContent className="pt-6">
<div className="flex flex-col items-start gap-3">
<div className="flex flex-row mb-3 gap-4">
<AiOutlineUnorderedList className="h-5 w-5 text-purple-500 mt-1" />
<h3 className="font-semibold text-xl">Questions</h3>
</div>
<div className="w-full space-y-4">
{local.prompts
.filter(prompt => prompt.text !== "")
.map((prompt, index) => (
<div key={index} className="bg-white shadow-inner rounded-lg border border-gray-100 p-4">
<h4 className="font-medium text-gray-700 mb-2">Question {index + 1}</h4>
<p className="text-gray-700">{prompt.text}</p>
</div>
))
}
</div>
</div>
</CardContent>
</Card>
</div>
)}
</>
)}
</>
);
};
export default InteractiveSpeaking;

View File

@@ -0,0 +1,280 @@
import React, { useEffect, useState } from 'react';
import AutoExpandingTextArea from "@/components/Low/AutoExpandingTextarea";
import { Card, CardContent } from "@/components/ui/card";
import { AiOutlineUnorderedList, AiOutlinePlus, AiOutlineDelete } from 'react-icons/ai';
import Header from "../../Shared/Header";
import GenLoader from "../Shared/GenLoader";
import useSectionEdit from "../../Hooks/useSectionEdit";
import useExamEditorStore from "@/stores/examEditor";
import { InteractiveSpeakingExercise } from "@/interfaces/exam";
import { BsFileText } from "react-icons/bs";
interface Props {
sectionId: number;
exercise: InteractiveSpeakingExercise;
}
const Speaking1: React.FC<Props> = ({ sectionId, exercise }) => {
const { currentModule, dispatch } = useExamEditorStore();
const [local, setLocal] = useState(() => {
const defaultPrompts = [
{ text: "Hello my name is {avatar}, what is yours?", video_url: "" },
{ text: "Do you work or do you study?", video_url: "" },
...exercise.prompts.slice(2)
];
return { ...exercise, prompts: defaultPrompts };
});
const updateAvatarName = (avatarName: string) => {
setLocal(prev => {
const updatedPrompts = [...prev.prompts];
updatedPrompts[0] = {
...updatedPrompts[0],
text: updatedPrompts[0].text.replace("{avatar}", avatarName)
};
return { ...prev, prompts: updatedPrompts };
});
};
const { generating, genResult } = useExamEditorStore(
(state) => state.modules[currentModule].sections.find((section) => section.sectionId === sectionId)!
);
const { editing, setEditing, handleSave, handleDiscard, modeHandle } = useSectionEdit({
sectionId,
mode: "edit",
onSave: () => {
setEditing(false);
dispatch({ type: "UPDATE_SECTION_STATE", payload: { sectionId: sectionId, update: local } });
},
onDiscard: () => {
setLocal({
...exercise,
prompts: [
{ text: "Hello my name is {avatar}, what is yours?", video_url: "" },
{ text: "Do you work or do you study?", video_url: "" },
...exercise.prompts.slice(2)
]
});
},
onMode: () => { },
});
useEffect(() => {
if (genResult && generating === "context") {
setEditing(true);
setLocal(prev => ({
...prev,
first_title: genResult[0].first_topic,
second_title: genResult[0].second_topic,
prompts: [
prev.prompts[0],
prev.prompts[1],
...genResult[0].prompts.map((item: any) => ({
text: item,
video_url: ""
}))
]
}));
dispatch({
type: "UPDATE_SECTION_SINGLE_FIELD",
payload: {
sectionId,
module: currentModule,
field: "genResult",
value: undefined
}
});
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [genResult, generating]);
const addPrompt = () => {
setLocal(prev => ({
...prev,
prompts: [...prev.prompts, { text: "", video_url: "" }]
}));
};
const removePrompt = (index: number) => {
if (index < 2) return;
setLocal(prev => ({
...prev,
prompts: prev.prompts.filter((_, i) => i !== index)
}));
};
const updatePrompt = (index: number, text: string) => {
if (index < 2) return;
setLocal(prev => {
const newPrompts = [...prev.prompts];
newPrompts[index] = { ...newPrompts[index], text };
return { ...prev, prompts: newPrompts };
});
};
const isUnedited = local.prompts.length === 2;
return (
<>
<div className='relative pb-4'>
<Header
title={`Speaking 1 Script`}
description='Generate or write the scripts for the videos.'
editing={editing}
handleSave={handleSave}
modeHandle={modeHandle}
handleDiscard={handleDiscard}
mode="edit"
module="speaking"
/>
</div>
{generating ? (
<GenLoader module={currentModule} />
) : (
<>
{editing ? (
<>
<Card>
<CardContent>
<div className="py-2 mt-2">
<div className="flex flex-row mb-3 gap-4">
<BsFileText className="h-5 w-5 text-blue-500 mt-1" />
<h3 className="font-semibold text-xl">Titles</h3>
</div>
<div className="flex gap-6 mt-6">
<div className="flex-1">
<h2 className="font-semibold text-lg mb-2">First Title</h2>
<AutoExpandingTextArea
value={local.first_title || ''}
onChange={(text) => setLocal(prev => ({ ...prev, first_title: 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 the first title"
/>
</div>
<div className="flex-1">
<h2 className="font-semibold text-lg mb-2">Second Title</h2>
<AutoExpandingTextArea
value={local.second_title || ''}
onChange={(text) => setLocal(prev => ({ ...prev, second_title: 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 the second title"
/>
</div>
</div>
</div>
</CardContent>
</Card>
<Card>
<CardContent>
<div className="flex items-center justify-between mb-4 mt-6">
<h2 className="font-semibold text-xl">Questions</h2>
</div>
<div className="space-y-5">
{local.prompts.length === 2 ? (
<div className="py-12 text-center bg-gray-200 rounded-lg border-2 border-dashed border-gray-400">
<p className="text-gray-600">No questions added yet</p>
</div>
) : (
local.prompts.slice(2).map((prompt, index) => (
<Card key={index}>
<CardContent>
<div className="bg-gray-50 rounded-lg pt-4">
<div className="flex justify-between items-center mb-3">
<h3 className="font-medium text-gray-700">Question {index + 1}</h3>
<button
type="button"
className="p-1.5 text-gray-400 hover:text-red-500 hover:bg-red-50 rounded-full transition-colors"
onClick={() => removePrompt(index + 2)}
>
<AiOutlineDelete className="h-5 w-5" />
</button>
</div>
<AutoExpandingTextArea
value={prompt.text}
onChange={(text) => updatePrompt(index + 2, text)}
className="w-full p-3 border border-gray-200 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-transparent min-h-[80px] transition-all bg-white"
placeholder={`Enter question ${index + 1}`}
/>
</div>
</CardContent>
</Card>
))
)}
</div>
<div className="mt-6">
<button
type="button"
onClick={addPrompt}
className="w-full py-3 px-4 bg-gray-50 border border-gray-200 rounded-lg hover:bg-gray-100 transition-colors flex items-center justify-center gap-2 text-gray-600 font-medium"
>
<AiOutlinePlus className="h-5 w-5" />
Add Question
</button>
</div>
</CardContent>
</Card>
</>
) : isUnedited ? (
<p className="w-full text-gray-600 px-7 py-8 border-2 bg-white rounded-3xl whitespace-pre-line">
Generate or edit the questions!
</p>
) : (
<div className="space-y-6">
<Card>
<CardContent className="pt-6">
<div className="flex flex-col items-start">
<div className="flex flex-row mb-4 gap-4">
<BsFileText className="h-5 w-5 text-blue-500 mt-1" />
<h3 className="font-semibold text-xl">Titles</h3>
</div>
<div className="w-full flex gap-6 mt-6">
<div className="flex-1">
<h4 className="font-medium text-gray-700 mb-2">First Title</h4>
<div className="w-full px-4 py-3 bg-white shadow-inner rounded-lg border border-gray-100">
<p className="text-lg">{local.first_title || 'No first title'}</p>
</div>
</div>
<div className="flex-1">
<h4 className="font-medium text-gray-700 mb-2">Second Title</h4>
<div className="w-full px-4 py-3 bg-white shadow-inner rounded-lg border border-gray-100">
<p className="text-lg">{local.second_title || 'No second title'}</p>
</div>
</div>
</div>
</div>
</CardContent>
</Card>
<Card>
<CardContent className="pt-6">
<div className="flex flex-col items-start gap-3">
<div className="flex flex-row mb-3 gap-4">
<AiOutlineUnorderedList className="h-5 w-5 text-purple-500 mt-1" />
<h3 className="font-semibold text-xl">Questions</h3>
</div>
<div className="w-full space-y-4">
{local.prompts.slice(2)
.filter(prompt => prompt.text !== "")
.map((prompt, index) => (
<div key={index} className="bg-white shadow-inner rounded-lg border border-gray-100 p-4">
<h4 className="font-medium text-gray-700 mb-2">Question {index + 1}</h4>
<p className="text-gray-700">{prompt.text}</p>
</div>
))
}
</div>
</div>
</CardContent>
</Card>
</div>
)}
</>
)}
</>
);
};
export default Speaking1;

View File

@@ -0,0 +1,280 @@
import AutoExpandingTextArea from "@/components/Low/AutoExpandingTextarea";
import { Card, CardContent } from "@/components/ui/card";
import { AiOutlinePlus, AiOutlineDelete } from 'react-icons/ai';
import { SpeakingExercise } from "@/interfaces/exam";
import useExamEditorStore from "@/stores/examEditor";
import { useEffect, useState } from "react";
import useSectionEdit from "../../Hooks/useSectionEdit";
import Header from "../../Shared/Header";
import { Tooltip } from "react-tooltip";
import { BsFileText } from 'react-icons/bs';
import { AiOutlineUnorderedList } from 'react-icons/ai';
import { BiQuestionMark, BiMessageRoundedDetail } from "react-icons/bi";
import GenLoader from "../Shared/GenLoader";
interface Props {
sectionId: number;
exercise: SpeakingExercise;
}
const Speaking2: React.FC<Props> = ({ sectionId, exercise }) => {
const { currentModule, dispatch } = useExamEditorStore();
const [local, setLocal] = useState(exercise);
const { generating, genResult } = useExamEditorStore(
(state) => state.modules[currentModule].sections.find((section) => section.sectionId === sectionId)!
);
const updateTopic = (topic: string) => {
setLocal(prev => ({ ...prev, topic: topic }));
dispatch({ type: "UPDATE_SECTION_STATE", payload: { sectionId, update: { topic: topic } } });
};
const { editing, setEditing, handleSave, handleDiscard, modeHandle } = useSectionEdit({
sectionId,
mode: "edit",
onSave: () => {
setEditing(false);
dispatch({ type: "UPDATE_SECTION_STATE", payload: { sectionId: sectionId, update: local } });
},
onDiscard: () => {
setLocal(exercise);
},
onMode: () => { },
});
useEffect(() => {
if (genResult && generating === "context") {
setEditing(true);
setLocal({
...local,
title: genResult[0].topic,
text: genResult[0].question,
prompts: genResult[0].prompts
});
dispatch({
type: "UPDATE_SECTION_SINGLE_FIELD",
payload: {
sectionId,
module: currentModule,
field: "genResult",
value: undefined
}
});
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [genResult, generating]);
const addPrompt = () => {
setLocal(prev => ({
...prev,
prompts: [...prev.prompts, ""]
}));
};
const removePrompt = (index: number) => {
setLocal(prev => ({
...prev,
prompts: prev.prompts.filter((_, i) => i !== index)
}));
};
const updatePrompt = (index: number, text: string) => {
setLocal(prev => {
const newPrompts = [...prev.prompts];
newPrompts[index] = text;
return { ...prev, prompts: newPrompts };
});
};
const isUnedited = local.text === "" ||
(local.title === undefined || local.title === "") ||
local.prompts.length === 0;
const tooltipContent = `
<div class='p-2 max-w-xs'>
<p class='text-sm text-white'>
Prompts are guiding points that help candidates structure their talk. They typically include aspects like:
<ul class='list-disc pl-4 mt-1'>
<li>Describing what/who/where</li>
<li>Explaining why</li>
<li>Sharing feelings or preferences</li>
</ul>
</p>
</div>
`;
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}
mode="edit"
module="speaking"
/>
</div>
{generating ? (
<GenLoader module={currentModule} />
) : (
<>
{editing ? (
<>
<Card>
<CardContent>
<div className="flex flex-col py-2 mt-2">
<h2 className="font-semibold text-xl mb-2">Title</h2>
<AutoExpandingTextArea
value={local.title || ''}
onChange={(text) => setLocal(prev => ({ ...prev, title: 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 the topic"
/>
</div>
</CardContent>
</Card>
<Card>
<CardContent>
<div className="flex flex-col py-2 mt-2">
<h2 className="font-semibold text-xl mb-2">Question</h2>
<AutoExpandingTextArea
value={local.text}
onChange={(text) => setLocal(prev => ({ ...prev, text: 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 the main question"
/>
</div>
</CardContent>
</Card>
<Card>
<CardContent>
<div className="flex items-center justify-between mb-4 mt-6">
<h2 className="font-semibold text-xl">Prompts</h2>
<Tooltip id="prompt-tp" />
<a
data-tooltip-id="prompt-tp"
data-tooltip-html={tooltipContent}
className='ml-1 w-6 h-6 flex items-center justify-center rounded-full hover:bg-gray-200 border bg-gray-100'
>
<BiQuestionMark
className="w-5 h-5 text-gray-500"
/>
</a>
</div>
<div className="space-y-5">
{local.prompts.length === 0 ? (
<div className="py-12 text-center bg-gray-200 rounded-lg border-2 border-dashed border-gray-400">
<p className="text-gray-600">No prompts added yet</p>
</div>
) : (
local.prompts.map((prompt, index) => (
<Card key={index}>
<CardContent>
<div className="bg-gray-50 rounded-lg pt-4">
<div className="flex justify-between items-center mb-3">
<h3 className="font-medium text-gray-700">Prompt {index + 1}</h3>
<button
type="button"
className="p-1.5 text-gray-400 hover:text-red-500 hover:bg-red-50 rounded-full transition-colors"
onClick={() => removePrompt(index)}
>
<AiOutlineDelete className="h-5 w-5" />
</button>
</div>
<AutoExpandingTextArea
value={prompt}
onChange={(text) => updatePrompt(index, text)}
className="w-full p-3 border border-gray-200 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-transparent min-h-[80px] transition-all bg-white"
placeholder={`Enter prompt ${index + 1}`}
/>
</div>
</CardContent>
</Card>
))
)}
</div>
<div className="mt-6">
<button
type="button"
onClick={addPrompt}
className="w-full py-3 px-4 bg-gray-50 border border-gray-200 rounded-lg hover:bg-gray-100 transition-colors flex items-center justify-center gap-2 text-gray-600 font-medium"
>
<AiOutlinePlus className="h-5 w-5" />
Add Prompt
</button>
</div>
</CardContent>
</Card>
</>
) : isUnedited ? (
<p className="w-full text-gray-600 px-7 py-8 border-2 bg-white rounded-3xl whitespace-pre-line">
Generate or edit the script!
</p>
) : (
<div className="space-y-6">
<Card>
<CardContent className="pt-6">
<div className="flex flex-col items-start gap-3">
<div className="flex flex-row mb-3 gap-4">
<BsFileText className="h-5 w-5 text-blue-500 mt-1" />
<h3 className="font-semibold text-xl">Title</h3>
</div>
<div className="w-full px-4 py-3 bg-white shadow-inner rounded-lg border border-gray-100">
<p className="text-lg">{local.title || 'Untitled'}</p>
</div>
</div>
</CardContent>
</Card>
<Card>
<CardContent className="pt-6">
<div className="flex flex-col items-start gap-3">
<div className="flex flex-row mb-3 gap-4">
<BiMessageRoundedDetail className="h-5 w-5 text-green-500 mt-1" />
<h3 className="font-semibold text-xl">Question</h3>
</div>
<div className="w-full px-4 py-3 bg-white shadow-inner rounded-lg border border-gray-100">
<p className="text-lg">{local.text || 'No question provided'}</p>
</div>
</div>
</CardContent>
</Card>
{local.prompts && local.prompts.length > 0 && (
<Card>
<CardContent className="pt-6">
<div className="flex flex-col items-start gap-3">
<div className="flex flex-row mb-3 gap-4">
<AiOutlineUnorderedList className="h-5 w-5 text-purple-500 mt-1" />
<h3 className="font-semibold text-xl">Prompts</h3>
</div>
<div className="w-full p-4 bg-gray-50 shadow-inner rounded-lg border border-gray-100">
<div className="flex flex-col gap-3">
{local.prompts.map((prompt, index) => (
<div key={index} className="px-4 py-3 bg-white shadow rounded-lg border border-gray-100">
<p className="text-gray-700">{prompt}</p>
</div>
))}
</div>
</div>
</div>
</CardContent>
</Card>
)}
</div>
)}
</>
)}
</>
);
}
export default Speaking2;

View File

@@ -7,6 +7,9 @@ import Header from "../../Shared/Header";
import GenLoader from "../Shared/GenLoader";
import { Card, CardContent } from "@/components/ui/card";
import AutoExpandingTextArea from "@/components/Low/AutoExpandingTextarea";
import Speaking2 from "./Speaking2";
import InteractiveSpeaking from "./InteractiveSpeaking";
import Speaking1 from "./Speaking1";
interface Props {
sectionId: number;
@@ -14,176 +17,24 @@ interface Props {
}
const Speaking: React.FC<Props> = ({ sectionId, exercise }) => {
const { currentModule, dispatch } = useExamEditorStore();
const { generating, genResult } = useExamEditorStore(
const { currentModule } = useExamEditorStore();
const { state } = 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) {
if ((exercise as SpeakingExercise).prompts.length > 0) {
return (exercise as SpeakingExercise).prompts;
}
return Array(5).fill("");
} else if (sectionId === 2) {
if ((exercise as SpeakingExercise).text && (exercise as SpeakingExercise).prompts.length > 0) {
return (exercise as SpeakingExercise).prompts;
}
return Array(3).fill("");
} else {
if ((exercise as InteractiveSpeakingExercise).prompts.length > 0) {
return (exercise as InteractiveSpeakingExercise).prompts?.map(p => p.text);
}
return 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 {
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);
},
onMode: () => {
setLocal(exercise);
if (sectionId === 1) {
setQuestions(Array(5).fill(""));
} else if (sectionId === 2) {
setQuestions(Array(3).fill(""));
} else {
setQuestions(Array(5).fill(""));
}
},
});
useEffect(() => {
const isLoading = generating === "context";
setLoading(isLoading);
if (isLoading) {
updateModule({ edit: Array.from(new Set([...edit, sectionId])) });
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [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 className="mx-auto p-3 space-y-6">
<div className="p-4">
<div className="flex flex-col space-y-6">
{sectionId === 1 && <Speaking1 sectionId={sectionId} exercise={state as InteractiveSpeakingExercise} />}
{sectionId === 2 && <Speaking2 sectionId={sectionId} exercise={state as SpeakingExercise} />}
{sectionId === 3 && <InteractiveSpeaking sectionId={sectionId} exercise={state as InteractiveSpeakingExercise} />}
</div>
</div>
)}
</div>
</>
);
};
export default Speaking;
export default Speaking;