create speaking set before question step
Ensure the speaking question set is created when moving from setup to questions, rename Set status to Status, and default new set status to PUBLISHED. Made-with: Cursor
This commit is contained in:
parent
7b08b228df
commit
85df446a66
|
|
@ -136,7 +136,9 @@ export function SpeakingPage() {
|
||||||
const [subCourseLoading, setSubCourseLoading] = useState(false)
|
const [subCourseLoading, setSubCourseLoading] = useState(false)
|
||||||
const [subCourseSearch, setSubCourseSearch] = useState("")
|
const [subCourseSearch, setSubCourseSearch] = useState("")
|
||||||
const [subCourseMenuOpen, setSubCourseMenuOpen] = useState(false)
|
const [subCourseMenuOpen, setSubCourseMenuOpen] = useState(false)
|
||||||
const [setStatus, setSetStatus] = useState<"DRAFT" | "PUBLISHED">("DRAFT")
|
const [setStatus, setSetStatus] = useState<"DRAFT" | "PUBLISHED">("PUBLISHED")
|
||||||
|
const [createdSetId, setCreatedSetId] = useState<number | null>(null)
|
||||||
|
const [creatingSet, setCreatingSet] = useState(false)
|
||||||
const [currentStep, setCurrentStep] = useState(1)
|
const [currentStep, setCurrentStep] = useState(1)
|
||||||
const [detailOpen, setDetailOpen] = useState(false)
|
const [detailOpen, setDetailOpen] = useState(false)
|
||||||
const [detailLoading, setDetailLoading] = useState(false)
|
const [detailLoading, setDetailLoading] = useState(false)
|
||||||
|
|
@ -399,10 +401,48 @@ export function SpeakingPage() {
|
||||||
setSetDescription("")
|
setSetDescription("")
|
||||||
setIntroVideoUrl("")
|
setIntroVideoUrl("")
|
||||||
setSubCourseId("")
|
setSubCourseId("")
|
||||||
setSetStatus("DRAFT")
|
setSetStatus("PUBLISHED")
|
||||||
|
setCreatedSetId(null)
|
||||||
setQuestionDrafts([createEmptyDraft()])
|
setQuestionDrafts([createEmptyDraft()])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleProceedToQuestions = async () => {
|
||||||
|
if (!canProceedToQuestions) return
|
||||||
|
if (createdSetId) {
|
||||||
|
setCurrentStep(2)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const parsedSubCourseId = Number(subCourseId)
|
||||||
|
if (!Number.isFinite(parsedSubCourseId) || parsedSubCourseId <= 0) {
|
||||||
|
toast.error("Please select a valid sub-course")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setCreatingSet(true)
|
||||||
|
try {
|
||||||
|
const setRes = await createQuestionSet({
|
||||||
|
title: setTitle.trim(),
|
||||||
|
...(setDescription.trim() ? { description: setDescription.trim() } : {}),
|
||||||
|
set_type: "PRACTICE",
|
||||||
|
owner_type: "SUB_COURSE",
|
||||||
|
owner_id: parsedSubCourseId,
|
||||||
|
status: setStatus,
|
||||||
|
...(introVideoUrl.trim() ? { intro_video_url: introVideoUrl.trim() } : {}),
|
||||||
|
})
|
||||||
|
const setId = setRes.data?.data?.id
|
||||||
|
if (!setId) throw new Error("Question set creation failed: missing set ID")
|
||||||
|
setCreatedSetId(setId)
|
||||||
|
setCurrentStep(2)
|
||||||
|
toast.success("Practice created. Continue adding questions.")
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to create speaking practice set:", error)
|
||||||
|
toast.error("Failed to create practice set")
|
||||||
|
} finally {
|
||||||
|
setCreatingSet(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const handleIntroVideoFileChange = async (event: ChangeEvent<HTMLInputElement>) => {
|
const handleIntroVideoFileChange = async (event: ChangeEvent<HTMLInputElement>) => {
|
||||||
const file = event.target.files?.[0]
|
const file = event.target.files?.[0]
|
||||||
event.target.value = ""
|
event.target.value = ""
|
||||||
|
|
@ -844,18 +884,7 @@ export function SpeakingPage() {
|
||||||
|
|
||||||
setSaving(true)
|
setSaving(true)
|
||||||
try {
|
try {
|
||||||
// 1) Create speaking practice set.
|
const setId = createdSetId
|
||||||
const setRes = await createQuestionSet({
|
|
||||||
title: setTitle.trim(),
|
|
||||||
...(setDescription.trim() ? { description: setDescription.trim() } : {}),
|
|
||||||
set_type: "PRACTICE",
|
|
||||||
owner_type: "SUB_COURSE",
|
|
||||||
owner_id: parsedSubCourseId,
|
|
||||||
status: setStatus,
|
|
||||||
...(introVideoUrl.trim() ? { intro_video_url: introVideoUrl.trim() } : {}),
|
|
||||||
})
|
|
||||||
|
|
||||||
const setId = setRes.data?.data?.id
|
|
||||||
if (!setId) throw new Error("Question set creation failed: missing set ID")
|
if (!setId) throw new Error("Question set creation failed: missing set ID")
|
||||||
|
|
||||||
// 2) Create all AUDIO questions then attach in sequence.
|
// 2) Create all AUDIO questions then attach in sequence.
|
||||||
|
|
@ -1039,6 +1068,7 @@ export function SpeakingPage() {
|
||||||
<Button
|
<Button
|
||||||
className="h-11 w-full shrink-0 bg-brand-500 px-5 shadow-sm hover:bg-brand-600 sm:w-auto"
|
className="h-11 w-full shrink-0 bg-brand-500 px-5 shadow-sm hover:bg-brand-600 sm:w-auto"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
resetCreateForm()
|
||||||
setOpenCreate(true)
|
setOpenCreate(true)
|
||||||
setCurrentStep(1)
|
setCurrentStep(1)
|
||||||
}}
|
}}
|
||||||
|
|
@ -1487,7 +1517,10 @@ export function SpeakingPage() {
|
||||||
type="button"
|
type="button"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className="h-10 w-full shrink-0 border-grayScale-200 text-grayScale-700 sm:w-auto"
|
className="h-10 w-full shrink-0 border-grayScale-200 text-grayScale-700 sm:w-auto"
|
||||||
onClick={() => setOpenCreate(false)}
|
onClick={() => {
|
||||||
|
resetCreateForm()
|
||||||
|
setOpenCreate(false)
|
||||||
|
}}
|
||||||
disabled={saving}
|
disabled={saving}
|
||||||
>
|
>
|
||||||
<ArrowLeft className="h-4 w-4" />
|
<ArrowLeft className="h-4 w-4" />
|
||||||
|
|
@ -1641,7 +1674,7 @@ export function SpeakingPage() {
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-1.5">
|
<div className="space-y-1.5">
|
||||||
<label className="text-sm font-medium text-grayScale-700">Set status</label>
|
<label className="text-sm font-medium text-grayScale-700">Status</label>
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
<Button
|
<Button
|
||||||
|
|
@ -1669,15 +1702,23 @@ export function SpeakingPage() {
|
||||||
</aside>
|
</aside>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-8 flex flex-col-reverse gap-3 border-t border-grayScale-100 bg-grayScale-50/30 px-0 py-4 sm:flex-row sm:justify-end sm:px-0 sm:py-5">
|
<div className="mt-8 flex flex-col-reverse gap-3 border-t border-grayScale-100 bg-grayScale-50/30 px-0 py-4 sm:flex-row sm:justify-end sm:px-0 sm:py-5">
|
||||||
<Button variant="outline" onClick={() => setOpenCreate(false)} disabled={saving} className="sm:w-auto">
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => {
|
||||||
|
resetCreateForm()
|
||||||
|
setOpenCreate(false)
|
||||||
|
}}
|
||||||
|
disabled={saving}
|
||||||
|
className="sm:w-auto"
|
||||||
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
className="bg-brand-500 hover:bg-brand-600 sm:min-w-[180px]"
|
className="bg-brand-500 hover:bg-brand-600 sm:min-w-[180px]"
|
||||||
onClick={() => setCurrentStep(2)}
|
onClick={handleProceedToQuestions}
|
||||||
disabled={!canProceedToQuestions}
|
disabled={!canProceedToQuestions || creatingSet}
|
||||||
>
|
>
|
||||||
Next: Questions
|
{creatingSet ? "Creating..." : "Next: Questions"}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user