mirror of
https://github.com/Tria-plc/emaui.git
synced 2026-08-26 13:02:50 +00:00
@@ -121,13 +121,18 @@ export function ExamDetailPage() {
|
|||||||
|
|
||||||
// Only approved bank items may go on a paper (US-EXAM-003), so the picker
|
// Only approved bank items may go on a paper (US-EXAM-003), so the picker
|
||||||
// must not offer drafts or retired questions either.
|
// must not offer drafts or retired questions either.
|
||||||
|
//
|
||||||
|
// BOTH describes a mixed paper — a question itself is never "BOTH" (see
|
||||||
|
// QuestionForm), so an equality check against it would match nothing and
|
||||||
|
// silently offer zero questions. Same skip-condition as the backend's own
|
||||||
|
// random draw (ExamService.selectRandomQuestions).
|
||||||
const eligibleQuestions = useMemo(() => {
|
const eligibleQuestions = useMemo(() => {
|
||||||
if (!exam) return [];
|
if (!exam) return [];
|
||||||
return allQuestions
|
return allQuestions
|
||||||
.filter(
|
.filter(
|
||||||
(q) =>
|
(q) =>
|
||||||
q.certificationId === exam.certificationId &&
|
q.certificationId === exam.certificationId &&
|
||||||
q.form === exam.form &&
|
(exam.form === 'BOTH' || q.form === exam.form) &&
|
||||||
q.status === 'APPROVED',
|
q.status === 'APPROVED',
|
||||||
)
|
)
|
||||||
.map((q) => ({ id: q.id, title: q.title, form: q.form, points: q.points }));
|
.map((q) => ({ id: q.id, title: q.title, form: q.form, points: q.points }));
|
||||||
|
|||||||
@@ -3,6 +3,11 @@ import type { EstimatedTime } from "../../question/types/question";
|
|||||||
import type { QuestionForm } from "../../question/types/question";
|
import type { QuestionForm } from "../../question/types/question";
|
||||||
export type { QuestionForm };
|
export type { QuestionForm };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The paper's own form — unlike a question's (QuestionForm), an exam may mix
|
||||||
|
* both: BOTH means a mixed paper, resolved per-question against each
|
||||||
|
* question's own ESSAY/CHOICE form. Mirrors backend EExamForm.
|
||||||
|
*/
|
||||||
export type ExamForm = QuestionForm | "BOTH";
|
export type ExamForm = QuestionForm | "BOTH";
|
||||||
|
|
||||||
export type ExamType = "WRITTEN" | "ORAL";
|
export type ExamType = "WRITTEN" | "ORAL";
|
||||||
@@ -10,12 +15,7 @@ export type ExamAdministrationMethod = "OFFLINE" | "ONLINE";
|
|||||||
export type ExamEvaluationMethod = "SUM" | "AVERAGE" | "PERCENTAGE";
|
export type ExamEvaluationMethod = "SUM" | "AVERAGE" | "PERCENTAGE";
|
||||||
export type ExamSelectionMethod = "MANUAL" | "RANDOM";
|
export type ExamSelectionMethod = "MANUAL" | "RANDOM";
|
||||||
export type ExamStatus =
|
export type ExamStatus =
|
||||||
| "PENDING"
|
"PENDING" | "ACTIVE" | "COMPLETED" | "CANCELLED" | "POSTPONED" | "PUBLISHED";
|
||||||
| "ACTIVE"
|
|
||||||
| "COMPLETED"
|
|
||||||
| "CANCELLED"
|
|
||||||
| "POSTPONED"
|
|
||||||
| "PUBLISHED";
|
|
||||||
|
|
||||||
/** Only populated when the exam is fetched with `?i=questions,questions.options`. */
|
/** Only populated when the exam is fetched with `?i=questions,questions.options`. */
|
||||||
export interface QuestionOptionBrief {
|
export interface QuestionOptionBrief {
|
||||||
@@ -103,19 +103,14 @@ export interface RandomQuestionsPayload {
|
|||||||
|
|
||||||
/** What the invigilator recorded on the day (US-EXAM-009). */
|
/** What the invigilator recorded on the day (US-EXAM-009). */
|
||||||
export type AttendanceStatus =
|
export type AttendanceStatus =
|
||||||
| 'REGISTERED'
|
"REGISTERED" | "PRESENT" | "ABSENT" | "LATE" | "WITHDRAWN" | "DISQUALIFIED";
|
||||||
| 'PRESENT'
|
|
||||||
| 'ABSENT'
|
|
||||||
| 'LATE'
|
|
||||||
| 'WITHDRAWN'
|
|
||||||
| 'DISQUALIFIED';
|
|
||||||
|
|
||||||
export interface ExamRegistration {
|
export interface ExamRegistration {
|
||||||
id: string;
|
id: string;
|
||||||
examId: string;
|
examId: string;
|
||||||
profileId: string;
|
profileId: string;
|
||||||
admissionNumber: string;
|
admissionNumber: string;
|
||||||
kind: 'NEW' | 'RETAKE';
|
kind: "NEW" | "RETAKE";
|
||||||
attemptNumber: number;
|
attemptNumber: number;
|
||||||
attendanceStatus: AttendanceStatus;
|
attendanceStatus: AttendanceStatus;
|
||||||
attendanceRemark: string | null;
|
attendanceRemark: string | null;
|
||||||
@@ -129,12 +124,14 @@ export interface ExamRegistration {
|
|||||||
seafarerNumber: string | null;
|
seafarerNumber: string | null;
|
||||||
};
|
};
|
||||||
/** The candidate's online sitting, when one has been started. */
|
/** The candidate's online sitting, when one has been started. */
|
||||||
attempt?: { id: string; status: 'IN_PROGRESS' | 'SUBMITTED' | 'EXPIRED' } | null;
|
attempt?: {
|
||||||
|
id: string;
|
||||||
|
status: "IN_PROGRESS" | "SUBMITTED" | "EXPIRED";
|
||||||
|
} | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type RegradeOutcome =
|
export type RegradeOutcome =
|
||||||
| { graded: true; resultId: string }
|
{ graded: true; resultId: string } | { graded: false; reason: string };
|
||||||
| { graded: false; reason: string };
|
|
||||||
|
|
||||||
export interface RecordAttendancePayload {
|
export interface RecordAttendancePayload {
|
||||||
registrationId: string;
|
registrationId: string;
|
||||||
@@ -143,17 +140,10 @@ export interface RecordAttendancePayload {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type ExamIncidentType =
|
export type ExamIncidentType =
|
||||||
| 'MISCONDUCT'
|
"MISCONDUCT" | "TECHNICAL_FAILURE" | "MEDICAL" | "ADMINISTRATIVE" | "OTHER";
|
||||||
| 'TECHNICAL_FAILURE'
|
|
||||||
| 'MEDICAL'
|
|
||||||
| 'ADMINISTRATIVE'
|
|
||||||
| 'OTHER';
|
|
||||||
|
|
||||||
export type ExamIncidentStatus =
|
export type ExamIncidentStatus =
|
||||||
| 'OPEN'
|
"OPEN" | "UNDER_REVIEW" | "RESOLVED" | "DISMISSED";
|
||||||
| 'UNDER_REVIEW'
|
|
||||||
| 'RESOLVED'
|
|
||||||
| 'DISMISSED';
|
|
||||||
|
|
||||||
export interface ExamIncident {
|
export interface ExamIncident {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -178,6 +168,6 @@ export interface CreateIncidentPayload {
|
|||||||
|
|
||||||
export interface ResolveIncidentPayload {
|
export interface ResolveIncidentPayload {
|
||||||
incidentId: string;
|
incidentId: string;
|
||||||
outcome: 'RESOLVED' | 'DISMISSED';
|
outcome: "RESOLVED" | "DISMISSED";
|
||||||
resolution: string;
|
resolution: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,16 +65,9 @@ export interface ActionDefinition {
|
|||||||
*/
|
*/
|
||||||
export const ACTIONS: ActionDefinition[] = [
|
export const ACTIONS: ActionDefinition[] = [
|
||||||
// ------------------------------------------------------------- workflow
|
// ------------------------------------------------------------- workflow
|
||||||
{
|
// Claim is deliberately absent here: an officer claims from the queue
|
||||||
id: 'claim',
|
// (LicenseQueuePage), not from this detail page. That implementation is
|
||||||
tier: 'workflow',
|
// separate — see LicenseQueuePage/actions.tsx — and is unaffected by this.
|
||||||
labelKey: 'review.actions.claim',
|
|
||||||
// Mirrors the CLAIM transition's `from` list: an examined cert (CoC/CoP)
|
|
||||||
// sits in ELIGIBILITY_PAID once its eligibility fee clears, not SUBMITTED.
|
|
||||||
from: ['SUBMITTED', 'ELIGIBILITY_PAID'],
|
|
||||||
permissions: ['can:claim:license-application'],
|
|
||||||
emphasis: 'light',
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
id: 'assign',
|
id: 'assign',
|
||||||
tier: 'workflow',
|
tier: 'workflow',
|
||||||
|
|||||||
Reference in New Issue
Block a user