From 5ad43ba7609e6c29b8e19b4c9961cbf9186b6350 Mon Sep 17 00:00:00 2001 From: mihretue Date: Wed, 26 Aug 2026 06:23:06 +0000 Subject: [PATCH] fix(exam): step the exam form instead of validating hidden tabs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Submitting from Basic Info complained that required fields on the Settings tab were empty — a correct error the user could not act on, since Settings had not been shown yet. The primary action now reads "Next" while Basic Info is open and validates only that tab before moving on; Create/Update appears on Settings, the final step, alongside a Back button. Submit still checks both tabs, since the tab headers stay clickable and a user can reach Settings without going through Next. --- .../features/exam/pages/ExamPage/index.tsx | 66 +++++++++++++++---- apps/backoffice/src/app/i18n/locales/am.ts | 2 + apps/backoffice/src/app/i18n/locales/en.ts | 2 + 3 files changed, 57 insertions(+), 13 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 34039328c..1bdf2c247 100644 --- a/apps/backoffice/src/app/features/exam/pages/ExamPage/index.tsx +++ b/apps/backoffice/src/app/features/exam/pages/ExamPage/index.tsx @@ -79,21 +79,44 @@ function ExamForm({ const [status, setStatus] = useState(editing?.status ?? null); const [activeTab, setActiveTab] = useState("basic"); - const handleSubmit = (e: React.FormEvent) => { - e.preventDefault(); + /** + * Split per tab so "Next" can check just the tab in front of the user. + * Submitting from Basic Info used to complain about Settings fields the + * user had not been shown yet — the error was correct and unactionable at + * the same time. Each returns the message key for what is missing, or null. + */ + const validateBasic = (): string | null => { if (!certificationId || !titleEn || !titleAm || !date || !venue) { - setActiveTab("basic"); - notify.error(t("exam.form.fillRequiredBasic")); - return; + return "exam.form.fillRequiredBasic"; } if ((directionEn || directionAm) && !(directionEn && directionAm)) { - setActiveTab("basic"); - notify.error(t("exam.form.directionBothLanguages")); + return "exam.form.directionBothLanguages"; + } + return null; + }; + + const validateSettings = (): string | null => + !type || !form || !adminMethod || !evalMethod || !cuttingPoint + ? "exam.form.fillRequiredSettings" + : null; + + const goNext = () => { + const error = validateBasic(); + if (error) { + notify.error(t(error)); return; } - if (!type || !form || !adminMethod || !evalMethod || !cuttingPoint) { - setActiveTab("settings"); - notify.error(t("exam.form.fillRequiredSettings")); + setActiveTab("settings"); + }; + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + // Still checks both: the tabs are clickable, so a user can reach Settings + // without going through Next. + const error = validateBasic() ?? validateSettings(); + if (error) { + setActiveTab(error === "exam.form.fillRequiredSettings" ? "settings" : "basic"); + notify.error(t(error)); return; } onSubmit( @@ -337,9 +360,26 @@ function ExamForm({ - + {activeTab === "basic" ? ( + /* Not type="submit": Basic Info is not the last step, so the + primary action advances rather than saves. */ + + ) : ( + <> + + + + )} diff --git a/apps/backoffice/src/app/i18n/locales/am.ts b/apps/backoffice/src/app/i18n/locales/am.ts index c8e51193d..9bdb4e114 100644 --- a/apps/backoffice/src/app/i18n/locales/am.ts +++ b/apps/backoffice/src/app/i18n/locales/am.ts @@ -271,6 +271,8 @@ export const am: Translations = { cuttingPointPercentageHint: "የመቶኛ ግምገማ — ከ100 አይበልጥም።", fillRequiredBasic: "በመሠረታዊ መረጃ ውስጥ ያሉ አስፈላጊ መስኮችን ይሙሉ።", fillRequiredSettings: "በቅንብሮች ውስጥ ያሉ አስፈላጊ መስኮችን ይሙሉ — ዓይነት፣ ቅጽ፣ የአስተዳደር ዘዴ፣ የግምገማ ዘዴ እና የማለፊያ ነጥብ።", + next: "ቀጣይ", + back: "ተመለስ", directionBothLanguages: "መመሪያ በሁለቱም እንግሊዝኛ እና አማርኛ ጽሑፍ ያስፈልገዋል፣ ወይም ሁለቱንም ባዶ ይተዉ።", status: "ሁኔታ", statusPlaceholder: "የፈተና ሁኔታ", diff --git a/apps/backoffice/src/app/i18n/locales/en.ts b/apps/backoffice/src/app/i18n/locales/en.ts index 7152067ce..ceb6e6c38 100644 --- a/apps/backoffice/src/app/i18n/locales/en.ts +++ b/apps/backoffice/src/app/i18n/locales/en.ts @@ -271,6 +271,8 @@ export const en = { 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.', + next: 'Next', + back: 'Back', status: 'Status', statusPlaceholder: 'Exam status', pending: 'Pending',