Files
emaui/apps/backoffice/src/app/features/exam/api/exam-api.ts
mihretue 35961ec809 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.
2026-08-24 12:57:37 +00:00

134 lines
4.3 KiB
TypeScript

import { baseApi } from '@ema-platform/api';
import type {
Exam,
ListResponse,
CreateExamPayload,
UpdateExamPayload,
AssignQuestionsPayload,
RandomQuestionsPayload,
ExamRegistration,
RecordAttendancePayload,
ExamIncident,
CreateIncidentPayload,
ResolveIncidentPayload,
RegradeOutcome,
GradingSheet,
} from '../types/exam';
const examApi = baseApi.injectEndpoints({
endpoints: (builder) => ({
getExams: builder.query<ListResponse<Exam>, void>({
query: () => '/exams?q=i=questions',
providesTags: ['Api'],
}),
getExam: builder.query<Exam, string>({
// Nested relation so CHOICE questions carry their options here too —
// needed to print real answer choices instead of blank A/B/C/D lines.
query: (id) => `/exams/${id}?i=questions,questions.options`,
providesTags: ['Api'],
}),
createExam: builder.mutation<Exam, CreateExamPayload>({
query: (body) => ({ url: '/exams', method: 'POST', body }),
invalidatesTags: ['Api'],
}),
updateExam: builder.mutation<Exam, UpdateExamPayload>({
query: ({ id, ...body }) => ({
url: `/exams/${id}`,
method: 'PUT',
body,
}),
invalidatesTags: ['Api'],
}),
deleteExam: builder.mutation<void, string>({
query: (id) => ({ url: `/exams/${id}`, method: 'DELETE' }),
invalidatesTags: ['Api'],
}),
assignQuestions: builder.mutation<Exam, AssignQuestionsPayload>({
query: ({ examId, questionIds, remark }) => ({
url: `/exams/${examId}/questions`,
method: 'POST',
body: { examId, questionIds, remark },
}),
invalidatesTags: ['Api'],
}),
/** Server-side draw from the approved bank (US-EXAM-005). */
selectRandomQuestions: builder.mutation<Exam, RandomQuestionsPayload>({
query: ({ examId, count }) => ({
url: `/exams/${examId}/questions/random`,
method: 'POST',
body: { count },
}),
invalidatesTags: ['Api'],
}),
// --- Candidates and attendance (US-EXAM-007/009) ---------------------
getExamRegistrations: builder.query<ExamRegistration[], string>({
query: (examId) => `/exams/${examId}/registrations`,
providesTags: ['Api'],
}),
recordAttendance: builder.mutation<ExamRegistration, RecordAttendancePayload>({
query: ({ registrationId, ...body }) => ({
url: `/exams/registrations/${registrationId}/attendance`,
method: 'POST',
body,
}),
invalidatesTags: ['Api'],
}),
// --- Session incidents (US-EXAM-010) ---------------------------------
getExamIncidents: builder.query<ExamIncident[], string>({
query: (examId) => `/exams/${examId}/incidents`,
providesTags: ['Api'],
}),
recordIncident: builder.mutation<ExamIncident, CreateIncidentPayload>({
query: ({ examId, ...body }) => ({
url: `/exams/${examId}/incidents`,
method: 'POST',
body,
}),
invalidatesTags: ['Api'],
}),
resolveIncident: builder.mutation<ExamIncident, ResolveIncidentPayload>({
query: ({ incidentId, ...body }) => ({
url: `/exams/incidents/${incidentId}/resolve`,
method: 'POST',
body,
}),
invalidatesTags: ['Api'],
}),
/** Staff-triggered re-run of auto-grading for one finalized attempt. */
regradeAttempt: builder.mutation<RegradeOutcome, string>({
query: (attemptId) => ({
url: `/exam-attempts/${attemptId}/regrade`,
method: 'POST',
}),
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,
});
export const {
useGetExamsQuery,
useGetExamQuery,
useCreateExamMutation,
useUpdateExamMutation,
useDeleteExamMutation,
useAssignQuestionsMutation,
useSelectRandomQuestionsMutation,
useGetExamRegistrationsQuery,
useRecordAttendanceMutation,
useGetExamIncidentsQuery,
useRecordIncidentMutation,
useResolveIncidentMutation,
useRegradeAttemptMutation,
useGetGradingSheetQuery,
} = examApi;