diff --git a/apps/backoffice/src/app/features/configuration/api/configuration-api.ts b/apps/backoffice/src/app/features/configuration/api/configuration-api.ts index 6ee3d2a0c..93a728f1e 100644 --- a/apps/backoffice/src/app/features/configuration/api/configuration-api.ts +++ b/apps/backoffice/src/app/features/configuration/api/configuration-api.ts @@ -14,12 +14,11 @@ const configurationApi = baseApi.injectEndpoints({ providesTags: ["Api"], }), - getProfessions: builder.query, { q?: string }>({ + getProfessions: builder.query, string>({ query: (params) => ({ - url: "/professions", - params, + url: `/professions?q=${encodeURIComponent(params)}`, }), - providesTags: ["Api"], + providesTags: ["Api", "backOfficeApi", "ProfessionApi"], }), createProfession: builder.mutation({ query: (body) => ({ url: "/professions", method: "POST", body }), diff --git a/apps/backoffice/src/app/features/configuration/pages/ConfigurationPage.tsx b/apps/backoffice/src/app/features/configuration/pages/ConfigurationPage.tsx index ac238a204..ef5479d31 100644 --- a/apps/backoffice/src/app/features/configuration/pages/ConfigurationPage.tsx +++ b/apps/backoffice/src/app/features/configuration/pages/ConfigurationPage.tsx @@ -172,9 +172,9 @@ function ProfessionTab() { isFetching, isError, refetch, - } = useGetProfessionsQuery({ - q: `skip:${skip},take:${take},orderBy:createdAt:DESC`, - }); + } = useGetProfessionsQuery( + `skip:${skip},take:${take},orderBy:createdAt:DESC`, + ); const [createProfession, { isLoading: isCreating }] = useCreateProfessionMutation(); const [updateProfession, { isLoading: isUpdating }] = diff --git a/apps/backoffice/src/app/features/exam/components/QuestionAssigner.tsx b/apps/backoffice/src/app/features/exam/components/QuestionAssigner.tsx index 8859a0f33..136d7ba21 100644 --- a/apps/backoffice/src/app/features/exam/components/QuestionAssigner.tsx +++ b/apps/backoffice/src/app/features/exam/components/QuestionAssigner.tsx @@ -1,4 +1,4 @@ -import { useState } from 'react'; +import { Dispatch, SetStateAction, useState } from "react"; import { Paper, Group, @@ -10,16 +10,17 @@ import { Checkbox, Box, Button, -} from '@mantine/core'; -import { useTranslation } from 'react-i18next'; -import { IconSearch } from '@tabler/icons-react'; -import type { QuestionBrief } from '../types/exam'; +} from "@mantine/core"; +import { useTranslation } from "react-i18next"; +import { IconSearch } from "@tabler/icons-react"; +import type { QuestionBrief, actionTypes } from "../types/exam"; interface QuestionAssignerProps { available: QuestionBrief[]; assigned: QuestionBrief[]; onChange: (assigned: QuestionBrief[]) => void; - mode?: 'manual' | 'random'; + mode?: "manual" | "random"; + actions: Dispatch>; } function QuestionList({ @@ -38,11 +39,13 @@ function QuestionList({ label: string; }) { const { t, i18n } = useTranslation(); - const locale = i18n.language as 'en' | 'am'; - const placeholder = t('exam.assigner.search'); + const locale = i18n.language as "en" | "am"; + const placeholder = t("exam.assigner.search"); return ( - {label} ({items.length}) + + {label} ({items.length}) + {items.length === 0 && ( - {t('exam.assigner.noQuestions')} + + {t("exam.assigner.noQuestions")} + )} {items.map((q) => ( onToggle(q.id)} > - onToggle(q.id)} size="xs" /> + onToggle(q.id)} + size="xs" + />
- {q.title[locale]} + + {q.title[locale]} + - {q.form} - {q.points} pts + + {q.form} + + + {q.points} pts +
@@ -91,49 +114,71 @@ function QuestionList({ ); } -export function QuestionAssigner({ available, assigned, onChange, mode = 'manual' }: QuestionAssignerProps) { +export function QuestionAssigner({ + available, + assigned, + onChange, + mode = "manual", + actions, +}: QuestionAssignerProps) { const { t } = useTranslation(); - const [searchLeft, setSearchLeft] = useState(''); - const [searchRight, setSearchRight] = useState(''); + const [searchLeft, setSearchLeft] = useState(""); + const [searchRight, setSearchRight] = useState(""); const [selectedLeft, setSelectedLeft] = useState>(new Set()); const [selectedRight, setSelectedRight] = useState>(new Set()); const assignedIds = new Set(assigned.map((q) => q.id)); const filteredAvailable = available.filter( - (q) => !assignedIds.has(q.id) && (q.title.en.toLowerCase().includes(searchLeft.toLowerCase()) || q.title.am.includes(searchLeft)) + (q) => + !assignedIds.has(q.id) && + (q.title.en.toLowerCase().includes(searchLeft.toLowerCase()) || + q.title.am.includes(searchLeft)), ); const filteredAssigned = assigned.filter( - (q) => q.title.en.toLowerCase().includes(searchRight.toLowerCase()) || q.title.am.includes(searchRight) + (q) => + q.title.en.toLowerCase().includes(searchRight.toLowerCase()) || + q.title.am.includes(searchRight), ); const assignSelected = () => { const toAssign = available.filter((q) => selectedLeft.has(q.id)); onChange([...assigned, ...toAssign]); + actions("add"); setSelectedLeft(new Set()); }; const removeSelected = () => { onChange(assigned.filter((q) => !selectedRight.has(q.id))); + actions("remove"); setSelectedRight(new Set()); }; return ( - {mode === 'manual' && {t('exam.assigner.title')}} - {mode === 'random' && {t('exam.assigner.assignedTitle')}} + {mode === "manual" && ( + + {t("exam.assigner.title")} + + )} + {mode === "random" && ( + + {t("exam.assigner.assignedTitle")} + + )} - {mode === 'manual' && ( + {mode === "manual" && ( { const next = new Set(selectedLeft); - if (next.has(id)) next.delete(id); else next.add(id); + if (next.has(id)) next.delete(id); + else next.add(id); setSelectedLeft(next); }} search={searchLeft} onSearchChange={setSearchLeft} - label={t('exam.assigner.available')} + label={t("exam.assigner.available")} /> )} - {mode === 'manual' && ( + {mode === "manual" && ( {selectedLeft.size > 0 && ( )} {selectedRight.size > 0 && ( - )} )} - {mode === 'random' && selectedRight.size > 0 && ( + {mode === "random" && selectedRight.size > 0 && ( - )} diff --git a/apps/backoffice/src/app/features/exam/pages/ExamDetailPage.tsx b/apps/backoffice/src/app/features/exam/pages/ExamDetailPage.tsx index c2616b164..ce77b3eef 100644 --- a/apps/backoffice/src/app/features/exam/pages/ExamDetailPage.tsx +++ b/apps/backoffice/src/app/features/exam/pages/ExamDetailPage.tsx @@ -1,5 +1,5 @@ -import { useState, useEffect, useRef, useMemo } from 'react'; -import { useNavigate, useParams } from 'react-router-dom'; +import { useState, useEffect, useRef, useMemo } from "react"; +import { useNavigate, useParams } from "react-router-dom"; import { Stack, Title, @@ -22,9 +22,9 @@ import { ThemeIcon, Box, rem, -} from '@mantine/core'; -import { useDisclosure } from '@mantine/hooks'; -import { useTranslation } from 'react-i18next'; +} from "@mantine/core"; +import { useDisclosure } from "@mantine/hooks"; +import { useTranslation } from "react-i18next"; import { IconArrowLeft, IconPrinter, @@ -38,68 +38,116 @@ import { IconUser, IconCheck, IconX, -} from '@tabler/icons-react'; -import { notify, useErrorHandler } from '@ema-platform/ui'; -import { useGetExamQuery, useUpdateExamMutation, useAssignQuestionsMutation } from '../api/exam-api'; -import { useGetQuestionsQuery } from '../../question/api/question-api'; -import { useGetCertificationsQuery } from '../../certification/api/certification-api'; -import { QuestionAssigner } from '../components/QuestionAssigner'; -import { RecordResultModal } from '../../result/components/RecordResultModal'; -import type { ExamStatus, QuestionBrief } from '../types/exam'; + IconDetails +} from "@tabler/icons-react"; +import { notify, useErrorHandler } from "@ema-platform/ui"; +import { + useGetExamQuery, + useUpdateExamMutation, + useAssignQuestionsMutation, +} from "../api/exam-api"; +import { useGetQuestionsQuery } from "../../question/api/question-api"; +import { useGetCertificationsQuery } from "../../certification/api/certification-api"; +import { QuestionAssigner } from "../components/QuestionAssigner"; +import { RecordResultModal } from "../../result/components/RecordResultModal"; +import type { ExamStatus, QuestionBrief, actionTypes } from "../types/exam"; const STATUS_COLOR: Record = { - PENDING: 'gray', ACTIVE: 'blue', COMPLETED: 'teal', - CANCELLED: 'red', POSTPONED: 'orange', PUBLISHED: 'green', + PENDING: "gray", + ACTIVE: "blue", + COMPLETED: "teal", + CANCELLED: "red", + POSTPONED: "orange", + PUBLISHED: "green", }; -const FORM_LABEL: Record = { ESSAY: 'Essay', CHOICE: 'Choice' }; -const TYPE_LABEL: Record = { WRITTEN: 'Written', ORAL: 'Oral' }; -const ADMIN_LABEL: Record = { OFFLINE: 'Offline', ONLINE: 'Online' }; -const EVAL_LABEL: Record = { SUM: 'Sum', AVERAGE: 'Average', PERCENTAGE: 'Percentage' }; +const FORM_LABEL: Record = { ESSAY: "Essay", CHOICE: "Choice" }; +const TYPE_LABEL: Record = { WRITTEN: "Written", ORAL: "Oral" }; +const ADMIN_LABEL: Record = { + OFFLINE: "Offline", + ONLINE: "Online", +}; +const EVAL_LABEL: Record = { + SUM: "Sum", + AVERAGE: "Average", + PERCENTAGE: "Percentage", +}; function InfoRow({ label, value }: { label: string; value: string }) { return (
- {label} - {value || '—'} + + {label} + + + {value || "—"} +
); } export function ExamDetailPage() { const { t, i18n } = useTranslation(); - const locale = i18n.language as 'en' | 'am'; + const locale = i18n.language as "en" | "am"; const { handleError } = useErrorHandler(); const { id } = useParams<{ id: string }>(); const navigate = useNavigate(); const printRef = useRef(null); - const [recordOpened, { open: openRecord, close: closeRecord }] = useDisclosure(false); - const [assignOpened, { open: openAssign, close: closeAssign }] = useDisclosure(false); + const [recordOpened, { open: openRecord, close: closeRecord }] = + useDisclosure(false); + const [assignOpened, { open: openAssign, close: closeAssign }] = + useDisclosure(false); const [draftQuestions, setDraftQuestions] = useState([]); const [randomCount, setRandomCount] = useState(5); const [updateExam] = useUpdateExamMutation(); - const [assignQuestions, { isLoading: isAssigning }] = useAssignQuestionsMutation(); - - const { data: exam, isLoading, isError } = useGetExamQuery(id ?? '', { skip: !id }); + const [assignQuestions, { isLoading: isAssigning }] = + useAssignQuestionsMutation(); + const { + data: exam, + isLoading, + isError, + } = useGetExamQuery(id ?? "", { skip: !id }); const { data: qRes } = useGetQuestionsQuery(); const { data: certRes } = useGetCertificationsQuery(); const allQuestions = qRes?.items ?? []; const certifications = certRes?.items ?? []; - + const [whatAction, setWhatAction] = useState(); const eligibleQuestions = useMemo(() => { if (!exam) return []; return allQuestions - .filter((q) => q.certificationId === exam.certificationId && q.form === exam.form) - .map((q) => ({ id: q.id, title: q.title, form: q.form, points: q.points })); + .filter( + (q) => + q.certificationId === exam.certificationId && q.form === exam.form, + ) + .map((q) => ({ + id: q.id, + title: q.title, + form: q.form, + points: q.points, + })); // eslint-disable-next-line react-hooks/exhaustive-deps }, [allQuestions, exam?.certificationId, exam?.form]); - if (isLoading) return
; + if (isLoading) + return ( +
+ +
+ ); if (isError || !exam) { return ( - - }>{t('exam.notFound')} + + }> + {t("exam.notFound")} + ); } @@ -112,18 +160,24 @@ export function ExamDetailPage() { const handleRandomSelect = () => { const assignedIds = new Set(draftQuestions.map((q) => q.id)); - const currentTotal = draftQuestions.reduce((s, q) => s + Number(q.points), 0); + const currentTotal = draftQuestions.reduce( + (s, q) => s + Number(q.points), + 0, + ); const cuttingPoint = Number(exam.cuttingPoint); const eligible = eligibleQuestions.filter((q) => !assignedIds.has(q.id)); if (eligible.length === 0) { - notify.error('No eligible questions available for random selection'); + notify.error("No eligible questions available for random selection"); return; } - const maxPossible = currentTotal + eligible.reduce((s, q) => s + Number(q.points), 0); + const maxPossible = + currentTotal + eligible.reduce((s, q) => s + Number(q.points), 0); if (maxPossible < cuttingPoint) { - notify.error(`Even all eligible questions combined (${maxPossible} pts) cannot reach the passing mark (${cuttingPoint} pts). Adjust the cutting point or add more questions.`); + notify.error( + `Even all eligible questions combined (${maxPossible} pts) cannot reach the passing mark (${cuttingPoint} pts). Adjust the cutting point or add more questions.`, + ); return; } @@ -141,9 +195,10 @@ export function ExamDetailPage() { } } - const msg = picked.length > targetCount - ? `Selected ${picked.length} questions (${picked.length - targetCount} extra added to meet the ${cuttingPoint} pts passing mark)` - : `Randomly selected ${picked.length} questions`; + const msg = + picked.length > targetCount + ? `Selected ${picked.length} questions (${picked.length - targetCount} extra added to meet the ${cuttingPoint} pts passing mark)` + : `Randomly selected ${picked.length} questions`; setDraftQuestions([...draftQuestions, ...picked]); notify.info(msg); @@ -152,49 +207,73 @@ export function ExamDetailPage() { const handleAssign = async () => { try { const questionIds = draftQuestions.map((q) => q.id); - await assignQuestions({ examId: exam.id, questionIds, remark: undefined }).unwrap(); - notify.success('Questions assigned'); - closeAssign(); + if (whatAction === "add") { + await assignQuestions({ + examId: exam.id, + questionIds, + remark: undefined, + }).unwrap(); + notify.success("Questions assigned"); + closeAssign(); + } else if (whatAction === "remove") { + // await removeQuestions({ + // examId: exam.id, + // questionIds, + // remark: undefined, + // }); + // notify.success("Questions removed"); + // closeAssign(); + notify.warning("no action yet"); + } } catch (e) { handleError(e); } }; - const handlePrint = async () => { - const total = (exam.questions ?? []).reduce((s, q) => s + Number(q.points), 0); + const total = (exam.questions ?? []).reduce( + (s, q) => s + Number(q.points), + 0, + ); if (total < Number(exam.cuttingPoint)) { - notify.error(`Total question marks (${total}) is less than the passing mark (${exam.cuttingPoint}). Add more questions or adjust the cutting point before printing.`); + notify.error( + `Total question marks (${total}) is less than the passing mark (${exam.cuttingPoint}). Add more questions or adjust the cutting point before printing.`, + ); return; } - const printWindow = window.open('', '_blank'); + const printWindow = window.open("", "_blank"); if (!printWindow) return; - let logoBase64 = ''; + let logoBase64 = ""; try { - const resp = await fetch('/ema-logo.png'); + const resp = await fetch("/ema-logo.png"); const blob = await resp.blob(); logoBase64 = await new Promise((resolve) => { const reader = new FileReader(); reader.onloadend = () => resolve(reader.result as string); reader.readAsDataURL(blob); }); - } catch { /* logo not available */ } + } catch { + /* logo not available */ + } const qMap = new Map(allQuestions.map((qq) => [qq.id, qq])); - const qHtml = (exam.questions ?? []).map((q, i) => { - const full = qMap.get(q.id); - const titleStr = q.title[locale] || q.title.en; - const descStr = full?.description?.[locale] || full?.description?.en || ''; - return ` + const qHtml = (exam.questions ?? []) + .map((q, i) => { + const full = qMap.get(q.id); + const titleStr = q.title[locale] || q.title.en; + const descStr = + full?.description?.[locale] || full?.description?.en || ""; + return `

Question ${i + 1} (${q.points} pts — ${FORM_LABEL[q.form] ?? q.form})

${titleStr}

- ${descStr ? `

${descStr}

` : ''} - ${q.form === 'ESSAY' ? '
'.repeat(3) : ''} - ${q.form === 'CHOICE' ? ['A. ______', 'B. ______', 'C. ______', 'D. ______'].map(l => `

${l}

`).join('') : ''} + ${descStr ? `

${descStr}

` : ""} + ${q.form === "ESSAY" ? '
'.repeat(3) : ""} + ${q.form === "CHOICE" ? ["A. ______", "B. ______", "C. ______", "D. ______"].map((l) => `

${l}

`).join("") : ""}
`; - }).join(''); + }) + .join(""); printWindow.document.write(` ${exam.title[locale] || exam.title.en} @@ -209,13 +288,13 @@ export function ExamDetailPage() { @media print { @page { margin: 20mm; } body { -webkit-print-color-adjust: exact; } }
- ${logoBase64 ? `` : ''} + ${logoBase64 ? `` : ""}

${exam.title[locale] || exam.title.en}

Date: ${exam.date} | Venue: ${exam.venue}

-

Form: ${FORM_LABEL[exam.form]} | Type: ${TYPE_LABEL[exam.type]} | Time Allowed: ${exam.givenTime ? `${exam.givenTime.days}d ${exam.givenTime.hours}h ${exam.givenTime.minutes}m` : 'N/A'}

+

Form: ${FORM_LABEL[exam.form]} | Type: ${TYPE_LABEL[exam.type]} | Time Allowed: ${exam.givenTime ? `${exam.givenTime.days}d ${exam.givenTime.hours}h ${exam.givenTime.minutes}m` : "N/A"}

Pass Mark: ${exam.cuttingPoint} | Total Questions: ${(exam.questions ?? []).length}

- ${exam.direction?.[locale] ? `
Directions: ${exam.direction[locale]}
` : ''} + ${exam.direction?.[locale] ? `
Directions: ${exam.direction[locale]}
` : ""} ${qHtml}
Generated by EMA — Ethiopian Maritime Authority @@ -227,15 +306,25 @@ export function ExamDetailPage() { setTimeout(() => printWindow.print(), 500); }; - const totalPoints = (exam.questions ?? []).reduce((s, q) => s + Number(q.points), 0); - const certName = exam.certification?.name?.[locale] ?? certifications.find((c) => c.id === exam.certificationId)?.name?.[locale] ?? '—'; + const totalPoints = (exam.questions ?? []).reduce( + (s, q) => s + Number(q.points), + 0, + ); + const certName = + exam.certification?.name?.[locale] ?? + certifications.find((c) => c.id === exam.certificationId)?.name?.[locale] ?? + "—"; return ( {/* Header */} - navigate('/exams')}> + navigate("/exams")} + >
@@ -243,41 +332,93 @@ export function ExamDetailPage() {
- -
{/* Status badge */} - + {t(`exam.status.${exam.status}`)} {/* Exam Info */} - {t('exam.detail.title')} + + {t("exam.detail.title")} + - - - - - - - - - - - - + + + + + + + + + + + + {(exam.direction?.en || exam.direction?.am) && ( <> - + )} @@ -285,24 +426,41 @@ export function ExamDetailPage() { {/* Questions */} - {t('exam.detail.questionsSection', { pts: totalPoints })} - {(exam.questions ?? []).length === 0 ? ( }> - {t('exam.noQuestionsAssigned')} + {t("exam.noQuestionsAssigned")} ) : ( {(exam.questions ?? []).map((q, i) => ( - {t('exam.detail.questionLabel')} {i + 1} + + {t("exam.detail.questionLabel")} {i + 1} + - {t(`exam.formType.${q.form}`)} - {q.points} pts + + {t(`exam.formType.${q.form}`)} + + + {q.points} pts + {q.title[locale]} @@ -312,32 +470,50 @@ export function ExamDetailPage() { )} - + {/* Question assignment modal */} - + - {exam.selectionMethod === 'MANUAL' ? ( + {exam.selectionMethod === "MANUAL" ? ( <> - - + + ) : ( <> - {t('exam.assigner.randomHint', { total: eligibleQuestions.length, pts: exam.cuttingPoint })} + {t("exam.assigner.randomHint", { + total: eligibleQuestions.length, + pts: exam.cuttingPoint, + })} setRandomCount(Number(v))} min={1} @@ -346,7 +522,7 @@ export function ExamDetailPage() { style={{ width: 80 }} /> - - + + )} diff --git a/apps/backoffice/src/app/features/exam/pages/ExamPage.tsx b/apps/backoffice/src/app/features/exam/pages/ExamPage.tsx index a31195bfd..a84cfc60a 100644 --- a/apps/backoffice/src/app/features/exam/pages/ExamPage.tsx +++ b/apps/backoffice/src/app/features/exam/pages/ExamPage.tsx @@ -1,5 +1,5 @@ -import { useState } from 'react'; -import { useNavigate } from 'react-router-dom'; +import { useState } from "react"; +import { useNavigate } from "react-router-dom"; import { Stack, Title, @@ -21,27 +21,35 @@ import { Tabs, SimpleGrid, Divider, -} from '@mantine/core'; -import { useDisclosure } from '@mantine/hooks'; -import { useTranslation } from 'react-i18next'; -import { IconEdit, IconTrash, IconPlus, IconInfoCircle, IconCalendar, IconClipboardList } from '@tabler/icons-react'; -import { notify, useErrorHandler } from '@ema-platform/ui'; -import { useGetCertificationsQuery } from '../../certification/api/certification-api'; +} from "@mantine/core"; +import { useDisclosure } from "@mantine/hooks"; +import { useTranslation } from "react-i18next"; +import { + IconEdit, + IconTrash, + IconPlus, + IconInfoCircle, + IconCalendar, + IconClipboardList, + IconDetails, +} from "@tabler/icons-react"; +import { notify, useErrorHandler } from "@ema-platform/ui"; +import { useGetCertificationsQuery } from "../../certification/api/certification-api"; import { useGetExamsQuery, useCreateExamMutation, useUpdateExamMutation, useDeleteExamMutation, -} from '../api/exam-api'; -import type { Exam } from '../types/exam'; +} from "../api/exam-api"; +import type { Exam } from "../types/exam"; const STATUS_COLOR: Record = { - PENDING: 'gray', - ACTIVE: 'blue', - COMPLETED: 'teal', - CANCELLED: 'red', - POSTPONED: 'orange', - PUBLISHED: 'green', + PENDING: "gray", + ACTIVE: "blue", + COMPLETED: "teal", + CANCELLED: "red", + POSTPONED: "orange", + PUBLISHED: "green", }; function ExamForm({ @@ -58,89 +66,285 @@ function ExamForm({ 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 [directionEn, setDirectionEn] = useState(editing?.direction?.en ?? ''); - const [directionAm, setDirectionAm] = useState(editing?.direction?.am ?? ''); - const [date, setDate] = useState(editing?.date ?? ''); + const [certificationId, setCertificationId] = useState( + editing?.certificationId ?? null, + ); + const [titleEn, setTitleEn] = useState(editing?.title?.en ?? ""); + const [titleAm, setTitleAm] = useState(editing?.title?.am ?? ""); + const [directionEn, setDirectionEn] = useState(editing?.direction?.en ?? ""); + const [directionAm, setDirectionAm] = useState(editing?.direction?.am ?? ""); + const [date, setDate] = useState(editing?.date ?? ""); const [days, setDays] = useState(editing?.givenTime?.days ?? 0); const [hours, setHours] = useState(editing?.givenTime?.hours ?? 0); const [minutes, setMinutes] = useState(editing?.givenTime?.minutes ?? 0); const [type, setType] = useState(editing?.type ?? null); const [form, setForm] = useState(editing?.form ?? null); - const [venue, setVenue] = useState(editing?.venue ?? ''); - const [adminMethod, setAdminMethod] = useState(editing?.administrationMethod ?? null); - const [evalMethod, setEvalMethod] = useState(editing?.evaluationMethod ?? null); - const [selMethod, setSelMethod] = useState(editing?.selectionMethod ?? null); - const [cuttingPoint, setCuttingPoint] = useState(editing?.cuttingPoint ?? 0); + const [venue, setVenue] = useState(editing?.venue ?? ""); + const [adminMethod, setAdminMethod] = useState( + editing?.administrationMethod ?? null, + ); + const [evalMethod, setEvalMethod] = useState( + editing?.evaluationMethod ?? null, + ); + const [selMethod, setSelMethod] = useState( + editing?.selectionMethod ?? null, + ); + const [cuttingPoint, setCuttingPoint] = useState( + editing?.cuttingPoint ?? 0, + ); const [status, setStatus] = useState(editing?.status ?? null); const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); - if (!certificationId || !titleEn || !titleAm || !date || !type || !form || !venue || !adminMethod || !evalMethod) { - notify.error('Please fill all required fields'); + if ( + !certificationId || + !titleEn || + !titleAm || + !date || + !type || + !form || + !venue || + !adminMethod || + !evalMethod + ) { + notify.error("Please fill all required fields"); return; } - onSubmit({ - certificationId, titleEn, titleAm, directionEn, directionAm, - date, days, hours, minutes, type, form, venue, adminMethod, evalMethod, selMethod, cuttingPoint, status, - }, !!editing); + onSubmit( + { + certificationId, + titleEn, + titleAm, + directionEn, + directionAm, + date, + days, + hours, + minutes, + type, + form, + venue, + adminMethod, + evalMethod, + selMethod, + cuttingPoint, + status, + }, + !!editing, + ); }; return (
- - - }>{t('exam.form.basicInfo')} - }>{t('exam.form.settings')} - + + + }> + {t("exam.form.basicInfo")} + + } + > + {t("exam.form.settings")} + + - - -