import { useState } from 'react'; import { useTranslation } from 'react-i18next'; import { ActionIcon, Button, Checkbox, Group, Modal, NumberInput, Select, Stack, Text, TextInput, } from '@mantine/core'; import { IconPlus, IconTrash } from '@tabler/icons-react'; import { ModalFooter, notify } from '@ema-platform/ui'; import { extractErrorMessage } from '@ema-platform/api'; import { useCreateExamQuestionMutation } from '../../api/exam-api'; import type { Exam, QuestionForm } from '../../types/exam'; import { describeExamQuestionError } from './errors'; type DraftOption = { textEn: string; textAm: string; isCorrect: boolean }; const BLANK: DraftOption[] = [ { textEn: '', textAm: '', isCorrect: false }, { textEn: '', textAm: '', isCorrect: false }, ]; /** * "Add new question from scratch": authored under this exam's subject and * put on its paper in one call — the officer never leaves the exam or copies * an id. The item is a real bank question (options, answer key), reusable on * a later paper. */ export function ExamQuestionCreateModal({ exam, opened, onClose, }: { exam: Exam; opened: boolean; onClose: () => void; }) { const { t } = useTranslation(); const [createQuestion, { isLoading }] = useCreateExamQuestionMutation(); const [titleEn, setTitleEn] = useState(''); const [titleAm, setTitleAm] = useState(''); const [form, setForm] = useState(exam.form === 'ESSAY' ? 'ESSAY' : 'CHOICE'); const [points, setPoints] = useState(1); const [options, setOptions] = useState(BLANK); const reset = () => { setTitleEn(''); setTitleAm(''); setForm(exam.form === 'ESSAY' ? 'ESSAY' : 'CHOICE'); setPoints(1); setOptions(BLANK); }; const close = () => { reset(); onClose(); }; const updateOption = (index: number, patch: Partial) => setOptions((current) => current.map((o, i) => (i === index ? { ...o, ...patch } : o))); // A mixed (BOTH) paper takes either form; otherwise the question must match. const formOptions = (exam.form === 'BOTH' ? ['ESSAY', 'CHOICE'] : [exam.form]).map((value) => ({ value, label: t(`exam.formType.${value}`), })); const submit = async () => { if (!titleEn.trim() || !form || !(points > 0)) { notify.error(t('exam.newQuestion.fillRequired')); return; } if (form === 'CHOICE') { if (options.length < 2) return void notify.error(t('exam.newQuestion.needTwo')); if (!options.some((o) => o.isCorrect)) return void notify.error(t('exam.newQuestion.needCorrect')); if (options.some((o) => !o.textEn.trim())) return void notify.error(t('exam.newQuestion.textRequired')); } try { await createQuestion({ examId: exam.id, // The API requires Amharic; it falls back to the English text server-side // as well, but sending it explicitly keeps the request self-describing. title: { en: titleEn.trim(), am: titleAm.trim() || titleEn.trim() }, form, points, options: form === 'CHOICE' ? options.map((o) => ({ text: { en: o.textEn.trim(), am: o.textAm.trim() || o.textEn.trim() }, isCorrect: o.isCorrect, })) : undefined, }).unwrap(); notify.success(t('exam.newQuestion.created')); close(); } catch (error) { notify.error(describeExamQuestionError(t, extractErrorMessage(error, t('exam.error')))); } }; return ( {t('exam.newQuestion.hint')} setTitleEn(e.currentTarget.value)} size="sm" required /> setTitleAm(e.currentTarget.value)} size="sm" />