diff --git a/src/api/courses.api.ts b/src/api/courses.api.ts index 786d47c..3542ae5 100644 --- a/src/api/courses.api.ts +++ b/src/api/courses.api.ts @@ -100,6 +100,7 @@ import type { PublishExamPrepModuleLessonRequest, CreateExamPrepLessonPracticeRequest, CreateExamPrepLessonPracticeResponse, + GetExamPrepLessonPracticesResponse, GetExamPrepModuleLessonsResponse, GetTopLevelModuleLessonsResponse, GetPracticesByParentContextResponse, @@ -610,6 +611,22 @@ export const createExamPrepLessonPractice = ( data, ) +/** GET /exam-prep/lessons/:lessonId/practices */ +export const getExamPrepLessonPractices = ( + lessonId: number, + params?: { limit?: number; offset?: number }, +) => + http.get( + `/exam-prep/lessons/${lessonId}/practices`, + { params }, + ) + +/** DELETE /exam-prep/practices/:practiceId */ +export const deleteExamPrepPractice = (practiceId: number) => + http.delete<{ message: string; success: boolean; status_code: number; metadata: unknown }>( + `/exam-prep/practices/${practiceId}`, + ) + /** Top-level course resource (Learn English track) — PUT /courses/:id */ export const updateTopLevelCourse = (courseId: number, data: UpdateTopLevelCourseRequest) => http.put(`/courses/${courseId}`, data) diff --git a/src/lib/learnEnglishDefinitionQuestion.ts b/src/lib/learnEnglishDefinitionQuestion.ts index 0142592..23d89d0 100644 --- a/src/lib/learnEnglishDefinitionQuestion.ts +++ b/src/lib/learnEnglishDefinitionQuestion.ts @@ -160,10 +160,14 @@ export function legacyQuestionTypeFromDefinition( return null } +export type QuestionDifficultyLevel = "EASY" | "MEDIUM" | "HARD" + export interface LearnEnglishDefinitionQuestionInput { questionText: string questionTypeDefinitionId: number dynamicFieldValues: Record + difficultyLevel?: QuestionDifficultyLevel + points?: number mcqOptions?: { option_text: string; is_correct: boolean }[] trueFalseAnswerIsTrue?: boolean shortAnswers?: string[] @@ -269,13 +273,27 @@ export function questionRowHasContent( return false } +function normalizeQuestionDifficulty( + value: string | undefined, +): QuestionDifficultyLevel { + const upper = (value ?? "EASY").trim().toUpperCase() + if (upper === "MEDIUM" || upper === "HARD") return upper + return "EASY" +} + +function normalizeQuestionPoints(value: number | undefined): number { + const n = Number(value) + if (!Number.isFinite(n) || n < 1) return 1 + return Math.round(n) +} + export function buildCreateQuestionFromDefinition( def: QuestionTypeDefinition, q: LearnEnglishDefinitionQuestionInput, status: "DRAFT" | "PUBLISHED", ): CreateQuestionRequest { - const difficulty = "EASY" - const points = 1 + const difficulty = normalizeQuestionDifficulty(q.difficultyLevel) + const points = normalizeQuestionPoints(q.points) const question_text = q.questionText.trim() if (definitionUsesDynamicPayload(def)) { diff --git a/src/lib/learnEnglishPracticePublish.ts b/src/lib/learnEnglishPracticePublish.ts index 5225496..b3dfd1d 100644 --- a/src/lib/learnEnglishPracticePublish.ts +++ b/src/lib/learnEnglishPracticePublish.ts @@ -1,21 +1,14 @@ import type { AxiosError } from "axios" -import { - addQuestionToSet, - createExamPrepLessonPractice, - createParentLinkedPractice, - createQuestion, - createQuestionSet, -} from "../api/courses.api" import type { PracticeParentKind } from "../types/course.types" import type { QuestionTypeDefinition } from "../types/questionTypeDefinition.types" import { - buildCreateQuestionFromDefinition, - questionRowHasContent, - validateDefinitionQuestion, + executePracticeCreation, + validatePracticeQuestionsWithDefinitions, type LearnEnglishDefinitionQuestionInput, -} from "./learnEnglishDefinitionQuestion" + type PracticeCreationInput, +} from "./practiceCreationOrchestrator" -export type { LearnEnglishDefinitionQuestionInput } from "./learnEnglishDefinitionQuestion" +export type { LearnEnglishDefinitionQuestionInput } from "./practiceCreationOrchestrator" export function learnEnglishPracticeApiErrorMessage(err: unknown): string { const ax = err as AxiosError<{ message?: string; error?: string }> @@ -28,34 +21,11 @@ export function learnEnglishPracticeApiErrorMessage(err: unknown): string { return "Request failed" } -export function validateLearnEnglishQuestionsWithDefinitions( - questions: LearnEnglishDefinitionQuestionInput[], - definitions: QuestionTypeDefinition[], -): string | null { - const byId = new Map(definitions.map((d) => [d.id, d])) - const filled = questions.filter((q) => { - const def = byId.get(q.questionTypeDefinitionId) - return def ? questionRowHasContent(q, def) : false - }) - if (filled.length === 0) return "Add at least one question with content." - for (let i = 0; i < filled.length; i++) { - const q = filled[i] - if (!Number.isFinite(q.questionTypeDefinitionId) || q.questionTypeDefinitionId <= 0) { - return `Question ${i + 1}: select a question type from the list.` - } - const def = byId.get(q.questionTypeDefinitionId) - if (!def) { - return `Question ${i + 1}: type definition #${q.questionTypeDefinitionId} was not found. Refresh and try again.` - } - const err = validateDefinitionQuestion(def, q, i + 1) - if (err) return err - } - return null -} +export const validateLearnEnglishQuestionsWithDefinitions = + validatePracticeQuestionsWithDefinitions /** - * Learn English parent-linked practice: create PRACTICE question set, - * create questions from GET /questions/type-definitions entries, attach them, POST /practices. + * @deprecated Use executePracticeCreation — kept for existing imports. */ export async function executeLearnEnglishPracticeCreation(opts: { parentKind: PracticeParentKind @@ -69,96 +39,10 @@ export async function executeLearnEnglishPracticeCreation(opts: { storyImage: string quickTips: string personaName?: string | null - /** Selected persona from step 2 — sent as `persona_id` on POST /practices. */ personaId: number questions: LearnEnglishDefinitionQuestionInput[] definitions: QuestionTypeDefinition[] - /** When set, links practice via POST /exam-prep/lessons/:id/practices instead of POST /practices. */ examPrepLessonId?: number }): Promise<{ questionSetId: number; practiceId: number }> { - const err = validateLearnEnglishQuestionsWithDefinitions( - opts.questions, - opts.definitions, - ) - if (err) throw new Error(err) - - if (!Number.isFinite(opts.personaId) || opts.personaId < 1) { - throw new Error("persona_id is required. Select a persona before saving.") - } - - const byId = new Map(opts.definitions.map((d) => [d.id, d])) - - const setRes = await createQuestionSet({ - title: opts.questionSetTitle.trim() || "Practice question set", - description: opts.questionSetDescription?.trim() || null, - set_type: "PRACTICE", - owner_type: opts.parentKind, - owner_id: opts.parentId, - shuffle_questions: opts.shuffleQuestions, - status: opts.status, - ...(opts.personaName?.trim() ? { persona: opts.personaName.trim() } : {}), - }) - - const setId = setRes.data?.data?.id - if (!setId) { - throw new Error( - (setRes.data as { message?: string } | undefined)?.message ?? - "Could not create question set", - ) - } - - const toCreate = opts.questions.filter((q) => { - const def = byId.get(q.questionTypeDefinitionId) - return def ? questionRowHasContent(q, def) : false - }) - let displayOrder = 0 - for (const q of toCreate) { - const def = byId.get(q.questionTypeDefinitionId) - if (!def) throw new Error(`Missing definition #${q.questionTypeDefinitionId}`) - displayOrder += 1 - const payload = buildCreateQuestionFromDefinition(def, q, opts.status) - const qRes = await createQuestion(payload) - const questionId = qRes.data?.data?.id - if (!questionId) { - throw new Error( - (qRes.data as { message?: string } | undefined)?.message ?? - "Could not create question", - ) - } - await addQuestionToSet(setId, { - question_id: questionId, - display_order: displayOrder, - }) - } - - const practiceRes = opts.examPrepLessonId - ? await createExamPrepLessonPractice(opts.examPrepLessonId, { - title: opts.practiceTitle.trim(), - story_description: opts.storyDescription.trim(), - story_image: opts.storyImage.trim(), - persona_id: opts.personaId, - question_set_id: setId, - quick_tips: opts.quickTips.trim(), - }) - : await createParentLinkedPractice({ - parent_kind: opts.parentKind, - parent_id: opts.parentId, - title: opts.practiceTitle.trim(), - story_description: opts.storyDescription.trim(), - story_image: opts.storyImage.trim(), - question_set_id: setId, - quick_tips: opts.quickTips.trim(), - publish_status: opts.status, - persona_id: opts.personaId, - }) - - const practiceId = practiceRes.data?.data?.id - if (!practiceId) { - throw new Error( - (practiceRes.data as { message?: string } | undefined)?.message ?? - "Could not create practice", - ) - } - - return { questionSetId: setId, practiceId } + return executePracticeCreation(opts satisfies PracticeCreationInput) } diff --git a/src/lib/practiceCreationOrchestrator.ts b/src/lib/practiceCreationOrchestrator.ts new file mode 100644 index 0000000..9498bbf --- /dev/null +++ b/src/lib/practiceCreationOrchestrator.ts @@ -0,0 +1,152 @@ +import type { AxiosResponse } from "axios" +import { + addQuestionToSet, + createExamPrepLessonPractice, + createParentLinkedPractice, + createQuestion, + createQuestionSet, +} from "../api/courses.api" +import type { PracticeParentKind } from "../types/course.types" +import type { QuestionTypeDefinition } from "../types/questionTypeDefinition.types" +import { + buildCreateQuestionFromDefinition, + questionRowHasContent, + validateDefinitionQuestion, + type LearnEnglishDefinitionQuestionInput, +} from "./learnEnglishDefinitionQuestion" + +export type { LearnEnglishDefinitionQuestionInput } from "./learnEnglishDefinitionQuestion" + +export interface PracticeCreationInput { + parentKind: PracticeParentKind + parentId: number + status: "DRAFT" | "PUBLISHED" + questionSetTitle: string + questionSetDescription?: string | null + shuffleQuestions: boolean + practiceTitle: string + storyDescription: string + storyImage: string + quickTips: string + personaName?: string | null + personaId: number + questions: LearnEnglishDefinitionQuestionInput[] + definitions: QuestionTypeDefinition[] + /** English proficiency lesson practices use POST /exam-prep/lessons/:id/practices. */ + examPrepLessonId?: number +} + +function extractCreatedResourceId( + res: AxiosResponse<{ data?: { id?: number }; message?: string }>, + fallbackMessage: string, +): number { + const id = res.data?.data?.id + if (typeof id === "number" && Number.isFinite(id) && id > 0) return id + const message = res.data?.message?.trim() + throw new Error(message || fallbackMessage) +} + +export function validatePracticeQuestionsWithDefinitions( + questions: LearnEnglishDefinitionQuestionInput[], + definitions: QuestionTypeDefinition[], +): string | null { + const byId = new Map(definitions.map((d) => [d.id, d])) + const filled = questions.filter((q) => { + const def = byId.get(q.questionTypeDefinitionId) + return def ? questionRowHasContent(q, def) : false + }) + if (filled.length === 0) return "Add at least one question with content." + for (let i = 0; i < filled.length; i++) { + const q = filled[i] + if (!Number.isFinite(q.questionTypeDefinitionId) || q.questionTypeDefinitionId <= 0) { + return `Question ${i + 1}: select a question type from the list.` + } + const def = byId.get(q.questionTypeDefinitionId) + if (!def) { + return `Question ${i + 1}: type definition #${q.questionTypeDefinitionId} was not found. Refresh and try again.` + } + const err = validateDefinitionQuestion(def, q, i + 1) + if (err) return err + } + return null +} + +/** + * Coordinated practice creation (5 APIs): + * 1. POST /question-sets — create PRACTICE question set + * 2. POST /questions — create each question (DYNAMIC or legacy type) + * 3. POST /question-sets/:id/questions — attach questions to the set + * 4. Personas are loaded in the UI (GET /personas) before submit; persona_id is sent on step 5 + * 5. POST /practices or POST /exam-prep/lessons/:id/practices — create practice + */ +export async function executePracticeCreation( + opts: PracticeCreationInput, +): Promise<{ questionSetId: number; practiceId: number }> { + const err = validatePracticeQuestionsWithDefinitions(opts.questions, opts.definitions) + if (err) throw new Error(err) + + if (!Number.isFinite(opts.personaId) || opts.personaId < 1) { + throw new Error("persona_id is required. Select a persona before saving.") + } + + const byId = new Map(opts.definitions.map((d) => [d.id, d])) + + // Step 1 — create question set + const setRes = await createQuestionSet({ + title: opts.questionSetTitle.trim() || "Practice question set", + description: opts.questionSetDescription?.trim() || null, + set_type: "PRACTICE", + owner_type: opts.parentKind, + owner_id: opts.parentId, + shuffle_questions: opts.shuffleQuestions, + status: opts.status, + ...(opts.personaName?.trim() ? { persona: opts.personaName.trim() } : {}), + }) + const setId = extractCreatedResourceId(setRes, "Could not create question set") + + const toCreate = opts.questions.filter((q) => { + const def = byId.get(q.questionTypeDefinitionId) + return def ? questionRowHasContent(q, def) : false + }) + + // Steps 2 & 3 — create questions and attach to set + let displayOrder = 0 + for (const q of toCreate) { + const def = byId.get(q.questionTypeDefinitionId) + if (!def) throw new Error(`Missing definition #${q.questionTypeDefinitionId}`) + displayOrder += 1 + const payload = buildCreateQuestionFromDefinition(def, q, opts.status) + const qRes = await createQuestion(payload) + const questionId = extractCreatedResourceId(qRes, "Could not create question") + await addQuestionToSet(setId, { + question_id: questionId, + display_order: displayOrder, + }) + } + + // Step 5 — create practice (persona_id + question_set_id from step 1) + const practiceRes = opts.examPrepLessonId + ? await createExamPrepLessonPractice(opts.examPrepLessonId, { + title: opts.practiceTitle.trim(), + story_description: opts.storyDescription.trim(), + story_image: opts.storyImage.trim(), + persona_id: opts.personaId, + question_set_id: setId, + quick_tips: opts.quickTips.trim(), + publish_status: opts.status, + }) + : await createParentLinkedPractice({ + parent_kind: opts.parentKind, + parent_id: opts.parentId, + title: opts.practiceTitle.trim(), + story_description: opts.storyDescription.trim(), + story_image: opts.storyImage.trim(), + question_set_id: setId, + quick_tips: opts.quickTips.trim(), + publish_status: opts.status, + persona_id: opts.personaId, + }) + + const practiceId = extractCreatedResourceId(practiceRes, "Could not create practice") + return { questionSetId: setId, practiceId } +} diff --git a/src/pages/content-management/AddPracticeFlow.tsx b/src/pages/content-management/AddPracticeFlow.tsx index c9593e6..8185217 100644 --- a/src/pages/content-management/AddPracticeFlow.tsx +++ b/src/pages/content-management/AddPracticeFlow.tsx @@ -15,10 +15,10 @@ import type { QuestionTypeDefinition } from "../../types/questionTypeDefinition. import { getQuestionTypeDefinitions } from "../../api/questionTypeDefinitions.api"; import { emptyDynamicFieldValuesForDefinition } from "../../lib/learnEnglishDefinitionQuestion"; import { - executeLearnEnglishPracticeCreation, learnEnglishPracticeApiErrorMessage, validateLearnEnglishQuestionsWithDefinitions, } from "../../lib/learnEnglishPracticePublish"; +import { executePracticeCreation } from "../../lib/practiceCreationOrchestrator"; import { ContextStep } from "./components/practice-steps/ContextStep"; import { ScenarioStep } from "./components/practice-steps/ScenarioStep"; @@ -198,6 +198,8 @@ export function AddPracticeFlow() { id: "q1", questionTypeDefinitionId: null as number | null, text: "", + difficultyLevel: "EASY" as "EASY" | "MEDIUM" | "HARD", + points: 1, dynamicFieldValues: {} as Record, mcqOptions: [ { text: "", isCorrect: true }, @@ -292,6 +294,11 @@ export function AddPracticeFlow() { const mappedQuestions = formData.questions.map((q) => ({ questionText: String(q.text ?? "").trim(), questionTypeDefinitionId: Number(q.questionTypeDefinitionId), + difficultyLevel: (q.difficultyLevel ?? "EASY") as + | "EASY" + | "MEDIUM" + | "HARD", + points: Number.isFinite(Number(q.points)) ? Number(q.points) : 1, dynamicFieldValues: { ...(q.dynamicFieldValues ?? {}) }, mcqOptions: (q.mcqOptions ?? []).map( (o: { text?: string; isCorrect?: boolean }) => ({ @@ -324,7 +331,7 @@ export function AddPracticeFlow() { setSubmitting(true); try { - await executeLearnEnglishPracticeCreation({ + await executePracticeCreation({ parentKind: parentContext.kind, parentId: parentContext.id, examPrepLessonId: useExamPrepLessonApi ? parentContext.id : undefined, @@ -351,13 +358,7 @@ export function AddPracticeFlow() { questions: mappedQuestions, definitions: typeDefinitions, }); - toast.success( - status === "PUBLISHED" ? "Practice published" : "Draft saved", - { - description: - "Question set, questions, and parent-linked practice were created.", - }, - ); + toast.success("Practice created successfully"); setIsPublished(true); } catch (e) { toast.error("Could not save practice", { @@ -415,6 +416,8 @@ export function AddPracticeFlow() { questionTypeDefinitionId: typeDefinitions[0]?.id ?? (null as number | null), text: "", + difficultyLevel: "EASY" as "EASY" | "MEDIUM" | "HARD", + points: 1, dynamicFieldValues: typeDefinitions[0] ? emptyDynamicFieldValuesForDefinition(typeDefinitions[0]) : {}, diff --git a/src/pages/content-management/LessonPracticesPage.tsx b/src/pages/content-management/LessonPracticesPage.tsx index 4c7aabb..59e5729 100644 --- a/src/pages/content-management/LessonPracticesPage.tsx +++ b/src/pages/content-management/LessonPracticesPage.tsx @@ -9,14 +9,28 @@ import { Loader2, RefreshCw, Sparkles, + Trash2, } from "lucide-react"; import { Link, useNavigate, useParams, useSearchParams } from "react-router-dom"; import { toast } from "sonner"; -import { getPracticesByParentLesson } from "../../api/courses.api"; +import { + deleteExamPrepPractice, + getExamPrepLessonPractices, + getPracticesByParentLesson, +} from "../../api/courses.api"; import { Badge } from "../../components/ui/badge"; import { Button } from "../../components/ui/button"; import { Card, CardContent } from "../../components/ui/card"; +import { + Dialog, + DialogContent, + DialogFooter, + DialogHeader, + DialogTitle, +} from "../../components/ui/dialog"; import type { + ExamPrepLessonPractice, + GetExamPrepLessonPracticesResponse, GetPracticesByParentContextResponse, ParentContextPractice, } from "../../types/course.types"; @@ -31,6 +45,32 @@ function unwrapPracticesEnvelope( return b.data ?? b.Data ?? null; } +function unwrapExamPrepPracticesEnvelope( + res: { data?: GetExamPrepLessonPracticesResponse & { Data?: GetExamPrepLessonPracticesResponse["data"] } }, +): GetExamPrepLessonPracticesResponse["data"] | null { + const b = res.data; + if (!b) return null; + return b.data ?? b.Data ?? null; +} + +function mapExamPrepPracticeToCard( + practice: ExamPrepLessonPractice, +): ParentContextPractice { + return { + id: practice.id, + parent_kind: "LESSON", + parent_id: practice.lesson_id, + title: practice.title, + story_description: practice.story_description ?? "", + story_image: practice.story_image ?? "", + question_set_id: practice.question_set_id, + quick_tips: practice.quick_tips ?? "", + publish_status: practice.publish_status, + persona_id: practice.persona_id, + created_at: practice.created_at, + }; +} + function formatPracticeDate(iso: string): string { const d = new Date(iso); return Number.isNaN(d.getTime()) @@ -42,10 +82,12 @@ function PracticeCard({ practice, index, total, + onDelete, }: { practice: ParentContextPractice; index: number; total: number; + onDelete?: () => void; }) { const [imgFailed, setImgFailed] = useState(false); const thumb = resolveThumbnailForPreview(practice.story_image); @@ -91,13 +133,35 @@ function PracticeCard({
-
- - Practice {index + 1} of {total} - - - ID {practice.id} - +
+
+ + Practice {index + 1} of {total} + + + ID {practice.id} + + {practice.publish_status ? ( + + {practice.publish_status} + + ) : null} +
+ {onDelete ? ( + + ) : null}

@@ -165,6 +229,8 @@ export function LessonPracticesPage() { const [totalCount, setTotalCount] = useState(0); const [loading, setLoading] = useState(true); const [loadError, setLoadError] = useState(null); + const [practiceToDelete, setPracticeToDelete] = useState(null); + const [deleting, setDeleting] = useState(false); const lid = lessonId ? Number(lessonId) : NaN; const validLesson = Number.isFinite(lid) && lid > 0; @@ -179,15 +245,29 @@ export function LessonPracticesPage() { setLoading(true); setLoadError(null); try { - const res = await getPracticesByParentLesson(lid, { limit: 100, offset: 0 }); - const envelope = unwrapPracticesEnvelope(res); - const list = Array.isArray(envelope?.practices) ? envelope.practices : []; - setPractices(list); - setTotalCount( - typeof envelope?.total_count === "number" - ? envelope.total_count - : list.length, - ); + if (isExamPrep) { + const res = await getExamPrepLessonPractices(lid, { limit: 100, offset: 0 }); + const envelope = unwrapExamPrepPracticesEnvelope(res); + const list = Array.isArray(envelope?.practices) + ? envelope.practices.map(mapExamPrepPracticeToCard) + : []; + setPractices(list); + setTotalCount( + typeof envelope?.total_count === "number" + ? envelope.total_count + : list.length, + ); + } else { + const res = await getPracticesByParentLesson(lid, { limit: 100, offset: 0 }); + const envelope = unwrapPracticesEnvelope(res); + const list = Array.isArray(envelope?.practices) ? envelope.practices : []; + setPractices(list); + setTotalCount( + typeof envelope?.total_count === "number" + ? envelope.total_count + : list.length, + ); + } } catch { setPractices([]); setTotalCount(0); @@ -196,7 +276,7 @@ export function LessonPracticesPage() { } finally { setLoading(false); } - }, [lid, validLesson]); + }, [isExamPrep, lid, validLesson]); useEffect(() => { void load(); @@ -209,6 +289,22 @@ export function LessonPracticesPage() { ? `/new-content/courses/${programType}/${courseId}/${unitId}/${moduleId}/add-practice?lessonId=${lid}&lessonTitle=${encodeURIComponent(lessonTitle || displayTitle)}` : `/new-content/learn-english/${level}/courses/add-practice?backTo=module&courseId=${courseId}&moduleId=${moduleId}&lessonId=${lid}&lessonTitle=${encodeURIComponent(lessonTitle || displayTitle)}`; + const confirmDeletePractice = async () => { + if (!practiceToDelete) return; + setDeleting(true); + try { + await deleteExamPrepPractice(practiceToDelete.id); + toast.success("Practice deleted"); + setPracticeToDelete(null); + await load(); + } catch (e: unknown) { + const err = e as { response?: { data?: { message?: string } } }; + toast.error(err.response?.data?.message || "Failed to delete practice"); + } finally { + setDeleting(false); + } + }; + return (
@@ -369,12 +465,52 @@ export function LessonPracticesPage() { practice={p} index={i} total={practices.length} + onDelete={ + isExamPrep ? () => setPracticeToDelete(p) : undefined + } /> ))}
)}

+ + { + if (!open && !deleting) setPracticeToDelete(null); + }} + > + + + Delete this practice? + +

+ + {practiceToDelete?.title} + {" "} + will be removed from this lesson. This action cannot be undone. +

+ + + + +
+
); } diff --git a/src/pages/content-management/components/practice-steps/QuestionsStep.tsx b/src/pages/content-management/components/practice-steps/QuestionsStep.tsx index fa842e0..73cf5b5 100644 --- a/src/pages/content-management/components/practice-steps/QuestionsStep.tsx +++ b/src/pages/content-management/components/practice-steps/QuestionsStep.tsx @@ -27,6 +27,8 @@ function createEmptyQuestionRow(id: string) { id, questionTypeDefinitionId: null as number | null, text: "", + difficultyLevel: "EASY" as "EASY" | "MEDIUM" | "HARD", + points: 1, dynamicFieldValues: {} as Record, mcqOptions: defaultMcqOptions(), trueFalseCorrect: true, @@ -376,6 +378,55 @@ export function QuestionsStep({ +
+
+ + +
+
+ + { + const newQuestions = [...formData.questions]; + const parsed = Number.parseInt(e.target.value, 10); + newQuestions[i] = { + ...newQuestions[i], + points: + Number.isFinite(parsed) && parsed > 0 ? parsed : 1, + }; + setFormData({ ...formData, questions: newQuestions }); + }} + className="h-11 rounded-lg border-grayScale-200" + /> +
+
+