Skip to content

Enhance exam functionality with layout, navigation, and submission - #2

Merged
AS1100K merged 9 commits into
masterfrom
next
Apr 22, 2026
Merged

Enhance exam functionality with layout, navigation, and submission#2
AS1100K merged 9 commits into
masterfrom
next

Conversation

@AS1100K

@AS1100K AS1100K commented Apr 16, 2026

Copy link
Copy Markdown
Owner

No description provided.

@AS1100K
AS1100K requested a review from Copilot April 16, 2026 22:18
@AS1100K AS1100K self-assigned this Apr 16, 2026
@AS1100K AS1100K added the enhancement New feature or request label Apr 16, 2026

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds core student exam-taking flow (start exam, question rendering, navigation overview, saving responses, and submission) across the frontend and backend.

Changes:

  • Implemented student exam UI components for section overview, question display, navigation grid, and submission.
  • Added backend endpoints to start an exam session, save a response (upsert), and submit an exam.
  • Updated DB schema to enforce one response per (test_session, question) via a unique constraint.

Reviewed changes

Copilot reviewed 9 out of 10 changed files in this pull request and generated 14 comments.

Show a summary per file
File Description
frontend/src/components/dashboard/student/ExamQuestion.jsx New question UI with answer selection, mark-for-review, and save flow.
frontend/src/components/dashboard/student/ExamOverview.jsx New per-section question navigation grid and instructions shortcut.
frontend/src/components/dashboard/student/ExamNavigation.jsx Adds timer display and submit action to the exam header.
frontend/src/components/dashboard/student/ExamInfo.jsx Starts exam via API and initializes session state (status/start time/sections).
frontend/src/components/dashboard/StudentExam.jsx Wires exam status state machine and lays out question + overview panes.
docker/mysql/init.sql Adds unique constraint to support response upsert semantics.
backend/routes/api/student.js Expands start_exam payload; adds save_response and submit endpoints.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread backend/routes/api/student.js
Comment thread frontend/src/components/dashboard/student/ExamQuestion.jsx
Comment on lines +70 to +73
selected_option:
status === "marked_for_review"
? null
: (selectedOption ?? null),

Copilot AI Apr 16, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Marking a question for review sets selected_option to null in state (status === "marked_for_review" ? null : ...). This will clear an already selected answer when the user navigates away/back, which is surprising for “mark for review” flows (they usually preserve the current response). Consider keeping the existing selected_option and tracking review status separately.

Suggested change
selected_option:
status === "marked_for_review"
? null
: (selectedOption ?? null),
selected_option: selectedOption ?? null,

Copilot uses AI. Check for mistakes.
Comment thread frontend/src/components/dashboard/StudentExam.jsx
Comment thread backend/routes/api/student.js
Comment on lines +267 to +288
const { question_id, selected_option } = req.body;

try {
if (selected_option === null) {
await pool.query(
"DELETE FROM student_response WHERE question_id=? AND test_session_id=?",
[question_id, test_session.id],
);

return res.status(200).send({
status: 200,
success: true,
});
}

await pool.query(
"INSERT INTO student_response (question_id, test_session_id, selected_option) VALUES (?, ?, ?) \
ON DUPLICATE KEY UPDATE \
selected_option = VALUES(selected_option), \
saved_at = CURRENT_TIMESTAMP",
[question_id, test_session.id, selected_option],
);

Copilot AI Apr 16, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/save_response accepts question_id from the client but does not verify that the question belongs to the student’s assigned exam/session (it only uses test_session.id for the response row). A student could submit responses for arbitrary question IDs. Add a server-side check that question_id is part of the current session’s exam (e.g., join questions -> sections -> exams using the session’s exam_id) before insert/delete.

Copilot uses AI. Check for mistakes.
Comment thread backend/routes/api/student.js Outdated
Comment on lines +121 to +127
if (
currentQuestionIndex === currentSection.questions.length - 1 &&
currentSectionIndex < sections.length
) {
setCurrentSectionIndex(currentSectionIndex + 1);
setCurrentQuestionIndex(-1);
} else if (currentQuestionIndex < currentSection.questions.length) {

Copilot AI Apr 16, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

handleNext has off-by-one conditions that can set indexes out of bounds: currentSectionIndex < sections.length allows incrementing to sections.length, and currentQuestionIndex < currentSection.questions.length allows incrementing to questions.length. That can make currentQuestion undefined and crash rendering (e.g., currentQuestion.status). Tighten the comparisons to sections.length - 1 / questions.length - 1 and/or clamp before updating state.

Suggested change
if (
currentQuestionIndex === currentSection.questions.length - 1 &&
currentSectionIndex < sections.length
) {
setCurrentSectionIndex(currentSectionIndex + 1);
setCurrentQuestionIndex(-1);
} else if (currentQuestionIndex < currentSection.questions.length) {
if (!currentSection?.questions?.length || sections.length === 0) {
return;
}
const lastQuestionIndex = currentSection.questions.length - 1;
const lastSectionIndex = sections.length - 1;
if (
currentQuestionIndex === lastQuestionIndex &&
currentSectionIndex < lastSectionIndex
) {
setCurrentSectionIndex(currentSectionIndex + 1);
setCurrentQuestionIndex(0);
} else if (currentQuestionIndex < lastQuestionIndex) {

Copilot uses AI. Check for mistakes.
Comment on lines +16 to +29
{section.instructions !== null && (
<Button
size="small"
fullWidth
variant="outlined"
sx={{ color: "gray", borderColor: "gray" }}
onClick={() => {
setCurrentSectionIndex(i);
setCurrentQuestionIndex(-1);
}}
>
Instructions
</Button>
)}

Copilot AI Apr 16, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The “Instructions” button is shown whenever section.instructions !== null, which will also be true for undefined or an empty string. Use a stricter check (e.g., truthy / non-empty string) so you don’t navigate to an empty instructions view.

Suggested change
{section.instructions !== null && (
<Button
size="small"
fullWidth
variant="outlined"
sx={{ color: "gray", borderColor: "gray" }}
onClick={() => {
setCurrentSectionIndex(i);
setCurrentQuestionIndex(-1);
}}
>
Instructions
</Button>
)}
{typeof section.instructions === "string" &&
section.instructions.trim() !== "" && (
<Button
size="small"
fullWidth
variant="outlined"
sx={{ color: "gray", borderColor: "gray" }}
onClick={() => {
setCurrentSectionIndex(i);
setCurrentQuestionIndex(-1);
}}
>
Instructions
</Button>
)}

Copilot uses AI. Check for mistakes.
Comment thread backend/routes/api/student.js
@AS1100K
AS1100K merged commit 2c64027 into master Apr 22, 2026
4 checks passed
@AS1100K
AS1100K deleted the next branch April 22, 2026 04:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants