Skip to content
Snippets Groups Projects
Commit 9c4fdf22 authored by yazSpaz's avatar yazSpaz
Browse files

added option to filter out students without test attempt

parent 95c92150
Branches
No related tags found
No related merge requests found
......@@ -194,6 +194,7 @@ def getTestDetails():
mcq_questions = TestDAO.getMCQQuestionsByTest(test_id, db)
fib_questions = TestDAO.getFIBQuestionsByTest(test_id, db)
students = TestDAO.getStudentsByTest(test_id, db)
test_attempts = TestDAO.getAttemptsByTest(test_id,db)
......
......@@ -122,6 +122,8 @@ $(document).ready(function () {
studentResultTable.clear().draw();
$("#difficulty").prop("disabled", true);
$("#objective").prop("disabled", true);
$("#onlyAttempts").prop("disabled", true);
$("#onlyAttempts").prop("checked", false);
$("#score").text("N/A"); // Reset Score
$("#timetaken").text("N/A"); // Reset Time Taken
......@@ -161,6 +163,8 @@ $(document).ready(function () {
studentResultTable.clear().draw();
$("#difficulty").prop("disabled", true);
$("#objective").prop("disabled", true);
$("#onlyAttempts").prop("disabled", true);
$("#onlyAttempts").prop("checked", false);
$("#score").text("N/A"); // Reset Score
$("#timetaken").text("N/A"); // Reset Time Taken
......@@ -193,8 +197,11 @@ $(document).ready(function () {
// Reset questions dropdown
questionsDropdown.prop("disabled", true).html('<option value="" disabled selected>No Questions Available</option>');
studentsDropdown.prop("disabled", true).html('<option value="" disabled selected>No Students Available</option>');
$("#onlyAttempts").prop("disabled", true);
$("#onlyAttempts").prop("checked", false);
$("#difficulty").prop("disabled", true);
$("#objective").prop("disabled", true);
$(".selectpicker").selectpicker('refresh');
// **CLEAR TABLE ENTRIES** before loading new data
studentResultTable.clear().draw();
......@@ -282,6 +289,7 @@ $(document).ready(function () {
studentsDropdown.prop("disabled", false);
$("#difficulty").prop("disabled", false);
$("#objective").prop("disabled", false);
$("#onlyAttempts").prop("disabled", false);
$(".selectpicker").selectpicker('refresh');
$(".selectpicker").selectpicker('selectAll');
......@@ -442,200 +450,251 @@ $(document).ready(function () {
// Function to check if DataTable is empty and enable/disable Export Button
function toggleExportButton() {
exportButton.prop("disabled", studentResultTable.rows().count() === 0);
exportButton.prop("disabled", studentResultTable.rows().count() === 0);
}
// Call the function whenever the DataTable is updated
studentResultTable.on('draw', toggleExportButton);
exportButton.click(function (event) {
event.preventDefault();
let selectedQuestions = [];
// Get selected questions and their IDs
questionsDropdown.find('option:selected').each(function () {
selectedQuestions.push({
id: $(this).val(),
question: $(this).text(),
type: $(this).data('qtype'),
});
event.preventDefault();
let selectedQuestions = [];
// Get selected questions and their IDs
questionsDropdown.find('option:selected').each(function () {
selectedQuestions.push({
id: $(this).val(),
question: $(this).text(),
type: $(this).data('qtype'),
});
let studentData = [];
// Collect data from the DataTable
studentResultTable.rows().every(function () {
let rowData = this.data();
studentData.push({
student_id: rowData.student_id,
forename: rowData.forename,
surname: rowData.surname,
score: rowData.score,
time_taken: rowData.time_taken,
attempt_date: rowData.attempt_date,
answers: {} // Initialize answers as an empty object
});
});
let studentData = [];
// Collect data from the DataTable
studentResultTable.rows().every(function () {
let rowData = this.data();
studentData.push({
student_id: rowData.student_id,
forename: rowData.forename,
surname: rowData.surname,
score: rowData.score,
time_taken: rowData.time_taken,
attempt_date: rowData.attempt_date,
answers: {} // Initialize answers as an empty object
});
let testId = testDropdown.val();
// Fetch test details
$.ajax({
url: "/getTestDetails",
type: "POST",
contentType: "application/json",
data: JSON.stringify({ test_id: testId })
}).done(function (response) {
let ajaxCalls = [];
response.students.forEach(studentAnswer => {
let attempt = response.test_attempts.find(attempt => attempt.student_id === studentAnswer.id);
if (attempt) {
// Store AJAX calls in an array
let ajaxCall = $.ajax({
url: "/getStudentQuestions",
type: "POST",
contentType: "application/json",
data: JSON.stringify({ test_attempt_id: attempt.id, test_id: testId })
}).done(function (response) {
response.question_attempts.forEach(question => {
let student = studentData.find(s => s.student_id == studentAnswer.id);
if (student) {
let answerKey = `${question.id}_${question.question_type}`;
student.answers[answerKey] = {
student_answer: question.student_answer,
correct_answer: question.correct_answer
};
}
});
});
ajaxCalls.push(ajaxCall);
});
let testId = testDropdown.val();
// Fetch test details
$.ajax({
url: "/getTestDetails",
type: "POST",
contentType: "application/json",
data: JSON.stringify({ test_id: testId })
}).done(function (response) {
let ajaxCalls = [];
response.students.forEach(studentAnswer => {
let attempt = response.test_attempts.find(attempt => attempt.student_id === studentAnswer.id);
if (attempt) {
// Store AJAX calls in an array
let ajaxCall = $.ajax({
url: "/getStudentQuestions",
type: "POST",
contentType: "application/json",
data: JSON.stringify({ test_attempt_id: attempt.id, test_id: testId })
}).done(function (response) {
response.question_attempts.forEach(question => {
let student = studentData.find(s => s.student_id == studentAnswer.id);
if (student) {
let answerKey = `${question.id}_${question.question_type}`;
student.answers[answerKey] = {
student_answer: question.student_answer,
correct_answer: question.correct_answer
};
}
});
});
// Wait for all AJAX calls to finish before calling generateExcel
$.when.apply($, ajaxCalls).done(function () {
generateExcel(studentData, selectedQuestions);
});
ajaxCalls.push(ajaxCall);
}
});
// Wait for all AJAX calls to finish before calling generateExcel
$.when.apply($, ajaxCalls).done(function () {
generateExcel(studentData, selectedQuestions);
});
});
});
function generateExcel(studentData, selectedQuestions) {
let wb = XLSX.utils.book_new();
let wsData = [];
let wb = XLSX.utils.book_new();
let wsData = [];
// Header Row
let headers = ["Student ID", "Forename", "Surname", "Score", "Time Taken", "Attempt Date"];
selectedQuestions.forEach(q => {
headers.push(q.question, "Correct Answer");
});
wsData.push(headers);
// Data Rows
studentData.forEach(student => {
let row = [
student.student_id,
student.forename,
student.surname,
student.score,
student.time_taken,
student.attempt_date
];
selectedQuestions.forEach(q => {
let answerKey = `${q.id}_${q.type}`;
let answerData = student.answers[answerKey] || { student_answer: "N/A", correct_answer: "N/A" };
row.push(answerData.student_answer, answerData.correct_answer);
});
wsData.push(row);
});
// Header Row
let headers = ["Student ID", "Forename", "Surname", "Score", "Time Taken", "Attempt Date"];
selectedQuestions.forEach(q => {
headers.push(q.question, "Correct Answer");
});
wsData.push(headers);
// Data Rows
studentData.forEach(student => {
let row = [
student.student_id,
student.forename,
student.surname,
student.score,
student.time_taken,
student.attempt_date
];
selectedQuestions.forEach(q => {
let answerKey = `${q.id}_${q.type}`;
let answerData = student.answers[answerKey] || { student_answer: "N/A", correct_answer: "N/A" };
row.push(answerData.student_answer, answerData.correct_answer);
});
wsData.push(row);
});
// Create worksheet from wsData
let ws = XLSX.utils.aoa_to_sheet(wsData);
// Create worksheet from wsData
let ws = XLSX.utils.aoa_to_sheet(wsData);
// Apply cell formatting
// Apply cell formatting
// 1. Set column widths (make question columns wider)
let colWidths = [
{ wpx: 100 }, // Student ID
{ wpx: 120 }, // Forename
{ wpx: 120 }, // Surname
{ wpx: 60 }, // Score
{ wpx: 90 }, // Time Taken
{ wpx: 120 }, // Attempt Date
];
// 1. Set column widths (make question columns wider)
let colWidths = [
{ wpx: 100 }, // Student ID
{ wpx: 120 }, // Forename
{ wpx: 120 }, // Surname
{ wpx: 60 }, // Score
{ wpx: 90 }, // Time Taken
{ wpx: 120 }, // Attempt Date
];
selectedQuestions.forEach(() => {
colWidths.push({ wpx: 250 }); // Question column (width for questions)
colWidths.push({ wpx: 150 }); // Correct Answer column (width for correct answers)
});
selectedQuestions.forEach(() => {
colWidths.push({ wpx: 250 }); // Question column (width for questions)
colWidths.push({ wpx: 150 }); // Correct Answer column (width for correct answers)
});
ws['!cols'] = colWidths; // Apply column widths to the worksheet
// 2. Apply borders to all cells and middle alignment
const range = XLSX.utils.decode_range(ws['!ref']); // Get the range of the worksheet
for (let row = range.s.r; row <= range.e.r; row++) {
for (let col = range.s.c; col <= range.e.c; col++) {
let cellAddress = { r: row, c: col };
let cellRef = XLSX.utils.encode_cell(cellAddress);
if (!ws[cellRef]) ws[cellRef] = {}; // Ensure cell exists
// Apply border to the cell
ws[cellRef].s = {
border: {
top: { style: 'thin', color: { rgb: "000000" } },
left: { style: 'thin', color: { rgb: "000000" } },
bottom: { style: 'thin', color: { rgb: "000000" } },
right: { style: 'thin', color: { rgb: "000000" } },
},
alignment: {
vertical: 'center', // Middle align vertically
horizontal: 'center', // Center align horizontally
wrapText: true // Enable text wrapping
}
};
}
}
// 3. Apply text wrapping and reduce row height
for (let row = range.s.r; row <= range.e.r; row++) {
let rowRef = XLSX.utils.encode_row(row); // Get row reference
if (!ws['!rows']) ws['!rows'] = [];
ws['!rows'][row] = { hpx: 24 }; // Reduced row height (adjust as needed)
}
// 4. Add a bottom border to the question and correct answer headers
selectedQuestions.forEach((q, idx) => {
let questionColIndex = 6 + idx * 2; // Column index for "Question"
let correctAnswerColIndex = 7 + idx * 2; // Column index for "Correct Answer"
// Add bottom border to the "Question" and "Correct Answer" header cells
let questionCellRef = XLSX.utils.encode_cell({ r: 0, c: questionColIndex });
let correctAnswerCellRef = XLSX.utils.encode_cell({ r: 0, c: correctAnswerColIndex });
if (!ws[questionCellRef]) ws[questionCellRef] = {}; // Ensure cell exists
if (!ws[correctAnswerCellRef]) ws[correctAnswerCellRef] = {}; // Ensure cell exists
// Apply bottom border to the header cells
ws[questionCellRef].s = {
border: {
bottom: { style: 'thin', color: { rgb: "000000" } }
}
};
ws[correctAnswerCellRef].s = {
border: {
bottom: { style: 'thin', color: { rgb: "000000" } }
}
};
});
// 5. Set the sheet name
XLSX.utils.book_append_sheet(wb, ws, "Student Results");
ws['!cols'] = colWidths; // Apply column widths to the worksheet
// 2. Apply borders to all cells and middle alignment
const range = XLSX.utils.decode_range(ws['!ref']); // Get the range of the worksheet
for (let row = range.s.r; row <= range.e.r; row++) {
for (let col = range.s.c; col <= range.e.c; col++) {
let cellAddress = { r: row, c: col };
let cellRef = XLSX.utils.encode_cell(cellAddress);
if (!ws[cellRef]) ws[cellRef] = {}; // Ensure cell exists
// Apply border to the cell
ws[cellRef].s = {
border: {
top: { style: 'thin', color: { rgb: "000000" } },
left: { style: 'thin', color: { rgb: "000000" } },
bottom: { style: 'thin', color: { rgb: "000000" } },
right: { style: 'thin', color: { rgb: "000000" } },
},
alignment: {
vertical: 'center', // Middle align vertically
horizontal: 'center', // Center align horizontally
wrapText: true // Enable text wrapping
}
};
// Export file
XLSX.writeFile(wb, "Student_Results.xlsx");
}
$("#onlyAttempts").change(function () {
if ($(this).is(":checked")) {
filterStudentsWithAttempts(true);
} else {
filterStudentsWithAttempts(false);
}
}
// 3. Apply text wrapping and reduce row height
for (let row = range.s.r; row <= range.e.r; row++) {
let rowRef = XLSX.utils.encode_row(row); // Get row reference
if (!ws['!rows']) ws['!rows'] = [];
ws['!rows'][row] = { hpx: 24 }; // Reduced row height (adjust as needed)
}
// 4. Add a bottom border to the question and correct answer headers
selectedQuestions.forEach((q, idx) => {
let questionColIndex = 6 + idx * 2; // Column index for "Question"
let correctAnswerColIndex = 7 + idx * 2; // Column index for "Correct Answer"
// Add bottom border to the "Question" and "Correct Answer" header cells
let questionCellRef = XLSX.utils.encode_cell({ r: 0, c: questionColIndex });
let correctAnswerCellRef = XLSX.utils.encode_cell({ r: 0, c: correctAnswerColIndex });
if (!ws[questionCellRef]) ws[questionCellRef] = {}; // Ensure cell exists
if (!ws[correctAnswerCellRef]) ws[correctAnswerCellRef] = {}; // Ensure cell exists
// Apply bottom border to the header cells
ws[questionCellRef].s = {
border: {
bottom: { style: 'thin', color: { rgb: "000000" } }
}
};
ws[correctAnswerCellRef].s = {
border: {
bottom: { style: 'thin', color: { rgb: "000000" } }
}
};
});
// 5. Set the sheet name
XLSX.utils.book_append_sheet(wb, ws, "Student Results");
// Export file
XLSX.writeFile(wb, "Student_Results.xlsx");
}
function filterStudentsWithAttempts(checked) {
let studentsWithAttempt = [];
let testId = testDropdown.val();
$.ajax({
url: "/getTestDetails",
type: "POST",
contentType: "application/json",
data: JSON.stringify({ test_id: testId }),
success: function (response) {
response.students.forEach(student => {
response.test_attempts.forEach(attempt => {
if(student.id === attempt.student_id) {
studentsWithAttempt.push(student.id.toString());
}
})
})
if (checked) {
studentsDropdown.find('option').each(function () {
let studentId = $(this).val();
if (!studentsWithAttempt.includes(studentId.toString())) {
$(this).hide();
$(this).prop('selected', false);
$(".selectpicker").selectpicker('refresh');
}
})
recalculateScores();
}
else {
studentsDropdown.find('option').each(function () {
$(this).show();
$(this).prop('selected', true);
$(".selectpicker").selectpicker('refresh');
})
recalculateScores();
}
}
})
}
});
\ No newline at end of file
......@@ -54,7 +54,8 @@
<!-- Filter Question Difficulty -->
<div class="form-group mb-3 col-md-2">
<label for="difficulty-select" class="form-label">Difficulty</label>
<select id="difficulty" type="text" name="difficulty-select" class="form-control selectpicker" multiple required disabled>
<select id="difficulty" type="text" name="difficulty-select" class="form-control selectpicker" multiple required
disabled>
<option value="Difficulty.Easy">Easy</option>
<option value="Difficulty.Intermediate">Intermediate</option>
<option value="Difficulty.Hard">Hard</option>
......@@ -64,7 +65,8 @@
<!-- Filter Question Learning Objective -->
<div class="form-group mb-3 col-md-2">
<label for="objective-select" class="form-label">Learning Objective</label>
<select id="objective" type="text" name="objective-select" class="form-control selectpicker" multiple required disabled>
<select id="objective" type="text" name="objective-select" class="form-control selectpicker" multiple required
disabled>
<option value="LearningObjective.Remember">Remember</option>
<option value="LearningObjective.Understand">Understand</option>
<option value="LearningObjective.Apply">Apply</option>
......@@ -88,13 +90,20 @@
<h3 id="timetaken">N/A</h3>
</div>
<!-- Choose Students -->
<div class="form-group mb-3 col-md-3">
<label for="s-select" class="form-label">Students</label>
<!-- Dynamically update for questions that are included / for what test is selected -->
<select id="students" type="text" name="s-select" class="form-control selectpicker" multiple required
data-live-search="true" disabled>
</select>
<!-- Choose Students -->
<div class="form-group mb-3 col-md-3">
<label for="s-select" class="form-label">Students</label>
<!-- Dynamically update for questions that are included / for what test is selected -->
<select id="students" type="text" name="s-select" class="form-control selectpicker" multiple required
data-live-search="true" disabled>
</select>
</div>
<div class="form-group mb-3 col-md-3" style="padding-left:15px">
<label for="f-check" class="form-label">Only Show Students with Attempts</label>
<div class="form-check form-check-inline" style="padding-left:100px">
<input id="onlyAttempts" class="form-check-input" type="checkbox" id="inlineCheckbox1" value="option1" disabled>
</div>
</div>
<!-- Show Students -->
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment