Merge pull request #27 from Tria-plc/WorkflowChange

Workflow change
This commit is contained in:
Nati Nigussie
2026-08-24 15:39:28 +03:00
committed by GitHub
3 changed files with 25 additions and 37 deletions

View File

@@ -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 }));

View File

@@ -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;
} }

View File

@@ -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',