From 4f692c6bcf8a3e36d8ac90b3ae771be01009af87 Mon Sep 17 00:00:00 2001 From: mihretue Date: Mon, 17 Aug 2026 08:48:32 +0000 Subject: [PATCH] fix(exam): stop Create/Update Exam form from silently failing to submit Three bugs compounded into a button that looked unwired: - cuttingPoint carried a native HTML required attribute while sitting on the hidden Settings tab (Mantine keeps inactive tab panels mounted with display:none). The browser blocked form submission entirely, silently - no toast, no error, nothing. Replaced required with withAsterisk and moved validation into JS. - Manual required-field validation never checked type/form/adminMethod/ evalMethod/cuttingPoint at all, so even without the native block above a half-filled Settings tab would silently no-op. Validation now covers every required field on both tabs and switches to whichever tab has the problem. - Filling only the English Direction field (leaving Amharic blank) passed frontend validation but the backend hard-rejects it, producing a 400 with a confusing error. Frontend now requires both-or-neither, matching the same rule already applied to MCQ options. Also translates the previously hardcoded validation message in both locales. Co-Authored-By: Claude Sonnet 5 --- .../features/exam/pages/ExamPage/index.tsx | 30 ++++++++++--------- apps/backoffice/src/app/i18n/locales/am.ts | 3 ++ apps/backoffice/src/app/i18n/locales/en.ts | 3 ++ 3 files changed, 22 insertions(+), 14 deletions(-) diff --git a/apps/backoffice/src/app/features/exam/pages/ExamPage/index.tsx b/apps/backoffice/src/app/features/exam/pages/ExamPage/index.tsx index c34e5975b..036d5f946 100644 --- a/apps/backoffice/src/app/features/exam/pages/ExamPage/index.tsx +++ b/apps/backoffice/src/app/features/exam/pages/ExamPage/index.tsx @@ -77,21 +77,23 @@ function ExamForm({ editing?.cuttingPoint ?? 0, ); const [status, setStatus] = useState(editing?.status ?? null); + const [activeTab, setActiveTab] = useState("basic"); 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 || !venue) { + setActiveTab("basic"); + notify.error(t("exam.form.fillRequiredBasic")); + return; + } + if ((directionEn || directionAm) && !(directionEn && directionAm)) { + setActiveTab("basic"); + notify.error(t("exam.form.directionBothLanguages")); + return; + } + if (!type || !form || !adminMethod || !evalMethod || !cuttingPoint) { + setActiveTab("settings"); + notify.error(t("exam.form.fillRequiredSettings")); return; } onSubmit( @@ -121,7 +123,7 @@ function ExamForm({ return (
- + }> {t("exam.form.basicInfo")} @@ -296,7 +298,7 @@ function ExamForm({ onChange={(v) => setCuttingPoint(Number(v))} min={0} size="sm" - required + withAsterisk /> {editing && ( diff --git a/apps/backoffice/src/app/i18n/locales/am.ts b/apps/backoffice/src/app/i18n/locales/am.ts index ed93619ff..2d2405ea1 100644 --- a/apps/backoffice/src/app/i18n/locales/am.ts +++ b/apps/backoffice/src/app/i18n/locales/am.ts @@ -254,6 +254,9 @@ export const am: Translations = { random: "በዘፈቀደ", cuttingPoint: "የማለፊያ ነጥብ", cuttingPointPlaceholder: "ለማለፍ ዝቅተኛ ነጥብ", + fillRequiredBasic: "በመሠረታዊ መረጃ ውስጥ ያሉ አስፈላጊ መስኮችን ይሙሉ።", + fillRequiredSettings: "በቅንብሮች ውስጥ ያሉ አስፈላጊ መስኮችን ይሙሉ — ዓይነት፣ ቅጽ፣ የአስተዳደር ዘዴ፣ የግምገማ ዘዴ እና የማለፊያ ነጥብ።", + directionBothLanguages: "መመሪያ በሁለቱም እንግሊዝኛ እና አማርኛ ጽሑፍ ያስፈልገዋል፣ ወይም ሁለቱንም ባዶ ይተዉ።", status: "ሁኔታ", statusPlaceholder: "የፈተና ሁኔታ", pending: "በመጠባበቅ ላይ", diff --git a/apps/backoffice/src/app/i18n/locales/en.ts b/apps/backoffice/src/app/i18n/locales/en.ts index bce0a87d9..adacf10a8 100644 --- a/apps/backoffice/src/app/i18n/locales/en.ts +++ b/apps/backoffice/src/app/i18n/locales/en.ts @@ -251,6 +251,9 @@ export const en = { random: 'Random', cuttingPoint: 'Cutting Point (Pass Mark)', cuttingPointPlaceholder: 'Minimum score to pass', + fillRequiredBasic: 'Please fill all required fields in Basic Info.', + fillRequiredSettings: 'Please fill all required fields in Settings — type, form, administration method, evaluation method, and cutting point.', + directionBothLanguages: 'Direction needs text in both English and Amharic, or leave both empty.', status: 'Status', statusPlaceholder: 'Exam status', pending: 'Pending',