import { useState } from 'react'; import { Stack, Title, Group, Button, Badge, ActionIcon, Modal, Text, TextInput, Card, Alert, Select, NumberInput, Textarea, } from '@mantine/core'; import { useDisclosure } from '@mantine/hooks'; import { useTranslation } from 'react-i18next'; import { IconEdit, IconTrash, IconPlus, IconInfoCircle, IconSend, IconGavel, } from '@tabler/icons-react'; import { AdvancedColumn, AdvancedTable, ModalFooter, notify, useErrorHandler, useServerTable } from '@ema-platform/ui'; import { extractErrorMessage } from '@ema-platform/api'; import { useGetCertificationsQuery } from '../../certification/api/certification-api'; import { useGetQuestionsQuery, useCreateQuestionMutation, useUpdateQuestionMutation, useDeleteQuestionMutation, useSubmitQuestionMutation, useReviewQuestionMutation, } from '../api/question-api'; import type { Question, QuestionForm, QuestionStatus } from '../types/question'; /** Bank items travel DRAFT → PENDING_APPROVAL → APPROVED (US-EXAM-003). */ const QC_COLOR: Record = { DRAFT: 'gray', PENDING_APPROVAL: 'yellow', APPROVED: 'teal', REJECTED: 'red', RETIRED: 'dark', }; function QuestionForm({ editing, certOptions, isSubmitting, onSubmit, onCancel, }: { editing: Question | null; certOptions: { value: string; label: string }[]; isSubmitting: boolean; onSubmit: (values: { certificationId: string; titleEn: string; titleAm: string; form: string; points: number; days: number; hours: number; minutes: number; }, isEdit: boolean) => void; onCancel: () => void; }) { const { t } = useTranslation(); const [certificationId, setCertificationId] = useState(editing?.certificationId ?? null); const [titleEn, setTitleEn] = useState(editing?.title?.en ?? ''); const [titleAm, setTitleAm] = useState(editing?.title?.am ?? ''); const [form, setForm] = useState(editing?.form ?? null); const [points, setPoints] = useState(editing?.points ?? 0); const [days, setDays] = useState(editing?.time?.days ?? 0); const [hours, setHours] = useState(editing?.time?.hours ?? 0); const [minutes, setMinutes] = useState(editing?.time?.minutes ?? 0); const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); if (!certificationId || !titleEn || !titleAm || !form) { notify.error('Please fill all required fields'); return; } onSubmit({ certificationId, titleEn, titleAm, form, points, days, hours, minutes }, !!editing); }; return (
setPoints(Number(v))} min={0} size="sm" required /> {t('question.form.timeAllowed')} setDays(Number(v))} min={0} size="sm" /> setHours(Number(v))} min={0} size="sm" /> setMinutes(Number(v))} min={0} size="sm" />
); } export function QuestionPage() { const { t, i18n } = useTranslation(); const locale = i18n.language as 'en' | 'am'; const { handleError } = useErrorHandler(); const { data: certRes } = useGetCertificationsQuery(); const { data, isFetching, isError, refetch } = useGetQuestionsQuery(); const { setPageIndex, pageSize, setPageSize, paginate } = useServerTable(); const [createQ, { isLoading: isCreating }] = useCreateQuestionMutation(); const [updateQ, { isLoading: isUpdating }] = useUpdateQuestionMutation(); const [deleteQ] = useDeleteQuestionMutation(); const [submitQ, { isLoading: isSubmittingReview }] = useSubmitQuestionMutation(); const [reviewQ, { isLoading: isReviewing }] = useReviewQuestionMutation(); const certifications = certRes?.items ?? []; const questions = data?.items ?? []; const [editing, setEditing] = useState(null); const [showForm, setShowForm] = useState(false); const [deleteTarget, setDeleteTarget] = useState(null); const [deleteOpened, { open: openDelete, close: closeDelete }] = useDisclosure(false); const [certFilter, setCertFilter] = useState(null); const [reviewTarget, setReviewTarget] = useState(null); const [reviewOutcome, setReviewOutcome] = useState<'APPROVED' | 'REJECTED' | 'RETIRED'>('APPROVED'); const [reviewRemark, setReviewRemark] = useState(''); const certOptions = certifications.filter((c) => c.isActive).map((c) => ({ value: c.id, label: c.name[locale] })); const filtered = questions.filter((q) => !certFilter || q.certificationId === certFilter); const page = paginate(filtered); const getCertName = (id: string) => certifications.find((c) => c.id === id)?.name?.[locale] ?? '-'; const resetForm = () => { setEditing(null); setShowForm(false); }; const handleSubmit = async (values: { certificationId: string; titleEn: string; titleAm: string; form: string; points: number; days: number; hours: number; minutes: number; }, isEdit: boolean) => { const title = { en: values.titleEn, am: values.titleAm }; const time = { days: values.days, hours: values.hours, minutes: values.minutes }; try { if (isEdit && editing) { await updateQ({ id: editing.id, certificationId: values.certificationId, title, form: values.form as QuestionForm, points: values.points, time }).unwrap(); notify.success(t('question.updated')); } else { await createQ({ certificationId: values.certificationId, title, description: { en: '', am: '' }, form: values.form as QuestionForm, points: values.points, time }).unwrap(); notify.success(t('question.created')); } resetForm(); } catch { notify.error(t('question.error')); } }; const handleSubmitForApproval = async (question: Question) => { try { await submitQ(question.id).unwrap(); notify.success(t('question.qc.submitted')); } catch (error) { notify.error(extractErrorMessage(error, t('question.qc.error'))); } }; const openReview = (question: Question, outcome: 'APPROVED' | 'REJECTED' | 'RETIRED') => { setReviewTarget(question); setReviewOutcome(outcome); setReviewRemark(''); }; const handleReview = async () => { if (!reviewTarget) return; if (reviewOutcome !== 'APPROVED' && !reviewRemark.trim()) { notify.error(t('question.qc.remarkRequired')); return; } try { await reviewQ({ id: reviewTarget.id, outcome: reviewOutcome, remark: reviewRemark.trim() || undefined, }).unwrap(); notify.success(t('question.qc.reviewed')); setReviewTarget(null); } catch (error) { notify.error(extractErrorMessage(error, t('question.qc.error'))); } }; const handleDelete = async () => { if (!deleteTarget) return; try { await deleteQ(deleteTarget.id).unwrap(); notify.success(t('question.deleted')); closeDelete(); setDeleteTarget(null); } catch (e) { handleError(e); } }; if (isError) return } color="red" title={t('question.loadError')} />; const columns: AdvancedColumn[] = [ { header: t('question.columns.title'), cell: ({ row }) => {row.original.title[locale]}, }, { header: t('question.columns.certification'), cell: ({ row }) => {getCertName(row.original.certificationId)}, }, { header: t('question.columns.form'), cell: ({ row }) => ( {t(`question.form.${row.original.form === 'ESSAY' ? 'essay' : 'choice'}`)} ), }, { header: t('question.columns.points'), cell: ({ row }) => {row.original.points}, }, { header: t('question.qc.column'), cell: ({ row }) => ( {t(`question.qc.${row.original.status}`)} ), }, { header: '', label: t('question.columns.actions', 'Actions'), cell: ({ row }) => { const q = row.original; return ( {(q.status === 'DRAFT' || q.status === 'REJECTED') && ( )} {q.status === 'PENDING_APPROVAL' && ( <> )} {q.status === 'APPROVED' && ( )} { setEditing(q); setShowForm(true); }}> { setDeleteTarget(q); openDelete(); }}> ); }, }, ]; return ( {t('question.title')} {!showForm && ( )} {showForm && ( )} {t('question.pool')}