import { useEffect, useState } from 'react'; import { ActionIcon, Alert, Button, Checkbox, Group, Loader, Stack, Text, TextInput } from '@mantine/core'; import { useTranslation } from 'react-i18next'; import { IconGripVertical, IconInfoCircle, IconPlus, IconTrash } from '@tabler/icons-react'; import { notify, useErrorHandler } from '@ema-platform/ui'; import type { BilingualValue } from '@ema-platform/ui'; import { useGetQuestionWithOptionsQuery, useSetQuestionOptionsMutation, } from '../api/question-api'; interface DraftOption { text: BilingualValue; isCorrect: boolean; } /** * MCQ options + correct-answer editor for a CHOICE-form question (Phase 2). * * Only reachable while editing an already-created question — options attach * to a question id, matching the backend's `PUT /questions/:id/options` * full-replace endpoint. Nothing here is ever shown to a candidate; this is * the authoring side only. */ export function QuestionOptionsEditor({ questionId }: { questionId: string }) { const { t } = useTranslation(); const { handleError } = useErrorHandler(); const { data: question, isFetching } = useGetQuestionWithOptionsQuery(questionId); const [setOptions, { isLoading: isSaving }] = useSetQuestionOptionsMutation(); const [draft, setDraft] = useState([]); useEffect(() => { if (!question) return; const existing = question.options ?? []; setDraft( existing.length ? existing .slice() .sort((a, b) => a.order - b.order) .map((o) => ({ text: o.text, isCorrect: false })) : [ { text: { en: '', am: '' }, isCorrect: false }, { text: { en: '', am: '' }, isCorrect: false }, ], ); // isCorrect never comes back from the API by design — an examiner // re-editing options re-marks the correct one(s) rather than us // pretending to know what they were. }, [question]); const updateField = (index: number, lang: keyof BilingualValue, value: string) => { setDraft((prev) => prev.map((o, i) => (i === index ? { ...o, text: { ...o.text, [lang]: value } } : o)), ); }; const toggleCorrect = (index: number) => { setDraft((prev) => prev.map((o, i) => (i === index ? { ...o, isCorrect: !o.isCorrect } : o))); }; const addOption = () => { setDraft((prev) => [...prev, { text: { en: '', am: '' }, isCorrect: false }]); }; const removeOption = (index: number) => { setDraft((prev) => prev.filter((_, i) => i !== index)); }; const handleSave = async () => { if (draft.length < 2) { notify.error(t('question.options.needAtLeastTwo')); return; } if (!draft.some((o) => o.isCorrect)) { notify.error(t('question.options.needOneCorrect')); return; } if (draft.some((o) => !o.text.en.trim() || !o.text.am.trim())) { notify.error(t('question.options.textRequired')); return; } try { await setOptions({ id: questionId, options: draft }).unwrap(); notify.success(t('question.options.saved')); } catch (e) { handleError(e); } }; if (isFetching) return ; return ( } color="blue" variant="light"> {t('question.options.hint')} {draft.map((option, index) => ( updateField(index, 'en', e.currentTarget.value)} size="sm" required /> updateField(index, 'am', e.currentTarget.value)} size="sm" required /> toggleCorrect(index)} /> removeOption(index)} > ))} {t('question.options.replaceNotice')} ); }