mirror of
https://github.com/Tria-plc/emaui.git
synced 2026-08-26 19:12:50 +00:00
feat(exam): let ONLINE exams take ESSAY/BOTH form, add grading-sheet API
Frontend half of the backend change: ExamPage no longer disables the form Select or force-resets it to CHOICE when administrationMethod is ONLINE (backend's assertOnlineIsChoiceOnly is gone, matching gate removed here). Also wires up GET .../grading-sheet as useGetGradingSheetQuery — RecordResultModal wiring (show candidate answers, prefill auto-computed CHOICE scores) is next.
This commit is contained in:
@@ -12,6 +12,7 @@ import type {
|
||||
CreateIncidentPayload,
|
||||
ResolveIncidentPayload,
|
||||
RegradeOutcome,
|
||||
GradingSheet,
|
||||
} from '../types/exam';
|
||||
|
||||
const examApi = baseApi.injectEndpoints({
|
||||
@@ -101,6 +102,15 @@ const examApi = baseApi.injectEndpoints({
|
||||
}),
|
||||
invalidatesTags: ['Api'],
|
||||
}),
|
||||
/** The candidate's answers plus auto-computable CHOICE scores, for RecordResultModal. */
|
||||
getGradingSheet: builder.query<
|
||||
GradingSheet,
|
||||
{ examId: string; profileId: string }
|
||||
>({
|
||||
query: ({ examId, profileId }) =>
|
||||
`/exam-attempts/exam/${examId}/candidate/${profileId}/grading-sheet`,
|
||||
providesTags: ['Api'],
|
||||
}),
|
||||
}),
|
||||
overrideExisting: false,
|
||||
});
|
||||
@@ -119,4 +129,5 @@ export const {
|
||||
useRecordIncidentMutation,
|
||||
useResolveIncidentMutation,
|
||||
useRegradeAttemptMutation,
|
||||
useGetGradingSheetQuery,
|
||||
} = examApi;
|
||||
|
||||
@@ -255,12 +255,6 @@ function ExamForm({
|
||||
onChange={setForm}
|
||||
size="sm"
|
||||
required
|
||||
disabled={adminMethod === "ONLINE"}
|
||||
description={
|
||||
adminMethod === "ONLINE"
|
||||
? t("exam.form.onlineChoiceOnlyHint")
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
<Select
|
||||
label={t("exam.detail.administration")}
|
||||
@@ -270,14 +264,7 @@ function ExamForm({
|
||||
{ value: "ONLINE", label: t("exam.form.online") },
|
||||
]}
|
||||
value={adminMethod}
|
||||
onChange={(value) => {
|
||||
setAdminMethod(value);
|
||||
// Online exams are graded automatically, and that only
|
||||
// has an answer model for CHOICE — matches the backend
|
||||
// rule (online_exam_requires_choice_form), not just a
|
||||
// UI nicety.
|
||||
if (value === "ONLINE") setForm("CHOICE");
|
||||
}}
|
||||
onChange={setAdminMethod}
|
||||
size="sm"
|
||||
required
|
||||
/>
|
||||
|
||||
@@ -136,6 +136,24 @@ export type RegradeOutcome =
|
||||
| { graded: true; resultId: string }
|
||||
| { graded: false; reason: string };
|
||||
|
||||
/** One question's row on the staff grading sheet — the candidate's own
|
||||
* answer plus the auto-computable score, where one exists. */
|
||||
export interface GradingSheetQuestion {
|
||||
questionId: string;
|
||||
form: QuestionForm;
|
||||
points: number;
|
||||
answerText: string | null;
|
||||
selectedOptionId: string | null;
|
||||
selectedOptionText: { en?: string; am?: string } | null;
|
||||
/** null means "no auto-score" — examiner enters one by hand. */
|
||||
autoScore: number | null;
|
||||
}
|
||||
|
||||
export interface GradingSheet {
|
||||
attemptStatus: 'IN_PROGRESS' | 'SUBMITTED' | 'EXPIRED';
|
||||
questions: GradingSheetQuestion[];
|
||||
}
|
||||
|
||||
export interface RecordAttendancePayload {
|
||||
registrationId: string;
|
||||
status: AttendanceStatus;
|
||||
|
||||
@@ -260,7 +260,6 @@ export const am: Translations = {
|
||||
both: "ሁለቱም",
|
||||
offline: "ከመስመር ውጪ",
|
||||
online: "በመስመር",
|
||||
onlineChoiceOnlyHint: "የመስመር ላይ ፈተናዎች በራስ-ሰር ይመዘገባሉ፣ ይህም ለምርጫ ጥያቄዎች ብቻ ይሰራል።",
|
||||
sum: "ድምር",
|
||||
average: "አማካይ",
|
||||
percentage: "መቶኛ",
|
||||
|
||||
@@ -259,7 +259,6 @@ export const en = {
|
||||
both: 'Both',
|
||||
offline: 'Offline',
|
||||
online: 'Online',
|
||||
onlineChoiceOnlyHint: 'Online exams are graded automatically, which only works for multiple choice.',
|
||||
sum: 'Sum',
|
||||
average: 'Average',
|
||||
percentage: 'Percentage',
|
||||
|
||||
Reference in New Issue
Block a user